C言語の配列の話の続き
対象読者
C 言語初学者
解決すること
C 言語の配列を少し深堀する
内容
以前、C 言語の配列の話で、配列へのアクセスについて書きましたが、サンプルコードを追加しておきます。
2次元配列について以下4通りのアクセス。結果はどれも 3 。
#include<stdio.h>
int main(){
int array[2][2] = {{0, 1}, {2, 3}};
printf("access to array a[2][2] = {{0, 1}, {2, 3}}\n-----\n");
printf("by array[1][1] : %d\n", array[1][1]);
printf("by *(array[1] + 1) : %d\n", *(array[1] + 1));
printf("by *((int *)array + 3) : %d\n", *((int*)array + 3));
printf("by *(*(array + 1) + 1)) : %d\n", *(*(array + 1) + 1));
}アセンブルされたコードは以下の様になる(gcc -S -masm=intel のインテル表記)。
.LC1:
.string "by array[1][1] : %d\n"
.LC2:
.string "by *(array[1] + 1) : %d\n"
.LC3:
.string "by *((int *)array + 3) : %d\n"
.LC4:
.string "by *(*(array + 1) + 1)) : %d\n"
.text
...snip...
sub rsp, 32
mov rax, QWORD PTR fs:40
mov QWORD PTR -8[rbp], rax
xor eax, eax
mov DWORD PTR -32[rbp], 0
mov DWORD PTR -28[rbp], 1
mov DWORD PTR -24[rbp], 2
mov DWORD PTR -20[rbp], 3
lea rax, .LC0[rip]
mov rdi, rax
call puts@PLT
mov eax, DWORD PTR -20[rbp]
mov esi, eax
lea rax, .LC1[rip]
mov rdi, rax
mov eax, 0
call printf@PLT
mov eax, DWORD PTR -20[rbp]
mov esi, eax
lea rax, .LC2[rip]
mov rdi, rax
mov eax, 0
call printf@PLT
lea rax, -32[rbp]
add rax, 12
mov eax, DWORD PTR [rax]
mov esi, eax
lea rax, .LC3[rip]
mov rdi, rax
mov eax, 0
call printf@PLT
lea rax, -32[rbp]
add rax, 12
mov eax, DWORD PTR [rax]
mov esi, eax
lea rax, .LC4[rip]
mov rdi, rax
mov eax, 0
call printf@PLT
...snip...最後のアクセスの仕方は2回 deref を含んでいますが、アドレスの中身を取り出したのは他のと同様に1度だけ。つまり左辺値の処理の時に、対象が配列だと型の昇格はあっても実際の deref はせずにアドレス計算だけをする。
もちろん、int *array[2] とは全く別もの。
こちらは int へのポインタを2つ分確保して、その先頭へのアドレスが array に入ってる。この場合は、
3番めの例の*((int*)array + 3)はうまく行かない。他のアクセスの仕方はきちんと2回 deref するようになる。
配列評価の時には、順に処理して array[] の場合はアドレス計算、ポインタの場合は deref、そして最後の評価の際に deref するかどうかを決めるということでしょうかね。まぁどっちにしても混同しちゃ駄目ってことで、配列は一定の大きさのメモリをスタックにとるだけ。