在C中使用**时的差异

我最近开始学习C语言,而且我在理解指针语法时遇到了一些问题,例如,当我编写以下代码行时:

int ** arr = NULL; 

我如何知道:

  • arr是指向整数的指针的指针

  • arr是指向整数指针数组的指针

  • arr是指向整数数组的指针数组的指针

这与int **不是一回事吗?


同样的问题的另一个问题:

如果我有一个接收char ** s的函数作为参数,我想把它作为一个pointerstring数组的指针,意味着指向一个char数组指针数组的指针,但它也是一个指针到一个char的指针?

这与int **不是一回事吗?

你刚刚发现了types系统中可能被认为是缺陷的东西。 您指定的每个选项都可以为true。 它实质上是从一个程序存储器的平面图导出的,其中一个地址可以用来引用各种逻辑存储器布局。

自C开始以来,C程序员一直在处理这个问题,就是通过一个约定。 比如接受这些指针的函数需要大小参数,并logging他们对内存布局的假设。 或者要求数组终止一个特殊的值,从而允许指向缓冲区的“锯齿形”缓冲区。


我觉得有一定的解释是为了。 正如你在看到其他非常好的答案时所看到的那样, 数组绝对不是指针 。 然而,他们在有足够的背景下衰退成为一个人,在教导他们(但是我离题)中需要长达数十年的错误。

我原来写的代码如下:

 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.当作为parameter passing时,数组将衰变成“指向指针”。 func的定义是如何知道从它的参数types传递过来的呢? 答案是它不能。 p_buff的静态types是int** ,但是它仍然允许func间接地访问(有部分的)有效types差异很大的对象。

声明int **arr说:“将arr声明为指向整数的指针”。 它(如果有效的话)指向指向(如果有效)单个整数对象的单个指针。 因为可以使用指针运算或者间接级别(即, *arrarr[0]相同,并且**arrarr[0][0] ),该对象可以用于访问任何从你的问题(即第二,访问一个指针数组指针数组,第三,访问指针数组的第一个元素的整数数组),只要指针指向数组的第一个元素…


然而, arr仍然被声明为指向单个整数对象的指针。 也可以声明一个指向定义数组的指针。 这里的a被声明为指向10个元素的数组的指针,指向10个整数的数组:

 cdecl> declare a as pointer to array 10 of pointer to array 10 of int; int (*(*a)[10])[10] 

实际上,数组指针最常用于将多维常量维数组传递到函数中,并用于传递可变长度数组。 将variables声明为数组的指针的语法很less见,因为只要将它们传递给一个函数,使用“array of undefined size”types的参数就可以更容易一些,所以不需要声明

 void func(int (*a)[10]); 

人们可以使用

 void func(int a[][10]) 

传入一个由10个整数组成的multidimensional array。 或者,可以使用typedef来减轻头痛。

我如何知道:

  • arr是指向整数的指针的指针

它始终是指向整数的指针。

  • arr是指向整数指针数组的指针
  • arr是指向整数数组的指针数组的指针

它永远不会是这样的。 一个指向整数指针数组的指针可以这样声明:

 int* (*arr)[n] 

这听起来好像你被欺骗使用int**由穷人教师/书籍/教程。 这几乎总是不正确的做法,正如这里和这里所解释的(关于数组指针的详细解释) 。

编辑

最后是写一篇详细的文章,解释数组是什么,查找表是什么,为什么后者不好,应该用什么来代替: 正确地分配multidimensional array 。

只有variables的声明,你不能区分这三种情况。 我们仍然可以讨论一下,如果不应该使用int *x[10]来expression一个10个指向int的指针或其他东西; 但int **x可以 – 由于指针算术,以三种不同的方式使用,每种方式假设一个不同的内存布局和(错误的)假设的(好)机会。

考虑下面的例子,其中int **以三种不同的方式被使用,即, p2p2i_v1作为指向(单个)int的指针的指针, p2p2i_v2作为指向int的指针数组的指针,并且p2p2i_v3作为一个指向int数组的指针。 请注意,您无法单独通过types来区分这三种含义,这三种含义都是int** 。 但是,如果初始化方式不同,以错误的方式访问它们中的每一个都会产生一些不可预测的结果,除非是访问第一个元素:

 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是指向整数数组的指针数组的指针

你不能。 它可以是任何这些。 它最终取决于你如何分配/使用它。

所以,如果你使用这些代码编写代码,logging你正在做什么,将大小parameter passing给使用它们的函数,并且通常在使用之前确定你分配的是什么。

指针不保留指向单个对象或数组元素的对象的信息。 而且,对于指针运算,单个对象被认为是由一个元素组成的数组。

考虑这些声明

 int a; int a1[1]; int a2[10]; int *p; p = &a; //... p = a1; //... p = a2; 

在这个例子中,指针p处理地址。 它不知道它所存储的地址是指向单个对象(如a还是指向仅有一个元素的数组a1的第一个元素或指向具有十个元素的数组a2的第一个元素。

types

 int ** arr; 

只有一个有效的解释。 它是:

 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]; 

在最后三种情况下,它可能看起来好像你有一个数组。 但是,types不是数组。 types总是只是a pointer to a pointer to an integer – 解引用是指针算术。 这不像是一个二维数组。

要知道哪个程序对手边有效,你需要看看初始化arr的代码。

更新

对于问题的更新部分:

如果你有:

 void foo(char** x) { .... }; 

你唯一知道的就是**x会给出一个char, *x会给你一个char指针(在这两种情况下都假定x是正确的初始化)。

如果你想以另一种方式使用x ,例如x[2]来获得第三个字符指针,它要求调用者初始化了x以便它指向一个至less有3个连续字符指针的存储区域。 这可以被描述为调用foo的合同。

C语法是合乎逻辑的。 作为声明中标识符之前的星号意味着指向variablestypes的指针,两个星号表示指向variablestypes的指针。

在这种情况下, arra pointer to a pointer to integer

有几个双指针的用法。 例如,你可以用一个指向一个指针向量的指针来表示一个matrix。 这个向量中的每个指针指向matrix本身的行。

也可以使用它创build一个二维数组,像这样

  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**int ,所以数组是指向一个整数的指针。

int **arr;int** arr;相同int** arr;

 int ** arr = NULL; 

这是告诉编译器, arr is a double pointer of an integer并分配NULL值。

这里已经有了很好的答案,但是我想提一下我的“goto”网站的复杂声明: http : //cdecl.org/

访问网站,粘贴你的声明,并将其翻译成英文。

对于int ** arr; ,它说declare arr as pointer to pointer to int

该网站还展示了一些例子。 在他们身上testing自己,然后将鼠标hover在查看答案。

(double (^)(int , long long ))foo

将foo分块(int,long long)返回double

int (*(*foo)(void ))[3]

将foo声明为函数指针(void)返回指向int数组3的指针

它也会将英语翻译成C语言,如果你的描述是正确的,那么这个语句是非常简洁的。