如何在C语言中声明一个结构体头文件,以便多个文件可以使用?

142
如果我有一个包含结构体的source.c文件:

struct a { 
    int i;
    struct b {
        int j;
    }
};

这个结构体应该如何在另一个文件(例如func.c)中使用?

我应该创建一个新的头文件,在那里声明结构体并在func.c中包含该头文件吗?

还是应该在头文件中定义整个结构体,并在source.cfunc.c中都包含它?如何在两个文件中将结构体声明为extern

我应该使用typedef吗?如果是,怎么做?


请注意,结构定义不是有效的C语言。在struct b的右括号后面至少应该有一个分号,但是您的结构体a声明了一个未使用的类型(您可能需要在内部右括号之后、分号之前定义一个成员名称,例如k)。 - Jonathan Leffler
3个回答

167

如果其他文件 func.c 要使用这个结构体,该怎么做?

当一个类型在文件中被使用时(例如 func.c 文件),它必须是可见的。最糟糕的方法是将其复制粘贴到每个需要它的源文件中。

正确的方法是将其放在头文件中,并在需要时包含此头文件。

我们应该打开一个新的头文件,在其中声明该结构体并将该头文件包含在 func.c 中吗?

这是我更喜欢的解决方案,因为它使代码高度模块化。我会编写您的结构体如下:

#ifndef SOME_HEADER_GUARD_WITH_UNIQUE_NAME
#define SOME_HEADER_GUARD_WITH_UNIQUE_NAME

struct a
{ 
    int i;
    struct b
    {
        int j;
    }
};

#endif

我会将使用这种结构的函数放在同一个头文件中(那些“语义上”属于其“接口”的函数)。
通常,我可以根据结构体名称命名文件,并再次使用该名称选择头文件保护定义。
如果您需要使用指向结构体的指针声明函数,则不需要完整的结构体定义。只需使用简单的前向声明即可:
struct a ;

这样就足够了,而且它减少了耦合。

或者我们可以在头文件中定义整个结构,并将其包含在source.c和func.c中吗?

这是另一种方式,相对容易一些,但不太模块化:某些仅需要您的结构才能工作的代码仍然必须包括所有类型。

在C ++中,这可能会导致有趣的复杂性,但这超出了主题(没有C ++标签),因此我不会详细说明。

那么如何在两个文件中将该结构声明为extern?

我看不出重点所在,但Greg Hewgill在他的帖子How to declare a structure in a header that is to be used by multiple files in c?中提供了非常好的答案。

我们应该typedef它吗?如何typedef?

  • 如果您使用的是C ++,则不要。
  • 如果您使用的是C,则应该。

原因是C结构管理可能很麻烦:您必须在使用它的每个地方都声明struct关键字:

struct MyStruct ; /* Forward declaration */

struct MyStruct
{
   /* etc. */
} ;

void doSomething(struct MyStruct * p) /* parameter */
{
   struct MyStruct a ; /* variable */
   /* etc */
}

使用typedef可以让你在不需要写struct关键字的情况下编写代码。

struct MyStructTag ; /* Forward declaration */

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

void doSomething(MyStruct * p) /* parameter */
{
   MyStruct a ; /* variable */
   /* etc */
}

重要的是你仍然为结构体命名。写成:

typedef struct
{
   /* etc. */
} MyStruct ;

只需创建一个具有typedef名称的匿名结构体,您将无法进行前向声明。因此,请遵循以下格式:

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

因此,您可以在任何想要避免添加struct关键字的地方使用MyStruct,并在typedef无法工作(即前向声明)时仍然使用MyStructTag。

编辑:

更正了关于C99结构体声明的错误假设,如Jonathan Leffler所指出的那样。

2018-06-01编辑:

Craig Barnes 在他的评论中提醒我们,您不需要为结构体“标签”名称和其“typedef”名称保留单独的名称,就像我为了清晰起见而上面所做的那样。

实际上,上面的代码可以写成:

typedef struct MyStruct
{
   /* etc. */
} MyStruct ;

据我所知,这实际上是C++在幕后使用其更简单的结构声明来保持与C兼容的方式:

// C++ explicit declaration by the user
struct MyStruct
{
   /* etc. */
} ;
// C++ standard then implicitly adds the following line
typedef MyStruct MyStruct;

回到C语言,我见过两种用法(分别使用不同的名称和相同的名称),我不知道有什么缺点,因此如果你不使用C单独的“命名空间”来区分结构体和其他符号,使用相同的名称可以使阅读更简单。


2
你能否评论或指出C99标准的哪个部分支持你的“如果你正在使用C99,请勿”评论? - Jonathan Leffler
你说得对。最近我测试了一下C99,惊讶地发现当我用C++的方式时,我的未typedef的struct无法被识别。我搜索了编译器选项,并查阅了所有可得到的标准文档,但没有找到任何能够解释这种情况的解释...... - paercebal
2
无论如何,感谢您的评论。我现在已经纠正了它。 - paercebal
不需要为“struct”标签和“typedef”名称使用不同的名称。C语言为“struct”标签使用了不同的命名空间,因此您可以同时使用“MyStruct”。 - Craig Barnes
1
@CraigBarnes 你说得对,但我希望通过阅读代码就能清楚地表达这一点。如果我给了相同的名称,可能会让C语言新手在同一“声明”中需要两次编写名称而感到困惑。我将添加一条注释提及您的评论。谢谢! - paercebal

42

如果一个结构定义将要在多个源文件中使用,那么您应该将其放在头文件中。然后在需要使用该结构的任何源文件中包含该头文件。

extern声明不用于结构定义,而是用于变量声明(即,您已定义了某个具有结构类型的数据值)。如果您想要在多个源文件中使用同一变量,请在头文件中声明为extern

extern struct a myAValue;

接下来,在一个源文件中定义实际变量:one

struct a myAValue;

如果您忘记执行此操作或者意外在两个源文件中定义了它,链接器会告诉您这一点。


你可能会遇到链接错误,也可能不会。在C语言中,链接模型允许使用“common”定义,因此多个没有初始化程序(也许具有相同的初始化程序)的定义可能会起作用。这是一种“常见扩展”。我相信有些编译器还支持缺失定义。 - Jonathan Leffler
然后,在一个源文件中定义实际变量:; …或者意外地在两个源文件中定义它… :) - Johannes Schaub - litb
我用过的一种解决声明/定义问题的技巧是,在头文件顶部有条件地将GLOBAL定义为extern或空,然后将变量声明为GLOBAL struct a myAValue;。在大多数源文件中,您可以安排使用#define GLOBAL extern版本(_声明_变量),并且在恰好一个源文件中,它会导致使用空定义,从而_定义_变量。 - TripeHound
在C语言中,你可以将结构体名称与typedef名称相同,但在C++中不行。 - xuhdev

9

a.h:

#ifndef A_H
#define A_H

struct a { 
    int i;
    struct b {
        int j;
    }
};

#endif

好的,现在你只需要将a.h包含到想要使用这个结构体的文件中。


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