網站首頁 編程語言 正文
前言
在本篇文章當中主要給大家介紹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;
}
原文鏈接:https://www.cnblogs.com/Chang-LeHung/p/16804703.html
相關推薦
- 2023-03-25 Go語言基于viper實現apollo多實例快速_Golang
- 2022-03-29 Android中的xml解析介紹_Android
- 2022-03-26 正則表達式詳析+常用示例_正則表達式
- 2023-02-05 Android?開發與代碼無關技巧詳解_Android
- 2022-11-09 LyScript實現Hook隱藏調試器的方法詳解_python
- 2022-05-11 C++類繼承時的構造函數_C 語言
- 2022-04-01 PyPy?如何讓Python代碼運行得和C一樣快_python
- 2022-12-11 SQL中創建存儲過程_MsSql
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支