通过未初始化全局变量,研究BSS段和COMMON段的不同

本文探讨了在C语言编程中,BSS段与COMMON段的区别及其对全局变量的影响。通过对两个不同初始化状态的全局变量进行实验,揭示了强符号与弱符号在链接过程中的行为差异。
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net

最近正在重温《程序员的自我修养》一书,由于水平比以前有所提升,所以读书的收获也不一样。

下面针对该书3.3.3节BSS段的内容进行更细节的探讨——该节内容不在本文中重复说明了,只说一下结论。对于全局变量来说,如果初始化了不为0的值,那么该全局变量则被保存在data段,如果初始化的值为0,那么将其保存在bss段,如果没有初始化,则将其保存在common段,等到链接时再将其放入到BSS段。关于第三点不同编译器行为会不同,有的编译器会把没有初始化的全局变量直接放到BSS段。

关于上面这个结论,我就不重复进行探讨了,下面探讨一下更细节点的问题。从上面的内容上看,尽管未初始化的全局变量有可能在编译阶段被保存在common段,但是最终还是会放到BSS段。那么我们是否可以将未初始化的全局变量与初始为0的全局变量等同起来呢?

请看下面的代码:
文件test1.c:
  1. #include <stdio.h>

  2. int init = 0;

  3. void init1()
  4. {
  5.     if (== init) {
  6.         init = 1;
  7.         printf("init1\n");
  8.     }
  9. }
编译gcc -g -c test1.c
文件test2.c:
  1. #include <stdio.h>

  2. int init = 1;

  3. void init2()
  4. {
  5.     if (init) {
  6.         init = 0;
  7.         printf("init2\n");
  8.     }
  9. }
编译gcc -g -c test2.c
文件main.c
  1. void init1();
  2. void init2();


  3. int main()
  4. {
  5.     init1();
  6.     init2();

  7.     return 0;
  8. }
编译gcc -g -o test main.c test1.o test2.o
这时会报错
  1. test2.o:(.data+0x0): multiple definition of `init'
  2. test1.o:/root/work/test/test1.c:4: first defined here
  3. collect2: ld returned 1 exit status
这个报错大家都可以接受吧?OK,那么现在我们做一个小小的改动,将文件test1.c中的int init = 0改为int init,对于test1.c来说,这个改动不影响其逻辑,因为init如果未初始化,其值也应该是0。

当前的test1.c
  1. #include <stdio.h>

  2. int init;

  3. void init1()
  4. {
  5.     if (== init) {
  6.         init = 1;
  7.         printf("init1\n");
  8.     }
  9. }
编译gcc -g -c test1.c
gcc -g -Wall -o test main.c test1.o test2.o
这次即使使用-Wall打开所以warning,也没有任何警告和错误,已经生成了输出文件test。
执行test
  1. [root@Lnx99 test]#./test
  2. init2

好,下面探讨一下为什么是这样。第一种情况,当test1.c中的init被初始化为0时,尽管init被放置在bss段,但是它是一个强符号。而test2.c中,定义了init为1,也是一个强符号,所以引发了错误。第二种情况,当test1.c中的init不进行初始化,尽管其值仍然为0,但是其被保存在common段,为一个弱符号。当test2.c中定义了init为1一个强符号,那么在链接的过程中,gcc会用这个强符号覆盖掉弱符号,并不会引起链接冲突错误。但是在运行阶段,进入init1时,这个init的值却并不是其所期望的值,因此导致没有打印init1。

我想关于BSS与COMMON段的这点不同,应该讲得比较清楚了。从这个区别中,我们需要注意,当定义全局变量时,有两点需要注意:一,如果只有本文件使用,那么需要添加上static;二,即使不能使用static,那么一定要为该全局变量定义初值,即使这个值就是0。这样可以保证该变量为强符号,当名字冲突时,可以发现,而不是被未知的值覆盖。三嘛,最好能够避免全局变量,或者定义一个独一无二的名字。

再多说一点,在编译阶段,还可以通过-fno-common选项来禁止将未初始化的全局变量放入到common段。
同样的文件,看下面的编译链接过程:
  1. [root@Lnx99 test]#gcc --fno-common -c test1.c
  2. [root@Lnx99 test]#gcc --fno-common -c test2.c
  3. [root@Lnx99 test]#gcc --fno-common -o test main.c test1.o test2.o
  4. test2.o:(.data+0x0): multiple definition of `init'
  5. test1.o:/root/work/test/test1.c:6: first defined here
  6. collect2: ld returned 1 exit status


原文地址:http://blog.chinaunix.net/uid-23629988-id-2888209.html
修改BSS(Block Started by Symbol)的大小通常涉及调整程序中初始化全局变量静态变量的内存分配。BSS是程序内存布局的一部分,用于存储这些初始化的变量。以下是修改BSS大小的方法步骤: --- ### **1. 理解BSS** - **BSS**:存储初始化全局变量静态变量(如 `int global_var;`)。 - **特点**: - 不占用可执行文件的空间(仅在运行时分配内存)。 - 由链接器脚本或编译器选项控制其大小。 --- ### **2. 修改BSS大小的常见方法** #### **(1) 通过链接器脚本(Linker Script)** 链接器脚本(如 `ld` 脚本)可以直接定义内存区域的大小。例如: ```ld MEMORY { RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } SECTIONS { .bss : { . = ALIGN(4); __bss_start__ = .; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); __bss_end__ = .; } > RAM } ``` - **关键点**: - 通过调整 `RAM` 的 `LENGTH` 或 `.bss` 的布局,可以间接影响BSS大小。 - 需确保总内存分配不超过目标平台的限制。 #### **(2) 通过编译器选项** 某些编译器(如GCC)允许通过选项优化初始化变量的存储: ```bash gcc -fno-zero-initialized-in-bss # 将显式初始化0的变量放在.data而非.bss ``` - **效果**:减少BSS大小,但可能增加 `.data` 大小。 #### **(3) 手动调整变量初始化** - 将初始化变量改为显式初始化为 `0`,可能使其从 `.bss` 移到 `.data` (取决于编译器): ```c int global_var = 0; // 可能被放入.data ``` - **注意**:行为因编译器而异,需结合 `objdump` 或 `map` 文件验证。 --- ### **3. 验证修改结果** 1. **生成 `map` 文件**: ```bash gcc -Wl,-Map=output.map ... # 查看.bss的地址大小 ``` 2. **使用 `objdump` 或 `size` 工具**: ```bash arm-none-eabi-size -A a.out # 显示各大小(适用于嵌入式工具链) ``` 3. **检查反汇编**: ```bash objdump -t a.out | grep .bss # 查看符号是否在.bss ``` --- ### **4. 注意事项** - **内存限制**:BSS过大可能导致运行时内存不足(尤其在嵌入式系统中)。 - **对齐要求**:大小通常需按4字节对齐(`ALIGN(4)`)。 - **平台差异**:不同架构(如ARM、x86)的链接器脚本语法可能不同。 --- ### **示例:嵌入式系统中的BSS调整** 假设使用ARM GCC自定义链接器脚本: ```ld MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } SECTIONS { .bss : { __bss_start__ = .; *(.bss .bss.*) *(COMMON) __bss_end__ = .; } > RAM } ``` - **目标**:将BSS限制在RAM的前16KB内: - 修改 `LENGTH = 16K` 或添加逻辑检查 `__bss_end__ - __bss_start__ <= 0x4000`。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值