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 funcitonthis 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 initthis is mainthis 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 initthis is mainthis in at exitthis in on exitthis 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 initthis is mainthis in on exitthis in at exitthis 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 init1this is init2this is mainthis in at exit2this in at exit1this in on exit2this in on exit1this is exit2this 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 init1this is init2this 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-LeHu...

关注公众号:一无是处的钻研僧,理解更多计算机(Java、Python、计算机系统根底、算法与数据结构)常识。