我曾经认为我真的理解了这个问题,并且重新阅读标准(ISO 9899:1990)只是确认了我的明显错误的理解,所以现在我在这里提问。
以下程序会崩溃:
#include <stdio.h>
#include <stddef.h>
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
相同的地址处。
进一步来说:
根据C语言规范6.2.2.1中的描述: "除非它是sizeof运算符或一元&运算符的操作数,...,具有类型类型数组
的lvalue转换为类型类型的指针
的表达式,它指向数组对象的初始元素,并且不是左值。"
my_test.array
的类型为"int数组",按照上述规则,被转换为类型为"指向int的指针"的表达式,其值为第一个元素的地址。因此,*my_test.array
评估为一个整数对象,其存储位于数组的第一个元素的地址处。
最后,在C语言规范6.5.2.1中描述了结构体和联合体说明符:
针对结构体对象进行适当转换的指针指向其初始成员...反之亦然。在结构体对象内部可能会存在未命名的填充,但不会在其开头处,因为需要满足适当的对齐要求。
由于type1_t
的第一个成员是数组,所以该数组和整个type1_t
对象的起始地址与上述相同。因此,我认为*type2_p->ptr
评估为一个整数对象,其存储位于数组中第一个元素的地址处,因此等同于*my_test.array
的值。
但是,这不可能是正确的,因为在solaris、cygwin和gcc版本2.95.3、3.4.4和4.3.2上程序总是崩溃,因此任何环境问题都是完全排除的。
我的推理有什么问题/我没有理解的地方在哪里?如何声明type2_t
使得指针ptr
指向数组的第一个成员?
type2_t *type2_p = (type2_t *) &my_test;
类型转换)。很抱歉没有接受您的答案,但我会选择Chuck的答案,因为我认为它更加精确。 - hlovdal