原型:
#include<stdlib.h>
int atexit(void(*func)(void));
其中,atexit的参数是一个函数地址,当调用此函数时无须传递任何参数,该函数也不能返回值,atexit函数称为终止处理程序注册程序,注册完成以后,当函数终止是exit()函数会主动的调用前面注册的各个函数,但是exit函数调用这些函数的顺序于这些函数登记的顺序是相反的,我认为这实质上是参数压栈造成的,参数由于压栈顺序而先入后出。同时如果一个函数被多次登记,那么该函数也将多次的执行。
------------------------------------------------------------------
下面来个sample说明调用atexit的情况:
#include <stdio.h>
#include <stdlib.h>
void my_exit1 (void)
{
printf("first exit handler\n") ;
}
void my_exit2 (void)
{
printf("second exit handler\n") ;
}
int main (int argc ,char *argv[])
{
if (atexit (my_exit1) != 0)
printf ("can't exit my_exit1\n") ;
if (atexit (my_exit2) != 0)
printf ("can't exit my_exit2\n") ;
if (atexit (my_exit2) != 0)
printf ("can't exit my_exit2\n") ;
return 0;
}
#include <stdlib.h>
void my_exit1 (void)
{
printf("first exit handler\n") ;
}
void my_exit2 (void)
{
printf("second exit handler\n") ;
}
int main (int argc ,char *argv[])
{
if (atexit (my_exit1) != 0)
printf ("can't exit my_exit1\n") ;
if (atexit (my_exit2) != 0)
printf ("can't exit my_exit2\n") ;
if (atexit (my_exit2) != 0)
printf ("can't exit my_exit2\n") ;
return 0;
}
结果:
second exit handler
first exit handler
first exit handler
--------------------------机制-------------------------------
Windows:
在Windows中,用的是vs2005调试。阅读汇编代码,发现
在Windows中,是把每一次注册的函数放在一个地址,然后每当再注册一个一个函数时,则把该地址加4。
这样当函数执行完main函数返回时,先于基地址比较,如果不相等,然后再取出该地址中的函数地址,比较是否为0,
如果为NULL,则继续,如果不为NULL,则执行该地址的函数
Linux:
在Linux中(UBT中9.**),采取了不同的机制。
在Linux中,存放了一个计数器。先判断是否为该计数器是否为0。
如果为0,则表示没有注册函数。
如果大于0,则说明有注册函数。
另外在Linux中,注册函数是存放在一个大小为16字节的结构体中,每个结构体有一个成员是注册函数地址。
struct atexit_fn {
int fn_type; /* ATEXIT_? from above */
union {
void (*std_func)(void);void (*cxa_func)(void *);
#ifdef __BLOCKS__void (^block)(void);
#endif /* __BLOCKS__ */
}fn_ptr; /* function pointer */
void *fn_arg; /* argument for CXA callback */
void *fn_dso; /* shared module handle */
}
--------------------------------------------------------------
程序执行与结束原理:
内核使程序执行的唯一方法是调用一个exec函数,进程自愿终止哦的唯一方法是显式或者隐式调用(通过exit函数)_exit()或者_Exit()函数。因此exit函数中实质是对_exit()或者_Exit()函数的封装。exit会先执行自定义的终止处理函数,然后执行I/O库函数清理函数fclose(),这也是为什么可以在终止处理函数中可以继续运用printf之类函数的原因,因为I/O库函数的流对象还没有被清除,当然可以继续运用。执行完了所有的fclose()以后,可以执行真正意义上的终止函数_exit()或者_Exit()函数。
参考:《UNIX环境高级编程》