不同编译器用于处理数字转换溢出的常用策略是什么?

day*_*oli 4 c++ compiler-construction integer-overflow type-conversion floating-point-conversion

据我所知,在C++中,当我转换float/ doubleint,由此浮点数超出该范围内int可容纳,则结果没有被定义为C++语言的一部分.结果取决于实现/编译器.常见的编译器使用什么策略来处理这个问题?

转换7.2E12为a int可以产生值16348119042147483647.例如,有没有人知道编译器在每种情况下做了什么?

Pas*_*uoq 10

编译器生成指令序列,为不会导致溢出的所有输入生成正确的结果.这是它必须担心的(因为从浮点到整数的转换中的溢出是未定义的行为).编译器不会"处理"溢出,而是完全忽略它们.如果平台上的基础汇编指令引发异常,那很好.如果它们环绕,那很好.如果他们产生荒谬的结果,再次,罚款.


例如,常量表达式可以在编译时转换为整数,其规则与平台上生成的汇编指令的行为不同.我的博文给出了一个例子:

int printf(const char *, ...);

volatile double v = 0;

int main()
{
  int i1 = 2147483648.0;
  int i2 = 2147483648.0 + v;
  printf("%d %d\n", i1, i2);
}
Run Code Online (Sandbox Code Playgroud)

它生成一个程序,为i1和打印两个不同的值i2.这是因为计算中的转换i1是在编译时应用的,而计算中的转换i2是在运行时应用的.


作为另一个例子,在x86-64平台上从double32位转换为32位的特定情况下unsigned int,结果可能很有趣:

x86指令集中没有指令可以将浮点数转换为无符号整数.

在用于Intel的Mac OS X上,编译64位程序时,从单个指令编译从32位到32位无符号int的转换:64位转换指令cvttsd2siq,目标为64位寄存器,仅限64位寄存器底部的32位随后将用作它代表的32位无符号整数:

$ cat t.c
#include <stdio.h>
#include <stdlib.h>
int main(int c, char **v)
{
  unsigned int i = 4294967296.0 + strtod(v[1], 0);
  printf("%u\n", i);
}
$ gcc -m64 -S -std=c99 -O t.c && cat t.s
…
addsd LCPI1_0(%rip), %xmm0 ; this is the + from the C program
cvttsd2siq %xmm0, %rsi ; one-instruction conversion
…
Run Code Online (Sandbox Code Playgroud)

这就解释了如何,在该平台上,其结果是模2 32可以用于双打足够小(具体地,小到足以在一个符号的64位整数)来获得.

在旧的IA-32指令集中,没有指令将a转换double为64位有符号整数(并且没有指令将a转换double为32位unsigned int).32位转换unsigned int必须通过结合几个确实存在的指令,包括两个指令来完成cvttsd2si,从双转换为32位有符号整数:

$ gcc -m32 -S -std=c99 -O t.c && cat t.s
…
addsd LCPI1_0-L1$pb(%esi), %xmm0 ; this is the + from the C program
movsd LCPI1_1-L1$pb(%esi), %xmm1 ; conversion to unsigned int starts here
movapd %xmm0, %xmm2
subsd %xmm1, %xmm2
cvttsd2si %xmm2, %eax
xorl $-2147483648, %eax
ucomisd %xmm1, %xmm0
cvttsd2si %xmm0, %edx
cmovael %eax, %edx
…

分别在%eax和中计算两个替代解决方案%edx.替代方案在不同的定义域上都是正确的.如果要转换的数字in %xmm0大于常数2 31 in %xmm1,则选择一个替代,否则,另一个选择.仅使用从double到int的转换的高级算法将是:

if (d < 231)
then (unsigned int)(int)d
else (231 + (unsigned int)(int)(d - 231))

将C转换从double转换为unsigned int会产生与它依赖的32位转换指令相同的饱和行为:

$ gcc -m32 -std=c99 -O t.c && ./a.out 123456
0
Run Code Online (Sandbox Code Playgroud)

  • @dayuloli我想你有它.一个单独的汇编指令通常具有已定义的行为(汇编比C或C++更彻底地定义).如果编译器始终生成此单个汇编指令,则可以预期底层指令的行为将应用于源级溢出.不幸的是,源代码到汇编的转换比这更复杂."常量折叠"优化可能意味着转换是在编译时完成的,在这种情况下,编译器不需要匹配运行时行为.有时一条指令会...... (2认同)