前面的文章展示了如何通过自制的运行库实现堆初始化和IO初始化,以及标准输入输出功能,这些都是标准的C功能实现。而对于C++而言,则拥有更多的复杂特性,如new/delete、STL、异常处理、流等,这些特性显然是语言层的,故而需要GCC库提供相应的实现。对于C++而言,其毕竟是在C的基础上衍生而来的,故而C++运行库显然只需要实现这些C++特有的特性,而其他和C共享的功能如基本文件操作、堆管理等都可以直接依赖C运行库(如Windows下C运行库为msvcr90.dll,C++运行库为msvcp90.dll)。
正式这种依赖关系,MiniCRT++需要上一篇MiniCRT所有的基础部件(本篇提供了修改后的entry.c(启动全局对象的构造和析构),需要替换MiniCRT中的entry.c,其余部件均沿用MiniCRT)。
程序员的自我修养一书中出于简化显式的目的,只选择实现部分的C++特性,如:
- string类的实现;
- stream类的实现,包括操作符;
- new/delete
new_delete.cpp:new/delete特性实现
//new_delete.cpp
extern "C" void* malloc(unsigned int);
extern "C" void* free(void*); //申明该函数是外部的,并且该函数的编译方式是类C的,从而正好和前面malloc.c对应起来,从而可以实现在.cpp文件中调用C语言实现的函数
//new和delete只负责堆空间的申请和释放,不负责构造和析构
//new delete + - / * %都被认为是操作符,都有对应的操作符函数
//全局操作符重载(比如可以用自己定义的+ - *等符号取代全局
//指定对象申请地址Replacement new也可以将操作符重载的影响范围限制在更小范围内。
void* operator new(unsigned int size)
{
return malloc(size);
}
void operator delete(void* p)
{
free(p);
}
void* operator new[](unsigned int size)
{
return malloc(size);
}
void operator delete[](void* p)
{
free(p);
}
ctors.cpp:C++全局构造与析构
//ctors.cpp
#include "minicrt.h"
typedef void (*init_func)(void);
#ifdef WIN32
#pragma section(".CRT$XCA", long, read) //在该文件中声明一个.crt$xca段
#pragma section(".CRT$XCZ", long, read)
__declspec(allocate(".CRT$XCA")) init_func ctors_begin[] = {0};
__declspec(allocate(".CRT$XCZ")) init_func ctors_end[] = {0};
//extern "C" 中的C表示并非是C语言,而是一种编译和链接规范,而不是语言,C表示的是
//符合C语言的编译和链接规范的任何语言,如Fortran、 assembler等。
extern "C" void do_global_ctors () //表示函数do_global_ctors按照类C规范进行编译和修饰,如函数修饰规则
{
init_func* p = ctors_begin;
while (p < ctors_end)
{
if (*p != 0)
(**p) ();
++p;
}
}
#else
void run_hooks();
extern "C" void do_global_ctors()
{
run_hooks();
}
#endif
crtbegin.cpp:Linux下ELF文件结构下C++全局构造与析构还需crtbegin.o和crtend.o两文件支持
//crtbegin.cpp
#ifndef WIN32
typedef void(*ctor_func) (void);
ctor_func ctors_begin[1] __attribute__ ((section(".ctors"))) =
{
(ctor_func) -1 //作为所有ctors段的开始,crtbegin.o的.ctor段里面存储的是一个4字节的-1(0xFFFF FFFF)
//由链接器负责将这个数字改成全局构造函数的数量
};
void run_hooks()
{
const ctor_func* list = ctors_begin;
while((int) *++list != -1)
(**list)();
}
#endif
crtend.cpp:Linux下ELF文件结构下C++全局构造与析构还需crtbegin.o和crtend.o两文件支持
//crtend.cpp
#ifndef WIN32
typedef void (*ctor_func)(void);
ctor_func crt_end[1] __attribute__((section(".ctors"))) =
{
(ctor_func) -1 //crtend.o的.ctor段将将该文件的初始化函数指针置为-1
};
#endif
atexit.c:注册析构函数
//atexit.cpp
#include "minicrt.h"
/*
typedef void (*cxa_func_t)(void* );
typedef void (*atexit_func_t)(void );
int __cxa_atexit(cxa_func_t func, void* arg, void*);
int atexit(atexit_func_t func);
*/
//atexit和exit()函数实际桑并不属于C++运行库的一部分,它们是C语言运行库的一部分
//这两个函数应该是属于C语言运行库的一部分。本来atexit函数在C运行库中属于作用较弱
//的CRT函数,但是在C++中全局对象的析构全部需要atexit或cxa_atexit来实现
//但是atexit和cxa_atexit两个函数的实现略有不同,从上面的函数定义就可以看出来。
//可以看到atexit注册回调函数,只需要提供函数指针即可;
//而__cxa_atexit则需要同时提供函数指针和函数调用参数
typedef void (*atexit_func_t)(void );
typedef struct _func_node
{
atexit_func_t func;
void* arg;
int is_cxa;
struct _func_node* next;
}func_node;
static func_node* atexit_list = 0;
int register_atexit(atexit_func_t func, void* arg, int is_cxa)
{
func_node* node;
if (!func) return -1;
node = (func_node*)malloc( sizeof(func_node) );
if (node == 0) return -1;
node->func = func;
node->arg = arg;
node->is_cxa = is_cxa;
node->next = atexit_list;
atexit_list = node;
return 0;
}
#ifndef WIN32
typedef void(*cxa_func_t) (void*);
int __cxa_atexit(cxa_func_t func, void* arg, void* unused)
{
return register_atexit((atexit_func_t)func, arg, 1)
}
#endif
int atexit(atexit_func_t func)
{
return register_atexit(func, 0, 0);
}
//析构函数的顺序是先构造的后析构
void mini_crt_call_exit_routine()
{
func_node* p = atexit_list;
for(; p!=0; p=p->next)
{
#ifdef WIN32
p->func();
#else
if (p->is_cxa)
( (cxa_func_t)p->func )(p->arg);
else
p->func();
#endif
free(p);
}
atexit_list = 0;
}
entry.c:对于C++需要修改提供新的entry.c
//entry.c
#include "minicrt.h"
#ifdef WIN32
#include <Windows.h>
#endif
extern int main(int argc, char* argv[]);
void exit(int);
static void crt_fatal_error(const char* msg)
{
//printf("fatal error:%s", msg);
exit(1);
}
void mini_crt_entry(void)
{
int ret;
//printf("ENTRY mini_crt_entry\n");
#ifdef WIN32
int flag = 0;
int argc = 0;
char* argv[16]; //最多16个参数
char* cl = GetCommandLineA();
int plusFlag = 0;
argv[0] = cl;
argc++;
while(*cl) {
if(*cl == '\"')
{
if(flag == 0) flag = 1;
else flag=0;
}
else if(*cl == ' ' && flag == 0)
{
*cl = '\0';
while(*(cl+1) && *(cl+1) == ' ')
{
cl++;
plusFlag = 1;
}
if(plusFlag)
{
argv[argc] = cl;
plusFlag = 0;
}
else
argv[argc] = cl + 1;
argc++;
}
cl++;
}
#else
int argc;
char** argv;
char* ebp_reg = 0;
asm(" movw %%ebp, %0 \n\t":"=r"(ebp_reg));
argc = *(int*)(ebp_reg + 4);
argv = (char**)(ebp_reg + 8);
#endif
if (! mini_crt_heap_init())
crt_fatal_error("heap initialize failed");
if (! mini_crt_io_init())
crt_fatal_error("IO initialize failed");
do_global_ctors();//启动全局对象的构造函数
ret = main(argc, argv);
exit(ret);
}
void exit(int exitCode)
{
mini_crt_call_exit_routine(); //启动退出时,调用注册的全局对象析构函数
#ifdef WIN32
ExitProcess(exitCode);
#else
asm("movl %0, %%ebx \n\t"
"movl $1, %%eax \n\t"
"int $0x80 \n\t"
"hlt \n\t"::"m"(exitCode));
#endif
}
string:注意此处string并没有.c或.cpp后缀,只需要提供这样,然后将该文件加载到include的搜索路径下,通过#include 加载。
//string
namespace std{
class string
{
unsigned len;
char* pbuf;
public:
explicit string(const char* str);
string(const string&);
~string();
string& operator = (const string&);
string& operator = (const char* s);
const char& operator[] (unsigned idx) const;
char& operator[] (unsigned idx);
const char* c_str() const;
unsigned length() const;
unsigned size() const;
};
string::string(const char* str) : len(0), pbuf(0)
{
*this = str;
}
string::~string()
{
if (pbuf != 0)
{
delete[] pbuf;
pbuf = 0;
}
}
string& string::operator=(const string& s)
{
if (&s == this)
return *this;
this->~string();
len = s.len;
pbuf = strcpy(new char[len+1], s.pbuf);
return *this;
}
string& string::operator=(const char* s)
{
this->~string();
len = strlen(s);
pbuf = strcpy(new char[len+1], s);
return *this;
}
const char& string::operator[] (unsigned idx) const
{
return pbuf[idx];
}
char& string::operator[] (unsigned idx)
{
return pbuf[idx];
}
const char* string::c_str() const
{
return pbuf;
}
unsigned string::length() const
{
return len;
}
unsigned string::size() const
{
return len;
}
ofstream& operator<<(ofstream& o, const string& s)
{
return o << s.c_str();
}
}
iostream:注意此处iostream同样并没有.c或.cpp后缀,然后将该文件加载到include的搜索路径下,通过#include 加载
//iostream
//在真正的STL实现中,string和stream的实现十分复杂,不仅有强大的模板定
//制功能、缓冲,庞大的继承体系及一系列辅助类。本代码是演示目的,故而
//简化了诸多特征,如没有内置缓冲功能(no stream_buffer support),
//流对象仅实现了ofstream,没有继承体系,既没有ios_base\stream\ostream
#include "minicrt.h"
namespace std{
class ofstream
{
protected:
FILE* fp;
ofstream(const ofstream&);
public:
enum openmode{in =1, out=2, binary = 4, trunc = 8};
ofstream();
explicit ofstream(const char* filename, ofstream::openmode md = ofstream::out);
~ofstream();
ofstream& operator<<(char c);
ofstream& operator<<(int n);
ofstream& operator<<(const char* str);
ofstream& operator<<(ofstream& (*)(ofstream&));
void open(const char* filename, ofstream::openmode md = ofstream::out);
void close();
ofstream& write(const char* buf, unsigned size);
};
inline ofstream& endl(ofstream& o)
{
return o<<'\n';
}
class stdout_stream : public ofstream
{
public:
stdout_stream();
};
extern stdout_stream cout; //实现标准输出的封装
}
iostream.cpp:上面iostream的实现
#include "minicrt.h"
#include <own_iostream>
#ifdef WIN32
#include <Windows.h>
#endif
namespace std
{
stdout_stream::stdout_stream() : ofstream()
{
fp = stdout;
// printf("stdout created\n");
}
stdout_stream cout;
ofstream::ofstream() : fp(0)
{
}
ofstream::ofstream(const char* filename, ofstream::openmode md) : fp(0)
{
open(filename, md);
}
ofstream::~ofstream()
{
close();
}
ofstream& ofstream::operator<<(char c)
{
fputc(c, fp);
return *this;
}
ofstream& ofstream::operator<<(int n)
{
fprintf(fp, "%d", n);
return *this;
}
ofstream& ofstream::operator<<(const char* str)
{
fprintf(fp, "%s", str);
return *this;
}
ofstream& ofstream::operator<<( ofstream& (*manip)(ofstream&) )
{
return manip(*this);
}
void ofstream::open(const char* filename, ofstream::openmode md)
{
char mode[4];
close();
switch (md)
{
case out | trunc:
strcpy(mode, "w");
break;
case out | in | trunc:
strcpy(mode, "w+");
case out | trunc | binary:
strcpy(mode, "wb");
break;
case out | in | trunc | binary:
strcpy(mode, "wb+");
}
fp = fopen(filename, mode);
}
void ofstream::close()
{
if (fp)
{
fclose(fp);
// printf("stdout destory\n");
fp = 0;
}
}
ofstream& ofstream::write(const char* buf, unsigned size)
{
fwrite(buf, 1, size, fp);
return *this;
}
}
到这里基本上MiniCRT++在事先目标中列举的C++特性都得到了实现。下面给出相应的检验函数。
test.cpp:验证程序
#include "own_iostream"
#include "own_string"
//主要这里的#include采用的是“xxx”格式,而非#include <xxx>格式,原因可以参考我的这篇文章http://blog.csdn.net/roger_ranger/article/details/78535551
using namespace std;
int main(int argc, char* argv[])
{
string* msg = new string("Hello World!");
cout << *msg << endl;
cout<<"hello work"<<endl;
delete msg;
return 0;
}
同样的在最后给出MiniCRT++在Windows和Linux下使用方法
//Windows下
>cl /c /DWIN32 /GS- entry.c malloc.c printf.c stdio.c string.c atexit.c
>cl /c /DWIN32 /GS- /GR- crtbegin.cpp crtend.cpp ctors.cpp new_delete.cpp iostream.cpp
>lib entry.obj malloc.obj printf.obj stdio.obj string.obj ctors.obj new_delete.obj atexit.obj iostream.obj /OUT:minicrt.lib
>cl /c /DWIN32 /GR- test.cpp
>link test.obj minicrt.lib kernel32.lib /NODEFAULTLIB /entry:mini_crt_entry
//DWIN32是采用/D宏申明,即相当于#define WIN32,因为WIN32是代码中用来做系统识别的宏
//GS- 关闭堆栈保护功能,MSVC和GCC都会在涉及不定参数时插入堆栈保护功能,如果不关闭这个功能,会导致在最终链接阶段出现"_security_cookie"和"_security_check_cookie"符号未定义错误
//entry:mini_crt_entry 既然MiniCRT是用来取代C语言的CRT运行库的,那么显然入口函数已经不再是MSVC-CRT库默认的mainCRTStartup(http://blog.csdn.net/roger_ranger/article/details/78221331),故而需要通过该指令指定新的入口函数
//GR-关闭RTTI功能,否则编译器会为有虚函数的类产生RTTI相关代码,并在最终链接时看到"const type_info::vftable"符号未定义的错误
//Linux下
$ gcc -c -g -fno-builtin -nostdlib -fno-stack-protector entry.c malloc.c stdio.c string.c printf.c atexit.c -m32
$ g++ -c -g -nostdinc++ -fno-rtti -fno-exceptions -fno-builtin -nostdlib -fno-stack-protector crtbegin.cpp crtend.cpp ctors.cpp new_delete.cpp sysdep.cpp iostream.cpp sysdep.cpp -m32
$ ar -rs minicrt.a malloc.o printf.o stdio.o string.o ctors.o atexit.o iostream.o new_delete.o sysdep.o
$ g++ -c -nostdinc++ -fno-rtti -fno-exceptions -fno-builtin -nostdlib -fno-stack-protector easy_test.cpp -m32 -g
$ g++ -c -nostdinc++ -fno-rtti -fno-exceptions -fno-builtin -nostdlib -fno-stack-protector easy_test.cpp -m32 -g
//加-g是为了后面可以启用gdb做调试
//加-m32是因为我的电脑是64位的,而程序中涉及到对栈上参数的直接操作,采用的是32位的默认格式,即一个参数占4各字节,故而需要给gcc指明本文件需要按照elf32编译
//-fno-builtin 参数关闭GCC对C的优化功能,如GCC会将strlen\strcmp这类函数展开替换成它自己的内部的高效实现方式
//-nostdlib 参数表示不适用任何来自GCC的库文件和启动文件,该参数包括了-nostartfiles这个参数功能
//-fno-stack-protector关闭堆栈保护功能,否则会出现对‘_Unwind_Resume’未定义的引用
test.o:(.eh_frame+0x18b):对‘__gxx_personality_v0’未定义的引用的错误
//-fno-rtti 的作用和cl中的/GR-一样用来关闭RTTI功能
//-fno-exceptions用来关闭异常支持,否则可能GCC会产生异常支持代码,导致最终链接错误
至此我们可以看到运行库作为在操作系统和用户程序之间的中间层的作用,它是极大的方便了顶层程序员构建更强大更高级程序的基础,避免了程序员直接和操作系统底层接触带来的代码复杂行和移植性差等问题,再次验证了“银弹”理论。下面给出运行库和操作系统以及用户程序之间的关系示意图
Fig.1 运行库、用户程序和操作系统接口的层次图