0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

`char *argv[]`と`char **argv`の違い

Last updated at Posted at 2024-08-20

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)
0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?