代码之家  ›  专栏  ›  技术社区  ›  hlovdal

C语言中的指针与数组,非平凡差分

  •  11
  • hlovdal  · 技术社区  · 17 年前

    我想我真的理解了这一点,重新阅读标准(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 因此,计算为一个整数对象,该对象的存储地址与 米尔试验 有。

    进一步:

    6.2.2.1 lvalues、数组和函数指示符

    “除非它是 运算符或一元的大小 操作员,…,一个左值 类型 array of type 转换为 类型为的表达式 pointer to type 指的是开头 数组对象的元素,而不是 一个左撇子

    my_test.array 具有类型“array of int”,如上所述转换为“pointer to int”,第一个元素的地址为值。 *my_test.array 因此,计算为一个整数对象,该对象的存储地址与数组中的第一个元素的地址相同。

    最后

    6.5.2.1结构和联合规范

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

    自从第一个成员 type1_t 是数组,其起始地址为 以及整个 1型 对象与上面描述的相同。 因此我的理解是 *类型2_p->ptr 评估为 一个整数,其存储地址与第一个 数组中的元素,因此与 * MyTyTest.数组 .

    但情况并非如此,因为程序始终崩溃 在使用GCC版本2.95.3、3.4.4的Solaris、Cygwin和Linux上 以及4.3.2,因此任何环境问题都是完全不可能的。

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

    4 回复  |  直到 16 年前
        1
  •  10
  •   Chuck    17 年前

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

    *我不确定这是否是真正定义的行为,但至少在最常见的系统上是这样工作的。

        2
  •  11
  •   Johannes Schaub - litb    17 年前

    如果我在你的分析中忽略了什么,请原谅我。但我认为最根本的缺陷是这个错误的假设

    类型2_p->ptr具有类型“指向int的指针”,该值是我的_测试的起始地址。

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

    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;
    }
    
        3
  •  3
  •   MSN    17 年前

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

    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 适当地。

        4
  •  0
  •   Vitali    16 年前

    必须要有明确的行为。从记忆的角度来考虑。

    为了简单起见,假设我的_测试位于地址0x8000000。

    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
    

    当你把它转换成2字时,

    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_数组),或者将ptr声明为数组(或可变长度数组,例如int ptr[])。

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