使用GCC编译一个.c文件影藏了哪些过程?
GCC四步详解
第一步:预处理(也叫预编译) gcc -E hello.c -o hello.i 或者 cpp hello.c > hello.i 【cpp是预编译器】 将所有#define删除,并且展开所有的宏定义 处理所有的条件预编译指令,如#if #ifdef #undef #ifndef #endif #elif 处理#include,将包含的文件插入到此处,这是一个递归的过程 删除所有注释 // /* */ 添加行号和文件名标识,以便于编译时产生的错误警告能显示行号 保留#pragma编译器指令第二步:编译 gcc -S hello.i -o hello.s 将预处理完的.i文件进行一系列的词法分析、语法分析、语义分析及优 化后生成响应的汇编代码文件,这是整个程序构建的最核心的部分,也是最复杂的部分第三步:汇编
gcc -c hello.s -o hello.o或者 as hello.s -o hello.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行:
.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 查看段表(主要为了查看每个段的段号)然后我们再使用 readelf -s test.o看一下符号表(我们定义的变量名都是符号,包括函数名)
符号表里会显示这个符号所在的位置
我们看到static_var1和g_var1所在段的段号为3(3是.data段),static_var2所在段的段号为4(4是.bss段),而g_var2却没有被放入任何一个段,只是用COM标记了一下,那这个COM表示什么意思呢?COM标记的符号被称为弱符号,一个变量名是弱符号,则这个变量的大小在编译的时候不能被确定,而在链接之后才能确定该变量的大小。test.o文件在链接之后,g_var2会被放入.bss段(当然,也只是说明g_var2所需要的空间大小,并不会存放内容),而在程序运行的时候g_var2这样的变量才会真正去占用内存空间
强制将某变量或者某函数放入某个段__attribute__((section(".data"))) int g_var2; //强制将g_var2放入.data段中
各种变量所在位置总结
全局已初始化非静态变量、局部已初始化静态变量会被放入.data段 全局未初始化静态变量会被放入.bss段 全图未初始化非静态变量不会被放入任何一个段,只是用COM标记一下
---------------------
本文转摘自https://blog.csdn.net/gt1025814447/article/details/80442673