但是很多同学之前并没有学过太多相关的具体知识,所以经常会犯一些很基础的低级错误,所以今天就提前帮助同学们学妹们解决一些零基础学习C语言的常见错误,希望能帮助同学们少走一些弯路。1.指针没有指向合法的内存。定义了一个指针变量,但是没有为该指针分配内存,即该指针没有指向合法的内存。我不会举明显的例子,但这里有一些更微妙的例子。1.结构体成员指针未初始化structstudent{char*name;intscore;}stu,*pstu;intmain(){strcpy(stu.name,"Jimy");stu.score=99;return0;}很多初学者使mistakes得到这个错误并且不知道发生了什么。这里定义了结构体变量stu,没想到结构体内部的成员char*name在定义结构体变量stu的时候只给指针变量本身分配了4个字节。名字指针没有指向合法地址,此时它的内存只是一些乱码。因此,在调用strcpy函数时,会将字符串“Jimy”复制到乱码指示的内存中,而这块内存的name指针根本无权访问,从而导致错误。解决方案是为名称指针分配一个空间。同样,有人会犯如下错误:intmain(){pstu=(structstudent*)malloc(sizeof(structstudent));strcpy(pstu->name,"Jimy");pstu->score=99;free(pstu);return0;}为指针变量pstu分配内存,但也不为名称指针分配内存。报错和上面第一种情况一样,解决方法一样。这里用了一个malloc,给人一种错觉,内存也分配给了name指针。2.没有为结构体指针分配足够的内存intmain(){pstu=(structstudent*)malloc(sizeof(structstudent*));strcpy(pstu->name,"Jimy");pstu->score=99;free(pstu);return0;}为pstu分配内存时,分配的内存大小不合适。这里sizeof(structstudent)被错误地写成了sizeof(structstudent*)。当然,名字指针也是没有分配内存的。解决办法同上。3.函数入口验证无论何时我们使用指针,我们都必须确保指针是有效的。assert是一个宏,而不是函数,包含在assert.h头文件中。如果后面括号中的值为false,则程序终止,并提示错误;如果后面括号中的值为真,下面的代码会继续运行。该宏只对Debug版本有效,在Release版本中完全由编译器优化,不会影响代码的性能。可能有人会问,既然Release版本是完全由编译器优化的,那么Release版本是不是根本就没有这个参数入口检查呢?那不就和没用一样吗?是的,使用assert宏的地方在Release版本中没有这些检查。但是我们要知道,assert宏只是用来帮助我们调试代码的,它的所有作用都是为了让我们在调试功能的同时尽可能的排除错误,而不是等到Release之后。它本身没有调试功能。还有一点,参数错误不是这个函数的问题,而是调用者传递的实参有问题。assert宏可以帮助我们定位错误,而不是消除错误。2.Thememoryallocatedforthepointeristoosmall为指针分配了内存,但内存大小不够,导致越界错误。char*p1="abcdefg";char*p2=(char*)malloc(sizeof(char)*strlen(p1));strcpy(p2,p1);p1是一个字符串常量,长度为7个字符,但是内存它占用的大小是8个字节。初学者经常忘记字符串常量的结束标记“\0”。在这种情况下,p1的字符串中的最后一个空字符“\0”将不会被复制到p2。解决方案是添加此字符串结束标记:char*p2=(char*)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));这里需要注意的是,只有字符串Constants才有结束标记。例如下面的写法没有结束符:chara[7]={'a','b','c','d','e','f','g'};另外,不要因为char类型的大小是1个字节,所以省略了sizof(char)的写法。这只会降低您的代码的可移植性。3、内存分配成功,但还没有初始化。这个错误往往是由于缺乏初始化的概念或者认为分配内存后内存的值自然为0。未初始化的指针变量可能看起来没有那么严重,但它确实是一个非常严重的问题,而且往往很难找到导致这种错误的原因。也许这种严重的问题很少见,但绝不能掉以轻心。所以在定义一个变量的时候,首先要做的就是初始化。你可以把它初始化成一个有效的值,比如:我检查了他的代码,没发现问题,就单步执行了。在观察这个结构体变量的内存时,发现有几个成员的值是乱码。是乱码之一造成的!因为系统会根据这个结构体中某些特定成员的值在字体库中寻找匹配的字体,当这些值与字体库中某个字体的某些项匹配时,就会调用这个字体。但遗憾的是,正是因为这些乱码,才没有找到匹配的字体!因为系统无法区分哪些数据是乱码,哪些数据是有效数据。只要有数据,系统就理所当然地认为它是有效的。也许这种严重的问题很少见,但绝不能掉以轻心。所以在定义一个变量的时候,首先要做的就是初始化。您可以将其初始化为有效值,例如:inti=10;char*p=(char*)malloc(sizeof(char));但是往往这个时候我们并不能确定这个变量的初始值,所以可以初始化为0或者NULL。inti=0;char*p=NULL;如果定义的是数组,可以这样初始化:inta[10]={0};或者使用memset函数初始化为0:memset(a,0,sizeof(a));memset函数有三个参数,第一个是要设置的内存的起始地址;第二个参数是要设置的值;第三个参数是要设置的内存大小,单位是byte。这里不想过多讨论memset函数的用法,如果想了解更多请参考相关资料。至于指针变量,如果不初始化,if语句或者assert宏校验都会失败。这一点上面已经分析过了。4.内存越界内存分配成功,已经初始化,但是操作越界了内存。这种错误往往是由于在操作数组或指针时“多了1”或“少了1”。例如:inta[10]={0};for(i=0;i<=10;i++){a[i]=i;}所以for循环的循环变量必须使用半开和半闭区间,并且如果不是特殊情况,尽量从0开始循环变量。5.内存泄漏内存泄漏几乎是不可避免的,不管你是老手还是新手,这个问题都存在。即使是windows、linux等软件,也或多或少存在内存泄漏的情况。或许对于一般的应用软件来说,这个问题似乎并没有那么突出,重启也不会造成太大的损失。但是如果你开发嵌入式系统软件呢?如汽车制动系统、心脏起搏器等安全要求非常高的系统。不能让心脏起搏器重启,冥王大人好客。会泄漏的内存是堆上的内存(资源或者句柄等泄漏这里不讨论),也就是说malloc系列函数或者new操作符分配的内存。如果在使用后没有及时free或者delete,这块内存要等到整个程序终止才能释放。1、如何理解内存分配和释放的过程?先看下面的对话:万岁君:爱卿,你为我立下了大功,你想要什么奖赏?某英雄:金银万岁,我视之如粪土。臣年老,欲告老,归乡。我乞求万亩良田,为子孙后代遮荫,别无所求。万岁爷:爱卿,你辛苦了,想要的只是这么一点点报酬。今天我会实现你的愿望。户部刘侍郎,查一查湖广地区还有几千亩优质良田没有分批。刘世朗:长沙还有5万多亩优质良田没有授牌。万岁君:在长沙分出良田千亩,赏赐爱卿。爱卿,你要一千亩良田做什么?某英雄:谢谢你,万岁。长沙地区适合种水稻,我想用它来种水稻。种水稻,需要分一亩田,方便耕种。....2.不要对如何使用malloc函数感到困惑。其实上面的小对话就是使用malloc的过程。malloc是一个旨在从堆中分配内存的函数。使用malloc函数需要几个条件:内存分配给谁?这里是分配良田给英雄。分配多少内存?这里是一千亩地的分配。是否还有足够的内存分配?这里分配的良田还是够多的。内存将用于存储什么格式的数据,即内存将用于什么?这里是用来种水稻的,需要分一亩田。分配的内存在哪里?这是在长沙。以上就是使用malloc函数成功分配一块内存的过程。但是你能每次都分配成功吗?不确定。在上面的对话中,皇帝让户部尚书询问是否还有足够的好土地没有分配。使用malloc函数时还要注意一点:如果申请的内存块大于当前堆上剩余的内存块(整个块),内存分配就会失败,函数会返回NULL。注意,这里所说的“堆上剩余内存块”并不是所有剩余内存块的总和,因为malloc函数申请的是连续的一块内存。3、使用malloc函数申请0字节内存还有一个问题:使用malloc函数申请0字节内存会返回NULL指针吗?您可以对其进行测试,也可以查找有关malloc函数的文档。申请0字节内存,函数不返回NULL,而是返回一个正常的内存地址。但是你不能用这个大小为0的内存,这是尺子上的一定刻度。秤本身是没有长度的,只能用一定的两把秤来量度长度。4.既然有内存释放的分配,就必须释放。否则,有限的内存会一直被用完,未释放的内存会一直闲置。与malloc对应的是free函数。free函数只有一个参数,就是要释放的内存块的首地址。比如上面的例子:free(p);free函数看起来很残酷,但它到底是干什么的呢?6.内存已经释放了,但是通过指针继续使用。这里一般有三种情况:第一种:如上所说,free(p)后,继续通过p指针访问内存。解决方案是将p设置为NULL。第二种:函数返回栈内存。这是初学者最常犯的错误。例如,在函数内部定义了一个数组,但使用return语句返回指向数组的指针。解决办法就是弄清楚变量在栈上的生命周期。第三种:内存的使用过于复杂,分不清哪块内存释放,哪块不释放。解决办法是重新设计程序,完善对象间的调用关系。上面详细讨论了六个常见错误及其解决方案。希望读者仔细研究,尽量熟悉每一个错误的产生原因和预防措施。一定要多练习,多调试代码,同时多总结经验。
