C++ 移行ガイド

文字列リテラルと char*

標準 C++では文字列リテラルは const char[] 型として扱われ、char* と宣言された関数パラメータは文字列リテラルには渡されません。この変更の経緯を順を追って説明します。標準の C では、const キーワードと定数オブジェクトの概念が導入されました。これらのどちらも従来の C 言語 (K&R 形式の C) にはなかったものです。次の例に見られるような無意味な結果が出されないようにするには、論理的には「Hello world」などの文字列リテラルは const で宣言するべきです。


#define GREETING“Hello world”;
char* greet = GREETING; // コンパイラからのエラー出力はない
greet[0] = `J';
printf(“%s”, GREETING); // システムによっては「Jello world」と出力される

C、C++ とも、文字列リテラルを変更した結果がどうなるかは未定義です。同じ文字列リテラルに対し同じ書き込み可能記憶域を使用する実装の場合は、上の例のように奇妙な出力となります。

当時存在していたコードの多くが上記の例の 2 行目のようになっていたため、1989 年に C 標準委員会は文字列リテラルを const にはしませんでした。そのため、C++ 言語は当初 C 言語の規則に従いました。しかし、後日、C++ 標準委員会は、C++ においては型の安全性が重要と判断し、この文字列リテラルに関する規則を変更しました。

標準の C++ では、文字列リテラルは定数であり、const char[] 型です。上記の例の 2 行目は標準の C++ では無効です。同じように、char* で宣言した関数パラメータは、文字列リテラルとして渡すべきではありません。ところが C++ 標準では、文字列リテラル const char[] から char* への変換は不適切であると規定されています。この例をいくつか示します。


char *p1 =“Hello”;  // 従来は問題なかったが、現在は不適切
const char* p2 =“Hello”; // OK
void f(char*);
f(p1);        // p1 は const として宣言されていないので常に OK
f(p2);        // エラー、const char* を char* に渡している
f(“Hello”); // 従来は問題なかったが、現在は不適切
void g(const char*);
g(p1);        // 常に OK
g(p2);        // 常に OK
g(“Hello”); // 常に OK

引数として渡された文字配列が直接的にも間接的にも関数によって変更されることがない場合は、パラメータを const char* または const char[] と宣言してください。このようにすると、プログラムのいたるところで const 修飾子を追加する必要があることに気づくでしょう。修飾子を追加するほど、さらに多くの修飾子が必要になります (「const 中毒 (const poisoning)」と呼ばれることがある現象)。

標準モードの C++ 5.0 は、文字列リテラルから char* への変換が適切でないと警告を出します。妥当と思われるあらゆる場所に const を使用していれば、既存のプログラムは新しい規則でもおそらく変更なしにコンパイルされます。

関数を多重定義するために、標準モードでは、文字列リテラルは常に定数とみなされます。


void f(char*);
void f(const char*);
f(“Hello”); // どの f が呼び出されるか

上の例を互換モード (または 4.2 コンパイラ) でコンパイルすると、関数 f(char*) が呼び出されます。

標準モードでは、コンパイラは、リテラル文字列をデフォルトで読み取り専用記憶域に置きます。この文字列を変更しようとすると (char* への自動変換によって変更されることがある)、プログラムはメモリー違反で異常終了します。

次の例では、4.2 コンパイラも、互換モードの 5.0 コンパイラも、文字列リテラルを書き込み可能記憶域に置きます。プログラムは動作しますが、技術的にはその動作がどうなるかは未定義です。標準モードの 5.0 コンパイラは文字列リテラルをデフォルトで読み取り専用記憶域に置くため、プログラムはメモリー違反で異常終了します。そのため、文字列リテラルの変換に対するすべての警告に注意し、変換が起こらないようにプログラムを修正する必要があります。そうすれば、プログラムはどの C++ 実装でも正しく動作します。


void f(char* p) { p[0] = `J'; }

int main()
{
    f(“Hello”); // const char[] から char* への変換
}

コンパイラの動作は、コンパイラオプションを使って変更できます。

C 形式の文字列ではなく標準の C++ の string クラスを使用した方が便利なこともあります。標準の C++ の string オブジェクトは個別に const かどうか宣言したり、参照、ポインタ、値のどれによっても関数に渡せるため、string クラスには文字列リテラルに関係する問題はありません。