【问题标题】:are header guards limited to the scope of their respective library?标头保护是否仅限于其各自库的范围?
【发布时间】:2019-02-28 19:07:29
【问题描述】:

我继承了一些代码*,它在头文件 (a_A.h) 中声明和定义了一个结构。此结构位于包含层次结构树的顶部文件中,其符号如下所示:

 file: t_T.h (#includes "c_C.h") //defines a struct
 file: c_C.h (#includes "h_H.h")
 file: h_H.h (#includes "a_C.h")
 file: a_C.h (#includes <stdio.h>) 

每个标头都有适当的标头保护,并且在作为文件的平面集合查看时似乎是非递归的。但是,文件 c_C.h 和 a_C.h 位于同一个库中。而 h_H.h 位于不同的库中。从图书馆的角度来看,这象征性地表现为:

t_T.h (includes a file from Lib_C) 
Lib_C: (includes a file from Lib_H)
Lib_H (includes a file from Lib_C)

这是递归的,可能是我编译代码时重新定义问题的原因(链接器抱怨文件 a_C.h 中的结构被重新定义)。

1) 我是否正确识别了问题?

2) 如果是这样,为什么?我猜想库中的链接对象对链接器来说是平坦的(即它们已经丢失了层次结构上下文)。如果猜对了,那么:

3) 我是否应该考虑将标头保护限制在其各自库的范围内?

以下是问题窗口中的错误说明:

symbol "ov5642_1280x960_RAW" redefined: first defined in "./TakePhoto.obj"; redefined in "./ArduCam/ov5642_Config.obj"  

./TakePhoto 中的标题:

#ifndef TAKEPHOTO_H
#define TAKEPHOTO_H
#include "ov5642_Config.h"
#include "HAL_ArduCAM.h"
...
#endif /* TAKEPHOTO_H_ */

./ArduCAM/ov5642_Config.h 中的标头:

#ifndef ARDUCAM_OV5642_CONFIG_H_
#define ARDUCAM_OV5642_CONFIG_H_
#include "HAL_ArduCAM.h"
#include "ov5642_Sensor_Values.h"
....
#endif /* ARDUCAM_OV5642_CONFIG_H_ */

HAL_ArduCAM 中的标头

#ifndef  HAL_ArduCAM_h
#define  HAL_ArduCAM_h
#include <stdint.h>
#include "driverlib.h"
....
#endif /* HAL_ArduCAM_h */

ov5642_Sensor_Values.h 有以下内容

#ifndef ARDUCAM_OV5642_SENSOR_VALUES_H_
#define ARDUCAM_OV5642_SENSOR_VALUES_H_
#include <stdint.h>
const struct sensor_reg ov5642_1280x960_RAW[] =
{
     {0x3103,0x93},
     {0x3008,0x02},
     {0x3017,0x7f},
 .....

#endif /* ARDUCAM_OV5642_SENSOR_VALUES_H_ */

OV5642_Sensor_Values 的内容似乎被复制了两次,一次用于 TakePhoto,一次用于 ovV5642_Config,尽管它们有标头保护。我最初的想法是存在递归依赖关系但没有找到它。

好的,我在下面粘贴了一个示例。此示例中有五个文件,三个文件(bar.h、foo.h、foo.c 驻留在库中),其他两个文件(foo1.h、foo1.c)不存在。请注意,foo.h 包含 bar.h,而 foo1 包含 foo.h 和 bar.h。

我推测当预处理器复制到 foo.h 时,bar.h 的保护标头不会被保留。因此,当 foo1 包含 bar.h 和 foo.h 时,就会出现符号重新定义。所以回答我自己的问题,不,这不是图书馆问题。不保留标题保护似乎是我的问题的可能原因。

它们粘贴在下面。

/*
 * bar.h
 *
 */

#ifndef ARDUCAM_BAR_H_
#define ARDUCAM_BAR_H_
#include <stdint.h>

typedef struct regStruct {
     uint16_t reg;
     uint8_t val;
 } regStruct;

 const struct regStruct regArray[] =
  {
      {0x3103,0x03},
      {0x3104,0x03},
      {0x3008,0x82},
      {0xffff,0xff},
  };
 const struct regStruct tinyArray[] =
   {
       {0x3106,0x03},
       {0x3003,0x82},
       {0xffff,0xff},
   };

#endif /* ARDUCAM_BAR_H_ */

/*
 * foo.h
 *
 *
 */

#ifndef ARDUCAM_FOO_H_
#define ARDUCAM_FOO_H_

#include <stdint.h>
#include <stdio.h>
#include "bar.h" //including this file causes redefinition


typedef struct Init_Parameters {
    //! Select sensor resolution
    //! options.
    //! \n Valid values are:
    //! - \b big
    //! - \b small
    //! - \b tiny
    uint8_t size;

} Init_Parameters;


uint8_t Sensor_Init(Init_Parameters *param);

typedef enum {
    small=0,
    big,
    tiny
} select_size;

#endif /* ARDUCAM_FOO_H_ */

/*
 * foo.c
 *
 *
 */

#include "foo.h"

uint8_t Sensor_Init(Init_Parameters *param)
{
    switch(param->size)
    {
    case big:
        break;
    case  small:
        break;
    case  tiny:
        break;
    }
    return 0x01;
}

/*
 * foo1.h
 *
 *  Created on: Feb 28, 2019
 *      Author: jnadi
 */

#ifndef FOO1_H_
#define FOO1_H_

#include "foo.h"
#include "bar.h"

#endif /* FOO1_H_ */

/*
 * foo1.c
 *
 *  Created on: Feb 28, 2019
 *      Author: jnadi
 */

#include "foo1.h"

void Camera_Init(){

    Init_Parameters setParams; //create instance
    setParams.size=big;
    Sensor_Init(&setParams);

}

【问题讨论】:

  • 你能发帖minimal reproducible example吗?就像提到的所有文件的最小内容一样,它会重现问题。
  • 我面临着让这段代码正常工作的压力,目前正在重组代码以避免递归库问题。我需要一段时间才能这样做。我曾希望我的描述就足够了
  • “文件 c_C.h 和 a_C.h 位于同一个库中”是什么意思?头文件可能会声明函数,而这些函数定义可能来自特定的库,也许这就是您的意思。但是使用“reside”这个词似乎意味着你误解了头文件和库之间的关系。特别是,我不知道“t_T.h(包括来自 Lib_C 的文件)”是什么意思。您不能包含库中的文件,尽管您可以包含可能来自用于构建库的源树的文件。
  • 请编辑问题并添加来自链接器的完整且未编辑错误消息。我认为您误解了它的含义,因此在错误的地方寻找问题。
  • 你好zwoi。我添加了来自链接器的消息。我真的希望我误解了这个错误。我还添加了头文件,其中包括包含重新定义的“ov5642_1280x960_RAW”符号的文件。

标签: c struct header


【解决方案1】:

头文件的物理位置仅通过包含文件搜索路径影响 C 源代码编译。标头必须位于要搜索的目录之一中,如果有多个具有相同名称的目录,则搜索路径顺序确定使用哪个目录。编译器不知道给定头文件和库之间的关联(如果有),并且它不会影响编译,除非通过搜索路径间接。

你声称

链接器抱怨文件 a_C.h 中的结构被重新定义

(强调添加)只有在“结构”是指结构类型的 object 时才有意义。如果在多个翻译单元中定义了具有外部链接的变量,链接器可能会报错,如果标头包含该变量的定义(而不仅仅是声明),则可能会发生这种情况。

如果问题是结构 type 被重新定义,那么这将由编译器而不是链接器来诊断,并且它往往会与您的结论相矛盾

每个标题都有相应的标题保护

。正确的标头保护正是防止此类重新定义问题的方法。

1) 我是否正确识别了问题?

当然,没有任何特殊性。与不同库关联的标头之间存在双向标头依赖关系反映了糟糕的设计,但它不会固有地导致编译或链接失败。

2) 如果是的话[...]

不适用

3) 我是否应该将标头保护限制在其范围内 各自的图书馆?

没有。标头保护仅在编译阶段相关,而 C 编译对库一无所知。编译器在翻译单个翻译单元的过程中处理的所有头文件都是平等的。事实上,这方面的主要风险是在相反的方向:头后卫的碰撞。


这回答了所提出的问题。至于你的构建问题的真正性质可能是什么,你没有给我们足够的信息来做更多的推测。上面已经传达了我自己的推测。

【讨论】:

  • 我同意您的看法,即库之间存在标头依赖关系是糟糕的设计。我正在重构代码。搜索路径正确 - 翻译单元编译没有问题。我已经仔细检查了头部防护装置,它们是正确的。类型结构仅在一个头文件中声明和定义。链接器抱怨重新定义发生在两个不同的翻译单元中,这似乎是一个标头保护问题。我会按照你的建议去文件的物理位置检查是否有重复的文件名。
  • 请考虑您认为自己没有那种跨库头依赖的可能性。如果再次出现关于重新定义结构 type 的编译器错误,那么可能是您进行了真正的重新定义。类型本身或整个标头可能已被复制,但如果它是整个标头,那么它的包含保护也需要在一个地方或另一个地方进行修改。
  • 结构类型仅在一个带有正确标头保护的头文件中定义和声明。但它确实似乎以某种方式包含了两次标头,这就是为什么我认为笨拙的库间依赖项复制了两次标头,每个库一次(可能如您所建议的那样错误)。我正在移动代码以删除跨库依赖项,看看它是否有所作为(不是一个好的工程方法)。无论结果如何,我都要感谢您的时间和考虑。你当然提出了一些值得我思考的问题。
  • 嗨,John,我添加了错误消息和两个头文件中使用的标头保护以供参考。希望我在头球后卫中搞砸了一些东西
【解决方案2】:

使用上面发布的五个文件并在 foo.h 中注释掉 #includes bar.h,我相信我找到了问题的答案。

简单的答案是,一旦将标头保护包含到另一个文件的标头中,就不会保留标头保护。

当 bar.h 包含在另一个头文件中时,它的头保护被其新主机 (foo.h) 的头保护所取代。因此,当 foo1.h 包含 bar.h 和 foo.h 时,就会出现符号重新定义问题

【讨论】:

  • 你是对的:这是一个链接器错误。因此,正如我所说,它是关于 object 的多个定义。当一个标头包含到另一个标头中时,标头保护不会以某种方式丢失。他们不是。问题是两个不同的翻译单元都从标头中获取同一对象的定义。实际上,头部定义了两个对象:数组regArraytinyArray。标题中存在这些定义是您的根本问题。标头可以声明这些,但它们的定义应该只在任何给定程序的一个翻译单元中。
  • 我并不是要争论,但这不是 I 所说的依赖。我只会称其为有缺陷的标题。标题定义具有外部链接的对象是不合适的,因为未定义的行为会导致将该标题包含在对同一程序有贡献的两个不同翻译单元中。那么外部链接的意义何在?标头通常也不适合用 internal 链接定义对象,但至少不会产生 UB。
【解决方案3】:

标题保护仅防止.h 文件在一个 顶级翻译单元中包含其内容两次或更多次。他们将处理这样的情况,其中两个或多个标头需要包含相同的一组通用定义:

// A.h
struct A { int x, y, z; };

// B.h
#include "A.h"
struct B { struct A aye; float f, g; };

// C.h
#include "A.h"
struct C { struct A aye; long l, m; };

// main.c
#include "B.h"
#include "C.h" // error, redefinition of struct A

但是每个翻译单元都从一个干净的宏环境开始,因此如果您在两个不同的顶级翻译单元中包含一个头文件,则该头文件的声明对每个都可见(一次)。这就是你想要的。 (想想标准库头文件。你不会希望 stdio.h 不在 bar.c 中声明 printf 只是因为在同一个项目中存在 foo.c 还包括 stdio.h。)

现在,您的问题是ov5642_Sensor_Values.h 定义了一个数据对象(不是类型)ov5642_1280x960_RAW,并且此标头包含在两个不同的顶级翻译单元中(.c源文件)。每个翻译单元都会生成一个包含ov5642_1280x960_RAW 定义的目标文件,当您尝试组合它们时,您会从链接器收到多重定义错误。

导致这个问题的bug不是ov5642_Sensor_Values.h的header guards无效。错误是ov5642_Sensor_Values.h 不应该进行任何全局定义。头文件应该只声明东西(有极少数例外,除非你遇到它们,否则你不应该担心)。

要修复该错误,请将ov5642_Sensor_Values.h 更改为声明ov5642_1280x960_RAW 但不定义它,如下所示:

#ifndef ARDUCAM_OV5642_SENSOR_VALUES_H_
#define ARDUCAM_OV5642_SENSOR_VALUES_H_

#include <stdint.h>
#include "sensor_reg.h"

extern const struct sensor_reg ov5642_1280x960_RAW[];

#endif

并创建一个名为 ov5642_Sensor_Values.c 的新文件,其中包含已初始化的定义:

#include "ov5642_Sensor_Values.h"

extern const struct sensor_reg ov5642_1280x960_RAW[] =
{
    {0x3103,0x93},
    {0x3008,0x02},
    {0x3017,0x7f},
    .....
};

并将该文件添加到您的链接中。

【讨论】:

    【解决方案4】:

    谢谢大家,尤其是 John,Zwoi。我面临着最后期限(这是继承的代码),但能够让自己冷静下来,弄清楚约翰在说什么。我将结构定义移动到一个 c 文件中,并在标题中使用了一个外部指针,这类似于 zwoi 的声明。我也为我所做的与 zwoi 的示例相匹配而感到欣慰(非常感谢!)。

    extern const struct sensor_reg * ov5642_1280x960_RAW;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-20
      • 2011-01-06
      • 1970-01-01
      相关资源
      最近更新 更多