char **からconst char **への変換が暗黙に出来ないのはなぜ?

解決


かざかざ  2004-10-22 01:16:55  No: 54835

C++についての質問です。

通常、以下のコードは警告も無くコンパイル可能だと思います。

static void funcchild2(const char *str)
{
  puts(str);
}

static void funcparent2()
{
  char *str="Atring";
  str[0]='S';
  funcchild2(str);
}

しかし、以下のコードはエラーが出ます。

static void funcchild(const char **aStr)
{
  puts(aStr[0]);
  puts(aStr[1]);
  puts(aStr[2]);
  puts(aStr[3]);
}

static void funcparent()
{
  char *aStr[]=
  {
    "Atring1",
    "String2",
    "String3",
    "String4"
  };
  *aStr[0]='S';
  funcchild((char**)aStr);
}

ポインタが2重になっている場合、constを明示的にキャストしないとエラーになるようです。
そういうものといわれればそれまでなのですが、引数の値を変更しない関数ではその引数はconstで宣言しておきたいですし、だからといってそのために呼び出し元でわざわざconst付にキャストするのはあまり美しくないように思います。

なぜこのような仕様になっているのか、わかる方がいらっしゃったら教えてください。

Visual C++ .NET と CodeWarriorで同様のエラーが出ることを確認しております。


tetrapod  2004-10-22 01:31:42  No: 54836

何が const 修飾されたことになるか考えましょう。

ちなみに funcparent/2 はおおいにバグっています。


かざかざ  2004-10-22 01:44:31  No: 54837

双方ともcharがconst修飾されていると理解しておりました。実際には何が修飾されており、何が問題となっているのでしょうか?
よろしければもう少し詳しくお教えください。

funcparent/2は確かにお行儀の悪いコード(ROM上で動作させるコードだとするとちとまずい)だとは思いますが、バグっているというのはどの部分を差しているのでしょうか?
ついでのようで申し訳ありませんがこちらもご教授いただけるとありがたく思います。


tetrapod  2004-10-22 02:35:37  No: 54838

先に後者の解説
> funcparent/2は確かにお行儀の悪いコード(ROM上で動作させるコードだとするとちとまずい)だとは思いますが
文字列リテラルの変更は禁じられています (ISO/IEC 14882:1998 2.13.4-2)
手元の HPUX11.00 でこの関数を実行しようとすると core dump しますし、
VC++6 でも -GF オプションを指定するとエラーとなります。

次に前者の解説
> char** が const char ** に変換できない
理由ですが、こんなサンプルを書けば理解できるでしょうか。
int main() {
    const char c='c';
    char *x;
    const char **y=&x; // error
    // もしできたとしたら、以下の手順で c を変更できちゃう
    *y=&c;
    *x='b';
}


KING・王  2004-10-22 02:41:00  No: 54839

> バグっているというのはどの部分を差しているのでしょうか?

以下の部分じゃないでしょうか?

>    char *str="Atring";
>    str[0]='S';

strは文字列定数(表現に自信がない)である"Atring"へのポインタが格納されます。
str[0]='S'では、文字列定数を変更しに行っています。

これでは、他の部分で、同じように
char *str2 = "Atring";
としていた場合、上記部分が実行された後に、
str2をprintf("%s",str2);
を実行すると、
String
と表示されてしまいます。
#確認していないけで、そうなったとおもいます。


かざかざ  2004-10-22 03:22:17  No: 54840

tetrapodさん、KING・王さん、詳しいご解説ありがとうございました。

> 文字列リテラルの変更は禁じられています (ISO/IEC 14882:1998 2.13.4-2)
全く知りませんでした。勉強になります。

> str[0]='S'では、文字列定数を変更しに行っています。
これはまさにそのつもりで書きました。

サンプルも非常にわかりやすく、すぐに理解できました。全然気づかなかったです。

負け惜しみですが、文字列リテラルの変更や、char*を介してconst char **の内容を書き換えられる事については、別に禁止することでも無いように感じてしまいます。char*をわざわざ入れてるならそれは変更したいからだろうと想像できますし。
そんなノリがCの良いところだと..。
C++になったら変に厳密になってしまって魅力が薄まってしまいました。C++も好きですけどね。

大変勉強になりました。ありがとうございました。


かざかざ  2004-10-22 03:58:16  No: 54841

追伸です。
以下のように書き換えたらコンパイルエラーはなくなりました。

static void funcchild(const char * const *aStr)
{
    puts(aStr[0]);
    puts(aStr[1]);
    puts(aStr[2]);
    puts(aStr[3]);
}

char *が入れられなくなれば大丈夫ってことですね。
中途半端にconst使うと駄目ですね。入れるなら変更しないところ全部入れないと。

文字列リテラルの件も、複数箇所で使用される同じ内容の文字列は最適化でまとめられたりするから、小さいツール書きのとき以外は割と危険ですね。

というわけで、更に納得。


tetrapod  2004-10-22 19:58:36  No: 54842

> char *が入れられなくなれば大丈夫ってことですね。
御意。
FAQ ではありますが難しい系の内容です。納得いただけて幸いです。

あと第三者読者のために蛇足など。
char aStr[][8]={
  "Atring1",
  "String2",
  "String3",
  "String4"
};
aStr[0][0]='S';
なら完全に合法です。書き換えてるところを *aStr[0]='S'; でも合法。
これだと「配列を文字列リテラルで初期化してる」ことになります。
書き換えられるのは non-const な aStr であって文字列リテラルではありません。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加