C中的指针与数组,非平凡差异

我以为我真的理解这一点,并重新阅读标准(ISO 9899:1990)只是证实了我明显错误的理解,所以现在我在这里问。

以下程序崩溃:

#include  #include  typedef struct { int array[3]; } type1_t; typedef struct { int *ptr; } type2_t; type1_t my_test = { {1, 2, 3} }; int main(int argc, char *argv[]) { (void)argc; (void)argv; type1_t *type1_p = &my_test; type2_t *type2_p = (type2_t *) &my_test; printf("offsetof(type1_t, array) = %lu\n", offsetof(type1_t, array)); // 0 printf("my_test.array[0] = %d\n", my_test.array[0]); printf("type1_p->array[0] = %d\n", type1_p->array[0]); printf("type2_p->ptr[0] = %d\n", type2_p->ptr[0]); // this line crashes return 0; } 

根据我对标准的解释,比较表达式my_test.array[0]type2_p->ptr[0]

6.3.2.1数组下标

“下标运算符[]的定义是E1 [E2]与(*((E1)+(E2)))相同。”

应用这个给出:

 my_test.array[0] (*((E1)+(E2))) (*((my_test.array)+(0))) (*(my_test.array+0)) (*(my_test.array)) (*my_test.array) *my_test.array type2_p->ptr[0] *((E1)+(E2))) (*((type2_p->ptr)+(0))) (*(type2_p->ptr+0)) (*(type2_p->ptr)) (*type2_p->ptr) *type2_p->ptr 

type2_p->ptr类型为“指向int的指针”,值是my_test的起始地址。 *type2_p->ptr因此计算为一个整数对象,其存储位置与my_test具有相同的地址。

进一步:

6.2.2.1左值,数组和函数指示符

“除非它是sizeof运算符或一元&运算符的操作数,…,具有类型array of type的左值转换为具有类型pointer to type的表达式, pointer to type类型pointer to type数组对象的初始元素,不是左值。“

my_test.array类型为“array of int”,如上所述转换为“指向int的指针”,第一个元素的地址为value。 *my_test.array因此计算为整数对象,其存储位于与数组中第一个元素相同的地址。

最后

6.5.2.1结构和联合说明符

指向适当转换的结构对象的指针指向其初始成员…,反之亦然。 在结构对象中可能存在未命名的填充,但不是在其开头,以实现适当的对齐。

由于type1_t的第一个成员是数组,因此它的起始地址和整个type1_t对象与上述相同。 因此,我的理解是*type2_p->ptr求值为一个整数,其存储与数组中第一个元素的地址相同,因此与*my_test.array相同。

但事实并非如此,因为程序在solaris,cygwin和linux上一直崩溃,gcc版本为2.95.3,3.4.4和4.3.2,因此任何环境问题都是完全不可能的。

我的推理在哪里错了/我什么不明白? 如何声明type2_t使ptr指向数组的第一个成员?

数组是一种存储。 从语法上讲,它用作指针,但在物理上,该结构中没有“指针”变量 – 只有三个整数。 另一方面,int指针是存储在struct中的实际数据类型。 因此,当你执行强制转换时,你可能正在使ptr取得数组中第一个元素的值,即1。

*我不确定这是否是实际定义的行为,但至少它是如何在大多数常见系统上运行的。

如果我忽略了你的分析中的任何内容,请原谅我。 但我认为所有这一切的基本错误都是错误的假设

type2_p-> ptr的类型为“指向int的指针”,值是my_test的起始地址。

没有什么能使它具有这种价值。 相反,很可能它指向了某个地方

 0x00000001 

因为你要做的是将构成整数数组的字节解释为指针。 然后你添加一些东西和下标。

另外,我非常怀疑你对其他结构的转换实际上是有效的(因为,保证可以工作)。 如果它们都是联合的成员,您可以转换然后读取任一结构的公共初始序列。 但它们不在你的例子中。 您也可以转换为指向第一个成员的指针。 例如:

 typedef struct { int array[3]; } type1_t; type1_t f = { { 1, 2, 3 } }; int main(void) { int (*arrayp)[3] = (int(*)[3])&f; (*arrayp)[0] = 3; assert(f.array[0] == 3); return 0; } 

我的推理在哪里错了/我什么不明白?

type_1::array (不是严格的C语法)不是int * ; 它是一个int [3]

如何声明type2_t使ptr指向数组的第一个成员?

 typedef struct { int ptr[]; } type2_t; 

这声明了一个灵活的数组成员。 根据C标准(6.7.2.1第16段):

但是,当一个。 (或 – >)运算符有一个左操作数,它是一个带有灵活数组成员的结构(指针),右操作数命名该成员,它的行为就好像该成员被最长的数组替换(具有相同的元素类型) )不会使结构大于被访问的对象; 数组的偏移量应保持为灵活数组成员的偏移量,即使这与替换数组的偏移量不同。

即,它可以正确别名type1_t::array

它必须被定义为行为。 从内存的角度考虑它。

为简单起见,假设my_test位于地址0x80000000。

 type1_p == 0x80000000 &type1_p->my_array[0] == 0x80000000 // my_array[0] == 1 &type1_p->my_array[1] == 0x80000004 // my_array[1] == 2 &type1_p->my_array[2] == 0x80000008 // my_array[2] == 3 

当你把它强制转换为type2_t时,

 type2_p == 0x80000000 &type2_p->ptr == 0x8000000 // type2_p->ptr == 1 type2_p->ptr[0] == *(type2_p->ptr) == *1 

要做你想做的事,你必须创建一个二级结构并将数组的地址分配给ptr(例如type2_p-> ptr = type1_p-> my_array)或将ptr声明为一个数组(或一个可变长度数组,例如int ptr [])。

或者,您可以以丑陋的方式访问元素: (&type2_p-> ptr)[0](&type2_p-> ptr)[1] 。 但是,这里要小心,因为(&type2_p-> ptr)[0]实际上是int * ,而不是int 。 例如,在64位平台上, (&type2_p-> ptr)[0]实际上是0x100000002(4294967298)。