cl.exe和link.exe分别是visual studio 中的编译器和链接器
配置属性中的【c/c++】(设置编译的一些选项) 和 【链接器】选项页中,最后的那个命令行汇总了所有生效的设置,就是最终执行的命令行
配置属性中的VC++ 目录用于设置各个路径,相当于设置环境变量PATH(搜可执行文件的路径):
INCLUDE(搜include中文件的路径)
LIBPATH(搜使用using 引入文件的路径)
LIB(搜库文件的路径)
编译过程:语法错误
链接过程:结构错误
运行过程:逻辑错误
目录
编译:
1.头文件路径设置
2.优化选项设置
3.汇编输出文件设置
4.obj文件查看
1.使用/summary选项,或者不输入任何选项
2.使用/headers 选项
3.使用/section:段名 查看具体的段的内容
4.使用/symbols 查看符号表
5.运行库设置
链接:
1.一些链接错误
2.附加依赖库
3.符号解析过程
可执行文件
一般一个大的项目,修改完一个cpp以后,一般都先单独编译一下这个cpp,免得到整个工程生成的时候发现这个cpp的一些编译错误。把光标停留在某个cpp的页面中,然后【生成】--【编译】,或直接ctrl+F7
编译所做的事情:
对一个cpp进行预处理,将头文件加载进来,并且将各种#define信息代入,生成一个独立的编译单元,然后进行编译生成obj文件,一个cpp对应一个obj文件。
编译过程,先生成汇编语言,再生成机器语言(即汇编指令对应的机器码,cpu认识的指令)
除了cpp定义的include,还有属性中可以配置的附加包含目录,以及VC++目录中的包含目录。
ps:【VC++目录中的包含目录】和【C/C++常规中的附加包含目录】的区别:
包含目录:修改了系统的include宏的值,是全局的;
附加包含目录:用于当前项目,对其他项目没有影响。————里面填写的相对路径就是指的是相对于项目路径的。如果有提示说找不到头文件的,应该就是这里配错了,看看是不是单词拼错了,还是路径层级弄错了,还是压根这里就没有配置。
(同理库目录和附加库目录的区别)
在不同的配置中,release 和 debug,默认的优化选项也不一样。
优化选项不一样,最后生成的机器码也不一样。
根据需要可以进行自行配置
选择启用优化的话,针对一些没有用的代码,就会被优化掉,也就是目标文件中就不会有对应的机器码了。这里有介绍过应用。
选择汇编程序输出,默认选择的是 无列表【C/C++-》输出文件-》汇编输出程序】
其他值的含义:
/FA 仅输出汇编到文件, 文件默认扩展名是 .asm。
/FAc 输出汇编和相应的机器码到文件,文件默认扩展名是 .cod。
/FAs 输出汇编和相应的源代码到文件,文件默认扩展名是 .asm。
/FAcs 输出汇编、机器码、源代码到文件,文件默认扩展名是 .cod。
一般如要选的话,就选择最后一个FAcs,可以通过这个辅助查找崩溃的具体行。具体可以查看https://blog.csdn.net/wind19/article/details/40614745
可以使用dumpbin.exe 查看obj文件
dumpbin是在Windows平台下用于显示COFF格式文件信息的一个命令行工具。你可以使用DUMPBIN去显示COFF格式的文件信息,比如像vc编译器生成的目标文件(obj),可执行文件(exe)和动态链接库(DLLs)等。
显示:每个段的基本信息(大小+段名)。
这里.obj 是 COFF OBJECT
另外还有:
.lib 是 LIBRARY
.dll 是 DLL
.exe 是 EXECUTABLE IMAGE
所有节(SECTION)的描述结构,即节头
先显示一个汇总,
再分别显示各个section, SECTION HEADER #1 到 SECTION HEADER #B,11个
最后再显示上面1中显示的summary
/summary 中显示出来的段名
bss段存放的是未初始化的全局变量或(全局和局部的)静态变量。
data段存放的是初始化的全局变量或(全局和局部的)静态变量。
显示各个段的一些信息
:预留空间,cpp中分别定义了一个初始化的和未初始化的全局变量int,分别在data段和bss段,均占用4个字节。
:读写权限,bss和data段都是可读可写的段
:字节对齐align,影响预留空间 size of raw data,如果int int char 就是9,如果是int char int 就是12。
关于const变量属于只读。
const全局变量存储在只读数据段,编译期最初将其保存在符号表中。当运行时第一次使用时为其分配内存,在程序结束时释放。
const局部变量存储在栈中,栈区也是运行时才有的。当运行时第一次使用时为其分配内存,代码块结束时释放。
vs
只有存在于data段全局变量和静态变量,在编译期就分配空间。(bss段编译器不分配空间,的file pointer to raw data 是 空,bss段只是有个占位符,运行的时候系统自动都初始化为0)
使用/symbols 查看符号表,符号就是通常指定义出的函数,全局变量。
源文件的全局符号 (global symbol) 分成强 (strong) 和弱 (weak) 两类传给汇编器
汇编器则将强弱信息编码并保存在目标文件的符号表中
编译器认为函数与初始化了的全局变量都是强符号,而未初始化的全局变量则成了弱符号
设置在【配置属性-》C/C++->代码生成-》运行库】
C和C++运行时库 由编译器分别实现,实现的内容是C标准和C++标准定义了一系列常用的函数(标准只是定义函数原型,编译器来实现),实现的库分别称为CRT库和C++类库。
比如 main() exit() 就是CRT 提供c程序运行的基本函数。
VC2010使用的CRT库的DLL版本在MSVCR100.DLL中实现,对应调试版本为MSVCR100D.DLL。
VC2010使用的CRT库的LIB版本在libcmt.lib中实现,对应的调试版为libcmtd.lib
VC2010使用的C++类库的DLL版本在MSVCP100.DLL中实现,对应调试版本为MSVCP100D.DLL。
VC2010使用的C++类库的LIB版本在libcpmt.lib中实现,对应的调试版为libcpmtd.lib
由于C++对C的兼容性,C++标准库包括了C标准库,除此之外还包括IO流和标准模板库STL
(用Dependency Walker打开MSVCRT100.DLL,我们可以在其中找到我们经常使用使用的C函数,如printf ,getchar,malloc等。打开MSVCP100.DLL,也可以找到这些C函数。)
【配置属性-》C/C++->代码生成-》运行库】中的不同选项就是选择不同的DLL版本还是LIB版本,调试版本还是发布版本:
MT选项:LIB版的C和C++运行库。在链接时就会在将C和C++运行时库集成到程序中成为程序中的代码,程序体积会变大。
MTd选项:LIB的调试版。
MD选项:DLL版的C和C++运行库,这样在程序运行时会动态的加载对应的DLL,程序体积会减小,缺点是程序在系统没有对应DLL时程序无法运行。
MDd选项:DLL的调试版。
代码生成-》较小类型检查 从/RTCc 改成 否
因为没有包含 unique_ptr 所在的头文件 #include
看看是不是真的无法打开 lllll 没有找到这个源文件
将 C++->预编译头 【创建/使用编译头】 改为“不使用编译头”
在jenkins上使用 msbuild命令行编译工程的时候 提示找不到某头文件,但打开vs工程编译没有问题。
原因是在工程的配置中有添加头文件目录,但是 msbuild 不认识$(SolutionDir)这个宏,所以提示找不到头文件
解决加上一个参数,定义此宏:
/p:SolutionDir=path
https://stackoverflow.com/questions/15053915/how-to-get-rid-of-solutiondir-when-building-visual-studio-project-from-outs
解决方法二:使用硬链接,放到系统的include下
cd /d C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include
mklink /D 取个文件夹名 头文件真正所在的路径
在jenkins上编译工程的时候,同一个sln下,有的用msbuild 有的用devenv。
set MSBuild=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe
set devenv="C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe"
set Src=%JENKINS_HOME%\jobs\Checkout\workspace\XXX\Src
set PubSrc=%JENKINS_HOME%\jobs\Checkout\workspace\YYY\Project
set Config=/t:Rebuild /p:Configuration=Release;Platform=Win32
set DevConfig=/Rebuild "Release|Win32"
cd /d %Src%\UnrealBuildTool\
::Msbuild
%MSBuild% UnrealBuildTool.csproj %Config%;OutputPath=..\..\Intermediate\UnrealBuildTool\Release\
%MSBuild% %PubSrc%\Common.vcxproj %Config%
::devenv
%devenv% %Src%\GamePhysLibDev.vcxproj %DevConfig%
链接器 (linker) 将一个个的目标文件 ( 还会有若干程序库 ) 链接在一起生成一个完整的可执行文件。
我们经常在遇到一些连接错误
1.LNK1104 ,这里遇到过一次。
2.LNK2005 ,重定义错误,可能跟不同项目使用了不同的运行库有关系(上述的运行库设置)
3.LNK1169
在链接的时候有三个规则:
规则 1: 不允许强符号被多次定义 ( 即不同的目标文件中不能有同名的强符号 ) ;
规则 2: 如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么选择强符号;
规则 3: 如果一个符号在所有目标文件中都是弱符号,那么选择其中任意一个;
或者 #pragma comment(lib, “**.lib”)
在符号解析 (symbol resolution) 阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序(配置属性-链接器-命令行)从左至右把他们放入输入文件列表中,然后依次扫描它们,在此期间它要维护若干个集合 :
(1) 集合 E 是将被合并到一起组成可执行文件的所有目标文件集合;
(2) 集合 U 是未解析符号 (unresolvedsymbols ,比如已经被引用但是还未被定义的符号 ) 的集合;
(3) 集合 D 是所有之前已被加入到 E 的目标文件定义的符号集合。
一开始,这三个集合都是空的。 链接器的工作过程:
(1) 对命令行中的每一个输入文件 f ,链接器确定它是目标文件还是库文件,如果它是目标文件,就把 f 加入到 E ,并把 f 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中,然后处理下一个输入文件。
(2) 如果 f 是一个库文件,链接器会尝试把 U 中的所有未解析符号与 f 中各目标模块定义的符号进行匹配。如果某个目标模块 m 定义了一个 U 中的未解析符号,那么就把 m 加入到 E 中,并把 m 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中。不断地对 f 中的所有目标模块重复这个过程直至到达一个不动点 (fixed point) ,此时 U 和 D 不再变化。而那些未加入到 E 中的f 里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。
(3) 如果处理过程中往 D 加入一个已存在的符号,或者当扫描完所有输入文件时 U 非空,链接器报错并停止动作。否则,它把 E 中的所有目标文件合并在一起生成可执行文件。
编译链接生成的可执行文件
1.可以在操作系统中直接运行
2.包含:1)机器码和数据 2)相关的描述信息,比如程序有多大,要占多少内存空间
操作系统根据描述信息,讲可执行文件中的机器码和数据装入内存,并进行相关的初始化操作,比如将设置cs:ip指向第一条要执行的指令。
是谁将可执行文件中程序装载进入内存并使它运行
DOS系统中是通过command.com 命令解释器将可执行文件装入内存中。
command设置cpu的cs:ip指向程序的第一条命令,将cpu的控制权交给他,然后程序退出以后,控制器交回给command
debug可以将程序加载入内存,但是不放弃对cpu的控制,这样就可以利用debug的相关命令来单步执行程序。