C潜规则篇之防止重定义

C程序编译时常出现类似xxx redefinition错误,除了模块间的命名冲突(命名污染及static),问题多数与头文件管理有关。大型C工程的头文件管理很麻烦:C源文件往往包含很多头文件,头文件又包含其他头文件,形成复杂的嵌套包含;C没有严格限定源文件和头文件的功能边界,二者都可以包含全局变量和函数等实体定义。这都可能导致类型或实体定义被重复包含和展开,使编译器抛出重定义错误。

解决重定义问题分三部分,多数人只知其一而不知其二和其三:

其一,用条件编译(头文件卫士)防止头文件重复包含

假设源文件test.c中包含a.h和b.h两个头文件,而a.h和b.h里又都包含另一个头文件x.h(很常见),那么x.h就会被test.c两次include,如果x.h里定义了某结构体,如:

typedef struct

{

……

}TEST

预处理(见C编译过程)后,test.c里包含两个struct TEST定义,编译器就会报重定义错误。一个巧妙办法是套用下面头文件模板(俗称头文件卫士):

#ifndef _HDRNAME_H //_HDRNAME_H按头文件的文件名取名,防止同名冲突

#define _HDRNAME_H

…… (content of header file)

#endif

当头文件第一次被包含,_HDRNAME_H还未define,#ifndef条件满足,预处理器进入#ifndef和#endif之间,_HDRNAME_H被正式define,头文件内容也得到处理。当再次被包含,由于_HDRNAME_H已定义,开头的#ifndef不再满足,头文件内容被直接忽略。这样防止因头文件重复包含引起的类型重定义错误。这种做法基本算是C的江湖标准了。

其二,在C源文件里定义全局变量与函数,不要在头文件里定义

#ifndef能防止头文件重复包含导致的编译阶段类型重定义错误,却无法防止头文件中的全局变量和函数定义导致的链接阶段实体重定义错误。例如:

/************main.c************/

#include "test.h"

void main()

{

test1();

test2();

}

/********** test.h**********/

#ifndef _TEST_H_

#define _TEST_H_

char str1[] = "char1";

char str2[] = "char2";

#endif

/*********test1.c***********/

#include "test.h"

extern char str1[];

void test1()

{

printf(str1);

}

/*********test2.c************/

#include "test.h"

extern char str2[];

void test2()

{

printf(str2);

}

上面情形,有些编译器报warn,有些可能出现str1和str2重定义error,概念不清的人可能会问:test.h已用#ifndef防止重包含,为什么还有重定义?

这其实是另一个问题,错误根源在于test.h里包含变量/函数等占用内存的实体元素,而不仅仅是define/struct/union等虚类型。虽然用#ifndef防止test.h重复包含,但注意test1.c和test2.c中都包含test.h,预处理器会把test.h分别附到两个源文件开头,相当于在test1.c和test2.c中重复定义了str1,str2两个全局变量。编译完开始link时,linker会发现test1.obj和test2.obj中都有str1,str2两个符号,于是报错,这跟C命名冲突是同一情况。

解决办法是在.c文件中定义全局变量,然后建一个包含所有全局变量extern声明的头文件,其他所有使用这些变量的.c文件中都要包含这个头文件。如下:

/*****main.c*****/

#include "test.h"

char str1[] = "char1";

char str2[] = "char2";

void main()

{

test1();

test2();

}

/***** test.h*****/

#ifndef _TEST_H_

#define _TEST_H_

extern char str1[];

extern char str2[];

#endif

/*****test1.c*****/

#include "test.h"

void test1() { printf(str1); }

/*****test2.c*****/

#include "test.h"

void test2() { printf(str2); }

在头文件中定义函数,错误现象和原因类似。因此头文件中可以包含类型定义和实体声明,不应该包含实体定义。另外,有时遗漏typedef也会导致类似重定义问题:

typedef struct{

….

}TEST_S;

如果遗漏struct前的typedef,TEST_S就变成无名结构体变量而不是原来的自定义类型,放在头文件里也会出错。

其三,用wrapper合理使用作用域

有时源文件要同时包含两个有同名定义的系统或SDK头文件,如同时包含的两个第三方库的API里有同名的自定义类型,也会导致错误。因为一般不方便修改第三方SDK头文件,为解决冲突,可考虑对其中一个库用wrapper方式封装。也就是程序员自己在一个单独.c文件中封装一套全新API,这套API直接调用封装对象lib里的函数并一一对应。这样原lib对应的.h只在wrapper.c文件里包含,而对外API的新.h文件中就可以去掉和其他系统相冲突的定义。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值