C基础库

来源:互联网 发布:淘宝化妆品照片怎么拍 编辑:程序博客网 时间:2024/06/02 03:08

    最近看了点C基础库的代码,主要是Android系统的C基础库bionic,对比浏览了一些glibc的代码,多少有一些收获吧。好久没更新博客了,写几篇C基础库的文章算是记录这段时间的学习过程吧。

    C基础库是操作系统中最底层的函数库,提供了大量的基础函数,比如标准I/O函数、字符处理函数、字符串处理函数、数学函数、内存分配器等,基本上就是实现了C99标准中规定的函数。C基础库之所以称为“基础库”,就在于C基础库不依赖于任何其他库,C基础库没有调用其他库中的函数。C基础库唯一依赖的是内核,因为一些功能必须由内核实现,比如标准I/O函数最后必须调用open(2)、read(2)、write(2)、close(2)等系统调用实现。

    除了前面提当的基础函数,C基础库还负责创建和清理一个进程的运行环境。比如,我们都知道一个C程序从main()函数开始执行,那么main()是由谁调用的呢?其实一个程序是从_start()开始执行的,_start()就是C基础库中的函数,这个函数做一些初始化工作,然后就调用了main()函数,这样main()就开始执行了。那么_start()也是由谁调用的呢?_start()的地址写在了ELF文件中。我们知道,Linux中可执行文件是ELF格式的,ELF文件中包含了ELF文件头、代码段、数据段等内容。我们调用fork(2)创建一个新进程,然后调用execve(2)将可执行文件的代码段和数据块映射到进程地址空间中。ELF文件头中有一个字段,这个字段存储的是一个函数的地址,当可执行文件映射完毕后,内核用这个地址替换中断返回地址,然后返回用户态,这样就开始执行这个函数了。一般情况下,这个函数就是_start()的地址,这是在链接过程中由链接器设置的。

    另一方面,当main()执行完毕后,其实一个程序并没有结束,比如我们可以通过atexit(3)注册一些函数,这些函数在main()之后执行。main()结束后,C基础库会调用exit(3),exit(3)有两项工作:(1)依次调用用户通过atexit(3)注册的各个函数;(2)调用_exit(2)结束进程。因此,如果main()函数不是通过return语句结束,而是直接调用_exit(2)结束,那么通过atexit(3)注册的函数就不会执行了。汗,上次面试还被问到了这个问题。

   看过glibc和bionic的源代码后,才发现这里面大有学问。比如:如何编码计算一个字符串的长度?我想到的方法就是设置两个指针ptr1和ptr2,这两个指针都指向字符串起始地址,然后ptr2向后移动,移到*ptr2=='\0'结束,ptr2-ptr1就是字符串的长度。这也是最简单的方法。但是glibc中不是这么实现的,glibc从算法层面对strlen(3)进行了优化,从而大幅提升了strlen(3)的效率。还有,以前只知道malloc(3)可以申请内存,free(3)可以释放内存,但是从来不知道具体怎么实现的。以前一致纳闷的一个问题是free(ptr)究竟释放了多少内存?因为我只向free(3)传递了一个指针(就是一个地址),它怎么知道要释放多少内存?看过bionic和glibc代码后也清楚了内存的申请和释放过程。

    总之,C基础库中东西很多,值得花时间看一看。