Menu

C言語のプログラムをコンパイルして実行する方法(gcc を使って手順どおりに)

.c ファイルを動くプログラムに変える方法: gcc コマンド、プリプロセス・コンパイル・リンクが実際に行っていること、最初の日から使う価値のあるフラグ、そして失敗したときのエラーの読み方。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

C言語はコンパイル型の言語です。コンパイラがソースをあなたのCPUとオペレーティングシステム向けの機械語に翻訳するまで、何も動きません。その手順はコマンド1つですが、それが何をしているのかを知ると、ほとんどのエラーメッセージが謎から当たり前のものに変わります。

2つのコマンド

これを hello.c として保存します:

#include <stdio.h>

int main(void) {
    printf("Hello, C!\n");
    return 0;
}

そして、同じフォルダで:

gcc hello.c -o hello
./hello

最初のコマンドがコンパイルし、2つ目が結果を実行します。Windows ではコマンドプロンプトで hello、PowerShell では .\hello が実行コマンドになります - ./ の接頭辞はありません。Windows はデフォルトでカレントディレクトリを検索し、Unix のシェルは意図的にそうしないからです。

-o hello を省くと、gcc は出力を a.out(または a.exe)と名付けます。古いチュートリアルが ./a.out で終わっているのはそのためです。出力には常に名前を付けましょう。4文字のコストで混乱を防げます。

こちらは同じプログラムをブラウザのエディタで実行するもので、両方の手順を代わりにやってくれます:

「コンパイル」が実際に行っていること

gcc hello.c -o hello は1つの手順に見えます。実際には4つあり、それぞれが独自の種類のエラーで失敗し得ます。

1. プリプロセス。 C言語がコンパイルされる前に、プリプロセッサが # で始まるすべての行を処理します。#include <stdio.h> は文字どおりそのヘッダーファイルの中身に置き換えられ、#define マクロは展開され、#ifdef ブロックは残されるか削除されます。出力は # 行が1つも残っていない大きなCファイルです。実際に見ることもできます:

gcc -E hello.c

これは何百行も表示します - そのほとんどは貼り付けられた stdio.h です。

2. コンパイル。 プリプロセス済みのCが解析され、型検査され、あなたのCPU向けのアセンブリに翻訳されます。構文エラー、型エラー、警告はここから来ます。

3. アセンブル。 アセンブリがオブジェクトファイルになります - 他のファイルの関数への呼び出し箇所にプレースホルダーを持つ機械語です。

gcc -c hello.c   # hello.o を生成し、リンクの前で止まる

4. リンク。 オブジェクトファイルがC標準ライブラリと縫い合わされ、すべてのプレースホルダーが実際のアドレスで埋められ、結果が実行ファイルになります。「undefined reference」エラーはここから来ます。

実務上の要点: コンパイルエラーはソースの行を指しますが、リンクエラーは指しません。リンクはすべての行がすでに受け入れられた後に起こるからです。

最初の日から使う価値のあるフラグ

素の gcc file.c -o prog は、危険なコードを黙って大量に受け入れます。4つのフラグがそれを変えます。

gcc -std=c17 -Wall -Wextra -g hello.c -o hello
  • -Wall はよくある警告を有効にします。名前に反して「すべての」警告ではありません - 妥当なセットです。
  • -Wextra はさらに追加し、未使用の仮引数や一部の比較の間違いを含みます。
  • -std=c17 は言語標準を固定し、コードがどのマシンでも同じ意味を持つようにします。古い教材に沿っているなら -std=c99 を使いましょう。
  • -g はデバッグ情報を残すので、何かがクラッシュしたときに gdblldb が実際のソース行を表示できます。

いずれ欲しくなるものがもう2つ:

  • -O2 はリリースビルド向けに最適化を有効にします。学習中はオフのままにしましょう。最適化されたコードはデバッグが難しく、警告も変わることがあります。
  • -fsanitize=address,undefined(gcc と clang)は、範囲外を読んだり未定義動作に当たったりした瞬間に、明確なメッセージとともにプログラムを中断させます。これはC言語における学習用フラグとして最も有用な1つです。

警告を自分で試してみましょう。このプログラムはコンパイルも実行もできますが、本物のバグが2つあります:

count には値が与えられていないので、そこにたまたまあったバイトが表示されます。-Wall を付けるとコンパイラがそう言ってくれます: 'count' is used uninitialized。C言語における警告はほとんどノイズではありません - まだ踏んでいないエラーとして扱いましょう。

複数のファイルをコンパイルする

実際のプログラムは複数のファイルに分かれます。すべてを gcc に渡しましょう:

gcc -std=c17 -Wall main.c utils.c -o myprog

あるいは、それぞれ別々にコンパイルして最後にリンクします。これはビルドシステムが行っていることで、1つのファイルを変更してもすべてを再ビルドせずに済みます:

gcc -c main.c      # -> main.o
gcc -c utils.c     # -> utils.o
gcc main.o utils.o -o myprog

一部のライブラリには明示的なリンクフラグが必要です。初心者が必ず出会うのは数学ライブラリです:

gcc calc.c -o calc -lm

-lm がないと、math.hsqrt を使ってもコンパイルは通り、リンク時に undefined reference to sqrt で失敗します - 宣言はヘッダーにありましたが、コードは誰も要求しなかったライブラリの中にあったのです。

エラーを読む

C言語のエラーは簡潔ですが一貫しています。3つの例で、最初のうちに目にするもののほとんどをカバーできます。

セミコロンの抜けは、コンパイラが読み進めるため次の行で報告されます:

hello.c:5:5: error: expected ';' before 'return'

修正すべきは5行目ではなく4行目です。エラーが問題なさそうな行を指しているときは、いつでもその1つ上を確認しましょう。

ヘッダーの抜けは、明らかに正しく綴った関数についての謎のように見えます:

hello.c:4:5: warning: implicit declaration of function 'printf'

これはコンパイラが printf の宣言を一度も見ていないという意味で、#include <stdio.h> を忘れています。C99 以降ではこれは警告ではなくエラーです。

リンクの失敗には行番号がまったくありません:

/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x1a): undefined reference to `helper'

コンパイラは helper がどこかに存在するというあなたの言葉を信じました。リンカが探してみたら、なかったのです。定義を書いていないか、綴りが違うか、それを含むファイルを gcc に渡し忘れたかのいずれかです。

常に最初のエラーを直しましょう。C言語のエラーは連鎖します - 1行の誤りが20個のメッセージを生み、最初の1つを直すとそのうち19個が消えます。

終了コード

mainreturn 0 は飾りではありません。プログラムの終了ステータスであり、シェルがそれを読めます:

実行後、macOS/Linux では echo $?(Windows では echo %errorlevel%)がその数値を表示します。スクリプトやビルドツールは、続行するかどうかをそれで判断します。この規約は絶対です: 0 は成功、非ゼロはあなたが選ぶエラーコードです。名前のほうが好みなら、stdlib.hEXIT_SUCCESSEXIT_FAILURE を定義しています。

C99 以降、main の末尾に return なしで到達することは return 0 として扱われます - しかし書いたほうが明確ですし、他のすべての関数では必須です。

よくある質問

C言語のプログラムはどうやってコンパイルして実行しますか?

コードを hello.c として保存し、gcc hello.c -o hello でビルドして ./hello で実行します(Windows では単に hello)。gcc が見つからない場合は、まずコンパイラをインストールする必要があります。

gcc の -o は何をしますか?

-o は出力ファイルの名前を指定します。gcc hello.c -o hellohello という実行ファイルを作ります。-o がないと gcc は a.out(Windows では a.exe)に書き出します。古いチュートリアルが ./a.out を実行しているのはそのためです。

gcc ではどのフラグを常に使うべきですか?

gcc -std=c17 -Wall -Wextra -g yourfile.c -o yourprog です。-Wall -Wextra は本物のバグを捕まえる警告を有効にし、-std=c17 は言語のバージョンを固定し、-g はデバッガがソースを表示できるようデバッグ情報を残します。リリースビルドで速度が欲しいときは -O2 を加えましょう。

C言語の「undefined reference to」はどういう意味ですか?

リンカのエラーです。コンパイラはその関数の呼び出しを受け入れましたが、関数の定義がどこにも見つかりませんでした。よくある原因は、名前のタイプミス、2つ目の .c ファイルのコンパイル忘れ、そして数学ライブラリ(-lm)をリンクせずに数学関数を使ったことです。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める