大家好,我是良许。
计算机专业的小伙伴,在学校期间一定学过 C 语言。它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理、操作系统、内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候,多次强调大家一定要好好学习这门语言。
但是,即使是最有经验的程序员也会写出各种各样的 Bug。本文就盘点一下学习或使用 C 语言过程中,非常容易出现的 5 个 Bug,以及如何规避这些 Bug。
这篇文章主要是为初学者,老鸟可以忽略呵呵(其实很多老鸟还会犯这些低级错误)~
[En]
This article is mainly for beginners, old birds can ignore ha (in fact, many old birds still make these low-level mistakes) ~
1. 变量未初始化
当程序启动时,系统自动为其分配一块内存,程序可以使用该内存来存储数据。因此,如果您定义一个变量,它的值在没有初始化的情况下可能是任意的。
[En]
When the program starts, the system automatically allocates a piece of memory to it, which the program can use to store data. So if you define a variable, its value may be arbitrary without initialization.
但这并不是绝对的,有些环境会在程序启动时自动将内存置零,因此每个变量的缺省值都是零。为了可移植性,最好是初始化变量,这是一个合格的软件工程师应该养成的好习惯。
[En]
But this is not absolute, some environments will automatically “zero” the memory when the program starts, so the default value of each variable is zero. For portability, it is best to initialize variables, which is a good habit that a qualified software engineer should develop.
让我们看一下使用几个变量和两个数组的以下示例程序:
[En]
Let’s take a look at the following example program that uses several variables and two arrays:
#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;
}
</stdlib.h></stdio.h>
该程序不会初始化变量,因此变量的值可能是随机的,不一定是零。在我的电脑上,结果如下:
[En]
This program does not initialize the variable, so the value of the variable may be random, not necessarily zero. On my computer, the results are as follows:
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)。
如果在不同的操作系统上编译相同的程序,结果可能会不同。因此,不要认为您的结果是正确和唯一的,并确保考虑可移植性。
[En]
If you compile the same program on different operating systems, the results may be different. So don’t feel that your results are correct and unique, and be sure to consider portability.
例如,这是在 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
可以看到,手术的结果几乎与上面的大不相同。因此,初始化变量将为您省去许多不必要的麻烦,并方便以后的调试。
[En]
It can be seen that the results of the operation are almost very different from those above. Therefore, initializing variables will save you a lot of unnecessary trouble and facilitate future debugging.
2. 数组越界
在计算机世界里,都是从 0 开始计数,但总有人有意无意忘记这点。比如一个数组长度为 10 ,想要获取最后一个元素的值,总有人用 array[10] ……
别问,问就是我写过……
新手朋友会犯很多这样的低级错误。让我们来看看当数组越过这条线时会发生什么。
[En]
Novice friends make many such low-level mistakes. Let’s take a look at what happens when an array crosses the line.
#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;
}
</stdlib.h></stdio.h>
请注意,程序初始化了数组 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
函数非常危险,因为它不知道接收它的字符串中可以存储多少数据,只会天真地从用户那里读取数据。
如果用户输入的字符串很短,这是可以接受的,但如果用户输入的值超过了接收到的字符串的长度,则可能是灾难性的。
[En]
It’s fine if the user input string is short, but it can be catastrophic if the user enters a value that exceeds the length of the received string.
让我们来演示一下这个现象:
[En]
Let’s demonstrate this phenomenon:
#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;
}
</%s></string.h></stdio.h>
在此代码中,接收数组的长度为10,因此当输入数据的长度小于10时,程序运行将没有问题。
[En]
In this code, the length of the receiving array is 10, so when the length of the input data is less than 10, the program will have no problem running.
例如,输入城市 Beijing ,长度为 7 :
var1 = 1; var2 = 2
Where do you live?
Beijing
<beijing> is length 7
var1 = 1; var2 = 2
Ok
</beijing>
威尔士小镇 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)
</llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch>
在中止之前,程序使用长字符串覆盖内存的其他部分。请注意, var1
和 var2
不再是它们的起始值 1
和 2
。
所以我们需要使用更安全的方法来读取用户数据。例如, getline
函数就是一个不错的选择,它将分配足够大的内存来存储用户输入,因此用户不会因输入太长字符串而意外溢出。
4. 内存重复释放
良好的 C 编程规则之一是,如果分配了内存,就一定要将其释放。
我们可以使用 malloc
函数为数组和字符串申请内存,系统将开辟一块内存并返回一个指向该内存起始地址的指针。内存使用完毕后,我们一定要记得使用 free
函数释放内存,然后系统将该内存标记为未使用。
但是,这个过程中,你只能调用 free
函数一次。如果你第二次调用 free
函数,将导致意外行为,而且可能会破坏你的程序。
让我们举一个简单的例子:
[En]
Let’s give a simple example:
#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");
}
</stdlib.h></stdio.h>
运行此程序会导致第二次调用 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
文件里,程序运行时,就可以调用这个文件,读取配置信息。
因此,从文件中读取数据的能力对所有程序员都很重要。但是,如果您想要读取的文件不存在怎么办?
[En]
Therefore, the ability to read data from files is important to all programmers. But what if the file you want to read doesn’t exist?
在 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;
}
</%c></stdio.h>
当你运行这个程序时,如果 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 王!
Original: https://www.cnblogs.com/yychuyu/p/15474622.html
Author: 良许Linux
Title: 人人都写过的5个Bug!
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/524415/
转载文章受原作者版权保护。转载请注明原作者出处!