wakatonoの戯れメモ

はてなダイアリーから引っ越してきました。

C/C++のポインタの機能--配列との関係…

これもひどい。


ポインタ変数と配列との深い関係を表す例を示そう。それは、配列の変数名をそのままポインタ変数名として扱えるということだ
扱えないです…。
配列の変数名は、配列の先頭アドレスを参照するためには使えても、それをそのまま変数としては扱えませぬ。
「変数名として扱える」のであれば、任意の値を入れることも可能なはず。ということで、検証してみました。
以下にサンプルソースを示します。これはあくまで「コンセプト」です。

main()
{
int n = { 1,2,3,4,5 };
int m
= { 1,2,3,4,5 };
n = m;
printf("%d\n", *n);
}
これをコンパイルすると、以下のような結果が…。

wakatono@kids:~/zdnet$ !cc
cc array.c
array.c: In function ‘main’:
array.c:5: error: incompatible types in assignment
array.c:6: warning: incompatible implicit declaration of built-in function ‘printf’
5行目(n = m;)の部分で、エラーが出てはじかれる。
いいたかないが、のっけの説明から間違っている、というわけである。

あと、完全に筆者の方の理解が間違ってるところが…


 ちなみに文字列は、sではなく*sに直接代入する記述も可能だ。以下に例を示す。両者とも処理の仕方は特に変わりないことがお分かりいただけるだろう。これは文字列のみに有効な記述であり、数値型でint *n = { 1, 2, 3 };などと記述することはできない。
細かい話かもしれないすけど、s="Hello";という書き方は単純に好かんw
オレが好かんだけならばまだしも、書いた人は「処理の仕方」としては、s = { 'H', 'e', 'l', 'l', 'o', '\0' } というのとはまったく違ってるということに気がついてないのでしょーか?
s
="Hello"という記述は、Hello\0という文字列(6バイト)を格納する領域へのポインタを「初期化時に渡している」という意味であり、s = { 'H', 'e', 'l', 'l', 'o', '\0' } ってのは、s[0]、s[1]、s[2]、…、s[5]にそれぞれの文字コードを代入している、ということに気付いてないのでしょうか?


追記
sorry.間違いでしたorz
ありがとうございます>通りすがりさん
JISX3010を見て、仕様としては同じ&コンパイラの実装として異なるコードを出力することがある、ということも理解できました。
さらに面白いこともわかりました…。

アドレス値を
これについて、前者と後者について、アセンブリ言語に展開された例を示します。


s="Hello"と書いた場合のアセンブリ言語での展開例は以下のとおり(printf()の実行まで)
.file "array2a.c"
.section .rodata
.LC1:
.string "%c\n"
.LC0:
.string "Hello"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $36, %esp
movl .LC0, %eax
movl %eax, -10(%ebp)
movzwl .LC0+4, %eax
movw %ax, -6(%ebp)
movzbl -10(%ebp), %eax
movsbl %al,%eax
movl %eax, 4(%esp)
movl $.LC1, (%esp)
call printf

s = { 'H', 'e', 'l', 'l', 'o', '\0' } と書いた場合のアセンブリ言語での展開例(最初のprintf()の実行まで)
.file "array2.c"
.section .rodata
.LC0:
.string "%c\n"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $36, %esp
movb $72, -10(%ebp)
movb $101, -9(%ebp)
movb $108, -8(%ebp)
movb $108, -7(%ebp)
movb $111, -6(%ebp)
movb $0, -5(%ebp)
movzbl -10(%ebp), %eax
movsbl %al,%eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
見ればわかるが、text領域に配列の初期化処理が入ってるか、それともデータ領域にHello\0というデータが入っていて、それを参照しているかがすでに違っている。この2つを「いっしょのもの」として説明するほうが無理があると思うです。

追記:どちらかというと、コンパイラのほうがわかりにくい内容を出力してるともとれます。
JISX3010では、


char s="abc", t[3]="abc";は、要素を単純文字列リテラルで定義された“単なる”char型の配列オブジェクトsおよびtを定義する。この宣言は、
char s = {'a','b','c','\0'},
t[3] = {'a','b','c'}
と同一となる。配列の内容は、変更できる。一方、宣言
char *p = "abc";
は、pを“charへのポインタ”型として定義し、要素が単純文字列リテラルで初期化され、長さが4のcharの配列”型オブジェクトを指すように初期化する。pを用いてその配列の内容を変更しようとした場合、その動作は未定義である。
とあります。
…アセンブリのコード上、.string "Hello"と書かれているのですが、"Hello"を「長さ6のcharの配列」と認識できなかった…
余談ですが、char s = {'a','b','c','\0' } と書かれてるほうは、似たような内容のアセンブリを出力する「異なるコード」をかけました。
意味としては全く違うんで、ちょっと気をつけます…。

main()
{
char a='a';
char b='b';
char c='c';
char d='\0';
printf("%s\n",&a);
}

main()
{
char a={ 'a','b','c','\0'};
printf("%s\n",&a);
}

は、同じアセンブラのコードを出力します。

あと、「数値型でint *n = { 1, 2, 3 };などと記述することはできない。」という記述があったけど、これは単に*nは入れ物の場所を示しているだけに過ぎず、この状態でブツをどうにもできない、というだけであり、この変数に「すでに宣言された配列のアドレスを入れる」ことは可能。
微妙に中途半端…。実際に、以下のコードはコンパイル可能だし、実行も可能。

追記:もっとも、この場合のo[0]とかo[1]とかの場合は、前述のJISX0301の規定では「動作は未定義」であり、動作するしないは完全に処理系依存という感じかな。
さらに追記:「動作が未定義」なのは、ポインタを用いて「配列の内容を変更する」場合ですね。よく読むべきでした。


main()
{
int n = { 1, 2, 3 };
int *o = n;
printf("%d\n",o[0]);
printf("%d\n",o[1]);
printf("%d\n",o[2]);

}

<追記>
動作が未定義なのは、以下の場合ですね。


main()
{
int n = { 1, 2, 3 };
int *o = n;
printf("%d,%d,%d\n",o[0],o[1],o[2]);
o[2] = 10;
printf("%d,%d,%d\n",o[0],o[1],o[2]);
}
ちなみにgcc 4.1.2では、o[2]=10;で、o[2]の値を10に変更でけました。

main()
{
int *n = "Hello;
printf("%c,%c,%c\n",n[0],n[1],n[2]);
n[2] = 'W';
printf("%c,%c,%c\n",n[0],n[1],n[2]);
}
通りすがりさん、ありがとうございます。
ちなみにこれは、"Hello"という文字列リテラルが読み出し専用のdataセクション(.rodataセクション)に配置されるため、n[2]='W'という処理をやろうとするとSegmentation Faultで落ちます。
アセンブリにすると以下のとおり。

.file "mai.c"
.section .rodata
.LC0:
.string "Hello"
ちなみに、上記の.rodataを.dataに変更すると、Segmentation Faultは出なくなります(あたりまえですが)。時間が出来るとアセンブラばっかり見てるなぁ。
</追記>

もうそろそろ疲れてきたので終わりにしたいんだけど、最後に…


さきほど*sで1つの文字列を表していたが、この例では*sによって複数の文字列を要素とする配列を表している。ただ、さきほどs[]でも*sでも1つの文字列を表すことができ、処理するにあたって特に変わりないことを説明したところだ。
「複数の文字列を要素とした配列」って何?
複数の文字列「が格納されるアドレス」を要素とした配列ならばわかるけど、この書法では、アドレスしか配列にならねえです(汗

…疲れた…