CodeGolfのコードスニペット はコンパイラ爆弾として意図されており、main
は巨大な配列として宣言されています。私は次の(非爆弾)バージョンを試しました:
int main[1] = { 0 };
Clangでは正常にコンパイルされ、GCCでは警告のみが表示されるようです:
警告: 'main'は通常関数です[-Wmain]
結果のバイナリは、もちろんゴミです。
しかし、なぜコンパイルされるのでしょうか? C仕様でも許可されていますか?関連があると思うセクションには次のように書かれています。
5.1.2.2.1プログラムの起動
プログラムの起動時に呼び出される関数の名前はmainです。実装は、この関数のプロトタイプを宣言しません。戻り値の型intでパラメータなしで[...]、2つのパラメータ[...]で、または他の実装定義の方法で定義されます。
「その他の実装定義の方法」にはグローバル配列が含まれていますか? (私には、仕様はまだfunctionを参照しているようです)
そうでない場合、それはコンパイラ拡張機能ですか?または、ツールチェーンの機能で、他の目的に役立つものであり、フロントエンドから利用可能にすることに決めましたか?
これは、Cがmain
関数を必要としない「ホストされていない」環境または独立した環境を許可しているためです。これは、名前main
が他の用途のために解放されることを意味します。これが、言語自体がそのような宣言を許可する理由です。ほとんどのコンパイラーは両方をサポートするように設計されており(違いは主にリンクの実行方法です)、したがって、ホストされた環境では違法となるコンストラクトを許可しません。
標準で参照するセクションはホスト環境を指し、自立型に対応するものは次のとおりです。
独立した環境(Cプログラムの実行はオペレーティングシステムの利点なしで実行される可能性がある)では、プログラムの起動時に呼び出される関数の名前とタイプは実装定義です。独立したプログラムで利用できるライブラリ機能は、4節で必要とされる最小限のセット以外は、実装定義です。
その後、通常のようにリンクすると、リンカーは通常、シンボルの性質(持っている型、または関数や変数であっても)についてほとんど知識を持たないため、うまくいきません。この場合、リンカはmain
への呼び出しをmain
という名前の変数に喜んで解決します。シンボルが見つからない場合、リンクエラーが発生します。
いつものようにリンクしている場合、ホストされた操作でコンパイラを使用しようとしており、付録J.2に従って未定義の動作を意味するはずなので、main
を定義していません。
次の状況では、動作は未定義です。
- ...
- ホスト環境のプログラムは、指定された形式のいずれかを使用してmainという名前の関数を定義しません(5.1.2.2.1)
独立した可能性の目的は、(たとえば)標準ライブラリまたはCRT初期化が与えられていない環境でCを使用できるようにすることです。これは、main
が呼び出される前に実行されるコード(Cランタイムを初期化するCRT初期化)が提供されない可能性があり、それを自分で提供することが期待されることを意味します(そして、main
またはそうしないこともあります)。
メイン配列でプログラムを作成する方法に興味がある場合: https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html 。サンプルのソースには、機械命令で満たされたmain
というchar(および後でint)配列が含まれています。
主な手順と問題は次のとおりです。
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!
問題は、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プログラムであり、コンパイラはそれを書くことを止めることはできません。
main
は-コンパイル後-他の多くのオブジェクトオブジェクト(グローバル関数、グローバル変数など)の別のシンボルです。
リンカは、そのタイプに関係なく、シンボルmain
をリンクします。実際、リンカはシンボルのタイプをまったく見ることができません(canを参照してください。ただし、.text
--セクションにはありませんが、気にしません;))
Gccを使用する場合、標準のエントリポイントは_startであり、ランタイム環境の準備後にmain()を呼び出します。そのため、整数配列のアドレスにジャンプします。これにより、通常、命令、セグメンテーション違反、またはその他の不正な動作が発生します。
これはもちろん、C標準とは何の関係もありません。
適切なオプションを使用しないためにコンパイルします(そして、リンカーはシンボルのnameのみを考慮し、typeではないために動作します)。
$ 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]
const int main[1] = { 0xc3c3c3c3 };
これはコンパイルされ、x86_64で実行されます...単に何も返しません:D