今天写程序,碰到了一个头疼的问题,一直给我讲multiply defination of。。。很烦,我检查了好多遍,我明明有加#ifndef...
我定义的全局变量还是有重复定义的报错,百度了之后终于清楚了,大家不妨也看看,以后注意!!!
其一,用条件编译(头文件卫士)防止头文件重复包含
假设源文件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就变成无名结构体变量而不是原来的自定义类型,放在头文件里也会出错。
三.想省事一点就在全局变量前面加一个static,这就不会出错了!