C语言中这么骚的退出程序的方式你知道几个?
C语言中这么骚的退出程序的方式你知道几个?
前言
在本篇文章当中主要给大家介绍C语言当中一些不常用的特性,比如在main函数之前和之后设置我们想要执行的函数,以及各种花式退出程序的方式。
main函数是最先执行和最后执行的函数吗?
C语言构造和析构函数
通常我们在写C程序的时候都是从main函数开始写,因此我们可能没人有关心过这个问题,事实上是main函数不是程序第一个执行的函数,也不是程序最后一个执行的函数。
#include <stdio.h>
void __attribute__((constructor)) init1() {
printf("before main funciton\n");
}
int main() {
printf("this is main funciton\n");
}
我们编译上面的代码然后执行,输出结果如下图所示:
➜ code git:(main) ./init.out
before main funciton
this is main funciton
由此可见main函数并不是第一个被执行的函数,那么程序第一次执行的函数是什么呢?很简单我们看一下程序的调用栈即可。
从上面的结果可以知道,程序第一个执行的函数是_start,这是在类Unix操作系统上执行的第一个函数。
那么main函数是程序执行的最后一个函数吗?我们看下面的代码:
#include <stdio.h>
void __attribute__((destructor)) __exit() {
printf("this is exit\n");
}
void __attribute__((constructor)) init() {
printf("this is init\n");
}
int main() {
printf("this is main\n");
return 0;
}
上面程序的输出结果如下:
➜ code git:(main) ./out.out
this is init
this is main
this is exit
由此可见main函数也不是我们最后执行的函数!事实上我们除了上面的方法之外我们也可以在libc当中注册一些函数,让程序在main函数之后,退出执行前执行这些函数。
on_exit和atexit函数
我们可以使用上面两个函数进行函数的注册,让程序退出之前执行我们指定的函数
#include <stdio.h>
#include <stdlib.h>
void __attribute__((destructor)) __exit() {
printf("this is exit\n");
}
void __attribute__((constructor)) init() {
printf("this is init\n");
}
void on__exit() {
printf("this in on exit\n");
}
void at__exit() {
printf("this in at exit\n");
}
int main() {
on_exit(on__exit, NULL);
atexit(at__exit);
printf("this is main\n");
return 0;
}
this is init
this is main
this in at exit
this in on exit
this is exit
我们可以仔细分析一下上面程序执行的顺序。首先是执构造函数,然后执行 atexit 注册的函数,再执行 on_exit 注册的函数,最后执行析构函数。从上面程序的输出我们可以知道我们注册的函数生效了,但是需要注意一个问题,先注册的函数后执行,不管是使用 atexit 还是 on_exit 函数。我们现在看下面的代码:
#include <stdio.h>
#include <stdlib.h>
void __attribute__((destructor)) __exit() {
printf("this is exit\n");
}
void __attribute__((constructor)) init() {
printf("this is init\n");
}
void on__exit() {
printf("this in on exit\n");
}
void at__exit() {
printf("this in at exit\n");
}
int main() {
// 调换下面两行的顺序
atexit(at__exit);
on_exit(on__exit, NULL);
printf("this is main\n");
return 0;
}
上面的代码输出如下:
this is init
this is main
this in on exit
this in at exit
this is exit
从输出的结果看确实和上面我们提到的规则一样,先注册的函数后执行。这一点再linux程序员开发手册里面也提到了。

但是这里有一点需要注意的是我们应该尽可能使用atexit函数,而不是使用on_exit函数,因为atexit函数是标准规定的,而on_exit并不是标准规定的。
exit和_exit函数
其中exit函数是libc给我们提供的函数,我们可以使用这个函数正常的终止程序的执行,而且我们在前面注册的函数还是能够被执行。比如在下面的代码当中:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void __attribute__((destructor)) __exit1() {
printf("this is exit1\n");
}
void __attribute__((destructor)) __exit2() {
printf("this is exit2\n");
}
void __attribute__((constructor)) init1() {
printf("this is init1\n");
}
void __attribute__((constructor)) init2() {
printf("this is init2\n");
}
void on__exit1() {
printf("this in on exit1\n");
}
void at__exit1() {
printf("this in at exit1\n");
}
void on__exit2() {
printf("this in on exit2\n");
}
void at__exit2() {
printf("this in at exit2\n");
}
int main() {
// _exit(1);
on_exit(on__exit1, NULL);
on_exit(on__exit2, NULL);
atexit(at__exit1);
atexit(at__exit2);
printf("this is main\n");
exit(1);
return 0;
}
上面的函数执行结果如下所示:
this is init1
this is init2
this is main
this in at exit2
this in at exit1
this in on exit2
this in on exit1
this is exit2
this is exit1
可以看到我们的代码被正常执行啦。
但是_exit是一个系统调用,当执行这个方法的时候程序会被直接终止,我们看下面的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void __attribute__((destructor)) __exit1() {
printf("this is exit1\n");
}
void __attribute__((destructor)) __exit2() {
printf("this is exit2\n");
}
void __attribute__((constructor)) init1() {
printf("this is init1\n");
}
void __attribute__((constructor)) init2() {
printf("this is init2\n");
}
void on__exit1() {
printf("this in on exit1\n");
}
void at__exit1() {
printf("this in at exit1\n");
}
void on__exit2() {
printf("this in on exit2\n");
}
void at__exit2() {
printf("this in at exit2\n");
}
int main() {
// _exit(1);
on_exit(on__exit1, NULL);
on_exit(on__exit2, NULL);
atexit(at__exit1);
atexit(at__exit2);
printf("this is main\n");
_exit(1); // 只改了这个函数 从 exit 变成 _exit
return 0;
}
上面的代码输出结果如下所示:
this is init1
this is init2
this is main
可以看到我们注册的函数和最终的析构函数都没有被执行,程序直接退出啦。
花式退出
出了上面的_exit函数之外,我们还可以使用其他的方式直接退出程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
void __attribute__((destructor)) __exit1() {
printf("this is exit1\n");
}
void __attribute__((destructor)) __exit2() {
printf("this is exit2\n");
}
void __attribute__((constructor)) init1() {
printf("this is init1\n");
}
void __attribute__((constructor)) init2() {
printf("this is init2\n");
}
void on__exit1() {
printf("this in on exit1\n");
}
void at__exit1() {
printf("this in at exit1\n");
}
void on__exit2() {
printf("this in on exit2\n");
}
void at__exit2() {
printf("this in at exit2\n");
}
int main() {
// _exit(1);
on_exit(on__exit1, NULL);
on_exit(on__exit2, NULL);
atexit(at__exit1);
atexit(at__exit2);
printf("this is main\n");
syscall(SYS_exit, 1); // 和 _exit 效果一样
return 0;
}
出了上面直接调用函数的方法退出函数,我们还可以使用内联汇编退出函数,比如在64位操作系统我们可以使用下面的代码退出程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
void __attribute__((destructor)) __exit1() {
printf("this is exit1\n");
}
void __attribute__((destructor)) __exit2() {
printf("this is exit2\n");
}
void __attribute__((constructor)) init1() {
printf("this is init1\n");
}
void __attribute__((constructor)) init2() {
printf("this is init2\n");
}
void on__exit1() {
printf("this in on exit1\n");
}
void at__exit1() {
printf("this in at exit1\n");
}
void on__exit2() {
printf("this in on exit2\n");
}
void at__exit2() {
printf("this in at exit2\n");
}
int main() {
// _exit(1);
on_exit(on__exit1, NULL);
on_exit(on__exit2, NULL);
atexit(at__exit1);
atexit(at__exit2);
printf("this is main\n");
asm(
"movq $60, %%rax;"
"movq $1, %%rdi;"
"syscall;"
:::"eax"
);
return 0;
}
上面是在64位操作系统退出程序的汇编实现,在64为系统上退出程序的系统调用号为60。下面我们使用32位操作系统上的汇编实现程序退出,在32位系统上退出程序的系统调用号等于1:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
void __attribute__((destructor)) __exit1() {
printf("this is exit1\n");
}
void __attribute__((destructor)) __exit2() {
printf("this is exit2\n");
}
void __attribute__((constructor)) init1() {
printf("this is init1\n");
}
void __attribute__((constructor)) init2() {
printf("this is init2\n");
}
void on__exit1() {
printf("this in on exit1\n");
}
void at__exit1() {
printf("this in at exit1\n");
}
void on__exit2() {
printf("this in on exit2\n");
}
void at__exit2() {
printf("this in at exit2\n");
}
int main() {
// _exit(1);
on_exit(on__exit1, NULL);
on_exit(on__exit2, NULL);
atexit(at__exit1);
atexit(at__exit2);
printf("this is main\n");
asm volatile(
"movl $1, %%eax;"
"movl $1, %%edi;"
"int $0x80;"
:::"eax"
);
return 0;
}
总结
在本篇文章当中主要给大家介绍C语言当中一些与程序退出的骚操作,希望大家有所收获!
以上就是本篇文章的所有内容了,我是LeHung,我们下期再见!!!更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore
关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。
C语言中这么骚的退出程序的方式你知道几个?的更多相关文章
- C 语言中模板的几种实现方式
简单宏定义实现 简单宏定义 - 方式一 这种方式将主要实现部分放在一个宏定义中,利用字符替换的方式实现不同 type 的运算,详细思路见代码: simple_macro_1.c #include &l ...
- C语言学习及应用笔记之七:C语言中的回调函数及使用方式
我们在使用C语言实现相对复杂的软件开发时,经常会碰到使用回调函数的问题.但是回调函数的理解和使用却不是一件简单的事,在本篇我们根据我们个人的理解和应用经验对回调函数做简要的分析. 1.什么是回调函数 ...
- C语言中函数参数传递
C语言中函数参数传递的三种方式 (1)值传递,就是把你的变量的值传递给函数的形式参数,实际就是用变量的值来新生成一个形式参数,因而在函数里对形参的改变不会影响到函数外的变量的值.(2)地址传递,就是把 ...
- 036_go语言中的原子计数器
代码演示 package main import ( "fmt" "runtime" "sync/atomic" "time&qu ...
- 【R语言入门】R语言中的变量与基本数据类型
说明 在前一篇中,我们介绍了 R 语言和 R Studio 的安装,并简单的介绍了一个示例,接下来让我们由浅入深的学习 R 语言的相关知识. 本篇将主要介绍 R 语言的基本操作.变量和几种基本数据类型 ...
- C语言中字符串详解
C语言中字符串详解 字符串时是C语言中非常重要的部分,我们从字符串的性质和字符串的创建.程序中字符串的输入输出和字符串的操作来对字符串进行详细的解析. 什么是字符串? C语言本身没有内置的字符串类型, ...
- JAVA语言中的修饰符
JAVA语言中的修饰符 -----------------------------------------------01--------------------------------------- ...
- C语言中的结构体
用户自己建立自己的结构体类型 1. 定义和使用结构体变量 (1).结构体的定义 C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体. (2).声明一个结构体类型的一般形式为: ...
- Java语言中的面向对象特性总结
Java语言中的面向对象特性 (总结得不错) [课前思考] 1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类? 2. 面向对象编程的特性有哪三个?它们各自又有哪些特性? 3. 你知 ...
随机推荐
- devops-4:Jenkins基于k8s cloud和docker cloud动态增减节点
Jenkins管理动态节点 上文介绍Jenkins增加静态agent的步骤,除了静态增加外,还有动态管理的功能,两者最大的差异在于动态可以在有job运行时,临时加入一个agent到jenkins ma ...
- Windows Embedded CE 6.0开发环境的搭建(2)
最近开始在学习嵌入式,在这里首先得安装Windows Embedded CE 6.0,其中遇到了很多问题,电脑的系统以及相关配置都会在安装过程中受到影响,因此笔者就安装中的问题以及环境搭建来介绍一下. ...
- 8. 利用Ansible快速构建MGR | 深入浅出MGR
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 目录 1. 安装ansbile 2. 配置ansible 3. 建立ssh信任 4. 测试ansible 5. 使用ans ...
- java的stream让我灵光一现
说实话,我是一个到了退役也没有搞明白C++的istream和ostream的. 刚开始的时候我把<iostream>直接拆解成ios和tream 真,果粉暴露 退役之后划水,倒是从java ...
- Redis的两种持久化机制
Redis的两种持久化机制 1.持久化机制 client--->redis(内存)--->内存数据-数据持久化--->磁盘 两种方法 快照(Snapshot) AOF(Append ...
- java学习第三天常用类.day12
String String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了 如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & String ...
- Python之验证码识别功能
Python之pytesseract 识别验证码 1.验证码来一个 2.适合什么样的验证码呢? 只能识别简单.静态.无重叠.只有数字字母的验证码 3.实际应用:模拟人工登录.页面内容识别.爬虫抓取信息 ...
- 字节一面:说说TCP的三次握手
上周有朋友去了字节面试,问到了TCP三次握手的问题,当时回答的不是很好,对于三次握手的发送的报文信息都不太熟,本文主要做一下总结和记录. TCP全称为Transmission Control Prot ...
- 部署k8s的heapster监控
Heapster是容器集群监控和性能分析工具,天然的支持Kubernetes和CoreOS heapster监控目前官网已经不更新,部署学习使用 heapster: 收集监控数据 influxdb:数 ...
- 最短路径算法-迪杰斯特拉(Dijkstra)算法在c#中的实现和生产应用
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径. 它的主要特点是以起始点为中心向外层层扩展(广度优先遍历思想),直到扩展到终点为止 贪心算法(Greedy ...
