C或C++中常见的bug来源是
size_t n = // ...
for (unsigned int i = 0; i < n; i++) // ...
字符串
当unsigned int
溢出时,其可以无限循环。
例如,在Linux上,unsigned int
是32位的,而size_t
是64位的,所以如果n = 5000000000
,我们得到一个无限循环。
我如何使用GCC或Clang获得有关此问题的警告?
GCC的-Wall
x 1 m5n1x不能做到这一点:
#include <stdint.h>
void f(uint64_t n)
{
for (uint32_t i = 0; i < n; ++i) {
}
}
gcc-13 -std=c17 \
-Wall -Wextra -Wpedantic \
-Warray-bounds -Wconversion \
-fanalyzer \
-c -o 76840686.o 76840686.c
的数据
(no输出)
- 我正在寻找一个不需要
n
是编译时常数的解决方案。 - 理想情况下,该解决方案可以在现有的C/C++项目上工作,而不必完全重写它们。
- 建议使用编译器警告以外的其他工具也会很有用,但编译器警告本身会更好
编辑:上游编译器功能请求
答案表明当前的编译器中不存在此类警告。我已经开始提交上游功能请求:
6条答案
按热度按时间swvgeqrz1#
gcc
或clang
中似乎没有内置警告选项来执行所请求的操作。但是,我们可以使用clang-query
代替。下面是一个
clang-query
命令,它将报告32位和64位整数的比较,假设int
是32位,long
是64位。(更多内容见下文)。字符串
当在以下
test.c
上运行时,它会报告所有指示的情况:型
关于
clang-query
命令的一些细节:-w
传递给clang-query
以抑制其他警告。这只是因为我编写测试的方式会引发关于未使用值的警告,而对于普通代码来说是不必要的。set bind-root false
,因此唯一报告的位置是感兴趣的操作数,而不是报告整个表达式。clang-query
报告“Matcher does not support binding(匹配器不支持绑定)”。该查询的不足之处在于它显式地列出了源和目标类型。不幸的是,
clang-query
没有匹配器来报告任何32位类型,因此必须单独列出它们。您可能希望在目标端添加[unsigned] long long
。如果使用针对IL32平台(如Windows)的编译器选项运行此代码,则可能还需要删除[unsigned] long
。相关地,注意
clang-query
接受--
之后的编译器选项,或者在compile_commands.json
文件中。不幸的是,没有专门的clang-query
命令行文档,甚至它的--help
也没有提到--
命令行选项。我所能链接的最好的是libtooling的文档,因为clang-query
在内部使用该库进行命令行处理。最后,我要指出,我没有在真实的代码中对这个查询进行任何“调优”。它可能会产生很多噪音,需要进一步调整。关于如何使用
clang-query
的教程,我推荐Stephen Kelly的博客文章Exploring Clang Tooling Part 2: Examining the Clang AST with clang-query。还有一个AST Matcher Reference,但文档非常简洁。j1dl9f462#
这并不能直接回答问题(提供警告),但您会考虑完全避免问题的替代方案吗?
字符串
现在n是什么类型已经不重要了,因为
i
总是与n
相同的类型,你应该永远不会遇到由于i
是一个更小的类型或具有比n
更小的范围而导致的无限循环的问题。q0qdq0h23#
PVS Studio可以发出这样的警告(以及更多),这里是他们文档中几乎相同的示例:
https://pvs-studio.com/en/docs/warnings/v104/
它是一个付费工具,但他们为开源项目给予免费许可。
我在Clang-tidy中没有发现这样的警告,这是LLVM项目中的一个免费linter工具,但是添加一个检查来比较不同大小的整数是非常简单的(Scott McPeak后来的回复用出色的clang-query完成了大部分工作-剩下的部分只是将这个查询插入clang-tidy)。不过会很吵。我们可以通过限制循环条件的检查来限制噪音,这也可以用Clang-tidy来完成,但用AST匹配器需要做更多的工作。
ztyzrc3y4#
最近的gcc版本似乎支持
-Warith-conversion
:-Warith-conversion
个即使将操作数转换为相同类型不能更改其值,也要对算术运算的隐式转换发出警告。这会影响来自
-Wconversion
、-Wfloat-conversion
和-Wsign-conversion
的警告。字符串
然而,它不适用于your example,可能是因为
i < n
不是算术表达式。对于泛型二进制表达式,似乎没有此警告的变体。0ve6wy6x5#
对于C++,你甚至可以做得比编译器警告更好,假设
n
是编译时常量。这也适用于非gcc编译器。但这种逻辑不适用于C代码。这个想法基本上是在变量类型中编码值信息,而不是变量值。
字符串
如果该值不是编译时常量,您仍然可以为整数创建 Package 器模板类型,并重载
<
运算符以与整数值进行比较,将static_assert
s添加到该运算符的主体中。型
请注意,更改比较运算符的操作数之一的类型的必要性既有好处也有缺点:它要求你修改代码,但它也允许你在每个变量的基础上应用检查…
fkaflof66#
遵循编码标准,在源代码处停止
大多数嵌入式软件的编码标准都禁止使用“int”,原因和你说的一样。C标准要求存在固定长度的等效数据类型(8、16和32位多年来一直是强制性的; 64位是较新的,但仍然支持基本上无处不在)。在任何地方使用它们都是一个很好的做法,但在安全相关的软件中,它通常是强制性的。有许多工具可用于流行的编码标准,如MISRA-C,它们将为您捕获这些问题。
对于上面的许多评论者来说,你的例子似乎很模糊,因为它需要2^32次迭代才能溢出。许多现代编码器忘记的是,int也可以是16位的,这使得溢出在过去变得容易得多。Ariane-5 disaster是由截断为16位时的64位值溢出引起的。Therac-25 disaster也部分是由清 debugging 误状态的整数溢出引起的。
不过,32位中也有示例。Windows 95和98 famously crashed after 49.7 days由32位毫秒计时器溢出引起。在15年的时间里,我们有Y2038问题可以期待。大多数现代系统都为Y2038做好了准备,但我们很可能会到达那里并有一些惊喜!
所有这些都构成了软件工程作为一门工程学科的制度历史的一部分,就像泰桥和塔科马窄桥构成了土木工程制度历史的一部分一样。我有点震惊地看到上面有人说他们在40年的C编码中从未听说过这个。当然,仅仅是一个编码员而不知道这一点是可能的,就像仅仅是一个建筑师而不知道土木工程原理是可能的一样。然而,工程师必须意识到他们设计背后的更深层次的原则。这个问题,看起来微不足道,是软件工程师和单纯的编码员之间明显区分专业水平的一件事。考虑到阿丽亚娜-5和Therac-25,我不会为对此做出判断而道歉。