C で 'char**' を 'const char* const*' に変換できないのはなぜですか?
-
09-06-2019 - |
質問
次のコード スニペットは (正しく) C で警告を出し、C++ でエラーを出します (それぞれ gcc と g++ を使用し、バージョン 3.4.5 と 4.2.1 でテストしました。MSVC は気にしていないようです):
char **a;
const char** b = a;
私はこれを理解して受け入れることができます。
この問題に対する C++ の解決策は、 b を const char * const * に変更することです。これにより、ポインタの再割り当てが禁止され、const-correctness (C++ よくある質問).
char **a;
const char* const* b = a;
ただし、純粋な C では、修正されたバージョン (const char * const * を使用) でも警告が表示されますが、その理由はわかりません。キャストを使用せずにこれを回避する方法はありますか?
明確にするために:
1) これにより C で警告が生成されるのはなぜですか?これは完全に const セーフである必要があり、C++ コンパイラはそれをそのように認識しているようです。
2) この char** をパラメータとして受け入れ、それが指す文字を変更しないことをコンパイラに強制させる正しい方法は何ですか?たとえば、関数を書きたいとすると、次のようになります。
void f(const char* const* in) {
// Only reads the data from in, does not write to it
}
そして、それを char** で呼び出したいと思ったのですが、パラメータの正しい型は何でしょうか?
編集:回答してくださった方々、特に質問に答えていただいたり、私の回答をフォローアップしてくださった方々に感謝します。
私がやりたいことは、できるかどうかは別として、キャストなしではできないという答えを受け入れました。
解決
私も数年前に同じ問題を抱えていましたが、それは私を果てしなくイライラさせました。
C のルールはより単純に記述されています (つまり、変換などの例外はリストされていません char**
に const char*const*
)。結果として、それは許可されません。C++ 標準では、このようなケースを許可するためのルールがさらに追加されました。
結局のところ、これは C 標準の問題にすぎません。次の規格 (または技術レポート) でこの問題が解決されることを願っています。
他のヒント
互換性があるとみなされるには、ソース ポインタが直前の間接レベルで const である必要があります。したがって、これにより GCC で警告が表示されます。
char **a;
const char* const* b = a;
しかし、これでは次のことはできません。
const char **a;
const char* const* b = a;
あるいは、次のようにキャストすることもできます。
char **a;
const char* const* b = (const char **)a;
前述したように、関数 f() を呼び出すには同じキャストが必要になります。私の知る限り、この場合に暗黙的な変換を行う方法はありません (C++ を除く)。
> ただし、純粋な C では、これでも警告が表示されますが、その理由がわかりません。
問題はすでに特定されています。このコードは const が正しくありません。「const が正しい」とは、const_cast および C スタイルのキャストで const を削除する場合を除き、これらの const ポインターまたは参照を通じて const オブジェクトを変更できないことを意味します。
const-correctness -- const の値は、主にプログラマーのエラーを検出するためにあります。何かを const として宣言すると、それを変更すべきではないと考えていることを表明していることになります。少なくとも、const バージョンのみにアクセスできるユーザーは変更できないはずです。考慮する:
void foo(const int*);
宣言されているように、foo には 許可 引数が指す整数を変更します。
投稿したコードが const 正しくない理由がわからない場合は、HappyDude のコードとわずかに異なるだけの次のコードを検討してください。
char *y;
char **a = &y; // a points to y
const char **b = a; // now b also points to y
// const protection has been violated, because:
const char x = 42; // x must never be modified
*b = &x; // the type of *b is const char *, so set it
// with &x which is const char* ..
// .. so y is set to &x... oops;
*y = 43; // y == &x... so attempting to modify const
// variable. oops! undefined behavior!
cout << x << endl;
非 const 型は、明示的なキャストを行わないデータ型の 'const' の回避を防ぐための特定の方法でのみ const 型に変換できます。
最初に const として宣言されたオブジェクトは特に特別であり、コンパイラはそれらが決して変更されないと想定できます。ただし、キャストなしで「b」に「a」の値を割り当てることができる場合、誤って const 変数を変更しようとする可能性があります。これにより、コンパイラに要求したチェックが無効になり、その変数値の変更が禁止されるだけでなく、コンパイラの最適化も無効になる可能性があります。
一部のコンパイラでは「42」が出力され、一部のコンパイラでは「43」が出力され、その他のコンパイラではプログラムがクラッシュします。
編集-追加:
ハッピーデュード:あなたのコメントは的を射ています。C 言語または使用している C コンパイラは、const char * const * を C++ 言語とは根本的に異なる方法で扱います。おそらく、このソース行についてのみコンパイラ警告をサイレントにすることを検討してください。
編集-削除: タイプミスを削除しました
これは面倒ですが、別のレベルのリダイレクトを追加したい場合は、次のようにしてポインター間のプッシュダウンを行うことができます。
char c = 'c';
char *p = &c;
char **a = &p;
const char *bi = *a;
const char * const * b = &bi;
意味は少し異なりますが、通常は実行可能であり、キャストは使用しません。
少なくとも MSVC 14 (VS2k5) と g++ 3.3.3 では、char** を const char * const * に暗黙的にキャストするときにエラーを取得できません。GCC 3.3.3 は警告を発行しますが、これが正しいかどうかは正確にはわかりません。
テスト.c:
#include <stdlib.h>
#include <stdio.h>
void foo(const char * const * bar)
{
printf("bar %s null\n", bar ? "is not" : "is");
}
int main(int argc, char **argv)
{
char **x = NULL;
const char* const*y = x;
foo(x);
foo(y);
return 0;
}
C コードとしてコンパイルして出力します。cl /TC /W4 /Wp64 テスト.c
test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter
C++ コードとしてコンパイルして出力します。cl /TP /W4 /Wp64 テスト.c
test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter
gcc で出力:gcc -Wall test.c
test2.c: In function `main':
test2.c:11: warning: initialization from incompatible pointer type
test2.c:12: warning: passing arg 1 of `foo' from incompatible pointer type
g++ での出力:g++ -ウォールテスト.C
出力なし
const キーワードは、データが変更できない/定数であることを意味するものではなく、データが読み取り専用として扱われることを意味していると確信しています。このことを考慮:
const volatile int *const serial_port = SERIAL_PORT;
これは有効なコードです。volatile と const はどのように共存できるのでしょうか?単純。volatile は、データを使用するときに常にメモリを読み取るようにコンパイラに指示し、const は、serial_port ポインタを使用してメモリに書き込もうとしたときにエラーを作成するようにコンパイラに指示します。
const はコンパイラのオプティマイザーに役立ちますか?いいえ。全くない。キャストを通じてデータに定数を追加したり、データから定数を削除したりできるため、コンパイラは、const データが実際に定数であるかどうかを判断できません (キャストが別の変換単位で行われる可能性があるため)。C++ には、事態をさらに複雑にする mutable キーワードもあります。
char *const p = (char *) 0xb000;
//error: p = (char *) 0xc000;
char **q = (char **)&p;
*q = (char *)0xc000; // p is now 0xc000
実際には読み取り専用であるメモリ (ROM など) に書き込もうとしたときに何が起こるかは、おそらく標準ではまったく定義されていません。