假设我使用C语言创建了一个程序,并在常规的64位台式机上对其进行了编译:

#include <stdint.h>

int main(void)
{
    uint64_t a = 0x12345679abcdefULL;
    uint64_t b = 0xfdcba987654321ULL;
    uint64_t c = a + b;
    return 0;
}


我刚刚使用gcc -O0 -S测试了代码。似乎在堆栈上分配了两个32位值,然后分别与addladcl相加。即使我使用的是64位计算机,-m32-m64切换也会发生这种情况,并且确实确实存在一个专用于64位整数的指令集,包括movqaddq之类的操作。



为什么我告诉gcc生成类似32位计算机的代码,即使我告诉它在-m64上使用64位算术?

>uname -a
CYGWIN_NT-6.2-WOW work 1.7.35(0.287/5/3) 2015-03-04 12:07 i686 Cygwin

>gcc --version
gcc (GCC) 4.9.2



现在,假设我在32位计算机上,并且我对64位整数进行操作。


该程序在上分配了两个32位变量堆栈。这是C / C ++标准的一部分,还是正在将它们分配给某些编译器所期望的堆(因为它们尝试以其他方式将64位整数适合32位堆栈)?
如果我将movq放入专为32位计算机设计的程序中,则会仿真所需的行为,还是会误解该指令?


#1 楼

1:

请参见此64位汇编参考上的“立即数”部分。


指令中的立即数保留为​​32位,并且其值符号扩展为64位


没有指令将64位立即数移动到内存(*)。而且由于可以通过移动两个32位块来很好地模拟它,因此没有理由引入新的64位指令。 (他们可以将立即数mov更改为始终使用64位。但是考虑到您将使用的大多数常量是<= 2 ^ 31,使用32位仅会节省大量零位高字节空间,并且花费了当您实际使用较大的常数时,此位可以节省内存)。

(*)但是,有些指令将64位立即数移动到64位寄存器,因为您无法访问

我不知道为什么您的程序生成单独的addl / adcl指令;为什么?这是我从您的程序中得到的:

    pushq   %rbp
    movq    %rsp, %rbp
    movl    41302511, -24(%rbp)
    movl    93046, -20(%rbp)
    movl    $-2023406815, -16(%rbp)
    movl    632745, -12(%rbp)
    movq    -16(%rbp), %rax
    movq    -24(%rbp), %rdx
    leaq    (%rdx,%rax), %rax
    movq    %rax, -8(%rbp)
    movl    q4312078q, %eax
    leave
    ret


您可以看到,leaq (%rdx,%rax), %rax可以添加64位数字。这是RHEL 6.6 64位系统上的gcc 4.4.7。请始终声明您的编译器和OS版本,因为输出可能完全取决于它们。

2:

只要您使用的是x86 / amd64架构,您就可以依赖于放在堆栈中的局部变量,而不是放在堆栈中的全局变量。但是请注意,“堆栈”和“堆”的概念并未像看起来那样明确定义。不建议使用brk/sbrk分配内存的机制;现代实现使用mmap。这可能意味着您在地址空间的不同部分中有几个小堆。在ARM和MIPS上,根本没有堆栈指针-只是一个约定,即一个特定的寄存器用作堆栈指针,但是压入/弹出指令也可以与其他寄存器一起使用(*)。从理论上讲,编译器可以在每个函数的开头随意分配一个mmap()来分配本地内存,并在函数的结尾随意分配一个munmap()。编译器唯一要做的就是在函数退出后不保留分配的内存(用于分配的合理定义)。

(*)这有点过分简化,但是说明了这个概念。

当然,使用mmap()为局部变量腾出空间的想法是一个极端的例子,可能没人会使用。但是许多编译器将局部变量放入处理器寄存器中,并且从不为它们保留堆栈空间(如果您从未使用过指向它们的指针,以及在那些不像x86那样缺乏寄存器的架构上)。许多架构也将处理器寄存器用于函数参数。而且我见过微控制器C编译器,如果您使用某个关键字,则可以将函数本地的所有变量放入静态区域,这样编译器就知道该函数不是递归调用的。因此,尽管在大多数情况下,局部变量将被放置在堆栈上,但您不应该假定它是刻在石头上的。

3.

该说明将被误解。处理器可以处于32位或64位模式,并且相同的指令(含义为:相同的指令字节序列)在每个指令中具有不同的含义。例如,48 89 43 ec在64位模式下为mov [rbx-20],rax,但是dec eax; mov DWORD PTR [ebx-0x14],eax在32位模式下。

评论


非常好的介绍答案,谢谢!还有两个问题:关于1-关于gcc如何具体决定使用哪个指令集(64/32位),我能读懂什么吗?关于2-使用mmap声音要比移动堆栈指针(是否标准化)昂贵得多。 ARM或MIPS编译器是否偏离您提到的约定?

– rr-
15年3月13日在15:04



gcc使用哪种指令集取决于-m32或-m64。使用该指令集中的确切指令取决于(至少)编译器版本号和优化。除了源代码本身,我认为没有关于此的全面文档。我对局部变量约定做了一些修改。

–贡特拉姆·布洛姆(Guntram Blohm)
15年3月13日在16:37