C/C++中在頭文件中定義函數或變量會出現的問題

在 C/C++ 中,我們一般是把代碼分爲頭文件(.h)和源文件(.c/.cpp)分開保存,
這樣可以方便代碼管理和閱讀。
但是如果把函數或變量的定義也放在頭文件中會出現什麼問題呢?
一般在小的工程中,這種問題不太明顯,也比較容易解決,
但是在複雜點的項目中,可能就會出現重定義等錯誤,具體請看下面的一個例子:

(爲了看清楚錯誤所在的位置,我們把編譯和鏈接分開執行)

現在新建了一個項目,裏面包含了 6 個文件,
分別是: A.hB1.hB1.cppB2.hB2.cppmain.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.hB2.h中分別都包含了 A.h這個頭文件,
最後在 main.cpp中分別又 includeB1.hB2.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.cppB2.cpp都是單獨編譯,各自生成一個 obj文件,
但是這兩個 obj文件中都會包含 var_Afunc_A的定義,
我們可以使用 objdump工具分別看一下 B1.objB2.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.objB2.obj文件中都包含了兩個相同的定義(func_A),
因此,在鏈接的時候,鏈接器就會報錯了。

正確的做法應該是把定義放到 .cpp文件中,然後只在頭文件中聲明,
在這裏,我們可以給 A 添加一個源文件(A.cpp),
然後把定義部分放入到 A.cpp中,聲明部分分爲兩種情況:

  • 對於函數來說,在 A.h中添加聲明即可,
  • 對於變量來說,如果只是在A.h聲明,鏈接的時候還是會報錯,因此,需要給變量添加 extern關鍵字,
    修改過後的 A.hA.cppMakefile如下(其它文件不變):
    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++中在頭文件中定義函數或變量會出現的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章