方括号数组和指针数组有什么区别?

17

作为一个非C/C++专家,我一直认为方括号和指针数组是相等的。

即:

char *my_array_star;
char my_array_square[];

但我注意到当在一个结构体/类中使用时,它们的行为并不相同:

typedef struct {
   char whatever;
   char *my_array_star;
} my_struct_star;

typedef struct {
   char whatever;
   char my_array_square[];
} my_struct_square;

下面的代码显示了16。 whatever 占用1个字节,my_array_pointer 占用8个字节。 由于填充,结构体的总大小为16。

printf("my_struct_star: %li\n",sizeof(my_struct_star));

以下代码显示数字 1,whatever 占用 1 字节,my_array_pointer 没有被考虑在内。

printf("my_struct_square: %li\n",sizeof(my_struct_square));

通过尝试,我注意到方括号在结构中被用作额外的空间。

my_struct_square  *i=malloc(2);

i->whatever='A';
i->my_array_square[0]='B';

下面的代码展示了 A:

printf("i[0]=%c\n",((char*)i)[0]);

下面这行显示了B:

printf("i[1]=%c\n",((char*)i)[1]);

所以我不能再说方括号相当于指针了。但我想理解这种行为的原因。我担心错过该语言的关键概念。


在 C 语言中,你不能声明 something[],必须定义数组的大小。在两者之间进行选择。 - Eregrith
1
@Eregrith:你可以在结构体的末尾声明这样的数组,这就是为什么它能编译的原因。 - Blagovest Buyukliev
我会坚持使用指针来处理动态创建的数组,不必担心它。我不喜欢那种语法,对我来说似乎是滥用。 - evanmcdonnal
可能是在C语言中,数组名是指针吗?的重复问题。 - Bo Persson
2个回答

28

数组和指针的行为不同,因为它们根本不是同一种东西,只是看起来很像。

数组是一组相邻的项,而指针是指向单个项的指针。

被指向的单个项可能是数组中的第一个,以便您也可以访问其他项,但指针本身既不知道也不关心这一点。

数组和指针经常看起来相同的原因在于,在许多情况下,数组会退化为指向该数组第一个元素的指针。

其中之一就是在函数调用中发生的情况。当您将数组传递给函数时,它会退化为指针。这就是为什么像数组大小这样的事情不会明确地传递到函数中的原因。我的意思是:

#include <stdio.h>

static void fn (char plugh[]) {
    printf ("size = %d\n", sizeof(plugh)); // will give char* size (4 for me).
}

int main (void) {
    char xyzzy[10];
    printf ("size = %d\n", sizeof(xyzzy)); // will give 10.
    fn (xyzzy);

    return 0;
}

另外一个你会发现的是,虽然你可以 plugh++plugh-- 直到你心满意足(只要你不超出数组范围进行解引用),但你不能对数组 xyzzy 这样做。

在你的两个结构中,有一个主要的区别。在指针版本中,你有一个固定大小的指向结构体外部项目的指针 内部 结构体中。

这就是为什么它占用空间的原因——你的 8 字节指针按如下所示对齐到 8 字节边界:

+----------------+
| 1 char variable|
+----------------+
| 7 char padding |
+----------------+
| 8 char pointer |
+----------------+

使用“无限制”数组,你将其放在结构内部,可以使其大小任意大——只要在创建变量时分配足够的内存即可。默认情况下(即根据sizeof),大小为零:

+----------------+
| 1 char variable|
+----------------+
| 0 char array   |
+----------------+

但是你可以分配更多的空间,例如:

typedef struct {
   char whatever;
   char my_array_square[];
} my_struct_square;

my_struct_square twisty = malloc (sizeof (my_struct_square) + 10);

提供一个名为twisty的变量,它包含一个whatever字符和一个名为my_array_square的十个字符数组。

这些不定长数组只能出现在结构体的最后,并且只能有一个(否则编译器将无法确定这些变量长度的部分从何处开始和结束),它们专门用于允许在结构体末尾具有任意大小的数组。


谢谢你提供这个高质量的回答,恭喜! - Raphael
+1 优秀的解释。我不确定未指定大小的成员数组是否符合标准。如果这样做,我通常会收到有关使用非标准扩展的警告。 - Kevin
当数组作为函数参数传递时,它们会退化为简单指针。这就是为什么我总是更喜欢在函数参数中使用指针语法 void fn (char *foobar) 而不是数组语法 void fn (char foobar[]) 的原因。 - Flimm

5
my_array_square成员是所谓的“灵活”数组成员。这种没有指定大小的数组只能出现在结构体的末尾,并且它们不会影响其大小。其目的是手动分配剩余空间,以获得所需的元素数量。否则,数组的大小将在编译时确定。
这种结构体的使用模式如下:
my_struct_square *s = malloc(sizeof(my_struct_square) + 5 * sizeof(char));
...
s->my_array_square[4]; // the last element of the array

在其他所有情况下,数组的大小必须在编译时知道。甚至数组的类型也与其大小一起确定,即int a[20]的类型是int[20],而不仅仅是int[]

此外,理解数组和指针之间的区别非常重要。@paxdiablo已经很好地涵盖了这一点。


请注意,灵活数组成员仅允许在C语言版本C99或更高版本中使用。如果在C90(“ANSI C”)中尝试编写此类代码,则可能会导致在向结构体末尾可能存储的填充字节写入数据时出现崩溃,特别是当写入的数据是“陷阱表示”时。如果我说错了,请纠正我。 - Lundin
@Lunding:我猜使用C90编译器应该根本无法编译。 - Blagovest Buyukliev
小细节:自从C99引入了可变长度数组,不是所有情况下都必须在编译时知道数组大小。 - Daniel Fischer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接