GCC全过程详解+剖析生成的.o文件

参考:https://blog.csdn.net/gt1025814447/article/details/80442673

GCC四步详解

第一步:预处理(也叫预编译)       

gcc -E test.c -o test.i

        将所有#define删除,并且展开所有的宏定义

        处理所有的条件预编译指令,如#if #ifdef  #undef  #ifndef  #endif #elif

        处理#include,将包含的文件插入到此处,这是一个递归的过程

        删除所有注释   //   /* */

        添加行号和文件名标识,以便于编译时产生的错误警告能显示行号

        保留#pragma编译器指令

第二步:编译      

gcc -S test.i -o test.s

        将预处理完的.i文件进行一系列的词法分析、语法分析、语义分析及优
        化后生成响应的汇编代码文件,这是整个程序构建的最核心的部分,也是最复杂的部分

第三步:汇编        

gcc -C test.s -o test.o

        汇编是将第二步生成的汇编代码编程机器可执行的指令,每一个汇编语句几乎都对应一条机器指令

第四步:链接

         链接动态库和静态库
————————————————

生成的目标文件有什么,什么是目标文件?

目标文件就是源代码经过编译后但未进行链接的那些中间文件。Linux下的 .o文件就是目标文件,目标文件和可执行文件内容和
格式几乎都一样,所以我们可以广义地将目标文件和可执行文件看成一类型文件。他们都是按照ELF文件格式存储的

Linux下有哪些ELF类型的文件?

.o文件、可执行文件、核心转储文件(core dump)、.so文件(动态链链接库)

可执行文件的概貌详解

File  Header 、.text section 、.data section 、.bss section
文件头(File Header)
描述了整个文件的文件属性,包括目标文件是否可执行、是静态链接还是动态链接及入口地址、目标硬件、目标操作系统等信息、段表(描述文件中各个段的偏移位置及属性等)
代码段(.text)
存放了程序源代码编译后生成的机器指令
数据段(.data)
存放已初始化的全局静态与非静态变量和已初始化的局部静态变量
.bss段
存放未初始化的全局变量(全局静态和非静态变量)和局部静态变量
但是.bss段只是为这些变量预留位置而已,并没有内容,所以这些变量在.bss段中也不占据空间
—————————————————————————————————

深入挖掘 .o文件

使用命令:

objdump  -h  xxxx.o
            打印主要段的信息
objdump  -x  xxxx.o 
            打印更多的详细信息
objdump  -s  xxx.o
            将所有段的内容以16进制方式打印出来
objdump  -d  xxx.o  或者-S
            将所有包含指令的段反汇编
objdump   -t   xxx.o
            查看所有的符号以及他们所在段
readelf  -h   xxx.o
            查看.o文件的文件头详细信息
readelf   -S   xxx.o
            显示.o文件中的所有段,即查看段表
size xxx.o
            查看.o文件中各个段所占大小
nm xxx.o 
            查看.o文件中所有的符号

使用命令gcc -c test.c编译下面这个test.c程序生成test.o文件,然后查看test.o文件结构

test.c

/* this is a test code */
/* test.c */ 

int printf(const char *format, ...); 

int g_var2 = 10;
int g_var2; 

void func(int i){
    printf("%d\n",i);
} 

int main(void){    
	static int static_var1 = 20;    
	static int static_var2;        
	int var3 = 1;    
	int var4;    
	func(static_var1 + static_var2 + var3 + var4);    
	return var3;
}

然后查看生成的test.o文件的结构:  objdump -h test.o

GCC全过程详解+剖析生成的.o文件_第1张图片

行:
    .text  :代码段(存放函数的二进制机器指令)
    .data :数据段(存已初始化的局部/全局静态变量、未初始化的全局静态变量)
    .bss  :bss段(声明未初始化变量所占大小)
    .rodata :只读数据段(存放 " " 引住的只读字符串)
    .comment :注释信息段
    .node.GUN-stack :堆栈提示段
列:
    Size:段的长度
    File Off :段的所在位置(即距离文件头的偏移位置)
段的属性:
    CONTENTS:表示该段在文件中存在
    ALLOC :表示只分配了大小,但没有存内容

关于.bss段

我们说.bss段是存放未初始化的全局变量(静态与非静态)和局部静态变量的
所以我们程序中的g_var2和stactic_var2应该都在.bss段中被预留位置,所以.bss段的size应该是8个字节,但是结果却是4个字节,怎么回事呢?
这就是不同的编译器实现不一样的原因了,有些编译器会将未初始化的全局非静态变量放在.bss段,有些则不放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss段分配空间。而我的编译器是没有将g_var2(全局未初始化的非静态变量)放在任何段
下面让我们真正的查看一下g_var2
首先,我们查看段表

readelf -S test.o

(主要为了查看每个段的段号)
GCC全过程详解+剖析生成的.o文件_第2张图片
然后我们再看一下符号表 (我们定义的变量名都是符号,包括函数名)

readelf -s test.o

符号表里会显示这个符号所在的位置

GCC全过程详解+剖析生成的.o文件_第3张图片

我们看到static_var1和g_var1所在段的段号为24(24是.data段)
static_var2和g_var2所在段的段号为25(25是.bss段)

各种变量所在位置总结

    全局已初始化非静态变量、局部已初始化静态变量会被放入.data段
    全局未初始化静态变量会被放入.bss段

你可能感兴趣的:(C++,Linux)