2016-01-13 84 views
44

我看到a snippet of code on CodeGolf這是一個編譯器炸彈,其中main被聲明爲一個巨大的數組。我嘗試以下(無彈)版本:爲什麼將main聲明爲數組編譯?

int main[1] = { 0 }; 

這似乎編譯罰款鏘之下,只有在GCC警告:

警告:「主」通常是一個函數[ - Wmain]

生成的二進制文件當然是垃圾文件。

但爲什麼它編譯?它是否被C規範允許?我認爲相關的部分說:

5.1.2.2.1計劃啓動

稱爲在程序啓動的功能被命名爲主力。該實現沒有聲明這個函數的原型。它應該用int類型的返回類型定義,並且不帶任何參數或者帶有兩個參數,或者以某種其他實現定義的方式定義。

「某些其他實現定義的方式」是否包含全局數組? (在我看來,該規範仍然指代函數。)

如果不是,它是一個編譯器擴展嗎?或者是工具鏈的一個特徵,用於其他目的,並且他們決定通過前端使其可用。

+1

它**不**編譯。 ISO C禁止零大小的數組。 – Jens

+7

C規範不允許。編譯器通常會執行規範未涵蓋的內容。 –

+0

相關問題:[一個全局變量的程序如何調用main而不是主函數?](http://stackoverflow.com/q/32851184/1708801)。我想也受到了一個codegolf問題的啓發。 –

回答

30

這是因爲C允許「非託管」或獨立環境,它不需要main函數。這意味着名稱main被釋放用於其他用途。這就是爲什麼這樣的語言允許這樣的聲明。大多數編譯器都支持這兩種(主要區別在於鏈接是如何完成的),因此他們不允許在託管環境中構建非法的構造。

你指的是在標準是指宿主環境中的部分中,獨立式的對應是:

在獨立環境中

(其中C程序執行可發生而無需操作系統的任何 益處),程序 啓動時調用的函數的名稱和類型是實現定義的。除了第4條所要求的最低限度設置以外,獨立式 程序可用的任何圖書館設施都是實施定義的。

如果然後將其鏈接像往常一樣,將走壞,因爲鏈接器通常具有對符號的本質知之甚少(它有什麼類型的,甚至如果它是一個函數或變量)。在這種情況下,鏈接器將愉快地將對main的調用解析爲名爲main的變量。如果找不到符號,將導致鏈接錯誤。

如果你像往常一樣鏈接它,你基本上試圖在宿主操作中使用編譯器,然後不定義main,因爲你應該意味着根據附錄J的未定義行爲。2:

行爲是在以下情況下未定義:

  • ...
  • 計劃在託管環境中沒有定義使用的特定形式之一 命名 主要 功能(5.1.2.2.1)

自支撐possibil的目的ity可以在沒有給出標準庫或CRT初始化的環境中使用C.這意味着,在main之前運行的代碼被稱爲(這是CRT初始化初始化C運行時)可能不提供,你預計將提供自己(和你可能決定一個main或可決定不) 。

+0

這個編譯和鏈接罰款(以及警告)與gcc 4.9.3上cygwin:'int f(int argc,char ** argv) { \t return 0; } char * main =(char *)f;' –

+0

@ PeterA.Schneider但是,如果它運行良好,它只是純粹的運氣。 CRT-init會嘗試調用'main',這是指針存儲的地方,而不是它指向的地方。 – skyking

+0

它鏈接,但段錯誤。順便說一句,我不認爲這個問題與「獨立」有很大關係。例如,下面的代碼在VS13中編譯和鏈接(到一個dll):''''''''''''''''''main main = 0; } } '。這是相當主要(和主要在C#中)不是關鍵字,C連接器是愚蠢的,錯誤,簡單。 –

7

main是 - 在許多人一樣(全局函數,全局變量等)的對象文件只是一個符號 - 編譯後。

連接器將連接符號main無論其類型。事實上,鏈接器無法看到所有符號的類型(他可以看到,這是不是在.text -section然而,但他並不在乎;))

使用gcc,標準入口點是_start,它在準備運行時環境後又調用main()。所以它會跳轉到整型數組的地址,這通常會導致錯誤的指令,段錯誤或其他不良行爲。

這一切當然是無關與C標準。

+0

我作爲skyking的回覆鏈接發表評論的最小例子,但segfaults。任何調整,使內聯彙編程序或類似的工作呢? –

+0

@ PeterA.Schneider它是segfaults,因爲它會跳轉到指針的_address_而不是它的內容。 – Ctx

+0

謝謝!我想我仍然期望這些工具鏈的C前端拋出,即使鏈接器在看到目標文件時並不關心。 –

2

它只編譯,因爲你不使用正確的選項(和作品,因爲接頭有時只關心名稱符號,而不是他們)。

$ gcc -std=c89 -pedantic -Wall x.c 
x.c:1:5: warning: ISO C forbids zero-size array ‘main’ [-Wpedantic] 
int main[0]; 
    ^
x.c:1:5: warning: ‘main’ is usually a function [-Wmain] 
+2

它仍然編譯和鏈接。唯一的區別是它警告你'main'通常是一個函數(然後繼續並鏈接)。 – skyking

+0

@skyking你想編譯/鏈接失敗?然後添加'-Werror'。 – Jens

+0

但是其他(其他)有效的C程序也無法編譯。 – skyking

20

如果你有興趣如何創建主陣列方案:https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html。那裏的示例源只包含一個名爲main的char(以及後面的int)數組,其中充滿了機器指令。

的主要步驟和問題是:

  • 從GDB存儲器轉儲獲得一個主要功能的機器指令,並在main[]可執行文件複製到陣列
  • 標籤中的數據通過聲明它常量(數據顯然是可寫或可執行的)
  • 最後詳細信息:更改實際字符串數據的地址。

將所得的C代碼僅僅是

const int main[] = { 
    -443987883, 440, 113408, -1922629632, 
    4149, 899584, 84869120, 15544, 
    266023168, 1818576901, 1461743468, 1684828783, 
    -1017312735 
}; 

但導致一個可執行程序64位PC上:

$ gcc -Wall final_array.c -o sixth 
final_array.c:1:11: warning: ‘main’ is usually a function [-Wmain] 
const int main[] = { 
     ^
$ ./sixth 
Hello World! 
5

的問題是,main不是保留標識符。 C標準只說在託管系統中通常有一個稱爲main的函數。但標準中的任何內容都不能阻止您爲了其他惡意目的而濫用相同的標識符。

GCC會給你一個滿意的警告「main通常是一個函數」,暗示爲其他不相關的目的使用標識main不是一個好主意。


傻例如:

#include <stdio.h> 

int main (void) 
{ 
    int main = 5; 
    main: 

    printf("%d\n", main); 
    main--; 

    if(main) 
    { 
    goto main; 
    } 
    else 
    { 
    int main (void); 
    main(); 
    } 
} 

這個程序將反覆打印號碼5,4,3,2,1,直到它得到一個堆棧溢出和崩潰(不要在家裏嘗試這個) 。不幸的是,上述程序是一個嚴格符合C標準的程序,編譯器無法阻止您編寫它。

相關問題