私は最近Cを学び始めましたが、たとえば次の行を書くとき、ポインター構文の理解に問題があります。
int ** arr = NULL;
どうすればわかります:
arrは整数のポインターへのポインターです
arrは、整数へのポインターの配列へのポインターです。
arrは、整数の配列へのポインターの配列へのポインターです。
int **
と同じではありませんか?
パラメーターとしてchar ** s
を受け取る関数がある場合、pointer
の配列へのポインターの配列へのポインターを意味する、文字列の配列へのchars
として参照したい] _ですが、char
へのポインターへのポインターでもありますか?
int **
と同じではありませんか?
型システムの欠陥と考えられるものを発見しました。指定したオプションはすべて真である可能性があります。これは、基本的にプログラムメモリのフラットビューから派生したもので、単一のアドレスを使用してさまざまな論理メモリレイアウトを参照できます。
Cの開始以来、Cプログラマがこれに対処してきた方法は、慣習を整えることです。そのようなポインタを受け入れる関数のサイズパラメータを要求し、メモリレイアウトに関する仮定を文書化するなど。または、特別な値で配列を終了することを要求し、バッファーへのポインターの「ギザギザ」バッファーを許可します。
ある程度の明確化が必要だと感じています。ここで他の非常に良い答えを参照するとわかるように、配列は間違いなくポインターではありません。しかし、それらは、それらについて教える際に数十年の長きにわたる誤りを保証するのに十分な文脈で崩壊します(しかし、私は逸れます)。
私が最初に書いたのは、次のようなコードです。
void func(int **p_buff)
{
}
//...
int a = 0, *pa = &a;
func(&pa);
//...
int a[3][10];
int *a_pts[3] = { a[0], a[1], a[2] };
func(a_pts);
//...
int **a = malloc(10 * sizeof *a);
for(int i = 0; i < 10; ++i)
a[i] = malloc(i * sizeof *a[i]);
func(a);
func
と仮定し、各コードスニペットは個別の翻訳単位でコンパイルされます。各例(タイプミスがない限り)は有効なCです。引数として渡されると、配列は「ポインターからポインター」に減衰します。 func
の定義は、パラメーターの型だけから渡されたものを正確に知るにはどうすればよいのでしょうか?!答えはできないということです。 p_buff
の静的型はint**
ですが、func
が非常に異なる有効型を持つオブジェクト(の一部)に間接的にアクセスすることも可能です。
宣言int **arr
は、「整数へのポインターへのポインターとしてarrを宣言する」と言います。 (有効な場合)単一の整数オブジェクトを指す(有効な場合)単一のポインターを指します。どちらのレベルの間接指定でもポインター演算を使用することが可能であるため(つまり、*arr
はarr[0]
と同じで、**arr
はarr[0][0]
と同じです)、オブジェクトはaccessingのいずれかの質問に使用します(つまり、2番目は整数へのポインターの配列にアクセスし、3番目は配列にアクセスします)整数配列の最初の要素へのポインターの)、ポインターが配列の最初の要素を指している場合...
ただし、arr
は、singleへのポインタとしてsingle整数オブジェクト。 defined次元のarrayへのポインタを宣言することもできます。ここで、a
は、10個の整数の配列へのポインターの10要素配列へのポインターとして宣言されています。
cdecl> declare a as pointer to array 10 of pointer to array 10 of int;
int (*(*a)[10])[10]
実際には、配列ポインターは、定数次元の多次元配列を関数に渡すため、および可変長配列を渡すために最も使用されます。変数を配列へのポインタとして宣言する構文はめったに見られません。関数に渡されるときはいつでも、代わりに「未定義サイズの配列」型のパラメータを使用する方が多少簡単なので、宣言する代わりに
void func(int (*a)[10]);
使用できます
void func(int a[][10])
10個の整数の配列の多次元配列を渡します。または、typedef
を使用して頭痛を軽減することもできます。
どうすれば知ることができます:
- arrは整数のポインターへのポインターです
常に整数へのポインターへのポインターです。
- arrは、整数へのポインターの配列へのポインターです。
- arrは、整数の配列へのポインターの配列へのポインターです。
それは決してありえません。整数へのポインターの配列へのポインターは、次のように宣言されます。
int* (*arr)[n]
貧しい教師/本/チュートリアルによってint**
を使用するようにだまされたように聞こえます。 here および here および (配列ポインターについての詳細な説明)here で説明されているように、それはほとんど常に間違った慣行です。
編集
最後に、配列とは何か、ルックアップテーブルとは何か、後者はなぜ悪いのか、代わりに何を使うべきなのかを説明する詳細な記事を書きました: Correctly allocating multi-dimensional array 。
変数の宣言のみがあると、3つのケースを区別できません。 int *x[10]
intまたは他の何かへの10個のポインターの配列を表現します。しかし、int **x
can-ポインタの計算により、3つの異なる方法で使用できます。各方法は、異なるメモリレイアウトを想定し、間違った仮定をする(良い)機会を与えます。
次の例を考えてください。int **
は3つの異なる方法で使用されます。つまり、p2p2i_v1
(単一の)intへのポインターへのポインターとして、p2p2i_v2
intへのポインターの配列へのポインターとして、およびp2p2i_v3
int配列へのポインターへのポインターとして。これらの3つの意味をタイプだけで区別することはできません。タイプはint**
3つすべて。しかし、初期化が異なると、それぞれに間違った方法でアクセスすると、最初の要素にアクセスする場合を除き、予測不能な結果が生じます。
int i1=1,i2=2,i3=3,i4=4;
int *p2i = &i1;
int **p2p2i_v1 = &p2i; // pointer to a pointer to a single int
int *arrayOfp2i[4] = { &i1, &i2, &i3, &i4 };
int **p2p2i_v2 = arrayOfp2i; // pointer to an array of pointers to int
int arrayOfI[4] = { 5,6,7,8 };
int *p2arrayOfi = arrayOfI;
int **p2p2i_v3 = &p2arrayOfi; // pointer to a pointer to an array of ints
// assuming a pointer to a pointer to a single int:
int derefi1_v1 = *p2p2i_v1[0]; // correct; yields 1
int derefi1_v2 = *p2p2i_v2[0]; // correct; yields 1
int derefi1_v3 = *p2p2i_v3[0]; // correct; yields 5
// assuming a pointer to an array of pointers to int's
int derefi1_v1_at1 = *p2p2i_v1[1]; // incorrect, yields ? or seg fault
int derefi1_v2_at1 = *p2p2i_v2[1]; // correct; yields 2
int derefi1_v3_at1 = *p2p2i_v3[1]; // incorrect, yields ? or seg fault
// assuming a pointer to an array of pointers to an array of int's
int derefarray_at1_v1 = (*p2p2i_v1)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v2 = (*p2p2i_v2)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v3 = (*p2p2i_v3)[1]; // correct; yields 6;
どうすれば知ることができます:
arrは整数のポインターへのポインターです
arrは、整数へのポインターの配列へのポインターです。
arrは、整数の配列へのポインターの配列へのポインターです。
できません。それらのどれでも構いません。最終的には、どのように割り当て/使用するかによって異なります。
したがって、これらを使用してコードを記述する場合は、それらを使用して何をしているのかを文書化し、それらを使用して関数にサイズパラメータを渡し、一般的に使用する前に割り当てたものを確認してください。
ポインターは、単一のオブジェクトを指しているのか、配列の要素であるオブジェクトを指しているのかという情報を保持しません。さらに、ポインター算術演算では、単一オブジェクトは1つの要素から構成される配列のように見なされます。
これらの宣言を検討してください
int a;
int a1[1];
int a2[10];
int *p;
p = &a;
//...
p = a1;
//...
p = a2;
この例では、ポインターp
はアドレスを扱います。格納するアドレスがa
のような単一のオブジェクトを指すのか、配列の最初の要素を指すのかa1
エレメントが1つのみ、または配列の最初のエレメントまでa2
には10個の要素があります。
のタイプ
int ** arr;
有効な解釈は1つだけです。それは:
arr is a pointer to a pointer to an integer
上記の宣言以上の情報がない場合、つまり、arr
がおそらく初期化されている場合、それは別のポインタを指します。初期化されている場合は整数を指します。
適切な初期化を前提として、それを使用する唯一の保証された有効な方法は次のとおりです。
**arr = 42;
int a = **arr;
ただし、Cでは複数の方法で使用できます。
•arrは、整数へのポインターへのポインターとして使用できます(つまり、基本的な場合)
int a = **arr;
•arrは、整数配列へのポインターへのポインターとして使用できます。
int a = (*arr)[4];
•arrは、整数へのポインターの配列へのポインターとして使用できます。
int a = *(arr[4]);
•arrは、整数の配列へのポインターの配列へのポインターとして使用できます。
int a = arr[4][4];
最後の3つのケースでは、配列があるように見えます。ただし、タイプはnot配列です。タイプは常にa pointer to a pointer to an integer
-間接参照はポインター演算です。それは2D配列のようなものではありません。
どちらのプログラムが有効かを知るには、arr
を初期化するコードを調べる必要があります。
更新
質問の更新された部分について:
あなたが持っている場合:
void foo(char** x) { .... };
あなたが確実に知っている唯一のものは、**x
は、charと*x
は、charポインターを提供します(どちらの場合も、x
の適切な初期化が想定されます)。
x
を別の方法で使用したい場合、例えばx[2]
3番目の文字ポインタを取得するには、呼び出し元がx
を初期化して、少なくとも3つの連続した文字ポインタを持つメモリ領域を指すようにする必要があります。これは、foo
を呼び出すためのコントラクトとして説明できます。
ポインターを使用する際には、右から左へ読み上げるコツが1つあります。
int** arr = NULL;
何を取得します:arr
、*
、*
、int
。したがって、配列は整数へのポインターへのポインターです。
int **arr;
はint** arr;
と同じです。
C構文は論理的です。宣言の識別子の前のアスタリスクは変数の型へのポインタを意味するため、2つのアスタリスクは変数の型へのポインタへのポインタを意味します。
この場合、arr
はa pointer to a pointer to integer
。
ダブルポインターにはいくつかの使用法があります。たとえば、ポインターのベクトルへのポインターでマトリックスを表すことができます。このベクトルの各ポインターは、マトリックス自体の行を指します。
次のように、それを使用して2次元配列を作成することもできます。
int **arr=(int**)malloc(row*(sizeof(int*)));
for(i=0;i<row;i++) {
*(arr+i)=(int*)malloc(sizeof(int)*col); //You can use this also. Meaning of both is same. //
arr[i]=(int*)malloc(sizeof(int)*col); }
int ** arr = NULL;
コンパイラに伝える_arr is a double pointer of an integer
および割り当てられたNULL
値。