GCC __attribute__ 和 link 脚本控制 section 基地址

本文介绍了如何使用GCC的__attribute__属性来控制数据区的基地址,并通过link脚本进一步调整section的基地址。通过具体代码示例和编译过程展示,实现了变量在不同section中的定位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GCC __attribute__ 和 link 脚本控制 section 基地址

网上看的一篇文章,感谢作者,另外加上自己的一点注释。
... ... ... ... .... .... .....

利用 GCC 的 __attribute__ 属性的 section 选项来控制数据区的基地址。
以下例子,主要涉及到两个知识点,一个是 GNU C 扩展中的 attribute section 属性,关于这个知识点的相关信息可以参考: http://www.groad.net/bbs/read.php?tid=1035
另外一个知识点是 ld 连接器所用到的 link 脚本相关知识。

测试代码
#include <stdio.h>
int localmemory0 __attribute__(( section( "LOCALmem"))) = 0;
int localmemory1 __attribute__(( section( "LOCALmem"))) = 0;
int globalmemory __attribute__(( section( "GLOBALmem"))) = 0;
int main ( int argc , char * argv [])
{
localmemory0 = 0x456;
localmemory1 = 0x123;
globalmemory = localmemory0 + localmemory1;
}

在上面的代码中,定义了两个非传统的 section : LOCALmem 和 GLOBALmem 。
程序里要求变量 localmemory0 和 localmemory1 存放在 section LOCALmem 中,而 globalmemory 存放在 section GLOBALmem 中。

下面编译程序
beyes@linux-beyes:~/C/GNU_C_EXT> gcc -c -o elf_section.o elf_section.c

或者
beyes@linux-beyes:~/C/GNU_C_EXT> gcc -c -o elf_section2.o -fdata-sections elf_section.c

-fdata-sections 选项的目的是让编译器为每一个单独申明的数据 section 实际分配一个 section,而不是占用 .bss ,在某些系统上需要显式的使用这一参数。

使用 objdump 命令查看生成的 elf_section.o 文件
beyes@linux-beyes:~/C/GNU_C_EXT> objdump -S elf_section.o

elf_section.o: file format elf32-i386


Disassembly of section .text:

00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 04 sub $0x4,%esp
11: c7 05 00 00 00 00 56 movl $0x456,0x0
18: 04 00 00
1b: c7 05 00 00 00 00 23 movl $0x123,0x0
22: 01 00 00
25: a1 00 00 00 00 mov 0x0,%eax
2a: 8b 15 00 00 00 00 mov 0x0,%edx
30: 01 d0 add %edx,%eax
32: a3 00 00 00 00 mov %eax,0x0
37: 83 c4 04 add $0x4,%esp
3a: 59 pop %ecx
3b: 5d pop %ebp
3c: 8d 61 fc lea -0x4(%ecx),%esp
3f: c3 ret

上面的 objdump 使用了 -S 选项,这是查看目标文件反汇编代码的一个选项;对此,也可以使用 -d 选项,并建议使用 -d 选项。
上面加蓝色高亮的两条 movl 指令分别将数据存放到 localmemory0 和 localmemroy1 ;加粉红色高亮的 mov 指令将两个变量相加的结构存放到 globalmemory 中。
由于是目标文件,变量符号对应的地址没有经过解析和分配,所以指令变量中对应的地址都是 0x0 。

下面,使用一个 link 脚本来控制连接器 ld 输出 section 的己地址(像linux的内核源码树中的 arch/arm/vmlinux-armv.lds.in 文件也是这样的脚本文件<这里是ARM平台,编译内核后的文件)。
文件内容如下:
SECTIONS
{
.text : {

*(.text)
}

LOCALmem 0x1f0000 : {

*(LOCALmem)
}

GLOBALmem 0xff0000 : {

*(GLOBALmem)
}

}

用下面的命令观察生成的 elf 文件内容:
beyes@linux-beyes:~/C/GNU_C_EXT> objdump -S elf_section.elf

elf_section.elf: file format elf32-i386


Disassembly of section .text:

00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: c7 05 00 00 1f 00 56 movl $0x456,0x1f0000
15: 04 00 00
18: c7 05 04 00 1f 00 23 movl $0x123,0x1f0004
1f: 01 00 00
22: 8b 15 00 00 1f 00 mov 0x1f0000,%edx
28: a1 04 00 1f 00 mov 0x1f0004,%eax
2d: 8d 04 02 lea (%edx,%eax,1),%eax
30: a3 00 00 ff 00 mov %eax,0xff0000
35: b8 00 00 00 00 mov $0x0,%eax
3a: 59 pop %ecx
3b: 5d pop %ebp
3c: 8d 61 fc lea -0x4(%ecx),%esp
3f: c3 ret

说明
ld 命令的选项 -T ,等同于选项 -c ,这是告诉 ld 从 -c 后面的文件(commandfile) 中读取连接命令。这些命令彻底的覆盖 ld 的缺省连接格式 (不是添加);commandfile 必须详尽的描述目标格式的所有细节。
在两个加了蓝色高亮的语句中,可以看到,0x456 和 0x123 两数已经分别搬往 0x1f0000 与 0x1f0004 两个地址。

使用 objdump -s 来查看一下生成的 elf 文件:
beyes@linux-beyes:~/C/GNU_C_EXT> objdump -s elf_section.elf

elf_section.elf: file format elf32-i386

Contents of section .text:
0000 8d4c2404 83e4f0ff 71fc5589 e551c705 .L$.....q.U..Q..
0010 00001f00 56040000 c7050400 1f002301 ....V.........#.
0020 00008b15 00001f00 a104001f 008d0402 ................
0030 a30000ff 00b80000 0000595d 8d61fcc3 ..........Y].a..
Contents of section LOCALmem:
1f0000 00000000 00000000 ........
Contents of section GLOBALmem:
ff0000 00000000 ....
Contents of section .comment:
0000 00474343 3a202853 55534520 4c696e75 .GCC: (SUSE Linu
0010 78292034 2e332e32 205b6763 632d345f x) 4.3.2 [gcc-4_
0020 332d6272 616e6368 20726576 6973696f 3-branch revisio
0030 6e203134 31323931 5d00 n 141291].
Contents of section .comment.SUSE.OPTs:
0000 6f737077 6700 ospwg.

上面命令中, -s 选项表示显示任何一个 section 的所有内容。在默认情况下,所有非空 section 都要显示。

由上可见,section LOCALmem 从 0x1f0000 开始,而 section GLOBALmem 从 0xff0000 开始,程序正文段 .text 从 0x0 地址开始。
<think>好的,我需要回答用户关于C语言常量值是否存储在Flash中的问题。首先,我得回忆一下C语言中常量的存储方式。C语言中的常量有很多种,比如整数常量、浮点常量、字符串常量,还有用const修饰的变量。 用户的问题可能涉及到不同的常量类型,所以需要分情况讨论。首先,const修饰的变量通常被认为是只读的,但根据C标准,它们不一定被存储在Flash里。实际存储位置取决于编译器目标平台。比如在嵌入式系统中,为了节省RAM,编译器可能会把const变量放在Flash中。但在桌面环境,可能还是放在内存里,只是设为只读。 然后是字符串常量,这部分通常存储在程序的只读数据段,比如.rodata。在嵌入式系统中,.rodata段可能被链接到Flash区域,这样运行时直接从Flash读取,节省RAM。不过有些情况下,启动代码可能会在初始化时将这部分数据复制到RAM中,特别是当Flash访问速度较慢时。 接下来是普通的常量,比如#define定义的宏。这些在预处理阶段就被替换成具体的值,不会占用存储空间,所以不需要存储。但是如果是用const定义的变量,或者枚举常量,情况又不同了。枚举常量在编译时会被替换成整数,同样不占存储空间。 还需要考虑不同平台的情况。比如在Arduino这样的微控制器上,FlashRAM是分开的,使用PROGMEM关键字可以将数据显式存储在Flash中。这时候,即使用const声明,如果没有PROGMEM,可能还是会被放在RAM里。所以这取决于具体的编译器扩展配置。 另外,链接脚本linker script)也会影响存储位置。开发人员可以自定义链接脚本,将特定的段分配到Flash或RAM中。因此,常量的存储位置并不是绝对的,需要结合编译器硬件环境来分析。 总结起来,C语言中的常量存储位置不是固定的,主要看类型、编译器选项目标平台。大多数情况下,const变量字符串常量在嵌入式系统中会存储在Flash,而在其他环境中可能放在内存的只读段。用户如果需要精确控制存储位置,应该查阅具体编译器硬件平台的文档,或者使用特定的关键字修饰符。</think>在C语言中,常量值的存储位置取决于常量的类型、编译器实现以及目标硬件平台(尤其是嵌入式系统)。以下是逐步分析: --- ### 1. **常量的分类** - **字面常量**:如 `5`, `3.14`, `"Hello"`(字符串常量)。 - **`const`修饰的变量**:如 `const int a = 10;`。 - **枚举常量**:如 `enum { MAX = 100 };`。 - **预处理宏**:如 `#define PI 3.1415`。 --- ### 2. **存储位置分析** #### (1) **字面常量** - **数值常量**(如 `5`, `3.14`): 直接嵌入指令中(编译后成为机器码的一部分),通常不单独占用存储空间。 - **字符串常量**(如 `"Hello"`): 存储在程序的**只读数据段(.rodata)**。在嵌入式系统中,`.rodata` 段通常位于 **Flash** 中;在通用计算机中,可能映射到内存的只读页。 #### (2) **`const`修饰的变量** - **标准C语言规范**: `const` 仅表示变量“只读”,不强制要求存储位置。编译器可能将其放在内存(RAM)或 Flash 中。 - **嵌入式系统实践**: 为了节省 RAM,编译器(如 GCC 的 `-fdata-sections` 选项)通常将 `const` 变量分配到 Flash 的 `.rodata` 段。 - **例外情况**: 若需运行时修改(如通过指针强制写入),则必须存储在 RAM 中。 #### (3) **枚举常量预处理宏** - **枚举常量**:编译时被替换为整数值,不占用存储空间。 - **预处理宏**:在预处理阶段被替换,不分配存储。 --- ### 3. **嵌入式系统的特殊处理** - **显式指定 Flash 存储**: 某些编译器提供扩展语法(如 Arduino 的 `PROGMEM` 关键字),强制将常量存储在 Flash 中。 示例: ```c const uint8_t data[] PROGMEM = {0x01, 0x02}; // 显式存储在 Flash ``` - **链接脚本控制**: 通过自定义链接脚本Linker Script),可指定 `.rodata` 段映射到 Flash 地址空间。 --- ### 4. **通用计算机 vs 嵌入式系统** | 场景 | 存储位置 | |--------------------|----------------------------| | 通用计算机(如 PC) | 常量可能存储在内存的只读段 | | 嵌入式系统(如 MCU)| 常量通常存储在 Flash | --- ### 5. **验证方法** - **查看编译生成的 map 文件**: 通过分析链接器生成的 map 文件,确认常量的实际存储地址(Flash 或 RAM)。 - **反汇编代码**: 使用工具(如 `objdump`)查看汇编代码,判断常量是否被嵌入指令或存储在特定段。 --- ### 总结 - **字符串常量** **`const`变量**通常存储在 Flash(嵌入式系统)或内存只读段(通用计算机)。 - 若需精确控制存储位置,需依赖编译器扩展(如 `PROGMEM`)或链接脚本配置。 - 预处理宏枚举常量不占用存储空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值