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

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

时间:2022-10-19 00:01:02浏览次数:88  
标签:__ exit1 exit2 void 程序 C语言 exit printf 退出

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、计算机系统基础、算法与数据结构)知识。

标签:__,exit1,exit2,void,程序,C语言,exit,printf,退出
From: https://www.cnblogs.com/Chang-LeHung/p/16804703.html

相关文章

  • C源代码程序如何转换为可执行文件
    #gcc生成intel风格的汇编gcc-S-masm=intelsample.c-osample.s一、C源代码文件如何成为可执行文件以helloworld.c为例,说明C语言源代码文件如何变成可执行文件的,经......
  • C语言实例3
    题目:在100内,一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?程序分析:在100以内判断,先将该数加上100后再开方,再将该数加上268后再开方,如......
  • C语言中的字符串、转义字符、注释
    一.字符串"helloworld!\n"现这种由双引号引起来的一串字符称为字符串面值,简称字符串。这里需要注意:字符串的结束标志是一个\0的转义字符。在计算字符串长度时\0是结束标志,......
  • 浅谈C语言中的变量
    一.定义变量的方法就是类型+变量名+数值,比如:inta=12;shortage=22;charch='w';二.变量的分类1.全局变量2.局部变量全局变量:定义在代码块({})之外的变量。局部......
  • 浅谈C语言中的常量(字面常量、const修饰的常变量、#define定义的标识符常量、枚举常量
    一.常量不会变的量就是常量,比如性别,血型等;二.常量的分类1.字面常量2.const修饰的常变量3.#define定义的标识符常量4.枚举常量1.字面常量2.const修饰的常变量    在......
  • C语言学习笔记-地址和指针
    1背景嵌入式开发的时候频繁使用指针数组,以前本科的时候学的都忘了,因此接着学习GD32固件库学习的机会系统的看了一下书,并做出整理2查找地址:&运算符&主要是给出变量的地......
  • 信息过载的时代,程序员如何破局?
    1、没有时间,怎么精进技术?工作了以后,除了工作之外会感觉其他时间少之又少。尤其好多带娃的朋友感触更深吧。那怎么提升?怎么精进技术呢?“禅定一下”,是真的很忙?还是朋友圈表现......
  • C语言学习2--10/18
    常量: 不会变化的数据1.“hello”字符串常量,‘A’字符常量,-10整型常量,3.14浮点常量2.#definePI3.14,宏定义,推荐3.consta=10 const关键字,被该关键字......
  • C语言基础-数组得初始化
    #include<stdio.h>intmain(){inta[10];intsize=sizeof(a)/sizeof(a[0]);//计算数组得大小for(inti=0;i<size;i++){a[i]=i*100;......
  • 实验1 C语言开发环境使用和数据类型、运算符、表达式
    Task1.c#include<stdio.h>intmain(){printf("O\n");printf("<H>\n");printf("II\n");return0;}Task1_1.c#include<stdio.h>intmai......