• C语言中这么骚的退出程序方式你知道几个?


    前言

    在本篇文章当中主要给大家介绍C语言当中一些不常用的特性,比如在main函数之前和之后设置我们想要执行的函数,以及各种花式退出程序的方式。

    1、main函数是最先执行和最后执行的函数吗?

    1)C语言构造和析构函数

    通常我们在写C程序的时候都是从main函数开始写,因此我们可能没人有关心过这个问题,事实上是main函数不是程序第一个执行的函数,也不是程序最后一个执行的函数。

    1. #include
    2. void __attribute__((constructor)) init1() {
    3. printf("before main funciton\n");
    4. }
    5. int main() {
    6. printf("this is main funciton\n");
    7. }

    我们编译上面的代码然后执行,输出结果如下所示:

    1. ➜ code git:(main) ./init.out
    2. before main funciton
    3. this is main funciton

    由此可见main函数并不是第一个被执行的函数,那么程序第一次执行的函数是什么呢?很简单我们看一下程序的调用栈即可。

    从上面的结果可以知道,程序第一个执行的函数是_start,这是在类Unix操作系统上执行的第一个函数。

    那么main函数是程序执行的最后一个函数吗?我们看下面的代码:

    1. #include
    2. void __attribute__((destructor)) __exit() {
    3. printf("this is exit\n");
    4. }
    5. void __attribute__((constructor)) init() {
    6. printf("this is init\n");
    7. }
    8. int main() {
    9. printf("this is main\n");
    10. return 0;
    11. }

    上面程序的输出结果如下:

    1. ➜ code git:(main) ./out.out
    2. this is init
    3. this is main
    4. this is exit

    由此可见main函数也不是我们最后执行的函数!事实上我们除了上面的方法之外我们也可以在libc当中注册一些函数,让程序在main函数之后,退出执行前执行这些函数。

    2)on_exit和atexit函数

    我们可以使用上面两个函数进行函数的注册,让程序退出之前执行我们指定的函数

    1. #include
    2. #include
    3. void __attribute__((destructor)) __exit() {
    4. printf("this is exit\n");
    5. }
    6. void __attribute__((constructor)) init() {
    7. printf("this is init\n");
    8. }
    9. void on__exit() {
    10. printf("this in on exit\n");
    11. }
    12. void at__exit() {
    13. printf("this in at exit\n");
    14. }
    15. int main() {
    16. on_exit(on__exit, NULL);
    17. atexit(at__exit);
    18. printf("this is main\n");
    19. return 0;
    20. }
    1. this is init
    2. this is main
    3. this in at exit
    4. this in on exit
    5. this is exit

    我们可以仔细分析一下上面程序执行的顺序。首先是执构造函数,然后执行 atexit 注册的函数,再执行 on_exit 注册的函数,最后执行析构函数。从上面程序的输出我们可以知道我们注册的函数生效了,但是需要注意一个问题,先注册的函数后执行,不管是使用 atexit 还是 on_exit 函数。我们现在看下面的代码:

    1. #include
    2. #include
    3. void __attribute__((destructor)) __exit() {
    4. printf("this is exit\n");
    5. }
    6. void __attribute__((constructor)) init() {
    7. printf("this is init\n");
    8. }
    9. void on__exit() {
    10. printf("this in on exit\n");
    11. }
    12. void at__exit() {
    13. printf("this in at exit\n");
    14. }
    15. int main() {
    16. // 调换下面两行的顺序
    17. atexit(at__exit);
    18. on_exit(on__exit, NULL);
    19. printf("this is main\n");
    20. return 0;
    21. }

    上面的代码输出如下:

    1. this is init
    2. this is main
    3. this in on exit
    4. this in at exit
    5. this is exit

    从输出的结果看确实和上面我们提到的规则一样,先注册的函数后执行。这一点再linux程序员开发手册里面也提到了。

    但是这里有一点需要注意的是我们应该尽可能使用atexit函数,而不是使用on_exit函数,因为atexit函数是标准规定的,而on_exit并不是标准规定的。

    3)exit和_exit函数

    其中exit函数是libc给我们提供的函数,我们可以使用这个函数正常的终止程序的执行,而且我们在前面注册的函数还是能够被执行。比如在下面的代码当中:

    1. #include
    2. #include
    3. #include
    4. void __attribute__((destructor)) __exit1() {
    5. printf("this is exit1\n");
    6. }
    7. void __attribute__((destructor)) __exit2() {
    8. printf("this is exit2\n");
    9. }
    10. void __attribute__((constructor)) init1() {
    11. printf("this is init1\n");
    12. }
    13. void __attribute__((constructor)) init2() {
    14. printf("this is init2\n");
    15. }
    16. void on__exit1() {
    17. printf("this in on exit1\n");
    18. }
    19. void at__exit1() {
    20. printf("this in at exit1\n");
    21. }
    22. void on__exit2() {
    23. printf("this in on exit2\n");
    24. }
    25. void at__exit2() {
    26. printf("this in at exit2\n");
    27. }
    28. int main() {
    29. // _exit(1);
    30. on_exit(on__exit1, NULL);
    31. on_exit(on__exit2, NULL);
    32. atexit(at__exit1);
    33. atexit(at__exit2);
    34. printf("this is main\n");
    35. exit(1);
    36. return 0;
    37. }

    上面的函数执行结果如下所示:

    可以看到我们的代码被正常执行啦。

    但是_exit是一个系统调用,当执行这个方法的时候程序会被直接终止,我们看下面的代码:

    1. #include
    2. #include
    3. #include
    4. void __attribute__((destructor)) __exit1() {
    5. printf("this is exit1\n");
    6. }
    7. void __attribute__((destructor)) __exit2() {
    8. printf("this is exit2\n");
    9. }
    10. void __attribute__((constructor)) init1() {
    11. printf("this is init1\n");
    12. }
    13. void __attribute__((constructor)) init2() {
    14. printf("this is init2\n");
    15. }
    16. void on__exit1() {
    17. printf("this in on exit1\n");
    18. }
    19. void at__exit1() {
    20. printf("this in at exit1\n");
    21. }
    22. void on__exit2() {
    23. printf("this in on exit2\n");
    24. }
    25. void at__exit2() {
    26. printf("this in at exit2\n");
    27. }
    28. int main() {
    29. // _exit(1);
    30. on_exit(on__exit1, NULL);
    31. on_exit(on__exit2, NULL);
    32. atexit(at__exit1);
    33. atexit(at__exit2);
    34. printf("this is main\n");
    35. _exit(1); // 只改了这个函数 从 exit 变成 _exit
    36. return 0;
    37. }

     上面的代码输出结果如下所示:

    可以看到我们注册的函数和最终的析构函数都没有被执行,程序直接退出啦。

    2、花式退出

    除了上面的_exit函数之外,我们还可以使用其他的方式直接退出程序:

    1. #include
    2. #include
    3. #include
    4. #include
    5. void __attribute__((destructor)) __exit1() {
    6. printf("this is exit1\n");
    7. }
    8. void __attribute__((destructor)) __exit2() {
    9. printf("this is exit2\n");
    10. }
    11. void __attribute__((constructor)) init1() {
    12. printf("this is init1\n");
    13. }
    14. void __attribute__((constructor)) init2() {
    15. printf("this is init2\n");
    16. }
    17. void on__exit1() {
    18. printf("this in on exit1\n");
    19. }
    20. void at__exit1() {
    21. printf("this in at exit1\n");
    22. }
    23. void on__exit2() {
    24. printf("this in on exit2\n");
    25. }
    26. void at__exit2() {
    27. printf("this in at exit2\n");
    28. }
    29. int main() {
    30. // _exit(1);
    31. on_exit(on__exit1, NULL);
    32. on_exit(on__exit2, NULL);
    33. atexit(at__exit1);
    34. atexit(at__exit2);
    35. printf("this is main\n");
    36. syscall(SYS_exit, 1); // 和 _exit 效果一样
    37. return 0;
    38. }

    除了上面直接调用函数的方法退出函数,我们还可以使用内联汇编退出函数,比如在64位操作系统我们可以使用下面的代码退出程序:

    1. #include
    2. #include
    3. #include
    4. #include
    5. void __attribute__((destructor)) __exit1() {
    6. printf("this is exit1\n");
    7. }
    8. void __attribute__((destructor)) __exit2() {
    9. printf("this is exit2\n");
    10. }
    11. void __attribute__((constructor)) init1() {
    12. printf("this is init1\n");
    13. }
    14. void __attribute__((constructor)) init2() {
    15. printf("this is init2\n");
    16. }
    17. void on__exit1() {
    18. printf("this in on exit1\n");
    19. }
    20. void at__exit1() {
    21. printf("this in at exit1\n");
    22. }
    23. void on__exit2() {
    24. printf("this in on exit2\n");
    25. }
    26. void at__exit2() {
    27. printf("this in at exit2\n");
    28. }
    29. int main() {
    30. // _exit(1);
    31. on_exit(on__exit1, NULL);
    32. on_exit(on__exit2, NULL);
    33. atexit(at__exit1);
    34. atexit(at__exit2);
    35. printf("this is main\n");
    36. asm(
    37. "movq $60, %%rax;"
    38. "movq $1, %%rdi;"
    39. "syscall;"
    40. :::"eax"
    41. );
    42. return 0;
    43. }

    上面是在64位操作系统退出程序的汇编实现,在64为系统上退出程序的系统调用号为60。下面我们使用32位操作系统上的汇编实现程序退出,在32位系统上退出程序的系统调用号等于1:

    1. #include
    2. #include
    3. #include
    4. #include
    5. void __attribute__((destructor)) __exit1() {
    6. printf("this is exit1\n");
    7. }
    8. void __attribute__((destructor)) __exit2() {
    9. printf("this is exit2\n");
    10. }
    11. void __attribute__((constructor)) init1() {
    12. printf("this is init1\n");
    13. }
    14. void __attribute__((constructor)) init2() {
    15. printf("this is init2\n");
    16. }
    17. void on__exit1() {
    18. printf("this in on exit1\n");
    19. }
    20. void at__exit1() {
    21. printf("this in at exit1\n");
    22. }
    23. void on__exit2() {
    24. printf("this in on exit2\n");
    25. }
    26. void at__exit2() {
    27. printf("this in at exit2\n");
    28. }
    29. int main() {
    30. // _exit(1);
    31. on_exit(on__exit1, NULL);
    32. on_exit(on__exit2, NULL);
    33. atexit(at__exit1);
    34. atexit(at__exit2);
    35. printf("this is main\n");
    36. asm volatile(
    37. "movl $1, %%eax;"
    38. "movl $1, %%edi;"
    39. "int $0x80;"
    40. :::"eax"
    41. );
    42. return 0;
    43. }

    3、总结

    在本篇文章当中主要给大家介绍C语言当中一些与程序退出的骚操作,希望大家有所收获!

  • 相关阅读:
    关于瑞萨R7 的CANFD切换为经典CAN
    [node文件的上传和下载]一.node实现文件上传;二、Express实现文件下载;三、遍历下载文件夹下的文件,拼接成一个下载的url,传递到前端
    Azure + React + ASP.NET Core 项目笔记一:项目环境搭建(三)
    从傅里叶变换,到短时傅里叶变换,再到小波分析(CWT),看这一篇就够了(附MATLAB傻瓜式实现代码)
    nginx+tomcat集群,动静分离
    FiRa标准——蓝牙OOB技术规范(一)
    信息安全实验一:DES加密算法的实现
    QT--day3
    网安学习-Python安全开发
    0079__多线程私有数据pthread_key_create
  • 原文地址:https://blog.csdn.net/chengxuyuanlaow/article/details/130840488