C言語

分割コンパイルとデバッグ

プログラムを役割ごとに分け、警告・デバッガー・テストで問題を見つける流れを学びます。

01

宣言と定義を分ける

公開する関数宣言をヘッダー、実装をソースファイルへ置きます。

calculator.hC
#ifndef CALCULATOR_H
#define CALCULATOR_H
int add(int left, int right);
#endif
calculator.cC
#include "calculator.h"
int add(int left, int right) { return left + right; }
main.cC
#include <stdio.h>
#include "calculator.h"
int main(void) {
    int result = add(10, 20);
    printf("%d\n", result);
    return 0;
}
03

別々にコンパイルしてリンクする

ビルドCOMMAND
gcc -std=c17 -Wall -Wextra -pedantic -c main.c
gcc -std=c17 -Wall -Wextra -pedantic -c calculator.c
gcc main.o calculator.o -o app
./app
実行結果OUTPUT
30

-cはリンクせずオブジェクトファイルを作ります。最後に必要なオブジェクトファイルをリンクして実行ファイルにします。

04

Makefileで手順を記録する

MakefileMAKE
CC = gcc
CFLAGS = -std=c17 -Wall -Wextra -pedantic

app: main.o calculator.o
	$(CC) main.o calculator.o -o app

main.o: main.c calculator.h
	$(CC) $(CFLAGS) -c main.c

calculator.o: calculator.c calculator.h
	$(CC) $(CFLAGS) -c calculator.c

clean:
	rm -f app main.o calculator.o
実行COMMAND
make
./app

コマンド行の先頭はスペースではなくタブです。makeは更新されたファイルに必要な処理だけを実行します。

05

警告とサニタイザーを使う

警告は不具合の候補です。学習中も-Wall -Wextra -pedanticを有効にし、理由を理解して解消します。GCCやClangを使える開発環境では、次のような検査も役立ちます。

検査付きビルド例COMMAND
gcc -std=c17 -Wall -Wextra -pedantic \
    -fsanitize=address,undefined -g main.c calculator.c -o app
./app
本番用とは分ける サニタイザーは開発時の検査用です。処理系によって利用方法が異なり、すべての不具合を見つける保証はありません。
06

デバッガーで値を追う

-g付きでビルドすると、GDBなどで停止位置を設定し、変数を確認できます。

GDBの基本操作COMMAND
gdb ./app
(gdb) break main
(gdb) run
(gdb) next
(gdb) print result
(gdb) continue
(gdb) quit

デバッガーが出力するアドレスや行番号は環境とコードにより異なります。最初はmainで止め、一行ずつ進めて予想した値と比べます。

07

assertで小さく確認する

test_calculator.cC
#include <assert.h>
#include <stdio.h>
#include "calculator.h"
int main(void) {
    assert(add(2, 3) == 5);
    assert(add(-2, 2) == 0);
    puts("すべて成功しました");
    return 0;
}
コンパイルと実行COMMAND
gcc -std=c17 -Wall -Wextra -pedantic test_calculator.c calculator.c -o test_calculator
./test_calculator
実行結果OUTPUT
すべて成功しました

NDEBUGを定義するとassertは無効になります。利用者入力の検証には使わず、通常の条件分岐で処理します。

PRACTICE

ミニ課題:料金計算を分割してテストする

単価と個数から合計を返すcalculate_total関数を作り、宣言をprice.h、定義をprice.c、利用例をmain.cへ分けてください。さらにtest_price.cで通常値、0個、複数個をassertし、すべてを警告なしでビルドします。

完成条件
  • ヘッダーにインクルードガードがある
  • Makefileのappターゲットでアプリを作れる
  • testターゲットでテストをビルドして実行できる
  • 一つのソースを変更したとき、必要なファイルだけ再コンパイルされる