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

时间:2009-03-19 01:23:09

标签: c arrays pointers

我以为我真的明白这一点,重新阅读标准(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数组下标

     

“下标的定义   operator []是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运营商或一元&amp;   运算符,...,具有的左值   类型array of 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指向数组的第一个成员?

4 个答案:

答案 0 :(得分:11)

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

  

type2_p-&gt; 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;
}

答案 1 :(得分:10)

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

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

答案 2 :(得分:3)

  

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

type_1::array(严格来说不是C语法)不是int *;它是int [3]

  

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

typedef struct 
{    
    int ptr[];
} type2_t;

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

  

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

即,它可以正确地替换type1_t::array

答案 3 :(得分:0)

必须定义行为。从记忆的角度考虑它。

为简单起见,假设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

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

或者,您可以以丑陋的方式访问元素:(&amp; type2_p-&gt; ptr)[0] (&amp; type2_p-&gt; ptr)[1] 。但是,请注意,因为(&amp; type2_p-&gt; ptr)[0] 实际上是 int * ,而不是 int 。例如,在64位平台上,(&amp; type2_p-&gt; ptr)[0] 实际上是0x100000002(4294967298)。