atexit注册的函数是在main函数之后执行?
跟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函数之后执行?的更多相关文章
- 一个 Map 函数、一个 Reduce 函数和一个 main 函数
MapReduce 最简单的 MapReduce应用程序至少包含 3 个部分:一个 Map 函数.一个 Reduce 函数和一个 main 函数.main 函数将作业控制和文件输入/输出结合起来.在这 ...
- C++的函数重载和main函数之外的工作
今天被问到一个C++的函数重载问题,一下子没反应过来,这种基础的问题竟然忘记了,以下记录一下这些忘记的内容. 函数重载 函数重载的定义是:在相同的作用域中,如果函数具有相同名字而仅仅是形参表不 ...
- 指针数组,数组指针,函数指针,main函数实质,二重指针,函数指针作为參数,泛型函数
1.指针数组 数组里面的每一个元素都是指针. 指针数组的案比例如以下: 易犯错误: 2.数组指针 归根结底还是指针,仅仅是取*的时候可以取出一整个数组出来. 数组指针:(一个指针指向了数组.一般 ...
- main函数执行前、后再执行的代码
一.main结束 不代表整个进程结束 (1)全局对象的构造函数会在main 函数之前执行, 全局对象的析构函数会在main函数之后执行: 用atexit注册的函数 ...
- 设计main函数退出后继续执行一段代码
原理: 使用 _onexit() 函数注册一个函数,这个函数会在main函数退出后执行 使用原则: 1.包含在cstdlib中,是c语言中的库函数: 2.需要注册的函数格式为:int类型返回值.无参数 ...
- linux编程之main()函数启动过程【转】
转自:http://blog.csdn.net/gary_ygl/article/details/8506007 1 最简单的程序 1)编辑helloworld程序,$vim helloworld. ...
- [汇编与C语言关系]2. main函数与启动例程
为什么汇编程序的入口是_start,而C程序的入口是main函数呢?以下就来解释这个问题 在<x86汇编程序基础(AT&T语法)>一文中我们汇编和链接的步骤是: $ as hell ...
- c语言main函数返回值、参数详解(返回值是必须的,0表示正常退出)
C语言Main函数返回值 main函数的返回值,用于说明程序的退出状态.如果返回0,则代表程序正常退出:返回其它数字的含义则由系统决定.通常,返回非零代表程序异常退出. 很多人甚至市面上的一些书籍,都 ...
- main函数和启动例程
为什么汇编程序的入口是_start,而C程序的入口是main函数呢?本节就来解释这个问题.在讲例 18.1 “最简单的汇编程序”时,我们的汇编和链接步骤是: $ as hello.s -o hello ...
随机推荐
- MongoDB基本命令的使用
成功启动MongoDB后,再打开一个命令行窗口输入mongo,就可以进行数据库的一些操作. 输入help可以看到基本操作命令: show dbs:显示数据库列表 show collections:显示 ...
- 程序员遇到Bug时的30个反应
开发应用程序是一个非常有压力的工作.没有人是完美的,因此在这个行业中,代码中出现bug是相当普遍的现象.面对bug,一些程序员会生气,会沮丧,会心烦意乱,甚至会灰心丧气,而另一些程序员会依然保持冷静沉 ...
- springmvc 对REST风格的支持
1.PathVariable注解 用于映射url的占位符到目标方法的参数中 例子: @RequestMapping("/testPathVariable/{id}") public ...
- [Java] 读写字节数据,过滤流DataOutputStream和DataInputStream
package test.stream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io ...
- easyUi中的一段漂亮代码之将list转换成tree.
function convert(rows){ function exists(rows, parentId){ for(var i=0; i<rows.length; i++){ if (ro ...
- eclipse打jar包步骤
eclipse->文件->export->java->JAR file 选择项目,Options增加Add directory entries finish hadoop ja ...
- Tile-Based Deferred Rendering
目前所有的移动设备都使用的是 Tile-Based Deferred Rendering(TBDR) 的渲染架构.TBDR 的基本流程是这样的,当提交渲染命令的时候,GPU 不会立刻进行渲染,而是一帧 ...
- Docker学习总结之docker入门
Understanding Docker 以下均翻译自Docker官方文档 ,转载请注明:Vikings翻译. What is Docker? Docker 是一个开源的平台,设计目标是可以方便开发, ...
- KVM虚拟化(一)—— 介绍与简单使用
一.架构及介绍 KVM(Kernel-based Virtual Machine)它由 Quramnet 开发,该公司于 2008年被 Red Hat 收购: 自Linux 2.6.20后整合到内核, ...
- Windows Azure - App Services
1. 需要了解的概念:App Service Plan, Resource Group 2. Create an ASP.NET web app in Azure App Services 3. Cr ...