argvのメモリ構造
main関数のargvの構造がどうなっているのか調べてみた。
1 #include <stdio.h> 2 3 int main(int argc, char *argv[]) 4 { 5 int i; 6 7 printf("%p : argc %d\n", &argc, argc); 8 printf("%p : argv %p\n", &argv, argv); 9 printf("%p : argv+1 %p\n", &argv+1, *(&argv+1)); 10 11 for(i=0; i<=argc; i++){ 12 if(argv[i] == NULL){ 13 printf("%p : argv[%d] (NULL)\n", &argv[i], i); 14 break; 15 } 16 printf("%p : argv[%d] %p ----> %s\n", &argv[i], i, argv[i], argv[i]) ; 17 } 18 19 return 0; 20 }
出力結果
$ ./sample aaa bbbb ccccc 0xbfa0e330 : argc 4 0xbfa0e334 : argv 0xbfa0e3c4 0xbfa0e338 : argv+1 0xbfa0e3d8 0xbfa0e3c4 : argv[0] 0xbfa0f466 ----> ./sample 0xbfa0e3c8 : argv[1] 0xbfa0f46f ----> aaa 0xbfa0e3cc : argv[2] 0xbfa0f473 ----> bbbb 0xbfa0e3d0 : argv[3] 0xbfa0f478 ----> ccccc 0xbfa0e3d4 : argv[4] (NULL)
参照関係を整理してまとめると…
addr | symbol | value | refer by | addr | value | addr | value | ||
---|---|---|---|---|---|---|---|---|---|
0xbfa0e330 | argc | 0x00000004 | |||||||
0xbfa0e334 | argv | 0xbfa0e3c4 | --------> | *argv | 0xbfa0e3c4 | 0xbfa0f466 | --------> | 0xbfa0f466 | ./sample\0 |
0xbfa0e338 | &argv+1 | 0xbfa0e3d8 | *(argv+1) | 0xbfa0e3c8 | 0xbfa0f46f | --------> | 0xbfa0f46f | aaa\0 | |
*(argv+2) | 0xbfa0e3cc | 0xbfa0f473 | --------> | 0xbfa0f473 | bbbb\0 | ||||
*(argv+3) | 0xbfa0e3d0 | 0xbfa0f478 | --------> | 0xbfa0f478 | ccccc\0 | ||||
*(argv+4) | 0xbfa0e3d4 | (NULL) | |||||||
*(&argv+1) | 0xbfa0e3d8 | (ゴミ) |
スタートアップルーチンからmain()に渡されるのは、argv=「0xbfa0e3c4」という値である。これは、引数のアドレスリストが格納された領域の位置情報(ポインタ値)である。シンボル「argv」にバインドされた領域の隣の領域(&argv+1)を見ても分かるように、領域argvの隣には、残存データが格納されている。
引数のアドレスリストの先頭位置が分かったので、この位置から+1(*(argv+1))すれば、第2引数の位置情報が取得できる。第3引数以降も同様である。この位置情報を使えば、引数の実体を間接参照することができる。位置情報がNULLであれば、それはアドレスリストの終端を意味する。
というように、アドレスリストの要素がメモリ上の連続領域に格納されていることが、実装上保証されているからこそ、*(argv+1)といったような「隣の領域を除く」操作が許されることは忘れないでいたい。アドレスリストで管理しているから、本来なら、引数文字列の実体は飛び飛びに格納されていても(アドレスリストを片手に文字列実体を参照する限り)問題は無いのだが。
アドレスリストで間接参照する利点は、文字列実体を可変長で持つことができる、という点である。これが配列であると、全要素のサイズを最長の長さで統一する必要があるため、リソース的に不経済となってしまう。例えばELF形式において、各セクション名の実体は.shstrtabセクションに集約されるが、個々のセクション名の参照はポインタ(Elf_shdr.sh_name)で間接参照するのも、この利点によるものである。