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

深入理解计算机系统之链接(一)

程序是怎样运行的写好的c程序怎样运行的呢?答案是一个写好的程序要先经过语言预处理器,编译器,汇编器和链接器生成最后的可执行文件,然后加载器将可执行文件加载到内存中才能运行。这里以一

程序是怎样运行的

写好的c程序怎样运行的呢?答案是一个写好的程序要先经过语言预处理器,编译器,汇编器和链接器生成最后的可执行文件,然后加载器将可执行文件加载到内存中才能运行。

这里以一个c程序main.c和GNU编译系统(静态链接)为例来说明生成可执行文件的过程:
1.首先经过c预处理器(cpp)将main.c翻译成一个 ASCII码的中间文件main.i;
2.接下来驱动程序运行C编译器(cc1)将main.i翻译成一个ASCII汇编语言文件main.s;
3.随后驱动程序运行汇编器,将main.s(as)翻译成一个可重定位目标文件main.o;
4.最后运行链接器ld将main.o和其他的一些的可重定位目标文件链接在一起创建一个可执行目标文件。

下面说一下程序运行之前每个阶段的所做的具体工作:
1.预处理阶段:预处理器cpp根据以字符#开头的命令,修改原始的C程序。比如常见的命令

#include

就是告诉编译器读取系统头文件stdio.h的内容,这个头文件里包含了一些可能用到的函数的声明,预处理器将它直接插入到程序文本中,就得到了另一个c程序——main.i

2.编译阶段:编译器(cc1)将文本文件main.i翻译成文本文件翻译成文本文件mian.s,它包含了 一个汇编语言程序,汇编语言程序中的每条语句都描述了一句机器指令。汇编语言是非常有用的,因为它为不同的高级语言的不同编译器提供了通用的输出语言。

3.汇编阶段:接下来汇编器(as)将main.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标文件的格式,即main.o。它是一个二进制文件。

4.链接阶段:假设main.o调用了printf函数,而printf函数是标准C库中的一个函数,位于一个名为printf.o的单独预编译好了的目标文件中,问这个文件必须以某种方式合并到main.o中去,链接器(ld)就负责处理这种合并,结果得到main文件,它是一个可执行目标文件,可以被加载到内存中,由系统执行。

5.加载:在命令行上键入

./main

外壳程序会调用操作系统中一个叫做加载器的函数,它拷贝可执行文件中的代码和数据到存储器中,并将控制权转移到程序的开头。

什么是可重定位文件

那么什么是可重定位目标文件呢,它的结构又是什么样子呢?
接下来是一张图说明了在Unix系统下的elf格式的可重定位目标文件的结构
《深入理解计算机系统之链接(一)》

下面来解释一下每个部分作用。
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分暂时可以先不理会。

加载ELF头和节头部表之间的都是节。
.text:已编译程序的机器代码
.rodata:只读数据,比如printf语句中的格式串和开关语句的跳转表
.data:已初始化的全局C变量
.bss:未初始化的全局C变量,注意这个节不占据实际的空间,仅仅是一个占位符,因为未初始化的全局变量初始值为0,没有保存的必要,这样做可以更好地节省空间。
.symtab:一个符号表,他存放在程序中定义和引用的函数和全局变量的信息。

剩下的节先不说,这里说一下关于符号表相关的内容。
首先吐槽一下csap这本书的翻译,实在是含糊不清,有些词比如引用,全局等容易引起歧义的词太多,还有几处翻译错误如果英文好的直接看原版可能更好理解吧…..

首先”符号”这个词就很令人困惑,其实符号就是定义的函数名或者变量名(局部变量不在符号表上,局部变量运行时在栈上被管理,链接器不管)

这一章所用的”全局”也有歧义,这一章的”全局符号”是指不带static修饰的全局变量,也就是说能在所有模块中使用的变量,这其中又分为在本地定义的”全局符号”和在其他模块中定义的外部(extern)全局符号。
在本地定义的带static修饰的全局变量被称为本地符号。
以上这三种(两种全局符号和一种本地符号)就是符号表中的内容,但是还有一种特例,那就是带static修饰的局部变量, 它不是在栈中管理,而是由编译器在.data和.bss中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。比如说以下这种情况

int f()
{
static int x = 0;
return x;
}
int g()
{
static int x = 1;
return x;
}

在这种情况下,编译器引出两个唯一的本地链接器符号给汇编器。比如它可以用x.1表示函数f中的定义,用x.2表示函数g中的定义。其中x.1存储在.bss中,x.2存储在.data中。

既然说到了static,不妨说一下它的作用,刚才说了用static修饰的全局变量只能在本地被访问,这个就好像面向对象里的private的作用,所以可以用static属性隐藏变量和函数名字。


推荐阅读
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • CMake跨平台开发实践
    本文介绍如何使用CMake支持不同平台的代码编译。通过一个简单的示例,我们将展示如何编写CMakeLists.txt以适应Linux和Windows平台,并实现跨平台的函数调用。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
author-avatar
我是王灿_246
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有