大家好,我是良旭。学计算机专业的朋友在学校肯定学过C语言。它是许多高级语言的鼻祖。深入学习这门语言,会让你对计算机原理、操作系统、内存管理等底层知识有更深入的了解。学习这门语言。然而,即使是最有经验的程序员也能写出各种错误。本文将盘点在学习或使用C语言过程中非常容易出现的5个bug,以及如何避免这些bug。本文主要针对新手,老手可以无视(其实很多老手还是会犯这些低??级错误)~1.变量未初始化。当程序启动时,系统会自动为其分配一块内存,程序可以用它来存储数据。所以如果你定义了一个变量,如果它没有被初始化,它的值可能是任意的。但这不是绝对的。有些环境会在程序启动时自动“清除”内存,因此每个变量的默认值为零。考虑到可移植性,最好对变量进行初始化,这是一个合格的软件工程师应该养成的好习惯。让我们看一下以下使用多个变量和两个数组的示例程序:#include#includeintmain(){inti,j,k;intnumbers[5];int*array;puts("这些变量没有初始化:");printf("i=%d\n",i);printf("j=%d\n",j);printf("k=%d\n",k);puts("这个数组没有初始化:");for(i=0;i<5;i++){printf("numbers[%d]=%d\n",i,numbers[i]);}puts("mallocanarray...");array=malloc(sizeof(int)*5);if(array){puts("Thismalloc'edarrayisnotinitialized:");for(i=0;i<5;i++){printf("array[%d]=%d\n",i,array[i]);}free(array);}/*done*/puts("Ok");return0;}本程序不初始化变量,所以变量的值可能是随机的,不一定为零。在我的电脑上,其运行结果如下:Thesevariablesarenotinitialized:i=0j=0k=32766Thisarrayisnotinitialized:numbers[0]=0numbers[1]=0numbers[2]=4199024numbers[3]=0numbers[4]=0mallocanarray...Thismalloc'edarrayisnotinitialized:array[0]=0array[1]=0array[2]=0array[3]=0array[4]=0Ok从结果可以看出,i和j的值是恰好为0,但k的值为32766。在数字数组中,大多数元素也恰好为零,除了第三个(4199024)。在不同的操作系统上编译同一个程序可能会产生不同的结果。所以不要认为你的结果是正确的、唯一的,一定要考虑便携性。例如,下面是同一程序在FreeDOS上运行的结果:Thesevariablesarenotinitialized:i=0j=1074k=3120Thisarrayisnotinitialized:numbers[0]=3106numbers[1]=1224numbers[2]=784numbers[3]=2926numbers[4]=1224mallocanarray...Thismalloc'edarrayisnotinitialized:array[0]=3136array[1]=3136array[2]=14499array[3]=-5886array[4]=219Ok可以看出运行结果几乎完全不同从上面。因此,初始化变量会为你省去很多不必要的麻烦,也方便以后的调试。2、数组越界在计算机世界中,从0开始计数,但总有人有意无意地忘记了这一点。比如一个数组的长度是10,如果要获取最后一个元素的值,总会有人用array[10]。。。别问了,我之前写过。。。新手朋友make这种低级错误很多。让我们看看当数组越界时会发生什么。#include#includeintmain(){inti;intnumbers[5];int*array;/*test1*/puts("Thisarrayhasfiveelements(0to4)");/*initalizethearray*/for(i=0;i<5;i++){numbers[i]=i;}/*糟糕,这超出了数组范围:*/for(i=0;i<10;i++){printf("numbers[%d]=%d\n",i,numbers[i]);}/*test2*/puts("mallocanarray...");array=malloc(sizeof(int)*5);if(array){puts("Thismalloc'edarrayalsohasfiveelements(0to4)");/*initalizethearray*/for(i=0;i<5;i++){array[i]=i;}/*oops,thisgoesbeyondthearraybounds:*/for(i=0;i<10;i++){printf("array[%d]=%d\n",i,array[i]);}free(array);}/*完成*/puts(“确定”);返回0;}请注意,程序初始化数组numbers所有元素(0~4)的值,但是越界读取元素0~9的值。可以看出前五个值是正确的,但是之后,就没人知道这些值会是什么了:Thisarrayhasfiveelements(0to4)numbers[0]=0numbers[1]=1numbers[2]=2numbers[3]=3numbers[4]=4numbers[5]=0numbers[6]=4198512numbers[7]=0numbers[8]=1326609712numbers[9]=32764mallocanarray...Thismalloc'edarrayalsohasfiveelements(0to4)array[0]=0array[1]=1array[2]=2array[3]=3array[4]=4array[5]=0array[6]=133441array[7]=0array[8]=0array[9]=0ok所以写的时候大家一定要知道code数组的边界。读这样的数据没问题。如果你写这些内存,你会直接核心转储!3、字符串溢出在C语言中,字符串是一组char值,也可以看成是一个数组。因此,您还需要避免超出范围的字符串。如果超过,则称为字符串溢出。要测试字符串溢出,一种简单的方法是使用gets函数读取数据。gets函数是非常危险的,因为它不知道它接收到的字符串中可以存储多少数据,它只是天真地从用户那里读取数据。如果用户输入的字符串很短,那很好,但如果用户输入的值比接收到的字符串长,那将是灾难性的。让我们来演示一下这个现象:#include#includeintmain(){charname[10];/*Suchas"Beijing"*/intvar1=1,var2=2;/*showinitialvalues*/printf("var1=%d;var2=%d\n",var1,var2);/*这很糟糕..请不要使用gets*/puts("Wheredoyoulive?");gets(name);/*showendingvalues*/printf("<%s>islength%d\n",name,strlen(name));printf("var1=%d;var2=%d\n",var1,var2);/*done*/puts("Ok");return0;}这段代码中,接收数组的长度为10,所以如果输入数据的长度小于10,程序也能正常运行。例如,输入长度为7的城市北京:var1=1;var2=2Wheredoyoulive?Beijingislength7var1=1;var2=2Ok威尔士小镇Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch是世界上名字最长的城市。这个字符串有58个字符。远远超出了名称变量中可以保留的10个字符。如果输入这个字符串,其结果是程序运行内存的其它位置,比如var1和var2,都有可能被波及:var1=1;var2=2Wheredoyoulive?Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogochislength58var1=2036821625;var2=2003266668OkSegmentationfault(coredumped)在在中止之前,程序会用长字符串覆盖内存的其他部分。注意var1和var2不再是它们的起始值1和2。所以我们需要使用更安全的方式来读取用户数据。例如,一个不错的选择是getline函数,它分配足够大的内存来存储用户输入,因此用户不会因输入太长的字符串而意外溢出。4.重复释放内存良好的C编程规则之一是,如果分配了内存,就必须释放它。我们可以使用malloc函数为数组和字符串申请内存,系统会开辟一块内存,返回一个指向内存起始地址的指针。内存用完后,一定要记得使用free函数释放内存,然后系统将内存标记为未使用。但是,在这个过程中,你只能调用一次free函数。如果您第二次调用free函数,它将导致意外行为并可能破坏您的程序。让我们举一个简单的例子:#include#includeintmain(){int*array;puts("mallocanarray...");array=malloc(sizeof(int)*5);if(array){puts("mallocsucceeded");puts("Freethearray...");free(array);}puts("Freethearray...");free(array);puts("Ok");}运行这个程序会在第二次调用free函数时出现coredump错误:mallocanarray...mallocsucceededFreethearray...Freethearray...free():doublefreedetectedintcache2Aborted(coredumped)那么如何避免多次调用free函数?最简单的方法之一是将malloc和free语句放在一个函数中。如果你把malloc放在一个函数里,free放在另一个函数里,那么在使用过程中,如果逻辑设计不当,free可能会被调用多次。5.使用无效的文件指针文件是操作系统中一种非常常见的存储数据的方式。例如,您可以将程序的配置信息存储在名为config.dat的文件中。程序运行时,可以调用该文件读取配置信息。因此,从文件中读取数据的能力对所有程序员来说都很重要。但是,如果您要读取的文件不存在怎么办?在C语言中,读取一个文件,首先使用fopen函数打开文件,然后函数返回一个指向文件的流指针。如果您尝试读取的文件不存在或您的程序无法读取它,则fopen函数返回NULL。在这种情况下,我们仍然对其进行操作,会发生什么?我们一起来看看:#includeintmain(){FILE*pfile;intch;puts("OpentheFILE.TXTfile...");pfile=fopen("FILE.TXT","r");/*你应该检查文件指针是否有效,但我们跳过了它*/puts("NowdisplaythecontentsofFILE.TXT...");while((ch=fgetc(pfile))!=EOF){printf("<%c>",ch);}fclose(pfile);/*done*/puts("Ok");return0;}当你运行这个程序,如果文件FILE.TXT不存在,那么pfile将返回NULL。在这种情况下,如果我们也写入pfile,它会立即导致核心转储:OpentheFILE.TXTfile...NowdisplaythecontentsofFILE.TXT...Segmentationfault(coredumped)所以,我们总是检查文件指针是否有效。例如调用fopen打开文件后,使用if(pfile!=NULL)保证指针可用。总结无论多么有经验的程序员,都可能会出错,所以我们在写代码的时候一定要严谨严谨。但是,如果您养成一些良好的习惯并添加一些额外的代码来检查这五种类型的错误,则可以避免严重的C编程错误。