「関数はもう本体を持っている」。rpg225→227、10分24秒の重複定義修正

GBDK開発発掘記 / Bank overflowではない、小さくて分かりやすい「二重定義」の失敗

Bank overflowの記事が3本続いたので、少し種類の違う失敗を拾います。

rpg225のbuild jobは失敗していました。エラー分類はDUPLICATE_DEFINITION。build logに残っていたのは、skill_is_magic と skill_is_heal_magic の2関数に対する「already has body」です。

失敗jobは 20260512_145616_a815f8fc。次に成功したjobは 20260512_150640_4d87f312。job IDの時刻を読むと、その間は10分24秒でした。

エラーはかなり具体的だった

rpg225のログには、main.c:1259 と main.c:1263 でそれぞれ error 65 が出ています。メッセージは function 'skill_is_magic' already has body と function 'skill_is_heal_magic' already has body です。

コンパイラは「この関数にはすでに本体がある」と言っています。少なくとも、同じ名前の関数本体を重複して持つ状態になっていたことはログから確認できます。

ただし、今回の調査用抜粋には、重複していたもう一方の関数本体がどの行にあったかまでは載っていません。そこを想像で補わず、実diffで見える変更だけを追います。

成功版では、関数本体がprototype宣言に戻っていた

rpg225→227の main.c diff は +2 / -9。失敗側では、skill_is_magic() と skill_is_heal_magic() がその場で波括弧つきの関数本体として書かれていました。

成功側では、その2つが次のような宣言へ変わっています。

static UINT8 skill_is_magic(UINT8 skill_id);

static UINT8 skill_is_heal_magic(UINT8 skill_id);

つまり、この場所では処理本体を持たず、「この名前・引数・戻り値の関数がある」とだけ知らせる形へ変更されています。

ここからはC言語としての一般説明

Cでは、末尾がセミコロンで終わるprototype宣言と、波括弧の中に実処理を書く関数定義は役割が違います。

prototype宣言は関数の形をコンパイラへ知らせるものです。関数定義は実際の処理本体です。同じ関数を同じ翻訳単位の中で複数回定義すれば、今回のような重複定義エラーになります。

ここで一般論と一次資料を混ぜないようにします。一次資料が直接示しているのは、「rpg225でalready has bodyが2件出たこと」と、「rpg227では先ほどの2箇所が関数本体からprototype宣言へ変わったこと」です。

build.shとMakefileもrpg227へ更新されていた

同じdiffには build.sh と Makefile のOUT名変更も残っています。

rpg225_long_cave_spark_rewards.gb から rpg227_magic_skill_split_duplicate_fix.gb へ変わっています。

これはrpg225→227へ版が進んだ証拠にはなりますが、重複定義を直した直接原因としては扱いません。実際の修正として確認できる中心は main.c の +2 / -9 です。

失敗版には「失敗時のROM画面」はない

rpg225はbuild failedです。この失敗を説明するために、後からそれらしいゲーム画面を作って「rpg225の画面」と呼ぶことはしません。

また、この調査用抜粋だけではrpg227のhistorical ROM実体が現在救出済みかまでは確認できません。したがってこの記事の3枚は、job情報・実source diff・一般的なC言語説明を整理した技術図解です。

10分の修正でも、残しておくと教材になる

この事故はrpg568〜594の25回失敗に比べれば小さいです。それでも初心者向けの記事としては、むしろこちらの方が分かりやすい題材です。

エラー文に関数名がそのまま出ていて、成功版のdiffも小さい。何が起きたかを「コンパイラが何を言ったか → ソースがどう変わったか」の順に追えます。

今なら、コンパイルエラーもmanifestへ構造化して残す

今同じ仕組みを作るなら、build.logをそのまま保存するだけでなく、error code、file、line、function、messageを抽出してjob manifestへ残します。

例えば今回なら、error_code=65、file=main.c、function=skill_is_magic / skill_is_heal_magic、classification=DUPLICATE_DEFINITION のように検索できる形です。

そうしておけば、後から「Bank overflowだけ」「重複定義だけ」「syntax errorだけ」と事故の種類ごとに制作記を掘れます。

次は「ビルドは失敗したが、エラー文がもっと曖昧」な例へ

次の候補はrpg450→451です。rpg450はMAKE_BUILD_FAILUREとして止まり、rpg451では inventory.c に audio pump用のfile-scope helperが追加されています。

こちらはrpg225ほどエラー文だけで原因が見えません。次は、build logが短いときにsource diffからどこまで言えるか、その境界を扱います。

一次資料メモ

rpg225 failed job: 20260512_145616_a815f8fc

rpg227 success job: 20260512_150640_4d87f312

分類: DUPLICATE_DEFINITION

実エラー: skill_is_magic / skill_is_heal_magic に already has body

main.c diff: +2 / -9

成功側変更: 2関数の本体をprototype宣言へ変更

build.sh / Makefile: OUT名を rpg225_long_cave_spark_rewards.gb → rpg227_magic_skill_split_duplicate_fix.gb に更新

図版: 実job情報・実source diff・一般C言語解説。実ROM画面ではない

編集状態: 初稿 / Technical Review前