c语言算是非常古老了,像瑞士军刀灵活却也很容易伤到自己,即使是多年的老杆子,以致于市面上都有一本经典的C的书叫《C陷阱与缺陷》的书。
这个文章总结下c中常见的陷阱,可能在日常工作或面试题目中遇到。
1. sizeof 陷阱
sizeof 它是一个编译时运算符而非函数,用于判断变量或数据类型的字节大小。
比较常见的用法:
int arr[] = { 1, 2, 3 }; for (int i = 0; i < sizeof(arr) / sizeof(int); i++) { printf("%d,", arr[i]); }
sizeof(arr)的是整个数组的占字节数大小,除int占字节大小就是整个数组的大小了,但是如果不小心这样用了:
void clear(char array[]) { int i; for (i = 0; i < sizeof(array) / sizeof(array[0]); i++) { array[i] = 0x00; } } int main(void) { char arr[20]; clear(arr); }
问题: 这段代码的问题,在于clear中传递的是指针,这时候sizeof(char*) 一般为4,sizeof(array[0]),造成了结果是只对数组的前四个变量赋值为0,其他的没有赋值!
2. 小心无符号类型
先看段代码:
#include <stdio.h> int main() { if (-1L < 1U) { printf("true"); } else { printf("false"); } return 0; }
问题: 结果为false,原因,有符号和无符号比较的时候,将有符号转成无符号再比较。
还有一些看上去很傻的代码: 无符号char 最大255, 永远大于等于0,所以下面循环为死循环:a. unsigned char i; b. unsigned char i; for(i=0;i<256;i++) {… } for(i=10;i>=0;i--) { … }
3. volatile
volatile 标识变量是容易变化的,每次读取的时候不能从寄存器读取,需要从内存重新加载,多用在随时变化的变量,比如两个线程程序中,一个程序通过更改一个volatile类型的flag,另外一个线程判断这个flag为真还是假来决定是否继续执行。用volatile声明的变量编译器不会进行优化。
如果在一个a.h头文件中定义:
volatile unsigned int a;
在b.h 中引用:
extern unsigned int a;
编译器只会告警,但是运行的时候会有问题,隐藏的大Bug。
4. 局部变量
返回局部变量指针:
char * GetData(void) { char buffer[100]; //局部数组 … return buffer; }
说明: 很经典错误,buffer分配在栈上,函数调用结束后会释放。
5. 小心类型自动转换和提升
程序中,short,char,int,枚举,位段变量用在需要int的函数时候,自动转成int类型。
如果int可以完整的表示源类型的所有值,那么该源类型的值就转换为int, 否则转换为unsigned int,这被称为整体提升。——《C专家编程》
unsigned char -> int char ->int比如:
char a = 'a', b = 'b'; printf("%d\n", sizeof(a + b)); printf("%d\n", sizeof(a));说明: 输出的结果为:
4
1
当表达式使用到值的时候,就会进行进行类型提升。
6. free释放注意
free释放的传入的指针,必须是malloc申请的,在程序中,如果发生了计算,偏移了原始指针就会有问题:
#include<stdio.h> int main(int argc, char *argv[]) { char *ptr = (char*)malloc(10); if(NULL == ptr) { printf("\n Malloc failed \n"); return -1; } else if(argc == 1) { printf("\n Usage \n"); } else { memset(ptr, 0, 10); strncpy(ptr, argv[1], 9); while(*ptr != 'z') { if(*ptr == '') break; else ptr++; } if(*ptr == 'z') { printf("\n String contains 'z'\n"); // Do some more processing } free(ptr); } return 0; }
当传入的字符串第一个字符不是z的时候,循环会使ptr发生变化,再free的时候,会奔溃。
7. _exit退出
看下下面代码func为何未调用:
#include<stdio.h> void func(void) { printf("\n Cleanup function called \n"); return; } int main(void) { int i = 0; atexit(func); for(;i<0xffffff;i++); _exit(0); }
说明: _exit不会调用atexit注册的退出函数,需要调用需要return或exit(0)。
8 参数处理顺序
#include<stdio.h> int main(void) { int a = 10, b = 20, c = 30; printf("\n %d..%d..%d \n", a+b+c, (b = b*2), (c = c*2)); return 0; }
说明: 输出为:110..40..60 原因:参数从右到左处理。
来源:https://www.cnblogs.com/seaspring/p/12482883.html