我在尝试使用Clang 5.0和Undefined Behavior Sanitizer(UBsan)时最近清除了一个问题。我们有代码处理前向或后向的缓冲区。减少的案例是similar to the code shown below。
0-len
可能看起来有些不寻常,但早期的Microsoft .Net编译器需要它。 Clang 5.0和UBsan produced integer overflow findings:
adv-simd.h:1138:26: runtime error: addition of unsigned offset to 0x000003f78cf0 overflowed to 0x000003f78ce0
adv-simd.h:1140:26: runtime error: addition of unsigned offset to 0x000003f78ce0 overflowed to 0x000003f78cd0
adv-simd.h:1142:26: runtime error: addition of unsigned offset to 0x000003f78cd0 overflowed to 0x000003f78cc0
...
第1138,1140,1142行(以及朋友)是增量,可以是
由于0-len
而向后迈进。
ptr += inc;
根据Pointer comparisons in C. Are they signed or unsigned?(也讨论C ++),指针既不是有符号也不是无符号。我们的偏移是无符号的,我们依靠无符号整数换行来实现反向步幅。
在GCC UBsan和Clang 4以及早期的UBsan下,代码很好。我们最终用help with the LLVM devs为Clang 5.0清除了它。我们需要使用size_t
而不是ptrdiff_t
。
我的问题是,构造中的整数溢出/未定义行为在哪里? ptr + <unsigned>
如何导致有符号整数溢出并导致未定义的行为?
这是一个镜像真实代码的MSVC。
#include <cstddef>
#include <cstdint>
using namespace std;
uint8_t buffer[64];
int main(int argc, char* argv[])
{
uint8_t * ptr = buffer;
size_t len = sizeof(buffer);
size_t inc = 16;
// This sets up processing the buffer in reverse.
// A flag controls it in the real code.
if (argc%2 == 1)
{
ptr += len - inc;
inc = 0-inc;
}
while (len > 16)
{
// process blocks
ptr += inc;
len -= 16;
}
return 0;
}
答案 0 :(得分:2)
向指针添加整数的定义是(N4659 expr.add / 4):
我在这里使用了一个图像以保留格式(这将在下面讨论)。
请注意,这是一种新的措辞,它取代了之前标准中不太清晰的描述。
在您的代码中(当argc
为奇数时),我们最终得到的代码相当于:
uint8_t buffer[64];
uint8_t *ptr = buffer + 48;
ptr = ptr + (SIZE_MAX - 15);
对于应用于代码的标准报价中的变量,i
为48
且j
为(SIZE_MAX - 15)
且n
为64
现在的问题是0≤i+j≤n是否属实。如果我们解释&#34; i + j&#34;表示表达式i + j
的结果,那么等于32
小于n
。但如果它意味着数学结果,则它远大于n
。
标准在此处使用数学方程的字体,并且不使用字体作为源代码。 ≤
也不是有效的运算符。所以我认为他们打算用这个等式描述数学值,即这是未定义的行为。
答案 1 :(得分:1)
C标准将类型ptrdiff_t
定义为指针差异运算符产生的类型。系统有可能具有32位size_t
和64位ptrdiff_t
;对于使用64位线性或准线性指针的系统来说,这样的定义很自然,但确实要求每个对象的单个对象小于4GiB。
如果已知对象的每个小于2GiB,则存储类型ptrdiff_t
而不是size_t
的值可能会使程序无效率低效。但是,在这种情况下,代码不应使用size_t
来保存可能为负的指针差异,而是使用int32_t
[如果对象每个小于2GiB,则会足够大]。即使ptrdiff_t
为64位,类型int32_t
的值也会在从任何指针添加或减去之前正确地进行符号扩展。