Menu

C++のコンパイル: g++でソースコードから実行ファイルへ

C++が.cppソースをネイティブな実行ファイルに変える仕組み: g++(またはclang++/MSVC)でコンパイルし、バイナリを実行し、うまくいかないときにコンパイラのエラーを読む方法。

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

まずコンパイル、それから実行

コンパイラをインストールしたので、C++プログラムを動かすのは2段階のサイクルになります。インタプリタがファイルを1行ずつ読むスクリプト言語とは違い、C++はまずソースコードをネイティブな実行ファイル(機械語からなる単独のバイナリ)にコンパイルし、その後あなたがそのバイナリを直接実行します。実行時に、プログラムとCPUの間にインタプリタが居座ることはありません。

この分離こそがC++が速い理由であり、構文ミスが実行の途中ではなくコンパイル時にあなたを止める理由でもあります。コンパイラは何かを生成する前に、ファイル全体をチェックします。

完結した例なら、このページ上でそのまま実行できます。下のエディタがコンパイルして実行してくれます。とはいえ、自分のマシンで何が起きるかを知っておく価値はあります。実際のプロジェクトはそこに存在するからです。

自分のマシンでのコンパイルと実行のサイクル

これをmain.cppとして保存したとしましょう:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello from the terminal" << endl;
    return 0;
}

ファイルがあるフォルダでターミナルを開き、コンパイラを呼び出します。もっとも一般的なコンパイラはg++(GCCの一部)です。ここで扱う内容についてはclang++もまったく同じように動きます:

g++ main.cpp -o main

コードが問題なくコンパイルされると、g++は何も出力せず、ソースの隣に新しいファイルを作ります。main(Windowsではmain.exe)という実行ファイルです。このバイナリは機械語であり、読める文章ではなく、あなたのOSとCPUに結びついています。では実行しましょう:

./main
Hello from the terminal

Windowsでは、同じターミナルからmain.exe(または単にmain)として実行します。macOSとLinuxの./は、シェルに「プログラムはPATHのどこかではなく、まさにこのフォルダの中にある」と伝えます。初心者が忘れがちな一手間で、忘れるとmaincommand not foundと言う理由に悩むことになります。

-oの働き(とa.out)

-oフラグは出力に名前を付けます。これを省いてもg++はコンパイルしますが、実行ファイルを既定の名前で書き出します。macOS/Linuxではa.out、Windowsではa.exeです。

g++ main.cpp        # produces a.out, not main
./a.out

これは多くの人が何度もつまずくところです。コンパイルしてエラーが出ず、./mainを実行するとNo such file or directoryが返る。バイナリの本当の名前がa.outだからです。何を実行しているかを正確に把握するため、常に-oを渡しましょう。

ソースを変更するたびに一度コンパイルします。そのあとは実行ファイルは独立していて、再コンパイルなしで./mainを百回でも実行できます。g++を再び実行するのは、.cppファイルを編集したあとだけです。

C++標準を選ぶ

C++にはバージョンがあります。C++11、C++14、C++17、C++20、C++23で、それぞれ言語機能を追加します。落とし穴は、コンパイラが選ぶ既定の標準が想定より古いことがある点で、そのため現代的なコードが理由もなくコンパイルに失敗することがあります。標準は-stdで明示的に指定しましょう:

g++ -std=c++17 main.cpp -o main
g++ -std=c++20 main.cpp -o main

これはC++17の機能(structured bindings)を使ったコードです。エディタでは問題なくコンパイルされますが、自分のマシンでは-std=c++17以降が必要です:

もし*'structured bindings' only available with '-std=c++17'*のようなエラーを見かけたら、直すべきはコードではなく、正しい-stdフラグを足すことです。このコース全体を通して、C++17以降を前提にしてください。

警告をオンにする

C++プログラムは問題なくコンパイルできても、なお間違っていることがあります。お願いすれば、コンパイラは実行時に牙をむく前に怪しいコードを指摘してくれます。-Wall -Wextraを足しましょう:

g++ -std=c++17 -Wall -Wextra main.cpp -o main

このプログラムを見てください。-Wallなしでもコンパイルできますが、本当のバグがあります。値を一度も与えていない変数を読んでおり、これは未定義動作です:

#include <iostream>
using namespace std;

int main() {
    int count;                 // never initialized
    cout << count << endl;     // reads garbage - undefined behavior
    return 0;
}

警告をオンにすると、コンパイラはこれを指摘します:

warning: 'count' is used uninitialized [-Wuninitialized]

初日から-Wall -Wextraでコンパイルする習慣をつけましょう。警告とは、コンパイラが無料でコードレビューをしてくれているようなものです。それを無視することが、見つけにくいバグが生き残る原因になります。ここでの修正は単純にint count = 0;です。

コンパイラのエラーを読む

g++があなたのコードを拒否すると、ファイル、行、そして何が問題かを示します。これらのメッセージを読めるようになることが、行き詰まりから抜け出すための半分です。定番がこれ、セミコロンの抜けです:

#include <iostream>
using namespace std;

int main() {
    cout << "Oops"      // no semicolon
    return 0;
}
main.cpp:5:5: error: expected ';' before 'return'
    5 |     return 0;
      |     ^~~~~~

いくつか押さえておきたい点があります。エラーは5行目を報告していますが、間違いは4行目にあります。コンパイラはセミコロンが足りないことに、次のトークンに到達して初めて気づくのです。ですから、エラーが問題なさそうな行を指している場合は、そのの行を確認しましょう。main.cpp:5:5はファイル、行、そして列です。名指しされた一点だけを直して再コンパイルし、当て推量はやめましょう。

コンパイラのエラーは、まだ何も実行されていないことを意味します。これは、プログラムが起動してから落ちる実行時エラーとは違います。プログラムが走り出す前、コンパイル時に間違いを捕まえられることは、C++の最大の強みの一つです。

動作確認プログラム

これをエディタで実行するか、main.cppとして保存し、自分のマシンでg++ -std=c++17 -Wall main.cpp -o mainしてから./mainしてください。3行すべてが表示されれば、ツールチェーンが最初から最後まで動いています:

ここには、まもなくきちんと出会う3つのものが登場します。int変数、vectorC++のサイズ可変な配列)、そして出力のためのcoutです。今のところは、プログラムがコンパイルでき、3行を順番に表示できれば十分です。

次: C++の構文

いくつかのプログラムをコンパイルして実行してきましたが、約物(記号類)については素通りしてきました。#includeの行、波かっこ、セミコロン、int main()、そして各行がなぜそういう見た目なのか、です。次のページではC++の構文を一つひとつ分解していきます。そうすれば、構造がただのお決まりの定型に感じられなくなり、意味を持ち始めます。

よくある質問

C++プログラムをコンパイルして実行するには?

コードをmain.cppとして保存し、そのフォルダでターミナルを開いてg++ main.cpp -o mainを実行すると実行ファイルが作られます。あとはmacOS/Linuxなら./main、Windowsならmain.exeで実行します。コンパイルは一度だけで、できあがったバイナリは何度でも実行できます。

g++の-oフラグは何をするものですか?

-oは出力ファイルの名前を指定します。g++ main.cpp -o hellohelloという名前の実行ファイルを作ります。-oを付けないとg++は既定でa.out(Windowsではa.exe)を使います。初心者が、作ったつもりのないファイルを実行してしまいがちなのはこのためです。

C++17やC++20のように特定の標準でC++をコンパイルするには?

-stdフラグを渡します: g++ -std=c++17 main.cpp -o main または -std=c++20。これを付けないとコンパイラは独自の既定値を使い、それが想定より古いことがあります。そのため、structured bindingsや<ranges>のような新しい機能は、標準を明示的に指定するまでコンパイルに失敗することがあります。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める