我试图理解我们最近在使用Clang5.0和未定义的行为消毒器(UBsan)时清除的一个问题。我们有代码在正向或向后方向处理一个缓冲区。简化的情况是类似于下面显示的代码。
0-len看起来可能有点不寻常,但它是微软早期的.Net编译器所需要的。Clang5.0和UBsan 生成整数溢出发现
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;根据C中的指针比较,它们是有符号的还是无符号的? (也讨论C++),指针既不带符号也不带符号。我们的偏移量是无符号的,我们依靠无符号整数包装来实现反向移动。
该代码在GCC UBsan和Clang 4以及更早的UBsan下都很好。我们最终使用帮助LLVM开发人员为Clang5.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;
}发布于 2017-12-18 03:28:49
将整数添加到指针的定义是(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。
现在的问题是,如果我们把"i + j“解释为i + j表达式的结果,那么它是否等于32,它小于n。但是如果它意味着数学结果,那么它要比n大得多。
该标准在这里使用一种用于数学方程的字体,而不对源代码使用该字体。≤也不是有效的运算符。所以我认为他们打算用这个方程来描述数学值,也就是说,这是未定义的行为。
发布于 2017-12-18 21:31:34
C标准将类型ptrdiff_t定义为指针差操作符产生的类型.一个系统有一个32位的size_t和一个64位的ptrdiff_t是可能的;这样的定义对于一个使用64位线性或准线性指针但每个对象都小于4 4GiB的系统来说是很自然的。
如果已知每个对象都小于2GiB,那么存储ptrdiff_t类型的值而不是size_t类型的值可能会使程序不必要地效率低下。然而,在这种情况下,代码不应该使用size_t来保存可能为负值的指针差异,而应该使用int32_t,如果每个对象都小于2GiB,那么这个值就足够大了。即使ptrdiff_t是64位,int32_t类型的值在从任何指针中添加或减去之前也会正确地进行签名扩展。
https://stackoverflow.com/questions/47860626
复制相似问题