如何在标头中声明要由c中的多个文件使用的结构?


115

如果我有带有结构的source.c文件:

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

如何在另一个文件(即func.c)中使用此结构?

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

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

应该typedef吗 如果是这样,怎么办?


请注意,结构定义无效C。至少应在for的大括号后加一个分号struct b,但随后您的结构a声明一个未使用的类型(您可能应k在内部大括号和后定义一个成员名,–在分号之前
Jonathan Leffler 2014年

Answers:


139

如果此结构将由其他文件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在他的文章中给出了一个很好的答案:如何在标头中声明结构,以供c中的多个文件使用?

我们应该输入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 struct MyStructTag
{
   /* etc. */
} MyStruct ;

因此,您可以在要避免添加struct关键字的任何地方使用MyStruct,并且当typedef不起作用(即,正向声明)时仍可以使用MyStructTag。

编辑:

乔纳森·莱夫勒Jonathan Leffler)正确地指出了有关C99结构声明的错误假设。

编辑2018-06-01:

克雷格·巴恩斯(Craig Barnes)在他的评论中提醒我们,您无需为结构“标记”名称及其“ typedef”名称保留单独的名称,就像我在上面为清楚起见所做的那样。

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

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

IIRC,实际上这是C ++在后台使用其更简单的struct声明来使其与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,则不要”评论的部分吗?
乔纳森·莱夫勒

你是对的。我最近测试了C99,很惊讶地发现使用C ++方式时无法识别我的untypedefed结构。我搜索了编译器选项,然后在所有标准文档中都可以使用,但没有发现任何可以解释的信息,我相信这是可能的……
paercebal

2
...所以无论如何,谢谢你的发言。我现在纠正了。
paercebal

struct标记和名称无需使用其他名称typedef。C为struct标签使用了不同的名称空间,因此您可以同时使用MyStruct它们。
Craig Barnes

1
@CraigBarnes是的,但是我希望仅通过阅读代码就可以清楚地知道这一点。如果给我相同的名称,则可能会使C新手感到困惑,因为他们需要在同一“声明”中写入名称“ *两次”。我将添加一条注释以提及您的评论。谢谢!
paercebal

34

对于要在多个源文件中使用的结构定义,您绝对应该将其放在头文件中。然后将该头文件包含在任何需要该结构的源文件中。

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

extern struct a myAValue;

然后,在一个源文件中,定义实际变量:

struct a myAValue;

如果您忘记这样做或在两个源文件中意外定义了它,则链接器将让您知道这一点。


您可能会收到链接器错误,也可能不会。在C语言中,链接模型允许使用“通用”定义,因此没有初始化程序(可能具有相同的初始化程序)的多个定义可能会起作用。这是一个“通用扩展名”。我相信某些编译器也支持暂定(缺失)定义。
乔纳森·勒夫勒

然后,在一个源文件中,定义实际变量:...或在两个源文件中意外定义了它... :)
Johannes Schaub-litb

我用于声明/定义问题的一种技术是在标头顶部有条件地定义GLOBALas extern或不定义,然后将变量声明为GLOBAL struct a myAValue;。从大多数源文件中,您可以安排要使用的#define GLOBAL extern版本(声明变量),而从一个源文件中恰好会导致使用空的定义,因此可以定义变量。
TripeHound 2014年

您可以在C中使用与typedef名称相同的结构名称,但在C ++中不能使用。
xuhdev 2014年

6

啊:

#ifndef A_H
#define A_H

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

#endif

到这里,现在您只需要在要使用此结构的文件中包含ah即可。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.