大家好,我是良许。
计算机专业的小伙伴,在学校期间肯定学过 C 语言。它是泛滥高级语言的鼻祖,深刻学习这门语言会对计算机原理、操作系统、内存治理等等底层相干的常识会有更深刻的理解,所以我在直播的时候,屡次强调大家肯定要好好学习这门语言。
然而,即便是最有教训的程序员也会写出各种各样的 Bug。本文就盘点一下学习或应用 C 语言过程中,非常容易呈现的 5 个 Bug,以及如何躲避这些 Bug。
这篇文章次要面向初学者,老鸟能够疏忽哈(其实不少老鸟仍然还会犯这些低级谬误哦)~
1. 变量未初始化
当程序启动时,零碎会给它主动调配一块内存,程序能够用它来存储数据。所以如果你在定义一个变量时,在未初始化的状况下,它的值有可能是任意的。
但这也不是相对的,有些环境就会在程序启动时主动将内存「清零」,因而每个变量默认值都是零。思考到可移植性,最好要将变量进行初始化,这是一名合格软件工程师应该养成的好习惯。
咱们来看下上面这个应用几个变量和两个数组的示例程序:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i, j, k;
int numbers[5];
int *array;
puts("These variables are not initialized:");
printf("i = %d\n", i);
printf("j = %d\n", j);
printf("k = %d\n", k);
puts("This array is not initialized:");
for (i = 0; i < 5; i++) {printf("numbers[%d] = %d\n", i, numbers[i]);
}
puts("malloc an array ...");
array = malloc(sizeof(int) * 5);
if (array) {puts("This malloc'ed array is not initialized:");
for (i = 0; i < 5; i++) {printf("array[%d] = %d\n", i, array[i]);
}
free(array);
}
/* done */
puts("Ok");
return 0;
}
这段程序没有对变量进行初始化,所以变量的值有可能是随机的,不肯定是零。在我的电脑上它的运行后果如下:
These variables are not initialized:
i = 0
j = 0
k = 32766
This array is not initialized:
numbers[0] = 0
numbers[1] = 0
numbers[2] = 4199024
numbers[3] = 0
numbers[4] = 0
malloc an array ...
This malloc'ed array is not initialized:
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 0
array[4] = 0
Ok
从后果能够看出,i
和 j
的值刚好是 0,但 k
值为 32766。在 numbers 数组中,大多数元素也恰好是零,除了第三个(4199024)。
在不同的操作系统上编译这段雷同的程序,运行的后果有可能又是不一样的。所以千万不要感觉你的后果就是正确惟一的,肯定要思考可移植性。
例如,这是在 FreeDOS 上运行的雷同程序的后果:
These variables are not initialized:
i = 0
j = 1074
k = 3120
This array is not initialized:
numbers[0] = 3106
numbers[1] = 1224
numbers[2] = 784
numbers[3] = 2926
numbers[4] = 1224
malloc an array ...
This malloc'ed array is not initialized:
array[0] = 3136
array[1] = 3136
array[2] = 14499
array[3] = -5886
array[4] = 219
Ok
能够看进去,运行的后果跟下面简直是天差地别。所以,对变量进行初始化将为你省去很多不必要的麻烦,也便于未来的调试。
2. 数组越界
在计算机世界里,都是从 0 开始计数,但总有人有意无意遗记这点。比方一个数组长度为 10,想要获取最初一个元素的值,总有人用 array[10] ……
别问,问就是我写过……
老手敌人犯这种低级谬误特地多。咱们来看下数组越界会产生什么。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
int numbers[5];
int *array;
/* test 1 */
puts("This array has five elements (0 to 4)");
/* initalize the array */
for (i = 0; i < 5; i++) {numbers[i] = i;
}
/* oops, this goes beyond the array bounds: */
for (i = 0; i < 10; i++) {printf("numbers[%d] = %d\n", i, numbers[i]);
}
/* test 2 */
puts("malloc an array ...");
array = malloc(sizeof(int) * 5);
if (array) {puts("This malloc'ed array also has five elements (0 to 4)");
/* initalize the array */
for (i = 0; i < 5; i++) {array[i] = i;
}
/* oops, this goes beyond the array bounds: */
for (i = 0; i < 10; i++) {printf("array[%d] = %d\n", i, array[i]);
}
free(array);
}
/* done */
puts("Ok");
return 0;
}
请留神,程序初始化了数组 numbers 所有元素的值(0~4),然而越界读取了第 0~9 元素的值。能够看进去,前五个值是正确的,但之后鬼都不晓得这些值会是什么:
This array has five elements (0 to 4)
numbers[0] = 0
numbers[1] = 1
numbers[2] = 2
numbers[3] = 3
numbers[4] = 4
numbers[5] = 0
numbers[6] = 4198512
numbers[7] = 0
numbers[8] = 1326609712
numbers[9] = 32764
malloc an array ...
This malloc'ed array also has five elements (0 to 4)
array[0] = 0
array[1] = 1
array[2] = 2
array[3] = 3
array[4] = 4
array[5] = 0
array[6] = 133441
array[7] = 0
array[8] = 0
array[9] = 0
Ok
所以大家在写代码过程中,肯定要晓得数组的边界。像这种数据读取的还好,如果一旦对这些内存进行写操作,间接就 core dump!
3. 字符串溢出
在 C 编程语言中,字符串是一组 char
值,也能够将其视为数组。因而,你也须要防止超出字符串的范畴。如果超出,则称为 字符串溢出。
为了测试字符串溢出,一种简略办法是应用 gets
函数读取数据。gets
函数十分危险,因为它不晓得接管它的字符串中能够存储多少数据,只会天真地从用户那里读取数据。
如果用户输出字符串比拟短那很好,但如果用户输出的值超过接管字符串的长度,则可能是灾难性的。
上面咱们来演示一下这个景象:
#include <stdio.h>
#include <string.h>
int main()
{char name[10]; /* Such as "Beijing" */
int var1 = 1, var2 = 2;
/* show initial values */
printf("var1 = %d; var2 = %d\n", var1, var2);
/* this is bad .. please don't use gets */
puts("Where do you live?");
gets(name);
/* show ending values */
printf("<%s> is length %d\n", name, strlen(name));
printf("var1 = %d; var2 = %d\n", var1, var2);
/* done */
puts("Ok");
return 0;
}
在这段代码里,接管数组的长度为 10,所以当输出数据长度小于 10 的话,程序运行就没问题。
例如,输出城市 Beijing,长度为 7:
var1 = 1; var2 = 2
Where do you live?
Beijing
<Beijing> is length 7
var1 = 1; var2 = 2
Ok
威尔士小镇 Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
是世界上名字最长的城市,这个字符串有 58 个字符,远远超出了 name
变量中可保留的 10 个字符。
如果输出这个字符串,其后果是程序运行内存的其它地位,比方 var1
和var2
,都有可能被波及:
var1 = 1; var2 = 2
Where do you live?
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
<Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58
var1 = 2036821625; var2 = 2003266668
Ok
Segmentation fault (core dumped)
在停止之前,程序应用长字符串笼罩内存的其余局部。请留神,var1
和 var2
不再是它们的起始值 1
和 2
。
所以咱们须要应用更平安的办法来读取用户数据。例如,getline
函数就是一个不错的抉择,它将调配足够大的内存来存储用户输出,因而用户不会因输出太长字符串而意外溢出。
4. 内存反复开释
良好的 C 编程规定之一是,如果调配了内存,就肯定要将其开释。
咱们能够应用 malloc
函数为数组和字符串申请内存,零碎将开拓一块内存并返回一个指向该内存起始地址的指针。内存应用结束后,咱们肯定要记得应用 free
函数开释内存,而后零碎将该内存标记为未应用。
然而,这个过程中,你只能调用 free
函数一次。如果你第二次调用 free
函数,将导致意外行为,而且可能会毁坏你的程序。
上面咱们举个简略的例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *array;
puts("malloc an array ...");
array = malloc(sizeof(int) * 5);
if (array) {puts("malloc succeeded");
puts("Free the array...");
free(array);
}
puts("Free the array...");
free(array);
puts("Ok");
}
运行此程序会导致第二次调用 free
函数时呈现 core dump 谬误:
malloc an array ...
malloc succeeded
Free the array...
Free the array...
free(): double free detected in tcache 2
Aborted (core dumped)
那么怎么防止屡次调用 free
函数呢?一个最简略的办法就是将 malloc
和 free
语句放在一个函数里。
如果你将 malloc
放在一个函数里,而将 free
放在另一个函数里,那么,在应用的过程中,如果逻辑设计不失当,都有可能呈现 free
被调用屡次的状况。
5. 应用有效的文件指针
文件是操作系统里一种十分常见的数据存储形式。例如,您能够将程序的配置信息存储在名为 config.dat
文件里,程序运行时,就能够调用这个文件,读取配置信息。
因而,从文件中读取数据的能力对所有程序员都很重要。然而,如果你要读取的文件不存在怎么办?
在 C 语言中,要读取文件个别是先应用 fopen
函数关上文件,而后该函数返回指向文件的流指针。
如果您要读取的文件不存在或您的程序无奈读取,则 fopen
函数将返回 NULL
。在这种状况下,咱们依然对其进行操作,会产生什么状况?咱们一起来看下:
#include <stdio.h>
int main()
{
FILE *pfile;
int ch;
puts("Open the FILE.TXT file ...");
pfile = fopen("FILE.TXT", "r");
/* you should check if the file pointer is valid, but we skipped that */
puts("Now display the contents of FILE.TXT ...");
while ((ch = fgetc(pfile)) != EOF) {printf("<%c>", ch);
}
fclose(pfile);
/* done */
puts("Ok");
return 0;
}
当你运行这个程序时,如果 FILE.TXT 这个文件不存在,那么 pfile 将返回 NULL。在这种状况下咱们还对 pfile 进行写操作的话,会立即导致 core dump:
Open the FILE.TXT file ...
Now display the contents of FILE.TXT ...
Segmentation fault (core dumped)
所以,咱们要始终查看文件指针是否无效。例如,在调用 fopen
函数关上文件后,应用 if (pfile != NULL)
以确保指针是能够应用的。
小结
再有教训的程序员都有可能犯错误,所以写代码的时候咱们要谨严再谨严。然而,如果你养成一些良好的习惯,并增加一些额定的代码来查看这五种类型的谬误,则能够防止重大的 C 编程谬误。
下面介绍的 5 种常见谬误,你都写过哪些 Bug 呢?留言跟大家交换哦,看看谁是 Bug 王!
最初,最近很多小伙伴找我要Linux 学习路线图,于是我依据本人的教训,利用业余时间熬夜肝了一个月,整顿了一份电子书。无论你是面试还是自我晋升,置信都会对你有帮忙!
收费送给大家,只求大家金指给我点个赞!
电子书 | Linux 开发学习路线图
也心愿有小伙伴能退出我,把这份电子书做得更完满!
有播种?心愿老铁们来个三连击,给更多的人看到这篇文章
举荐浏览:
- 干货 | 程序员进阶架构师必备资源免费送
- 书单 | 程序员必读经典书单(高清 PDF 版)