C结构带有前向声明的typedef

时间:2014-02-02 19:25:45

标签: c

我在应用程序的各个地方都使用了typedef。然后我开始重构几个 头文件开始变得笨重时。我注意到我需要转发声明Object和Klass。 嗯,令我惊讶的是,我无法转发声明Object或Klass。这是因为,正如你在中看到的那样 对象和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并明确声明我的结构。

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

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

2 个答案:

答案 0 :(得分:4)

您错过了struct关键字。它应该是

typedef struct Object Object_t;

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

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

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

我个人更喜欢以下惯例

typedef struct Object Object;

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

答案 1 :(得分:3)

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

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

TL; DR

即使Linux内核不使用typedef,我通常使用typedef s,但我主要避免相互引用结构类型(我不能想想我使用过这种类型的代码。并且,就像他Jens Gustedt中的answer注释一样,我几乎总是使用这些符号:

typedef struct SomeTag SomeTag;

以便类型名称和结构标记相同(它们位于不同的名称空间中)。在C ++中不需要此操作;当您定义struct SomeTagclass SomeTag时,名称SomeTag将成为类型名称,而不需要明确的typedef(尽管typedef除了_t之外没有其他任何损害。揭示作者在C中比C ++更有经验,或者代码起源于C代码。

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

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

额外标题

typedefs.h:

#include "typedefs.h"

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

klass.h

#include "typedefs.h"

struct Object
{
    Klass_t *_klass;
};

object.h

Klass_t

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

在原型

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

klass.h

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

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

#include "klass.h"

struct Object
{
    Klass_t *_klass;
};

object.h

struct Object

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

GCC 4.8.2

在所有-std=c99-std=c11typedef下,GCC 4.8.2接受已复制的-std=c89 -pedantic,如下面的代码所示。它需要-std=c99 -pedantictypedef才能获得有关重复-pedantic的错误。

即使没有-pedantic选项,GCC 4.5.2也会拒绝此代码;但是,GCC 4.6.0及更高版本在没有#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 */ 选项的情况下接受它。

klass.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 */

object.h

#include "klass.h"
#include "object.h"

Klass_t k;
Object_t o;

consumer.c

{{1}}

您必须决定这是否是您愿意承担代码的风险 - 可移植性的重要性,以及C(以及哪些C编译器)必须可移植的版本。