C:习语式高效编码参考
目录
1. 内存管理 {#memory}
// ❌ 不检查malloc返回值
char *buf = malloc(size);
strcpy(buf, src);
// ✅
char *buf = malloc(size);
if (!buf) return -ENOMEM;
memcpy(buf, src, size);
// ❌ 对malloc结果进行强制类型转换(C中不必要,且会隐藏缺少#include的错误)
int *p = (int *)malloc(n * sizeof(int));
// ✅
int *p = malloc(n * sizeof *p);
// ❌ free后未置空指针(长生命周期作用域中悬挂指针风险)
free(ptr);
// ... 后续代码可能使用 ptr
// ✅
free(ptr);
ptr = NULL;
// ❌ 在提前返回路径上忘记释放
char *a = malloc(100);
char *b = malloc(200);
if (!b) return -1; // 泄露 a
// ✅ —— 单一清理标签
char *a = NULL, *b = NULL;
a = malloc(100);
if (!a) goto cleanup;
b = malloc(200);
if (!b) goto cleanup;
// ... 使用 a, b ...
cleanup:
free(b);
free(a);
使用 sizeof *ptr 而不是 sizeof(Type) —— 当类型改变时它仍然正确。
2. 指针与数组 {#pointers}
// ❌ 手动跟踪数组大小
void process(int *arr, int len) { ... }
process(data, 10);
// ✅ —— 将大小与指针一同传递,或使用结构体
typedef struct { int *data; size_t len; } IntSlice;
// ❌ 指针算术运算,但数组下标更清晰
*(arr + i) = value;
// ✅
arr[i] = value;
// ❌ 在生产代码中使用变长数组VLA(栈溢出风险,C11+中为可选特性)
int arr[n];
// ✅
int *arr = malloc(n * sizeof *arr);
if (!arr) return -ENOMEM;
// ... 使用 arr ...
free(arr);
3. 错误处理 {#errors}
// ❌ 使用魔数作为错误返回值
if (do_thing() == -1) { ... }
// ✅ —— 定义或使用具名错误码
#include <errno.h>
if (do_thing() < 0) {
perror("do_thing");
return errno;
}
// ❌ 深度嵌套的错误检查
int r1 = step1();
if (r1 == 0) {
int r2 = step2();
if (r2 == 0) {
int r3 = step3();
// ...
}
}
// ✅ —— 提前返回 / goto清理
if (step1() < 0) goto fail;
if (step2() < 0) goto fail;
if (step3() < 0) goto fail;
return 0;
fail:
cleanup();
return -1;
goto cleanup 是C语言中资源清理的习语式做法 —— 不要因为原则而回避它。
4. 字符串 {#strings}
// ❌ strcpy 无边界检查
strcpy(dest, src);
// ✅
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
// 或者更好:snprintf(dest, sizeof(dest), "%s", src);
// ❌ strcmp 误用
if (str == "hello") { ... } // 比较指针,而不是内容
// ✅
if (strcmp(str, "hello") == 0) { ... }
// ❌ 重复使用 strcat 构建字符串(O(n²))
char result[1024] = "";
for (int i = 0; i < n; i++) {
strcat(result, items[i]);
}
// ✅ —— 跟踪写位置
char result[1024];
int pos = 0;
for (int i = 0; i < n && pos < (int)sizeof(result); i++) {
pos += snprintf(result + pos, sizeof(result) - pos, "%s", items[i]);
}
优先使用 snprintf 而不是 sprintf —— 始终如此。
5. 结构体与枚举 {#structs}
// ❌ 裸结构体,到处需要 `struct` 关键字
struct point { int x, y; };
struct point p = {1, 2};
// ✅
typedef struct { int x, y; } Point;
Point p = {1, 2};
// ❌ 未初始化的结构体
Point p;
use(p.x); // 未定义行为
// ✅
Point p = {0};
// ❌ 魔数整型常量
if (state == 3) { ... }
// ✅
typedef enum { STATE_IDLE, STATE_RUNNING, STATE_DONE } State;
if (state == STATE_DONE) { ... }
6. 预处理器与头文件 {#preprocessor}
// ❌ 宏可以用内联函数替代(无类型安全,双重求值)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
MAX(x++, y) // 如果 x > y,x 会自增两次
// ✅
static inline int max_int(int a, int b) { return a > b ? a : b; }
// ❌ 无包含保护
// my_header.h
struct Foo { int x; };
// ✅
#ifndef MY_HEADER_H
#define MY_HEADER_H
struct Foo { int x; };
#endif
// 或: #pragma once(广泛支持,但非标准)
将宏用于条件编译和常量。使用 static inline 处理逻辑。
7. C语言特有反模式 {#antipatterns}
| 反模式 | 推荐做法 |
|---|---|
sprintf | 带缓冲区大小的 snprintf |
gets | fgets(gets在C11中已移除) |
对malloc结果进行强制类型转换 | 让隐式 void* 转换生效 |
malloc 中的 sizeof(Type) | sizeof *ptr |
| 大/运行时数组使用VLA | 堆分配 |
无上下文参数的 void* 回调 | 在函数指针旁传递 void *ctx |
| 全局可变状态 | 通过结构体指针传递状态 |
使用 assert 处理运行时错误 | 使用恰当的错误返回码 |
只读指针参数缺少 const | const char *str |
| 混用有符号/无符号类型进行比较 | 使用一致的类型,显式转换 |
局限性
- 这些是特定语言的代码规范,不涵盖整体架构决策。
- 过度压缩可能降低可读性;请酌情判断。