我继承了一些代码*,它在头文件(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);
}
头文件的物理位置仅通过包含文件搜索路径影响C源编译。标头必须位于其中一个搜索目录中,如果有多个具有相同名称,则搜索路径顺序将确定使用哪个目录。编译器不知道给定头和库之间的关联(如果有的话),它不会影响编译,除非通过搜索路径间接。
你的主张
链接器抱怨文件a_C.h中的结构被重新定义
(强调添加)只有当“结构”表示结构类型的对象时才有意义。如果在多个转换单元中定义了具有外部链接的变量,则链接器可能会抱怨,如果标头包含该变量的定义(而不仅仅是声明),则可能会发生这种情况。
如果相反的问题是结构类型被重新定义,那么这将由编译器诊断,而不是链接器,并且它倾向于与您的结论相矛盾
每个标题都有适当的标题保护
。正确的标题保护正是防止这种类型重新定义问题的原因。
1)我是否正确识别了问题?
没有任何特异性,当然。在与不同库关联的头之间存在两个方向上的头依赖性反映了糟糕的设计,但它本身不会导致编译或链接失败。
2)如果是这样[...]
N / A
3)我是否应该将标题保护限制在各自图书馆的范围内?
没有。标题保护仅在编译阶段相关,而C编译对库一无所知。编译器在翻译单个翻译单元的过程中处理的所有头文件都是平等的。事实上,这个领域的主要风险是相反的方向:头部防护装置的碰撞。
这回答了提出的问题。至于构建问题的真实性质,你没有给我们足够的信息来做更多的推测。我上面已经传达了自己的推测。
使用上面发布的五个文件并在foo.h中注释#includes bar.h,我相信我找到了问题的答案。
简单的答案是,一旦将标题保护包含在另一个文件的标题中,它就不会被保留。
当bar.h包含在另一个头文件中时,其标题保护将被其新主机(foo.h)的标头保护所取代。因此,当foo1.h包含bar.h和foo.h时,会出现符号重新定义问题
标头防护仅阻止.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
不在printf
中声明bar.c
,因为在同一个项目中也存在foo.c
,其中包括stdio.h
。)
现在,您的问题是ov5642_Sensor_Values.h
定义了一个数据对象(不是类型),ov5642_1280x960_RAW
,并且此标头包含在两个不同的顶级翻译单元(.c
源文件)中。每个转换单元都会生成一个包含ov5642_1280x960_RAW
定义的目标文件,当您尝试将它们组合在一起时,会从链接器中获得多重定义错误。
造成这个问题的错误并不是ov5642_Sensor_Values.h
的头部防守无效。问题是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},
.....
};
并将该文件添加到您的链接。
谢谢大家特别是John,Zwoi。我是在最后期限(这是继承的代码),但能够让自己冷静下来,弄清楚约翰在说什么。我将struct定义移动到一个c文件中,并在头文件中使用了一个类似于zwoi声明的extern指针。我也很放心,我所做的与zwoi的例子相符(非常感谢你!)。
extern const struct sensor_reg * ov5642_1280x960_RAW;