为什么“typedef struct foo foo;”被认为是有害的?

14

C和C++中typedef和结构体名称空间中,其中一个评论似乎暗示暴露某些struct foo比沿用typedef更可取...

typedef struct foo foo;

...然后在整个API中使用foo而不是struct foo

这种后一种方式有什么缺点吗?


3
可能我说错了,但我认为这在C++中是隐式的(也就是说,你可以使用foo而不需要显式地使用typedef来代替struct foo)。 - jtbandes
http://eetimes.com/discussion/programming-pointers/4024450/Tag-vs-Type-Names - William Bettridge-Radford
2
@jtbandes 那又怎样?这个问题是关于C语言的。 - Lundin
1
@Lundin 从问题描述中并不清楚,但标签表明你可能是正确的。无论如何,指出C++的行为没有任何问题! - jtbandes
6个回答

9
唯一的缺点是它隐藏了 foo 是一个结构体,而不是某种内置类型的别名。
注意:对于你来说,这是否是一个缺点纯属个人喜好。
  • 它非常适合完全的不透明性(请参见下面的第一条评论)。
  • 要了解为什么有些人认为这是一个缺点,请查看 linux 内核编码风格(typedefs 章节)。

3
我认为这是一种特性。当然,它并没有真正隐藏任何内容,但它建议最好使用库的辅助函数而不是直接操作数据。 - Philip

9
关于是否使用typedef定义结构体类型:
以下是一些意见(全部反对使用typedef定义结构体类型):
来自OpenBSD样式指南:
“避免使用typedef定义结构体类型。这使得应用程序无法透明地使用指向这样的结构体的指针,而在使用普通结构tag时这是可能且有益的。”
来自Linux内核编码风格:
“对于结构体和指针使用typedef是一个错误。”
来自Peter Van der Linden的《专家C编程》:
“不要为结构体使用typedef。它们所做的只是让你少写一个‘struct’单词,这表明你可能不应该隐藏它。”

4
OpenBSD风格指南在两个方面都是错误的:typedef struct foo foo; 可以用于不透明指针,而且也是合法的C++语法;虽然如果你不使用typedef,你可以声明没有显式前向声明struct foo;的指针变量,但你仍应该加一个:否则,如果结构体的第一次出现是在函数声明中,那么类型将具有原型作用域,这很少是想要的--虽然它实际上并不会有害,因为类型兼容规则,但编译器可能会发出警告... - Christoph
4
总的来说,我认为Linux内核风格相当晦涩难懂,没有考虑可移植性(在编写可移植代码时,不透明的类型非常有帮助),因此我不认为它应该被视为Linux内核编程之外的某种权威。而另一方面,Windows API恰恰相反:它总是对结构进行typedef处理。我认为这主要只是个风格问题。 - Lundin
1
实际上,我的评论是错误的:如果声明共享一个翻译单元,类型兼容性是无法帮助的;一个简单的 void foo(struct bar *);void foo(struct bar *); 将在没有前向声明的情况下无法编译,因为两个 struct bar 类型将不兼容... - Christoph
那个OpenBSD文档在说什么,我一点也不知道。我经常使用 typedef name name 的方式声明不透明类型,并使用指向该不透明类型的指针。也许他们假设不透明类型必须始终是 typedef name* name 或类似的形式。 - Lundin
3
@Christoph: 天哪,那么OpenBSD的这句话的意思是,“避免对结构类型使用typedef。这将强制我们在声明函数接受指向这些结构体指针的头文件中添加前向声明。我们选择不这样做。”听起来非常像一条本地样式规则,而不是普遍适用的规则。 - Steve Jessop
显示剩余3条评论

8
这取决于你对"struct"这个词的喜好程度。如果你觉得在程序中大量使用"struct that"和"struct tother"可以使程序更加清晰(当然,在C++中不能使用"struct this"),那么请务必使用"struct"版本。
个人认为,重复使用"struct"并没有任何好处,我很乐意只使用"typedef"名称。由于C++实际上自动提供了"typedef struct xyz xyz;"声明(它不完全准确,因为你可以在C++中明确地编写它,但它足够接近,你可能不必担心),所以我认为在C语言中使用相同的声明是完全有道理的。C编译器能够处理它,所以我通常使用"typedef struct tag tag;",然后在需要的地方使用"tag"和"tag*"。

若要了解另一种但完全可行的观点,请阅读Linux内核编码风格指南。


请注意,C2011允许您重新定义typedef,只要它与相同类型别名即可:

ISO/IEC 9899:2011 §6.7 Declarations

语义

¶5 声明指定一组标识符的解释和属性。标识符的定义是该标识符的声明:

— 对于对象,导致为该对象保留存储空间;

— 对于函数,包括函数体;119)

— 对于枚举常量,是标识符的(唯一)声明;

— 对于typedef名称,是标识符的第一个(或唯一)声明。

与不可能在C99中相比:

ISO/IEC 9899:1999 §6.7 Declarations

语义

¶5 声明指定了一组标识符的解释和属性。对于一个标识符的定义是指对该标识符的声明:

— 对于一个对象,会导致为该对象保留存储空间;

— 对于一个函数,包括函数体;98)

— 对于一个枚举常量或typedef名称,是该标识符的(唯一)声明。

只要您保持一致(但仅当您在每个相关平台上都有足够兼容的C2011编译器时),这将简化类型定义的创建。


那个风格指南是谁写的? - Pacerier
@Pacerier:我不确定是谁编写了Linux内核风格指南。可能不是Linus(它说:“编码风格非常个人化,我不会强迫任何人接受我的观点,但这适用于我必须维护的任何内容,而且我也希望大多数其他事情都是如此。”听起来比Linus的权威性要低一些),但我相信它已经得到了Linus的事实上的认可。 - Jonathan Leffler
Linux内核编码风格本身的规则被认为有些有害,因为它只是宣传了错误的知识:它假设通过规范化的类型抽象在任何情况下都是无用的。根据受众和具体项目,对于某些特定情况来说这可能是正确的,但一般来说,对于任何能够进行静态类型检查的高级语言来说,至少是错误的。 - FrankHB

2

这更多或少是一种口味问题。

仅通过声明一个struct标签,该名称也可用于在同一作用域中的变量、函数或枚举器(甚至另一种类型,如果您喜欢混淆)中使用;用户必须编写单词struct,这使得他们的代码更加明确。

通过还声明类型名称,您允许人们不想键入struct时可以不用键入。

我在其他问题中的评论是指声明与结构标记相同名称的指针类型:

typedef struct foo * foo;

从风格上讲,这样做有点不愉快,因为它隐藏了它是一个指针的事实。在那个问题的背景下,这是由API定义的不透明类型,这可能还好,但在我看来,对于非不透明类型而言,这样做可能会相当无礼。它也破坏了与C++的兼容性。在那种语言中,这样的声明是无效的,因为struct foo引入了foo到当前命名空间中,而不是一个单独的标签空间,并防止在该命名空间中声明任何其他具有相同名称的类型。


1
我认为不应该让不透明类型使用指针符号; 我不明白它们为什么会是一个特殊情况。在typedef后面隐藏指针是非常糟糕的做法,这就是结局。当我编写不透明类型时,我会这样做 typedef struct foo foo; 然后强制调用者使用指针符号来声明他们的对象 foo* obj; 。这本质上与在使用动态分配时强制调用者使用指针是一样的。你从来没有看到过这样的东西 typedef void* malloc_t; ... malloc_t obj = malloc(...); ,那么为什么不透明类型会有所不同呢? - Lundin
1
@Lundin:不透明类型会有所不同,因为 malloc 的调用者不可避免地最终会将其返回值用作指针,而并非所有函数都是如此。如果您的 API 返回某些内容,从调用者的角度来看它纯粹是一个不透明句柄,但出于实现原因,它实际上是一个地址更方便,那么在我看来,您不应该仅仅因为感觉指针是隐藏无关实现细节的通常准则的例外而使句柄类型成为指针类型。 - Steve Jessop
@Lundin:那是你的看法;其他人对于何为“非常非常糟糕的做法”以及故事如何收场有不同的想法。让我们保持客观。当然,malloc的返回值并不是不透明的;它是您请求的内存的指针。 - Mike Seymour
考虑到FILE*是一个指针类型,而文件描述符是整数类型,这符合您的规则,Lundin。但对于用户来说,fopen返回的类型是被称为FILE*还是只是FILE并没有任何区别。如果我想要实现文件系统API,使得FILE*的值实际上只是转换为FILE*的fd(或其他特定于操作系统的句柄),其中FILE是一个空结构体,那么我几乎可以这样做。调用者错误地认为它是一个地址的事实只是一个小障碍,但我不会以这种方式设计API。 - Steve Jessop

1

只要遵循命名规范,你就没问题了。

typedef struct
{
  //...
}              t_mytype;
//...
t_mytype thing;

这样你就知道它是一个自定义类型了。至于它是否为结构体,只需使用显式名称而不是真正的t_mytype


或者干脆不给结构体命名,直接使用 typedef struct { .. } mytype - GrahamS

0

另一个反对typedef的论点是它很难编写干净的头文件。让我们看一个头文件的例子:

#ifndef HEADER_H
#define HEADER_H
#include "s.h"
void f(struct s *);
#endif

这个头文件可以改进。实际上,struct s 不需要被定义。因此,它可以写成:

#ifndef HEADER_H
#define HEADER_H
struct s;
void f(struct s *);
#endif

因此,用户可以将struct s用作不透明结构,我们可以将"s.h"保密。
让我们使用typedef做同样的事情:
#ifndef HEADER_H
#define HEADER_H
typedef struct s s_t;
void f(s_t *);
#endif

每个想要使用 s_t * 的头文件中都会出现 typedef struct s s_t 这一行代码,我不喜欢冗余的代码。有多种方法可以避免这种情况,但最简单的方法可能是摆脱 typedef。


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