任何人都可以提供一些代码示例,这些示例在使用-fwrapv
与不使用编译时会有所不同?
gcc 文档说,-fwrapv
指示编译器假设加法、减法和乘法的有符号算术溢出使用二进制补码表示。
但是每当我尝试溢出时,无论有没有-fwrapv
,结果都是一样的。
想想这个函数:
int f(int i) {
return i+1 > i;
}
从数学上讲,对于任何整数i
,i+1
应始终大于i
。然而,对于 32 位int
,有一个值i
使该语句为假,这是2147483647
(即0x7FFFFFFF
,即INT_MAX
)。在该数字中添加 1 将导致溢出,根据 2 的赞美表示,新值将环绕并变得-2147483648
。因此,i+1>i
变得-2147483648>2147483647
这是错误的。
当你在没有-fwrapv
的情况下进行编译时,编译器将假定溢出是"非包装"的,它将优化该函数以始终返回1
(忽略溢出情况)。
当你使用-fwrapv
编译时,函数不会被优化,它会有加 1 并比较两个值的逻辑,因为现在溢出是"包装"(即溢出的数字将根据 2 的赞美表示进行包装)。
在生成的程序集中可以很容易地看到差异 - 在右窗格中,没有-fwrapv
,函数总是返回1
(true
)。
for (int i=0; i>=0; i++)
printf("%dn", i);
使用-fwrapv
,循环将在INT_MAX
迭代后终止。如果没有,它可以做任何事情,因为当i
具有值时,未定义的行为是通过i++
的评估无条件调用的INT_MAX
。实际上,优化编译器可能会省略循环条件并生成无限循环。
ISO 标准工作组 WG14 的存在是为了建立一个所有 C 编译器必须遵守的约定。一些编译器可能(并且确实)也实现了扩展。根据 ISO 标准 C,这些扩展被视为以下扩展之一:
- 实现定义,这意味着编译器开发人员必须做出选择,记录该选择并维护该批次,以便被视为兼容的 C 实现。
-
C11/3.4.3 为未定义的行为建立了定义,并给出了一个非常熟悉的示例,该示例远远优于我能写的任何内容:
1未定义的行为行为,在使用不可移植或错误的程序构造或错误数据时,本国际标准对此没有要求
2 注意 可能的未定义行为包括完全忽略情况并产生不可预测的结果,在转换或程序执行期间以环境特征的记录方式行事(无论是否发出诊断消息),再到终止转换或执行(发布诊断消息)。
3 示例 未定义行为的一个示例是整数溢出的行为。
还有一个未指定的行为,尽管我会把它留给你一个练习,让你在标准中阅读它。
小心你踩的地方。这是为数不多的普遍接受的未定义行为之一,其中通常预期 LIA 风格的包装将发生在没有陷阱抑制的二进制补码表示上。重要的是要意识到,有些实现使用陷阱表示形式对应于包含所有陷阱的位表示形式。
总之,fwrapv
和ftrapv
的存在是为了将选择传递给您,否则开发人员将不得不代表您做出选择,而该选择就是发生有符号整数溢出时发生的情况。当然,他们必须选择一个默认值,在您的情况下,该默认值似乎与fwrapv
相关,而不是ftrapv
。情况不一定如此,也不必如此,这些编译器选项也不必更改任何内容。
当使用新版本的 gcc 编译器时,我偶然遇到了一个问题,使标志 -fwrapv 变得清晰。
char Data[8]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int a=256*(256*(256*Data[3]+Data[2])+Data[1])+Data[0];
如果是否使用 -fwrapv 标志,结果将为 -1。 但是你这样做吗:
if(a<0)
printf("the value of a %d is negativen",a);
else
printf("the value of a %d is positiven",a);
它将打印"-1 的值为正数",因为在优化时它会删除第一部分,因为正数的 + - 和 * 将始终为正数(char 是无符号的).
当你使用 -fwrapv 标志编译时,它将打印正确的答案.
如果你使用它:
int a=(Data[3]<<24)+(Data[2]<<16)+(Data[1]<<8)+Data[0];
无论是否使用标志,代码都会按预期工作。
-fwrapv 告诉编译器,有符号整数算术的溢出必须被视为明确定义的行为,即使它在 C 标准中是未定义的。
目前广泛使用的几乎所有 CPU 架构都使用有符号整数的"2 补码"表示,并使用相同的处理器指令进行有符号和无符号值的有符号和无符号加法、减法和非加宽乘法。因此,在 CPU 架构级别,有符号和无符号艺术都环绕模 2n。
C 标准说有符号整数算术的溢出是未定义的行为。未定义的行为意味着"任何事情都可能发生"。任何事情都包括"你期望发生的事情",但它也包括"你的程序的其余部分将以不自洽的方式运行"。
特别是,当在现代编译器上调用未定义的行为时,优化器对变量中值的假设可能会与该变量中实际存储的值不一致。
因此,如果您允许在程序中发生有符号算术溢出并且不使用 fwrapv 选项,那么一开始事情可能会看起来不错,您的简单测试程序将产生您期望的结果。
但随后事情可能会变得非常糟糕。特别是检查变量的值是否为非负值可以优化,因为编译器假设变量必须是正数,而实际上它是负数。