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文件中就可以去掉和其他系统相冲突的定义。
- 09-29如何通过wrap malloc定位C/C++程序的内存泄漏
- 02-25打车软件大战升级,补贴还能维持多久?
- 12-23BMP文件右旋90度[c语言]
- 12-23寻找直方图中面积最大的矩形(C语言版)
- 12-23[ndk,2]ndk开发案例和错误处理
- 12-23[ndk,1]ndk开发,C语言入门讲解
- 12-23C语言连续存储实现队列机制
- 12-23Objective-c 数据类型
- 01-11全球最受赞誉公司揭晓:苹果连续九年第一
- 12-09罗伯特·莫里斯:让黑客真正变黑
- 12-09谁闯入了中国网络?揭秘美国绝密黑客小组TA
- 12-09警示:iOS6 惊现“闪退”BUG
- 11-28Bossjob宣布上线AI翻译功能
- 11-28腾讯应用宝电脑版推小宝AI助手 部分功能已
- 11-28周鸿祎亲自上阵演短剧,将于发布会上播出
- 11-28机构:2024第三季度全球NAND闪存产业营收增
- 11-18LG新能源宣布与Bear Robotics达成合作,成为