您的位置:首页 > 大数据 > 人工智能

atexit注册的函数是在main函数之后执行?

2014-02-27 20:06 405 查看
跟atexit函数相识已久,man手册里对atexit的解释是这么一段:

The  atexit()  function registers the given function to be called at normal process termination, either via exit(3) or via return from the program’s main().  Functions so registered are called in the reverse order of their registration; no arguments are passed.


乍一看,就形成了这样的印象:“哦,atexit函数就是来注册一个函数A,使main函数退出后,要执行一下函数A,进程才会彻底over”。

直到工作中遇到一个段错的bug,日志中发现,进程在执行atexit注册过的函数时,main函数里的线程依然在快活地运行,这种现象颠覆了我以往的认知,让我不得不重新思考,atexit注册的函数到底什么时候执行?何为“退出main函数”?

先上一段简单代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void bye(void)
{
printf("before bye\n");
sleep(10);
printf("after bye\n");
}

void *do_thread(void)
{
while(1)
{
printf("--------------I'm thread!\n");
sleep(1);
}
}

int main(void)
{
pthread_t pid_t;

atexit(bye);
pthread_create(&pid_t, NULL, (void *)do_thread, NULL);

exit(EXIT_SUCCESS);
}


上面的程序先用atexit注册一个程序正常退出后的执行函数,再创建一线程用来不断输出信息,然后主线程执行exit;

运行程序会输出什么呢?

我一开始的猜测运行结果应该是:

before bye

after bye

或者是:

--------------I'm thread!

before bye

after bye

因为在我的理解中,bye()是在退出main函数之后执行,那时候,main函数里创建的线程什么的应该都不复存在了,代码会清清静静地执行bye()函数。事实证明,我太想当然了。

上面程序实际运行结果是:

before bye
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
--------------I'm thread!
after bye


为什么在执行bye()的时候线程还在呢?

来看一下exit()函数的源码:

/* Copyright (C) 1991,95,96,97,99,2001,2002,2005,2009
Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>.  */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sysdep.h>
#include "exit.h"

#include "set-hooks.h"
DEFINE_HOOK (__libc_atexit, (void))

/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
perform stdio cleanup, and terminate program execution with STATUS.  */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit)
{
/* We do it this way to handle recursive calls to exit () made by
the functions registered with `atexit' and `on_exit'. We call
everyone on the list and use the status value in the last
exit (). */
while (*listp != NULL)
{
struct exit_function_list *cur = *listp;

while (cur->idx > 0)
{
const struct exit_function *const f =
&cur->fns[--cur->idx];
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);

case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
atfct ();
break;
case ef_cxa:
cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (cxafct);
#endif
cxafct (f->func.cxa.arg, status);
break;
}
}

*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element.  */
free (cur);
}

if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());

_exit (status);
}

void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true);
}
libc_hidden_def (exit)


从上面的源码可以看出:exit()先是执行atexit注册的函数,然后再执行_exit函数,_exit会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数。所以当exit()执行到_exit()的时候,之前创建的线程才会停止运行。之前我脑海里存在的“退出main函数”的概念还是太抽象了,其背后存在的其实是一个动作流,会执行atexit注册的函数,刷新流(stdin, stdout, stderr),把文件缓冲区的内容写回文件,关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数。exit()和_exit()源码有待好好研究。

再次回首篇头那一段atexit的解释,别有一番深意。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: