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++中在头文件中定义函数或变量会出现的问题。

    原文作者:eUMe
    原文地址: https://blog.csdn.net/u012332816/article/details/86535333
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞