解析在main函数之前调用函数以及对设计的作用详解
前几天为新员工写一个简单的测试框架,可让他们方便的写测试用例并且执行。期间遇到一个问题就是如何让他们增加测试用例而用不影响测试框架的代码?c++的单件模式可以解决这个问题,但是其中一个难点是要在main之前注册单件。c++可以通过构造函数来实现注册,c如何注册?
最后查了下资料,原来可以定义在main之前调用的函数!有了这个特性可以改善c的模块化设计。
特性介绍:
如果想定义在main函数之前调用的函数,可以在函数的声明之后加上一句“__attribute__((constructor))”,如下:
int before()__attribute__((constructor));
如果想定义在main函数之后调用的函数,可以在函数的声明之后加上一句“__attribute__((destructor))”,如下:
int after()__attribute__((destructor));
可以看得出来,应该类似于c++中的构造和析构。
一些细节问题:
写测试代码测试了一下这个程序,发现几点:
1、before在main之前调用,调用之前,各个全局变量已经完成初始化。也就是说,这些函数是在全局变量初始化之后,main函数之前调用的。这一点是非常重要的,否则可能会引起很多的问题。
2、after在main之后调用,但是有一点比较特殊,必须是在main中return的话才执行,否则,需要通过atexit执行某函数。这个特性目前对我没有太大的用处。
3、在main函数之前调用的函数可以声明为static。
4、在main函数之前调用的函数可以调用多个。这里就有一个问题,就是这些函数的调用顺序的问题。这个问题首先是一个设计的问题,也就是,我们应该设计这些函数为顺序无关的函数。另外,调用顺序和编译的顺序相关,我在linux下使用make进行编译,发现最后编译的源文件中的函数会最先调用。
5、可以在库(动态库和静态库)中定义这样的函数。
用对设计的作用:
1、可以优化c++中的单件模式。参考《设计模式》
单件模式有一个最大的特点就是可以在运行过程中连接单件。如果使用条件语句来决定使用哪个单件硬性限定了可能的单件集合。所以,书中引入了一个单件注册表的概念,书中对单件注册表的初始化采用的是如下的做法:
首先定义一个单件类,在单件类的构造函数中调用单件的注册函数注册自身:
代码如下:
MySingleton::MySingleton()
{
...
Singleton::Register("MySingleton", this);
}
这个函数是怎么被调用的那?可以定义一个静态实例:
static MySingleton theSingleton;
这样就会在main函数之前调用MySingleton的构造函数来构造这个静态实例,从而达到像注册表注册的目的。
这个方案有个缺点:它能够成功存在一个前提,就是在theSingleton实例化之前,单件注册表列表必须存在,否则会失败。则其实只是一个可能失败的点,如果MySingleton还应用其他的全局变量,则可能这个时候这些全局变量还没有初始化。
解决这个问题的一个方案就是将单件注册的时间由构造函数移到main函数之前调用的函数中来。
定义函数:
代码如下:
static void before_main()
{
Singleton::Register("MySingleton", &theSingleton);
}
声明:
staitc void before_main()__attribute__((constructor));
before_main会在main函数之前调用,而调用时全局变量已经全部初始化,这样就可以避免上面的问题。
其实单件不单单可以在c++(面向对象)中使用,也可以在c中使用。而且有了c的这个特性后,单件更好用。
2、构造插件开发框架,而不用对框架进行更改。
构造插件开发框架的一个问题是:如何新增一个插件而不用修改主框架代码就可以调用插件代码。一般情况下都会使用插件注册机制。也就是框架对外提供注册接口,插件使用这些接口进行注册。c要实行此功能,一个可行的方案是在插件中定义main之前执行的函数,在此函数中调用插件注册接口完成注册。(注:这里讨论的是插件的静态加载)。
3、一个模块有一些初始化工作要做,使用这种机制可以不更改main或者函数。
抛开插件框架,使用这个特性也可以对c的模块化进行很多优化。比如,可以把各个模块的初始化工作放在main之前进行从而防止对main的频繁修改。
注:本文描述的环境为linux c,c++。