Featured image of post C++ Way

C++ Way

背景

  • 高速なアプリケーションを動かすにはC/C++が必須になる
  • Pythonで書くと遅くなり、重い腰を上げてC++をやることになったの
  • そのためにC++やCmakeの基礎的な疑問をAIにぶつけたので、答えをまとめた

全体像

C/C++ では、プログラムはふつう 1回で全部完成するのではなく、下の3つの流れで作られる。

  1. 前処理
  2. コンパイル
  3. リンク

.h.c の役割

.h

宣言を書く場所

例:

1
int add(int a, int b);

上記のように、

  • 関数名
  • 引数の型
  • 戻り値の型

を他のファイルに知らせ。

.c

実装を書く場所

例:

1
2
3
int add(int a, int b) {
    return a + b;
}

上記のように、実際の処理の中身を書く。


なぜ .h.c を分けるのか

小さいプログラムなら、1ファイルに全部書いても動く。 でも複数ファイルになると、ある .c から別の .c の関数を使いたくなる。

たとえば

  • main.c
  • add.c
  • sub.c

があるとき、main.cadd()sub() の存在を知らないと呼べない。

そのために .h を使って、複数の .c ファイルで共有する宣言をまとめるということ。

つまり .h は、

複数ファイルのための共通の説明書ということ。


#include の意味

1
#include "calc.h"

は、

その場所に calc.h の中身を貼り付ける

と思えばOK。

なので main.c#include "calc.h" があると、コンパイラは main.c だけでなく、そこに貼られた calc.h の内容も見る。

< >" "

  • #include <stdio.h> 標準ライブラリなど、システム側のヘッダを探す
  • #include "calc.h" 自分のプロジェクト内のヘッダを探す

#include は前処理

#include はコンパイルそのものではなく、前処理

前処理の例

  • #include
  • #define
  • #ifndef

コンパイル

  • .c をそれぞれ .o にする

リンク

  • .o 同士をつないで実行ファイルにする

コンパイルとリンクの違い

たとえば

  • main.c
  • add.c
  • sub.c

があるとき、

1
2
3
gcc -c main.c   # main.o
gcc -c add.c    # add.o
gcc -c sub.c    # sub.o

のように、.c は別々にコンパイルされる。

そのあとで

1
gcc main.o add.o sub.o -o app

として、最後に1つの実行ファイルへまとめる。

つまり、

  • .c 同士が自動で1つのソースになるわけではない
  • まず別々に .o を作る
  • 最後にリンクして1つにする

なぜ main.c のコンパイル時に add.c の中身を見ないのか

gcc -c main.c の入力は基本的に main.c と、その中で #include されたものだけ す。

だからコンパイラは、main.c をコンパイルするときに add.c を勝手には見ない。

その代わり、add() の宣言が .h にあれば、

  • add という関数がある
  • 引数は int, int
  • 戻り値は int

と分かるので、main.c はコンパイルできる。


コンパイル時に実体はなくてもいいのか

はい、普通の関数ならコンパイル時には宣言があれば進められる

つまり

  • コンパイル時: 宣言が必要
  • リンク時: 実装が必要

たとえば main.cadd() を呼んでいても、コンパイル時には

1
int add(int a, int b);

が分かっていればよいということ。

でも最終的にリンクするときに add() の本体がないと、

  • undefined reference

のようなエラーになる。

つまり、

コンパイラは「呼び方」を確認し、リンカは「本体の所在」を確認する

実装ファイルが自分のヘッダを include する理由

たとえば

1
2
3
4
5
6
// sub.c
#include "calc.h"

int sub(int a, int b) {
    return a - b;
}

のように、sub.c でもヘッダを include することがある。

これは sub.csub() を使うためではなく、

ヘッダに書いた宣言と、自分の実装が一致しているか確認するため

つまり、

  • main.c が include する → 使うため
  • sub.c が include する → 宣言と実装の答え合わせのため

include guard とは何か

ヘッダの先頭によくある

1
2
3
4
5
6
#ifndef ADD_H
#define ADD_H

...

#endif

これは include guard

これは、

同じヘッダが複数回読み込まれても、1回だけ有効にする

ための仕組み。

これは文法上の絶対ルールではなく、昔からの定番の書き方。


.o, .a, .so, a.out

.o

オブジェクトファイル 各 .c / .cpp をコンパイルした結果

.a

静的ライブラリ 複数の .o をまとめたもの

.so

共有ライブラリ(動的ライブラリ) これも元は .o から作るが、実行時に読み込まれる

a.out

実行ファイルのデフォルト名 gcc main.c のように -o を省略したときにできることがある


.a.so の違い

どちらも元は .o から作れる。

  • .a = 静的ライブラリ
  • .so = 共有ライブラリ

違いは「元の材料」ではなく、できあがったものの使われ方

  • .a はリンク時に実行ファイルへ取り込まれる
  • .so は実行時に外から読み込まれる

ar rcs の意味

1
ar rcs libmylib.a foo.o bar.o

は、.o をまとめて .a を作るarchiveコマンド。

  • r = 入れる / 置き換える
  • c = 新規作成
  • s = シンボル索引を作る

つまり

静的ライブラリを作って、リンクしやすい形に整える

ということ。

PICオプション

PIC とは何か

PIC = Position-Independent Code 位置独立コード。

これは、どのメモリアドレスに置かれても動きやすいコード

共有ライブラリ .so を作るときによく使う。

1
2
g++ -fPIC -c foo.cpp -o foo.o
g++ -shared -o libmylib.so foo.o

PICで大事なこと

  • -fPIC.o の作り方
  • .so はその .o から作る共有ライブラリ

です。

しかも、PIC な .o から .a を作ることもできます。 つまり -fPIC.a を禁止するものではなく、単に中身の .o の性質。

フォルダ構成

include / src / third_party

よくあるフォルダ構成は以下:

  • include/ = 公開ヘッダ
  • src/ = 実装(非公開)
  • third_party/ = 外部ライブラリ

SDKのパターン

  • include/ = 利用者に見せる API
  • src/ = SDK 作者の実装ソース
  • 配布物 = include/ + ビルド済みライブラリ
  • 利用者 = ヘッダを include して、そのライブラリにリンク

Makefile / CMake

今までのmain.cadd.csub.ccalc.hを使う場合は以下のようになる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
app: main.o add.o sub.o
	gcc main.o add.o sub.o -o app

main.o: main.c calc.h
	gcc -c main.c -o main.o

add.o: add.c calc.h
	gcc -c add.c -o add.o

sub.o: sub.c calc.h
	gcc -c sub.c -o sub.o

clean:
	rm -f *.o app

もしくはもっときれいにかける。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CC = gcc
CFLAGS = -Wall -Wextra

app: main.o add.o sub.o
	$(CC) main.o add.o sub.o -o $@

%.o: %.c calc.h
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f *.o app

CMakeの場合は以下になる。

1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.16)
project(calc C)

add_executable(app
    main.c
    add.c
    sub.c
)

まとめ

一言でまとめると、

C/C++ では、ヘッダに宣言、ソースに実装を書き、各ソースを別々にコンパイルし、最後にリンクでつなぐ。

さらにもう少しかみ砕くと、

  • .h は「こう使ってね」という説明書
  • .c / .cpp は実際の中身
  • #include は説明書を貼り付ける前処理
  • コンパイルは各ファイルを別々に処理
  • リンクはバラバラの結果を最後につなぐ

最後に超短く言うと:

  • 宣言を共有するために .h
  • 実装を書くために .c / .cpp
  • コンパイル.o
  • リンクで実行ファイル
  • .a.so はどちらも .o から作れるが、使い方が違う

参考文献

最終更新 Apr 18, 2026 23:10 +0900
Built with Hugo
テーマ StackJimmy によって設計されています。