Since 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
符号(该值等于符号位置的地址)到低32位RBX
寄存器(高 32 位清零)。这bar.1535
符号本身是使用分配的.comm
指示。内存为baz
随后分配 COMMON 块。因为bar.1535
非常大,baz_
从开始到结束超过 2 GiB.bss
部分。这在第二个中提出了问题movl
指令,因为非 32 位(有符号)偏移量来自RIP
应该用来解决b
变量,其中的值EAX
必须搬进去。这仅在链接期间检测到。汇编器本身不知道适当的偏移量,因为它不知道指令指针的值是多少(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 字节长)来放置表示地址的 64 位值bar.1535
进入寄存器R10
。内存为bar.1535
符号是使用分配.largecomm
指令,因此它结束于.lbss
ELF 可执行文件的部分。.lbss
用于存储可能不适合前 2 GiB 的符号(因此不应使用 32 位指令或 RIP 相对寻址来寻址),而较小的东西则用于存储.bss
(baz_
仍然使用分配.comm
并不是.largecomm
)。自从.lbss
部分放置在.bss
ELF 链接器脚本中的部分,baz_
使用 32 位 RIP 相关寻址不会最终导致无法访问。
所有寻址模式均在System V ABI:AMD64 架构处理器补充。这是一本厚重的技术读物,但对于真正想了解 64 位代码如何在大多数 x86_64 Unix 上工作的人来说,这是必读的。
When an ALLOCATABLE
而是使用数组,gfortran
分配堆内存(考虑到分配的大小,最有可能实现为匿名内存映射):
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
这基本上是RDI = malloc(2575411200)
。从此以后的元素bar
通过使用存储在中的值的正偏移量来访问RDI
:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
对于从开始时起超过 2 GiB 的位置bar
,使用更复杂的方法。例如。实施b = bar(12,144*144*450)
gfortran
emits:
; 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)
where boo
有一个接口将其声明为采用假定形状数组)。它移动数组描述符的地址bar
into EDI
。这是一个 32 位立即移动,因此描述符预计位于前 2 GiB 中。事实上,它被分配在.bss
同时small
and medium
内存模型如下:
.local bar.1580
.comm bar.1580,72,32