无符号整数转无符号长整型是否定义良好?

5

我想看看当一个 unsigned long long 被赋值为一个 unsigned int 时,背后发生了什么。我写了一个简单的 C++ 程序来尝试它,并将所有的 io 移出了 main():

#include <iostream>
#include <stdlib.h>

void usage() {
        std::cout << "Usage: ./u_to_ull <unsigned int>\n";
        exit(0);
}

void atoiWarning(int foo) {
        std::cout << "WARNING: atoi() returned " << foo << " and (unsigned int)foo is " <<
 ((unsigned int)foo) << "\n";
}

void result(unsigned long long baz) {
        std::cout << "Result as unsigned long long is " << baz << "\n";
}

int main(int argc, char** argv) {
        if (argc != 2) usage();

        int foo = atoi(argv[1]);
        if (foo < 0) atoiWarning(foo);

        // Signed to unsigned
        unsigned int bar = foo;

        // Conversion
        unsigned long long baz = -1;
        baz = bar;

        result(baz);

        return 0;
}

生成的汇编代码对于主程序产生了这个结果:
0000000000400950 <main>:
  400950:       55                      push   %rbp
  400951:       48 89 e5                mov    %rsp,%rbp
  400954:       48 83 ec 20             sub    $0x20,%rsp
  400958:       89 7d ec                mov    %edi,-0x14(%rbp)
  40095b:       48 89 75 e0             mov    %rsi,-0x20(%rbp)
  40095f:       83 7d ec 02             cmpl   $0x2,-0x14(%rbp)
  400963:       74 05                   je     40096a <main+0x1a>
  400965:       e8 3a ff ff ff          callq  4008a4 <_Z5usagev>
  40096a:       48 8b 45 e0             mov    -0x20(%rbp),%rax
  40096e:       48 83 c0 08             add    $0x8,%rax
  400972:       48 8b 00                mov    (%rax),%rax
  400975:       48 89 c7                mov    %rax,%rdi
  400978:       e8 0b fe ff ff          callq  400788 <atoi@plt>
  40097d:       89 45 f0                mov    %eax,-0x10(%rbp)
  400980:       83 7d f0 00             cmpl   $0x0,-0x10(%rbp)
  400984:       79 0a                   jns    400990 <main+0x40>
  400986:       8b 45 f0                mov    -0x10(%rbp),%eax
  400989:       89 c7                   mov    %eax,%edi
  40098b:       e8 31 ff ff ff          callq  4008c1 <_Z11atoiWarningi>
  400990:       8b 45 f0                mov    -0x10(%rbp),%eax
  400993:       89 45 f4                mov    %eax,-0xc(%rbp)
  400996:       48 c7 45 f8 ff ff ff    movq   $0xffffffffffffffff,-0x8(%rbp)
  40099d:       ff
  40099e:       8b 45 f4                mov    -0xc(%rbp),%eax
  4009a1:       48 89 45 f8             mov    %rax,-0x8(%rbp)
  4009a5:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  4009a9:       48 89 c7                mov    %rax,%rdi
  4009ac:       e8 66 ff ff ff          callq  400917 <_Z6resulty>
  4009b1:       b8 00 00 00 00          mov    $0x0,%eax
  4009b6:       c9                      leaveq
  4009b7:       c3                      retq

C++ 中的 -1 明确表明 -0x8(%rbp) 对应于 baz(因为 $0xffffffffffffffff)。-0x8(%rbp) 的值由 %rax 写入,但是 %rax 的前四个字节似乎没有被赋值,只有 %eax 被赋值。
这是否意味着 -0x8(%rbp) 的前4个字节是未定义的?

@哈罗德,我认为需要一个显式的 cltq - asimes
结果函数的输出是什么? - abcthomas
1
cltq 是用于符号扩展的。普通的写操作(包括任何 32 位修改,例如 or eax, 0 和甚至 mov eax, eax)使用零扩展。 - harold
@MarkB,我认为Harold的评论很好地回答了这个问题,尽管我想在某个地方看到它被记录下来。我猜C++的保证也是不错的。编译为g++ u_to_ull.c -o u_to_ull - asimes
1
为什么大多数x64指令会将32位寄存器的高位清零? - phuclv
显示剩余3条评论
2个回答

5
Intel® 64和IA-32体系结构软件开发手册的第1卷第3.4.1.1章节(64位模式下的通用寄存器)中,它说:“32位操作数生成32位结果,在目标通用寄存器中零扩展为64位结果。”因此,在mov -0xc(%rbp),%eax之后,rax的上半部分被定义为零。这也适用于xchg eax,eax的87 C0编码,但不适用于其90编码(定义为nop,覆盖了上述规则)。

太好了,谢谢。我想肯定有些事情我错过了,否则疯狂的值最终会出现。 - asimes

3
从C++98(C++11似乎没有更改)4.7/2(整数转换-无升级相关)我们了解到:
如果目标类型是无符号的,则结果值是与源整数同余的最小无符号整数(模2n,其中n是用于表示无符号类型的位数)。
这清楚地表明,只要源和目标都是无符号的,且目标至少与源一样大,则该值将保持不变。如果编译器生成的代码未能使较大的值相等,则编译器有缺陷。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接