C语言程序可以没有main函数
原文鏈接:http://www.wufangbo.com/175871/
學習C語言的同學都知道,每個C程序要有一個main函數,程序從main函數開始執行,在main函數中結束。但事實上,C程序也可以沒有main函數,或者說自己可以指定入口函數。下面這篇文章介紹了如何實現這一過程。這篇文章轉自:http://www.codeweblog.com,作者不詳。學習這個內容對程序設計沒啥影響,但能更深入地了解程序編譯和鏈接的原理。
? ? 這篇文章主要介紹了c語言之沒有main函數的helloworld示例,本文分解了帶main函數的helloworld示例,從而分析出不需要main函數的helloworld示例,需要的朋友可以參考下。
? ? 幾乎所有程序員的第一堂課都是學習helloworld程序,下面我們先來重溫一下經典的C語言helloworld。
/* hello.c */?
#include <stdio.h>?
int main()?
{?
??? printf("helloworld!/n");?
??? return 0;?
}?
? ? 這是一個簡單的不能再簡單的程序,但它包含有一個程序最重要的部分,那就是我們在幾乎所有代碼中都能看到的main函數,我們編譯成可執行文件并查看符號表,過濾出里面的函數如下(為了方便查看我手動調整了grep的輸出的格式,所以和你的輸出格式是不一樣的)
$ gcc hello.c -o hello?
$ readelf -s hello | grep FUNC
Num: ? ? Value ? ? ? ? ? Size ? ?Type??? Bind ? ? Vis ? ? ? ? NdxName?
27: 000000000040040c ? ? ?0 ? ? ?FUNC??? LOCAL ? DEFAULT ? ? ?13 call_gmon_start?
32: 0000000000400430 ? ? ?0 ? ? ?FUNC??? LOCAL ? DEFAULT ? ? ?13 __do_global_dtors_aux?
35: 00000000004004a0 ? ? ?0 ? ? ?FUNC??? LOCAL ? DEFAULT ? ? ?13 frame_dummy?
40: 0000000000400580 ? ? ?0 ? ? ?FUNC??? LOCAL ? DEFAULT ? ? ?13 __do_global_ctors_aux?
47: 00000000004004e0 ? ? ?2 ? ? ?FUNC??? GLOBAL ?DEFAULT ? ? ?13 __libc_csu_fini?
48: 00000000004003e0 ? ? ?0 ? ? ?FUNC??? GLOBAL ?DEFAULT ? ? ?13 _start?
51: 0000000000000000 ? ? ?0 ? ? ?FUNC??? GLOBAL ?DEFAULT ? ? ?UND aliyunzixun@xxx.com@GLIBC_2.2.5?
52: 00000000004005b8 ? ? ?0 ? ? ?FUNC??? GLOBAL ?DEFAULT ? ? ?14 _fini?
53: 0000000000000000 ? ? ?0 ? ? ?FUNC??? GLOBAL ?DEFAULT ? ? ?UND aliyunzixun@xxx.com@GLIBC_?
58: 00000000004004f0 ? ? 137 ? ? FUNC??? GLOBAL ?DEFAULT ? ? ?13 __libc_csu_init?
62: 00000000004004c4 ? ? 21 ? ? ?FUNC??? GLOBAL ?DEFAULT ? ? ?13 main?
63: 0000000000400390 ? ? ?0 ? ? ?FUNC??? GLOBAL ?DEFAULT ? ? ?11 _init
? ? 大家都知道用戶的代碼是從main函數開始執行的,雖然我們只寫了一個main函數,但從上面的函數表可以看到還有其它很多函數,比如_start函數。實際上程序真正的入口并不是main函數,我們以下面命令對hello.c代碼進行編譯:
$ gcc hello.c -nostdlib?
/usr/bin/ld: warning: cannot find entrysymbol _start; defaulting to 0000000000400144
? ? -nostdlib命令是指不鏈接標準庫,報錯說找不到entry symbol _start,這里是說找不到入口符號_start,也就是說程序的真正入口是_start函數。
? ? 實際上main函數只是用戶代碼的入口,它會由系統庫去調用,在main函數之前,系統庫會做一些初始化工作,比如分配全局變量的內存,初始化堆、線程等,當main函數執行完后,會通過exit()函數做一些清理工作,用戶可以自己實現_start函數:
/* hello_start.c */?
#include <stdio.h>?
#include <stdlib.h>?
_start(void)?
{?
??? printf("hello world!/n");?
??? exit(0);?
}?
? ? 執行如下編譯命令并運行:
$ gcc hello_start.c -nostartfiles -ohello_start?
$ ./hello_start?
hello world!
? ? 這里的-nostartfiles的功能是Do notuse the standard system startup files when linking,也就是不使用標準的startupfiles,但是還是會鏈接系統庫,所以程序還是可以執行的。同樣我們查看符號表:
$ readelf -s hello_start | grep FUNC?
Num: ? ? ?Value ? ? ? ? ? Size ? ?Type ? ? Bind ? ? Vis ? ? ? ? ?NdxName?
20: ?0000000000400350 ? ? ?24 ? ? FUNC??? GLOBAL ? DEFAULT ? ? ? 10 _start?
21: ?0000000000000000 ? ? ?0 ? ? ?FUNC??? GLOBAL ? DEFAULT ? ? ? UND aliyunzixun@xxx.com@GLIBC_2.2.5?
22: ?0000000000000000 ? ? ?0 ? ? ?FUNC??? GLOBAL ? DEFAULT ? ? ? UND aliyunzixun@xxx.com@GLIBC_2.2.5
? ? 現在就只剩下三個函數了,并且都是我們自己實現的,其中printf由于只有一個參數會被編譯器優化為puts函數,在編譯時加-fno-builtin選項可以關掉優化。
? ? 如果我們在_start函數中去掉exit(0)語句,程序執行會出core,這是因為_start函數執行完程序就結束了,而我們自己實現的_start里面沒有調用exit()去清理內存。
? ? 好不容易去掉了main函數,這時又發現必須得有一個_start函數,是不是讓人很煩,其實_start函數只是一個默認入口,我們是可以指定入口的
/* hello_nomain.c */?
#include <stdio.h>?
#include <stdlib.h>?
int nomain()?
{?
??? printf("helloworld!/n");?
??? exit(0);?
}
采用如下命令編譯?
$ gcc hello_nomain.c -nostartfiles -enomain -o hello_nomain?
? ? 其中-e選項可以指定程序入口符號,查看符號表如下:
$ readelf -s hello_nomain | grep FUNC?
Num: ? ? Value ? ? ? ? ? ?Size ? ?Type ? ? ?Bind ? ? ? Vis ? ? ? NdxName?
20: ? 0000000000000000???? 0 ? ? ?FUNC??? ?GLOBAL ? ?DEFAULT ? ? aliyunzixun@xxx.com@GLIBC_2.2.5?
21: ? 0000000000000000???? 0 ? ? ?FUNC??? ?GLOBAL ? ?DEFAULT ? ? aliyunzixun@xxx.com@GLIBC_2.2.5?
22: ? 0000000000400350 ? ? 24 ? ? FUNC ? ? GLOBAL ? ?DEFAULT ? ? 10 nomain
?
? ? 對比hello_start的符號表發現只是將_start換成了nomain。
? ? 到這里我們就很清楚了,程序默認的入口是標準庫里的_start函數,它會做一些初始化工作,調用用戶的main函數,最后再做一些清理工作,我們可以自己寫_start函數來覆蓋標準庫里的_start,甚至可以自己指定程序的入口。
總結
以上是生活随笔為你收集整理的C语言程序可以没有main函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim 打造属于自己的 IDE
- 下一篇: lds文件分析