为什么在结构体中要使用不同的标签和typedef?

13
在C代码中,我见过下面这样的代码:
typedef struct SomeStructTag {
    // struct members
} SomeStruct;

我不清楚这与以下内容有何不同:

typedef struct SomeStruct {
    // struct members
} SomeStruct;

使用特定的名称给类型命名并将其typedef为不同的类型名称有什么意义呢?


这会让它们在第一眼看起来更容易区分?!此外,一些公司有命名约定。 - Ed Heal
5
在问题标签中同时加上C和C++有些奇怪。在C语言中,typedef有非常好的理由。而在C++中,你只需编写 struct whatever 即可。 - Ed S.
很多人喜欢拥有相同的名字。这解决了一些不同名称可能引起的问题(您可以在右侧的相关问题中找到)。 - chris
这不是同一个问题,但与此相关,因此包含您可能想要阅读的好答案(特别是David的):https://dev59.com/L3I-5IYBdhLWcg3wxruQ - leemes
5个回答

8

在typedef和struct之间使用不同的标签有一个功能性原因。这是当你正在创建一个链表结构时,你的struct中的一个字段是指向被定义的struct实例的指针时。由于语句还没有完成,所以无法在语句中使用typedef名称。

例如:

typedef struct {
    link_t *next;
    void   *data;
} link_t;

这样写是错误的,因为您试图在编译器看到该符号之前使用link_t。相反,您应该这样写:

typedef struct LL {
    struct LL *next;
    void      *data;
} link_t;

在使用LL结构之前,编译器会提前一行知道它存在。


即使“foo”是一种类型名称,您仍然可以使用“struct foo”。 - Eric Postpischil
只有当 foo 同时是 typedef 名称和结构体名称时才需要这样做。如果结构体被留空,就像我的第一个例子一样,你无法在内部引用它。根据上面 EJP 的回答,如果将结构体转移到 C++ 中,将它们命名为相同的名称会存在命名空间的危险。 - Erik Johnson
重点是,此答案提出的“功能原因”并未解释为什么会使用不同的名称。您可以为标记和类型使用相同的名称:typedef struct foo { struct foo *next; void *data; } foo; 是合法的。EJP猜测在C++中重新声明名称是非法的是错误的;根据C++(N3092)7.1.3 3和4,这是明确允许的。(那是一个旧草案,但我认为它没有改变。) - Eric Postpischil
一个人可以这样说:typedef struct LL_S LL; struct LL_S { LL *next; };。如果想的话,可以使用名称LL_S作为标签而不是LL,但是对标签使用不同的名称将使得更容易强制执行一致地使用关键字struct来创建类型的事物,或者始终不使用它。在两个地方使用相同的名称意味着允许不一致的用法而没有诊断。 - supercat
@ErikJohnson 是正确的,这个前向引用是微软开发人员给出的解释:https://blogs.msdn.microsoft.com/oldnewthing/20080327-00/?p=22973 - Hibou57

8
在大多数情况下,人们可以在不产生任何问题的情况下为两个不同用途使用相同的名称。人们在代码中使用不同的名称的惯例可能源于 ANSI 标准化之前的时代,在那个时期,有许多编译器接受类似于 C 的方言,这些方言大多兼容。某些结构和特性几乎可以在任何编译器上运行,而其他的则可以在大多数编译器上运行(但会在一定数量的编译器上失败),还有一些只能在少数编译器上运行。虽然标准多年来已经明确规定,类型、结构体标签以及每个单独结构体成员应有不同的命名空间,但是在早期的一些编译器中,有些编译器未能将所有这些命名空间识别为不同的,因此编写了相当数量的代码,这些代码应该即使在这样的编译器上也能正常工作,而且人们往往会编写类似于他们看到的其他代码的代码。
我认为根据今天的规则使用相同标识符最强烈的反对意见是不同的标识符应该看起来不同,一般应避免为给定目的使用两个语义等效的标识符。如果两个标识符之间存在明显的语义区别(例如,“至少为 32 位的最快类型”与“恰好为 32 位的类型”之间的区别),则可以为某些目的使用两个不同的标识符,但是对于随意使用两个不同的标识符执行相同操作的代码不好。如果声明 typedef struct FOO {...} FOO;,那么 struct FOOFOO 将是看起来应该是可以互换的不同标识符。如果有特定情况需要使用其中一个并且名称是不同的,则在不应该使用 struct 的地方使用它或者反过来,都将导致编译错误。如果名称匹配,则可以编译代码,并且没有警告表明名称的用法与其他地方的用法不一致。
顺便说一下,虽然结构体标签是全局可用的,但通常没有必要多次使用任何标记。即使结构体应该包含自我引用指针,在 ANSI C 中也可以声明它:
typedef struct NODE_S NODE;
struct NODE_S {
  NODE *parent,*left,*right;
};

不需要在除了声明typedef名称的特定行之外的任何地方引用类型为struct NODE_S的类型。 在其他地方,可以直接使用typedef名称。


@supercat,所以"struct FOO"和只是"FOO"不够明显吗?我不知道。所有编程语言都有一些特定于上下文的方面。我的意思是,星号有三个非常不同的含义(声明指针变量,取消引用指针变量,乘法运算,其中可能是浮点数或整数乘法)。什么时候不清楚"FOO"是标签还是类型? - Rich Jahn
忽略前置声明问题,我正在尝试在以下几种选择之间做出决定:完整匿名、相同的标签和类型名称、一些前缀/后缀但是其他都相同。我不喜欢 PREFast 在我保持匿名时发出 "未命名结构体 123" 警告。我也不喜欢 Doxygen 在标签名称与类型名称不同时更喜欢标签名称的情况。 - Rich Jahn
2
考虑以下代码:@RichJahn: typedef struct foo { int x; } foo; int bar; ... void test(void) { int foo; foo *bar; bar = 0; },这个test函数在原样或者将foo *bar语句替换为struct foo *bar;声明后都是有效的,但是在有或没有struct关键字的情况下,它们的含义是不同的。 - supercat

1
原因是在C语言中,结构体标记和typedef名称位于不同的命名空间中。只能在关键字struct之后使用结构体标记,因此知道哪个是哪个非常方便。
在C++中,它们位于同一命名空间中,因此这个问题不会出现。这也意味着你的第二个示例可能不是合法的C++代码。你的第一个示例在两种语言中都是合法的。

1
这是矛盾的。因为你只能在 structunion 之后使用标签,不能在它们之后使用类型名称,所以不会有混淆。将它们命名不同并不能提供任何消歧义,因此没有什么方便的地方。 - Eric Postpischil
@EricPostpischil 这是从错误前提得出的无效推论。(1) 我没有使用“模棱两可”这个词:我说它是“方便的”;(2) 无论如何,消除歧义并不是某物成为方便的先决条件。它之所以“方便”,是因为你知道对于任何结构体X,结构体标签都是XTag,typedef都是X。 - user207421
1
如果名称相同,您就知道对于任何结构X,结构标签为X(在源代码中显示为struct X),类型名称为X(仅出现在没有 struct 的情况下)。将它们命名不同不提供任何额外的功能。另一种看待这个问题的方法是,在前面已经有了struct,所以添加Tag没有意义。 - Eric Postpischil
@supercat:为什么希望这10%被更改?如果它们不被更改会引起什么问题? - Eric Postpischil
@supercat:如果我们认为这是有益的,那么我们会看到你的声明:“如果编译器不接受它们与关键字一起使用,那么最有可能发生这种情况。” 这个声明是说,如果使用不同的标签和类型名称,比如“FooTag”和“Foo”,那么编译器将拒绝 struct Foo x;。但是代码不会包含这样的声明;鉴于不同的名称,声明将是 struct FooTag x;Foo x;,编译器都接受并不会拒绝它们。因此,代码并没有朝着你所声称的这个好处的方向引导。 - Eric Postpischil
显示剩余6条评论

0

在C标准中,没有理由使用不同的标签和类型名称。它们不能相互干扰,因为它们处于不同的命名空间,并且不能在相同的位置使用。

因此,使用不同名称的唯一原因是出于人类心理。尽管其他答案建议在代码中使用不同的标签和类型名称可以更容易地发现它们,但永远不会存在任何歧义,因为结构体标签名称仅出现在struct之后,而且只有结构体标签名称出现在struct之后。此外,如果它们相同,则无法在您打算使用另一个的地方意外使用其中一个。因此,没有必要使用不同的名称。

C++采用结构体标签作为类名的事实证明了没有必要有任何区别。


-2
重点是 SomeStruct 将成为一个正确的类型名称。所以你不必再输入。
struct SomeStructTag my_struct;

在声明中:

SomeStruct my_struct;

除此之外,在我看来,名称后面添加的“_t”代表类型,而不是标签。

2
需要注意的是,以 _t 结尾的标识符是为 POSIX 保留的。 - Captain Obvlious
2
这并没有解释为什么标签会与类型名称不同。 - Eric Postpischil
我没有意识到这是你最初的问题。然而,我发现许多人认为结构体名称是“显式/全局唯一名称”,而typedef名称被认为更具有“口语化、本地唯一性”。虽然我从未遇到过完全不同的名称,除非出于隐藏实现细节的原因(考虑特定于平台的代码)。 - FRob

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