C Basic Notes
编程习惯
Macro(宏)
括号
尽量添加足够的括号,减少宏定义的二义性
特殊用法
#
: 字符串化##
: 强制连接符do { ... } while (0)
: 防止语法错误
头文件
缺少标准库头文件
缺少函数原型
链接成功 - 链接器自动装载库函数,不影响程序执行 只警告,不报错
覆盖标准库函数原型
- 定义过多参数原型,调用时传入过多参数,函数正确执行(无视多余参数)
- 定义缺少参数原型,调用时传入不完整参数,函数错误执行,误把 0xc(%ebp),0x10(%ebp),…等更多内存单元当作函数参数
缺少宏定义
链接失败 - 宏定义会被识别为函数,但链接器查找不到相应库函数
防止重复包括头文件
#ifndef _FILENAME_H_
#define _FILENAME_H_
...
...
...
#endif
头文件不申请内存单元
除了全局共享静态变量外, 头文件中的定义不允许申请实际的内存单元
检查
边界检查
- 空/满栈检查
- 参数合法性检查 e.g elemSize > 0 检查
指针检查
- Alloctor 失败,需添加 NULL 检查:
- assert
- exit
类型转换
机器码转换
- 有符号类型转换: 进行符号扩展
- 无符号类型转换: 进行零扩展
Pointer Tips and Best Practice
Error Prone Pointers
int i = 37;
float f = *(float *)&i;
float f = 7.0;
short s = *(short *)&f;
- 悬挂指针
- 未初始化
- 改写未知区域
- 下标越界
- 内存上溢 e.g
gets(string);
- 指针相关运算符优先级与结合性
- 返回局部变量的地址
- 重复释放内存空间
- 内存泄漏 e.g 未释放空间/未释放部分深度空间(多维数组)
- 不能引用 void 指针指向的内存单元
Debugging Malloc
处理 void 指针
Tips: 中途运用强制类型转换,使得 void 指针可以执行指针加减运算
void *target = (char *)void_pointer + ...;
利用 void 指针实现 Generic
通用型 Swap 函数
void swap(void *vp1, void *vp2, int size) {
char buffer[size];
memcpy(buffer, vp1, size);
memcpy(vp1, vp2, size);
memcpy(vp2, buffer, size);
}
通用型 Search Function
实现
void *lsearch(void *key, void *base, int n, int elemSize,
int (*cmp_fn)(void *, void *)) {
for (int i = 0;i < n;i++) {
void * elemAddr = (char *)base + i * elemSize;
if (cmp_fn(key, elemAddr) == 0) {
return elemAddr;
}
}
return NULL;
}
int 实例
int IntCmp(void *elem1, void *elem2) {
int *ip1 = (int *)elem1;
int *ip2 = (int *)elem2;
return *ip1 - *ip2;
}
int array[] = {4, 2, 3, 7, 11, 6},
size = 6,
target = 7;
// 应进行强制类型转换
int * found = (int *)lsearch(&target, array, 6, sizeof(int), IntCmp);
if (found == NULL) {
printf("Not Found");
} else {
printf("Found");
}