C语言中花式退出程序的方式总结

目录
  • 前言
  • main函数是最先执行和最后执行的函数吗
    • C语言构造和析构函数
    • on_exit和atexit函数
    • exit和_exit函数
    • 花式退出

前言

在本篇文章当中主要给大家介绍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语言中花式退出程序的方式总结的详细内容,更多关于C语言退出程序的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++在C语言基础之上增强的几个实用特性总结

    变量的定义 C语言中的变量都必须在作用域开始的位置定义!!  C++中更强调语言的"实用性",所有的变量都可以在需要使用时再定义. #include <iostream> using namespace std; int main11() { int i = 0; printf("ddd"); int k; // 这段代码在vc6,C语言编译情况下就会报错.就是因为这里的定义 system("pause"); return 0; }

  • 简单了解C语言中主线程退出对子线程的影响

    这篇文章主要介绍了简单了解C语言中主线程退出对子线程的影响,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 对于程序来说,如果主进程在子进程还未结束时就已经退出,那么Linux内核会将子进程的父进程ID改为1(也就是init进程),当子进程结束后会由init进程来回收该子进程. 那如果是把进程换成线程的话,会怎么样呢?假设主线程在子线程结束前就已经退出,子线程会发生什么? 在一些论坛上看到许多人说子线程也会跟着退出,其实这是错误的,原因在于他们混

  • 奇怪的C语言特性

    下面列出的特性未必奇怪,有的算是有趣. 1)a[2] 等价于 2[a] "aabbccdd"[5] 等价于 5["aabbccdd"] 这条特性可以用于使用数组.指针.字符串,但不能用在变量定义时.K&R C Programming language 217页对此有介绍. 2)二元.三元复合字符 http://en.wikipedia.org/wiki/Digraphs_and_trigraphs 字符串字面值??!将被认为是|,所以两个问号同时出现在字符串

  • 简要对比C语言中三个用于退出进程的函数

    C语言_exit()函数:结束进程执行 头文件: #include <unistd.h> 定义函数: void _exit(int status); 函数说明:_exit()用来立刻结束目前进程的执行, 并把参数status 返回给父进程, 并关闭未关闭的文件. 此函数调用后不会返回, 并且会传递SIGCHLD 信号给父进程, 父进程可以由wait 函数取得子进程结束状态. 附加说明:_exit ()不会处理标准I/O 缓冲区, 如要更新缓冲区请使用exit (). C语言on_exit()函

  • C语言中花式退出程序的方式总结

    目录 前言 main函数是最先执行和最后执行的函数吗 C语言构造和析构函数 on_exit和atexit函数 exit和_exit函数 花式退出 前言 在本篇文章当中主要给大家介绍C语言当中一些不常用的特性,比如在main函数之前和之后设置我们想要执行的函数,以及各种花式退出程序的方式. main函数是最先执行和最后执行的函数吗 C语言构造和析构函数 通常我们在写C程序的时候都是从main函数开始写,因此我们可能没人有关心过这个问题,事实上是main函数不是程序第一个执行的函数,也不是程序最后一

  • go语言中使用timer的常用方式

    本文实例总结了go语言中使用timer的常用方式.分享给大家供大家参考.具体分析如下: 下面三段代码(A,b,C)的功能都是在5分钟后执行指定的函数的go语言代码: 复制代码 代码如下: // (A) time.AfterFunc(5 * time.Minute, func() {     fmt.Printf("expired") } // (B) create a Timer object timer := time.NewTimer(5 * time.Minute) <-t

  • C语言中计算二叉树的宽度的两种方式

    C语言中计算二叉树的宽度的两种方式 二叉树作为一种很特殊的数据结构,功能上有很大的作用!今天就来看看怎么计算一个二叉树的最大的宽度吧. 采用递归方式 下面是代码内容: int GetMaxWidth(BinaryTree pointer){ int width[10];//加入这棵树的最大高度不超过10 int maxWidth=0; int floor=1; if(pointer){ if(floor==1){//如果访问的是根节点的话,第一层节点++; width[floor]++; flo

  • C语言中结构体偏移及结构体成员变量访问方式的问题讨论

    c语言结构体偏移 示例1 我们先来定义一下需求: 已知结构体类型定义如下: struct node_t{ char a; int b; int c; }; 且结构体1Byte对齐 #pragma pack(1) 求: 结构体struct node_t中成员变量c的偏移. 注:这里的偏移量指的是相对于结构体起始位置的偏移量. 看到这个问题的时候,我相信不同的人脑中浮现的解决方法可能会有所差异,下面我们分析以下几种可能的解法: 方法1 如果你对c语言的库函数比较熟悉的话,那么你第一个想到的肯定是of

  • C语言中四种取整方式,取余/取模运算以及负数取模问题详解

    目录 零向取整.负无穷向取整.正无穷向取整.四舍五入取整 总结 零向取整.负无穷向取整.正无穷向取整.四舍五入取整 如果将一个浮点数赋值给整形,只会保存整数位: 这种取整方式为零向取整,C语言默认采用的是这种方式 C语言中也有对应的零向取整函数: 同理还有一种函数是负无穷大取整: 它的取整方案是向负无穷大取整: 有地板取整,当然也有正无穷大取整的函数: 它的取整方式是向正无穷大取整: 最后,还有四舍五入取整的函数: 取模/取余 取模概念: 如果a和d是两个自然数,d非零,可以证明存在两个唯一的整

  • C语言中字符串的两种定义方式详解

    目录 方式1 方式2 总结 我们知道C语言中是没有字符串这种数据类型的,我们只能依靠数组进行存储,即字符数组,而我们定义并且初始化数组有两种方式.下面将给大家介绍这两种方式并且介绍这两种方式的区别: 方式1 前两种是正确的定义方式,第一种之所以没有指定字符数组长度的原因是编译器能够自己推断出其长度,无需程序员自己设定,这也是我们比较推荐的一种定义方式,但注意内存长度编译器一经判定就无法再次更改,接下来我们分析一下第三种编译器为什么会出现乱码. 相信大家都知道,字符串是以'\0'字符为结束标志的,

  • go语言中五种字符串的拼接方式(小结)

    目录 +拼接方式 sprintf函数 Join函数 buffer.Builderbuffer.WriteString函数 buffer.Builder函数 ps:直接使用运算符 主要结论 +拼接方式 这种方式是我在写golang经常用的方式,go语言用+拼接,php使用.拼接,不过由于golang中的字符串是不可变的类型,因此用 + 连接会产生一个新的字符串对效率有影响. func main() { s1 := "hello" s2 := "word" s3 :=

  • C语言中斐波那契数列的三种实现方式(递归、循环、矩阵)

    目录 一.递归 二.循环 三.矩阵 <剑指offer>里讲到了一种斐波那契数列的 O(logN) 时间复杂度的实现,觉得挺有意思的,三种方法都记录一下. 一.递归 一般来说递归实现的代码都要比循环要简洁,但是效率不高,比如递归计算斐波那契数列第n个元素. long long Fibonacci_Solution1(unsigned int n) { // printf("%d ", n); if (n <= 0) return 0; if (n == 1) retur

  • C语言中的字符串数据在C中的存储方式

    目录 内存中的五大区域 字符串数据在C语言中有两种存储方式 几个比较容易混的点 统计字符串中某一个字符出现的次数 使用字符指针数组来存储多个字符串数据 内存中的五大区域 栈:是专门用来存储局部变量的,所有的局部变量都是声明在栈区域中 堆:允许程序员手动的从堆申请指定字节数的空间来使用 BSS段:是用来存储未初始化的全局变量和静态变量,声明一个全局变量,如果我们没有初始化,在程序运行最开始的时候,这个全局变量是没有初始化的,存储在BSS段[程序运行后系统就自动的初始化为0,并把初始化后的全局变量存

  • C语言中进程间通讯的方式详解

    目录 一.无名管道 1.1无名管道的原理 1.2功能 1.3无名管道通信特点 1.4无名管道的实例 二.有名管道 2.1有名管道的原理 2.2有名管道的特点 2.3有名管道实例 三.信号 3.1信号的概念 3.2发送信号的函数 3.3常用的信号 3.4实例 四.IPC进程间通信 4.1IPC进程间通信的种类 4.2查看IPC进程间通信的命令 4.3消息队列 4.4共享内存 4.5信号灯集合 一.无名管道 1.1无名管道的原理 无名管道只能用于亲缘间进程的通信,无名管道的大小是64K.无名管道是内

随机推荐