C言語で書いたコードは、インクルードガードのような素朴なヘッダや構造体定義だけであれば、ほとんどそのままC++でも使えます。
しかし、実際にはコンパイルエラーになったり、挙動が変わる部分も少なくありません。
この記事では、CとC++の互換性の全体像と、典型的な非互換ポイントを整理して解説します。
CとC++の互換性の考え方
CはC++の部分集合ではない
C言語はC++の完全な部分集合ではありません。
そのため、Cでコンパイルできるコードが、必ずしもC++でそのままコンパイルできるとは限りません。

C++はCから大きく発展しており、以下のような追加・変更があります。
- 新しいキーワードの追加
- 型チェックの厳格化
- 暗黙変換の仕様変更
- オーバーロードや名前修飾(name mangling)によるリンク仕様の違い
このような違いが、CコードをC++としてコンパイルしたときの非互換につながります。
実務での基本スタンス
実務では、次のようなスタンスで考えると安全です。
- 「Cとして書いたコードをC++で使う可能性があるなら、最初から“C++でも通るCコード”を意識して書く
- 既存のCライブラリをC++から呼び出す場合は、インタフェース部分だけをC++互換に整える
こうした意識を持つことで、後から移植や再利用が必要になったときのコストを大きく下げることができます。
そのまま動く部分と動かない部分の全体像
大まかな互換性マップ
以下は、CコードをC++としてコンパイルしたときの一般的な傾向です。
- ほぼそのまま動くもの
- 構造体や共用体の定義
- 列挙型の単純な定義
- 基本的な式・制御構造(if, for, while など)
- 素朴な関数定義
- 注意すれば動くもの
- ヘッダのリンク指定(
extern "C") - キャストまわり
mallocなどCの標準ライブラリ関数の利用
- ヘッダのリンク指定(
- 動かない、もしくは挙動が変わる代表例
- C++でキーワードになった識別子の使用
- 関数ポインタや可変長引数の一部の使い方
- 暗黙の関数宣言に依存した古いCコード
- マクロとC++の演算子/名前空間との衝突
この中でも、特にトラブルになりやすいポイントを、具体例とともに見ていきます。
非互換ポイント1: C++で予約語になった識別子
代表的な新キーワード
C++では、Cには存在しないキーワードが数多く追加されています。
これらをCコードの変数名や関数名として使っていると、C++ではコンパイルエラーになります。
代表的なものを一部だけ抜粋すると、次のようになります。
| カテゴリ | キーワード例 |
|---|---|
| 型・宣言関連 | class / namespace / typename / template |
| 参照・メモリ | new / delete / this |
| 例外関連 | try / catch / throw |
| その他 | operator / explicit / mutable など |
これらの単語を、Cコードの識別子として使うのは避けたほうが安全です。
具体例
CではOKだがC++ではエラーになる例
/* Cではコンパイル可能だが、C++ではエラーになる例 */
int class = 10; /* CではOK、C++では"class"がキーワードなのでNG */
int new = 5; /* CではOK、C++では"new"がキーワードなのでNG */
int template(int x) { /* "template"もC++ではキーワード */
return x * 2;
}
int main(void) {
return class + new + template(3);
}
このコードをC++コンパイラでコンパイルしようとすると、キーワードを識別子として使っているためにエラーになります。
対策
- 新規に書くCコードでは、C++キーワードとの衝突を避ける命名規則を決めておく
- 既存コードをC++対応させる場合は、ツールやエディタの検索機能を使ってキーワードとの衝突を洗い出し、リネームする
非互換ポイント2: 関数宣言と暗黙宣言
Cでは許された「暗黙の宣言」
古いC(特にC90)では、関数のプロトタイプ宣言なしに関数を呼び出すことが許されていました。
この場合、コンパイラは「戻り値intの関数」とみなして暗黙的に宣言します。
/* 古いCでは許される書き方 */
#include <stdio.h>
int main(void) {
/* fooのプロトタイプ宣言がない */
int x = foo(10); /* C90では暗黙にint foo(int)とみなされることがあった */
printf("%d\n", x);
return 0;
}
int foo(double d) { /* 実際の定義はint foo(double) */
return (int)(d * 2);
}
このようなコードは、C++ではエラーになります。
C++では暗黙の関数宣言は禁止されているためです。
C++での必要条件
C++としてコンパイルする場合は、必ず関数を使用する前に宣言を行う必要があります。
/* C++としても正しく動く書き方 */
#include <stdio.h>
/* 先にプロトタイプ宣言を書く */
int foo(double d);
int main(void) {
int x = foo(10.0);
printf("%d\n", x);
return 0;
}
/* 定義部分 */
int foo(double d) {
return (int)(d * 2);
}
このように、関数は「宣言→使用→定義」という順序を徹底することで、CとC++の両方でコンパイルしやすくなります。
非互換ポイント3: キャストとポインタ変換
voidポインタからの代入
Cでは、void *を他のポインタ型に代入する際にキャストが不要でした。
一方C++では暗黙キャストは認められず、明示的なキャストが必要です。
CではOK / C++ではエラーになる例
#include <stdlib.h>
int main(void) {
int *p;
p = malloc(sizeof(int)); /* CではOK、C++ではエラー */
*p = 10;
free(p);
return 0;
}
C++としてコンパイルすると、void *からint *への変換が暗黙には許されないため、コンパイルエラーになります。
C++としても通る書き方
#include <stdlib.h>
int main(void) {
int *p;
p = (int *)malloc(sizeof(int)); /* 明示的なキャストを付ける */
*p = 10;
free(p);
return 0;
}
Cではキャストは不要ですが、C++での互換性を考えるなら、malloc系の返り値にはキャストを付けるというスタイルも選択肢になります。
ただし、C++では本来new / deleteを使うのが推奨であり、Cのメモリアロケーション関数をそのまま使い続けるかどうかは、プロジェクトの方針によります。
関数ポインタの互換性
CとC++では、関数の型チェックがより厳密になっています。
そのため、関数ポインタの型が少しでも違うとエラーになるケースが増えます。
/* Cでは許されることがあるが、C++ではエラーになりやすい例 */
void func(int x) { /* ... */ }
int main(void) {
void (*fp)(); /* 引数型を省略した関数ポインタ */
fp = func; /* CではOK、C++では型不一致でエラー */
return 0;
}
C++として扱う場合は、関数ポインタの引数や戻り値の型まで正確に一致させる必要があります。
非互換ポイント4: 構造体・列挙型まわりの違い
structの名前空間の違い
Cではstructタグと通常の識別子は別の名前空間として扱われますが、C++では同じ名前空間に属します。
そのため、Cでは許される同名定義が、C++では衝突することがあります。
/* CではOK、C++ではNGになる可能性のある例 */
struct Data {
int x;
};
int Data; /* Cでは別名前空間なのでOK、C++では同名で衝突 */
C++でも使用したいCコードでは、構造体タグと変数名・関数名を意図的に区別する命名を行うと安全です。
enumの扱いの違い
Cでは列挙子は整数型へ自由に暗黙変換されますが、C++では列挙型の扱いがやや厳密になります(C++11以降のenum classなども存在します)。
ただし、純粋なCスタイルのenumであれば、基本的には大きな問題にはなりません。
非互換ポイント5: ヘッダと名前修飾(リンク)
C関数をC++から呼ぶときの問題
C++では関数名がオーバーロードされるため、コンパイラが名前に「修飾」をつけて区別します。
これが、Cでコンパイルされたオブジェクトファイルとリンクするときの問題になります。

C側で定義された関数をC++から呼びたいときは、「この関数はCの規約でリンクしてほしい」とコンパイラに伝える必要があります。
extern “C” の基本形
C/C++両対応のヘッダを書くときの典型パターンを示します。
/* mylib.h - CとC++の両方から使えるヘッダ */
#ifndef MYLIB_H
#define MYLIB_H
#ifdef __cplusplus
extern "C" { /* C++でコンパイルされるときだけ、Cリンケージを指定 */
#endif
/* C言語スタイルのAPI宣言 */
void my_function(int x);
/* 他にも関数やグローバル変数など */
#ifdef __cplusplus
} /* extern "C" の終わり */
#endif
#endif /* MYLIB_H */
このように書いておけば、同じヘッダをCコンパイラとC++コンパイラのどちらからインクルードしても問題なく使えるようになります。
簡単なサンプル: CライブラリをC++から呼ぶ
ここまでの内容を踏まえて、Cで書かれた小さなライブラリをC++から呼び出す例を示します。
C側ライブラリコード(mylib.c)
/* mylib.c - Cで書かれたライブラリ実装 */
#include "mylib.h"
#include <stdio.h>
/* 足し算を行う単純な関数 */
int add_int(int a, int b) {
return a + b;
}
/* デバッグ用のメッセージを表示する関数 */
void print_message(const char *msg) {
printf("Message: %s\n", msg);
}
C/C++両対応ヘッダ(mylib.h)
/* mylib.h - C/C++両方から利用できるヘッダ */
#ifndef MYLIB_H
#define MYLIB_H
#ifdef __cplusplus
extern "C" { /* C++の場合はCリンケージを指定 */
#endif
/* Cスタイルの関数宣言 */
int add_int(int a, int b);
void print_message(const char *msg);
#ifdef __cplusplus
} /* extern "C" 終了 */
#endif
#endif /* MYLIB_H */
C++側からの利用(main.cpp)
/* main.cpp - C++からCライブラリを呼び出す */
#include <iostream>
#include "mylib.h" // Cで書かれたライブラリのヘッダ
int main() {
int x = 3;
int y = 4;
// Cで実装された関数add_intをそのまま呼べる
int sum = add_int(x, y);
// 結果をC++の標準出力で表示
std::cout << "sum = " << sum << std::endl;
// Cの標準出力を使う関数も普通に呼べる
print_message("Hello from C function!");
return 0;
}
(ここでは環境依存のため実行結果のコードブロックは省略しますが、適切にコンパイル・リンクすれば、C++の標準出力とCのprintfの両方が表示されます。)
この例のように、ライブラリ側の実装をCのままに保ちつつ、ヘッダでextern "C"による対応を行うことで、C++側からも自然に再利用できるようになります。
実務で意識しておきたいチェックポイント
CコードをC++でも動かしたい場合、最低限次のポイントをチェックすると安全です。
- C++のキーワードを識別子に使っていないか
- すべての関数にプロトタイプ宣言があるか(暗黙宣言に依存していないか)
void *から他のポインタ型への代入で、必要なキャストを付けているか- 構造体タグ名と変数名・関数名が衝突していないか
- C/C++両対応ヘッダでは
extern "C"ガードを使っているか
これらを押さえるだけでも、CからC++への移行・併用時のトラブルをかなり減らすことができます。
まとめ
C言語コードは「多くの場合C++でも動きます」が、「完全な互換性」はありません。
特に、C++で新たに導入されたキーワードや、より厳密になった型チェック・名前修飾などが原因で、Cでは通るコードがC++ではエラーになることがあります。
C/C++両方で利用する可能性があるコードでは、関数プロトタイプを必ず書く、void *の扱いに注意する、extern "C"でリンケージを明示する、といったポイントを意識しておくと、安全に両言語から再利用しやすくなります。
