跟atexit函数相识已久,man手册里对atexit的解释是这么一段:

The  atexit()  function registers the given function to be called at normal process termination, either via exit() 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();
printf("after bye\n");
} void *do_thread(void)
{
while()
{
printf("--------------I'm thread!\n");
sleep();
}
} 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 > )
{
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的解释,别有一番深意。

atexit注册的函数是在main函数之后执行?的更多相关文章

  1. 一个 Map 函数、一个 Reduce 函数和一个 main 函数

    MapReduce 最简单的 MapReduce应用程序至少包含 3 个部分:一个 Map 函数.一个 Reduce 函数和一个 main 函数.main 函数将作业控制和文件输入/输出结合起来.在这 ...

  2. C++的函数重载和main函数之外的工作

    今天被问到一个C++的函数重载问题,一下子没反应过来,这种基础的问题竟然忘记了,以下记录一下这些忘记的内容.     函数重载 函数重载的定义是:在相同的作用域中,如果函数具有相同名字而仅仅是形参表不 ...

  3. 指针数组,数组指针,函数指针,main函数实质,二重指针,函数指针作为參数,泛型函数

     1.指针数组 数组里面的每一个元素都是指针. 指针数组的案比例如以下: 易犯错误: 2.数组指针 归根结底还是指针,仅仅是取*的时候可以取出一整个数组出来. 数组指针:(一个指针指向了数组.一般 ...

  4. main函数执行前、后再执行的代码

    一.main结束 不代表整个进程结束  (1)全局对象的构造函数会在main 函数之前执行,          全局对象的析构函数会在main函数之后执行:          用atexit注册的函数 ...

  5. 设计main函数退出后继续执行一段代码

    原理: 使用 _onexit() 函数注册一个函数,这个函数会在main函数退出后执行 使用原则: 1.包含在cstdlib中,是c语言中的库函数: 2.需要注册的函数格式为:int类型返回值.无参数 ...

  6. linux编程之main()函数启动过程【转】

    转自:http://blog.csdn.net/gary_ygl/article/details/8506007 1 最简单的程序  1)编辑helloworld程序,$vim helloworld. ...

  7. [汇编与C语言关系]2. main函数与启动例程

    为什么汇编程序的入口是_start,而C程序的入口是main函数呢?以下就来解释这个问题 在<x86汇编程序基础(AT&T语法)>一文中我们汇编和链接的步骤是: $ as hell ...

  8. c语言main函数返回值、参数详解(返回值是必须的,0表示正常退出)

    C语言Main函数返回值 main函数的返回值,用于说明程序的退出状态.如果返回0,则代表程序正常退出:返回其它数字的含义则由系统决定.通常,返回非零代表程序异常退出. 很多人甚至市面上的一些书籍,都 ...

  9. main函数和启动例程

    为什么汇编程序的入口是_start,而C程序的入口是main函数呢?本节就来解释这个问题.在讲例 18.1 “最简单的汇编程序”时,我们的汇编和链接步骤是: $ as hello.s -o hello ...

随机推荐

  1. java 反射技术

    什么是反射?反射就是将字节码中的各种成分映射到相应的java类中来,java反射技术自JDK1.1以来就出现了,目前大多数流行的框架都采用了这种技术,可见其重要性,这篇文章将详细介绍我对java反射技 ...

  2. FreeMarker语法知识

    FreeMarker的模板文件并不比HTML页面复杂多少,FreeMarker模板文件主要由如下4个部分组成:1,文本:直接输出的部分2,注释:<#-- ... -->格式部分,不会输出3 ...

  3. nyoj 97 兄弟郊游问题

    点击打开链接 兄弟郊游问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:2 描述 兄弟俩骑车郊游,弟弟先出发,每分钟X米,M分钟后,哥哥带一条狗出发.以每分钟Y米的速度去追弟弟 ...

  4. isAnagram

    /*Given two strings s and t, write a function to determine if t is an anagram of s. For example, s = ...

  5. task中的一些属性

    1.android:allowTaskReparenting 这个属性用来标记一个Activity实例在当前应用退居后台后,是否能从启动它的那个task移动到有共同affinity的task,“tru ...

  6. Sqoop增量从MySQL中向hive导入数据

    sqoop job --create incretest -- import --connect jdbc:mysql://10.8.2.19:3306/db --table table1 --use ...

  7. The 2013 ACM-ICPC Asia Changsha Regional Contest - J

    Josephina and RPG Time Limit: 2 Seconds      Memory Limit: 65536 KB      Special Judge A role-playin ...

  8. windows Android开发环境快速搭建和部署

    windows安装Android的开发环境相对来说比较简单,本文写给第一次想在自己Windows上建立Android开发环境的朋友们,为了确保大家能顺利完成开发环境的搭建,文章写的尽量详细,希望对初级 ...

  9. SQL基本语句(1)

    利用select的结果创建表 可以通过选择一个表的全部内容(无 WHERE 子句)来拷贝一个表,或利用一个总是失败的 WHERE 子句来创建一个空表,如: mysql> CREATE TABLE ...

  10. c语言描述简单的线性表,获取元素,删除元素,

    //定义线性表 #define MAXSIZE 20 typedef int ElemType; typedef struct { ElemType data[MAXSIZE]; //这是数组的长度, ...