C语言编译过程——预处理、编译汇编和链接详解

news/2024/5/6 2:10:03/文章来源:https://blog.csdn.net/weixin_46935110/article/details/126967840

引言

C语言经典的 “hello world ” 程序,伴随着每个程序员一起步入编程世界的大门。从编写、编译到运行,看到屏幕上输出的“hello world ”,那么你知道它都经历了什么吗?今天我们就来聊聊这个话题。

一、从hello.c聊起

hello world.c

#include <stdio.h>int main(){printf("hello,world!\n");return 0;
}

在linux下,使用 gcc 编译hello.c源文件,会在当前目录下默认生成 a.out 可执行文件,在终端输出hello,world!。

[Panda@centos test]$ gcc hello.c
[Panda@centos test]$ ./a.out
[Panda@centos test]$ hello,world!

预编译器、汇编器as、链接器ld,实际上gcc 命令只是对这些不同程序的封装,根据不同的参数去调用不同的程序。

从 hello.c 到可执行文件的全过程,可分为4个步骤:

  1. 预处理
    gcc -E hello.c -o hello.i 得到预处理文件,其中,-E 表示只进行预编译。
    源文件在预编译阶段会被编译器生成.i文件,主要处理源代码文件中以“#”开头的预编译指令。如:宏定义展开,将被包含的文件插入到该编译指令的位置等。

  2. 编译
    gcc -S hello.i -o hello.s 得到汇编文件,其中,-S 表示生成汇编文件。
    编译就是把预处理完的文件,进行语法分析、词法分析、语义分析及优化后生成相应的汇编代码文件,这个过程是整个程序构建的核心过程,也是最复杂的部分。

  3. 汇编
    as hello.s -o hello.o 或者 gcc -c hello.s -o hello.o,其中,-c 表示只编译不链接。
    将汇编代码文件转变成机器可以执行的指令文件,即目标文件。也可以直接使用:gcc -c hello.c -o hello.o 经过预处理、编译、汇编直接输出目标文件。
    为什么汇编器不直接生成可执行程序,而是一个目标文件呢?为什么要链接?这个我们后面会详细讨论。

  4. 链接
    随着代码量的增多,所有代码若是都放在同一个文件里,那将是一场灾难。现代大型软件,动辄由成千上万的模块组成,每个模块相互依赖又相互独立。将这些模块组装起来的过程就是链接。
    这些模块如何形成一个单一的程序呢?无非就是两种方式:1、模块间的函数调用;2、模块间的变量访问。函数访问必须知道函数地址,变量访问必须知道变量地址,所以终归到底就是一种方式,不同模块间符号的引用

二、什么是静态链接

比如:我们在模块main.c中,调用了另一个模块func.c中的foo()函数。我们在main.c中每一处调用foo的时候,都需要确切的知道foo函数的地址,但是每个模块都是独立编译的,在编译main.c的时候并不知道foo函数的地址,这些foo的地址会先跳过,链接器会在链接的时候根据你所引用的符号foo,自动去func.c的模块查找foo的地址,然后将main.c中所有调foo函数的指令全部修正,这就是静态链接最基本的作用。

三、目标文件里有什么

源代码在经理预处理、编译、汇编后生成的未进行链接的中间文件,也叫目标文件(windows下是.obj文件,linux下是.o文件)。那么目标文件里到底存放的是什么呢?

3.1 目标文件的格式

PC平台流行的可执行文件格式主要有一下两种:

  • Windows下的PE
  • Linux下的ELF
    不光是可执行文件按照可执行文件的格式存储,**动态链接库(Windows下的.dll和linux下的.so)静态链接库(Windows下的.lib和linux下的.a)**文件也都是按照可执行文件的格式存储的。

3.2 目标文件长啥样

目标文件里除了保存着源代码编译后的机器指令、数据,还包括链接时所需要的信息,如:符号表等。目标文件将这些信息按照不同的属性,以“段”的方式存储。

下面让我们来看一个简单的程序被编译成目标文件后的结构:
simpleSection.c

#include <stdio.h>int global_int_var = 84;
int global_unint_var;void func(int i) {  printf(" %d\n", i); }int main(void){static int static_var = 85;static int static_var2;int a = 1;int b;func(static_var + static_var2 + a + b);return 0;
}

在这里插入图片描述从图中可以看到,ELF文件的开头,“File Header”描述了整个文件的属性,如:是可执行文件、静态链接、动态链接,如果是可执行文件,还会记录可执行文件的入口地址。文件头还包括一个段表,段表实际上是记录了该文件中所有段的偏移位置和属性。

一般C语言编译后的机器代码保存在代码段(.text段);已初始化的全局变量和局部静态变量保存在.data段;未初始化的全局变量和局部静态变量保存在.bss段,默认为0,因为是0所以为其在.data段分配空间并存放0是没有意义的,在文件中.bss段不占空间。

总体来说,程序源代码被编译后主要分为两段:程序指令和程序数据,也就是代码段和数据段。指令和数据分开存储好处多多:

  1. 程序被装载后,数据和指令被映射到不同的虚拟内存其区域。代码段通常是只读的,数据段对于进程来说是可读写的,所以,这两块区域就可以设置不同的权限。
  2. 对于CPU来说,他们有着极为强大的缓存体系,所以,程序应尽量提高缓存的命中率。指令和数据分离可以提高缓存的命中率;
  3. 当系统中运行这多个该程序时,内存中只需要保存一份该程序的指令部分即可,大大节约了内存的使用。

3.3 objdump工具

objdump是一款可以查看目标文件的工具。“-h”参数就是把ELF文件中各个段的信息打印出来。

在这里插入图片描述
Size 列式对应段的大小,如:.text 段大小为0x55,即85字节。

size 命令可以查看ELF文件中,代码段、数据段和.bss段的总长度。(dec十进制,hex十六进制)
在这里插入图片描述

objdump -s -d hello.o 其中,-s 表示使用十六进制打印信息,-d 可以将所有包含指令的段进行反汇编。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.4 ELF文件结构

可以使用readelf详细查看ELF文件。readelf -h simpleSection.o
在这里插入图片描述
魔数:魔数用来确认文件的类型,操作系统加载可执行文件的时候会确认魔数是否正确,正确才加载。所有的ELF文件魔数的最开始的四个字节都相同,都是0x7f 0x45 0x4c 0x46,其中:0x7f 对应ASCII码的DEL控制符,另外三个分别对应 ELF 的ASCII码。

段表:我们知道ELF文件中有各种段,段表就是保存这些段的属性。比如:段名、段的长度在文件中的偏移、读写权限等等。编译器、链接器、加载器都是依赖段表来定位和访问某个段的。上图中,段表起始位置在1064(0x428)偏移的位置。

前面说得 objdump -h 只能查看部分的段信息,详细的段信息使用 readelf -S查看,如下:可以看到有13个段信息,以数组的方式存储,第一个段的Type为NULL,是无效的段描述符,所以,该ELF文件有12个有效的段。每个段描述符占40个字节,总共13*40=520字节。
在这里插入图片描述

注意到simpleSection.o 中有个 “rela.text” 段,它就是重定位表。也就是链接器在链接目标文件时,对代码段和数据段中那些对绝对地址引用的位置进行重定位。这些重定位的信息就是保存在重定位表中,每个需要重定位的代码段或者数据段,都会有一个对应的重定位表。比如:simpleSection.o 中的 “rela.text” 就是针对 “.text”段的重定位表,这是因为“.text”段中的“printf” 函数的调用;而“.data” 段中因为没有对绝对地址的引用,所以它没有对应的“rela.data”的重定位表。

字符串表:ELF文件中很多地方用到了字符串,如段名、变量名、函数名等。ELF使用字符串表的方式存储字符串,使用的时候只需要传偏移量即可。比如你想:用world,偏移量从6开始,遇到‘\0’结束,取出来的就是world。
在这里插入图片描述
字符串表在ELF文件中也以段的方式保存,通常保存在“.strtab” 和 “.shstrtab” 段中。前者表示普通字符串,后者表示段表中所用的字符串,如段名,有点专用感觉。

符号表:在ELF文件中也以段的方式保存,通常保存在“.symtab”中。
在这里插入图片描述
上图总共有16 个符号,第一个符号永远是未定义的,不用管。
func和main函数都是定义在simpleSection.c里的,并且保存在“.text”段,所以它们所在Ndx位置为1,因为“.text”段的下标1。
符号 printf 在simpleSection.c 里被引用,但是没有定义,所以它的Ndx是UDN。
符号global_int_var 已经是初始化后的全局变量,保存在.data段,对应下标为3。

四、链接的接口——符号

链接本质上处理的是目标文件之间函数和变量地址的引用。比如目标文件B用到了目标文件A中的“foo”函数,那么就称目标文件B引用了A中的函数“foo”,目标文件A定义了函数“foo”。

在链接过程中,我们将函数和变量统称为符号,函数名和变量名就是符号名。每个目标文件都会有一个符号表,表里记录了所有的符号及其对应的符号值,对应到函数和变量就是函数地址和变量地址。我们列出在链接过程中需要关注的符号:

  • 定义在本目标文件的全局符号,可以被其他目标文件引用。如simpleSection.o中的func函数和全局变量“global_int_var”。
  • 在本目标文件中引用的全局符号,但是定义不在本目标文件的,这种符号叫“外部符号”,如simpleSection.o中的printf函数。

文章参考于<零声教育>的C/C++linux服务期高级架构,及书籍(程序员的自我修养)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_12174.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java多线程~线程的状态以及状态转移的条件

目录 线程的六种状态 状态转移的条件 NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED 线程的六种状态 线程共有六种状态&#xff0c;分别为&#xff1a; NEW(初始状态)&#xff1a;new表示新建一个线程对象&#xff0c;即安排了工作&#xff0c;但未开始行…

Ubuntu指令说明

1、ls ls命令是list的缩写&#xff0c;用来打印出当前目录的清单。如果ls指定其他目录&#xff0c;那么就会显示指定目录里的文件及文件夹清单。通过ls命令不仅可以查看linux文件夹包含的文件&#xff0c;而且可以查看文件权限&#xff08;包括目录、文件夹、文件权限&#xf…

【逻辑】【java基础】代码逻辑思路 层级关系 【层级注解】【架构逻辑】

命名规范: 层级逻辑关系图: 层级逻辑思路图:(代码架构逻辑)

(附源码)springboot高校宿舍交电费系统 毕业设计 031552

Springboot高校宿舍交电费系统 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运…

低通滤波,高通滤波,中值滤波

低通滤波和高通滤波 【参考:图像处理之高通滤波及低通滤波】 低通滤波和高通滤波需用到傅里叶变换知识,可参考这里。 图像在频域里面,频率低的地方说明它是比较平滑的,因为平滑的地方灰度值变化比较小,而频率高的地方通常是边缘或者噪声,因为这些地方往往是灰度值突变的。…

用户终身价值利用xgboost进行LTV预测

在对用户进行细分的时候需要衡量用户的一个重要指标就是用户生命周期价值。不管是什么投入最终的目的是为了盈利,当然如何识别正确的用户周期价值就至关重要了。 其中用户的终身价值计算就非常容易。可以通过一个时间窗口期,即具体的时间可以是年,可以是月,也可以是日计算…

数字藏品app开发

目前大平台的数字藏品主要功能分为三个大体的方向&#xff1a; 1.建立独立的电商平台&#xff0c;平台方组织发行并销售或者赠送&#xff0c;这种玩的方式是国内的主流运行方向&#xff1b; 2.用户将持有的数字藏品引入到了社交平台&#xff0c;国外平台允许用户验证所…

React教程之每个开发人员都应该使用的可扩展和可维护的 React 项目结构

一个好的项目结构可以对项目在理解代码库、灵活性和维护方面的成功程度产生巨大影响。没有良好结构和维护的项目很快就会变成一团糟和可怕的遗产,没有人愿意与之合作。 现在,我将向您展示我在项目中经常使用的结构,并解释其背后的原因。这种结构应该是大型应用程序的一个很…

endpoint is blank

报错图 问题:简单来说使用nacos作为注册中心的时候 并没有对注册中心进行配置而出现的报错 nacos注册中心采用bootstrap.yml或者bootstrap.properties文件进行配置,所以有的人在application.yml或application.properties进行配置了 还是会报同样的错误 nacos正确的配置应该使…

使用RestfulTool插件模拟前端向后端发送请求体,通过SpringMVC结合MyBaits响应返回体

✨✨博主简介:一个会bbox的&#x1f468;‍&#x1f4bb; ✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f496;&#x1f496;如果文章对你有所帮…

Nacos2.0.3 单例模式mysql配置启动,完整版

一、copy配置文件application.properties 从运行的容器中把application.properties文件copy到虚拟机指定目录&#xff1a;/opt/nacos/conf/ docker cp nacos:/home/nacos/conf/application.properties /opt/nacos/conf/application.properties二、修改配置文件 application.…

外汇天眼:美联储如预期再次加息75个基点 并誓言进一步加息以对抗通胀

当地时间周三下午&#xff0c;美联储(Federal Reserve)将基准利率再上调75个基点&#xff0c;并暗示将继续在远高于当前水平的水平上加息。为了降低接近上世纪80年代初以来最高水平的通货膨胀率&#xff0c;美联储将联邦基金利率上调至3%-3.25%的区间&#xff0c;为2008年初以来…

计算机专业毕业设计怎么选?计算机本科毕业设计选题 2023年选题推荐

计算机专业毕业设计怎么选?计算机本科毕业设计选题 2023年选题推荐前言 现在已经迎来2023年的毕业季,很多同学咨询“IT跃迁谷毕设展”关于计算机毕业设计选题方面的问题。例如计算机毕设选题什么好?计算机毕设选题选什么新颖一些?计算机毕设选题如何好过关一些?等等一些问…

Fabric.js 喷雾笔刷 从入门到放肆

theme: smartblue 我报名参加金石计划1期挑战——瓜分10万奖池&#xff0c;这是我的第18篇文章&#xff0c;点击查看活动详情 本文简介 点赞 关注 收藏 学会了 喷雾笔刷 SprayBrush 是 fabric.js 提供的一个很好玩的工具&#xff0c;而且 fabric.js 也封装好了很多非常方便的…

ASP.NET 错误机制

部分包括设置为 On 的 mode 属性。mode 属性用于控制错误重定向发生的方式。例如&#xff0c;如果您正开发应用程序&#xff0c;则很可能希望查看实际的 ASP.NET 错误信息&#xff0c;并且不希望被重定向到更用户友好的错误页。mode 属性包括以下设置&#xff1a; On&#xff1…

【C语言】规范掌握C语言函数|数组名的妙用|指针快速入门|综合使用小案例

✅作者简介&#xff1a;热爱后端语言的大学生&#xff0c;CSDN内容合伙人 ✨精品专栏&#xff1a;C面向对象 文章目录1、函数的结构1.1、无参无返1.2、无参有返1.3、有参无返1.4、有参有返1.5、主函数中的调用及运行效果2、C语言数组创建和基本操作2.1、数组创建的方式2.2、数组…

CVE-2017-0143(永恒之蓝)漏洞复现By metasploit

一、永恒之蓝介绍 永恒之蓝是在Windows的SMB服务处理SMB v1请求时发生的漏洞,这个漏洞导致攻击者在目标系统上可以执行任意代码。通过永恒之蓝漏洞会扫描开放445文件共享端口的Windows机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控…

笔记-设备相关知识

目录 设备类 接口类 Windows 如何安装设备 步骤 1&#xff1a;标识设备 步骤 2&#xff1a;选择设备的驱动程序包 搜索驱动程序包 选择驱动程序 步骤 3&#xff1a;已安装设备的驱动程序包 硬件 为设备创建硬件 ID 根枚举设备的硬件 ID 获取设备的硬件 ID 列表 硬件…

低浓度阿拉特津(ATZ)诱导MCF-7细胞增殖的生物标志物发现及其代谢组学机制探究

低浓度阿拉特津&#xff08;ATZ&#xff09;诱导MCF-7细胞增殖的生物标志物发现及其代谢组学机制探究 文章标题&#xff1a;Integrated metabolomics and transcriptomics analysis reveals new biomarkers and mechanistic insights on atrazine exposures in MCF7 cells 发…

【转】详谈判断点在多边形内的七种方法

原帖地址: https://blog.csdn.net/WilliamSun0122/article/details/77994526 射线法时间复杂度:O(n) 适用范围:任意多边形算法思想:以被测点Q为端点,向任意方向作射线(一般水平向右作射线),统计该射线与多边形的交点数。如果为奇数,Q在多边形内;如果为偶数,Q在多边…