我有一个简单的C练习(请参阅下面的代码)。该程序采用一个包含三个分量的向量,并将每个分量加倍。IDE向我显示了此警告(绿色波浪形):C6011 dereferencing null pointer v.
,位于此行中:v[0] = 12;
。我认为这是一个错误,因为在调试器中我读取了the program exited with code 0
。你觉得怎么样?
#include <stdlib.h>
#include <stdint.h>
void twice_three(uint32_t *x) {
for (size_t i = 0; i < 3; ++i) {
x[i] = 2 * *x;
}
}
int main(void) {
uint32_t *v = malloc(3 * sizeof(uint32_t));
v[0] = 12;
v[1] = 59;
v[2] = 83;
twice_three(v);
free(v);
return 0;
}
注意:我在用Visual Studio。
首先,请注意,警告是由编译器(或静态分析器,或linter)生成的,而不是由调试器生成的,正如您最初所写的那样。
该警告告诉您,您的程序可能会取消引用空指针。出现此警告的原因是执行malloc()
,然后使用结果(指针)而不检查NULL值。在这个特定的代码示例中,malloc()
很可能只返回请求的内存块。在任何台式电脑或笔记本电脑上,通常都没有理由不能分配12个字节。这就是为什么您的应用程序运行良好并成功退出的原因。然而,如果这是更大应用程序的一部分和/或在内存有限的系统(如嵌入式系统)上运行,malloc()
可能会失败并返回NULL
。请注意,malloc()
不仅在没有足够的可用内存的情况下失败,而且在由于碎片而没有足够大的连续内存块的情况下也可能失败。
根据C标准,取消引用NULL指针是未定义的行为,这意味着任何事情都可能发生。在现代计算机上,它可能会杀死你的应用程序(这可能导致数据丢失或损坏,具体取决于应用程序的功能)。在较旧的计算机或嵌入式系统上,问题可能未被检测到,并且您的应用程序会从地址NULL读取或(更糟)写入地址NULL(很可能是0,但即使是C标准也不能保证)。这可能会导致数据损坏、崩溃或在发生这种情况后的任意时间出现其他意外行为。
请注意,编译器/分析器/linter对您的应用程序或您将要运行它的平台一无所知,也不会对此做出任何假设。它只是警告您可能存在的问题。这取决于你来确定这个特定的警告是否与你的情况相关,以及如何处理它
一般来说,你可以做三件事:
-
如果您确信
malloc()
永远不会失败(例如,在这样一个玩具示例中,您只能在具有千兆字节内存的现代计算机上运行),或者如果您不关心结果(因为应用程序会被您的操作系统杀死,您也不介意),那么就没有必要发出此警告。只需在编译器中禁用它,或者忽略警告消息。 -
如果不希望
malloc()
失败,但确实希望在发生时得到通知,那么快速而肮脏的解决方案是在malloc之后添加assert(v != NULL);
。请注意,发生这种情况时,这也会退出您的应用程序,但以一种稍微可控的方式,您会收到一条错误消息,说明问题发生的位置。对于简单的业余项目,我建议你这样做,因为你不想在错误处理和角落案例上花太多时间,只想有一些有趣的编程:-) -
当发生
malloc()
将失败的实际更改,并且您希望应用程序具有定义良好的行为时,您绝对应该添加代码来处理这种情况(检查NULL值)。如果是这种情况,您通常需要做的不仅仅是添加一个if
-语句。您必须考虑应用程序如何在不需要更多内存分配的情况下继续工作或正常关闭。在嵌入式系统上,您还必须考虑诸如内存碎片之类的问题。
对于有问题的示例代码,最简单的修复方法是添加NULL检查。这将使警告消失,并且(假设malloc()
不会失败)您的程序将仍然运行。
int main(void) {
uint32_t *v = malloc(3 * sizeof(uint32_t));
if (v != NULL) {
v[0] = 12;
v[1] = 59;
v[2] = 83;
twice_three(v);
free(v);
}
return 0;
}
我相信您的IDE正在警告您,您没有确保malloc
返回NULL
以外的内容。当要分配的内存不足时,malloc
可以返回NULL
。
是否需要这样的检查是有争议的。在malloc
返回NULL
的不太可能的情况下,您的程序最终会被杀死(在具有虚拟内存的现代计算机上)[1]所以问题是,在内存耗尽的非常罕见的情况下,您是否希望在退出时得到一条干净的消息。
如果您添加了支票,请不要使用assert
。那没用。对于初学者来说,它只适用于开发构建(而不是生产构建),在开发构建中,malloc不太可能返回NULL,并且已经非常容易发现内存泄漏(例如,使用valgrind
)。使用正确的检查(if (!v) { perror(NULL); exit(1) }
)。
-
尽管有规则,但人们仍试图在评论中讨论这个问题,看来我必须更详细地阐述我的主张。
一些人在评论中建议;任何事情都可能发生";如果您不检查NULL,但在具有虚拟内存的现代计算机上这根本不是真的。
当C规范没有定义某个事物的行为(即所谓的"未定义行为")时,并不意味着任何事情都可能发生;这只是意味着C语言不在乎编译器/机器在这种情况下做什么。NULL解引用在这样的系统上定义得非常好。捕捉到这样的情况是内存虚拟化存在的理由!
就像你可以依赖于其他编译器特定的功能,比如gcc的字段打包属性一样,有人可以说,依靠内存虚拟化来检测malloc的失败是可以的。
- 始终检查malloc的结果
- 使用对象而不是
sizeof
中的类型
int main(void) {
uint32_t *v = malloc(3 * sizeof(*v));
if(v)
{
v[0] = 12;
v[1] = 59;
v[2] = 83;
twice_three(v);
}
free(v);
return 0;
}