C指针:指向一个固定大小的数组

这个问题出在那里的C大师:

在C中,可以像下面这样声明一个指针:

char (* p)[10]; 

..基本上说这个指针指向一个10个字符的数组。 关于声明一个像这样的指针的整洁的事情是,如果您尝试将一个不同大小的数组的指针指定给p,您将会遇到编译时错误。 如果您尝试将简单字符指针的值赋给p,它也会给您一个编译时错误。 我用gcc试过,它似乎与ANSI,C89和C99一起工作。

在我看来像声明这样的指针将是非常有用的 – 特别是,当传递一个指针函数。 通常,人们会这样写这样一个函数的原型:

 void foo(char * p, int plen); 

如果你期待一个特定大小的缓冲区,你可以简单地testingplen的值。 但是,不能保证传递给你的人真的会给你缓冲区中有效的内存位置。 你必须相信调用这个函数的人是正确的。 另一方面:

 void foo(char (*p)[10]); 

..将强制调用者给你一个指定大小的缓冲区。

这看起来非常有用,但是我从来没有在任何代码中看到过像这样的指针。

我的问题是:有什么理由为什么人们不要这样的指针? 我没有看到一些明显的陷阱?

你在post中说的是绝对正确的。 我想说,每个C开发者都会得到完全相同的发现,如果达到一定程度的C语言熟练程度,就会得到完全相同的结论。

当您的应用程序区域的细节需要一个特定的固定大小的数组(数组大小是一个编译时常量)时,将这样的数组传递给一个函数的唯一正确方法是使用一个指向数组的指针

 void foo(char (*p)[10]); 

(在C ++语言中,这也是通过引用完成的

 void foo(char (&p)[10]); 

)。

这将启用语言级别的types检查,这将确保提供完全正确大小的数组作为参数。 事实上,在很多情况下,人们隐式地使用这种技术,甚至没有意识到,将数组types隐藏在typedef名称的后面

 typedef int Vector3d[3]; void transform(Vector3d *vector); /* equivalent to `void transform(int (*vector)[3])` */ ... Vector3d vec; ... transform(&vec); 

另外请注意,上面的代码与Vector3dtypes是一个数组或一个struct是不变的。 您可以随时将Vector3d的定义从一个数组切换到一个struct并返回,您不必更改函数声明。 在任何一种情况下,函数都会通过“引用”接收一个聚合对象(这里也有例外,但是在这个讨论的范围内是这样的)。

然而,你不会看到这种数组传递的方法经常被明确地使用,只是因为太多的人被一个相当复杂的语法弄糊涂了,而且对C语言的这些特性不能很好的适应。 因此,在平均现实生活中,将数组作为指向其第一个元素的指针是一种比较stream行的方法。 它看起来“更简单”。

但实际上,使用指向数组传递的第一个元素的指针是一个非常有趣的技术,它是一个非常具体的目的:它唯一的目的是促进传递不同大小的数组(即运行时大小) 。 如果你真的需要能够处理运行时大小的数组,那么传递这样一个数组的正确方法是通过一个指向它的第一个元素的指针,具体大小由一个附加参数提供

 void foo(char p[], unsigned plen); 

实际上,在很多情况下,能够处理运行时间大小的数组非常有用,这也有助于该方法的普及。 许多C开发人员根本不会遇到(或从不认识)处理固定大小数组的需要,因此仍然不知道适当的固定大小的技术。

尽pipe如此,如果数组的大小是固定的,将它作为指针传递给一个元素

 void foo(char p[]) 

是一个主要的技术层面的错误,不幸的是这些日子相当普遍。 指针数组技术在这种情况下是更好的方法。

阻碍采用固定大小的数组传递技术的另一个原因是对dynamic分配数组的input的天真方法的支配地位。 例如,如果程序调用char[10]types的固定数组(如你的例子),一般的开发人员将malloc这样的数组

 char *p = malloc(10 * sizeof *p); 

这个数组不能被传递给声明为的函数

 void foo(char (*p)[10]); 

这使普通开发人员感到困惑,并放弃了固定大小的参数声明,而没有给出进一步的思考。 但事实上,问题的根源在于幼稚的malloc方法。 上面显示的malloc格式应该保留给运行时大小的数组。 如果数组types有编译时的大小,更好的方法malloc它看起来如下所示

 char (*p)[10] = malloc(sizeof *p); 

当然,这可以很容易地传递给上面声明的foo

 foo(p); 

编译器将执行正确的types检查。 但是,对于一个毫无准备的C开发人员来说,这又太过扑朔迷离了,这就是为什么在“典型”普通日常代码中你不会经常看到它。

我想补充AndreyT的答案(如果有人在这个页面上绊倒寻找关于这个话题的更多信息):

当我开始更多地使用这些声明时,我意识到C语言中存在很大的障碍(显然不是C ++)。 有一种情况,你想给调用者一个const指针指向你写入的缓冲区,这是相当普遍的。 不幸的是,当在C中声明这样的指针时,这是不可能的。换句话说,C标准(6.7.3 – 第8段)与这样的内容是不一致的:

 int array[9]; const int (* p2)[9] = &array; /* Not legal unless array is const as well */ 

这个约束似乎并不存在于C ++中,使得这些types的声明更加有用。 但是在C的情况下,只要你想要一个指向固定大小缓冲区的const指针(除非缓冲区本身被声明为const开始),就必须回到正则指针声明。 您可以在此邮件主题中find更多信息: 链接文本

这是我认为的一个严重的约束,这可能是人们通常不会在C中声明这样的指针的主要原因之一。另一个原因是大多数人甚至不知道你可以像这样声明一个指针AndreyT指出。

显而易见的原因是这个代码不能编译:

 extern void foo(char (*p)[10]); void bar() { char p[10]; foo(p); } 

数组的默认提升是一个不合格的指针。

也看到这个问题 ,使用foo(&p)应该工作。

那么,简单地说,C不会那样做。 Ttypes的数组作为指针传递给数组中的第一个T ,这就是你所得到的。

这允许一些很酷和优雅的algorithm,比如像expression式一样循环遍历数组

 *dst++ = *src++ 

缺点是pipe理的规模取决于你。 不幸的是,如果不这样做,也会导致数百万的C编码错误和/或恶意开发的机会。

接近于你在C中要求的是传递一个struct (通过值)或一个指针(通过引用)。 只要在该操作的两侧使用相同的结构types,分发引用的代码和使用它的代码就所处理的数据的大小达成一致。

你的结构体可以包含任何你想要的数据; 它可以包含一个定义好的大小的数组。

尽pipe如此,没有什么能够阻止你或者一个无能或恶意的编码者使用强制转换来愚弄编译器把你的结构视为一个不同的大小。 做这种事情几乎不受束缚的能力是Cdevise的一部分。

您可以通过多种方式声明一个字符数组:

 char p[10]; char* p = (char*)malloc(10 * sizeof(char)); 

原型为一个按值取数组的函数是:

 void foo(char* p); //cannot modify p 

或通过参考:

 void foo(char** p); //can modify p, derefernce by *p[0] = 'f'; 

或者通过数组语法:

 void foo(char p[]); //same as char* 

我不会推荐这个解决scheme

 typedef int Vector3d[3]; 

因为它掩盖了Vector3D有一个你必须知道的types的事实。 程序员通常不希望相同types的variables具有不同的大小。 考虑:

 void foo(Vector3d a) { Vector3D b; } 

其中sizeof a!= sizeof b

我也想用这个语法来启用更多的types检查。

但是我也同意使用指针的语法和心智模型更简单,更容易记住。

这是我遇到的一些更多的障碍。

  • 访问数组需要使用(*p)[]

     void foo(char (*p)[10]) { char c = (*p)[3]; (*p)[0] = 1; } 

    使用本地指针转换为char是诱人的:

     void foo(char (*p)[10]) { char *cp = (char *)p; char c = cp[3]; cp[0] = 1; } 

    但是这会部分地破坏使用正确types的目的。

  • 在将一个数组的地址赋给一个指向数组的指针时,必须记得使用地址 – 运算符:

     char a[10]; char (*p)[10] = &a; 

    地址运算符在&a中获得整个数组的地址,使用正确的types将其分配给p 。 如果没有运算符,则会自动将a转换为数组第一个元素的地址,与具有不同types的&a[0]中的相同。

    由于这种自动转换已经发生,我总是感到困惑的是&是必要的。 这与使用& on其他types的variables一致,但我必须记住,一个数组是特殊的,我需要&得到正确的地址types,即使地址值是相同的。

    我的问题的一个原因可能是我在80年代学过K&R C,它不允许在整个数组上使用&运算符(尽pipe一些编译器忽略了这个语法或者容忍了这个语法)。 顺便说一下,这可能是指针到数组很难被采用的另一个原因:它们只能在ANSI C以后才能正常工作,而且&运算符的限制也许是另一个原因,因为它们太过于笨拙。

  • typedef 没有用于为数组指针(在一个公共头文件中)创build一个types时,那么全局的数组指针需要一个更复杂的extern声明来跨文件共享:

     fileA: char (*p)[10]; fileB: extern char (*p)[10]; 

也许我错过了一些东西,但是…因为数组是常量指针,基本上这意味着传递指针是没有意义的。

难道你不能只使用void foo(char p[10], int plen);

在我的编译器(vs2008)中,它将char (*p)[10]当作一个字符指针数组,就好像没有括号一样,即使我编译为一个C文件。 编译器是否支持这个“variables”? 如果是这样,这是一个不使用它的主要原因。