C 语言的左右之间

大概讲一讲自己对 C语言、语言和汇编,POSIX 编程的一些个人理解。本文也是将自己的知识体系进行一个梳理,加深巩固。

C 语言大家只要是科班出身都学过。C 的缘起,或者说初衷,是汇编的代码生成器。也被称为结构化的汇编。也因为是一个汇编的代码生成器,因此他可以和具体的汇编无关,从而可以使得不同的计算机可以使用相同的代码,在当时那种CPU架构非常多样化的环境中,就使得我们的代码逻辑抽象出来,运行起来不需要和具体的CPU供应商绑定。

我们完全将汇编与高层的 C 映射起来。正是由于这种高度映射,才使得 C 语言几乎取代了汇编在常规开发领域的地位。

限于笔者能力,接下来我们漫谈的内容只能局限于 Linux x64 的场景 。我们同时几乎只讨论用户态,也就是说系统编程的范畴。

从一个程序的启动引申

在 Linux 下面,一个程序需要启动,就要被内核加载。但如果我们玩过最简单的单片机的编程(几毛一个的可编程的芯片),实际上会让我们意识到程序根本不需要在内存中。在内存中只需要保留需要不停修改,对性能要求最高的运行时信息即可。也就是说,需要不停动态修改(比如各种数字的加加减减和其结果)的数据存在 RAM 内存,程序自身逻辑存在 ROM 也就是“硬盘”上。最早的类 x86 并不是 80386 而是 8086 这种单片机。

但是实际上,现在流行的服务器和 PC 要求程序必须在内存中运行。也就是说必须就将程序本身从持久化的存储中复制到内存。这个被叫做加载(loading),因此,高级一点的操作系统才有了普通意义上被叫 loader 的东西。说白了,就是把程序从硬盘复制到内存,不再直接读取硬盘。

因此从逻辑上,我们要清楚,其实程序本身的二进制指令和运行时的堆栈数据逻辑上是完全不一样的两个东西。在冯诺依曼的计算机中,他们只是恰好在一个内存空间的抽象中。指令就是一段随机的二进制,只要导入机器,就可以执行。那么,我们见到的可执行文件和二进制的关系是什么呢?

必须要能理解到,可执行文件,就只是硬盘上的一个普通文件。只不过可以它可以被操作系统进行识别,解释为需要干的应用逻辑。

这种文件可以切分成一段一段的,每一段都有其对应的含义。最重要的一段存储了指令,我们叫做text。

ELF