C
のmain()
関数なしで次のプログラムをコンパイルして実行しようとしています。次のコマンドを使用してプログラムをコンパイルしました。
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フラグは、コンパイラーがリンク時に標準の起動ファイルを使用できないようにします
プログラムで生成された 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
の戻り値で呼び出されるためです。 。
Cでは、関数/サブルーチンが呼び出されると、スタックは次のように(順番に)入力されます。
main()が開始点であるため、ELFは、最初に来る命令が最初にプッシュされるようにプログラムを構造化します。この場合、printfsはそうです。
これで、プログラムはreturn-address OR __end__
そして、実際には、そのスタックにあるものは何でも想定しています(__end__
)locationは返信先アドレスですが、残念ながらそうではないため、クラッシュします。