Wiki LogoWiki - The Power of Many

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 program

2.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 指令集:

指令集寄存器宽度典型操作
SSE128 位4 × float, 2 × double
AVX256 位8 × float, 4 × double
AVX-512512 位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
- 浮点: XMM0

5.3 Windows x64 ABI

参数传递:
- 整数/指针: RCX, RDX, R8, R9
- 浮点: XMM0-XMM3
- 更多参数: 栈 (影子空间)

返回值:
- RAX 或 XMM0

5.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. 思考题

  1. 可变参数如何获取参数类型?
  2. 尾递归优化的条件是什么?
  3. inline 和宏有什么区别?
  4. 不同平台的调用约定有什么影响?
  5. 为什么 noreturn 属性有用?

8. 本周小结

  • 可变参数: stdarg.h, va_list.
  • 递归: 基本递归, 尾递归.
  • 函数属性: noreturn, const, format.
  • 内联函数: inline, static inline.
  • 调用约定: 参数传递规则.

理解高级函数特性, 可以写出更灵活、更高效的 C 代码.

On this page