在 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++中在頭文件中定義函數或變量會出現的問題。