一个 `struct {...};` 是一种类型还是一个无名变量?

6
以下内容在文件作用域,是类型声明还是未命名变量?
struct student_s {
    char* name;
    int age;
    double height;
    struct student_s* next;   
};

如果它是一个类型定义,那么它与以下内容有何区别:

typedef struct student_s {
    char* name;
    int age;
    double height;
    struct student_s* next;   
};

背景:请参考我在Changing a variable from global to local - C的回答,在那里我认为第一个引入了一个无名变量,然后被编译器优化掉了。

注意:这个问题已经被标记为可能是结构体成员标识符放置的作用域是什么?的重复问题,但我认为我并没有问关于成员作用域的问题,而是关于声明实际上创建了什么。然而,C ++中'struct'和'typedef struct'之间的区别的答案解释了我的问题。


你认为为什么会定义一个未命名的变量,而不是没有变量?你期望从第二个片段中获得一个未命名的类型别名吗? - Quentin
https://dev59.com/41XTa4cB1Zd3GeqPyiH1 - smali
1
你也可以查看这个帖子获取更多信息。 - Peut22
你发布的链接指向的答案不是你自己的。你想要的是这个:http://stackoverflow.com/a/31874396/2793118 前者是类型定义,后者无效,因为typedef没有名称参数。 - Filipe Gonçalves
1
区别在于:第一个是有效的C代码,而第二个不是。 - too honest for this site
6个回答

7
根据C标准,形如以下结构声明:
```c struct { int x; int y; } point; ```
其中 `point` 是一个结构体变量。
struct student_s {
    char* name;
    int age;
    double height;
    struct student_s* next;   
};

这是一种类型的声明。引用C11,第6.7.2.1章:

在结构体或联合体说明符中出现的struct-declaration-list声明了一个新类型,位于一个翻译单元内。struct-declaration-list是结构体或联合体成员的一系列声明。[...]

C标准没有强制要求为该类型创建一个变量(无论是否具有名称)。

在你的第二个片段中,你实际上(试图)将其typedef为空值。然而,如果你将代码更改为以下形式:

typedef struct {
         //.....members
} student_s ;

你将创建一个类型为student_s的结构体(typedef到一个未命名的结构体类型),以便后续使用。
顺带一提,我们在这里从未谈论过创建一个变量,这都是关于类型的问题。

在最后一个例子中,当结构体是匿名的且typedef尚未完成时,next指针可以像那样声明吗? - Useless
@Useless 是的,我(我们?)忽略了成员变量的情况。并不是为了辩解错误(已经纠正),但在我看来这不是问题的直接范围。 :-) - Sourav Ghosh
1
同意,我直到第三次阅读时才注意到它 :) - Useless

3

第一个声明声明了一个类型。在那个声明之后,类型struct student_s已知,你可以声明该类型的变量或指向它的指针:

struct student_s student1;
struct student_s *pStudent;

第二个声明即使编译通过也很奇怪。通常的用法应该是:
typedef struct {
    char* name;
    int age;
    double height;
    struct student_s* next;   
} studend_t;

这个语句声明了一个别名student_t,它指向了一个匿名结构体。之后可以直接使用:

student_t student1;
student_t *pStudent;
但第二个例子编译成功(即使有警告),和第一个例子一样! 它实际上是声明了一个指向struct student_s的空别名。typedef被忽略并产生警告:typedef需要名称,但作为副作用,结构体被声明,与第一个例子完全相同。
因此,实际问题的真正答案是 没有区别,除了一个警告。

除非按照Sourav的答案所述,否则您不能将“next”指针声明为匿名结构体。 - Useless
尽管第二个声明并不像OP认为的那样工作,但我认为它并不是格式错误的。这可能是一个好的语言律师问题。 - David Hammen
@DavidHammen:我尝试了一下,很惊讶它竟然编译通过了。请看我的编辑。 - Serge Ballesta
我问了关于这个问题的合法性,https://dev59.com/Ro3da4cB1Zd3GeqP5tsN 即使它是非法的,编译器通过发出诊断完成了它的工作。标准并没有说编译器不应该在面对不合规范代码时生成目标代码。在某些源文件的文件范围内插入无意义的 static; 后,多个编译器都生成了目标代码。它们确实发出了诊断信息。 - David Hammen

2

这两个都是类型定义。第二个是不完整的。它没有提供一个名称作为typedef的参数。更好的做法是使用:

typedef struct student_s {
    char* name;
    int age;
    double height;
    struct student_s* next;   
} student;

关于合法性
typedef struct student_s {
    char* name;
    int age;
    double height;
    struct student_s* next;   
};

我已经将这个问题作为一个单独的问题提出,关于`typedef struct foo {int bar};`的合法性


1

这是一种类型,你不能拥有一个匿名的结构体实例。作为对比,下面这个声明了类型 struct_type 和该类型的一个实例 struct_instance

struct struct_type {
    /* blah */
} struct_instance;

如果您想要在隔离的环境中声明另一个实例(与类型声明无关),则应使用。
struct struct_type another_instance;

如果使用得当,typedef 可以让你给类型取另一个名字,而不需要使用 struct 关键字来声明实例,与你的示例不同。
typedef struct_type MyStruct;
MyStruct yet_another_instance;

或者等价地。
typedef struct struct_type {
    /* blah */
} MyStruct;

省略名称 (struct_type) 可以得到一个匿名的结构体类型,只能通过其typedef'd名称来引用。

注意1

由于您原始的结构体包含指向其自身类型的next指针,因此在声明成员时该类型必须具有名称。因此,您不能使用自我类型化指针声明匿名结构体。如果使用typedef为匿名结构体命名,则该名称直到成员声明之后才存在,因此无法在那里使用。

typedef struct /*S*/ {
    struct S *next; /* T doesn't exist yet, so without S, you can't declare this */
} T;

注意事项2

您可以声明一个匿名联合体的匿名实例作为成员:

struct S {
    union {
        int i;
        unsigned u;
    };
};

struct S s;
s.i = -1;
printf("%x\n", s.u);

但那是一个非常特殊的情况。我把关于这个的评论从主要论点中剔除,以防它会误导。


1
struct A { ... }

创建一个名为'A'的结构体,使其在结构体命名空间中可见(与C++命名空间无关)。因此,要访问该结构体,必须使用关键字“struct”。
struct A a;

当使用typedef struct定义时:

typedef struct A { ... } B;

变得可见并绑定到B,你可以轻松地创建类似于类型B的普通变量的struct

B a;

如果我错了,请有人纠正我。


0
区别在于第一个例子中,您必须像这样声明一个新变量:struct student_s variable;,然而在第二个例子中,您可以简单地执行student_s variable;

3
这个例子不行。没有定义任何typedef名称。 - David Hammen
实际上,gcc确实编译它,但会给出警告warning: useless storage class specifier in empty declaration - dbush
@dbush - 这是因为声明的形式为 declaration-specifiers init-declarator-list <sub>opt</sub> ; 在这里,typedef 充当了一个 storage_class_specifier。标准并没有规定必须提供名称作为 typedef 的名称。它确实说至少要引入一个名称,并且正在发生这种情况。 - David Hammen
@pelya - 假设您在代码的某个文件作用域中添加了一个多余的 typedef int;。标准规定,由于这是非法代码,必须发出诊断。标准对该诊断的后果没有任何规定。标准没有提到致命诊断与仅仅是警告之间的区别。编译器仍然可以生成目标文件。这在多个版本的clang和gcc中都可以编译(以产生目标文件的意义上)。 - David Hammen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接