web-dev-qa-db-ja.com

Cで 'char **'を 'const char * const *'に変換できないのはなぜですか?

次のコードスニペットは(正しく)Cで警告を表示し、C++でエラーを表示します(バージョン3.4.5および4.2.1でテストされたgccとg ++をそれぞれ使用します。MSVCは気にしないようです)。

char **a;
const char** b = a;

私はこれを理解して受け入れることができます。
この問題に対するC++の解決策は、bをconst char * const *に変更することです。これにより、ポインターの再割り当てができなくなり、const-correctnessを回避できなくなります( C++ FAQ )。

char **a;
const char* const* b = a;

ただし、純粋なCでは、修正されたバージョン(const char * const *を使用)でも警告が表示され、その理由がわかりません。キャストを使わずにこれを回避する方法はありますか?

明確にするために:
1)これがCで警告を生成するのはなぜですか?完全にconst-safeである必要があり、C++コンパイラはそれをそのように認識しているようです。
2)私が指す文字を変更しないことを言っている(そしてコンパイラーに強制させる)ときに、この文字**をパラメーターとして受け入れる正しい方法は何ですか?たとえば、関数を作成したい場合:

void f(const char* const* in) {
  // Only reads the data from in, does not write to it
}

そして、私はそれをchar **で呼び出したかったのですが、パラメータの正しいタイプは何でしょうか?

編集:回答していただいた方、特に質問に対応したり、私の回答をフォローしてくださった方に感謝します。

やりたいことはできなくても、キャストなしでは成し遂げられないという答えを受け入れました。

59
HappyDude

数年前に私も同じ問題を抱えていて、それは私に終わりがたまりませんでした。

Cのルールはより簡単に説明されています(つまり、char**const char*const*に変換するような例外はリストされていません)。結果として、それは許可されていません。 C++標準では、このようなケースを許可するためのルールが追加されました。

結局のところ、それはC標準の問題にすぎません。次の標準(または技術レポート)がこれに対処することを願っています。

57
Kevin

>しかし、純粋なCでは、これでも警告が表示されます。理由はわかりません。

あなたはすでに問題を識別しています-このコードはconst-correctではありません。 「const正しい」とは、const_castおよびCスタイルのキャストでconstを削除する場合を除き、それらのconstポインターまたは参照を通じてconstオブジェクトを変更することはできないことを意味します。

Const-correctnessの値-constは、主にプログラマーのエラーを検出するために存在します。何かをconstとして宣言した場合、それを変更する必要はないと考えていることを意味します。少なくとも、constバージョンにのみアクセスできるユーザーは、変更できないようにする必要があります。考慮してください:

void foo(const int*);

宣言されているように、fooには、その引数が指す整数を変更する権限がありません。

投稿したコードがconst-correctではない理由がわからない場合は、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」では、プログラムがクラッシュします。

編集-追加:

HappyDude:あなたのコメントはその場にあります。 C言語または使用しているCコンパイラは、const char * const *をC++言語が扱うのとは根本的に異なる方法で扱います。おそらく、このソース行に対してのみコンパイラ警告をサイレントにすることを検討してください。

編集-削除:タイプミスを削除

10
Aaron

互換性があると見なされるには、ソースポインターは、直前の間接参照レベルで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++を除いて)暗黙的な変換を行う方法はありません。

10
Fabio Ceconello

これは面倒ですが、別のレベルのリダイレクトを追加したい場合は、多くの場合、次のようにしてポインターツーポインターにプッシュダウンできます。

char c = 'c';
char *p = &c;
char **a = &p;

const char *bi = *a;
const char * const * b = &bi;

意味は少し異なりますが、通常は機能し、キャストを使用しません。

1
wnoise

少なくともMSVC 14(VS2k5)およびg ++ 3.3.3では、char **をconst char * const *に暗黙的にキャストするときにエラーが発生しません。 GCC 3.3.3は警告を出しますが、それが正しいかどうかはわかりません。

test.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 test.c

test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter

C++コードとしてコンパイルして出力:cl/TP/W4/Wp64 test.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 ++ -Wall test.C

出力なし

0
user7116

Constキーワードは、データを変更できない、または定数であるという意味ではなく、データが読み取り専用として扱われることを確信しています。このことを考慮:

const volatile int *const serial_port = SERIAL_PORT;

これは有効なコードです。 volatileとconstはどのように共存できますか?シンプル。 volatileは、データの使用時に常にメモリを読み取るようコンパイラーに指示し、constは、serial_portポインターを使用してメモリーに書き込もうとしたときにエラーを作成するようコンパイラーに指示します。

Constはコンパイラのオプティマイザを助けますか?いいえ、まったく違います。キャストによってデータにconstnessを追加したり削除したりできるため、constデータが実際に定数であるかどうかをコンパイラーが判断できません(キャストは別の変換単位で実行できるため)。 C++には、問題をさらに複雑にするためのmutableキーワードもあります。

char *const p = (char *) 0xb000;
//error: p = (char *) 0xc000;
char **q = (char **)&p;
*q = (char *)0xc000; // p is now 0xc000

実際には読み取り専用のメモリ(ROMなど)に書き込もうとした場合に何が起こるかは、おそらく標準でまったく定義されていません。

0
Skizz