配列とポインタの違い(p87)

変数名とは

「変数名」とは、データの格納領域の先頭アドレスを、human-readableに置き換えたシンボル名を指します。IPアドレスとホスト名の関係ですね。シンボル名に対応する格納領域の先頭アドレスは、プログラム内では「&」演算子でアドレスを表示したり、nmコマンドのダンプ結果で確認できます。

配列とポインタの違い

pointer.c

#include <stdio.h>

char s1[] = "This is array.";
char *s2 = "This is pointer.";

int main()
{
        printf(" s1 = 0x%p\n", s1);
        printf("&s1 = 0x%p\n", &s1);
        printf(" s2 = 0x%p\n", s2);
        printf("&s2 = 0x%p\n", &s2);
        printf("sizeof(s1) = %d\n", sizeof(s1));
        printf("sizeof(s2) = %d\n", sizeof(s2));

        return 0;
}

実行結果

 s1 = 0x0x804a020
&s1 = 0x0x804a020
 s2 = 0x0x8048560
&s2 = 0x0x804a030
sizeof(s1) = 15
sizeof(s2) = 4

配列名を右辺値として参照すると、その配列名(シンボル)に対応する格納領域の先頭アドレスに展開されます。これは、&演算子をつけたときに表示されるアドレスと同じです。そのため、s1==&s1となります。

一方、ポインタ変数を右辺値として参照すると、そのポインタ変数に格納されたデータ(ポインタ値)に展開されます。もちろんポインタ変数自身も、特定の格納領域の先頭アドレスに対応しているので、&演算子をつけると、対応するアドレスに展開されます。ポインタ変数の領域に格納された「ポインタ値」と、シンボルの「アドレス」は異なるものなので、s2!=&s2となります。

nmコマンドで、このプログラムのシンボルテーブルをダンプしてみると、以下のように、シンボルs1とs2に対応する領域の先頭アドレスを確認することができます(最後の2行)。

$ nm pointer
08049f14 d _DYNAMIC
0804a000 d _GLOBAL_OFFSET_TABLE_
0804855c R _IO_stdin_used
         w _ITM_deregisterTMCloneTable
         w _ITM_registerTMCloneTable
         w _Jv_RegisterClasses
080486c0 r __FRAME_END__
08049f10 d __JCR_END__
08049f10 d __JCR_LIST__
0804a034 D __TMC_END__
0804a034 B __bss_start
0804a018 D __data_start
080483d0 t __do_global_dtors_aux
08049f0c t __do_global_dtors_aux_fini_array_entry
0804a01c D __dso_handle
08049f08 t __frame_dummy_init_array_entry
         w __gmon_start__
08049f0c t __init_array_end
08049f08 t __init_array_start
08048540 T __libc_csu_fini
080484d0 T __libc_csu_init
         U __libc_start_main@@GLIBC_2.0
08048350 T __x86.get_pc_thunk.bx
0804a034 D _edata
0804a038 B _end
08048544 T _fini
08048558 R _fp_hw
080482b4 T _init
08048320 T _start
0804a034 b completed.6339
0804a018 W data_start
08048360 t deregister_tm_clones
080483f0 t frame_dummy
0804841c T main
         U printf@@GLIBC_2.0
08048390 t register_tm_clones
0804a020 D s1
0804a030 D s2