关于linux:C语言中这么骚的退出程序的方式你知道几个

40次阅读

共计 6094 个字符,预计需要花费 16 分钟才能阅读完成。

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-LeHu…

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

正文完
 0