【问题标题】:Best place to define a struct for a library [closed]为库定义结构的最佳位置[关闭]
【发布时间】:2016-06-06 06:40:00
【问题描述】:

目前我正在为通信协议编写一个库。对于这个任务,我需要这个结构:

typedef struct _C8B10 {
    unsigned int six :6;
    unsigned int four :4;
} C8B10;

但是我应该在哪里定义它?我有一个包含在所有其他库中的“主”*.h 文件。每个库的 *.c 文件仅包括“父”*.h 文件。结构类似于树:

Main.h
      Main.c
  Child.h
      Child.c
  Child2.h
      Child2.c
  ....

孩子总是包括Main.h 和child.c 他们的父母。但在一个库中,我收到错误,即类型未定义。

目前它在 Main.h 文件中声明。但是放在哪里最好呢?

【问题讨论】:

  • 如果报错,标头不包含(或尝试使用后包含)
  • 我也这么认为。但即使我清理项目,删除包含并再次包含库,也没有任何变化,并且错误像以前一样出现。
  • 这是一个错字吗:“孩子总是包括... child.c”?作为建议:始终使用包含警卫:en.wikipedia.org/wiki/Include_guard
  • 不,这应该意味着'Child.h'等包含'main.h'并且'child.c'包含'child.h'。所以有一个树结构,没有圆圈。所有 *.h 文件都有一个唯一的包含保护。

标签: c struct typedef


【解决方案1】:

命名空间冲突

无论您做什么,都应该将结构标记从_C8B10 更改为不会践踏为实现保留的命名空间的内容。

ISO/IEC 9899:2011

7.1.3 保留标识符

¶1 每个标头声明或定义其相关子条款中列出的所有标识符,并且 可选地声明或定义在其关联的未来库方向中列出的标识符 始终保留用于任何用途或用作文件的子条款和标识符 范围标识符。
— 以下划线和大写字母或其他字母开头的所有标识符 下划线始终保留用于任何用途。
— 所有以下划线开头的标识符始终保留用作标识符 在普通名称空间和标记名称空间中都具有文件范围。

以下划线开头的名称有风险;它们是为系统库的提供者而存在的。显然,如果您的库是实现的一部分,那么这不适用于您,但我认为如果是这种情况,您不太可能会问这个问题。

如果是我的代码,我会直接去掉下划线:typedef struct C8B10 { ... } C8B10; 很好(typedef 名称在普通标识符命名空间中,结构标签在标签命名空间中,两者不冲突) .


头文件组织

您的图表与大多数人通常编写此类图表的方式相反。源代码包括标题,所以它可能被图解为:

main.c
    main.h
child1.c
    child1.h
child2.c
    child2.h

然而,这不可能是全部。您创建一个标头以在源文件之间共享声明;仅包含在一个文件中的标头并非绝对必要(尽管创建此类文件可能有正当理由)。文件main.c 使用child1.c 中定义的一些函数(和类型——可能是宏甚至全局变量,别想了),或者child1.c 中的代码使用main.c 中的材料(或者可能两者都使用) )。因此,main.c 应包含 child1.h 或 child1.c 应包含 main.h 或两者兼而有之。

对于库,您需要考虑的另一个方面是“库的客户将如何使用代码?”这是至关重要的。客户端代码需要什么标头?客户端代码需要哪些功能?客户端代码是否需要任何类型定义?客户端代码是否需要访问 C8B10 结构的成员,还是只需将其视为不透明类型?

您的外部标头(库的客户使用的标头)应尽可能小,但自包含。假设外部标头是c8b10.h。如果客户端源代码将#include "c8b10.h" 作为源文件中的第一个或唯一标头,则标头中的代码应编译。如果你的接口使用size_t,例如,你需要在c8b10.h中#include <stddef.h>。

在您的情况下,我希望您的C8B10 结构应在main.h 中定义,并且child1.c 和child2.c 都应包含main.h。 main.c 很可能还应该包括 child1.h 和 child2.h。而main.h 应该包含外部c8b10.h 标头,所以实际上每个文件都包含它。

main.c
    main.h
        c8b10.h
    child1.h
    child2.h
child1.c
    main.h
        c8b10.h
    child1.h
child2.c
    main.h
        c8b10.h
    child2.h

child2.c是否需要包含child1.h,child1.c是否需要包含child2.h取决于代码的编写方式以及每个文件提供的哪些服务在哪里使用。

您可能会发现只有两个标头更明智——外部标头c8b10.h 和一个内部标头c8b10-private.h。 c8b10-private.h 标头将包含在库中的每个源文件中。它将包含的第一个标头是外部 c8b10.h 标头(以帮助自动检查 c8b10.h 标头是自包含的)。 c8b10-private.h 标头将对应于main.h、child1.h 和child2.h 的合并,减去c8b10.h 中定义的外部可访问内容。这导致:

main.c
    c8b10-private.h
        c8b10.h
child1.c
    c8b10-private.h
        c8b10.h
child2.c
    c8b10-private.h
        c8b10.h

要记住的关键点是标头用于源文件之间的通信。其余的大部分都是自动的。

【讨论】:

  • 感谢您的精彩回答!我重命名了没有'_'的结构。用户应该实现“main.h”,他有大约 5 种方法可以做所有事情。用户不需要使用定义的结构。它仅用于给定函数内部的计算。 “main.h”包含在所有其他头文件中,实现文件仅包含它们的头文件,而不包含“main.h”。到目前为止一切正常,但是一个头文件会抛出错误,即使这个头文件的实现使用这个结构并且编译没有任何错误或警告......
  • 另请参阅Should I use #include in headers?,了解有关自包含(和幂等)标头的更多信息。 Design Principles, Best Practices and Design Patterns for C 也有有用的信息。如果您有全局变量,请阅读What are extern variables in C? 处的建议
【解决方案2】:

头文件通常像 Java 的接口一样使用,因为头文件定义了在实现之外可用的信息。

如果结构应该在实现之外可用,请在标头中定义它,否则在包含在其他 .c 文件中的 .c 文件中定义一次。

【讨论】:

    猜你喜欢
    • 2011-09-30
    • 2020-07-19
    • 2011-09-30
    • 2017-04-16
    • 1970-01-01
    • 2020-10-24
    • 1970-01-01
    • 1970-01-01
    • 2013-05-15
    相关资源
    最近更新 更多