C言語のmain関数でよくint main(int argc, char *argv[])
やint main(int argc, char **argv)
と記載する。argc
はプログラム起動時の引数の数ということは直感的に理解しやすいが、argv
については書き方が2パターンあるため混乱しやすい。そのため、違いを確認してみる。
結論
出力結果は同じですが、引数として受け取った文字列へのアクセス方法が違います。
-
*argv[]
は文字列へのポインタの配列である -
**argv
は文字列へのポインタのポインタである
検証
ソースコードは以下の通り。*argv[]
と**argv
を表示させ違いを確認する。
argType.c
#include <stdio.h>
#include <string.h>
/*
char *argv[]と char **argvの違いを確認するためメモリアドレスを表示するプログラム
メモリアドレスは32bitOSであれば4バイト、64bitOSであれば8バイトとなる
*/
int main(int argc, char *argv[]){
int i;
// 引数の値、サイズ、変数の先頭アドレスを表示
for(i=0;i<argc;i++){
printf("\t* %s, valueSize: %ld, Address:%p (+%ldbytes -> next address)\n", argv[i], strlen(argv[i])+1, argv[i], strlen(argv[i])+1);
}
// *argv[]を用いた場合の情報を表示
printf("char *argv[]\n");
for(i=0;i<argc;i++){
printf("\targv[%d]: %p\n", i, argv[i]);
}
// **argvを用いた場合の情報を表示
printf("char **argv\n");
for(i=0;i<argc;i++){
printf("\targv+%d: %p -> *(argv+%d): %p\n", i, (argv+i), i, *(argv+i));
}
return 0;
}
実行結果は以下の通り。
*argv[]
の場合は、引数文字列を値として持つchar型変数の先頭のアドレスを持っている。
**argv
の場合は、引数文字列を値として持つchar型変数の先頭アドレスを持っているポインタ変数を示すアドレスを持っている。
引数は文字列は各要素の終端にNULL文字(
\0
)がセットされるため、次の引数のアドレスを算出するためには文字数+1
の値を加算したものが次の変数のアドレスとなる。
$ ./argType 1 10 100 1000
* ./argType, valueSize: 10, Address:0x7ffff7e674b8 (+10bytes -> next address)
* 1, valueSize: 2, Address:0x7ffff7e674c2 (+2bytes -> next address)
* 10, valueSize: 3, Address:0x7ffff7e674c4 (+3bytes -> next address)
* 100, valueSize: 4, Address:0x7ffff7e674c7 (+4bytes -> next address)
* 1000, valueSize: 5, Address:0x7ffff7e674cb (+5bytes -> next address)
char *argv[]
argv[0]: 0x7ffff7e674b8
argv[1]: 0x7ffff7e674c2
argv[2]: 0x7ffff7e674c4
argv[3]: 0x7ffff7e674c7
argv[4]: 0x7ffff7e674cb
char **argv
argv+0: 0x7ffff7e66128 -> *(argv+0): 0x7ffff7e674b8
argv+1: 0x7ffff7e66130 -> *(argv+1): 0x7ffff7e674c2
argv+2: 0x7ffff7e66138 -> *(argv+2): 0x7ffff7e674c4
argv+3: 0x7ffff7e66140 -> *(argv+3): 0x7ffff7e674c7
argv+4: 0x7ffff7e66148 -> *(argv+4): 0x7ffff7e674cb
個人的には*argv[]
のほうが個のみである。理由としては、配列番号で明示的に何個目の引数か直感的に把握でき、ダブルポインタを意識しなくて済むため。
おまけ
ダブルポインタ(ポインタのポインタ)をもっとわかりやすく理解するための検証コードです。
doublePointer.c
#include <stdio.h>
int main(int argc, char *argv[]){
int i=100; // int型変数
int *p2i = NULL; // int型変数へのポインタ変数
int **p2p2i = NULL; // int型変数へのポインタ変数へのポインタ変数
p2i = &i; // ポインタ変数へint型変数 i のアドレスを格納
p2p2i = &p2i; // ポインタ変数へポインタ変数p2iのアドレスを格納
printf("int i value: %d, size: %ld, Address: %p\n", i,sizeof(i), &i);
printf("int p2i value: %p, size: %ld, Address: %p\n", p2i, sizeof(p2i), &p2i);
printf("int p2p2i value: %p, size: %ld, Address: %p\n", p2p2i, sizeof(p2p2i), &p2p2i);
printf("p2p2i: %p (value: %p) -> p2i: %p (value: %p) -> i: %p (value: %d)\n", &p2p2i, p2p2i, &p2i, p2i, &i, i);
return 0;
}
$ ./doublePointer
int i value: 100, size: 4, Address: 0x7ffe89d25f74
int p2i value: 0x7ffe89d25f74, size: 8, Address: 0x7ffe89d25f78
int p2p2i value: 0x7ffe89d25f78, size: 8, Address: 0x7ffe89d25f80
p2p2i: 0x7ffe89d25f80 (value: 0x7ffe89d25f78) -> p2i: 0x7ffe89d25f78 (value: 0x7ffe89d25f74) -> i: 0x7ffe89d25f74 (value: 100)