为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C
绝地无双 2019-12-10 09:08:34
这个问题向那里的C专家提出:在C中,可以如下声明一个指针:char (* p)[10];..基本上指出此指针指向10个字符的数组。像这样声明指针的整洁之处在于,如果您尝试将不同大小的数组的指针分配给p,则会出现编译时错误。如果您尝试将简单的char指针的值分配给p,也会给您带来编译时错误。我在gcc上尝试过,似乎可以在ANSI,C89和C99上使用。在我看来,像这样声明一个指针将非常有用-特别是在将指针传递给函数时。通常,人们会这样写这样的函数原型:void foo(char * p, int plen);如果期望使用特定大小的缓冲区,则只需测试plen的值。但是,不能保证将p传递给您的人确实会在该缓冲区中为您提供有效的内存位置。您必须相信调用此函数的人在做正确的事情。另一方面:void foo(char (*p)[10]);..将强制调用方为您提供指定大小的缓冲区。这似乎非常有用,但是我从未在我遇到的任何代码中看到过像这样声明过的指针。我的问题是:人们为什么不声明这样的指针有什么理由?我没有看到明显的陷阱吗?
查看完整描述

3 回答

?
holdtom

TA贡献1805条经验 获得超10个赞

您在帖子中所说的是绝对正确的。我想说的是,每位C开发人员在达到一定水平的C语言水平时都会得出完全相同的发现和得出完全相同的结论。


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


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

(在C ++语言中,这也可以通过引用来完成


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

)。


这将启用语言级别的类型检查,这将确保提供大小完全正确的数组作为参数。实际上,在许多情况下,人们甚至没有意识到就暗中使用此技术,而是将数组类型隐藏在typedef名称后面


typedef int Vector3d[3];


void transform(Vector3d *vector);

/* equivalent to `void transform(int (*vector)[3])` */

...

Vector3d vec;

...

transform(&vec);

另外请注意,上述代码对于Vector3dtype是数组还是a 是不变的struct。您可以Vector3d随时将数组的定义从a 切换到a struct,然后再切换回来,而不必更改函数声明。在这两种情况下,函数都将“通过引用”接收聚合对象(对此有一些例外,但是在此讨论的上下文中,这是正确的)。


但是,您不会看到这种显式使用数组传递的方法被频繁地使用,这仅仅是因为太多的人对相当复杂的语法感到困惑,并且根本不适应C语言的此类功能以正确使用它们。因此,在一般现实生活中,将数组作为指向其第一个元素的指针传递是一种更为流行的方法。它看起来只是“简单”。


但实际上,使用指向第一个元素的指针进行数组传递是一种非常特殊的技术,这是一个技巧,它具有非常特定的目的:其唯一的目的是促进传递大小不同(即运行时大小)的数组。如果您确实需要能够处理运行时大小的数组,则传递此类数组的正确方法是使用指向其第一个元素的指针,并使用附加参数提供的具体大小


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

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


但是,如果数组大小固定,则将其作为指向元素的指针传递


void foo(char p[])

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


可能会阻止采用固定大小的数组传递技术的另一个原因是,幼稚方法在动态分配数组的类型化方面占主导地位。例如,如果程序调用类型的固定阵列char[10](如在你的例子),平均显影剂将malloc此类阵列如


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

该数组不能传递给声明为


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

这使普通开发人员感到困惑,并使他们放弃了固定大小的参数声明,而无需进一步考虑。但实际上,问题的根源在于幼稚的malloc方法。malloc上面显示的格式应保留给运行时大小的数组。如果数组类型具有编译时大小,则更好的方法malloc如下所示


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

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


foo(p);

编译器将执行正确的类型检查。但是,这再一次给一个没有准备的C开发人员带来了太多混乱,这就是为什么您不会在“典型”的日常代码中经常看到它的原因。




查看完整回答
反对 回复 2019-12-11
?
紫衣仙女

TA贡献1839条经验 获得超15个赞

我想补充一下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 ++中不存在,这使这些类型的声明更加有用。但是对于C语言,每当您要使用指向固定大小缓冲区的const指针时,都必须回退到常规指针声明(除非缓冲区本身被声明为const开头)。您可以在此邮件主题中找到更多信息:链接文本


在我看来,这是一个严格的约束,这可能是人们通常不使用C声明这样的指针的主要原因之一。另一个事实是,大多数人甚至都不知道您可以这样声明一个指针: AndreyT指出。



查看完整回答
反对 回复 2019-12-11
?
ABOUTYOU

TA贡献1812条经验 获得超5个赞

简而言之,C不会那样做。类型数组T作为指针传递给T该数组中的第一个指针,仅此而已。


这允许一些很酷而优雅的算法,例如使用类似以下表达式的数组循环


*dst++ = *src++

缺点是大小的管理取决于您。不幸的是,不认真执行此操作还导致了C编码中的数百万个错误和/或恶意利用的机会。


与您在C语言中所要求的最接近的是传递一个struct(按值)或一个指向一个(按引用)的指针。只要在此操作的两边使用相同的结构类型,则分发引用的代码和使用引用的代码都将与要处理的数据大小一致。


您的结构可以包含您想要的任何数据;它可能包含大小明确定义的数组。


但是,没有什么能阻止您或功能不强或恶意的编码器使用强制转换来欺骗编译器,以将您的结构视为不同大小的结构之一。C的设计几乎包括了完成这种事情的能力。



查看完整回答
反对 回复 2019-12-11
  • 3 回答
  • 0 关注
  • 304 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信