当前位置: 首页 > 知识库问答 >
问题:

Visual C是否认为有符号整数溢出未定义?

松鸣
2023-03-14

最近,有符号整数溢出在C和C中没有正式定义,这引起了很多关注。然而,给定的实现可能会选择定义它;在C语言中,实现可以设置std::numeric_limits

Visual C设置< code>std::numeric_limits

我发现一个实例,其中优化程序发出 x86-64 程序集代码,如果将恰好INT_MAX的值传递给特定函数,则该代码的行为不正确。我无法判断这是否是一个错误,因为 Visual C 似乎没有说明是否有符号整数溢出被视为已定义。所以我想知道,它应该在视觉C中定义吗?

编辑:我在阅读Visual C 2013 Update 2中一个讨厌的bug时发现了这个bug,这个bug在Update 1中没有,如果启用了优化,下面的循环会生成错误的机器代码:

void func (int *b, int n)
{
  for (int i = 0; i < n; i++)
    b[i * (n + 1)] = 1;
}

Update 2错误导致重复行的代码生成,就好像它是b[i]=1;,这显然是错误的。它变成了rep stosd

真正有趣的是,在之前的版本Update 1中有一个奇怪的地方。它生成的代码没有正确处理n完全等于INT_MAX的情况。具体来说,如果nINT_MAX,乘法将表现为nlong long而不是int-换句话说,添加n 1不会导致结果变成INT_MIN

这是更新1中的程序集代码:

    movsxd  rax, edx          ; RDX = 0x000000007FFFFFFF; RAX = 0x000000007FFFFFFF.
    test    edx, edx
    jle     short locret_76   ; Branch not taken, because EDX is nonnegative.
    lea     rdx, ds:4[rax*4]  ; RDX = RAX * 4 + 4; RDX becomes 0x0000000200000000.
    nop                       ; But it's wrong. RDX should now be 0xFFFFFFFE00000000.
loc_68:
    mov     dword ptr [rcx], 1
    add     rcx, rdx
    dec     rax
    jnz     short loc_68
locret_76:
    retn

问题是我不知道这是否是编译器错误-在GCC和Clang中,这不是编译器错误,因为这些编译器认为有符号整数溢出/下溢是未定义的。这是否是Visual C中的错误取决于Visual C是否认为有符号整数上溢/下溢未定义。

除此之外,我所看到的所有其他案例都表明,Visual C考虑定义签名溢出/下溢,因此这是一个谜。


共有2个答案

胥博文
2023-03-14

对于< code>n == INT_MAX,您的示例可能确实有未定义的行为,但不仅仅是因为有符号整数溢出未定义(Microsoft编译器上可能没有)。相反,您可能正在调用未定义的越界指针算法。

赏夕
2023-03-14
匿名用户

从2016年(VS2015更新3)中发现了一个有趣的花絮:

他们谈到了他们希望在VS2015中引入的新SSA优化器:

C 团队博客 - 介绍新的高级可视化 C 代码优化器

... ... ...

从历史上看,Visual C 没有利用 C 和 C 标准认为溢出的有符号操作未定义的结果这一事实。其他编译器在这方面非常激进,这促使他们决定实现一些利用未定义的整数溢出行为的模式。我们实现了我们认为安全的代码,并且没有在生成的代码中施加任何不必要的安全风险。

所以你有它。我把它读为:“我们从来没有在任何额外的位中编程来利用这个UB”,但是从VS2015/Update3开始,我们会有一些。

我应该注意到,即使在那之前,我也会非常小心,因为对于64位代码和32位变量,如果编译器/优化器简单地将32位有符号整数放入64位寄存器中,无论如何你都会有未定义的。(如“如何不编码:未定义的行为比你想象的更接近”中所示——不幸的是,从博客文章中不清楚他是使用VS2015前还是更新后3。)

因此,我对这整个事件的看法是,MSVC一直认为它是UB,即使过去的优化版本没有特别利用这一事实。新的SAA优化器似乎可以做到这一点。(测试< code >–D2 undefintoverflow –开关是否正常工作将会很有趣。)

 类似资料:
  • 未定义行为的一个例子是在flow上的整数行为 有没有一个历史的或者(甚至更好!)造成这种差异的技术原因是什么?

  • 众所周知,有符号整数溢出是一种未定义的行为。但是在C 11文档中有一些有趣的东西: 带符号整数类型,宽度分别为8、16、32和64位,不带填充位,并使用2的补码表示负值(仅在实现直接支持该类型时提供) 参见链接 我的问题是:既然标准明确规定,、、和负数是2的补码,那么这些类型的溢出仍然是一种未定义的行为吗? 编辑我检查了C 11和C11标准,以下是我的发现: C 11,§18.4.1: 标题定义了

  • C99标准中哪里说有符号整数溢出是未定义的行为? 我看到关于无符号整数溢出的评论定义得很好(看看为什么定义了无符号整数溢出行为,但没有定义有符号整数溢出?)在第6.2.5节中: 涉及无符号操作数的计算永远不会溢出,因为不能由结果无符号整数类型表示的结果将被减少为比结果类型可以表示的最大值大一的数的模。 但是我在附录J中查看了未定义的行为,我只在列表中看到了这些类似的项目: 具有有符号提升类型的表达

  • Rust在调试和发布模式下处理有符号整数溢出的方式不同。当它发生时,Rust在调试模式下会恐慌,而在发布模式下会默默地执行两个补码的包装。 据我所知,C/C将有符号整数溢出视为未定义的行为,部分原因是: 在C标准化的时候,表示有符号整数的不同底层架构,例如补码,可能仍在某个地方使用。编译器不能假设硬件中如何处理溢出。 后来的编译器因此做出假设,例如两个正整数的总和也必须是正的,以生成优化的机器代码

  • 我正在读一篇关于整数安全性的文章。以下是链接:http://ptgmedia.pearsoncmg.com/images/0321335724/samplechapter/seacord_ch05.pdf 在第166页,有这样一句话: 涉及无符号操作数的计算永远不会过流,因为不能由结果无符号整数类型表示的结果将被模化为比结果类型可以表示的最大值大一的数字。 这是什么意思?感谢您的回复。

  • 我正在简单的C程序中试验无符号int数据类型和主方法参数。作为一个实验,我写了一个程序,从命令行获取一个int数作为main方法的参数,并对该数和0之间的每个整数求和。 例如,程序计算 f(n) = (1 2 3... n) 当 n 时有效 我开始注意到的第一件事是当f(n) 我手动发现数学上的最大值,我的程序生成的结果将是有效的(例如,在整数溢出之前),对于有符号整数为65535,对于无符号in