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

时间:2009-11-27 18:20:17

标签: c pointers size

这个问题出现在那里的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]);

..会强制调用者为您提供指定大小的缓冲区。

这看起来非常有用,但我从未见过在我遇到过的任何代码中都声明了这样的指针。

我的问题是:人们有没有理由不宣布像这样的指针?我没有看到一些明显的陷阱吗?

9 个答案:

答案 0 :(得分:154)

您在帖子中所说的内容绝对正确。我会说,每个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);

另外请注意,上述代码与作为数组的Vector3d类型或struct相关是不变的。您可以随时将Vector3d的定义从数组切换到struct并返回,您不必更改函数声明。在任何一种情况下,函数都将“通过引用”接收聚合对象(对此有例外,但在本讨论的上下文中这是真的)。​​

但是,你不会经常过多地使用这种数组传递方法,只是因为有太多人因为语法相当复杂而感到困惑,并且对C语言的这些功能使用它们感到不舒服。出于这个原因,在平均现实生活中,将数组作为指向其第一个元素的指针传递是一种更流行的方法。它只是看起来“更简单”。

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

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

实际上,在许多情况下,能够处理运行时大小的数组非常有用,这也有助于该方法的流行。许多C开发人员根本不会遇到(或永远不会认识到)处理固定大小数组的需要,因此仍然无法使用正确的固定大小技术。

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

void foo(char p[])

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

可能阻碍采用固定大小的数组传递技术的另一个原因是天真的方法对动态分配数组的类型的主导地位。例如,如果程序调用{​​{1}}类型的固定数组(如您的示例所示),则普通开发人员将char[10]此类数组作为

malloc

此数组无法传递给声明为

的函数
char *p = malloc(10 * sizeof *p);

会让普通开发人员感到困惑,并让他们放弃固定大小的参数声明而不再进一步考虑。但实际上,问题的根源在于天真的void foo(char (*p)[10]); 方法。上面显示的malloc格式应保留给运行时大小的数组。如果数组类型具有编译时大小,则malloc的更好方法如下所示

malloc

当然,这可以很容易地传递给上面声明的char (*p)[10] = malloc(sizeof *p);

foo

并且编译器将执行正确的类型检查。但同样,这对于一个毫无准备的C开发人员来说过于混乱,这就是为什么你不会经常在“典型的”日常平均代码中看到它。

答案 1 :(得分:8)

我想补充一下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开始)。您可以在此邮件主题中找到更多信息:link text

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

答案 2 :(得分:4)

显而易见的原因是此代码无法编译:

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

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

另请参阅this question,使用foo(&p)应该有效。

答案 3 :(得分:1)

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

这允许一些很酷且优雅的算法,例如使用类似

的表达式遍历数组
*dst++ = *src++

缺点是管理尺寸取决于您。遗憾的是,如果不能认真做到这一点,也会导致数百万的C编码错误和/或恶意利用的机会。

接近你在C中提出的要求是传递struct(按值)或指向一个(通过引用)的指针。只要在此操作的两侧使用相同的结构类型,分发引用的代码和使用它的代码都与正在处理的数据的大小一致。

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

尽管如此,没有什么可以阻止你或一个无能或恶意的程序员使用强制转换来欺骗编译器将你的结构视为一个不同的大小。几乎没有做过这种事情的能力是C设计的一部分。

答案 4 :(得分:1)

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

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*

答案 5 :(得分:1)

我不推荐这个解决方案

typedef int Vector3d[3];

因为它模糊了Vector3D具有你的类型这一事实 必须知道。程序员通常不会期望变量 相同类型有不同的尺寸。考虑一下:

void foo(Vector3d a) {
   Vector3D b;
}

其中sizeof a!= sizeof b

答案 6 :(得分:1)

我还想使用这种语法来启用更多的类型检查。

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

以下是我遇到的一些障碍。

  • 访问数组需要使用(*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;
    }
    

    但这会部分地破坏使用正确类型的目的。

  • 在将数组的地址分配给指向数组的指针时,必须记住使用address-of运算符:

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

    address-of运算符在&a中获取整个数组的地址,并使用正确的类型将其分配给p。如果没有运算符,a将自动转换为数组第一个元素的地址,与&a[0]中的地址相同,后者具有不同的类型。

    由于这种自动转换已经开始,我总是感到困惑的是&是必要的。它与&在其他类型的变量上的使用是一致的,但我必须记住,数组是特殊的,我需要&来获取正确的地址类型,即使地址也是如此价值是一样的。

    我的问题的一个原因可能是我在80年代学会了K&amp; R C,但是不允许在整个阵列上使用&运算符(尽管有些编译器忽略了它或者容忍了语法)。顺便说一句,这可能是指针到阵列很难被采用的另一个原因:它们只能在ANSI C之后正常工作,而&运算符限制可能是另一个理由认为它们也是别扭。

  • typedef 用于创建指针到数组的类型(在公共头文件中)时,则需要全局指针到数组的指针一个更复杂的extern声明,用于跨文件共享它:

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

答案 7 :(得分:-1)

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

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

答案 8 :(得分:-1)

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