不同链接器数组对齐差异:GNU ld与GHS链接器行为解析
ALIGN函数行为差异问题分析 问题背景
在大型嵌入式项目中,尝试通过链接脚本的ALIGN内置函数将数组对齐到16KB(0x4000)边界,未使用C语言的aligned属性:
- C源码定义两个数组:
boo放入自定义段.data_aligned,foo放入默认.data段 - 链接脚本中,
.data段内先放置默认.data内容,再通过ALIGN(0x4000)调整位置计数器后放置.data_aligned段
现象差异
- GNU ld链接后,
boo数组起始地址为0x104000 - GHS链接器链接后,
boo数组起始地址为0x105000
疑问
根据ld文档,位置计数器在段内为相对偏移,.data段起始地址为0x101000,那么ALIGN(0x4000)后位置计数器应为0x101000 + 0x4000 = 0x105000,这与GHS结果一致。疑问点:
- 上述分析是否正确?
- GNU ld为何给出不同结果?
问题分析与解答
1. 初始分析的偏差
你的分析存在关键误解:ALIGN()函数调整的是当前位置计数器的绝对地址,而非段内相对偏移。
GNU ld的ALIGN(alignment)行为是将位置计数器(.)向上调整到最近的、满足alignment对齐要求的绝对地址,而非基于段起始地址的相对偏移计算。
假设.data段起始地址0x101000,默认.data内容(如foo数组)占用0x3000空间,放置完.data后位置计数器的绝对地址是0x101000 + 0x3000 = 0x104000,该地址本身已满足16KB对齐要求,因此ALIGN(0x4000)不会再调整位置计数器,最终.data_aligned段起始地址就是0x104000——这和你观察到的GNU ld结果一致。
而GHS链接器的ALIGN行为可能是强制将位置计数器调整为段起始地址加上对齐值的整数倍,即使当前位置已满足对齐,仍会跳到段起始+对齐值的位置(即0x101000 + 0x4000 = 0x105000),这就导致了两者的差异。
2. 验证方法
可以通过以下方式验证逻辑:
- 用
objdump -h查看目标文件的段大小,计算放置完.data后的位置计数器值 - 修改
.data内容的大小(比如给foo数组增加元素),若GNU ld的boo地址随内容大小变化,说明它是基于当前位置的绝对对齐;若地址始终为0x105000,则符合强制段起始+对齐值的逻辑
3. 实现统一对齐效果的方法
如果需要让GNU ld达到和GHS一致的效果(强制从段起始+16KB位置放置.data_aligned),可修改链接脚本:
.data : { *(.data) . = ORIGIN(.data) + 0x4000; *(.data_aligned) } > RAM
或者用通用计算式确保对齐到段起始后的第一个16KB边界:
.data : { *(.data) _sDataAlign = . + (0x4000 - (. - ORIGIN(.data)) % 0x4000) % 0x4000; . = _sDataAlign; *(.data_aligned) } > RAM
内容的提问来源于stack exchange,提问作者Kalendistrien

