パスワードを忘れた? アカウント作成
452754 journal

Endowsの日記: Endows の へっぽこプログラマ日誌 第19回 6

日記 by Endows

ちょっとお勉強、ということで書いたコードを 2つほど載せてみる。

その1 :

int i0;
const int i1;
int const i2;

int * pi0;
const int * pi1;
int const * pi2;
int * const pi3;
const int * const pi4;
int const * const pi5;

int main(int argc, char *argv[])
{
                i0 = 0;
                i1 = 1;
                i2 = 2;

                pi0 = &i0;
                pi0 = &i1;
                pi0 = &i2;
                pi1 = &i0;
                pi1 = &i1;
                pi1 = &i2;
                pi2 = &i0;
                pi2 = &i1;
                pi2 = &i2;
                pi3 = &i0;
                pi3 = &i1;
                pi3 = &i2;
                pi4 = &i0;
                pi4 = &i1;
                pi4 = &i2;
                pi5 = &i0;
                pi5 = &i1;
                pi5 = &i2;

                *pi0 = 0;
                *pi1 = 1;
                *pi2 = 2;
                *pi3 = 3;
                *pi4 = 4;
                *pi5 = 5;

                return 0;
}

その2 :

int main(int argc, char *argv[])
{
                char *ptest1 = "test1";

                char stest2[6] = "test2";
                char *ptest2 = stest2;

                char stest3[6] = "test3";
                char *ptest3 = &stest3[0];

                "test0"[0] = 'T';
                *(ptest1) = 'T';
                *(ptest2) = 'T';
                *(ptest3) = 'T';

                return 0;
}

その1 は const 修飾子の位置による意味合いについて、その2 は char *ptest1 = "test1" としたとき "test1" は char [] なのか const char [] なのかが気になったので。

ちょっと気になったんですが、 gcc だと
"test0"[0] = 'T' の時は "test0" は const char [] ですが (11行目で Warning)、
char *ptest1 = "test1" の時は "test1" は char [] みたいですね。(ただし -Wwrite-strings を付けると const char [] と見なし 3行目で Warning 。)

…と思ったら、 "test0"[0] = 'T' を *("test0"+0) = 'T' に変えたら Warning 消えたぞ? ありゃりゃ?

教えて偉い人~ (またかよ)

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • 長文失礼。

    その1について:
    宣言は、記憶クラス指定子(autostatic等)、型指定子(intdouble等)、修飾子(constvolatileの2つ)が、任意の順番で現れたものの後に宣言子(ポインタ、配列、引数リスト、修飾子を含む識別子)のリストがくっついたものです。正確な定義はプログラミング言語C 第2版 付録AのA8節、A8.5節あたりを参照のこと。

    なので、int const *const int *は同じ意味で、「(読み込み専用のint)へのポインタ」。int * constは、ポインタがconst修飾されているので、「intへの(読み込み専用のポインタ)」となります。 余談ですが、宣言にまつわるトリビアでも。
    • 一見、その目的が相反しているようにみえるregistervolatileですが、registerは記憶クラス指定子、volatileは修飾子なので、一つの宣言に同時に現れることができます。
      でも、register volatileって、「できるだけレジスタにいれっぱなしにしろ。ただし、レジスタに入れっぱなしなどの最適化は絶対にするな」という意味になるから……どうしろと?(笑)
      ああそうか。独自拡張で、特定のレジスタを割り当てる構文を用意して、必ずそれに割り当てろっていう意味に使うなら……
    • 記憶クラス指定子、型指定子、修飾子が任意の順番に現れることができるので、
      long auto const int volatile signed hoge;
      のような変態宣言も可能です。
    • さらに、typedefも記憶クラス指定指圧界なので、
      unsigned typedef char Byte;
      のようなこれまた変態宣言も可能です。

    その2について:
    文字列リテラルは、別にconst宣言された型としては扱われないので、文字列を直接変更するような構文も許可されています(const char*として扱われるなら、char*型の変数には代入不可……は特例で認めるにしても、文字列自体の変更は、警告ではなくエラーになりますよ)。ただし、その時の挙動は処理系依存となっているので、多くのコンパイラでは警告がでるようになっています。
    *("str"+0)で警告がでないのは、その形が警告パターンにマッチしないからでしょう。苦労してこの形に対する警告を実装しても、狙った人しかそんなコードを書かないでしょうしね。
    ちなみに、gcc-3.2.3では、0["str"]='a'も警告がでませんでした。

    なお、文字列リテラルを上書きしたときには、
    • 問題なく動く
    • 読み込み専用領域を書き換えようとした咎で強制終了(セグメンテーションフォルトとか一般保護法違反とか)
    • 同じ文字列リテラルを使っている別の場所でも文字列が変わる
    等の事態が考えられます。
    --
    巧妙に潜伏したバグは心霊現象と区別が付かない。
    • 詳細な解説ありがとうございます。

      その1 については、一応分かってはいたのですが自分の理解で間違いないのかどうか確認したかったので。

      しかし、記憶クラス指定子とか修飾子とかそういったところまで深くは考えていなかったので、なるほどな~、と思いました。

      その2 については、 FAQ でしたか (^^;
      何処だったかで

      char *ptest = "test";



      char stest[] = "test";
      char *ptest = stest;



      char stest[] = "test";
      char *ptest = &stest[0];

      が等価であるというような解説があったので、「2番目と3番目は同じだけど、1番目は違わないか? っていうかこれって書き換えようとしたらエラーにならない? コンパイルはできるの?」という疑問から始まったのでした。
      実際、コンパイルしたものを実行してみるとセグメンテーションフォルトとなりました。というわけで、件の解説を信じちゃった人はご愁傷様です、って感じです。

      んでも、それぞれコンパイルした時にマシン語レベルでどのような違いになるのか分からないと、なかなかこれらの違いは理解出来ないかもなぁ、とは思いますね。 1番目の場合は ptest は実行コード中に埋め込まれた文字列を直接参照するのに対し、 2・3番目では実行コード中に埋め込まれた文字列を一旦変数領域にコピーした後、 ptest はその変数を参照する、ということで合ってるかな。
      親コメント
      • ん?

        char *ptest = "test" のとき "test" の実体がどこにあるかは、環境依存かな?
        親コメント
        • 単にエロいだけなので、混乱させる方向で。(?

          #include
          main()
          {
                  char *s1 = "test";
                  const char *s2 = "test";
                  char s3[] = "test";
                  const char s4[] = "test";

                  printf("s1=%s[%p]\n",s1,s1);
                  printf("s2=%s[%p]\n",s2,s2);
                  printf("s3=%s[%p]\n",s3,s3);
                  printf("s4=%s[%p]\n",s4,s4);
           
          • gcc-3.xの場合、-f[no-]writable-stringsで、書き込み可能な領域に文字列リテラルを書き込むか選択でき、-f[no-]merge-constantsで、同じ文字列(と、浮動小数点定数)を同じ領域に確保するか選択できますよ。
            デフォルトは、-fno-writable-strings-fmerge-constantsです。
            2.95.xは未確認ですが、この手のオプション選択機能は、古めのコンパイラ(10年近く前のコンパイラ--THINK C 6.0にもありました)でも持っている機能なので、2.95ともなると当然のようにあるのではないでしょうか。

            また、(char *)(char [])は、同一視されたりされなかったりしますが、基本的に別物です。 例外は、関数の引数宣言と、関数の引数にされた変数/定数のみです。

            このへんに関する話題で、ある書籍からの引用を紹介します。 「エキスパートCプログラミング(Peter van der Linden著、梅原 系 訳、アスキー出版局)」という本のページ(第9章「配列についてもう少し」)から:
            「プログラミング言語C」第2版の99ページ(日本語番の121ページ)の終わりの方にはこんな記述がある。
            関数定義の仮引数としては、
            char s[];
            および、
            char *s;
            は全く同一である。
            (中略)
            K&R第2版から、この部分だけは破り捨ててしまえ! これが通用するのは、関数定義ので引数定義という特殊な状況だけだという点を読み飛ばす危険が高すぎる--とりわけ、その周りでは、配列の添字はポインタ+オフセットの形式出かけるなんて話題を持って来てるんじゃね。
            私は、これを読んで初めてポインタと配列の違いを理解しました。まあ、その前にろくな解説書に巡り合わなかっただけで、良質な解説書は他にもたくさんあるとは思いますが。
            # 軽いノリの文体が性に合うなら、この本はかなりお勧め。というか、C関係の細かな話が出るたびに勧めてますな(笑)。
            --
            巧妙に潜伏したバグは心霊現象と区別が付かない。
            親コメント
    • その1の最後の方:s/指定指圧界/指定子扱い/
typodupeerror

192.168.0.1は、私が使っている IPアドレスですので勝手に使わないでください --- ある通りすがり

読み込み中...