Cでの_start()の使用法は何ですか?


125

同僚から、main()関数を書かなくてもCプログラムを書いて実行できることがわかりました。これは次のように行うことができます:

my_main.c

/* Compile this with gcc -nostartfiles */

#include <stdlib.h>

void _start() {
  int ret = my_main();
  exit(ret); 
}

int my_main() {
  puts("This is a program without a main() function!");
  return 0; 
}

次のコマンドでコンパイルします。

gcc -o my_main my_main.c nostartfiles

次のコマンドで実行します。

./my_main

いつこの種のことをする必要があるでしょうか?これが役立つであろう現実のシナリオはありますか?



7
プログラムの起動方法の内部動作の一部を示す古典的な記事:Linux用の本当にティーンらしいELF実行可能ファイルの作成に関するWhirlwindチュートリアル。これは、の細かい点_start()や、その他のその他のことについて説明している読み物ですmain()

1
C言語自体は_start、について、またはそれ以外のエントリポイントについては何も述べていませんmain(ただし、エントリポイントの名前は、自立(埋め込み)実装の実装定義です)。
キース・トンプソン2015年

回答:


107

シンボル_startは、プログラムのエントリポイントです。つまり、そのシンボルのアドレスは、プログラムの開始時にジャンプするアドレスです。通常、この名前の関数は、Cランタイム環境の起動コードを含む_startと呼ばれるファイルによって提供されcrt0.oます。それはいくつかのものをセットアップし、引数の配列にデータを入力し、argvそこにある引数の数を数え、そしてを呼び出しますmainmain復帰後、exit呼び出されます。

プログラムがCランタイム環境を使用したくない場合は、独自のコードをに提供する必要があります_start。たとえば、Goプログラミング言語のリファレンス実装では、スタックの魔法が必要な非標準のスレッドモデルが必要なため、そうしています。_start非常に小さなプログラムや、型破りなことをするプログラムを作成したい場合にも、独自のプログラムを提供すると便利です。


2
別の例は、独自の_startが定義されているLinuxの動的リンカー/ローダーです。
PPの

2
@BlueMoonしかし、それ_startはオブジェクトファイルcrt0.oにも由来します。
fuz 2015

2
標準が指定されていません@ThomasMatthews _start。実際、それmainが呼び出される前に何が起こるかを指定するのではなく、が呼び出されたときに満たす必要がある条件を指定するだけmainです。エントリポイント_startが昔にさかのぼるというのは、もっと慣例です。
fuz 2015

1
「Goプログラミング言語のリファレンス実装は、非標準のスレッドモデルが必要なため、そうしています」crt0.oはC固有です(crt-> Cランタイム)。他の言語での使用を期待する理由はありません。そして、Goのスレッドモデルは完全に標準に準拠しています
Steve Cox

8
@SteveCoxこの方法で言語を実装する方が簡単であるため、多くのプログラミング言語はCランタイムの上に構築されています。Goは通常のスレッドモデルを使用しません。ヒープに割り当てられた小さなスタックと独自のスケジューラを使用します。これは確かに標準のスレッドモデルではありません。
fuz 2015

45

一方でmain、プログラマの観点から、プログラムのエントリポイントである、_startOSの視点(あなたのプログラムがOSから開始された後に実行される最初の命令)から通常のエントリポイントがあります

典型的なCプログラム、特にC ++プログラムでは、実行がメインになる前に多くの作業が行われています。特にグローバル変数の初期化のようなもの。 ここでは、メイン間が再び終了した後_start()main()およびメインが終了した後に行われているすべての事柄についての適切な説明を見つけることができます(以下のコメントを参照)。
そのために必要なコードは通常、コンパイラの作成者が起動ファイルで提供しますが、フラグ–nostartfilesを使用して基本的にコンパイラに通知します。開始"。

これは必要な場合があり、組み込みシステムでよく使用されます。たとえば、OSがなく、グローバルオブジェクトを初期化する前に、メモリシステムの特定の部分(キャッシュなど)を手動で有効にする必要がある場合などです。


グローバル変数はデータセクションの一部であるため、プログラムのロード中にセットアップされます(constの場合、それらはテキストセクションの一部であり、同じストーリーです)。_start関数は、それとはまったく無関係です。
Cheiron、2015

@Cheiron:申し訳ありませんが、私の誤解c ++では、グローバル変数は内部で実行されるコンストラクター_start()(または実際にはそれによって呼び出される別の関数)によって初期化されることが多く、多くのベアメタルプログラムでは、フラッシュからRAMにすべてのグローバルデータを明示的にコピーしますまず、これもで発生します_start()が、この質問はC ++やベアメタルコードに関するものではありません。
MikeMB 2015

1
独自のを提供するプログラムでは、_start特別な手順を実行しない限りCライブラリは初期化されないことに注意してください。このようなプログラムから非同期信号に対して安全でない関数を使用するのは安全ではない可能性があります。(という公式の保証はありません任意のライブラリ機能が動作しますが、彼らは故障に自分の道から出て行かなければならないと思いますので、非同期シグナルセーフ機能は、すべての任意のグローバルデータを参照することはできません。)
zwol

@zwolは部分的にしか正しくありません。たとえば、このような関数はメモリを割り当てる場合があります。の内部データ構造mallocが初期化されていない場合、メモリの割り当てに問題があります。
fuz 2015

1
@FUZxxlとは言っても、非同期シグナルセーフ関数変更許可されておりerrno(たとえばreadwrite非同期シグナルセーフであり、設定できますerrno)、スレッドごとのerrno場所がいつ割り当てられるかによって、問題になる可能性があります。
zwol 2015

2

これは、以前の プログラム起動時何が起こるかについての概要ですmain。特に、それが示して__startあり、実際のエントリポイント OSの観点から、あなたのプログラムへ。

これは、命令ポインタがプログラムでカウントを開始する最初のアドレスです。

そこでのコードは、いくつかのハウスキーピングを実行するためにいくつかのCランタイムライブラリルーチンを呼び出し、次にを呼び出してmainから、物事を停止し、返さexitれたすべての終了コードで呼び出しますmain


写真は千の言葉に値します:

Cランタイム起動図


PS:この回答は、SOがこの質問の複製として有用に終了した別の質問から移植されました。


クロスポストして、優れた分析と見栄えの良い写真を保存します。
ulidtko

1

いつこの種のことをする必要があるでしょうか?

プログラムに独自の起動コードが必要な場合。

mainCプログラムの最初のエントリではなく、_startカーテンの後ろの最初のエントリです。

Linuxでの例:

_start: # _start is the entry point known to the linker
    xor %ebp, %ebp            # effectively RBP := 0, mark the end of stack frames
    mov (%rsp), %edi          # get argc from the stack (implicitly zero-extended to 64-bit)
    lea 8(%rsp), %rsi         # take the address of argv from the stack
    lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
    xor %eax, %eax            # per ABI and compatibility with icc
    call main                 # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main

    mov %eax, %edi    # transfer the return of main to the first argument of _exit
    xor %eax, %eax    # per ABI and compatibility with icc
    call _exit        # terminate the program

これが役立つであろう現実のシナリオはありますか?

もしそうなら、私たち自身のものを実装してください_start

はい、私が使用したほとんどの商用組み込みソフトウェアでは_start、特定のメモリとパフォーマンスの要件に関して独自に実装する必要があります。

もしそうなら、main関数をドロップして別のものに変更してください:

いいえ、それを行うメリットはありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.