运行库实现
运行库实现
C语言运行库
-
开始
-
入口函数
-
程序运行的最初入口点不是main函数,而是由运行库为其提供的入口函数。主要负责:准备好程序运行环境及初始化运行库,调用main函数执行程序主体,清理程序运行后的各种资源。
-
运行库为所有程序提供的入口函数应该相同,在链接程序时需要指定该入口函数名。
-
-
-
堆的实现
-
实现一个以空闲链表算法为基础的堆空间分配算法
-
简单起见,堆空间大小固定为32MB
-
在Windows平台下不使用HeapAlloc等堆分配算法,采用VirtualAlloc向系统直接申请32,MB空间,由我们自己的堆分配算法实现malloc
-
Linux平台下,使用brk数据段结束地址向后调整32MB,将这块空间作为堆空间
-
黑话:践踏(trample)一块内存指的是去读写这块内存的每一个字节。brk所分配的虚地址就是需要在践踏之后才会被操作系统自动地分配实际页面。所以按页需求分配又称为按践踏分配。
-
-
IO与文件操作
-
仅实现基本的文件操作
-
简单起见,不实现缓冲机制
-
不对换行机制进行转换
-
支持三个标准的输入输出stdin/stdout/stderr
-
在Windows下,文件基本操作可以使用API实现
-
在Linux下必须使用内嵌汇编实现几个系统调用
-
fopen仅区分r/w/+及它们的组合,不支持追加模式,不对文本模式和二进制模式进行区分
-
-
字符串相关操作
- 纯用户态计算,无须涉及任何与内核交互。
-
格式化字符串
-
printf实现仅支持%d%s,且不支持格式控制
-
实现fprintf和vprintf,实际上printf是fprintf的特殊形式,即目标文件为标准输出
-
实现与文件字符串操作相关的几个函数,fputc和fputs
-
如何使用Mini CRT
C++运行库实现
- HelloWorld程序无须用到的功能就不实现,比如异常
- 尽量简化设计,尽量符合C++标准库的规范
- 对于可以直接在头文件实现的模块尽量在头文件中实现,以免诸多的类、函数的声明和定义造成代码量膨胀,不便于演示
- 运行库代码要做到可以在Windows和Linux上同时运行,因此对于平台相关部分要使用条件编译分别实现。虽然C++运行库几乎没有与系统相关的部分(全局构造和析构除外),CRT已经将大部分系统相关部分封装成C标准库接口,C++运行库只需要调用这些接口即可
- 模板是不需要运行库支持的,它的实现依赖于编译器和链接器,对运行库基本上没有要求
-
new与delete:
-
全局操作符重载,使得有机会在new/delete时记录对象的空间分配和释放,可以实现一些特殊的功能,比如内存泄漏检测。
-
类操作符重载,可以实现一些特殊的需求,比如指定对象申请地址,或者使用自己实现的堆算法对某个对象的申请/释放进行优化等
-
-
C++全局构造与析构
- 它们的实现是依赖于编译器、链接器和运行库三者共同的支持和协作的。构造函数主要实现的是依赖特殊的段合并后形成构造函数数组,而析构则依赖于atexit函数。
-
atexit实现
-
atexit的用法十分简单,即由它注册的函数会在进程退出前,在exit函数中被调用。atexit和exit函数实际上并不属于C++运行库的一部分,它们是C语言运行库的一部分。
-
Windows实现思路:使用一个链表把所有注册的函数存储起来,到exit时将链表遍历一遍,执行其中所有的回调函数。
-
Linux版要复杂一些,GCC实现全局对象的析构调用的__cxa_atexit,它不是C语言标准库函数,它是GCC实现的一部分。
-
-
入口函数修改
- 由于增加了全局构造和析构的支持,需要对入口函数和exit函数进行修改
-
stream与string