为什么允许命名嵌套结构?

Why are named nested structure allowed?

我偶然发现了 C 的一个非常奇怪的特性:您可以在另一个结构中声明一个命名结构,前提是同时声明该类型的成员变量:

struct Robot_st {
    int pos_x;
    int pos_y;
    struct BatteryStatus_st { /* <- named struct */
        int capacity;
        int load;
    } battery;
};

然后内部结构就可以像任何其他类型一样在结构外部使用,使像这样的奇怪代码完全有效:

struct Robot_st my_robot = {2, 3, {200, 50}};
struct BatteryStatus_st battery_snapshot; /* <- use of inner struct */

memcpy(
    &battery_snapshot,
    &my_robot.battery,
    sizeof(struct BatteryStatus_st)
);

printf("robot position: %d,%d\n", my_robot.pos_x, my_robot.pos_y);
printf("battery load: %d%%\n", battery_snapshot.load);

嵌套未命名结构感觉不错,因为以后不能访问该类型,所以不会混淆该类型的范围。上面的代码在C++中也是无效的,虽然嵌套声明是有效的,因为C++将其理解为外部结构命名空间中的类型,所以需要使用

访问它
struct Robot_st::BatteryStatus_st battery_snapshot;

尽管同时声明类型和成员感觉很奇怪,但更有意义。

那么为什么这个构造在 C 中有效?后面有没有history/reason?这种结构有用例吗? (我是一个导致失败的错误,因此是这个问题。)

Link to full working code.

假设我有这个结构:

struct OuterStruct
{
    int a;
    struct InnerStruct
    {
        int i;
        int j;
    } b;
} s;

现在我可以访问 s.a,我什至可以将它保存在一个变量中,以便更好地处理它,将它传递给函数,...

int sa = s.a;

嗯,现在我想对 s.b 做同样的事情,为此我需要 InnerStruct 有一个名字!

???? sb = s.b;   // here I have to use InnerStruct, otherwise sb would have no valid type!

另一种方法是在 OuterStruct 之外声明 InnerStruct,然后将其实例作为 OuterStruct 的成员放入其中。但这会掩盖 InnerStruct 属于 OuterStruct 的事实,使您的意图不那么明确。

嗯,这在 C:

中是合法的
struct BatteryStatus_st {
    int capacity;
    int load;
};

struct Robot_st {
    int pos_x;
    int pos_y;
    struct BatteryStatus_st battery;
};

而且,正如您所指出的,它实际上与您发布的代码相同(因为 C 没有在 C++ 中引入的 namespace/scoping 规则)。

如果在内部移动 "inner" 类型不会改变任何东西,但有时可能会澄清意图,那么禁止它似乎很奇怪。

除了其他两个答案之外,这里还有一个真实的用例,它需要这样一个命名的内部结构:

struct LinkedList {
    //data members stored once per list
    struct LinkedListNode {
        //data members for each entry of the list
        struct LinkedListNode *next;
    } *head, *tail;
};

很难为链表的结构写出更简洁的定义。

未命名的内部结构将不起作用:将某些内容插入链表的代码可能必须声明带有节点指针的局部变量。而且在链表结构之外声明节点结构也没有意义。

C 最初允许这样的构造,因为结构名称占据了一个完全属于自己的宇宙,从未应用任何类型的范围规则 [struct member names 也这样做了,通过方式,这就是为什么旧标准库中的某些结构类型在其成员上有前缀]。由于某些代码以与作用域不一致的方式使用结构名称,因此无法在不破坏现有代码的情况下更改标准以禁止此类使用。虽然有时值得打破现有代码(例如,摆脱 C 的可憎之物 gets),但这确实不是其中之一。