数据结构对齐的任务是由谁完成的?是编译器、链接器、装载器还是像x86那样的硬件本身?编译器是否进行相对对齐寻址,以便在由链接器正确“放置”在已编译的可执行文件中时,数据结构始终对应于各自本地大小边界?此后,装载器还有哪些任务需要完成?
数据结构对齐的任务是由谁完成的?是编译器、链接器、装载器还是像x86那样的硬件本身?编译器是否进行相对对齐寻址,以便在由链接器正确“放置”在已编译的可执行文件中时,数据结构始终对应于各自本地大小边界?此后,装载器还有哪些任务需要完成?
.bss
、.rodata
、.data
还是其他某个部分中。这些部分的对齐方式至少与存储在其中任何对象的最大对齐要求一样大。const
)全局对象,它具有64字节对齐方式,则.rodata
部分的最小对齐方式为64字节,并且链接器将确保满足此要求。objdump -h
查看Algn
列中任何对象文件的实际对齐要求。以下是一个随机示例:Sections:
Idx Name Size VMA LMA File off Algn Flags
0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 00000030 0000000000400298 0000000000400298 00000298 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 00000288 00000000004002c8 00000000004002c8 000002c8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr 00000128 0000000000400550 0000000000400550 00000550 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version 00000036 0000000000400678 0000000000400678 00000678 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version_r 00000050 00000000004006b0 00000000004006b0 000006b0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rela.dyn 00000060 0000000000400700 0000000000400700 00000700 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.plt 00000210 0000000000400760 0000000000400760 00000760 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .init 0000001a 0000000000400970 0000000000400970 00000970 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .plt 00000170 0000000000400990 0000000000400990 00000990 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .plt.got 00000008 0000000000400b00 0000000000400b00 00000b00 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .text 000021e2 0000000000400b10 0000000000400b10 00000b10 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .fini 00000009 0000000000402cf4 0000000000402cf4 00002cf4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE
15 .rodata 00000700 0000000000402d00 0000000000402d00 00002d00 2**5 CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .eh_frame_hdr 000000b4 0000000000403400 0000000000403400 00003400 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA
17 .eh_frame 000003d4 00000000004034b8 00000000004034b8 000034b8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA
18 .init_array 00000008 0000000000603e10 0000000000603e10 00003e10 2**3 CONTENTS, ALLOC, LOAD, DATA
19 .fini_array 00000008 0000000000603e18 0000000000603e18 00003e18 2**3 CONTENTS, ALLOC, LOAD, DATA
20 .jcr 00000008 0000000000603e20 0000000000603e20 00003e20 2**3 CONTENTS, ALLOC, LOAD, DATA
21 .dynamic 000001d0 0000000000603e28 0000000000603e28 00003e28 2**3 CONTENTS, ALLOC, LOAD, DATA
22 .got 00000008 0000000000603ff8 0000000000603ff8 00003ff8 2**3 CONTENTS, ALLOC, LOAD, DATA
23 .got.plt 000000c8 0000000000604000 0000000000604000 00004000 2**3 CONTENTS, ALLOC, LOAD, DATA
24 .data 00000020 00000000006040d0 00000000006040d0 000040d0 2**4 CONTENTS, ALLOC, LOAD, DATA
25 .bss 000001c8 0000000000604100 0000000000604100 000040f0 2**5 ALLOC
26 .comment 00000034 0000000000000000 0000000000000000 000040f0 2**0 CONTENTS, READONLY
2 ** 0
(不需要对齐)到2 ** 5
(在32字节边界上对齐)不等。malloc
和相关函数返回适合任何基本类型对齐的内存(通常在64位系统上仅意味着8字节对齐),尽管当您谈论超对齐类型或C ++ alignas
时,事情会变得更加复杂。
0我最初将(编译时)链接器和(运行时)加载器分为一组,因为它们实际上是同一个硬币的两面(实际上大部分链接实际上是运行时链接)。然而,在更仔细地研究加载过程后,似乎加载器可能只会以其现有文件偏移量加载段(部分),自动遵守链接器设置的对齐方式。
1在像x86这样允许未对齐访问的平台上不太严格,但在对齐限制更严格的平台上,如果遇到不正确的对齐,代码可能会失败。
我认为正确的最短答案是:这是编译器的工作。
这就是为什么有各种 #pragma 和其他编译器级别的魔法旋钮可以控制对齐方式,当必要时可以使用。
我不认为C语言规定了那些其他组件(链接器和加载器)的存在。
foo(double): foo(double):
push ebp lea ecx, [esp+4]
mov ebp, esp and esp, -8
sub esp, 40 push DWORD PTR [ecx-4]
mov eax, DWORD PTR [ebp+8] push ebp
mov DWORD PTR [ebp-40], eax mov ebp, esp
mov eax, DWORD PTR [ebp+12] push ecx
mov DWORD PTR [ebp-36], eax sub esp, 20
fld1 mov eax, ecx
fstp QWORD PTR [ebp-8] fld1
fld QWORD PTR [ebp-40] fstp QWORD PTR [ebp-16]
fstp QWORD PTR [ebp-16] fld QWORD PTR [eax]
fld QWORD PTR [ebp-8] fstp QWORD PTR [ebp-24]
fmul QWORD PTR [ebp-16] fld QWORD PTR [ebp-16]
leave fmul QWORD PTR [ebp-24]
ret add esp, 20
pop ecx
pop ebp
lea esp, [ecx-4]
ret
如果一个对象 a 相对于一个 Y对齐 的对象 b 是 X对齐 的,且 X | Y (Y 是 X 的倍数),那么相对于同一参考系的 b,a 也是 X对齐 的。
例如,PE/ELF 文件中的节(以及某些 malloc
缓冲区)可以在特定边界(8 字节、16 字节、4KiB 等)上对齐加载。
如果一个节被对齐加载到了 4KiB,则一旦它在内存中,所有的二次幂对齐都会自动得到尊重,即使它们是相对于该节的起始处取的,不管该节加载到哪里。
在长度为 2X-1 的缓冲区 B 中,至少有一个地址 A 是 X对齐 的,并且 2X-1 - (A-B) >= X (它有足够的空间来容纳大小为 X 的对象)。
如果您需要将一个长度为 8 字节的对象对齐到 8 字节边界,并且该对象通常是 8 字节长的,则分配一个 16-1 = 15 字节的缓冲区将保证适当的地址存在于缓冲区的每个可能的起始地址处。
1 给出,不要过多解释。
malloc
绝对不会将所有分配都对齐到4K:这意味着每个分配至少需要那么多的空间!实际上,小的分配通常对齐到8或16字节。因此,malloc
通常不会返回适合于“超对齐”对象的内存(例如,您使用了#pragma align
或其他对齐属性来增加对齐)。 - BeeOnRopereadelf -s
显示的那样),但仍然是编译器在合适的对齐方式上对链接器进行调整,所以我喜欢将其视为一种中立功能(就像“and”指令一样)。感谢您的反馈! - Margaret Bloom