在2018年初,我们的博客上出现了一系列文章,专门讨论了Chromium项目的源代码的第六次验证。 该周期包括8篇文章,专门针对错误和预防错误的建议。 有两篇文章引起了热烈的讨论,到目前为止,我很少收到有关其中讨论的主题的评论。 也许,应该给出一些附加的解释,并且如他们所说,在i上加点。
自撰写一系列专门用于Chromium项目源代码的下一次验证的文章以来已经过去了一年:
- 铬:第六次项目检查和250个错误
- 美丽的铬和笨拙的模版
- 突破和失败
- 铬:内存泄漏
- 铬错别字
- 铬:使用不正确的数据
- 为什么检查malloc函数返回什么很重要
- 铬:其他错误
关于
memset和
malloc的文章
已经引起并继续引起对我来说似乎很奇怪的讨论。 显然,由于我没有清楚地表达自己的想法,所以造成了一些误解。 我决定返回这些文章并做一些澄清。
记忆集
让我们从有关
memset的文章开始,因为这里的一切都很简单。 关于如何最好地初始化结构存在争议。 很多程序员写道,最好不要写建议:
HDHITTESTINFO hhti = {};
依此类推:
HDHITTESTINFO hhti = { 0 };
参数:
- 与{}相比,{0}构造在读取代码时更容易注意到。
- {0}构造比{}更直观。 也就是说,0表示该结构填充有零。
因此,在本文中提供了更改该初始化示例的信息。 我不同意这些论点,也不打算修改本文。 现在,我证明自己的立场是正确的。
关于可见性。 我认为这是一个品味和习惯问题。 我认为花括号内的0不会从根本上改变这种情况。
但是,对于第二种说法,我一点也不同意。 像{0}这样的记录说明了误解代码的原因。 例如,您可以计算出,如果将0替换为1,则所有字段都将初始化为单位。 因此,这种写作风格有害无益。
PVS-Studio分析仪甚至具有与此主题相关的诊断
V1009 ,我现在将给出其描述。
V1009。 检查阵列初始化。 仅第一个元素被显式初始化。分析器检测到可能的错误,原因是在声明数组时,仅为一个元素指定了该值。 因此,其余元素将隐式初始化为零或默认构造函数。
考虑一个可疑代码的示例:
int arr[3] = {1};
程序员可能希望
arr由一个单元组成,但事实并非如此。 该数组将包含值1、0、0。
正确的代码是:
int arr[3] = {1, 1, 1};
由于与
arr = {0}构造的相似性而可能发生这种混淆,该构造将整个数组初始化为零。
如果项目中积极使用了类似的设计,则可以禁用此诊断。
也建议不要忽略代码的可见性。
例如,用于编码颜色值的代码如下所示:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00 }; int Green[3] = { 0x00, 0xff };
由于隐式初始化,所有颜色均已正确设置,但最好更清楚地重写代码:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00, 0x00, 0x00 }; int Green[3] = { 0x00, 0xff, 0x00 };
分配
在进一步阅读之前,我要求您刷新文章“
为什么检查malloc函数返回什么很重要 ”的内容。 这篇文章引起了很多讨论和批评。 这里是一些讨论:
reddit.com/r/cpp,reddit.com/r/C_Programming和habr.com 。 有时他们现在在邮件中给我写这篇文章。
这篇文章在以下几点上受到读者的批评:
1.如果 malloc 返回 NULL ,那么立即退出程序胜于编写一堆 ifs 并尝试以某种方式处理内存不足的情况,因为这通常仍然无法执行程序。直到最后一次我才打电话来处理内存不足的后果,错误越来越高。 如果允许应用程序在不发出警告的情况下关闭,那就可以了。 为此,只需在
malloc或使用
xmalloc之后立即进行一次检查(请参阅下一段)。
我反对并警告说没有检查,因此检查程序继续“好像什么都没发生”那样继续工作。 这是完全不同的。 这很危险,因为它会导致未定义的行为,数据损坏等。
2.不讨论该解决方案,该解决方案包括编写函数包装程序以通过后续验证或使用现有功能(例如 xmalloc) 来分配内存 。我同意,我错过了这一刻。 在写文章时,我只是没有考虑如何解决这种情况。 对我来说,向读者传达缺少验证的危险是更重要的。 如何解决错误已经是口味和实现细节的问题。
xmalloc函数不是C标准库的一部分(请参阅“
xmalloc和malloc之间有什么区别? ”)。 但是,可以在其他库中声明此函数,例如,在GNU utils库(
GNU libiberty )中。
该功能的实质是如果内存分配失败,程序将终止。 该功能的实现可能如下所示:
void* xmalloc(size_t s) { void* p = malloc(s); if (!p) { fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s); exit(EXIT_FAILURE); } return p; }
因此,通过在
各处而不是
malloc 处调用
xmalloc函数
,可以确保该程序不会由于使用null指针而具有未定义的行为。
不幸的是,
xmalloc也不是所有疾病的灵丹妙药。 必须记住,在编写库代码时,使用
xmalloc是不可接受的。 我稍后再谈。
3.大多数评论如下:“实际上, malloc 永远不会返回 NULL 。”幸运的是,我并不孤单地理解这是错误的方法。 我真的很喜欢我的支持中的以下
评论 :
从讨论这样一个话题的经验中,人们会感觉到互联网上有两个教派。 第一个的坚定者坚信在Linux下,malloc永远不会返回NULL。 第二种方法的拥护者坚信,如果无法分配程序中的内存,但是原则上什么也做不了,那么您只需倒下。 您无法以任何方式说服他们。 尤其是当这两个宗派相交时。 您只能将其视为理所当然。 讨论使用什么个人资料资源都没有关系。我认为并决定听从建议,我不会尝试说服:)。 我们希望这些开发团队只编写非关键程序。 例如,如果某些数据在游戏中恶化或崩溃,则没什么大不了的。
唯一重要的是库,数据库等的开发人员不要这样做。
呼吁库和负责的代码开发人员
如果要开发库或其他负责的代码,请始终检查
malloc / realloc函数返回的指针的值,如果无法分配内存,则将错误代码返回到外部。
在库中,如果无法分配内存,则无法调用
exit函数。 出于同样的原因,您不能使用
xmalloc 。 对于许多应用程序来说,简单地将它们拿走并使其崩溃是不可接受的。 因此,例如,数据库可能已损坏。 已经数小时的数据可能会丢失。 因此,该程序可能容易受到拒绝服务漏洞的攻击,而当多线程应用程序无法以某种方式正确地处理不断增长的负载时,它便会终止。
无法假定将如何以及在哪些项目中使用该库。 因此,应该假定该应用程序可以解决非常重要的任务。 而且仅仅通过叫
出口 “杀死”他是不好的。 编写此类程序很可能会考虑到内存不足的可能性,并且在这种情况下可以执行某些操作。 例如,某些CAD系统由于内存的碎片严重,无法为下一个操作分配足够的内存缓冲区。 但这不是她因数据丢失而进入紧急模式的原因。 程序可以保存项目并以正常模式重新启动。
在任何情况下,您都不能依靠
malloc始终可以分配内存这一事实。 还不知道在哪个平台上以及如何使用该库。 如果在一个平台上缺少内存是很奇怪的,那么在另一个平台上可能是非常普遍的情况。
不能指望如果
malloc返回
NULL ,则程序将崩溃。 什么都可能发生。 如我在
文章中所述,程序可以完全在非零地址写入数据。 结果,某些数据可能会被破坏,从而导致不可预测的后果。 即使是
记忆集也是危险的。 如果以相反的顺序填充数据,则首先某些数据被破坏,然后程序才会崩溃。 但是秋天可能为时已晚。 如果在执行
memset函数时在并行线程中使用了损坏的数据,则后果可能是致命的。 您可以在数据库中获得损坏的事务,或发送命令删除“不必要的”文件。 任何事情都会及时发生。 我建议读者独立地想象在内存中使用垃圾会导致什么。
因此,该库只有一个正确的选项可用于
malloc函数。 您需要立即检查函数是否返回,如果为NULL,则返回错误状态。
网站连结
- OOM处理 。
- 使用NULL指针的乐趣: 第1 部分 , 第2部分 。
- 每个C程序员应该了解的未定义行为: 第1 部分 , 第2 部分 , 第3部分 。

如果您想与说英语的读者分享这篇文章,请使用以下链接:Andrey Karpov。
第六次铬检查,后记 。