C结构带有前向声明的typedef

我在我的应用程序中的任何地方都使用了typedef。 然后,当它开始变得笨重时,我开始重构成几个头文件。 我注意到我需要转发声明Object和Klass。 嗯,令我惊讶的是,我无法转发声明Object或Klass。 这是因为,正如你在Object和Klass结构中看到的那样,我使用了Object和Klass的typedef。

//Klass.h typedef struct Klass Klass_t; struct Klass { void (*_initialize)(Object_t* object, Klass_t* klass); }; //Object.h typedef struct Object Object_t; struct Object { Klass_t* _klass; }; 

起初,使用typedef很棒。 但是试图转发声明对象:

 struct Object_t; 

不起作用,因为我需要通过函数声明重写为:

 void (*_initialize)(struct Object_t* object, Klass_t* klass); 

所以我决定在Klass.h头文件中输入typedef:

 typedef struct Object Object_t; 

当所有头文件都包含在我的Main.c文件中时,它会包含:

 Object.h:5: error: redefinition of typedef 'Object_t' 

所以,我决定只删除所有struct typedef和explicity声明我的结构。

有没有办法在不明确使用struct Object的情况下在另一个文件中输入结构和转发声明?

我想在结构声明的头文件中保留结构typedef。 如果我必须将所有typedef分组到一个头文件中,那么我宁愿不使用typedef。 无论如何,谢谢你的时间。

请记住, typedef只是类型的替代名称。 有一个原因,Linux内核不会将typedef用于结构类型,而是在展示它。

您可以通过几种方式解决问题。 我注意到C11确实允许多次出现相同的typedef ,但我假设你遇到了一个不支持它的旧编译器。

TL; DR

即使Linux内核不使用typedef ,我通常也会使用typedef ,但我主要避免使用相互引用的结构类型(我想不出任何使用这种类型的代码)。 而且,就像Jens Gustedt在他的回答中指出的那样,我几乎总是使用这些符号:

 typedef struct SomeTag SomeTag; 

因此类型名称和结构标记是相同的(它们位于不同的名称空间中)。 在C ++中不需要这个操作; 当你定义struct SomeTagclass SomeTag ,名称SomeTag变成了一个类型名称,而不需要一个显式的typedef (尽管typedef除了揭示作者在C中比C ++更有经验,或者代码起源于C之外没有任何害处码)。

我还观察到以下划线开头的名称最好被视为“为实现保留”。 这些规则比这复杂得多,但是当你使用以下划线开头的名字时,你冒着篡夺你的名字 – 以及篡夺你的名字的权利 – 的风险,所以不要这样做。 同样,如果包含任何POSIX标头(例如,当您未在严格的仅标准C模式下编译时, ,POSIX会为实现保留以_t结尾的类型名称。 避免创建这样的名称; 他们迟早会伤到你的。 (我没有修复下面的代码来处理这些问题中的任何一个: 告诫emptor !)。

在下面的代码片段中,我大多忽略了防止多个包含的代码(但是你应该在代码中使用它)。

额外标题

typedefs.h:

 typedef struct Object Object_t; typedef struct Klass Klass_t; 

klass.h

 #include "typedefs.h" struct Klass { void (*_initialize)(Object_t *object, Klass_t *klass); }; 

object.h

 #include "typedefs.h" struct Object { Klass_t *_klass; }; 

这是有效的,因为在使用它们之前声明了两个类型名Klass_tObject_t

在原型中使用struct Object

klass.h

 typedef struct Klass Klass_t; struct Object; struct Klass { void (*_initialize)(struct Object *object, Klass_t *klass); }; 

或者,为了保持一致性,它甚至可能使用:

  void (*_initialize)(struct Object *object, struct Klass *klass); 

object.h

 #include "klass.h" struct Object { Klass_t *_klass; }; 

这是因为(在宽范围内 – 基本上,如果类型是在文件范围定义的,而不是在函数内定义), struct Object总是引用相同的类型,无论是否所有细节都已完全定义。

GCC 4.8.2

-std=c89-std=c99-std=c11 ,GCC 4.8.2接受复制的typedef ,如下面的代码所示。 它需要-std=c89 -pedantic-std=c99 -pedantic来获取有关重复的typedef的错误。

即使没有-pedantic选项,GCC 4.5.2也拒绝此代码; 但是,GCC 4.6.0及更高版本在没有-pedantic选项的情况下接受它。

klass.h

 #ifndef KLASS_H_INCLUDED #define KLASS_H_INCLUDED typedef struct Klass Klass_t; typedef struct Object Object_t; struct Klass { void (*_initialize)(Object_t *object, Klass_t *klass); }; #endif /* KLASS_H_INCLUDED */ 

object.h

 #ifndef OBJECT_H_INCLUDED #define OBJECT_H_INCLUDED typedef struct Klass Klass_t; typedef struct Object Object_t; struct Object { Klass_t *klass; }; #endif /* OBJECT_H_INCLUDED */ 

consumer.c

 #include "klass.h" #include "object.h" Klass_t k; Object_t o; 

你必须决定你是否愿意为你的代码带来风险 – 可移植性有多重要,以及哪些版本的C(以及哪些C编译器)必须是可移植的。

您缺少struct关键字。 它应该是

 typedef struct Object Object_t; 

应始终以这种方式进行前向声明(但见下文)。 此转发同时声明typedef标识符和struct标记。

只需在真实声明之前放置所有 struct前向声明。 只要你在声明中只使用指向这些struct指针,那么一切都应该没问题。

nitpick:带有_t名称由POSIX保留。 这意味着你应该避免它,因为某些平台上的某一天可能存在预定义的Object_t并且会与你的类型冲突。

我个人更喜欢以下惯例

 typedef struct Object Object; 

所以带有或不带struct的单词Object总是指相同的。