第六次铬测试,后记

严格的独角兽

在2018年初,我们的博客上出现了一系列文章,专门讨论了Chromium项目的源代码的第六次验证。 该周期包括8篇文章,专门针对错误和预防错误的建议。 有两篇文章引起了热烈的讨论,到目前为止,我很少收到有关其中讨论的主题的评论。 也许,应该给出一些附加的解释,并且如他们所说,在i上加点。

自撰写一系列专门用于Chromium项目源代码的下一次验证的文章以来已经过去了一年:

  1. 铬:第六次项目检查和250个错误
  2. 美丽的铬和笨拙的模版
  3. 突破和失败
  4. 铬:内存泄漏
  5. 铬错别字
  6. 铬:使用不正确的数据
  7. 为什么检查malloc函数返回什么很重要
  8. 铬:其他错误

关于memsetmalloc的文章已经引起并继续引起对我来说似乎很奇怪的讨论。 显然,由于我没有清楚地表达自己的想法,所以造成了一些误解。 我决定返回这些文章并做一些澄清。

记忆集


让我们从有关memset的文章开始,因为这里的一切都很简单。 关于如何最好地初始化结构存在争议。 很多程序员写道,最好不要写建议:

HDHITTESTINFO hhti = {}; 

依此类推:

 HDHITTESTINFO hhti = { 0 }; 

参数:

  1. 与{}相比,{0}构造在读取代码时更容易注意到。
  2. {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,则返回错误状态。

网站连结


  1. OOM处理
  2. 使用NULL指针的乐趣: 第1 部分第2部分
  3. 每个C程序员应该了解的未定义行为: 第1 部分第2 部分第3部分



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

Source: https://habr.com/ru/post/zh-CN438764/


All Articles