热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

预处理、编译汇编和链接详解

引言C语言经典的“helloworld”程序,伴随着每个程序员一起步入编程世界的大门。从编写、编译到运行,看到屏幕上输出的“helloworld”&#

引言

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


一、从hello.c聊起

hello world.c

#include 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 目标文件长啥样

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

下面让我们来看一个简单的程序被编译成目标文件后的结构:
在这里插入图片描述从图中可以看到,ELF文件的开头,“File Header”描述了整个文件的属性,如:是可执行文件、静态链接、动态链接,如果是可执行文件,还会记录可执行文件的入口地址。文件头还包括一个段表,段表实际上是记录了该文件中所有段的偏移位置和属性。

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

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


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

3.3 objdump工具

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

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

size 命令可以查看ELF文件中,代码段、数据段和.bss段的总长度。(dec十进制,hex十六进制)
在这里插入图片描述
objdump -s -d hello.o 其中,-s 表示使用十六进制打印信息,-d 可以将所有包含指令的段进行反汇编。
在这里插入图片描述
在这里插入图片描述
文章参考于<零声教育>的C/C&#43;&#43;linux服务期高级架构&#xff0c;及书籍&#xff08;程序员的自我修养&#xff09;。


推荐阅读
  • 本文详细介绍了C语言的起源、发展及其标准化过程,涵盖了从早期的BCPL和B语言到现代C语言的演变,并探讨了其在操作系统和跨平台编程中的重要地位。 ... [详细]
  • 在Java中,this是一个引用当前对象的关键字。如何通过this获取并显示其所指向的对象的属性和方法?本文详细解释了this的用法及其背后的原理。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • CMake跨平台开发实践
    本文介绍如何使用CMake支持不同平台的代码编译。通过一个简单的示例,我们将展示如何编写CMakeLists.txt以适应Linux和Windows平台,并实现跨平台的函数调用。 ... [详细]
  • 本文详细介绍了macOS系统的核心组件,包括如何管理其安全特性——系统完整性保护(SIP),并探讨了不同版本的更新亮点。对于使用macOS系统的用户来说,了解这些信息有助于更好地管理和优化系统性能。 ... [详细]
  • Linux设备驱动程序:异步时间操作与调度机制
    本文介绍了Linux内核中的几种异步延迟操作方法,包括内核定时器、tasklet机制和工作队列。这些机制允许在未来的某个时间点执行任务,而无需阻塞当前线程,从而提高系统的响应性和效率。 ... [详细]
  • 本文详细介绍了C语言中的指针,包括其基本概念、应用场景以及使用时的优缺点。同时,通过实例解析了指针在内存管理、数组操作、函数调用等方面的具体应用,并探讨了指针的安全性问题。 ... [详细]
  • C语言标准及其GCC编译器版本
    编程语言的发展离不开持续的维护和更新。本文将探讨C语言的标准演变以及GCC编译器如何支持这些标准,确保其与时俱进,满足现代开发需求。 ... [详细]
  • 本文介绍如何解决在 IIS 环境下 PHP 页面无法找到的问题。主要步骤包括配置 Internet 信息服务管理器中的 ISAPI 扩展和 Active Server Pages 设置,确保 PHP 脚本能够正常运行。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • 本文介绍了几种不同的编程方法来计算从1到n的自然数之和,包括循环、递归、面向对象以及模板元编程等技术。每种方法都有其特点和适用场景。 ... [详细]
  • Python入门:第一天准备与安装
    本文详细介绍了Python编程语言的基础知识和安装步骤,帮助初学者快速上手。涵盖Python的特点、应用场景以及Windows环境下Python和PyCharm的安装方法。 ... [详细]
  • 解析SQL查询结果的排序问题及其解决方案
    本文探讨了为什么某些SQL查询返回的数据集未能按预期顺序排列,并提供了详细的解决方案,帮助开发者理解并解决这一常见问题。 ... [详细]
  • C语言基础入门:7个经典小程序助你快速掌握编程技巧
    本文精选了7个经典的C语言小程序,旨在帮助初学者快速掌握编程基础。通过这些程序的实践,你将更深入地理解C语言的核心概念和语法结构。 ... [详细]
  • 本文探讨了如何使用自增和自减运算符遍历二维数组中的元素。通过实例详细解释了指针与二维数组结合使用的正确方法,并解答了常见的错误用法。 ... [详细]
author-avatar
三心两意真实扭
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有