Endowsの日記: Endows の へっぽこプログラマ日誌 第19回 6
ちょっとお勉強、ということで書いたコードを 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 消えたぞ? ありゃりゃ?
教えて偉い人~ (またかよ)
わりとFAQですよん(特にその2) (スコア:1)
その1について:
宣言は、記憶クラス指定子(auto、static等)、型指定子(int、double等)、修飾子(const、volatileの2つ)が、任意の順番で現れたものの後に宣言子(ポインタ、配列、引数リスト、修飾子を含む識別子)のリストがくっついたものです。正確な定義はプログラミング言語C 第2版 付録AのA8節、A8.5節あたりを参照のこと。
なので、int const *とconst int *は同じ意味で、「(読み込み専用のint)へのポインタ」。int * constは、ポインタがconst修飾されているので、「intへの(読み込み専用のポインタ)」となります。 余談ですが、宣言にまつわるトリビアでも。
でも、register volatileって、「できるだけレジスタにいれっぱなしにしろ。ただし、レジスタに入れっぱなしなどの最適化は絶対にするな」という意味になるから……どうしろと?(笑)
ああそうか。独自拡張で、特定のレジスタを割り当てる構文を用意して、必ずそれに割り当てろっていう意味に使うなら……
long auto const int volatile signed hoge;
のような変態宣言も可能です。
unsigned typedef char Byte;
のようなこれまた変態宣言も可能です。
その2について:
文字列リテラルは、別にconst宣言された型としては扱われないので、文字列を直接変更するような構文も許可されています(const char*として扱われるなら、char*型の変数には代入不可……は特例で認めるにしても、文字列自体の変更は、警告ではなくエラーになりますよ)。ただし、その時の挙動は処理系依存となっているので、多くのコンパイラでは警告がでるようになっています。
*("str"+0)で警告がでないのは、その形が警告パターンにマッチしないからでしょう。苦労してこの形に対する警告を実装しても、狙った人しかそんなコードを書かないでしょうしね。
ちなみに、gcc-3.2.3では、0["str"]='a'も警告がでませんでした。
なお、文字列リテラルを上書きしたときには、
- 問題なく動く
- 読み込み専用領域を書き換えようとした咎で強制終了(セグメンテーションフォルトとか一般保護法違反とか)
- 同じ文字列リテラルを使っている別の場所でも文字列が変わる
等の事態が考えられます。巧妙に潜伏したバグは心霊現象と区別が付かない。
Re:わりとFAQですよん(特にその2) (スコア:1)
その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 はその変数を参照する、ということで合ってるかな。
Re:わりとFAQですよん(特にその2) (スコア:1)
char *ptest = "test" のとき "test" の実体がどこにあるかは、環境依存かな?
Re:わりとFAQですよん(特にその2) (スコア:0)
#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);
Re:わりとFAQですよん(特にその2) (スコア:1)
デフォルトは、-fno-writable-stringsと-fmerge-constantsです。
2.95.xは未確認ですが、この手のオプション選択機能は、古めのコンパイラ(10年近く前のコンパイラ--THINK C 6.0にもありました)でも持っている機能なので、2.95ともなると当然のようにあるのではないでしょうか。
また、(char *)と(char [])は、同一視されたりされなかったりしますが、基本的に別物です。 例外は、関数の引数宣言と、関数の引数にされた変数/定数のみです。
このへんに関する話題で、ある書籍からの引用を紹介します。 「エキスパートCプログラミング(Peter van der Linden著、梅原 系 訳、アスキー出版局)」という本のページ(第9章「配列についてもう少し」)から: 私は、これを読んで初めてポインタと配列の違いを理解しました。まあ、その前にろくな解説書に巡り合わなかっただけで、良質な解説書は他にもたくさんあるとは思いますが。
# 軽いノリの文体が性に合うなら、この本はかなりお勧め。というか、C関係の細かな話が出るたびに勧めてますな(笑)。
巧妙に潜伏したバグは心霊現象と区別が付かない。
誤字修正(シグネチャつけたくないのでAC) (スコア:0)