标签:
我一直以为,如果一个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