为了总结在《程序员的自我修养--链接、装载与库》这本书中的”运行库实现“章节学到的知识,现编写”运行库实现“一文。因为要讲清楚实现运行库的细节是比较复杂的,同时也限于篇幅和避免复杂繁琐,所以本文只会提及关键实现处并给出源代码,同时给出相关知识点在《程序员的自我修养--链接、装载与库》中的相关章节和相关参考资料,如有不理解的地方请回帖或私信我,鉴于本人才疏学浅,如有错误,欢迎指正。
1.为了便于读者理解,本文大量复制《程序员的自我修养--链接、装载与库》中的文字,如有侵权,请管理员删帖或联系我删帖。 2.《程序员的自我修养--链接、装载与库》已经给出了“运行库实现”的完整源代码,且本文中的绝大多数源代码都是直接复制其中的代码(如有侵权,请管理员删帖或联系我删帖),但是因为书籍年代久远(09年出版的)和实验环境的改变的原因,我修改了一部分源代码以顺利运行。
实现32位简单版 Windows和Linux双平台的C++运行库
帮助理解CRT和C++运行库的结构,从而举一反三
我们先实现CRT,然后再添加C++运行库的功能
1.Linux部分的实现代码中的内联汇编使用的是AT&T汇编 ,和x86汇编相似,但是区别也很大 2.规定源代码中的函数执行失败返回-1
3.规定《程序员的自我修养--链接、装载与库》简称《修养》 4.实验环境:
在开始实现Mini CRT之前,首先要对它进行基本的规划。“麻雀虽小五脏俱全”,虽然Mini CRT很小,但它应该具备CRT的基本功能以及遵循几个基本设计原则,这些我们归结为如下几个方面:
为了使CRT能够同时支持Linux和 Windows两个平台,必须针对这两个操作系统环境的不同进行条件编译。在Mini CRT中,我们使用宏WIN32 为标准来决定是Windows还是Linux 。因为实际的代码常呈现这样的结构:
通常我们会把CRT的各个函数的声明放在不同的头文件中,比如 IO相关的位于stdio.h
;字符串和堆相关的放在stdlib.h
中。为了简单起见,将Mini CRT中所有函数的声明都放在minicrt.h
中。
因为我们的main函数需要两个参数:参数个数和参数字符串指针数组,所以我们得进行命令行解析
Windows实现部分
Windows用GetCommandLineA
获取输入字符串,然后手动提取参数,不过该提取算法有缺陷,如果有多余的空格,会得到"\0"的字符串
实现代码如下:
Linux实现部分
当进入入口函数的时候,堆栈依次保存着参数个数、参数字符串指针、环境变量字符串指针和其它信息(注意保存没有返回地址,因为这是入口函数 ),此时ESP指向argc,堆栈信息如下:
然而因为我们的入口函数不是裸函数 ,所以会在函数开头生成如下指令
所以执行完上述指令后,此时EBP指向旧的EBP,新的堆栈信息如下:
所以此时可以通过EBP加上偏移的方式获取我们所需的参数
实现代码如下:
参考本文—“5.2.2 堆的实现”中的mini_crt_heap_init
函数
参考本文—“5.2.3 IO与文件操作”中的mini_crt_io_init
函数
调用需要在main
函数之前执行的函数,如全局对象的构造函数,在C++运行库部分进行实现
也就是调用我们程序的入口函数
Mini CRT结束部分很简单,它要完成两项任务:一个就是调用由atexit()
注册的退出回调函数;另外一个就是实现结束进程。这两项任务都由exit()
函数完成,这个函数在Linux中的实现是调用Linux的1号系统调用实现进程结束,ebx表示进程退出码:而Windows则提供了一个叫做ExitProcess
的API,直接调用该API即可结束进程。
不过在进行系统调用或API之前,exit()
还有一个任务就是调用由atexit()
注册的退出回调函数,这个任务通过调用mini_crt_exit_routine()
实现。atexit()
注册回调函数的机制主要是用来实现全局对象的析构的,在这一节中暂时不打算让MiniCRT支持C++,所以暂时将调用mini_crt_exit_routine()
这个函数的那行代码去掉。
实现malloc()
函数和free()
函数。当然堆的实现方法有很多,在不同的操作系统平台上也有很多可以选择的方案,在遵循Mini CRT的原则下,我们将Mini CRT堆的实现归纳为下面几条。
堆分配算法的原理参考《修养》10.3.4章节 ,整个堆空间按照是否被占用而被分割成了若干个空闲(Free)块和占用(Used)块,它们之间由双向链表链接起来。
当用户要申请一块内存时,堆分配算法将遍历整个链表,直到找到一块足够大的空闲块,如果这个空闲块大小刚好等于所申请的大小,那么直接将这个空闲块标记为占用块,然后将它的地址返回给用户;如果空闲块大小大于所申请的大小,那么这个空闲块将被分割成两块,其中一块大小为申请的大小,标记为占用,另外一块为空闲块。
当用户释放某一块空间时,堆分配算法会判别被释放块前后两个块是否为空闲块,如果是,则将它们合并成一个大的空闲块。
实现代码如下:
我们在malloc.c中实现了3个对外的接口函数,分别是: mini_crt_init_heap、malloc和free
。不过这个堆的实现还比较简陋:它的搜索算法是O(n)的(n是堆中分配的块的数量);堆的空间固定为32MB,没有办法扩张;它没有实现realloc、calloc
函数;它没有很好的堆溢出防范机制;它不支持多线程同时访问等等。
虽然它很简陋,但是它体现出了堆分配算法的最本质的几个特征,其他的诸如改进搜索速度、扩展堆空间、多线程支持等都可以在此基础上进行改进,由于篇幅有限,我们也不打算一一实现它们,读者如果有兴趣,可以自己考虑动手改进Mini CRT,为它增加上述特性。
IO部分在任何软件中都是最为复杂的,在CRT中也不例外。在传统的C语言和UNIX里面,IO和文件是同一个概念,所有的IO都是通过对文件的操作来实现的。因此,只要实现了文件的基本操作(fopen、fread、fwrite、fclose和 fseek
),即是完成了Mini CRT的IO部分。与堆的实现一样,我们需要为Mini CRT的IO部分设计一些实现的基本原则:
实现代码如下:
另外还有―段与文件操作相关的声明须放在minicrt.h
里面:
由于省略了诸多实现内容,所以CRT IO部分甚至可以不要做任何初始化,于是IO的初始化函数mini_crt_init_io
也形同虚设,仅仅是一个空函数而已。
字符串相关的操作也是CRT的一部分,包括计算字符串长度、比较两个字符串、整数与字符串之间的转换等。由于这部分功能无须涉及任何与内核交互,是纯粹的用户态的计算,所以它们的实现相对比较简单。
实现代码如下:
现在的Mini CRT已经初具雏形了,它拥有了堆管理、文件操作、基本字符串操作。接下来将要实现的是CRT中一个如雷贯耳的函数,那就是printf
。printf
是一个典型的变长参数函数,即参数数量不确定,如何使用和实现变长参数的函数参考《修养》11.2.2章节 。本节实现的相关内容列举如下:
实现代码如下:
在Mini CRT的vfprintf
实现中,并不支持特殊的格式控制符,例如位数、进度控制等,仅支持%d
与%s
这样的简单转换。真正的vfprintf
格式化字符串实现比较复杂,因为它支持诸如%f
、%x
已有各种格式、位数、精度控制等,在这里并没有将它们一一实现,也没有这个必要,Mini CRT的printf
已经能够充分展示printf
的实现原理和它的关键技巧,读者也可以根据Mini CRT printf
的实现去更加深入地分析glibc或MSVC CRT的相关代码。
Mini CRT将以库文件和头文件的形式提供给用户。首先我们建立一个minicrt.h
的头文件,然后将所有相关的常数定义、宏定义,以及Mini CRT所实现的函数声明等放在该头文件里。当用户程序使用Mini CRT 时,仅需要#include "minicrt.h"
即可,而无须像标准的CRT一样,需要独立的包含相关文件,比如stdio.h
、stdlib.h
等。
minicrt.h
的代码如下:
接下来的问题是如何编译得到库文件了。由于动态库的实现比静态库要复杂,所以MiniCRT仅仅以静态库的形式提供给最终用户,在 Windows下它是minicrt.lib
;在Linux下它是minicrt.a
。在不同平台下编译和制作库文件的步骤如下所示,Linux 下的命令行为:
-m32
是指生成32位的中间目标文件,因为我的实验环境是64位的,而实现的运行库是32位的
在Windows 下, Mini CRT的编译方法如下:
为了测试Mini CRT是否能够正常运行,我们专门编写了一段测试代码,用于测试MiniCRT的功能:
这段代码用到了Mini CRT中绝大部分函数,包括malloc
、free
、fopen
、fclose
、fread
、fwrite
、printf
,并且测试了main
参数。它的作用就是将main的参数字符串都保存到文件中,然后再读取出来,由printf
显示出来。在Linux 下,可以用下面的方法编译和运行test.c
:
可以看到静态链接Mini CRT最后输出的可执行文件只有10964字节 ,这正体现出了Mini CRT的“迷你”之处,而如果静态链接glibc时,最后可执行文件则约为645KB 。在Windows 下,编译和运行test.c
的步骤如下:
与Linux类似,Windows下使用Mini CRT链接的可执行文件也非常小,只有12800字节 .如果我们使用dumpbin
查看它的导入函数可以发现,它仅依赖于Kernel32.DLL
中我们使用到的函数 。
现在Mini CRT已经能够支持最基本的C语言程序运行了。C++作为兼容C语言的扩展语言,它的运行库的实现其实并不复杂,在这一节中将介绍如何为Mini CRT添加对C++语言的一些常用的操作支持。
通常C++的运行库都是独立于C语言运行库的,比如Linux 下C语言运行库为libc.so/libc.a
,而C++运行库为(libstdc++.so/libstdc++.a
);Windows 的C语言运行库为libcmt.lib/msvcr90.dll
,而C++运行库为libcpmt.Jib/msvcp90.dll
。一般这些C++的运行库都是依赖于C运行库的,它们仅包含对C++的一些特性的支持,比如new/delete、STL、异常处理、流( stream)等。但是它们并不包含诸如入口函数、堆管理、基本文件操作等这些特性,而这些也是C++运行库所必需的,比如C++的流和文件操作依赖于C运行库的基本文件操作,所以它必须依赖于C运行库。 本节中我们将在 Mini CRT的基础上实现一个支持C++的运行库,当然出于简单起见,将这个C++运行库的实现与Mini CRT合并到-起,而不是单独成为一个库文件,也就是说经过这一节对Mini CRT的功能改进,最终编译出来的minicrt.a/minicrt.lib
将支持C++的诸多特性。 当然,要完整实现一个C++的运行库是很费事的一件事,C++标准模板库STL包含了诸如流、容器、算法、字符串等,规模较为庞大。出于演示的目的,我们将对C++的标准库进行简化,最终目标是实现一个能够成功运行如下C++程序代码的运行库:
上面这段程序看似简单,实际上它用到了C++运行库的诸多功能。我们将所用到的特性列举如下:
在开始本节之前,还是按照前面Mini CRT实现时的做法:在进入具体主题之前先列举一些实现的原则。在实现 Mini CRT对C++的支持时,我们遵循如下原则:
malloc/free
是C/C++语言的标准库函数,new/delete
是C++的运算符,它们都用于申请动态内存和释放。对于类类型的对象而言,光用malloc/free
无法满足动态申请对象的要求,因为对象在创建的同时要自动执行构造函数,对象在消亡时要自动执行析构函数。由于malloc/free
是库函数,编译器在编译时判定库函数是已编译的代码,编译器不会进行编译和检查。而new/delete
是C++的运算符,编译器会进行编译和检查,如果是类对象则不仅会调用new/delete
,而且会调用类对象的构造函数和析构函数。我们来看一个例子:
malloc/free生成的代码
用GCC编译并反汇编这段代码,将会看到malloc/free
的实现:
可以看到,malloc
仅仅申请了内存,并没有调用构造函数 ;同样free
也仅仅释放内存,也没有调用析构函数 。
new/delete生成的代码
用GCC编译并反汇编这段代码,将会看到new/delete
的实现:
可以看到,new
操作的实现实际上是先调用了_Znwm
函数,再调用_ZN1CC1Ev
函数;delete
操作的实现实际上是先调用了_ZN1CD1Ev
函数,再调用_ZdlPvm
函数,如果用c++filt
将这四个符号反修饰,可以看到它的真面目:
可以看到,new
不仅申请了内存,而且还调用了构造函数 ;同样delete
也不仅释放了内存,而且还调用了析构函数 。注意调用构造和析构函数的代码是编译器帮我们生成并进行调用的 。
从上节的实验可以知道new
的真面目是一个叫operator new
的函数,这也是我们在C++中熟悉的运算符函数。在C++中,运算符实际上是一种特殊的函数,叫做运算符函数,一般new
运算符被定义为(上面是64位程序,所以是long
):
void* operator new(unsigned int size)
除了new、delete
这样的运算符以外,+、-、*、%
等都可以被认为是运算符,这些运算符都有相对应的运算符函数。对于operator new
函数来说,它的参数size是指须要申请的空间大小,一般是指new
对象的大小,而返回值是申请的堆地址。delete
运算符函数的第一个参数是对象的地址,第二个参数是对象的大小,它没有返回值。
既然new/delete
的实现是相应的运算符函数,那么,如果要实现new/delete
,就只须要实现这两个函数就可以了。而这两个函数的主要功能是申请和释放堆空间,这再容易不过了,因为在 Mini CRT中已经实现了堆空间的申请和释放函数:malloc和free
。于是new/delete
的实现变得尤为简单,它们的实现源代码如下:
在上面代码中除了new/delete
之外,我们还看到了new[]和 delete[]
,它们分别是用来申请和释放数组的,在这里一并予以实现。另外除了申请和释放堆空间之外,没有看到任何对象构造和析构的调用,其实对象的构造和析构是在new/delete
之前/之后由编译器负责产生相应的代码进行调用的,new/delete
仅仅负责堆空间的申请和释放,不负责构造和析构。
在真实的C++运行库中,new/delete
的实现要比.上面的复杂一些,它们除了使用malloc/free
申请释放空间之外,还支持new_handler
在申请失败时给予程序进行补救的机会、还可能会抛出bad_alloc
异常等,由于Mini CRT并不支持异常,所以就省略了这些内容。
另外值得一提的是,在使用真实的C++运行库时,也可以使用上面这段代码自己实现new/delete
,这样就会将原先C++运行库的new/delete
覆盖,使得有机会在new/delete
时记录对象的空间分配和释放,可以实现一些特殊的功能,比如检查程序是否有内存泄露。这种做法往往被称为全局new/delete运算符重载(Global new/delete operator overloading)
。除了重载全局new/delete
运算符之外,也可以重载某个类的new/delete
,这样可以实现一些特殊的需求,比如指定对象申请地址(Replacement new),或者使用自己实现的堆算法对某个对象的申请/释放进行优化,从而提高程序的性能等,这方面的讨论在C++领域已经非常深入了,在此我们不一一展开了。
全局对象的构造函数要在main
函数之前被执行,全局对象的析构函数要在main
函数之后被执行。 要明白全局对象的构造函数是如何在main
函数之前被执行,全局对象的析构函数是如何在main
函数之后被执行的?我们可以从《修养》11.4章节 中找到答案,以下内容根据《修养》得出:
注意:因为完整的原理比较复杂,我就粗略说一下,详细内容请参考《修养》
结论:对于一个文件来说,全局对象/静态全局对象无论有多少个,都只会生成一个初始化函数_GLOBAL__sub_XXX
。 如下面的test.cpp
文件会生成_GLOBAL__sub_I__ZN10HelloWorldC2Ei
,xixi.cpp
会生成_GLOBAL__sub_I__ZN6PersonC2Ei
。
不用担心两个_GLOBAL__sub_XXX
函数中都调用_Z41__static_initialization_and_destruction_0ii
,而可能出现重定义的问题,因为最后在链接成ELF文件的时候,会将其中的一个_Z41__static_initialization_and_destruction_0ii
的末尾添加数字避免重定义。
_Z41__static_initialization_and_destruction_0ii
函数的内容:
我们可以发现,在_GLOBAL__sub_XXX
函数中会调用全局对象/静态全局对象的构造函数,并用__cxa_atexit
注册其析构函数。下面是__cxa_atexit
函数的原型:
第一个参数是要注册的函数,第二个参数是要给注册的函数用的参数,第三个参数是要注册的函数所在模块的标识句柄。用__cxa_atexit
注册的函数会被添加到一个链表中,然后会在exit
函数(exit
函数会在main
函数之后被执行)中遍历该链表,循环调用其中的函数,且__cxa_atexit
函数采用头插法
,先注册后调用,刚好满足先构造后析构的特性。总结一下,通过上述分析得到:如果_GLOBAL__sub_XXX
初始化函数被调用,那么全局对象/静态全局对象的构造函数和析构函数都会被正确调用。现在的问题是怎么调用_GLOBAL__sub_XXX
初始化函数呢?
glibc是这样做的,它会把_GLOBAL__sub_XXX
函数放在.ctors
段(当前新版本的glibc,如2.27版本,会放在.init_array
,同时兼容.ctors
段),然后在链接阶段,,把各个文件的.ctors
段拼接在一起,形成一个新的.ctors
段,这样就形成了一个函数指针数组。为了遍历这个数组,我们一般需要知道数组的首地址和数组元素个数,glibc会在链接阶段,链接crtbeginT.o
和crtend.o
文件,且链接顺序是这样的:
链接顺序: ld crt1.o crti.o crtbeginT.o [user_objects] [system_libraries] crtend.o crtn.o
crtbeginT.o
:也有一个ctors
段,里面存储的是一个4字节的-1(0xFFFFFFFF) ,由链接器将这个数字改成全局构造函数的数量。根据上面的链接顺序可知,该段是所有.ctors段的开头部分,这个段还将起始地址定义为符号CTOR_LIST ,这样CTOR_LIST 代表的就是所有.ctors
段最后合并后的起始地址了,这样就解决了初始化数组的遍历问题。
crtend.o
:也有一个.ctors
段,它的内容就是一个0 ,然后定义一个符号CTOR END__ ,根据链接顺序可知,CTOR END__ 代表的就是所有.ctors
段最后合并后的结束地址了,我们也可以用CTOR_LIST 和CTOR END__ 对初始化数组进行遍历。
GCC遍历初始化数组的代码:
根据上面glibc全局构造和析构的原理
可知,编译器会帮我们生成初始化函数并放在.ctors
段,链接器会帮我们把所有文件的.cotrs
段拼接在一起,形成一个初始化函数数组 ,那么我们只需要做以下几点即可:
1.实现类似crtbeginT.o
和crtend.o
的文件,用来遍历初始化数组 2.实现__cxa_atexit
函数,因为初始化函数中会用该函数注册析构函数 (在下一节atexit实现
中实现) 3.遍历初始化数组,循环调用其中的初始化函数
实现代码如下:
msvc全局构造和析构的原理和glibc差不多,都是通过段拼接形成数组,然后遍历数组即可。我们以下面的代码为例来进行讲解。
结论:对于一个文件来说,全局对象/静态全局对象有n个,那么会生成n个初始化函数(这和glibc有区别),初始化函数的符号名像这样??EHw1@@YAXXZ (void cdecl `dynamic initializer for 'Hw1''(void)) 。 我们以下面的代码为例来进行讲解。
我们用Visual Studio在全局对象定义处下断点,查看反汇编:
我们可以发现在初始化函数中会调用全局对象的构造函数,并用atexit
注册一个会调用析构函数的函数。下面是atexit
函数的原型
参数是要注册的函数,atexit
函数的功能和__cxa_atexit
一模一样,只是少两个参数罢了,这里就不重复了。总结一下,通过上述分析得到:如果初始化函数被调用,那么全局对象的构造函数和析构函数都会被正确调用。现在的问题是怎么调用初始化函数呢?
msvc是这样做的,它会把初始化函数放在.CRT$XCU
段(如上图,上面代码生成的两个初始化函数都放入.CRT$XCU
段),然后在链接阶段,,把各个文件的.CRT$XCU
段拼接在一起,然后放在.rdata
段中,这样就形成了一个函数指针数组。为了遍历这个数组,我们一般需要知道数组的首地址和数组元素个数。msvc在运行库中定义两个全局变量,如下:
其中pragma
指令是创建名为.CRT$XCA
和.CRT$XCZ
的段,__declspec
指令表示ctors_begin
变量会被分配到.CRT$XCA
段,ctors_end变量会被分配到.CRT$XCZ
段,链接器会把所有相同属性的段合并,即所有段名为.CRT$XC?
按照字母顺序依次拼接在一起,然后放入.rdata
段。所以实际的段拼接顺序如下:
.CRT$XCA ... .CRT$XCU .CRT$XCU .CRT$XCU ... .CRT$XCZ
这样,初始化数组的第一个元素为0 ,ctors_begin
指向数组的第一个元素,初始化数组的最后一个元素为0 ,ctors_end
指向数组的最后一个元素。这样我们就可以循环遍历数组,调用其中的初始化函数了。
根据上面msvc全局构造和析构的原理
可知,编译器会帮我们生成初始化函数并放在.CRT$XCU
段,链接器会把所有相同属性的段合并,即所有段名为.CRT$XC?
按照字母顺序依次拼接在一起,然后放入.rdata
段,形成一个初始化函数数组 ,那么我们只需要做以下几点即可:
1.添加属性为long,read
,名为.CRT$XCA
的段,添加属性为long,read
,名为.CRT$XCZ
的段 2.在.CRT$XCA
段中分配一个4个字节值为0的变量,在.CRT$XCZ
段中分配一个4个字节值为0的变量 3.实现atexit
函数,因为初始化函数会用该函数注册一个会调用析构函数的函数(在下一节atexit实现
中实现) 4.遍历初始化数组,循环调用其中的初始化函数
atexit()
的用法十分简单,即由它注册的函数会在进程退出前,在exit()
函数中被调用。atexit()
和exit()
函数实际上并不属于C++运行库的一部分,它们是C语言运行库的一部分。在前面实现Mini CRT时我们在exit()
函数的实现中预留了对atexit()
的支持(//mini_crt_call_exit_routine();)。
本来可以不实现atexit()
的,毕竟它不是非常重要的CRT函数,但是在这里不得不实现atexit
的原因是:所有全局对象的析构函数——不管是Linux还是 Windows——都是通过atexit
或其类似函数来注册的,以达到在程序退出时执行的目的。
实现它的基本思路也很简单,就是使用一个链表把所有注册的函数存储起来,到exit()
时将链表遍历一遍,执行其中所有的回调函数,Windows版的atexit
的确可以按照这个思路实现。
Linux 版的atexit 要复杂一些,导致这个的问题的原因是GCC实现全局对象的析构不是调用的 atexit,而是调用的__cxa_atexit
。它不是C语言标准库函数,它是GCC实现的一部分。为了兼容GCC,Mini CRT不得不实现它。它的定义与atexit()
有所不同的是,__cxa_atexit
所接受的参数类型和atexit
不同:
__cxa_atexit
所接受的函数指针必须有一个void*
型指针作为参数,并且调用__cxa_atexit
的时候,这个参数(void* arg
)也要随着记录下来,等到要执行的时候再传递进去。也就是说,__cxa_atexit()
注册的回调函数是带一个参数的,我们必须把这个参数也记下来。
__cxa_atexit
的最后一个参数可以忽略,在这里不会用到。
于是在设计链表时要考虑到这一点,链表的节点必须能够区分是否是atexit()
函数还是__cxa_atexit()
注册的函数,如果是__cxa_atexi()
注册的函数,还要把回调函数的参数保存下来。我们定义链表节点的结构如下:
其中is_cxa
成员如果不为0,则表示这个节点是由__cxa_atexit()
注册的回调函数,arg
成员表示相应的参数。atexit的实现代码如下:
值得一提的是,在注册函数时,被注册的函数是插入到列表头部的,而最后mini_crt_call_exit_routine()
是从头部开始遍历的,于是由atexit()
或__cxa_atexit()
注册的函数是按照先注册后调用的顺序,这符合析构函数的规则,因为先构造的全局对象应该后析构。
由于增加了全局构造和析构的支持,那么需要对Mini CRT的入口函数和exit()函数 进行修改,把对do_global_ctors()和 mini_crt_call_exit_routine()
的调用加入到entry()
和exit()
函数中去。修改后的entry.c
如下(省略一部分未修改的内容):
C++的Hello World里面一般都会用到cout
和string
,以展示C++的特性。流和字符串是C++ STL
的最基本的两个部分,我们在这一节中为MiniCRT增加string
和stream
的实现,在有了流和字符串之后,Mini CRT
将最终宣告完成,可以考虑将它重命名为Mini CRT++
。
当然,在真正的STL实现中,string
和stream
的实现十分复杂,不仅有强大的模板定制功能、缓冲,庞大的继承体系及一系列辅助类。我们在实现时还是以展示和剖析为最基本的目的,简化一切能够简化的内容。string
和 stream
的实现将遵循下列原则。
stream
和string
类的实现用到了不少C++语言的特性,已经一定程度上偏离了本文的意义,因此在此仅将它们的实现源代码列出,而不做更多的详细分析。有兴趣的读者可以参考C++STL
的相关实现的资料,如果对C++语言本身不熟悉,也可以跳过这一节,这并不影响对Mini CRT 整体实现的理解。string
和iostream
的实现如下:
我们的Mini CRT
终于完成了对C++
的支持,同时它也升级为了Mini CRT++
。在这一节中将介绍如何编译并且在自己的程序中使用它。首先展示在 Windows 下编译的方法:
这里新增的一个编译参数为/GR-
,它的意思是关闭RTTI
功能,否则编译器会为有虚函数的类产生RTTI
相关代码,在最终链接时会看到const type_info::vftable
符号未定义的错误。
而Mini CRT++
为了能够在Linux 下正常运行,还须要建立一个新的源代码文件叫做sysdep.cpp
,用于定义Linux 平台相关的一个变量:
这个变量是用于处理共享库的全局对象析构的。我们知道共享库也可以拥有全局对象,这些对象在共享库被卸载时必须被正确地析构。而共享库有可能在进程退出之前被卸载,比如使用dlopen/dlclose
就可能导致这种情况。那么一个问题就产生了,如何使得属于某个共享库的全局对象析构函数在共享库被卸载时运行呢? GCC的做法是向__cxa_atexit()
传递一个参数,这个参数用于标示这个析构函数属于哪个共享对象。我们在前面实现__cxa_atexit()
时忽略了第三个参数,实际上这第三个参数就是用于标示共享对象的,它就是__dso_handle
这个符号。由于在Mini CRT++
中并不考虑对共享库的支持,于是我们就仅仅定义这个符号为0
,以防止链接时出现符号未定义错误。
Mini CRT++
在Linux 平台下编译的方法如下:
在Windows 下使用Mini CRT++
的方法如下:
在Linux 下使用Mini CRT++
的方法如下:
crtbegin.o
和crtend.o
在ld链接时位于用户目标文件的最开始和最后端,以保证链接的正确性。
在本文中,我们首先尝试实现了一个支持C运行的简易CRT:Mini CRT 。接着又为它加上了一些C++语言特性的支持 ,并且将它称为Mini CRT++ 。在实现C语言运行库的时候,介绍了入口函数entry
、堆分配算法malloc/free
、IO和文件操作fopen/fread/fwrite/fclose
,字符串函数strlen/strcmp/atoi
和格式化字符串printf/fprintf
。在实现C++运行库 时,着眼于实现C++的几个特性:new/delete
、全局构造和析构
、stream
和string
类。 因此在实现Mini CRT++
的过程中,我们得以详细了解并且亲自动手实现运行库的各个细节,得到一个可编译运行的瘦身运行库版本 。当然,Mini CRT++
所包含的仅仅是真正的运行库的一个很小子集 ,它并不追求完整,也不在运行性能上做优化,它仅仅是一个CRT的雏形,虽说很小,但能够通过Mini CRT++
窥视真正的CRT
和C++运行库
的全貌,抛砖引玉、举一反三正是Mini CRT++
的目的。
这是msvc编译器的问题。 比如下面的代码:
用IDA查看main
函数的反汇编
我们发现,并不是直接使用cout
这个全局变量,而是进行拷贝构造,而且是强转cout
为父类型的指针后进行的拷贝构造,所以调用的是父类的拷贝构造 ,这还不关键。
关键的是在endl
函数调用之前,拷贝构造出来的对象就被析构了 ,不要紧,因为我写的析构函数不会改变属性,还是fp
存储的还是stdout,但是存储this指针的是esp
。
那么就导致在开栈的时候,就把属性fp
给改变了,就导致endl
函数的调用失败,我搞不懂msvc编译器怎么不用ebp-xxx
来存储局部变量
相反g++编译器就很正常
局部变量ebp-xxx
作为this对全局变量cout进行拷贝构造,然后打印字符串,调用endl
函数,然后才析构
这是在编译运行C++库时出现的警告,但不影响运行库的使用
new
对象数组的话,会多分配一个int,用于在开头存储元素的个数,用于循环析构和传递delete[]
第二个参数,而对于不是new
对象数组,那么没必要分配一个int,因为不需要循环析构,如下代码:
对于delete[]
的使用,不是对象数组,那么delete
和delete[]
没有区别,但是对于对象数组,只有delete[]
才会析构每个对象和正确释放内存,但如果用delete
对象数组的话,只会调用第一个对象的析构函数,并且释放的地址是原地址+sizeof(int) ,这是不对的,就是简单当成一个对象进行析构和释放内存了(以为当前地址就是申请的地址,当然对于一个对象来说就是如此)
见附件 密码均为:kanxue
C_minicrt.zip:C运行库源码 CPP_minicrt.zip:C++运行库源码
/
/
Windows部分代码
/
/
Linux部分实现代码
/
/
Windows部分代码
/
/
Linux部分实现代码
int
flag
=
0
;
int
argc
=
0
;
char
*
argv[
16
];
char
*
cl
=
GetCommandLineA();
/
/
解析命令行
/
/
算法缺陷:有多余的空格,会得到
"\0"
的字符串
argv[
0
]
=
cl;
argc
+
+
;
while
(
*
cl)
{
if
(
*
cl
=
=
'\"'
)
{
if
(flag
=
=
0
)
{
flag
=
1
;
}
else
{
flag
=
0
;
}
}
else
if
(
*
cl
=
=
' '
&& flag
=
=
0
)
{
if
(
*
(cl
+
1
))
{
argv[argc]
=
cl
+
1
;
argc
+
+
;
}
*
cl
=
'\0'
;
}
cl
+
+
;
}
int
flag
=
0
;
int
argc
=
0
;
char
*
argv[
16
];
char
*
cl
=
GetCommandLineA();
/
/
解析命令行
/
/
算法缺陷:有多余的空格,会得到
"\0"
的字符串
argv[
0
]
=
cl;
argc
+
+
;
while
(
*
cl)
{
if
(
*
cl
=
=
'\"'
)
{
if
(flag
=
=
0
)
{
flag
=
1
;
}
else
{
flag
=
0
;
}
}
else
if
(
*
cl
=
=
' '
&& flag
=
=
0
)
{
if
(
*
(cl
+
1
))
{
argv[argc]
=
cl
+
1
;
argc
+
+
;
}
*
cl
=
'\0'
;
}
cl
+
+
;
}
...
push ebp
mov ebp,esp
...
...
push ebp
mov ebp,esp
...
int
argc
=
0
;
char
*
*
argv
=
0
;
char
*
ebp_reg
=
0
;
/
/
ebp_reg
=
%
ebp;
asm(
"movl %%ebp,%0 \n"
:
"=r"
(ebp_reg));
argc
=
*
(
int
*
)(ebp_reg
+
4
);
argv
=
(char
*
*
)(ebp_reg
+
8
);
int
argc
=
0
;
char
*
*
argv
=
0
;
char
*
ebp_reg
=
0
;
/
/
ebp_reg
=
%
ebp;
asm(
"movl %%ebp,%0 \n"
:
"=r"
(ebp_reg));
argc
=
*
(
int
*
)(ebp_reg
+
4
);
argv
=
(char
*
*
)(ebp_reg
+
8
);
if
(mini_crt_heap_init()
=
=
-
1
)
{
crt_fatal_error(
"heap initialize failed"
);
}
if
(mini_crt_heap_init()
=
=
-
1
)
{
crt_fatal_error(
"heap initialize failed"
);
}
if
(mini_crt_io_init()
=
=
-
1
)
{
crt_fatal_error(
"IO initialize failed"
);
}
if
(mini_crt_io_init()
=
=
-
1
)
{
crt_fatal_error(
"IO initialize failed"
);
}
/
/
do_global_ctors();
ret
=
main(argc,argv);
exit(ret);
void exit(
int
exitCode){
/
/
mini_crt_call_exit_routine();
ExitProcess(exitCode);
asm(
"movl %0,%%ebx \n"
"movl $1,%%eax \n"
"int $0x80 \n"
"hlt \n"
:
:
"m"
(exitCode)
:
"%ebx"
);
exit(ret);
void exit(
int
exitCode){
/
/
mini_crt_call_exit_routine();
ExitProcess(exitCode);
asm(
"movl %0,%%ebx \n"
"movl $1,%%eax \n"
"int $0x80 \n"
"hlt \n"
:
:
"m"
(exitCode)
:
"%ebx"
);
/
/
malloc.c
typedef struct __heap_header{
enum{
HEAP_BLOCK_FREE
=
0xABABABAB
,
HEAP_BLOCK_USED
=
0xCDCDCDCD
,
}
type
;
unsigned size;
/
/
block size including header
struct __heap_header
*
next
;
struct __heap_header
*
prev;
} heap_header;
static heap_header
*
list_head
=
NULL;
void free(void
*
ptr){
heap_header
*
header
=
(heap_header
*
)ADDR_ADD(ptr,
-
HEADER_SIZE);
if
(header
-
>
type
!
=
HEAP_BLOCK_USED)
{
return
;
}
/
/
合并前一块
if
(header
-
>prev !
=
NULL && header
-
>prev
-
>
type
=
=
HEAP_BLOCK_USED)
{
header
-
>prev
-
>
next
=
header
-
>
next
;
if
(header
-
>
next
!
=
NULL)
{
header
-
>
next
-
>prev
=
header
-
>prev;
}
header
-
>prev
-
>size
+
=
header
-
>size;
header
=
header
-
>prev;
}
/
/
合并后一块
if
(header
-
>
next
!
=
NULL && header
-
>
next
-
>
type
=
=
HEAP_BLOCK_USED)
{
header
-
>size
+
=
header
-
>
next
-
>size;
header
-
>
next
=
header
-
>
next
-
>
next
;
if
(header
-
>
next
!
=
NULL)
{
header
-
>
next
-
>prev
=
header;
}
}
}
void
*
malloc(unsigned size){
heap_header
*
header
=
0
;
if
(size
=
=
0
)
{
return
NULL;
}
header
=
list_head;
while
(header !
=
0
)
{
if
(header
-
>
type
=
=
HEAP_BLOCK_USED)
{
header
=
header
-
>
next
;
continue
;
}
/
/
标记为USED && 没有空闲块
if
(header
-
>size >
=
size
+
HEADER_SIZE &&
header
-
>size <
=
size
+
HEADER_SIZE
*
2
)
{
header
-
>
type
=
HEAP_BLOCK_USED;
}
/
/
标记为USED && 有空闲块
if
(header
-
>size > size
+
HEADER_SIZE
*
2
)
{
/
/
split
heap_header
*
next
=
(heap_header
*
)ADDR_ADD(header,size
+
HEADER_SIZE);
next
-
>
next
=
header
-
>
next
;
next
-
>prev
=
header;
next
-
>
type
=
HEAP_BLOCK_FREE;
next
-
>size
=
header
-
>size
-
HEADER_SIZE
-
size;
if
(header
-
>
next
!
=
NULL)
{
header
-
>
next
-
>prev
=
next
;
}
header
-
>
next
=
next
;
header
-
>
type
=
HEAP_BLOCK_USED;
header
-
>size
=
size
+
HEADER_SIZE;
return
ADDR_ADD(header,HEADER_SIZE);
}
header
=
header
-
>
next
;
}
return
(void
*
)
-
1
;
}
/
/
brk成功返回
0
,失败返回
-
1
,所以不能用brk
/
/
linux brk system call
/
/
static
int
brk(void
*
end_data_segment){
/
/
int
ret
=
0
;
/
/
/
/
brk system call number:
45
/
/
/
/
int
/
usr
/
include
/
asm
-
i386
/
/
unistd.h
/
/
/
/
/
/
asm(
/
/
"movl $45,%%eax \n"
/
/
"movl %1,%%ebx \n"
/
/
"int $0x80 \n"
/
/
"movl %%eax,%0 \n"
/
/
:
"=r"
(ret):
"m"
(end_data_segment));
/
/
}
static void
*
mmap2(void
*
addr, unsigned
len
,
int
prot,
int
flags,
int
fd,
int
offset){
void
*
ret
=
0
;
asm(
"pushl %%ebp \n"
"movl $192,%%eax \n"
"movl %1,%%ebx \n"
"movl %2,%%ecx \n"
"movl %3,%%edx \n"
"movl %4,%%esi \n"
"movl %5,%%edi \n"
"movl %6,%%ebp \n"
"int $0x80 \n"
"movl %%eax,%0 \n"
"popl %%ebp \n"
:
"=r"
(ret)
:
"m"
(addr),
"m"
(
len
),
"m"
(prot),
"m"
(flags),
"m"
(fd),
"m"
(offset)
:
"%ebx"
,
"%ecx"
,
"%edx"
,
"%esi"
,
"%edi"
);
return
ret;
}
int
mini_crt_heap_init(){
void
*
base
=
NULL;
heap_header
*
header
=
NULL;
/
/
32MB
heap size
unsigned heap_size
=
1024
*
1024
*
32
;
base
=
VirtualAlloc(
0
,heap_size,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);
if
(base
=
=
NULL){
return
-
1
;
}
base
=
mmap2(
0
,heap_size,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
if
(base
=
=
(void
*
)
-
1
){
return
-
1
;
}
header
=
(heap_header
*
)base;
header
-
>size
=
heap_size;
header
-
>
type
=
HEAP_BLOCK_FREE;
header
-
>
next
=
NULL;
header
-
>prev
=
NULL;
list_head
=
header;
return
0
;
}
/
/
malloc.c
typedef struct __heap_header{
enum{
HEAP_BLOCK_FREE
=
0xABABABAB
,
HEAP_BLOCK_USED
=
0xCDCDCDCD
,
}
type
;
unsigned size;
/
/
block size including header
struct __heap_header
*
next
;
struct __heap_header
*
prev;
} heap_header;
static heap_header
*
list_head
=
NULL;
void free(void
*
ptr){
heap_header
*
header
=
(heap_header
*
)ADDR_ADD(ptr,
-
HEADER_SIZE);
if
(header
-
>
type
!
=
HEAP_BLOCK_USED)
{
return
;
}
/
/
合并前一块
if
(header
-
>prev !
=
NULL && header
-
>prev
-
>
type
=
=
HEAP_BLOCK_USED)
{
header
-
>prev
-
>
next
=
header
-
>
next
;
if
(header
-
>
next
!
=
NULL)
{
header
-
>
next
-
>prev
=
header
-
>prev;
}
header
-
>prev
-
>size
+
=
header
-
>size;
header
=
header
-
>prev;
}
/
/
合并后一块
if
(header
-
>
next
!
=
NULL && header
-
>
next
-
>
type
=
=
HEAP_BLOCK_USED)
{
header
-
>size
+
=
header
-
>
next
-
>size;
header
-
>
next
=
header
-
>
next
-
>
next
;
if
(header
-
>
next
!
=
NULL)
{
header
-
>
next
-
>prev
=
header;
}
}
}
void
*
malloc(unsigned size){
heap_header
*
header
=
0
;
if
(size
=
=
0
)
{
return
NULL;
}
header
=
list_head;
while
(header !
=
0
)
{
if
(header
-
>
type
=
=
HEAP_BLOCK_USED)
{
header
=
header
-
>
next
;
continue
;
}
/
/
标记为USED && 没有空闲块
if
(header
-
>size >
=
size
+
HEADER_SIZE &&
header
-
>size <
=
size
+
HEADER_SIZE
*
2
)
{
header
-
>
type
=
HEAP_BLOCK_USED;
}
/
/
标记为USED && 有空闲块
if
(header
-
>size > size
+
HEADER_SIZE
*
2
)
{
/
/
split
heap_header
*
next
=
(heap_header
*
)ADDR_ADD(header,size
+
HEADER_SIZE);
next
-
>
next
=
header
-
>
next
;
next
-
>prev
=
header;
next
-
>
type
=
HEAP_BLOCK_FREE;
next
-
>size
=
header
-
>size
-
HEADER_SIZE
-
size;
if
(header
-
>
next
!
=
NULL)
{
header
-
>
next
-
>prev
=
next
;
}
header
-
>
next
=
next
;
header
-
>
type
=
HEAP_BLOCK_USED;
header
-
>size
=
size
+
HEADER_SIZE;
return
ADDR_ADD(header,HEADER_SIZE);
}
header
=
header
-
>
next
;
}
return
(void
*
)
-
1
;
}
/
/
brk成功返回
0
,失败返回
-
1
,所以不能用brk
/
/
linux brk system call
/
/
static
int
brk(void
*
end_data_segment){
/
/
int
ret
=
0
;
/
/
/
/
brk system call number:
45
/
/
/
/
int
/
usr
/
include
/
asm
-
i386
/
/
unistd.h
/
/
/
/
/
/
asm(
/
/
"movl $45,%%eax \n"
/
/
"movl %1,%%ebx \n"
/
/
"int $0x80 \n"
/
/
"movl %%eax,%0 \n"
/
/
:
"=r"
(ret):
"m"
(end_data_segment));
/
/
}
static void
*
mmap2(void
*
addr, unsigned
len
,
int
prot,
int
flags,
int
fd,
int
offset){
void
*
ret
=
0
;
asm(
"pushl %%ebp \n"
"movl $192,%%eax \n"
"movl %1,%%ebx \n"
"movl %2,%%ecx \n"
"movl %3,%%edx \n"
"movl %4,%%esi \n"
"movl %5,%%edi \n"
"movl %6,%%ebp \n"
"int $0x80 \n"
"movl %%eax,%0 \n"
"popl %%ebp \n"
:
"=r"
(ret)
:
"m"
(addr),
"m"
(
len
),
"m"
(prot),
"m"
(flags),
"m"
(fd),
"m"
(offset)
:
"%ebx"
,
"%ecx"
,
"%edx"
,
"%esi"
,
"%edi"
);
return
ret;
}
int
mini_crt_heap_init(){
void
*
base
=
NULL;
heap_header
*
header
=
NULL;
/
/
32MB
heap size
unsigned heap_size
=
1024
*
1024
*
32
;
base
=
VirtualAlloc(
0
,heap_size,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);
if
(base
=
=
NULL){
return
-
1
;
}
base
=
mmap2(
0
,heap_size,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
if
(base
=
=
(void
*
)
-
1
){
return
-
1
;
}
header
=
(heap_header
*
)base;
header
-
>size
=
heap_size;
header
-
>
type
=
HEAP_BLOCK_FREE;
header
-
>
next
=
NULL;
header
-
>prev
=
NULL;
list_head
=
header;
return
0
;
}
/
/
stdio.c
int
mini_crt_io_init()
{
return
0
;
}
FILE
*
fopen(const char
*
filename, const char
*
mode)
{
HANDLE hFile
=
0
;
int
access
=
0
;
int
creation
=
0
;
if
(strcmp(mode,
"w"
)
=
=
0
)
{
access |
=
GENERIC_WRITE;
creation |
=
CREATE_ALWAYS;
}
if
(strcmp(mode,
"w+"
)
=
=
0
)
{
access |
=
GENERIC_WRITE | GENERIC_READ;
creation |
=
CREATE_ALWAYS;
}
if
(strcmp(mode,
"r"
)
=
=
0
)
{
access |
=
GENERIC_READ;
creation |
=
OPEN_EXISTING;
}
if
(strcmp(mode,
"r+"
)
=
=
0
)
{
access |
=
GENERIC_WRITE | GENERIC_READ;
creation |
=
OPEN_EXISTING;
}
hFile
=
CreateFileA(filename, access,
0
,
0
, creation,
0
,
0
);
if
(hFile
=
=
INVALID_HANDLE_VALUE)
{
return
(
FILE
*
)
-
1
;
}
return
(
FILE
*
)hFile;
}
/
/
返回读取的item
int
fread(void
*
buffer
,
int
size,
int
count,
FILE
*
stream)
{
int
read
=
0
;
if
(!ReadFile((HANDLE)stream,
buffer
, size
*
count, &read,
0
))
{
return
-
1
;
}
if
(size !
=
0
)
{
return
read
/
size;
}
else
{
return
0
;
}
}
/
/
返回写入的item
int
fwrite(const void
*
buffer
,
int
size,
int
count,
FILE
*
stream)
{
int
written
=
0
;
if
(!WriteFile((HANDLE)stream,
buffer
, size
*
count, &written,
0
))
{
return
-
1
;
}
if
(size !
=
0
)
{
return
written
/
size;
}
else
{
return
0
;
}
}
int
fclose(
FILE
*
fp)
{
return
CloseHandle((HANDLE)fp);
}
/
/
返回新指针
int
fseek(
FILE
*
fp,
int
offset,
int
set
)
{
return
SetFilePointer((HANDLE)fp, offset,
0
,
set
);
}
static
int
open
(const char
*
pathname,
int
flags,
int
mode){
int
fd
=
0
;
asm(
"movl $5,%%eax \n"
"int $0x80 \n"
:
"=a"
(fd)
:
"b"
(pathname),
"c"
(flags),
"d"
(mode));
return
fd;
}
static
int
read(
int
fd,void
*
buffer
,unsigned size){
int
ret
=
0
;
asm(
"movl $3,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd),
"c"
(
buffer
),
"d"
(size));
return
ret;
}
static
int
write(
int
fd,const void
*
buffer
,unsigned size){
int
ret
=
0
;
asm(
"movl $4,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd),
"c"
(
buffer
),
"d"
(size));
return
ret;
}
static
int
close(
int
fd){
int
ret
=
0
;
asm(
"movl $6,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd));
return
ret;
}
static
int
seek(
int
fd,
int
offset,
int
mode){
int
ret
=
0
;
asm(
"movl $19,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd),
"c"
(offset),
"d"
(mode));
return
ret;
}
FILE
*
fopen(const char
*
filename,const char
*
mode){
int
fd
=
-
1
;
int
flags
=
0
;
int
access
=
00700
;
/
/
创建文件的权限
/
/
来自于
/
usr
/
include
/
asm
-
generic
/
fcntl.h
/
/
注意:以
0
开头的数字是八进制的
if
(strcmp(mode,
"w"
)
=
=
0
)
{
flags |
=
O_WRONLY | O_CREAT | O_TRUNC;
}
if
(strcmp(mode,
"w+"
)
=
=
0
)
{
flags |
=
O_RDWR | O_CREAT | O_TRUNC;
}
if
(strcmp(mode,
"r"
)
=
=
0
)
{
flags |
=
O_RDONLY;
}
if
(strcmp(mode,
"r+"
)
=
=
0
)
{
flags |
=
O_RDWR;
}
fd
=
open
(filename,flags,access);
return
(
FILE
*
)fd;
}
int
fread(void
*
buffer
,
int
size,
int
count,
FILE
*
stream){
int
ret
=
0
;
ret
=
read((
int
)stream,
buffer
,size
*
count);
if
(ret
=
=
-
1
)
{
return
-
1
;
}
else
{
if
(size !
=
0
)
{
return
ret
/
size;
}
else
{
return
0
;
}
}
}
int
fwrite(const void
*
buffer
,
int
size,
int
count,
FILE
*
stream){
int
ret
=
0
;
ret
=
write((
int
)stream,
buffer
,size
*
count);
if
(ret
=
=
-
1
)
{
return
-
1
;
}
else
{
if
(size !
=
0
)
{
return
ret
/
size;
}
else
{
return
0
;
}
}
}
int
fclose(
FILE
*
fp){
return
close((
int
)fp);
}
int
fseek(
FILE
*
fp,
int
offset,
int
set
){
return
seek((
int
)fp,offset,
set
);
}
/
/
stdio.c
int
mini_crt_io_init()
{
return
0
;
}
FILE
*
fopen(const char
*
filename, const char
*
mode)
{
HANDLE hFile
=
0
;
int
access
=
0
;
int
creation
=
0
;
if
(strcmp(mode,
"w"
)
=
=
0
)
{
access |
=
GENERIC_WRITE;
creation |
=
CREATE_ALWAYS;
}
if
(strcmp(mode,
"w+"
)
=
=
0
)
{
access |
=
GENERIC_WRITE | GENERIC_READ;
creation |
=
CREATE_ALWAYS;
}
if
(strcmp(mode,
"r"
)
=
=
0
)
{
access |
=
GENERIC_READ;
creation |
=
OPEN_EXISTING;
}
if
(strcmp(mode,
"r+"
)
=
=
0
)
{
access |
=
GENERIC_WRITE | GENERIC_READ;
creation |
=
OPEN_EXISTING;
}
hFile
=
CreateFileA(filename, access,
0
,
0
, creation,
0
,
0
);
if
(hFile
=
=
INVALID_HANDLE_VALUE)
{
return
(
FILE
*
)
-
1
;
}
return
(
FILE
*
)hFile;
}
/
/
返回读取的item
int
fread(void
*
buffer
,
int
size,
int
count,
FILE
*
stream)
{
int
read
=
0
;
if
(!ReadFile((HANDLE)stream,
buffer
, size
*
count, &read,
0
))
{
return
-
1
;
}
if
(size !
=
0
)
{
return
read
/
size;
}
else
{
return
0
;
}
}
/
/
返回写入的item
int
fwrite(const void
*
buffer
,
int
size,
int
count,
FILE
*
stream)
{
int
written
=
0
;
if
(!WriteFile((HANDLE)stream,
buffer
, size
*
count, &written,
0
))
{
return
-
1
;
}
if
(size !
=
0
)
{
return
written
/
size;
}
else
{
return
0
;
}
}
int
fclose(
FILE
*
fp)
{
return
CloseHandle((HANDLE)fp);
}
/
/
返回新指针
int
fseek(
FILE
*
fp,
int
offset,
int
set
)
{
return
SetFilePointer((HANDLE)fp, offset,
0
,
set
);
}
static
int
open
(const char
*
pathname,
int
flags,
int
mode){
int
fd
=
0
;
asm(
"movl $5,%%eax \n"
"int $0x80 \n"
:
"=a"
(fd)
:
"b"
(pathname),
"c"
(flags),
"d"
(mode));
return
fd;
}
static
int
read(
int
fd,void
*
buffer
,unsigned size){
int
ret
=
0
;
asm(
"movl $3,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd),
"c"
(
buffer
),
"d"
(size));
return
ret;
}
static
int
write(
int
fd,const void
*
buffer
,unsigned size){
int
ret
=
0
;
asm(
"movl $4,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd),
"c"
(
buffer
),
"d"
(size));
return
ret;
}
static
int
close(
int
fd){
int
ret
=
0
;
asm(
"movl $6,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd));
return
ret;
}
static
int
seek(
int
fd,
int
offset,
int
mode){
int
ret
=
0
;
asm(
"movl $19,%%eax \n"
"int $0x80 \n"
:
"=a"
(ret)
:
"b"
(fd),
"c"
(offset),
"d"
(mode));
return
ret;
}
FILE
*
fopen(const char
*
filename,const char
*
mode){
int
fd
=
-
1
;
int
flags
=
0
;
int
access
=
00700
;
/
/
创建文件的权限
/
/
来自于
/
usr
/
include
/
asm
-
generic
/
fcntl.h
/
/
注意:以
0
开头的数字是八进制的
if
(strcmp(mode,
"w"
)
=
=
0
)
{
flags |
=
O_WRONLY | O_CREAT | O_TRUNC;
}
if
(strcmp(mode,
"w+"
)
=
=
0
)
{
flags |
=
O_RDWR | O_CREAT | O_TRUNC;
}
if
(strcmp(mode,
"r"
)
=
=
0
)
{
flags |
=
O_RDONLY;
}
if
(strcmp(mode,
"r+"
)
=
=
0
)
{
flags |
=
O_RDWR;
}
fd
=
open
(filename,flags,access);
return
(
FILE
*
)fd;
}
int
fread(void
*
buffer
,
int
size,
int
count,
FILE
*
stream){
int
ret
=
0
;
ret
=
read((
int
)stream,
buffer
,size
*
count);
if
(ret
=
=
-
1
)
{
return
-
1
;
}
else
{
if
(size !
=
0
)
{
return
ret
/
size;
}
else
{
return
0
;
}
}
}
int
fwrite(const void
*
buffer
,
int
size,
int
count,
FILE
*
stream){
int
ret
=
0
;
ret
=
write((
int
)stream,
buffer
,size
*
count);
if
(ret
=
=
-
1
)
{
return
-
1
;
}
else
{
if
(size !
=
0
)
{
return
ret
/
size;
}
else
{
return
0
;
}
}
}
int
fclose(
FILE
*
fp){
return
close((
int
)fp);
}
int
fseek(
FILE
*
fp,
int
offset,
int
set
){
return
seek((
int
)fp,offset,
set
);
}
typedef
int
FILE
;
/
/
string.c
/
/
int
n:只支持十进制
/
/
不支持radix不是十进制且n<
0
的情况
char
*
itoa(
int
n,char
*
str
,
int
radix){
char digit[]
=
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
;
char
*
p
=
str
;
char
*
head
=
str
;
if
(!p || radix <
2
|| radix >
36
)
{
return
p;
}
/
/
我觉得是可以行的
if
(radix !
=
10
&& n <
0
)
{
return
p;
}
if
(n
=
=
0
)
{
*
p
+
+
=
'0'
;
*
p
=
0
;
return
p;
}
if
(radix
=
=
10
&& n <
0
)
{
*
p
+
+
=
'-'
;
n
=
-
n;
}
while
(n)
{
*
p
+
+
=
digit[n
%
radix];
n
/
=
radix;
}
*
p
=
0
;
for
(
-
-
p; head < p;
+
+
head,
-
-
p)
{
char temp
=
*
head;
*
head
=
*
p;
*
p
=
temp;
}
return
str
;
}
/
/
有缺陷:没有检查空指针
int
strcmp(const char
*
src,const char
*
dst){
int
ret
=
0
;
unsigned char
*
p1
=
(unsigned char
*
)src;
unsigned char
*
p2
=
(unsigned char
*
)dst;
/
/
这里有缺陷,应该手动触发异常
while
(!(ret
=
*
p1
-
*
p2) &&
*
p2)
{
+
+
p1,
+
+
p2;
}
return
ret;
}
char
*
strcpy(char
*
dest,const char
*
src){
char
*
ret
=
dest;
if
(!dest || !src)
{
return
dest;
}
while
(
*
src)
{
*
dest
+
+
=
*
src
+
+
;
}
*
dest
=
'\0'
;
return
ret;
}
unsigned strlen(const char
*
str
){
int
cnt
=
0
;
if
(!
str
)
{
return
0
;
}
for
(;
*
str
!
=
'\0'
;
+
+
str
)
{
+
+
cnt;
}
return
cnt;
}
/
/
string.c
/
/
int
n:只支持十进制
/
/
不支持radix不是十进制且n<
0
的情况
char
*
itoa(
int
n,char
*
str
,
int
radix){
char digit[]
=
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
;
char
*
p
=
str
;
char
*
head
=
str
;
if
(!p || radix <
2
|| radix >
36
)
{
return
p;
}
/
/
我觉得是可以行的
if
(radix !
=
10
&& n <
0
)
{
return
p;
}
if
(n
=
=
0
)
{
*
p
+
+
=
'0'
;
*
p
=
0
;
return
p;
}
if
(radix
=
=
10
&& n <
0
)
{
*
p
+
+
=
'-'
;
n
=
-
n;
}
while
(n)
{
*
p
+
+
=
digit[n
%
radix];
n
/
=
radix;
}
*
p
=
0
;
for
(
-
-
p; head < p;
+
+
head,
-
-
p)
{
char temp
=
*
head;
*
head
=
*
p;
*
p
=
temp;
}
return
str
;
}
/
/
有缺陷:没有检查空指针
int
strcmp(const char
*
src,const char
*
dst){
int
ret
=
0
;
unsigned char
*
p1
=
(unsigned char
*
)src;
unsigned char
*
p2
=
(unsigned char
*
)dst;
/
/
这里有缺陷,应该手动触发异常
while
(!(ret
=
*
p1
-
*
p2) &&
*
p2)
{
+
+
p1,
+
+
p2;
}
return
ret;
}
char
*
strcpy(char
*
dest,const char
*
src){
char
*
ret
=
dest;
if
(!dest || !src)
{
return
dest;
}
while
(
*
src)
{
*
dest
+
+
=
*
src
+
+
;
}
*
dest
=
'\0'
;
return
ret;
}
unsigned strlen(const char
*
str
){
int
cnt
=
0
;
if
(!
str
)
{
return
0
;
}
for
(;
*
str
!
=
'\0'
;
+
+
str
)
{
+
+
cnt;
}
return
cnt;
}
/
/
printf.c
/
/
返回字符
int
fputc(
int
c,
FILE
*
stream){
if
(fwrite(&c,
1
,
1
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
return
c;
}
}
/
/
返回字符串长度
int
fputs(const char
*
str
,
FILE
*
stream){
int
len
=
strlen(
str
);
if
(fwrite(
str
,
len
,
1
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
return
len
;
}
}
int
vfprintf(
FILE
*
stream,const char
*
format
,va_list arglist){
int
translating
=
0
;
int
ret
=
0
;
const char
*
p
=
0
;
for
(p
=
format
;
*
p !
=
'\0'
;
+
+
p)
{
switch (
*
p)
{
case
'%'
:
if
(!translating)
{
translating
=
1
;
}
else
{
if
(fputc(
'%'
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
translating
=
0
;
}
}
break
;
case
'd'
:
if
(translating)
/
/
%
d
{
char buf[
16
];
translating
=
0
;
itoa(va_arg(arglist,
int
),buf,
10
);
if
(fputs(buf,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
ret
+
=
strlen(buf);
}
}
else
{
if
(fputc(
'd'
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
}
}
break
;
case
's'
:
if
(translating)
{
const char
*
str
=
va_arg(arglist,const char
*
);
translating
=
0
;
if
(fputs(
str
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
ret
+
=
strlen(
str
);
}
}
else
{
if
(fputc(
's'
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
}
}
break
;
default:
if
(translating)
{
translating
=
0
;
}
if
(fputc(
*
p,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
}
break
;
}
}
return
ret;
}
int
printf(const char
*
format
,...){
va_list(arglist);
va_start(arglist,
format
);
return
vfprintf(stdout,
format
,arglist);
}
int
fprintf(
FILE
*
stream,const char
*
format
,...){
va_list(arglist);
va_start(arglist,
format
);
return
vfprintf(stream,
format
,arglist);
}
/
/
printf.c
/
/
返回字符
int
fputc(
int
c,
FILE
*
stream){
if
(fwrite(&c,
1
,
1
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
return
c;
}
}
/
/
返回字符串长度
int
fputs(const char
*
str
,
FILE
*
stream){
int
len
=
strlen(
str
);
if
(fwrite(
str
,
len
,
1
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
return
len
;
}
}
int
vfprintf(
FILE
*
stream,const char
*
format
,va_list arglist){
int
translating
=
0
;
int
ret
=
0
;
const char
*
p
=
0
;
for
(p
=
format
;
*
p !
=
'\0'
;
+
+
p)
{
switch (
*
p)
{
case
'%'
:
if
(!translating)
{
translating
=
1
;
}
else
{
if
(fputc(
'%'
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
translating
=
0
;
}
}
break
;
case
'd'
:
if
(translating)
/
/
%
d
{
char buf[
16
];
translating
=
0
;
itoa(va_arg(arglist,
int
),buf,
10
);
if
(fputs(buf,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
ret
+
=
strlen(buf);
}
}
else
{
if
(fputc(
'd'
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
}
}
break
;
case
's'
:
if
(translating)
{
const char
*
str
=
va_arg(arglist,const char
*
);
translating
=
0
;
if
(fputs(
str
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
ret
+
=
strlen(
str
);
}
}
else
{
if
(fputc(
's'
,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
}
}
break
;
default:
if
(translating)
{
translating
=
0
;
}
if
(fputc(
*
p,stream)
=
=
-
1
)
{
return
EOF;
}
else
{
+
+
ret;
}
break
;
}
}
return
ret;
}
int
printf(const char
*
format
,...){
va_list(arglist);
va_start(arglist,
format
);
return
vfprintf(stdout,
format
,arglist);
}
int
fprintf(
FILE
*
stream,const char
*
format
,...){
va_list(arglist);
va_start(arglist,
format
);
return
vfprintf(stream,
format
,arglist);
}
/
/
minicrt.h
extern
"C"
{
/
/
malloc
typedef unsigned
int
size_t;
void free(void
*
ptr);
void
*
malloc(unsigned size);
/
/
static
int
brk(void
*
end_data_segment);
static void
*
mmap2(void
*
addr, unsigned
len
,
int
prot,
int
flags,
int
fd,
int
offset);
int
mini_crt_heap_init();
/
/
字符串
char
*
itoa(
int
n,char
*
str
,
int
radix);
int
strcmp(const char
*
src,const char
*
dst);
char
*
strcpy(char
*
dest,const char
*
src);
unsigned strlen(const char
*
str
);
/
/
文件与IO
typedef
int
FILE
;
int
mini_crt_io_init();
FILE
*
fopen(const char
*
filename,const char
*
mode);
int
fread(void
*
buffer
,
int
size,
int
count,
FILE
*
stream);
int
fwrite(const void
*
buffer
,
int
size,
int
count,
FILE
*
stream);
int
fclose(
FILE
*
fp);
int
fseek(
FILE
*
fp,
int
offset,
int
set
);
/
/
printf
int
fputc(
int
c,
FILE
*
stream);
int
fputs(const char
*
str
,
FILE
*
stream);
int
printf(const char
*
format
,...);
int
fprintf(
FILE
*
stream,const char
*
format
,...);
/
/
internal
void do_global_ctors();
void mini_crt_call_exit_routine();
/
/
atexit
typedef void (
*
atexit_func_t) (void);
int
atexit(atexit_func_t);
/
/
下面两个是C
+
+
库用到的
typedef void(
*
cxa_func_t)(void
*
);
int
__cxa_atexit(cxa_func_t func,void
*
arg,void
*
);
}
/
/
minicrt.h
extern
"C"
{
/
/
malloc
typedef unsigned
int
size_t;
void free(void
*
ptr);
void
*
malloc(unsigned size);
/
/
static
int
brk(void
*
end_data_segment);
static void
*
mmap2(void
*
addr, unsigned
len
,
int
prot,
int
flags,
int
fd,
int
offset);
int
mini_crt_heap_init();
/
/
字符串
char
*
itoa(
int
n,char
*
str
,
int
radix);
int
strcmp(const char
*
src,const char
*
dst);
char
*
strcpy(char
*
dest,const char
*
src);
unsigned strlen(const char
*
str
);
/
/
文件与IO
typedef
int
FILE
;
int
mini_crt_io_init();
FILE
*
fopen(const char
*
filename,const char
*
mode);
int
fread(void
*
buffer
,
int
size,
int
count,
FILE
*
stream);
int
fwrite(const void
*
buffer
,
int
size,
int
count,
FILE
*
stream);
int
fclose(
FILE
*
fp);
int
fseek(
FILE
*
fp,
int
offset,
int
set
);
/
/
printf
int
fputc(
int
c,
FILE
*
stream);
int
fputs(const char
*
str
,
FILE
*
stream);
int
printf(const char
*
format
,...);
int
fprintf(
FILE
*
stream,const char
*
format
,...);
/
/
internal
void do_global_ctors();
void mini_crt_call_exit_routine();
/
/
atexit
typedef void (
*
atexit_func_t) (void);
int
atexit(atexit_func_t);
/
/
下面两个是C
+
+
库用到的
typedef void(
*
cxa_func_t)(void
*
);
int
__cxa_atexit(cxa_func_t func,void
*
arg,void
*
);
}
gcc
-
c
-
fno
-
builtin
-
nostdlib
-
fno
-
stack
-
protector
-
m32 entry.c malloc.c stdio.c string.c printf.c
ar
-
rs minicrt.a malloc.o printf.o stdio.o string.o
gcc
-
c
-
fno
-
builtin
-
nostdlib
-
fno
-
stack
-
protector
-
m32 entry.c malloc.c stdio.c string.c printf.c
ar
-
rs minicrt.a malloc.o printf.o stdio.o string.o
cl
/
c
/
DWIN32
/
utf
-
8
/
GS
-
entry.c malloc.c printf.c stdio.c string.c
lib entry.obj malloc.obj printf.obj stdio.obj string.obj
/
OUT:minicrt.lib
cl
/
c
/
DWIN32
/
utf
-
8
/
GS
-
entry.c malloc.c printf.c stdio.c string.c
lib entry.obj malloc.obj printf.obj stdio.obj string.obj
/
OUT:minicrt.lib
/
/
test.c
int
main(
int
argc,char
*
argv[]){
int
i;
FILE
*
fp;
/
/
1.
拷贝参数到堆
char
*
*
v
=
malloc(argc
*
sizeof(char
*
));
for
( i
=
0
; i < argc; i
+
+
)
{
v[i]
=
malloc(strlen(argv[i])
+
1
);
strcpy(v[i],argv[i]);
}
/
/
2.
写入文件
fp
=
fopen(
"test.txt"
,
"w"
);
for
( i
=
0
; i < argc; i
+
+
)
{
int
len
=
strlen(v[i]);
fwrite(&
len
,sizeof(
int
),
1
,fp);
fwrite(v[i],strlen(v[i])
+
1
,
1
,fp);
}
fclose(fp);
/
/
3.
读取文件
fp
=
fopen(
"test.txt"
,
"r"
);
for
( i
=
0
; i < argc; i
+
+
)
{
int
len
;
char
*
buf;
fread(&
len
,sizeof(
int
),
1
,fp);
buf
=
malloc(
len
+
1
);
fread(buf,
len
+
1
,
1
,fp);
printf(
"%d %s\n"
,
len
,buf);
free(buf);
free(v[i]);
}
fclose(fp);
return
0
;
}
/
/
test.c
int
main(
int
argc,char
*
argv[]){
int
i;
FILE
*
fp;
/
/
1.
拷贝参数到堆
char
*
*
v
=
malloc(argc
*
sizeof(char
*
));
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: