こんにちは!開発現場で日々、コードとインフラに向き合っている先輩エンジニアです。
今回は、C/C++をWindows環境で開発する際になくてはならない「MinGW-w64 / MSYS2」を取り上げます。
「いざアプリケーションを作って配布しようとしたら、ただの『Hello World』なのに数十メガバイトもある巨大なバイナリができてしまい、絶望した…」
そんな経験はありませんか?
今回は、MinGW-w64を使って生成されるバイナリサイズを劇的に、限界まで小さくする最適化テクニックを徹底解説します。これをマスターすれば、軽快でスマートな配布用バイナリを自在に作り出せるようになりますよ。
—
1. MinGW-w64 / MSYS2 とは何か?(ツールの本質を理解する)
Windows環境でネイティブなC/C++アプリケーションを動かすためには、Windowsのシステムコール(Win32 API)を叩けるコンパイラが必要です。
- MinGW-w64: Windows上で動く、GCC(GNU Compiler Collection)の64bit/32bit対応ポート。純粋なWindowsネイティブバイナリ(`.exe`)を生成します。
- MSYS2: 単なるコンパイラだけでなく、Linuxでおなじみのパッケージ管理システム(`pacman`)やbashシェルをWindows上に持ち込み、快適な開発環境を提供するプラットフォームです。
この2つを組み合わせることで、Linuxの強力なツールチェインをそのままWindowsの手元に再現しつつ、依存関係の少ない軽量なポータブルバイナリを作ることができます。
—
2. 導入と基礎セットアップ(最短で迷わず環境を作る)
まずは、土台となるMSYS2のインストールと、必要最小限のコンパイラ環境の構築を行います。
ステップ1: MSYS2のインストール

ステップ2: ツールチェインのインストール
インストールが完了すると「MSYS2 MSYS」という専用のターミナルが立ち上がります。ここがあなたのコックピットです。以下のコマンドを叩いて、パッケージデータベースを最新化し、MinGW-w64のツールチェインを導入します。
パッケージデータベースとコアシステムの同期・更新
pacman -Syu
(※途中でウィンドウを閉じるように指示された場合は、指示に従って一度閉じ、再度「MSYS2 MSYS」を立ち上げて同じコマンドをもう一度実行してください)
続いて、64bitネイティブアプリケーションをビルドするためのGCCとmakeツールをインストールします。
64bit用GCCコンパイラ、make、デバッグツールを一括インストール
pacman -S –needed base-devel mingw-w64-ucrt-x86_64-toolchain
インストールが終わったら、Windows側の環境変数(PATH)に `C:\msys64\ucrt64\bin` を通しておきましょう。これで、通常のWindowsのコマンドプロンプトやPowerShellからもGCCが使えるようになります。
—
3. 精度高い「Hello World」で動作確認
環境が正しく構築されているか、おなじみのコードで確認しましょう。
任意のディレクトリに `main.c` というファイルを作成し、以下のコードを記述します。
include
int main(void) {
// 開発環境の無事を確認する挨拶
printf(“Hello, MinGW-w64 World!\n”);
return 0;
}
ターミナルを開き、以下のコマンドでコンパイルして実行してみましょう。
標準的な方法でコンパイル
gcc main.c -o hello.exe
実行
./hello.exe
出力結果: Hello, MinGW-w64 World!
無事に動きましたね!しかし、ここで生成された `hello.exe` のファイルサイズを確認してみてください。環境にもよりますが、数十KB〜数百KB、あるいは何も考えずにビルドするとデバッグ情報が含まれて大きくなっていることがあります。
ここからが本題です。このバイナリを限界まで削ぎ落とす技術を解説します。
—
4. バイナリサイズを劇的に小さくする最適化テクニック
「なぜバイナリが大きくなるのか?」その理由は主に以下の3点です。
1. 実行速度優先の最適化になっている(コードのインライン展開などによる肥大化)
2. デバッグ用シンボル(関数名や変数名、行番号など)が埋め込まれている
3. 不要なランタイム依存やセクションが含まれている
これらを一つずつ排除し、極限までスリム化していきましょう。
テクニック1: `-Os` フラグによるサイズ優先コンパイル
GCCには最適化レベルを指定するフラグがあります。速度を優先する `-O3` ではなく、コードサイズを最小限に抑えることを最優先する `-Os` を使用します。
コードサイズ最小化(-Os)を指定してコンパイル
gcc -Os main.c -o hello_os.exe
これにより、CPUキャッシュ効率とメモリフットプリントのバランスを取りながら、機械語命令レベルでコードが切り詰められます。
テクニック2: `-s` フラグによるシンボルの完全削除
コンパイル時に `-s` オプションを付与すると、バイナリからデバッグシンボル(関数名やソースコードの参照情報など、逆アセンブルやデバッグに必要なメタデータ)が完全に削ぎ落とされます。
デバッグシンボルを一切含めずにコンパイル
gcc -Os -s main.c -o hello_stripped.exe
※これにより、通常のデバッガー(GDBなど)での詳細な関数名追跡ができなくなりますが、エンドユーザーに配布する成果物としては必須の処理です。
テクニック3: `strip` コマンドによる後からの外科手術
もし既にコンパイルしてしまったバイナリがある場合でも、`strip` というツールを使えば、後から不要なシンボルやセクションを外科手術的に切り取ることができます。
まず通常通り(あるいは-Osで)ビルド
gcc -Os main.c -o hello.exe
後からバイナリを「裸」にする(シンボルや再配置情報を削除)
strip –strip-all hello.exe
テクニック4: 未使用コードの自動削除(Garbage Collection)
C/C++では、標準ライブラリや自作の関数を書いた際、実際には呼び出していない関数までバイナリにリンクされてしまうことがあります。これを防ぐために、リンカに対して「使われていない関数やデータは捨てる」よう指示を出します。
これには、コンパイル時とリンク時に特別なフラグを組み合わせます。
関数やデータごとに独立したセクション(-ffunction-sections, -fdata-sections)に分け、
リンカ側で不要なものを完全に切り捨てる(-Wl,–gc-sections)
gcc -Os -ffunction-sections -fdata-sections main.c -Wl,–gc-sections -o hello_gc.exe
仕上げにstripをかける
strip –strip-all hello_gc.exe
この組み合わせにより、巨大なサードパーティ製ライブラリを静的リンクした際でも、「実際にコード内で呼び出された関数・変数だけ」が最終的な実行ファイルに残り、サイズが劇的に小さくなります。
—
5. 最適化の効果測定(実測値の比較)
上記のテクニックを適用すると、ファイルサイズがどれほど変わるのか、その推移を見てみましょう。
| ビルド方法 | コマンド例 | 概算サイズ(目安) |
| :— | :— | :— |
| デフォルト | `gcc main.c` | 約 100 KB 〜 130 KB |
| サイズ優先 | `gcc -Os main.c` | 約 80 KB |
| シンボル削除 | `gcc -Os -s main.c` | 約 30 KB 〜 40 KB |
| 完全最適化 | `gcc -Os -ffunction-sections -fdata-sections -Wl,–gc-sections main.c` + `strip` | 数 KB (極限まで縮小) |
※C言語の標準的な `Hello World` の場合。大規模なアプリケーションやC++のSTL(標準テンプレートライブラリ)を用いた場合、この最適化の恩恵はメガ単位で現れます。
—
おわりに
今回は、MinGW-w64 / MSYS2環境におけるバイナリサイズ削減の極意を解説しました。
開発中はデバッグ情報の入った大きなバイナリを使い、リリースや配布のフェーズでは `-Os`、`–gc-sections`、そして `strip` を駆使して極限までスリム化する――このワークフローをビルドスクリプト(MakefileやCMakeなど)に組み込んでおくだけで、あなたの作るソフトウェアの品質とプロとしての洗練度は一段と跳ね上がります。
これをマスターすれば、毎日のコーディングやビルドの成果物を見るのがもっと楽しくなりますよ。ぜひ、あなたのプロジェクトでも試してみてください!