2017-02-19 130 views
69

我試圖在C中編譯並運行下面的程序,但沒有main()函數。我使用以下命令編譯了我的程序。編譯並運行沒有main()的程序在C中爲

gcc -nostartfiles nomain.c 

而編譯器在警告

/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400340 

行,沒問題。然後,我已經運行可執行文件(a.out),兩個printf語句打印成功,然後得到段錯誤

所以,我的問題是,成功執行打印語句後爲什麼分段錯誤?

我的代碼:

#include <stdio.h> 

void nomain() 
{ 
     printf("Hello World...\n"); 
     printf("Successfully run without main...\n"); 
} 

輸出:

Hello World... 
Successfully run without main... 
Segmentation fault (core dumped) 

注:

這裏,-nostartfiles GCC標誌鏈接

時防止編譯器使用標準的啓動文件
+32

我很驚訝這項作品。坦率地說,我認爲鏈接器的這種處理方式是錯誤的(或者至少是一件壞事):沒有入口點,所以鏈接器只是讓它從任何函數都很方便而產生幻覺。布萊什。 – imallett

+4

@imallett,至少該鏈接器足以引起人們對它的警告,並解釋它正在採取的回退行動!不過,你說得對,這可能更好,因爲錯誤而不僅僅是警告。 –

+0

你爲什麼不用main? –

回答

116

讓我們來看看你的程序產生的assembly

.LC0: 
     .string "Hello World..." 
.LC1: 
     .string "Successfully run without main..." 
nomain: 
     push rbp 
     mov  rbp, rsp 
     mov  edi, OFFSET FLAT:.LC0 
     call puts 
     mov  edi, OFFSET FLAT:.LC1 
     call puts 
     nop 
     pop  rbp 
     ret 

注意ret聲明。你的程序的入口點被確定爲nomain,一切正常。但是,一旦函數返回,它將嘗試跳入調用堆棧中的地址(未被填充)。這是非法訪問,並出現分段錯誤。

一個快速的解決辦法是打電話給exit()在程序結束(假設C11我們不妨標記功能_Noreturn):

#include <stdio.h> 
#include <stdlib.h> 

_Noreturn void nomain(void) 
{ 
    printf("Hello World...\n"); 
    printf("Successfully run without main...\n"); 
    exit(0); 
} 

事實上,現在你的函數的行爲很像一個常規的main函數,因爲從main返回後,exit函數調用main的返回值。

+6

我認爲有一些架構/操作系統組合,你可以從程序中「返回」; MS-DOS .COM可執行文件?無論如何,我們深入到特定於實現的行爲。 – pjc50

+4

@ pjc50 - 我們的確如此。儘管OP中的路徑建議使用Unix變體。加上某些體系結構和指令集的普及,我覺得在回答中提供生成的程序集的唯一原因。 – StoryTeller

+1

只是一個觀察。 '-nostartfiles'也可以使C庫不可用。如果不執行_C_啓動,則對_C_庫函數的後續調用可能會意外失敗。在Linux上,如果你想用'-nostartupfiles'和'-static'編譯,你可能會發現程序會出錯。像MUSL這樣的_C_庫不需要預先初始化,它們可以在這種環境下工作。 –

20

在C中,當函數/子程序調用堆棧被填充爲(按順序):

  1. 的參數,
  2. 返回地址,
  3. 局部變量, - >頂部的堆棧

的main()是在任何指令自帶第一會得到先推了,在這種情況下,用printfs是這樣的起點,ELF結構的程序。

現在,程序是有點截斷,沒有返回地址或__end__和INFACT它假定,無論是那裏的堆棧在上(__end__)的位置是返回地址,但遺憾的是它沒有因此崩潰。

+4

C標準定義了堆棧數據的順序嗎?我認爲這取決於系統架構 –

+1

這就是爲什麼我提到ELF(可執行文件和可鏈接文件格式),這是通過在所需的操作系統上針對特定ARCH類型進行交叉編譯而生成的。 –

+0

挑剔的是,即使在沒有堆棧的系統上,也可以使用ELF格式。這種系統的一個例子是使用Codewarrior編譯器生成ELF鏈接器文件的[Freescale RS08](https://en.wikipedia.org/wiki/Freescale_RS08)。 – Lundin