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

时间:2015-03-31 16:29:21

标签: c struct

我偶然发现了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中有效?背后有历史/原因吗?这样的构造是否有用例? (我的错误导致失败,因此问题。)

Link to full working code

4 个答案:

答案 0 :(得分:2)

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

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

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

一个未命名的内部结构不会做:在链表中插入内容的代码可能必须使用节点指针声明局部变量。在链表结构之外声明节点结构毫无意义。

答案 1 :(得分:2)

这样的构造最初是在C中允许的,因为结构名称占用了一个宇宙,它们从来没有应用任何类型的作用域规则[struct 成员名称也是如此,顺便说一句,这是为什么旧标准库中的某些结构类型在其成员上有前缀]。由于某些代码以与范围不一致的方式使用结构名称,因此无法更改标准以禁止此类使用而不破坏现有代码。虽然有时候破坏现有代码是值得的(例如,为了摆脱被称为gets的憎恶的C),但这真的不是其中之一。

答案 2 :(得分:1)

我们说我有这样的结构:

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!

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

答案 3 :(得分:1)

嗯,这在C:

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

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

并且,正如您所指出的,它实际上与您发布的代码相同(因为C没有C ++中引入的命名空间/作用域规则)。

如果在内部移动“内部”类型并没有改变任何东西,但有时可能会澄清意图,禁止它似乎很奇怪。