Week 13: 高级函数特性
掌握可变参数函数、递归、函数属性与调用约定.
1. 可变参数函数
1.1 stdarg.h
#include <stdarg.h>
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main(void) {
printf("%d\n", sum(3, 10, 20, 30)); // 60
printf("%d\n", sum(5, 1, 2, 3, 4, 5)); // 15
return 0;
}1.2 va_list 工作原理
// va_list: 指向参数栈的指针
// va_start: 初始化, 定位到第一个可变参数
// va_arg: 获取下一个参数, 并移动指针
// va_end: 清理1.3 类型安全问题
// 可变参数没有类型检查
int bad_sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
// 如果传入了非 int 类型, 结果未定义
total += va_arg(args, int);
}
va_end(args);
return total;
}
// 错误调用: bad_sum(2, 3.14, "hello");1.4 实现简易 printf
void my_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
while (*fmt) {
if (*fmt == '%') {
fmt++;
switch (*fmt) {
case 'd':
printf("%d", va_arg(args, int));
break;
case 'f':
printf("%f", va_arg(args, double));
break;
case 's':
printf("%s", va_arg(args, char *));
break;
case '%':
putchar('%');
break;
}
} else {
putchar(*fmt);
}
fmt++;
}
va_end(args);
}1.5 va_copy
void process_twice(const char *fmt, ...) {
va_list args1, args2;
va_start(args1, fmt);
va_copy(args2, args1); // 复制 va_list
vprintf(fmt, args1);
vprintf(fmt, args2);
va_end(args1);
va_end(args2);
}2. 递归
2.1 基本递归
// 阶乘
unsigned long factorial(unsigned int n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
// 斐波那契
unsigned long fib(unsigned int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}2.2 尾递归
// 非尾递归
unsigned long factorial(unsigned int n) {
if (n == 0) return 1;
return n * factorial(n - 1); // 返回前还有乘法
}
// 尾递归
unsigned long factorial_tail(unsigned int n, unsigned long acc) {
if (n == 0) return acc;
return factorial_tail(n - 1, n * acc); // 返回前无其他操作
}
unsigned long factorial(unsigned int n) {
return factorial_tail(n, 1);
}2.3 尾递归优化
# GCC 开启优化
gcc -O2 program.c -o program2.4 避免栈溢出
// 递归转迭代
unsigned long factorial_iter(unsigned int n) {
unsigned long result = 1;
for (unsigned int i = 2; i <= n; i++) {
result *= i;
}
return result;
}3. 函数属性 (GCC)
3.1 常用属性
// noreturn: 函数不返回
void die(const char *msg) __attribute__((noreturn));
// const: 纯函数, 无副作用, 仅依赖参数
int square(int x) __attribute__((const));
// pure: 无副作用, 但可能读全局变量
int get_value(void) __attribute__((pure));
// deprecated: 已弃用
int old_func(void) __attribute__((deprecated("use new_func")));
// unused: 忽略未使用警告
void debug_func(int x __attribute__((unused)));
// always_inline: 强制内联
inline void fast_func(void) __attribute__((always_inline));
// noinline: 禁止内联
void slow_func(void) __attribute__((noinline));3.2 格式检查
// 参数 1 是格式字符串, 参数 2 开始是可变参数
void my_printf(const char *fmt, ...)
__attribute__((format(printf, 1, 2)));
// 使用
my_printf("%d", "hello"); // 编译器警告: 类型不匹配3.3 构造和析构
// 在 main 之前执行
void init(void) __attribute__((constructor));
// 在 main 之后执行
void cleanup(void) __attribute__((destructor));
void init(void) {
printf("Initializing...\n");
}
void cleanup(void) {
printf("Cleaning up...\n");
}
int main(void) {
printf("Main\n");
return 0;
}
// 输出:
// Initializing...
// Main
// Cleaning up...4. 内联函数
4.1 inline 关键字
inline int max(int a, int b) {
return a > b ? a : b;
}4.2 inline 的语义
inline是建议, 不是强制- 编译器可能忽略
- 使用
-O2时编译器会自动内联小函数
4.3 static inline
// 在头文件中
static inline int min(int a, int b) {
return a < b ? a : b;
}4.4 extern inline (C99)
// 头文件
inline int square(int x) {
return x * x;
}
// 一个 .c 文件提供外部定义
extern inline int square(int x);4.5 SIMD Intrinsics
SIMD (Single Instruction, Multiple Data) 通过单条指令处理多个数据:
#include <immintrin.h> // Intel SSE/AVX
// 一次计算 4 个 float 相加
void vector_add_sse(float *a, float *b, float *c, size_t n) {
for (size_t i = 0; i < n; i += 4) {
__m128 va = _mm_loadu_ps(&a[i]); // 加载 4 个 float
__m128 vb = _mm_loadu_ps(&b[i]);
__m128 vc = _mm_add_ps(va, vb); // 并行加法
_mm_storeu_ps(&c[i], vc); // 存储结果
}
}
// AVX: 一次处理 8 个 float
void vector_add_avx(float *a, float *b, float *c, size_t n) {
for (size_t i = 0; i < n; i += 8) {
__m256 va = _mm256_loadu_ps(&a[i]);
__m256 vb = _mm256_loadu_ps(&b[i]);
__m256 vc = _mm256_add_ps(va, vb);
_mm256_storeu_ps(&c[i], vc);
}
}编译:
gcc -msse4.2 -mavx2 -O3 program.c -o program常用 Intel SIMD 指令集:
| 指令集 | 寄存器宽度 | 典型操作 |
|---|---|---|
| SSE | 128 位 | 4 × float, 2 × double |
| AVX | 256 位 | 8 × float, 4 × double |
| AVX-512 | 512 位 | 16 × float, 8 × double |
ARM NEON (嵌入式/移动端):
#include <arm_neon.h>
void vector_add_neon(float *a, float *b, float *c, size_t n) {
for (size_t i = 0; i < n; i += 4) {
float32x4_t va = vld1q_f32(&a[i]);
float32x4_t vb = vld1q_f32(&b[i]);
float32x4_t vc = vaddq_f32(va, vb);
vst1q_f32(&c[i], vc);
}
}SIMD 编程要点:
- 数据对齐 (16/32/64 字节) 提高性能
- 处理剩余元素 (标量回退)
- 使用
restrict帮助编译器优化
5. 调用约定
5.1 概念
调用约定定义:
- 参数如何传递 (寄存器/栈)
- 返回值如何传递
- 谁清理栈 (调用者/被调用者)
5.2 x86-64 System V ABI
参数传递:
- 整数/指针: RDI, RSI, RDX, RCX, R8, R9
- 浮点: XMM0-XMM7
- 更多参数: 栈
返回值:
- 整数/指针: RAX
- 浮点: XMM05.3 Windows x64 ABI
参数传递:
- 整数/指针: RCX, RDX, R8, R9
- 浮点: XMM0-XMM3
- 更多参数: 栈 (影子空间)
返回值:
- RAX 或 XMM05.4 指定调用约定 (Windows)
// __cdecl: C 默认, 调用者清理栈
int __cdecl func1(int a, int b);
// __stdcall: Windows API, 被调用者清理栈
int __stdcall func2(int a, int b);
// __fastcall: 寄存器传参
int __fastcall func3(int a, int b);6. 练习
6.1 实现 sprintf
实现一个简化版的 sprintf.
6.2 汉诺塔
使用递归解决汉诺塔问题.
6.3 函数属性
使用 constructor/destructor 实现自动初始化.
7. 思考题
- 可变参数如何获取参数类型?
- 尾递归优化的条件是什么?
- inline 和宏有什么区别?
- 不同平台的调用约定有什么影响?
- 为什么 noreturn 属性有用?
8. 本周小结
- 可变参数: stdarg.h, va_list.
- 递归: 基本递归, 尾递归.
- 函数属性: noreturn, const, format.
- 内联函数: inline, static inline.
- 调用约定: 参数传递规则.
理解高级函数特性, 可以写出更灵活、更高效的 C 代码.