導入
C++で開発を行っていると、既存のC言語のライブラリを利用したり、逆にC++で書いた関数をC言語のコードから呼び出したりする場面に遭遇します。しかし、C++には「名前修飾(マングリング)」という仕組みがあるため、そのままではリンカが関数を正しく認識できません。この課題を解決するのが extern “C” です。本記事では、この構文の仕組みと、実務で安全に利用するための定石を解説します。
基礎知識
C++では、関数のオーバーロード(多重定義)をサポートするために、コンパイラが関数名に引数の型情報を付加する「名前修飾」を行います。例えば、void func(int) という関数は、内部的には _Z4funci のように変換されます。一方で、C言語にはオーバーロードがないため、関数名はそのままの名前で保持されます。
この仕様の差により、C++側からC言語の関数を呼ぼうとすると、リンカが「未定義のシンボル」としてエラーを吐きます。extern “C” を使用すると、コンパイラに対して「この範囲の関数はC言語の形式(名前修飾なし)で扱いなさい」と指示し、リンク時の不整合を防ぐことができます。
実装/解決策
実務で最も重要なのは、ヘッダファイル内で extern “C” を扱う方法です。C++コンパイラは __cplusplus というマクロを定義しているため、これを利用して条件付きコンパイルを行うのが定石です。これにより、C言語のソースからインクルードされた場合は通常の宣言として、C++からインクルードされた場合は外部リンケージとして適切に処理されます。
サンプルプログラム
以下は、C言語とC++を混在させる際に推奨されるヘッダファイルの書き方です。
// my_library.h
ifndef MY_LIBRARY_H
define MY_LIBRARY_H
// C++コンパイラでコンパイルされている場合のみ extern “C” を適用する
ifdef __cplusplus
extern “C” {
endif
// ここに公開する関数のプロトタイプを記述
void perform_calculation(int value);
ifdef __cplusplus
}
endif
endif // MY_LIBRARY_H
// my_library.c (C言語側の実装)
include
include “my_library.h”
void perform_calculation(int value) {
printf(“計算値: %d\n”, value 2);
}
// main.cpp (C++側の呼び出し)
include “my_library.h”
int main() {
// extern “C” のおかげで名前修飾されず、C言語側の関数を正しくリンクできる
perform_calculation(10);
return 0;
}
応用・注意点
1. クラスやテンプレートは使用不可: extern “C” ブロック内では、C++特有の機能(クラス、テンプレート、オーバーロードなど)は使用できません。あくまで「C言語でも理解できるバイナリインターフェース」を公開するためのものだと割り切りましょう。
2. ライブラリのリンク順序: 複数のライブラリをリンクする際、C言語のライブラリが後方に配置されるとリンクエラーになることがあります。依存関係を考慮した順序でビルド設定を行う必要があります。
3. デバッグの難易度: C++のコードからC言語の関数を呼び出す際、名前修飾がないためデバッガ上で名前が単純化されます。大規模なプロジェクトでは、関数名が衝突しないよう、接頭辞(prefix)を付ける命名規則を徹底することをおすすめします。

コメント