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

跟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函数”?

先上一段简单代码:

 1 #include <stdio.h>

 2 #include <stdlib.h>

 3 #include <unistd.h>

 4 #include <pthread.h>

 5 

 6 void bye(void)

 7 {

 8     printf("before bye\n");

 9     sleep(10);

10     printf("after bye\n");

11 }

12 

13 void *do_thread(void)

14 {

15     while(1)

16     {

17         printf("--------------I'm thread!\n");

18         sleep(1);

19     }

20 }

21 

22 int main(void)

23 {

24     pthread_t pid_t;

25     

26     atexit(bye);

27     pthread_create(&pid_t, NULL, (void *)do_thread, NULL);

28 

29     exit(EXIT_SUCCESS);

30 }

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

运行程序会输出什么呢?

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

before bye

after bye

或者是:

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

before bye

after bye

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

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

 1 before bye

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

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

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

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

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

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

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

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

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

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

12 after bye

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

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

  1 /* Copyright (C) 1991,95,96,97,99,2001,2002,2005,2009

  2    Free Software Foundation, Inc.

  3    This file is part of the GNU C Library.

  4 

  5    The GNU C Library is free software; you can redistribute it and/or

  6    modify it under the terms of the GNU Lesser General Public

  7    License as published by the Free Software Foundation; either

  8    version 2.1 of the License, or (at your option) any later version.

  9 

 10    The GNU C Library is distributed in the hope that it will be useful,

 11    but WITHOUT ANY WARRANTY; without even the implied warranty of

 12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

 13    Lesser General Public License for more details.

 14 

 15    You should have received a copy of the GNU Lesser General Public

 16    License along with the GNU C Library; if not, see

 17    <http://www.gnu.org/licenses/>.  */

 18 

 19 #include <stdio.h>

 20 #include <stdlib.h>

 21 #include <unistd.h>

 22 #include <sysdep.h>

 23 #include "exit.h"

 24 

 25 #include "set-hooks.h"

 26 DEFINE_HOOK (__libc_atexit, (void))

 27 

 28 

 29 /* Call all functions registered with `atexit' and `on_exit',

 30    in the reverse of the order in which they were registered

 31    perform stdio cleanup, and terminate program execution with STATUS.  */

 32 void

 33 attribute_hidden

 34 __run_exit_handlers (int status, struct exit_function_list **listp,

 35              bool run_list_atexit)

 36 {

 37   /* We do it this way to handle recursive calls to exit () made by

 38      the functions registered with `atexit' and `on_exit'. We call

 39      everyone on the list and use the status value in the last

 40      exit (). */

 41   while (*listp != NULL)

 42     {

 43       struct exit_function_list *cur = *listp;

 44 

 45       while (cur->idx > 0)

 46     {

 47       const struct exit_function *const f =

 48         &cur->fns[--cur->idx];

 49       switch (f->flavor)

 50         {

 51           void (*atfct) (void);

 52           void (*onfct) (int status, void *arg);

 53           void (*cxafct) (void *arg, int status);

 54 

 55         case ef_free:

 56         case ef_us:

 57           break;

 58         case ef_on:

 59           onfct = f->func.on.fn;

 60 #ifdef PTR_DEMANGLE

 61           PTR_DEMANGLE (onfct);

 62 #endif

 63           onfct (status, f->func.on.arg);

 64           break;

 65         case ef_at:

 66           atfct = f->func.at;

 67 #ifdef PTR_DEMANGLE

 68           PTR_DEMANGLE (atfct);

 69 #endif

 70           atfct ();

 71           break;

 72         case ef_cxa:

 73           cxafct = f->func.cxa.fn;

 74 #ifdef PTR_DEMANGLE

 75           PTR_DEMANGLE (cxafct);

 76 #endif

 77           cxafct (f->func.cxa.arg, status);

 78           break;

 79         }

 80     }

 81 

 82       *listp = cur->next;

 83       if (*listp != NULL)

 84     /* Don't free the last element in the chain, this is the statically

 85        allocate element.  */

 86     free (cur);

 87     }

 88 

 89   if (run_list_atexit)

 90     RUN_HOOK (__libc_atexit, ());

 91 

 92   _exit (status);

 93 }

 94 

 95 

 96 void

 97 exit (int status)

 98 {

 99   __run_exit_handlers (status, &__exit_funcs, true);

100 }

101 libc_hidden_def (exit)

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

再次回首篇头那一段atexit的解释,别有一番深意。

你可能感兴趣的:(main)