C语言运行库及glibc介绍

发布时间 2023-10-29 16:39:37作者: zephyr~

C语言运行库

任何一个C程序,它的背后都有一套庞大的代码来进行支撑,以使得该程序能够正常运行。这套代码至少包括入口函数,及其所依赖的函数所构成的函数集合。当然,它还理应包括各种标准库函数的实现。

这样的一个代码集合称之为运行时库(Runtime Library)。而C语言的运行库,即被称为C运行库(CRT)。

一个C语言运行库大致包含了如下功能:

  • 启动与退出:包括入口函数及入口函数所依赖的其他函数等。
  • 标准库函数:山C语言标准规定的C语言标准库所拥有的函数实现
  • I/0:I/0功能的封装和实现,参见上一节中I/0初始化部分。
  • 堆:堆的封装和实现,参见上一节中堆初始化部分。
  • 语言实现:语言中一些特殊功能的实现。

C语言标准款

ANSIC的标准库由24个C头文件组成。与许多其他语言(如Java)的标准库不同,C语言的标准库非常轻量,它仅仅包含了数学函数、字符/字符串处理,I/O等基本方面,例如:

  • 标准输入输出(stdio.h)。
  • 文件操作(stdio.h)。
  • 字符操作(ctype.h)。
  • 字符串操作(string.h)。
  • 数学函数(math.h)。
  • 资源管理(stdlib.h)。
  • 格式转换(stdlib.h)。
  • 时间/日期(time.h)。
  • 断言(assert.h)。
  • 各种类型上的常数(limits.h float.h)。

除此之外,C语言标准库还有一些特殊的库,用于执行一些特殊的操作,例如:

  • 变长参数(stdarg.h)。
  • 非局部跳转(setjmp.h)。

glibc

历史

glibc即GNUCLibrary,是GNU旗下的C标准库。最初由1自由软件基金会FSF(FreeSoftware Foundation)发起开发,目的是为GNU操作系统开发一个C标准库。GNU操作系统的最初计划的内核是Hurd,一个微内核的构架系统。Hurd因为种种原因开发进展缓慢,而Linux因为它的实用性而逐渐风靡,最后取代Hurd成了GNNU操作系统的内核。于是glibc从最初开始支持Hurd到后来渐渐发展成同时支持Hurd和Linux,而且随着Linux的越来越流行,glibc也主要关注Linux下的开发,成为了Linux平台的C标准哇。

20世纪90年代初,在glibc成为Linux下的C运行库之前,Linux的开发者们因为开发的需要,从Linux内核代码里面分离出了一部分代码,形成了早期Linux下的C运行库。这个C运行库又被称为Linuxlibc。这个版本的C运行库被维护了很多年,从版本2一直开发到版本5。如果你去看早期版本的Linux,会发现/lib目录下面有1ibc.so.5这样的文件,这个文件就是第五个版本的Linux libc。1996年FSF发布了glibc2.0,这个版本的glibc开始支持诸多特性,比如它完全支持POSIX标准、国际化、IPv6、64-位委数据访问、多线程及改进了代码的可移植性。在此时Linux libc的开发者也认识到单独地维护一份Linux下专用的C运行库是没有必要的,于是Linux开始采用glibc作为默认的C运行库,并且将2.x版本的glibc看作是Linux libc的后继版本。于是我们可以看到,glibc在/lib目录下的.so文件为libc.so.6,即第六个libc版本,而且在各个Linux发行版中,glibc往往被称为libc6。glibc在Linux平台下占据了主导地位之后,它又被移植到了其他操作系统和其他硬件平台,诸如FreeBSD、NetBSD等,而且它支持数十种CPU及嵌入式平台。目前最新的glibc版本号是2.8(2008年4月)。

glibc的发布版本主要由两部分组成,一部分是头文件,比如stdio.h、stdlib.h等,它们往往位于/usr/include:另外一部分则是库的二进制文件部分二进制部分主要的就是C语言标准库,它有静态和动态两个版本。动态的标准库我们及在本书的前面章节中碰到过了,它位于/lib/libc.so.6;而静态标准库位于/usr/lib/libc.a。事实上glibc 除了C标准库之外,还有几个辅助程序运行的运行库,这几个文件可以称得上是真正的"运行库"。它们就是/usr/lib/crt1.o、/usr/lib/crti.o和/usr/lib/crtn.o。

启动文件

crt1.o里面包含的就是程序的入口函数_start,由它负责调用_libc_start_main初始化libc_start_main初始化
并且调用main函数进入真正的程序主体。实际上最初开始的时候它并不叫做crt1.o,而是叫做crt.o,包含了基本的启动、退出代码。由于当时有些链接器对链接时目标文件和库的顺序有依赖性,crt.o这个文件必须被放在链接器命令行中的所有输入文件中的第一个,为了强调这一点,crt.o被更名为crt0.o,表示它是链接时输入的第一个文件。

后来由于C++的出现和ELF文件的改进,出现了必须在main()函数之前执行的全局/静态对象构造和必须在main()函数之后执行的全局/静态对象析构。为了满足类似的需求,运行库在每个目标文件中引入两个与初始化相关的段".init"和".finit"。运行库会保证所有位于这两个段中的代码会先于/后于main()函数执行,所以用它们来实现全局构造和析构就是很自然的事情了。链接器在进行链接时,会把所有输入目标乐文件中的".init"和".finit"按照顺序收集起来,然后将它们合并成输出文件中的".init"和".finit"。但是这两个输出的段中所包含的指令还需要一些辅助的代码来帮助它们启动(比如计算GOT之类的),于是引入了两个目标文件分别用来帮助实现初始化函数的crti.o和crtn.o

与此同时,为了支持新的库和可执行文件格式,crt0.o也进行了升级级,变成了crt1.o。crt0.0和crt1.0之间的区别是crt0.0为原始的,不支持".init"和".finit"的启动代码,而crt1.0是改进过后,支持".init"和".finit"的版本。这一点我们从反汇编crt1.0可以看到,它向libc启动函数_libc_start_main()传递了两个函数指针"_libc_csu_init"和"_libc_csu_fini",这两个函数负责调用_init()和_finit()。