码迷,mamicode.com
首页 > 其他好文 > 详细

Makefile一个cpp文件依赖于多个header文件的陷阱

时间:2015-01-18 22:38:46      阅读:375      评论:0      收藏:0      [点我收藏+]

标签:

我一直以为,如果一个A.cpp文件中有多少条 #include "xxx.h"指令,在写Makefile的时候A.obj的依赖项除了A.cpp之外,就是A.cpp之内所有的 xxx.h

比如,如果A.cpp中有 #include "A.h" #include "B.h" #include "C.h",那么在Makefile中就有:A.obj: A.cpp A.h B.h C.h

但是

下面的例子是说明了,上面的想法是错误的

先看例子

 

环境:Windows + Microsoft Visual Studio NMAKE.exe\CL.exe\LINK.exe

 

文件组织(加粗字体的是文件夹):

  testproject

    src

    include

    Makefile

src中包含文件:Car.cpp, SportsCar.cpp, Engine.cpp, Gas.cpp, main.cpp

include中包含文件:Car.h, SportsCar.h, Engine.h, Gas.h

 

概要:Car有一个Engine,SportsCar是Car的子类,Car.drive()调用Engine.consumeGas(),Engine.consumeGas()调用Gas.burn(),SportsCar.drive()重写了Car.drive()

 

说明:

  通过下面的代码,在Makefile的第29行可以看到,SportsCar.obj只依赖于SportsCar.cpp和SportsCar.h,因为SportsCar.cpp只有一条#include "SportsCar.h"的指令

  但是,你可以尝试下面的步骤,就会发现问题所在

  1、打开VS2013 开发人员命令提示,切换到,testproject的根目录,执行nmake,生成bin\test.exe

  2、输入bin\test.exe,可以看到输出结果如下

技术分享

  3、删除Car.h的第8、9行,删除Car.cpp的第10行,保存

  4、再次输入nmake,生成新的bin\test.exe,可以看到,Car.obj和main.obj被重新生成了,因为这2者都依赖于Car.h,其中Car.obj还依赖于Car.cpp,并且链接

    也没有问题。输入bin\test.exe可以看到结果如下

技术分享

  5、发现奇怪的地方没?没发现?好吧。

    首先,第3步修改了Car.h之后,其实很显然,按C++的语义来讲,SportsCar.cpp第10行已经是错误的了,Car都没有了Engine,作为Car的子类,SportsCar从哪儿来的Engine?(注意SportsCar.h中并没有定义Engine)。但是由于Makefile中并没有写上SportsCar.obj依赖于Car.h的关系,所以SportsCar.cpp就没有被重新编译,SportsCar.obj也没有被重新生成。这时候SportsCar.obj已经是陈旧的了。

    其次,既然SportsCar.obj已经是陈旧的了,不符合C++的语义了。为什么在第3步之后,还能链接生成bin\test.exe?原因很简单,链接的时候SportsCar.obj对Engine.obj的链接仍然是合法的,因为Engine.consumeGas()仍然存在。另外,SportsCar.obj对Car.obj的链接也是合法的,因为Car的constructor和destructor都没有变,链接的时候,linker主要要检查的就是SportsCar.obj对Car.obj中方法的调用,也就是对Car的constructor和destructor的调用,因为在生成和销毁SportsCar对象的时候会用到这两者,显然,Car的constructor和destructor都存在,所以linker认为这是没有问题的,继而生成了bin\test.exe。

    所以就造成了上面奇怪的运行结果:Car都没有了Engine,作为Car的子类,SportsCar“平白无故”地有了一个Engine(注意SportsCar.h中并没有定义Engine)

  6、结论:如果A.cpp包含了X.h,X.h又包含了Y.h,Y.h又包含了Z.h,那么在写Makefile的时候,A.obj依赖的对象不仅有A.cpp, X.h,而且还有Y.h和Z.h(当然,对于库的头文件就不用写进Makefile了,这里说的头文件都是你自己在开发的时候写的头文件,也就是用#include ""指令包含的头文件。除非你有必要去修改库的头文件,才需要把库的头文件依赖也放进你的Makefile里)

   实际上,一般来讲,假设A.cpp包含了B.h,那么很可能A.cpp中会直接用到B这个类的某些function,比如说,在A中可能有诸如B.xxx()的调用,如果B.h包含了比如说X.h,但是A.cpp中没有代码直接用到了X,从而X.h的修改并不会导致A.cpp中的语义出错(因为A中没有代码直接对X进行使用,那比如说,如果SportsCar.cpp中没有直接使用Engine.consumeGas(),还会出现第5步中的情况么?就不会了!),那么实际上Makefile中也不一定非要让A.obj依赖于X.h。

  所以,上面的结论,准确来讲应该是:如果A.cpp包含了X.h,X.h又包含了Y.h,Y.h又包含了Z.h,并且A中的代码不仅对X进行了直接的使用,而且还对Y, Z进行了直接的使用,那么在写Makefile的时候,A.obj依赖的对象不仅有A.cpp, X.h,而且还有Y.h和Z.h。

  当然,如果你为了以防万一也不嫌麻烦的话,还是按照第6步给出的方法写Makefile吧

代码:

Makefile

 1 # compiler
 2 CC = cl
 3 # linker
 4 LINK = link
 5 # libraries
 6 
 7 # headers
 8 HEADER_PATH = /I include
 9 # options
10 EHSC = /EHsc
11 COMPILATION_ONLY = /c
12 C_OUT = /Fo:
13 L_OUT = /OUT:
14 # compiler & linker debug option, to disable debug, replace ‘/Zi‘ & ‘/DEBUG‘ with empty strings
15 C_DEBUG = /Zi
16 L_DEBUG = /DEBUG
17 # C_DEBUG = 
18 # L_DEBUG = 
19 # targets
20 bin\test.exe: bin obj obj\main.obj obj\Car.obj obj\SportsCar.obj obj\Engine.obj obj\Gas.obj
21     $(LINK) $(L_DEBUG) $(L_OUT)bin\test.exe obj\main.obj obj\Car.obj obj\SportsCar.obj obj\Engine.obj obj\Gas.obj
22 
23 obj\main.obj: src\main.cpp include\Car.h include\SportsCar.h
24     $(CC) $(C_DEBUG) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\main.cpp $(C_OUT)obj\main.obj
25 
26 obj\Car.obj: src\Car.cpp include\Car.h
27     $(CC) $(C_DEBUG) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\Car.cpp $(C_OUT)obj\Car.obj
28     
29 obj\SportsCar.obj: src\SportsCar.cpp include\SportsCar.h
30     $(CC) $(C_DEBUG) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\SportsCar.cpp $(C_OUT)obj\SportsCar.obj    
31     
32 obj\Engine.obj: src\Engine.cpp include\Engine.h include\Gas.h
33     $(CC) $(C_DEBUG) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\Engine.cpp $(C_OUT)obj\Engine.obj
34     
35 obj\Gas.obj: src\Gas.cpp include\Gas.h
36     $(CC) $(C_DEBUG) $(EHSC) $(HEADER_PATH) $(COMPILATION_ONLY) src\Gas.cpp $(C_OUT)obj\Gas.obj
37 
38 # folders
39 
40 obj:
41     mkdir obj
42     
43 bin:
44     mkdir bin
45     
46 # clean
47 # bin, obj folders and pdb files
48 
49 .PHONY: clean
50 clean:
51     -rmdir /s /q bin
52     -rmdir /s /q obj
53     -del *.pdb

 

Car.h

 1 #ifndef CAR_H
 2 #define CAR_H
 3 
 4 #include "Engine.h"
 5 
 6 class Car
 7 {
 8 protected:
 9     Engine engine;
10 public:
11     virtual void drive();
12     virtual ~Car();
13 };
14 
15 #endif // CAR_H

 

Car.cpp

 1 #include "Car.h"
 2 
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 void Car::drive()
 8 {
 9     cout << "Car drive" << endl;
10     engine.consumeGas();
11 }
12 
13 Car::~Car()
14 {
15     // do nothing
16 }

 

SportsCar.h

 1 #ifndef SPORTSCAR_H
 2 #define SPORTSCAR_H
 3 
 4 #include "Car.h"
 5 
 6 class SportsCar : public Car
 7 {
 8 
 9 public:
10     void drive();
11 };
12 
13 #endif // SPORTSCAR_H

 

SportsCar.cpp

 1 #include "SportsCar.h"
 2 
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 void SportsCar::drive()
 8 {
 9     cout << "SportsCar drive" << endl;
10     engine.consumeGas();
11 }

 

Engine.h

 1 #ifndef ENGINE_H
 2 #define ENGINE_H
 3 
 4 class Engine
 5 {
 6 
 7 public:
 8     void consumeGas();
 9 };
10 
11 #endif // ENGINE_H

 

Engine.cpp

 1 #include "Engine.h"
 2 #include "Gas.h"
 3 
 4 #include <iostream>
 5 
 6 using namespace std;
 7 
 8 void Engine::consumeGas()
 9 {
10     cout << "Engine consuming gas" << endl;
11     Gas g;
12     g.burn();
13 }

 

Gas.h

 1 #ifndef GAS_H
 2 #define GAS_H
 3 
 4 class Gas
 5 {
 6 
 7 public:
 8     void burn();
 9 };
10 
11 #endif // GAS_H

 

Gas.cpp

 1 #include "Gas.h"
 2 
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 void Gas::burn()
 8 {
 9     cout << "Gas burning" << endl;
10 }

 

Makefile一个cpp文件依赖于多个header文件的陷阱

标签:

原文地址:http://www.cnblogs.com/qrlozte/p/4232290.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!