1. C语言函数三要素解析
在C语言中,函数是程序的基本构建块,理解函数的三要素是掌握C语言编程的关键。函数三要素包括:函数声明、函数定义和函数调用。这三个部分共同构成了函数的完整生命周期。
1.1 函数声明
函数声明(也称为函数原型)告诉编译器函数的名称、返回类型和参数类型。它就像是一个契约,规定了函数的使用方式。一个典型的函数声明如下:
int add(int a, int b);这里有几个关键点需要注意:
- 返回类型(int)指明了函数执行后返回的数据类型
- 函数名(add)是函数的标识符
- 参数列表(int a, int b)定义了函数接受的输入类型和名称
提示:在C语言中,函数声明可以省略参数名,只保留类型,如
int add(int, int);。这在头文件中很常见。
1.2 函数定义
函数定义是函数的具体实现,包含了函数体(我们将在第2章详细讨论)。它必须与声明在返回类型、函数名和参数类型上完全一致。例如:
int add(int a, int b) { return a + b; }函数定义由以下几部分组成:
- 返回类型
- 函数名
- 参数列表(带参数名)
- 函数体(用大括号{}包围)
- return语句(非void函数必须包含)
1.3 函数调用
函数调用是实际使用函数的地方。调用时,需要提供与声明匹配的实际参数:
int result = add(3, 5);调用过程涉及以下步骤:
- 参数传递(实参传递给形参)
- 函数执行
- 返回值处理
注意:在C语言中,函数调用前必须有声明或定义,否则编译器会报错。这就是为什么我们通常在文件开头或头文件中声明函数。
2. 函数体深度剖析
函数体是函数定义的核心部分,包含了实现函数功能的代码。理解函数体的结构和特性对于编写高质量的C代码至关重要。
2.1 函数体的基本结构
一个完整的函数体通常包含以下部分:
返回类型 函数名(参数列表) { // 1. 局部变量声明 // 2. 执行语句 // 3. 控制结构 // 4. 返回语句(非void函数) }让我们看一个更复杂的例子:
float calculate_average(int scores[], int count) { if (count <= 0) { return 0.0f; // 处理边界情况 } int sum = 0; for (int i = 0; i < count; i++) { sum += scores[i]; } return (float)sum / count; }2.2 局部变量与作用域
函数体内声明的变量称为局部变量,它们的特点包括:
- 只在函数体内可见
- 函数调用时创建,返回时销毁
- 不同函数中的同名局部变量互不干扰
void demo_scope() { int x = 10; // 局部变量x printf("Inside function: %d\n", x); } int main() { int x = 20; // 不同的x demo_scope(); printf("In main: %d\n", x); return 0; }2.3 控制结构与函数流
函数体内可以使用所有C语言的控制结构:
- 条件语句(if-else)
- 循环语句(for, while, do-while)
- 跳转语句(break, continue, return)
- switch语句
一个包含多种控制结构的示例:
int find_max(int arr[], int size) { if (size <= 0) return INT_MIN; int max = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] > max) { max = arr[i]; } } return max; }3. 高级函数特性
掌握了函数的基础知识后,让我们深入探讨一些高级特性,这些特性能让你的C语言编程更加灵活强大。
3.1 递归函数
递归函数是指调用自身的函数。递归通常用于解决可以分解为相似子问题的问题,如阶乘、斐波那契数列等。
int factorial(int n) { if (n <= 1) return 1; // 基线条件 return n * factorial(n - 1); // 递归调用 }递归函数必须满足两个条件:
- 基线条件(停止递归的条件)
- 递归条件(调用自身的条件)
注意:递归虽然简洁,但可能消耗大量栈空间,对于深度递归问题应考虑使用迭代替代。
3.2 函数指针
C语言允许使用指针来引用函数,这为回调函数、函数表等高级特性提供了可能。
#include <stdio.h> void greet_english() { printf("Hello\n"); } void greet_spanish() { printf("Hola\n"); } int main() { void (*greet)() = greet_english; greet(); // 输出 Hello greet = greet_spanish; greet(); // 输出 Hola return 0; }函数指针的典型应用场景:
- 回调机制
- 策略模式实现
- 动态函数调用
3.3 可变参数函数
C语言支持参数数量可变的函数,如printf。我们可以使用stdarg.h中的宏来实现:
#include <stdarg.h> #include <stdio.h> double average(int count, ...) { va_list ap; double sum = 0; va_start(ap, count); for (int i = 0; i < count; i++) { sum += va_arg(ap, double); } va_end(ap); return sum / count; } int main() { printf("Average: %.2f\n", average(3, 1.0, 2.0, 3.0)); return 0; }4. 函数设计最佳实践
编写高质量的函数不仅关乎语法正确,还需要考虑可读性、可维护性和性能。以下是经过实践验证的函数设计原则。
4.1 单一职责原则
每个函数应该只做一件事,并且做好这件事。如果一个函数做了太多事情,考虑将其拆分为多个小函数。
不好的例子:
void process_data(int data[], int size) { // 验证数据 for (int i = 0; i < size; i++) { if (data[i] < 0) data[i] = 0; } // 计算平均值 double sum = 0; for (int i = 0; i < size; i++) { sum += data[i]; } double avg = sum / size; // 输出结果 printf("Average: %.2f\n", avg); }改进后的版本:
void validate_data(int data[], int size) { for (int i = 0; i < size; i++) { if (data[i] < 0) data[i] = 0; } } double calculate_average(int data[], int size) { double sum = 0; for (int i = 0; i < size; i++) { sum += data[i]; } return sum / size; } void process_data(int data[], int size) { validate_data(data, size); double avg = calculate_average(data, size); printf("Average: %.2f\n", avg); }4.2 合理的参数设计
函数参数设计应考虑以下原则:
- 参数数量不宜过多(通常不超过5个)
- 相关参数可以封装为结构体
- 输出参数使用指针明确标识
- 避免使用全局变量传递信息
参数过多的例子:
void draw_rect(int x, int y, int width, int height, int fill_color, int border_color, int border_width, int opacity);改进版本:
typedef struct { int x, y; int width, height; int fill_color; int border_color; int border_width; int opacity; } RectParams; void draw_rect(RectParams params);4.3 错误处理策略
良好的错误处理是健壮函数的关键。常见的错误处理方式包括:
- 返回错误码
- 设置全局错误变量(如errno)
- 使用回调函数处理错误
- 断言(仅用于调试)
返回错误码的示例:
#define SUCCESS 0 #define INVALID_INPUT 1 #define MEMORY_ERROR 2 int process_data(const char* input, char** output) { if (input == NULL || output == NULL) { return INVALID_INPUT; } *output = malloc(strlen(input) + 1); if (*output == NULL) { return MEMORY_ERROR; } strcpy(*output, input); return SUCCESS; }5. 常见问题与调试技巧
即使是有经验的程序员,在函数编写和使用过程中也会遇到各种问题。下面总结了一些常见问题及其解决方案。
5.1 链接错误:未定义的引用
错误现象:
undefined reference to `function_name'可能原因:
- 函数声明了但未定义
- 函数定义在其他源文件中但未正确链接
- 拼写错误导致名称不匹配
解决方案:
- 确保所有使用的函数都有定义
- 检查拼写是否一致(C语言区分大小写)
- 如果是外部函数,确保正确包含头文件并链接了对应的源文件或库
5.2 栈溢出
错误现象:程序崩溃,可能伴随"stack overflow"错误
常见原因:
- 无限递归
- 大型局部数组(如
int big_array[1000000];) - 过多的嵌套函数调用
解决方法:
- 检查递归函数的终止条件
- 将大型数组改为动态分配(使用malloc)
- 减少调用深度或增加栈大小(编译器选项)
5.3 参数传递问题
常见问题:
- 修改参数值但调用方未看到变化
- 传递了错误的指针
- 数组作为参数时的sizeof问题
示例:
void increment(int a) { a++; // 不影响调用方的变量 } void real_increment(int* a) { (*a)++; // 正确修改调用方的变量 } int main() { int x = 5; increment(x); printf("%d\n", x); // 输出5 real_increment(&x); printf("%d\n", x); // 输出6 return 0; }5.4 返回值处理不当
常见错误:
- 忽略函数返回值(特别是错误码)
- 返回局部变量的指针
- 返回类型与声明不匹配
危险示例:
char* get_string() { char str[] = "Hello"; // 局部数组 return str; // 错误:返回局部变量的地址 }安全版本:
const char* get_string() { return "Hello"; // 字符串常量,存储在静态区 } // 或者 char* get_string_dynamic() { char* str = malloc(6); if (str) strcpy(str, "Hello"); return str; // 调用方需要free }6. 性能优化技巧
编写高效函数对于性能敏感的应用至关重要。以下是一些经过验证的优化技巧。
6.1 内联函数
对于短小的函数,使用inline关键字可以避免函数调用的开销:
inline int max(int a, int b) { return a > b ? a : b; }注意事项:
- 内联函数通常定义在头文件中
- 适用于短小、频繁调用的函数
- 过度使用可能导致代码膨胀
6.2 循环优化
将不变的计算移出循环:
优化前:
for (int i = 0; i < strlen(s); i++) { // ... }优化后:
int len = strlen(s); for (int i = 0; i < len; i++) { // ... }6.3 减少函数调用开销
对于性能关键的代码:
- 减少不必要的函数调用
- 将小函数内联
- 使用宏替代简单函数(谨慎使用)
示例:
#define SQUARE(x) ((x)*(x)) // 注意括号! // 比函数调用更快,但缺乏类型检查6.4 内存访问优化
- 顺序访问数组(缓存友好)
- 减少指针解引用
- 使用寄存器变量(register关键字)
缓存友好示例:
// 好的:顺序访问 int sum_array(int arr[], int size) { int sum = 0; for (int i = 0; i < size; i++) { sum += arr[i]; } return sum; } // 不好的:随机访问 int sum_matrix(int mat[][100], int rows) { int sum = 0; for (int j = 0; j < 100; j++) { for (int i = 0; i < rows; i++) { sum += mat[i][j]; // 缓存不友好 } } return sum; }7. 实际应用案例分析
让我们通过几个实际案例来综合运用前面学到的知识,这些案例来自常见的编程场景。
7.1 字符串处理函数实现
标准库中的字符串函数是理解函数设计的绝佳例子。让我们实现几个常用的:
// 类似strlen的实现 size_t my_strlen(const char* str) { const char* p = str; while (*p) p++; return p - str; } // 类似strcpy的实现 char* my_strcpy(char* dest, const char* src) { char* ret = dest; while ((*dest++ = *src++)); return ret; } // 类似strcmp的实现 int my_strcmp(const char* s1, const char* s2) { while (*s1 && (*s1 == *s2)) { s1++; s2++; } return *(unsigned char*)s1 - *(unsigned char*)s2; }这些实现展示了良好的函数设计:
- 清晰的参数(const修饰不应修改的参数)
- 明确的返回值
- 高效的实现
- 遵循标准库的接口约定
7.2 数据结构操作函数
以链表为例,展示如何设计相关函数:
typedef struct Node { int data; struct Node* next; } Node; // 创建新节点 Node* create_node(int data) { Node* node = malloc(sizeof(Node)); if (node) { node->data = data; node->next = NULL; } return node; } // 在链表末尾添加节点 void append_node(Node** head, int data) { Node* new_node = create_node(data); if (*head == NULL) { *head = new_node; return; } Node* current = *head; while (current->next) { current = current->next; } current->next = new_node; } // 释放整个链表 void free_list(Node* head) { while (head) { Node* temp = head; head = head->next; free(temp); } }7.3 数学计算函数
实现一些常用的数学计算函数:
// 计算幂次(快速幂算法) double power(double base, int exponent) { if (exponent == 0) return 1; if (exponent < 0) return 1 / power(base, -exponent); double result = 1; while (exponent) { if (exponent & 1) result *= base; base *= base; exponent >>= 1; } return result; } // 判断素数 int is_prime(int n) { if (n <= 1) return 0; if (n == 2) return 1; if (n % 2 == 0) return 0; for (int i = 3; i * i <= n; i += 2) { if (n % i == 0) return 0; } return 1; } // 计算最大公约数(欧几里得算法) int gcd(int a, int b) { while (b) { int temp = b; b = a % b; a = temp; } return a; }这些数学函数展示了:
- 高效的算法实现
- 边界条件处理
- 清晰的逻辑结构
8. 函数测试与验证
编写函数只是第一步,确保它们按预期工作同样重要。让我们探讨几种测试方法。
8.1 单元测试基础
简单的测试框架可以这样实现:
#include <stdio.h> #define TEST(condition) \ do { \ if (!(condition)) { \ printf("Test failed at line %d: %s\n", __LINE__, #condition); \ return 1; \ } \ } while (0) int test_math_functions() { TEST(power(2, 3) == 8); TEST(power(2, 0) == 1); TEST(power(2, -1) == 0.5); TEST(is_prime(2) == 1); TEST(is_prime(4) == 0); TEST(is_prime(17) == 1); TEST(gcd(12, 18) == 6); TEST(gcd(35, 14) == 7); TEST(gcd(17, 23) == 1); printf("All math tests passed!\n"); return 0; } int main() { if (test_math_functions()) { return 1; } return 0; }8.2 边界条件测试
特别注意测试边界条件:
- 空输入
- 极值
- 非法输入
- 性能边界
示例:
void test_string_functions() { char buffer[100]; // 测试空字符串 TEST(my_strlen("") == 0); my_strcpy(buffer, ""); TEST(my_strlen(buffer) == 0); // 测试边界大小 const char* long_str = "This is a relatively long string for testing purposes"; TEST(my_strlen(long_str) == 52); my_strcpy(buffer, long_str); TEST(my_strcmp(buffer, long_str) == 0); // 测试字符串比较 TEST(my_strcmp("apple", "banana") < 0); TEST(my_strcmp("banana", "apple") > 0); TEST(my_strcmp("same", "same") == 0); }8.3 性能测试
测量函数执行时间:
#include <time.h> void test_performance() { clock_t start, end; double cpu_time_used; start = clock(); for (int i = 0; i < 1000000; i++) { is_prime(2147483647); // 一个大素数 } end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("is_prime took %f seconds for 1,000,000 calls\n", cpu_time_used); }9. 现代C语言函数特性
C语言标准在不断演进,了解现代特性可以让你的代码更安全、更高效。
9.1 C11中的函数特性
- _Generic选择:根据参数类型选择不同实现
- 匿名结构体和联合体作为参数
- 改进的类型安全
_Generic示例:
#define print_value(x) _Generic((x), \ int: print_int, \ double: print_double, \ char*: print_string)(x) void print_int(int i) { printf("int: %d\n", i); } void print_double(double d) { printf("double: %f\n", d); } void print_string(char* s) { printf("string: %s\n", s); } int main() { print_value(42); print_value(3.14); print_value("Hello"); return 0; }9.2 静态分析工具
使用现代工具检查函数问题:
- 未使用的参数
- 可能的空指针解引用
- 内存泄漏
- 缓冲区溢出
示例(使用clang静态分析):
clang --analyze -Xanalyzer -analyzer-output=text program.c9.3 函数属性
GCC/Clang提供的函数属性可以优化或检查函数:
// 提示编译器该函数从不返回 __attribute__((noreturn)) void fatal_error() { fprintf(stderr, "Fatal error occurred\n"); exit(1); } // 提示编译器该函数是纯函数(无副作用) __attribute__((pure)) int square(int x) { return x * x; } // 启用栈保护 __attribute__((stack_protect)) void sensitive_function() { // ... }10. 跨平台函数注意事项
编写可移植的函数需要考虑不同平台的差异。以下是关键注意事项。
10.1 数据类型大小
基本数据类型的大小可能因平台而异:
- 使用stdint.h中的固定宽度类型(int32_t等)
- 对大小敏感的操作使用sizeof检查
#include <stdint.h> void print_sizes() { printf("int: %zu\n", sizeof(int)); printf("long: %zu\n", sizeof(long)); printf("int32_t: %zu\n", sizeof(int32_t)); printf("int64_t: %zu\n", sizeof(int64_t)); }10.2 调用约定
不同平台可能有不同的函数调用约定:
- Windows: __stdcall, __cdecl
- Linux: 默认使用cdecl
- 嵌入式系统可能有特殊约定
跨平台示例:
#ifdef _WIN32 #define API __declspec(dllexport) #else #define API __attribute__((visibility("default"))) #endif API void cross_platform_function();10.3 内联汇编
如果需要内联汇编,必须为不同平台提供不同实现:
#ifdef __x86_64__ static inline uint64_t read_timestamp() { uint64_t ts; __asm__ volatile ("rdtsc" : "=A" (ts)); return ts; } #elif defined(__arm__) static inline uint64_t read_timestamp() { uint32_t val; __asm__ volatile ("mrc p15, 0, %0, c9, c13, 0" : "=r"(val)); return val; } #else #error "Unsupported architecture" #endif10.4 文件路径处理
不同平台的路径分隔符不同:
- Windows: ''
- Unix-like: '/'
可移植的处理方式:
#include <stdio.h> #include <string.h> #ifdef _WIN32 #define PATH_SEPARATOR '\\' #else #define PATH_SEPARATOR '/' #endif void join_path(char* dest, const char* dir, const char* file) { size_t dir_len = strlen(dir); strcpy(dest, dir); // 确保目录以分隔符结尾 if (dir_len > 0 && dest[dir_len-1] != PATH_SEPARATOR) { dest[dir_len] = PATH_SEPARATOR; dir_len++; } strcpy(dest + dir_len, file); }11. 函数与多文件编程
实际项目中,函数通常分布在多个源文件中。良好的组织方式能提高代码的可维护性。
11.1 头文件设计
头文件应包含:
- 函数声明
- 必要的类型定义
- 宏定义
- 文档注释
示例(math_utils.h):
#ifndef MATH_UTILS_H #define MATH_UTILS_H /** * @brief 计算base的exponent次幂 * @param base 底数 * @param exponent 指数 * @return 计算结果 */ double power(double base, int exponent); /** * @brief 判断一个数是否为素数 * @param n 要检查的数 * @return 1表示是素数,0表示不是 */ int is_prime(int n); #endif // MATH_UTILS_H11.2 源文件组织
对应的源文件(math_utils.c):
#include "math_utils.h" #include <stdint.h> double power(double base, int exponent) { // 实现同前 } int is_prime(int n) { // 实现同前 }11.3 静态函数
只在当前文件可见的函数应声明为static:
// 只在math_utils.c中使用的辅助函数 static int is_divisible(int n, int divisor) { return n % divisor == 0; } int is_prime(int n) { if (n <= 1) return 0; if (n == 2) return 1; if (is_divisible(n, 2)) return 0; for (int i = 3; i * i <= n; i += 2) { if (is_divisible(n, i)) return 0; } return 1; }11.4 链接规范
与C++交互时需要使用extern "C":
#ifdef __cplusplus extern "C" { #endif // 函数声明 #ifdef __cplusplus } #endif12. 函数文档与注释
良好的文档和注释是代码可维护性的关键。让我们看看专业级的做法。
12.1 Doxygen风格注释
Doxygen是广泛使用的文档生成工具:
/** * @brief 计算两个整数的最大公约数 * * 使用欧几里得算法计算两个整数的最大公约数。 * * @param a 第一个整数 * @param b 第二个整数 * @return 两个整数的最大公约数 * * @note 对于负数,函数会先取其绝对值再计算 * @warning a和b不能同时为0 */ int gcd(int a, int b);12.2 实现注释
在函数实现中,注释应解释"为什么"而不是"做什么":
int find_prime(int start) { // 从奇数开始检查,偶数(除了2)都不是素数 if (start <= 2) return 2; if (start % 2 == 0) start++; // 逐个检查奇数,直到找到素数 while (!is_prime(start)) { start += 2; } return start; }12.3 调试日志
有时在函数中添加条件调试输出很有用:
#define DEBUG 1 void complex_function(int param) { #if DEBUG fprintf(stderr, "[DEBUG] complex_function called with param=%d\n", param); #endif // 函数实现 #if DEBUG fprintf(stderr, "[DEBUG] complex_function completed\n"); #endif }12.4 版本与变更记录
在头文件中记录函数变更:
/** * @file math_utils.h * @brief 数学工具函数集合 * @version 1.2 * @date 2023-05-15 * * 变更记录: * v1.2 (2023-05-15): 优化power函数的负指数处理 * v1.1 (2023-04-10): 添加gcd函数 * v1.0 (2023-03-01): 初始版本,包含is_prime和power */13. 函数安全编程
编写安全的函数可以防止许多常见漏洞。以下是关键的安全实践。
13.1 缓冲区溢出防护
始终检查缓冲区边界:
// 不安全的版本 void unsafe_copy(char* dest, const char* src) { while (*dest++ = *src++); // 没有边界检查 } // 安全版本 int safe_copy(char* dest, const char* src, size_t dest_size) { if (dest == NULL || src == NULL || dest_size == 0) { return -1; } size_t i; for (i = 0; i < dest_size - 1 && src[i]; i++) { dest[i] = src[i]; } dest[i] = '\0'; return src[i] ? -1 : 0; // 返回0表示完全复制 }13.2 输入验证
永远不要信任外部输入:
int parse_positive_int(const char* str) { if (str == NULL) return -1; // 检查是否只包含数字 for (const char* p = str; *p; p++) { if (*p < '0' || *p > '9') return -1; } // 检查是否溢出 long result = 0; for (const char* p = str; *p; p++) { result = result * 10 + (*p - '0'); if (result > INT_MAX) return -1; } return (int)result; }13.3 错误处理
提供清晰的错误信息而不泄露敏感数据:
typedef enum { AUTH_OK, AUTH_INVALID_USER, AUTH_INVALID_PASSWORD, AUTH_LOCKED } AuthResult; AuthResult authenticate(const char* user, const char* pass) { if (user == NULL || pass == NULL) { return AUTH_INVALID_USER; } // 验证逻辑... return AUTH_OK; } void handle_login() { AuthResult result = authenticate(username, password); switch (result) { case AUTH_OK: printf("Login successful\n"); break; case AUTH_INVALID_USER: printf("Invalid username\n"); break; case AUTH_INVALID_PASSWORD: printf("Invalid password\n"); break; case AUTH_LOCKED: printf("Account locked\n"); break; default: printf("Unknown error\n"); } }13.4 资源清理
确保分配的资源被正确释放:
int process_file(const char* filename) { FILE* file = fopen(filename, "r"); if (file == NULL) return -1; char* buffer = malloc(1024); if (buffer == NULL) { fclose(file); return -1; } // 处理文件内容 free(buffer); fclose(file); return 0; }更安全的版本(使用goto简化错误处理):
int process_file_safe(const char* filename) { FILE* file = NULL; char* buffer = NULL; int result = -1; file = fopen(filename, "r"); if (file == NULL) goto cleanup; buffer = malloc(1024); if (buffer == NULL) goto cleanup; // 处理文件内容 result = 0; // 成功 cleanup: if (buffer) free(buffer); if (file) fclose(file); return result; }14. 函数性能调优
深入理解函数性能特性可以帮助你编写更高效的代码。
14.1 函数调用开销分析
函数调用涉及以下开销:
- 参数传递
- 栈帧设置
- 返回地址保存
- 寄存器保存/恢复
测量调用开销的示例:
#include <time.h> void empty_function() {} void measure_call_overhead() { const int iterations = 100000000; clock_t start = clock(); for (int i = 0; i < iterations; i++) { empty_function(); } double elapsed = (double)(clock() - start) / CLOCKS_PER_SEC; printf("Call overhead: %.2f ns per call\n", elapsed * 1e9 / iterations); }14.2 尾调用优化
尾调用是指函数最后一步是调用另一个函数。某些编译器可以优化为跳转而非调用。
// 可以被优化的尾调用 int factorial_tail(int n, int acc) { if (n <= 1) return acc; return factorial_tail(n - 1, n * acc); } // 调用方式 int factorial(int n) { return factorial_tail(n, 1); }14.3 热点函数优化
使用性能分析工具(如gprof)识别热点函数:
gcc -pg program.c -o program ./program gprof program gmon.out > analysis.txt优化策略:
- 内联小函数
- 减少内存访问
- 使用更高效的算法
- 循环展开
- 使用SIMD指令
14.4 分支预测优化
帮助CPU更好地预测分支:
// 不太友好的分支模式 void process_array(int arr[], int size) { for (int i = 0; i < size; i++) { if (arr[i] < 100) { // 条件随机分布 // 处理小值 } else { // 处理大值 } } } // 优化版本:先排序或分离处理 void process_sorted(int arr[], int size) { // 假设数组已按<100分区 int i; for (i = 0; i < size && arr[i] < 100; i++) { // 处理小值 } for (; i < size; i++) { // 处理大值 } }15. 函数与多线程
在多线程环境中使用函数需要特别注意线程安全问题。
15.1 线程安全函数
线程安全函数的特征:
- 不依赖全局或静态变量
- 只操作参数和局部变量
- 调用线程安全的库函数
// 线程安全的函数 int safe_add(int a, int b) { return a + b; } // 非线程安全的函数 static int counter = 0; int unsafe_counter() { return counter++; // 非原子操作 }15.2 可重入函数
可重入函数是更强的线程安全形式,即使被中断也能安全调用:
// 可重入版本的strtok char* strtok_r(char* str, const char* delim, char** saveptr) { // 实现使用saveptr而非静态变量 }15.3 互斥锁保护
保护非线程安全的函数:
#include <pthread.h> static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER; static int shared_counter = 0; int thread_safe_counter() { pthread_mutex_lock(&counter_mutex); int result = shared_counter++; pthread_mutex_unlock(&counter_mutex); return result; }15.4 线程局部存储
使用线程局部变量避免锁:
#include <threads.h> thread_local int per_thread_counter = 0; void increment_counter() { per_thread_counter++; // 每个线程有自己的副本 }