90%的C程序员都踩过这些坑,第5个连老手都翻车

📅 2026/7/23 4:39:46 👁️ 阅读次数 📝 编程学习
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");}

如果colorRED,输出会是:红色、绿色、蓝色全部打印

因为 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 个是最常踩的。它们的共同特点是:

编译不报错,运行可能不报错,但结果一定不对。

记住几条铁律:

  1. 赋值和比较分开,常量放左边
  2. 数组循环用<,别用<=
  3. free之后立刻NULL
  4. 字符串永远多留 1 字节
  5. switch每个case都加break
  6. 数组传参必须带长度
  7. malloc必须检查返回值
  8. 位运算加括号,别背优先级

C语言不会保护你,但你可以保护自己。