そもそも C に文字列型なんてありません.文字列みたいなアレは,文字の配列型です.
ポインタと配列型は,全く違うものです.そもそも typeof (GCC 拡張) で取れる型が違う,sizeof で取れるオブジェクトサイズも違う.
まぁ,文字列リテラルが存在していて,唯一複合型なのに静的 (static) 領域に初期化して置いとくことができたりして,いろいろややこしいのですが.
あと,配列の要素アクセスは,常にポインタ演算経由で行われるのも,ややこしいところですね.配列とポインタを同一視してしまうというもっともありがちな混乱は,ここに由来するのだと思います.アセンブリ言語のレベルでは,配列なんていう高級な概念は存在しなくて,メモリと番地しかありません.ポインタは番地を入れる変数 (メモリやレジスタ)
そんなこんなで,もともとアセンブリに由来する C は同じことを表現する方法が非常にたくさんあることで有名ですが,似ているようで違うものもたくさんあります.それが諸悪の根源ですね.
C99 で複合リテラルが入ったので,さらに変態的な書き方がいろいろ可能になってます.
どれが同じ意味で,どれが違うのか,全部ちゃんとわかってますか ?
#include <stdio.h>
int main() {
char a[] = "abcdefghi"; puts(a); printf("%d\n", sizeof(a)); putchar(a[1]); putchar('\n'); putchar(*(a + 1)); putchar('\n'); putchar(1[a]); putchar('\n'); a[1] = 'z'; puts(a); // a++; ******* error: lvalue required as increment operand ****** // puts(a); char *p1 = (char[]){'a','b','c','d','e','f','g','h','i','\0'}; puts(p1); printf("%d\n", sizeof(p1)); p1[1] = 'z'; puts(p1); p1++; puts(p1); char *p2 = (char[]){"abcdefghi"}; puts(p2); printf("%d\n", sizeof(p2)); p2[1] = 'z'; puts(p2); p2++; puts(p2);
char* const p3 = (char[]){'a','b','c','d','e','f','g','h','i','\0'}; puts(p3); printf("%d\n", sizeof(p3)); p3[1] = 'z'; puts(p3); // p3++; ****** error: increment of read-only variable 'p3' ****** // puts(p3); const char* p4 = (char[]){'a','b','c','d','e','f','g','h','i','\0'}; puts(p4); printf("%d\n", sizeof(p4)); // p4[1] = 'z'; ****** error: assignment of read-only location ****** // puts(p4); p4++; puts(p4);
char *p5 = "abcdefghi"; puts(p5); printf("%d\n", sizeof(p5)); // p5[1] = 'z'; ****** セグメンテーション違反です ****** // puts(p5); p5++; puts(p5);
const char* const p6 = (char[]){'a','b','c','d','e','f','g','h','i','\0'}; puts(p6); printf("%d\n", sizeof(p6)); // p6[1] = 'z'; ****** error: assignment of read-only location ****** // puts(p6); // p6++; ****** error: increment of read-only variable 'p6' ****** // puts(p6); const char p7[] = "abcdefghi"; puts(p7); printf("%d\n", sizeof(p7)); // p7[1] = 'z'; ****** error: assignment of read-only location ****** // puts(p7); // p7++; ****** error: lvalue required as increment operand ****** // puts(p7);
/* error: initializer element is not constant */ // static const char* p8 = (char[]){'a','b','c','d','e','f','g','h','i','\0'};
char (*p9)[10] = (char (*) [10])&"0123456789abcdefghi"; puts(*p9); printf("%d\n", sizeof(*p9)); p9++; // 10 バイト進む puts(*p9);
return 0; }
最後の例の意味がちゃんとわかれば,ポインタを理解できていると思って良いかもしれません.
配列の要素アクセス a[10] は *(a + 10) のシンタックスシュガーだから,*(10 + a) と同じ,すなわち 10[a] と書いても同じ意味,とかはわりと有名な話ですが.a に 10 を足したとき,アドレスがいくつ進むのか ? とかが,a の型の大きさによって決まるのです.
要するに sizeof(*a) ぶん増えます.char だったら 1 (バイト),4 バイト整数だったら 4, 文字の要素数10の配列型 (sizeof は 10) だったら,10 増えます.
追記
同じ意味でも,最適化が絡んでくるとびみょーに違ったりする場合もあります.
参考 : const char* const p = "ABC"; と const char q[] = "ABC"; はどちらがよいか、みたいな与太
|