在 C/C++ 中,我们一般是把代码分为头文件(.h)和源文件(.c/.cpp)分开保存,
这样可以方便代码管理和阅读。
但是如果把函数或变量的定义也放在头文件中会出现什么问题呢?
一般在小的工程中,这种问题不太明显,也比较容易解决,
但是在复杂点的项目中,可能就会出现重定义等错误,具体请看下面的一个例子:
(为了看清楚错误所在的位置,我们把编译和链接分开执行)
现在新建了一个项目,里面包含了 6 个文件,
分别是: A.h
、B1.h
、B1.cpp
、 B2.h
、 B2.cpp
和 main.cpp
,
这六个文件的内容如下:
A.h
#ifndef __A_H__
#define __A_H__
int var_A = 0;
int func_A()
{
var_A += 1;
return var_A;
}
#endif
B1.h
#ifndef __B1_H__
#define __B1_H__
#include "A.h"
int func_B1();
#endif
B1.cpp
#include "B1.h"
int func_B1()
{
return func_A() + 12;
}
B2.h
#ifndef __B2_H__
#define __B2_H__
#include "A.h"
int func_B2();
#endif
B2.cpp
#include "B2.h"
int func_B2()
{
return func_A() + 21;
}
main.cpp
#include "B1.h"
#include "B2.h"
#include <stdio.h>
int main()
{
int a = 0;
a = func_B1();
a += func_B2();
printf("a = %d\n", a);
return 0;
}
还有一个 Makefile 文件(或者直接在 Visual Studio 中编译也可以):
CC = gcc
CFLAGS = -Wall -O3
LFLAGS = -O3
# Files
BIN = include_test
OBJS = B1.obj B2.obj main.obj
SRCS = B1.cpp B2.cpp main.cpp
$(BIN) : $(OBJS)
$(CC) $(LFLAGS) -o $(BIN) $(OBJS)
$(OBJS) : $(SRCS)
$(CC) $(CFLAGS) -c B1.cpp -o B1.obj
$(CC) $(CFLAGS) -c B2.cpp -o B2.obj
$(CC) $(CFLAGS) -c main.cpp -o main.obj
clean:
rm $(OBJS) $(BIN)
也就是说,在A.h
这个头文件中定义了一个变量 var_A
和一个函数 func_A
,
然后在 B1.h
和 B2.h
中分别都包含了 A.h
这个头文件,
最后在 main.cpp
中分别又 include
了B1.h
和B2.h
两个头文件,
这样就会导致在链接(link)的时候出现重定义的错误,
如下所示:
D:\VS2015Project\INCLUDE_TEST\INCLUDE_TEST>make
gcc -Wall -O3 -c B1.cpp -o B1.obj
gcc -Wall -O3 -c B2.cpp -o B2.obj
gcc -Wall -O3 -c main.cpp -o main.obj
gcc -O3 -o include_test B1.obj B2.obj main.obj
B2.obj:B2.cpp:(.text+0x0): multiple definition of `func_A()'
B1.obj:B1.cpp:(.text+0x0): first defined here
B2.obj:B2.cpp:(.bss+0x0): multiple definition of `var_A'
B1.obj:B1.cpp:(.bss+0x0): first defined here
main.obj:main.cpp:(.text+0x0): multiple definition of `func_A()'
B1.obj:B1.cpp:(.text+0x0): first defined here
main.obj:main.cpp:(.bss+0x0): multiple definition of `var_A'
B1.obj:B1.cpp:(.bss+0x0): first defined here
c:/myprogramfiles/mingw32/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../../i686-w64-mingw32/bin/ld.exe: main.obj: bad reloc address 0xb in section `.text.startup'
collect2.exe: error: ld returned 1 exit status
makefile:13: recipe for target `include_test' failed
make: *** [include_test] Error 1
原因如下:
因为在编译阶段,B1.cpp
和 B2.cpp
都是单独编译,各自生成一个 obj
文件,
但是这两个 obj
文件中都会包含 var_A
和func_A
的定义,
我们可以使用 objdump
工具分别看一下 B1.obj
和B2.obj
这两个文件,
(以查看func_A
函数为例):
B1.obj
D:\VS2015Project\INCLUDE_TEST\INCLUDE_TEST>objdump -d B1.obj
B1.obj: file format pe-i386
Disassembly of section .text:
00000000 <__Z6func_Av>: # 这就是 func_A 在 B1.obj 中的定义
0: a1 00 00 00 00 mov 0x0,%eax
5: 83 c0 01 add $0x1,%eax
8: a3 00 00 00 00 mov %eax,0x0
d: c3 ret
e: 66 90 xchg %ax,%ax
00000010 <__Z7func_B1v>:
10: a1 00 00 00 00 mov 0x0,%eax
15: 8d 50 01 lea 0x1(%eax),%edx
18: 83 c0 0d add $0xd,%eax
1b: 89 15 00 00 00 00 mov %edx,0x0
21: c3 ret
22: 90 nop
........ # 省略后面无用部分
B2.obj
D:\VS2015Project\INCLUDE_TEST\INCLUDE_TEST>objdump -d B2.obj
B2.obj: file format pe-i386
Disassembly of section .text:
00000000 <__Z6func_Av>: # 这就是 func_A 在 B2.obj 中的定义
0: a1 00 00 00 00 mov 0x0,%eax
5: 83 c0 01 add $0x1,%eax
8: a3 00 00 00 00 mov %eax,0x0
d: c3 ret
e: 66 90 xchg %ax,%ax
00000010 <__Z7func_B2v>:
10: a1 00 00 00 00 mov 0x0,%eax
15: 8d 50 01 lea 0x1(%eax),%edx
18: 83 c0 16 add $0x16,%eax
1b: 89 15 00 00 00 00 mov %edx,0x0
21: c3 ret
22: 90 nop
........ # 省略后面无用部分
(注:以上反汇编也可以使用 VS自带的工具查看:
dumpbin /DISASM B1.obj
)
从上面可以看到,B1.obj
和 B2.obj
文件中都包含了两个相同的定义(func_A
),
因此,在链接的时候,链接器就会报错了。
正确的做法应该是把定义放到 .cpp
文件中,然后只在头文件中声明,
在这里,我们可以给 A 添加一个源文件(A.cpp
),
然后把定义部分放入到 A.cpp
中,声明部分分为两种情况:
- 对于函数来说,在
A.h
中添加声明即可, - 对于变量来说,如果只是在
A.h
声明,链接的时候还是会报错,因此,需要给变量添加extern
关键字,
修改过后的A.h
、A.cpp
和Makefile
如下(其它文件不变):
A.h
#ifndef __A_H__
#define __A_H__
extern int var_A;
int func_A();
#endif
A.cpp
#include "A.h"
int var_A = 0;
int func_A()
{
var_A += 1;
return var_A;
}
Makefile
CC = gcc
CFLAGS = -Wall -O3
LFLAGS = -O3
# Files
BIN = include_test
OBJS = A.obj B1.obj B2.obj main.obj
SRCS = A.cpp B1.cpp B2.cpp main.cpp
$(BIN) : $(OBJS)
$(CC) $(LFLAGS) -o $(BIN) $(OBJS)
$(OBJS) : $(SRCS)
$(CC) $(CFLAGS) -c A.cpp -o A.obj
$(CC) $(CFLAGS) -c B1.cpp -o B1.obj
$(CC) $(CFLAGS) -c B2.cpp -o B2.obj
$(CC) $(CFLAGS) -c main.cpp -o main.obj
clean:
rm $(OBJS) $(BIN)
然后就可以正常编译链接了,如下所示:
D:\VS2015Project\INCLUDE_TEST\INCLUDE_TEST>make
gcc -Wall -O3 -c A.cpp -o A.obj
gcc -Wall -O3 -c B1.cpp -o B1.obj
gcc -Wall -O3 -c B2.cpp -o B2.obj
gcc -Wall -O3 -c main.cpp -o main.obj
gcc -O3 -o include_test A.obj B1.obj B2.obj main.obj
D:\VS2015Project\INCLUDE_TEST\INCLUDE_TEST>include_test.exe
a = 36
D:\VS2015Project\INCLUDE_TEST\INCLUDE_TEST>
以上就是 解决 C/C++中在头文件中定义函数或变量会出现的问题。