【问题标题】:Why is "typedef struct foo foo;" considered harmful?为什么是“typedef struct foo foo;”被认为是有害的?
【发布时间】:2012-04-20 08:59:10
【问题描述】:

typedef and struct namespaces in C vs C++ 中,其中一个 cmets 似乎暗示暴露一些 struct foo 比使用 typedef 更可取...

typedef struct foo foo;

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

后一种变体有什么缺点吗?

【问题讨论】:

  • 我可能是错的,但我认为这在 C++ 中是隐含的(也就是说,您可以使用 foo 而不是 struct foo 而无需任何明确的 typedef)。
  • @jtbandes 所以?问题是关于 C 的。
  • @Lundin 从问题正文中我并不清楚,但标签表明您可能是对的。无论哪种方式,指出 C++ 行为都没有错!

标签: c coding-style


【解决方案1】:

唯一的缺点 (*) 是它隐藏了 foo 是一个结构,而不是某些内置类型的别名这一事实。

注意(*):这对你来说是否不利是个人喜好问题。

  • 它有利于完全不透明(请参阅下面的第一条评论)。
  • 要了解为什么有些人认为这是一个缺点,请查看linux kernel coding style(typedefs 章节)。

【讨论】:

  • 我认为这是一个功能。当然,它并没有真正隐藏任何东西,但它建议更好地使用库的辅助函数,而不是直接处理数据。
【解决方案2】:

这取决于您对struct 这个词的喜爱程度。如果你觉得你的程序会被struct thatstruct tother (当然你不能在C++ 中使用struct this)使你的程序更清晰,那么一定要使用struct 版本。

就我个人而言,我不认为重复 struct 会带来任何好处,我很乐意只使用 typedef 名称。而且因为 C++ 有效地自动提供了 typedef struct xyz xyz; 声明(它不是很准确,尤其是因为你可以用 C++ 显式编写它,但它足够接近,你可能不必担心它),我认为在 C 中使用它是非常有意义的。C 编译器对此很满意,所以我通常使用 typedef struct tag tag;,然后在需要时使用 tagtag *


对于另一种但完全站得住脚的观点,请阅读Linux kernel coding style 指南。


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

ISO/IEC 9899:2011 §6.7 声明

语义

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

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

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

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

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

与 C99 对比,这是不可能的:

ISO/IEC 9899:1999 §6.7 声明

语义

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

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

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

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

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

【讨论】:

  • 谁写的风格指南?
  • @Pacerier:我不确定谁编写了 Linux 内核风格指南。可能不是 Linus(它说 '编码风格是非常个人化的,我不会 将我的观点强加于任何人,但这就是我必须能够保持的任何东西,并且我也更喜欢它用于大多数其他事情。' 这听起来没有 Linus 的权威),但我很有信心它得到了 Linus 事实上的祝福。
  • Linux 内核编码风格中的规则本身被认为有些有害,因为它只是在宣传错误知识:它假定通过规范类型进行的抽象无条件无用。根据受众和具体项目,对于某些特定情况可能是正确的,但总的来说,对于任何能够进行静态类型的高级语言来说,至少是错误的。
【解决方案3】:

关于是否typedef结构类型:

这里有一些意见(都反对类型定义结构):

来自 OpenBSD 风格指南:

“避免对结构类型使用 typedef。这使得应用程序无法不透明地使用指向此类结构的指针,这在使用普通结构标记时既可行又有益。”

来自 Linux 内核编码风格:

“将 typedef 用于结构和指针是一个错误。”

来自 Peter Van der Linden 的 Expert C Programming:

“不要为结构体的 typedefs 操心。它们所做的只是省去你写“结构体”这个词的麻烦,这是一个你可能不应该隐藏的线索。”

【讨论】:

  • OpenBSD 风格指南在这两个方面都是错误的:typedef struct foo foo; 可用于不透明指针,并且也是有效的 C++;虽然如果你不使用 typedef,你可以在没有显式前向声明 struct foo; 的情况下声明指针变量,但无论如何你都应该添加一个:否则,如果结构的第一次出现是在函数声明中,则类型将具有原型范围,这很少需要 - 虽然由于类型兼容性规则它实际上并没有害处,但编译器可能会警告它......
  • 我发现 Linux 内核风格总体上相当晦涩,对可移植性的考虑为零(不透明类型在编写可移植代码时非常有用),所以我不认为它应该被视为某种形式的Linux 内核编程之外的权威。另一方面,Windows API 则完全相反:它总是对结构进行 typedef。我认为这主要只是风格问题。
  • 更可靠的权威是 MISRA-C 文件,它基于实际的科学研究。 MISRA-C 声明 typedef:ed 名称和结构标记都必须是唯一标识符,以避免出现问题。他们可能允许不透明类型的例外,但我不确定。
  • 实际上,我的评论是错误的:如果声明共享翻译单元,类型兼容性将无济于事;如果没有前向声明,一个简单的void foo(struct bar *);void foo(struct bar *); 将无法编译,因为这两种struct bar 类型将不兼容...
  • @Christoph:天哪,所以 OpenBSD 引用相当于,“避免对结构类型使用 typedef。这将迫使我们在头文件中添加结构的前向声明,声明函数采用指向这些结构的指针结构。我们选择不这样做。”?听起来很像本地风格规则,并不普遍适用。
【解决方案4】:

这或多或少是一个品味问题。

只需声明一个struct 标记,该名称也可用于同一范围内的变量、函数或枚举器(如果您喜欢混淆,甚至可以是其他类型);并且用户必须写struct这个词,这使得他们的代码更加明确。

通过同时声明类型名称,您可以允许人们在不想输入时不要输入struct

我在另一个问题中的评论是指与结构标签同名的 pointer 类型的声明:

typedef struct foo * foo;

在风格上,这有点令人不快,因为它隐藏了它是一个指针的事实。这隐藏了它是一个指针的事实;在那个问题的上下文中,这可能很好,这是由 API 定义的不透明类型,但在我看来,对于非透明类型来说是相当粗鲁的。它还破坏了与 C++ 的兼容性。在该语言中,这样的声明是无效的,因为struct foofoo 引入当前命名空间而不是单独的标记空间,并阻止在该命名空间中声明具有相同名称的任何其他类型。

【讨论】:

  • 我认为不透明类型也不能使用指针表示法;我不明白为什么他们会是一个特例。将指针隐藏在 typedef 后面是非常非常糟糕的做法,故事结束。当我编写不透明类型时,我会像 typedef struct foo foo; 一样执行它们,然后强制调用者使用指针表示法将它们的对象声明为 foo* obj;。这与在调用者使用动态分配时强制调用者使用指针本质上是一样的。你永远不会看到像typedef void* malloc_t; ... malloc_t obj = malloc(...); 这样的东西,那么为什么不透明类型会有所不同。
  • @Lundin:不透明类型会有所不同,因为malloc 的调用者最终将不可避免地使用其返回值作为指针,而并非所有函数都如此。如果你的 API 返回的东西就调用者而言纯粹是一个不透明的句柄,但出于实现原因,它实际上是一个地址很方便,那么 IMO 你不应该仅仅因为你觉得句柄类型是指针类型指针是通常的经验法则的一个例外,即不相关的实现细节应该被隐藏。
  • @Lundin:嗯,这就是你的意见;其他人对什么是“非常非常糟糕的做法”以及故事的结局有其他想法。让事情保持客观。 (当然,malloc 的返回值不是不透明的;它是指向您请求的内存的指针)。
  • 请考虑 FILE* 是指针类型,而文件描述符是整数,这遵循您的规则,Lundin。但是对于用户来说,fopen 返回的类型是被称为FILE* 还是只是FILE 完全没有区别。如果我想实现文件系统 API,使得 FILE* 的值实际上只是转换为 FILE* 的 fd(或其他特定于操作系统的句柄),其中 FILE 是一个空结构,那么我几乎可以.调用者错误地认为它是一个地址这一事实只是一个小障碍,但我不会那样设计 API。
【解决方案5】:

只要遵守命名约定就可以了。

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

这样你就会知道它是一个自定义类型。至于它是一个结构,只需使用明确的名称而不是真正的t_mytype

【讨论】:

  • 或者根本不用为结构命名,只需执行typedef struct { .. } mytype
【解决方案6】:

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

#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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-16
    • 1970-01-01
    • 2020-10-03
    • 2021-12-16
    • 2011-08-30
    • 2010-09-30
    • 2011-10-21
    相关资源
    最近更新 更多