由于bar 相当大,编译器生成静态分配而不是堆栈上的自动分配。静态数组是使用.comm 汇编指令创建的,该指令在所谓的 COMMON 部分中创建分配。收集该部分的符号,合并同名符号(减少到一个符号请求,其大小等于请求的最大大小),然后以大多数可执行格式将其余部分映射到 BSS(未初始化数据)部分。对于 ELF 可执行文件,.bss 部分位于数据段中,就在堆的数据段部分之前(还有另一个由匿名内存映射管理的堆部分,它不驻留在数据段中)。
在small 内存模型中,32 位寻址指令用于寻址 x86_64 上的符号。这使得代码更小也更快。使用small内存模型时的一些汇编输出:
movl $bar.1535, %ebx <---- Instruction length saving
...
movl %eax, baz_+4(%rip) <---- Problem!!
...
.local bar.1535
.comm bar.1535,2575411200,32
...
.comm baz_,12,16
这使用32位移动指令(5字节长)将bar.1535符号的值(该值等于符号位置的地址)放入RBX寄存器的低32位(高 32 位清零)。 bar.1535 符号本身是使用 .comm 指令分配的。 baz COMMON 块的内存是在之后分配的。因为bar.1535 非常大,所以baz_ 最终从.bss 部分的开始超过2 GiB。这在第二条movl 指令中产生了一个问题,因为应该使用与RIP 的非32 位(有符号)偏移量来寻址EAX 的值必须移入其中的b 变量。这仅在链接时检测到。汇编器本身不知道适当的偏移量,因为它不知道指令指针 (RIP) 的值是什么(它取决于加载代码的绝对虚拟地址,这由链接器确定) ,所以它只是简单地放置一个0 的偏移量,然后创建一个R_X86_64_PC32 类型的重定位请求。它指示链接器使用实际偏移值修补0 的值。但它不能这样做,因为偏移值不适合有符号的 32 位整数,因此会退出。
使用 medium 内存模型后,情况如下所示:
movabsq $bar.1535, %r10
...
movl %eax, baz_+4(%rip)
...
.local bar.1535
.largecomm bar.1535,2575411200,32
...
.comm baz_,12,16
首先使用64位立即移动指令(10字节长)将代表bar.1535地址的64位值放入寄存器R10。 bar.1535 符号的内存是使用 .largecomm 指令分配的,因此它在 ELF 可执行文件的 .lbss 部分结束。 .lbss 用于存储可能不适合前 2 GiB 的符号(因此不应使用 32 位指令或 RIP 相对寻址来寻址),而较小的东西转到 .bss(baz_ 仍然使用.comm 而不是.largecomm 分配)。由于在 ELF 链接描述文件中 .lbss 部分位于 .bss 部分之后,因此 baz_ 不会最终无法使用 32 位 RIP 相关寻址。
System V ABI: AMD64 Architecture Processor Supplement 中描述了所有寻址模式。这是一本繁重的技术读物,但对于真正想了解 64 位代码如何在大多数 x86_64 Unix 上工作的任何人来说都是必读的。
当改用 ALLOCATABLE 数组时,gfortran 分配堆内存(考虑到分配的大小,很可能实现为匿名内存映射):
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
这基本上是RDI = malloc(2575411200)。从那时起,bar 的元素通过使用存储在RDI 中的值的正偏移来访问:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
对于距离bar 开头超过 2 GiB 的位置,使用更精细的方法。例如。实现b = bar(12,144*144*450) gfortran 发出:
; Some computations that leave the offset in RAX
movl (%rdi,%rax), %eax
movl %eax, baz_+4(%rip)
此代码不受内存模型的影响,因为没有假设要进行动态分配的地址。此外,由于没有传递数组,因此没有构建描述符。如果您添加另一个函数,该函数采用假定形状的数组并将bar 传递给它,则bar 的描述符被创建为自动变量(即在foo 的堆栈上)。如果使用SAVE 属性将数组设为静态,则描述符将放在.bss 部分中:
movl $bar.1580, %edi
...
; RAX still holds the address of the allocated memory as returned by malloc
; Computations, computations
movl -232(%rax,%rdx,4), %eax
movl %eax, baz_+4(%rip)
第一步准备函数调用的参数(在我的示例案例call boo(bar) 中,boo 有一个接口,声明它采用假定形状的数组)。它将bar 的数组描述符的地址移动到EDI。这是一个 32 位立即移动,因此描述符应该在前 2 GiB 中。实际上,它在small 和medium 内存模型中都分配在.bss 中,如下所示:
.local bar.1580
.comm bar.1580,72,32