借用博主:Hello不爱World的资料:“为什么要把bss段清0”后,理解重定位时运行地址和链接地址相同时为什么还要清零的疑惑。
博主资料:
全局变量与静态变量没有初始化或初始化值为0时,都会放在.bss段。初始化为非0值,则放在.data段。
全局变量与静态变量没有初始化或初始化值为0时,都放在.bss段会产生一个问题:假如说我们定义一个全局的变量 int a = 0;我们知道这是一个初始化值为0 的全局变量,那么他会被放在.bss段,由于存储在bss段内的数据在下一次启动代码时不会被自动的重新初始化为0(即bss段清0),这就可能导致上一次运行代码过程中有可能修改了全局变量或静态变量的值,而修改过的值会被bss段保存下来,那么在下一次启动代码事我们定义的那个全局变量的值就可能不是我们第一次定义的 “0”值了,这样的话就有可能导致一些问题。
举个例子:
int a = 0//全局变量,被放在bss段
void main(void)
{
if(a == 0){
printf("a=0");
a = 1;
}
else{
printf("a != 0 ");
a = 0;
}
}
我们第一次运行程序时,是输出:a = 0,第二次再运行就输出:a != 0了,下一次再运行 又输出:a = 0 了,这样就导致同样的代码,在不同次的运行是产生不同的结果,这绝不是我们希望看到的。所以在初始化代码是我们要对bss段清0。这样每次运行是为初始化的全局变量、静态变量和初始化为0的全局变量、静态变量就默认全是0了,这样就不会导致上面例子中的那种情况。
如何对Bss段清零?在链接脚本中标记bss段的开始和结束位置,然后在初始化代码的时候把开始标记和结束标记之间的内容清0。
自己的理解:
学习重定位时,为什么运行地址和链接地址相同时(没有链接地址除外),bss段还要清零(下面蓝色代码):代码如下:
// 第4步:重定位
// adr指令用于加载_start当前运行地址
adr r0, _start // adr加载时就叫短加载
// ldr指令用于加载_start的链接地址:0xd0024000
ldr r1, =_start // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载
// bss段的起始地址
ldr r2, =bss_start // 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可
cmp r0, r1 // 比较_start的运行时地址和链接地址是否相等
beq clean_bss // 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss
//(为什么不重定位时,还要清零呢?因为此时程序是运行在运行地址处的那段代码,
//虽然这段代码在开始运行前,编译器链接器自动添加了清零程序,保证了此程序中的
//c语言的运行环境,但是此时正在开始运行了,此程序中的原来初始化为0 的全局变
//量会被使用,譬如a=2,这样的话,下次再次运行此程序(是执行./a.out文件,而不是
//指再次进行编译链接:执行gcc编译链接命令)时,局部变量a的值就不符合c语言的
//运行环境(局部变量a应该是0)了)。所以每次执行完后都要对bss段进行清零。
// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位
// 重定位完成后继续执行clean_bss。
// 用汇编来实现的一个while循环
copy_loop:
ldr r3, [r0], #4 // 源
str r3, [r1], #4 // 目的 这两句代码就完成了4个字节内容的拷贝
cmp r1, r2 // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2
bne copy_loop
// 清bss段,其实就是在链接地址处把bss段全部清零
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1 // 如果r0等于r1,说明bss段为空,直接下去
beq run_on_dram // 清除bss完之后的地址
mov r2, #0
clear_loop:
str r2, [r0], #4 // 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),
cmp r0, r1 // 然后r0 = r0 + 4
bne clear_loop
run_on_dram:
// 长跳转到led_blink开始第二阶段
ldr pc, =led_blink // ldr指令实现长跳转,此程序中只有这句是位置有关码
// 从这里之后就可以开始调用C程序了
//bl led_blink // bl指令实现短跳转
这是因为:如果有了链接地址,说明就要运行地址的存在,因为仅有一个链接地址不能运行,有了运行地址就说明这个程序开始时是运行在运行地址处,此程序运行前(执行可执行程序的命令:./a.out),是要对该程序进行编译链接的,编译链接时程序自动就添加了一段清bss段的代码了,于是这样做就保证了c语言的运行环境(定义的全局变量a,但未初始化为0,通过执行自动添加的这段代码,完成了全局变量的值为0),因为在执行此程序时,有可能全局变量a中间赋了新的值,譬如a=8,这个a的值始终在程序中,当程序进行重定位时,发现链接地址和运行地址相同,虽然不需要执行代码搬运任务(也就是重定位),下一条命令可以跳过清bss的地址处: clean_bss,不去清bss段,直接去执行地址为run_on_dram::的代码
// 长跳转到led_blink开始第二阶段
ldr pc, =led_blink
但是,如果程序员下一次执行此程序(即执行./a.out)时,不是每次都要编译链接此程序(编译链接工作是在什么时候做的呢?是在程序员编程的时候做的,一旦程序编译链接完生成可执行程序:.bin文件后,程序员就不在进行编译链接了,所以这里说的“下一次执行此程序”意思就是说程序员在电脑是把.bin文件拿来,直接去输入.bin文件名即可,不用编译链接了,执行bin文件时没有编译链接的代码了,也就是说没有自动清bss的代码了)也是说下次执行程序时(即执行./a.out)本应为0的全局变量a,现在已经变成了8了,8被送入程序中,破坏c语言的运行环境,于是,这就要求只要每次执行此程序(不一定是执行程序的全部,可以是程序的一部分,为了完全保证a的值在程序执行前始终为0,哪管是执行程序的第一句代码,就退出此程序去执行其他程序了,也要对这整个程序的bss段进行清零。)都有这样一步:清bss段。
说明:1、编译链接和清bss段是一一对应的,有编译链接就有清bss段,前者是后者的充分条件,不是必要条件,也就是只要有编译链接这一步,就一定会有清bss段这一步(这一步的代码不是程序员写的,是自动生成的)
2、一旦生成了可执行程序.bin文件,编译链接工作说明就结束了,所以程序员要保证每次执行(即执行./a.out)或者运行程序(即执行./a.out)时满足c语言的运行环境(已经定义但未初始化为0的全局变量始终为0),就要在设计程序时,在退出此程序时就要马上执行一段清bss段的代码,只有这样才能做到,下一次执行此程序时,程序中的bss段是清过0的程序。
注:有不对之处请及时指出!