90%的C程序员都踩过这些坑,第5个连老手都翻车
90%的C程序员都踩过这些坑,第5个连老手都翻车
你有没有过这种经历:代码编译通过,没有一行报错,运行结果却完全不对。你反复检查逻辑,最后发现——问题出在一个你压根没注意到的"陷阱"上。
C语言就是这样。它给你最大的自由,也给你最多的坑。编译器不会帮你拦住大部分错误,它只管"语法对不对",不管"逻辑对不对"。
今天盘了8个最容易踩的坑,每个都附错误代码和正确写法。不管你是刚入门还是写了几年,看完至少能少掉一半头发。
坑1:一个等号,让整个循环变成死循环
错误写法:把
==写成了=
// 这行代码不会报任何错!if(a=5){printf("a等于5\n");}a = 5是赋值,赋值表达式的值就是 5,5 非零,条件永远为真。写在while里?恭喜,死循环。
正确写法:常量放左边,语法级防错
if(a==5){printf("a等于5\n");}// 更好的写法:把常量写左边if(5==a){// 万一少写一个等号 → 编译器直接报错printf("a等于5\n");}💡老手的习惯:if (5 == a)而不是if (a == 5)。这样万一漏写一个等号变成if (5 = a),编译器直接报"不能给常量赋值"。
坑2:数组越界——C语言不会拦你,但内存会记仇
错误写法:循环条件用了
<=
intarr[5]={1,2,3,4,5};for(inti=0;i<=5;i++){printf("%d ",arr[i]);// arr[5] 越界了!}C 语言不做任何数组边界检查。arr[5]访问的是数组后面的未知内存,轻则打印乱码,重则修改函数返回地址,程序直接崩溃。
更可怕的是:有时候越界访问不报错,数据悄悄被改了,等你排查的时候已经面目全非。
正确写法:用
<而不是<=
for(inti=0;i<5;i++){printf("%d ",arr[i]);}💡记住:数组长度是 N,下标范围是0 ~ N-1,循环条件永远是i < N。
坑3:野指针——free之后别再碰它
错误写法:释放后继续使用
int*p=(int*)malloc(sizeof(int));*p=10;free(p);*p=20;// 野指针!这块内存已经不属于你了free(p)之后,p 指向的内存已经被系统回收。你再去读写,就是"进了别人家翻东西"——行为完全未定义。可能崩溃,可能数据错乱,也可能看起来正常,埋下定时炸弹。
正确写法:释放后立刻置空
free(p);p=NULL;// 置空后即使误用也会直接报错,而不是偷偷搞事💡铁律:free之后立刻p = NULL,这个习惯能救你无数次。
坑4:字符串那个看不见的 \0,坑了无数人
错误写法:数组只留了5字节
charstr[5];strcpy(str,"hello");// 溢出!"hello"看起来是 5 个字符,但在 C 语言里字符串必须以\0结尾,实际占6 字节。溢出的那个\0会覆盖相邻内存,这就是经典的缓冲区溢出漏洞——大量安全攻击的根源。
正确写法:预留结束符的位置
// 方法一:数组多开1字节charstr[6];strcpy(str,"hello");// 方法二:用 strncpy 更安全charstr[6];strncpy(str,"hello",sizeof(str)-1);str[sizeof(str)-1]='\0';💡记住:字符串永远多留 1 字节给\0,这是 C 语言字符串处理的底线。
坑5:switch没写break,老手也翻车
错误写法:每个 case 都没写 break
switch(color){caseRED:printf("红色\n");caseGREEN:printf("绿色\n");caseBLUE:printf("蓝色\n");}如果color是RED,输出会是:红色、绿色、蓝色全部打印。
因为 C 语言的switch是"穿透"的,不写break就会一直往下执行。这个坑连写了好几年的老手偶尔也会踩到,尤其是加班到凌晨的时候。
正确写法:每个 case 都加 break
switch(color){caseRED:printf("红色\n");break;caseGREEN:printf("绿色\n");break;caseBLUE:printf("蓝色\n");break;}💡例外情况:有时候穿透是故意的(比如多个 case 共用一段逻辑),但必须加注释说明,否则后人会以为是 bug。
坑6:sizeof对指针和数组,结果完全不同
错误写法:在函数里用 sizeof 求数组长度
voidprint_arr(intarr[]){intlen=sizeof(arr)/sizeof(arr[0]);// 错误!结果不是你想要的}数组作为函数参数时,会退化为指针。sizeof(arr)在函数内部得到的是指针的大小(4 或 8 字节),不是数组的总大小。
这个坑非常隐蔽:编译不报错,只是算出来的长度完全不对。
正确写法:数组长度必须在传参时一并传入
voidprint_arr(intarr[],intlen){for(inti=0;i<len;i++){printf("%d ",arr[i]);}}// 调用时intdata[]={1,2,3,4,5};intlen=sizeof(data)/sizeof(data[0]);// 这里算才是对的print_arr(data,len);💡记住:数组传参 = 传指针。想知道长度,必须显式传入。
坑7:malloc不检查返回值,等于给自己埋雷
错误写法:不检查 malloc 是否成功
int*p=(int*)malloc(1000000000*sizeof(int));*p=10;// 如果 malloc 失败返回 NULL,直接崩溃当内存分配失败时,malloc返回NULL。如果你不检查就解引用,就是空指针访问,程序直接段错误。
这种 bug 在开发环境(内存充足)下很难复现,到了生产环境才爆——最难查的那种。
正确写法:每次 malloc 都检查返回值
int*p=(int*)malloc(1000000000*sizeof(int));if(p==NULL){perror("内存分配失败");return-1;}*p=10;💡铁律:malloc之后必须先检查NULL,再使用。没有例外。
坑8:运算符优先级——你以为的顺序,不是实际的顺序
错误写法:想判断 flag 的某一位是否为 1
if(flags&FLAG!=0)// 实际执行的是 flags & (FLAG != 0)!=的优先级高于&,所以实际先算FLAG != 0(结果为 1),再用flags & 1。和你的本意完全不同。
C 语言有15 个优先级层次,没人能全记住。
正确写法:加括号,别指望记忆
if((flags&FLAG)!=0)💡原则:遇到位运算、逻辑运算混合的表达式,永远加括号。这不是水平问题,是态度问题。
写在最后
C 语言的坑远不止这 8 个,但这 8 个是最常踩的。它们的共同特点是:
编译不报错,运行可能不报错,但结果一定不对。
记住几条铁律:
- 赋值和比较分开,常量放左边
- 数组循环用
<,别用<= free之后立刻NULL- 字符串永远多留 1 字节
switch每个case都加break- 数组传参必须带长度
malloc必须检查返回值- 位运算加括号,别背优先级
C语言不会保护你,但你可以保护自己。
