gcc C语言结构体中的“继承”?

anhgbhbe  于 2022-11-13  发布在  其他
关注(0)|答案(4)|浏览(233)

下面我对这段代码有点困惑:

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

struct test_struct {
    uint8_t f;
    uint8_t weird[];
};

int main(void) {
    struct {
        struct test_struct tst;
        uint8_t weird[256];
    } test_in = {};

    printf("%u\n", test_in.weird[0]); // 0

    test_in.tst.weird[0] = 1;

    printf("%u\n", test_in.weird[0]); // 1

    return 0;
}

我不知道可以这样使用struct的字段,所以我有两个问题:

  • 它在C语言中是怎么叫的?
  • 当然,它是如何工作的?(为什么weird字段被更改,而我没有直接更改它,我以为这是两个不同的字段?)
nbnkbykc

nbnkbykc1#

下面我对这段代码有点困惑:
简短的回答是:该代码具有未定义行为
它在C语言中是如何被调用的?它是如何工作的?

  • struct test_struct的最后一个成员被定义为未指定长度的数组:uint8_t weird[];此成员称为 * 灵活数组成员 *,不要与可变长度数组混淆。
    6.7.2类型说明符

[...]
20 作为一种特殊情况,具有多个命名成员的结构的最后一个成员可能具有不完整的数组类型;这称为“灵活数组成员”。2在大多数情况下,灵活数组成员会被忽略。3特别是,结构的大小就像灵活数组成员被省略一样,只是它的尾部填充可能比省略所暗示的要多。4但是,当一个.(或->)运算子的左算子为(指向)具有灵活数组成员的结构,右操作数命名该成员,它的行为就好像该成员被最长的数组(具有相同的元素类型)所替换,这不会使结构大于被访问的对象;数组的偏移量应保持为可变数组成员的偏移量,即使这与替换数组的偏移量不同。如果此数组没有元素,它的行为就像它有一个元素一样,但如果试图访问该元素或生成一个经过它的指针,则行为未定义。

  • 如果您从堆中为数组元素分配这样一个结构,并为数组元素分配额外的空间,则可以通过weird成员访问这些元素,访问的元素数不超过这样分配的元素数。
  • C标准要求这样的结构只能被定义为另一个结构或联合体的成员,如果它出现在所述聚合的最后一个成员的话。在发布的代码中,程序员违反了这个约束,所以访问test_in.tst.weird的元素具有未定义的行为,访问test_in.weird的元素也是如此。
  • 程序员还假设test_in.tst.weird数组和test_in.weird数组完全重叠,但不保证也不支持这种情况:依赖于这种别名的代码也具有未定义的行为。
  • 在您的示例中,假设编译器接受空的初始化器{}(下一代C标准的一部分,从C++中借用),它似乎可以按预期工作,但这并不保证,对齐问题可能会导致它失败,如下面的修改版本所示:
#include <stdint.h>
#include <stdio.h>

struct test_struct {
    uint8_t f;
    uint8_t weird[];
};

struct test_struct1 {
    int x;
    uint8_t f;
    uint8_t weird[];
};

int main(void) {
    struct {
        struct test_struct tst;
        uint8_t weird[256];
    } test_in = {};

    struct {
        struct test_struct1 tst;
        uint8_t weird[256];
    } test_in1 = {};

    printf("modifying test_in.weird[0]:\n");
    printf("%u\n", test_in.weird[0]); // 0
    test_in.tst.weird[0] = 1;
    printf("%u\n", test_in.weird[0]); // 1

    printf("modifying test_in1.weird[0]:\n");
    printf("%u\n", test_in1.weird[0]); // 0
    test_in1.tst.weird[0] = 1;
    printf("%u\n", test_in1.weird[0]); // 0?

    return 0;
}

输出量:

chqrlie$ make 220930-flexible.run
clang -O3 -std=c11 -Weverything -o 220930-flexible 220930-flexible.c
220930-flexible.c:17:28: warning: field 'tst' with variable sized type 'struct test_struct' not at
      the end of a struct or class is a GNU extension [-Wgnu-variable-sized-type-not-at-end]
        struct test_struct tst;
                           ^
220930-flexible.c:19:17: warning: use of GNU empty initializer extension [-Wgnu-empty-initializer]
    } test_in = {};
                ^
220930-flexible.c:22:29: warning: field 'tst' with variable sized type 'struct test_struct1' not
      at the end of a struct or class is a GNU extension [-Wgnu-variable-sized-type-not-at-end]
        struct test_struct1 tst;
                            ^
220930-flexible.c:24:18: warning: use of GNU empty initializer extension [-Wgnu-empty-initializer]
    } test_in1 = {};
                 ^
4 warnings generated.
modifying test_in.weird[0]:
0
1
modifying test_in1.weird[0]:
0
0
wh6knrhe

wh6knrhe2#

struct test_struct {
    uint8_t f;
    uint8_t weird[];
};

int main(void) {
    struct {
        struct test_struct tst;
        uint8_t weird[256];
    } test_in = {};

实际上,在语言中有FAM之前,您声明的内容是:

int main(void) {
    struct {
        struct { uint8_t f; } tst;
        union {
            uint8_t weird0[1]; // any non-zero size up to 256
            uint8_t weird1[256];
        } overlay;
    } test_in = {};
q5iwbnjs

q5iwbnjs3#

相反,如上面的注解部分所述,

int array[];

不是可变长度数组,它要么称为未知大小的数组(cppreference),要么称为零长度数组(gcc)。
VLA的示例如下:

void foo(size_t n)
{
    int array[n]; //n is not available at compile time
}

基于以下评论(来自cppreference -请参见提供的链接):
在结构体定义中,一个未知大小的数组可能作为最后一个成员出现(只要至少有一个其他命名成员),在这种情况下,它是一个称为灵活数组成员的特殊情况。有关详细信息,请参阅结构体(解释部分):

struct s { int n; double d[]; }; // s.d is a flexible array member 
struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // as if d was double d[8]
atmip9wb

atmip9wb4#

提供的代码无效。
您声明了一个具有 * 灵活数组成员 * 的结构

struct test_struct {
    uint8_t f;
    uint8_t weird[];
};

来自C标准(6.7.2.1结构和联合说明符)
18作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型;这被称为“灵活阵列构件”。
从引用中可以看出,这样的成员必须是结构的最后一个元素,所以上面的结构声明是正确的。
但是,在main中,您声明了另一个未命名的结构

int main(void) {
    struct {
        struct test_struct tst;
        uint8_t weird[256];
    } test_in = {};
    //...

包含一个结构的元素作为成员,该结构具有灵活数组元素,但该元素现在不是未命名结构的最后一个元素。因此,这样的声明是无效的。
第二,你用空括号初始化一个未命名结构的对象。与C++相反,在C中你不能用空括号初始化对象。

相关问题