1、前言
#include <stdio.h>
int temp;
int main(void)
{
//打印temp的值是零
printf("temp=%d\n", temp);
return 0;
}
- 在C语言编程中,我们默认未初始化的全局变量、静态局部变量的初始化值都是零,底层原理如下
- 未初始化的全局变量、静态局部变量在链接时都属于bss、sbss段,会集中存放
- 编译器默认添加的启动代码里把bss段都做了清零操作
- 裸机开发时,如果你在编译时使用-nostdlib、-nostdinc、-T选项,不使用标准库、不使用标准头文件,自己编写汇编启动代码,如果自己添加的启动代码里没有对bss段进行清零,那未初始化的全局变量、静态局部变量的初始化值就不一定是零
2、查看sbss、bss段
3、查看默认链接脚本
- 使用–verbose选项查看默认链接脚本:riscv64-unknown-elf-ld --verbose > build.ld
- 在链接脚本中可以看到sbss、bss段,两个段是紧挨着的。开始地址是__bss_start标号处,结束地址是__BSS_END__标号处
4、查看编译器默认添加的启动代码
- 通过反汇编来查看编译器添加的启动代码。我们编写的代码是从main函数开始,main函数之前的代码都是编译器添加的,其中_start标号处是程序的入口,也就是执行的第一条代码
5、分析反汇编代码里的bss、sbss段
6、启动代码中初始化bss段
- 编译器调用memset函数来把sbss、bss段的初始值格式化成零
- memset函数需要传入三个参数:起始地址、格式化的值、格式化空间大小,分别是通过a0、a1、a2寄存器来传递,这是和芯片架构相关的,本文是基于riscv架构来分析,可参考博客:《RISC-V架构的函数调用规范和栈布局》
- 指定sbss段的起始地址这里没有用__bss_start标号,而是使用temp变量的地址,__bss_start标号也是指向temp变量的地址,两种方式是等价的