考虑这种情况:
uint64_t add(uint32_t a, uint32_t b)
{
return a + b; // programmer neglected (uint64_t) a + b.
}
我们如何让GCC(或任何其他编译器)的C或C ++前端警告这种情况:正在以立即扩展的狭窄类型进行操作?
我已经阅读了当前的GCC文档,并尝试了诸如-Wconversion
之类的各种警告,但是什么也没有。
答案 0 :(得分:3)
我不知道会向GCC发出警告的标志。 Coverity静态分析器将发出OVERFLOW_BEFORE_WIDEN警告,因为这在CERT标准中已标记。
免责声明:我曾经为Coverity工作。
答案 1 :(得分:1)
由于我正在使用的代码可以C或C ++的形式进行编译,并且所讨论的类型均为typedef(可以轻松地将其重新定位到类),所以我想到可以使用C ++解决方案。以下代码示例提示了这种想法:
#include <inttypes.h>
template <typename outer, typename inner, typename underlying> class arith {
public:
underlying val;
arith(underlying v) : val(v) { }
explicit operator underlying () const { return val; }
outer operator +(const inner &rhs) { return val + rhs.val; }
};
struct narrow;
struct narrow_result : public arith<narrow_result, narrow_result, uint32_t> {
narrow_result(uint32_t v) : arith(v) { }
narrow_result(const narrow &v);
};
struct narrow : public arith<narrow_result, narrow, uint32_t> {
narrow(uint32_t v) : arith(v) { }
narrow(const narrow_result &v) : arith(v.val) { }
};
inline narrow_result::narrow_result(const narrow &v)
: arith(v.val)
{
}
struct wide {
uint64_t val;
wide(uint64_t v) : val(v) { }
wide(const narrow &v) : val(v) { }
operator uint64_t () const { return val; }
wide operator +(const wide &rhs) { return val + rhs.val; }
};
int main()
{
narrow a = 42;
narrow b = 9;
wide c = wide(a) + b;
wide d = a + b; // line 43
narrow e = a + b;
wide f = a; // line 45
narrow g = a + b + b; // line 46
return 0;
}
在这里,GNU C ++仅诊断第43行:
overflow.cc: In function ‘int main()’:
overflow.cc:43:16: error: conversion from ‘narrow_result’ to non-scalar type ‘wide’ requested
请注意,如第45行所示,仍然允许从narrow
到wide
的隐式转换,仅仅是因为wide
具有直接面向narrow
的转换构造函数。 narrow_result
只缺少一个。
第46行显示我们可以对算术运算进行复合。这是可能的,因为narrow
隐式转换为narrow_result
,反之亦然。但是,这种隐式转换不会在第45行开始。添加的narrow_result
不会转换为narrow
,因此可以转换为wide
。
所有这些都可以用#ifdef __cplusplus
和一个条件调试宏进行包装,该宏还可以启用narrow
和wide
的类型定义作为typedef的替代定义。当然,arith
模板库中必须支持许多其他算术运算。
答案 2 :(得分:1)
它可以对整数溢出进行各种检查,包括无符号运算
C26450 RESULT_OF_ARITHMETIC_OPERATION_PROVABLY_LOSSY: [operator] 操作在编译时导致溢出。使用更广泛的类型来存储操作数。此警告表明在编译时算术运算可证明是有损的。当操作数都是编译时常量时,可以断言。目前,我们检查此类溢出的左移,乘法,加法和减法运算。
uint32_t multiply() { const uint32_t a = UINT_MAX; // the author used int here const uint32_t b = 2; // but I changed to unsigned for this question uint32_t c = a * b; // C26450 reported here [and also C4307] return c; }
C26451 RESULT_OF_ARITHMETIC_OPERATION_CAST_TO_LARGER_SIZE:对 [size1] 字节值使用运算符 [operator] ,然后将结果转换为 [size2 ] 字节值。在调用运算符 [operator] 之前,将值转换为更宽泛的类型,以避免溢出。
此警告指示错误的行为是由于积分提升规则和类型大于通常执行算术的行为而导致的。我们检测窄型整数值何时向左移动,相乘,相加或减去,然后将该算术运算的结果转换为较宽的类型值。如果操作溢出了窄类型值,则数据将丢失。您可以通过在算术运算之前将值转换为更大的类型来防止这种损失。
void leftshift(int i) { unsigned long long x; x = i << 31; // C26451 reported here // code // Corrected source: void leftshift(int i) { unsigned long long x; x = (unsigned long long)i << 31; // OK // code }
C26454 RESULT_OF_ARITHMETIC_OPERATION_NEGATIVE_UNSIGNED: [operator] 操作换行为0,并在编译时产生一个大的无符号数
此警告表明减法运算产生否定结果,该结果在无符号上下文中进行了评估。这将导致结果回绕到0并产生一个非常大的无符号数字,这可能导致意外的溢出。
// Example source: unsigned int negativeunsigned() { const unsigned int x = 1u - 2u; // C26454 reported here return x; } // Corrected source: unsigned int negativeunsigned() { const unsigned int x = 4294967295; // OK return x; }
Arithmetic overflow checks in C++ Core Check
这是一个实际的例子
从上面的示例中可以看到,如果操作数是编译时间常数,则编译器本身也会发出警告。如果它们是变量,那么您需要静态分析器
您可以在Compiler Explorer上使用它,尽管我不确定如何从命令行使其真正起作用。如果您知道如何将参数传递给VS代码分析,请在下面评论。在MSVC GUI上,只需按 Alt + F11
有关如何运行分析的信息,请阅读C++ Static Analysis Improvements for Visual Studio 2017 15.6 Preview 2
Clang 对此没有 compile-time 选项,但它具有在运行时检查的选项
-fsanitize=unsigned-integer-overflow
:无符号整数溢出,其中无符号整数计算的结果无法用其类型表示。与有符号整数溢出不同,这不是未定义的行为,但通常是无意的。该清理程序不会检查在进行此类计算之前执行的有损隐式转换(请参见-fsanitize=implicit-conversion
)。
它也可以很容易地禁用
Silencing Unsigned Integer Overflow
要使报告不受无符号整数溢出的影响,可以设置
UBSAN_OPTIONS=silence_unsigned_overflow=1
。此功能与-fsanitize-recover=unsigned-integer-overflow
结合使用,对于提供模糊信号而不炸毁日志特别有用。
不幸的是,GCC仅支持-fsanitize=signed-integer-overflow
。没有未签名的版本