原文:C++ 的 C 编译和链接方式 (VC)
作者:Breaker <breaker.zy_AT_gmail>
C++ 与 C 的编译方式
所有的 C 程序都是 C++ 程序,而所有的 C++ 编译器都是 C 编译器(几乎所有),兼容 C99 标准 wiki:
C99。一些编译器以 C/C++ Compiler 命名,如 VC: cl (C/C++ Optimizing Compiler)、ICC (Intel C/C++ Compilers)
开发环境:VC 2005
以 VC 为例,它根据源文件的扩展名来选择 C 或 C++ 的编译方式,并且有一组编译选项显式指定以 C 还是 C++ 的方式编译,规则如下:
1. .c 源文件默认以 C 方式编译,这时拒绝 C++ 的语法,如 class
2. .cpp/.cxx 源文件默认以 C++ 方式编译
3. 使用 /Tc, /Tp, /TC, /TP 编译选项显式指定源文件的 C 或 C++ 编译方式:
- /Tc file.ext: 指定 file.ext 以 C 方式编译
- /Tp file.ext: 指定 file.ext 以 C++ 方式编译
- /TC: 指定传给 cl 的所有源文件都以 C 方式编译,是全局选项
- /TP: 指定传给 cl 的所有源文件都以 C++ 方式编译
规则的覆盖顺序为:Tc/Tp => TC/TP => 根据扩展名 .c/cpp/.cxx 默认判断
参考 MSDN:
编译示例说明
工程 TestProj 含以下源文件:
testproj.c: 程序入口和主要逻辑
tool.h: 工具例程 void tool() 的声明
tool.c: 工具例程 void tool() 的实现
ctool.h: 工具例程 void ctool() 的声明
ctool.c: 工具例程 void ctool() 的实现
include 预处理、编译单元和对象文件对应如下:
编译单元:translation unit,上面用 [] 表示,一个 .c/.cpp 源文件预处理之后的结果。编译器针对编译单元进行编译,一个编译单元生成一个 obj 对象文件
分别编译:separate compilation,类似 TestProj 的这种源文件组织方式,是一种实际中常用的减小编译单元依赖、加快重编译过程的源文件组织方式
编译示例 1
编译命令如下:
解释:
- /TP: 覆盖默认的扩展名自动判断方式,将 testproj.c、tool.sub 作为 C++ 源文件编译
- /Tc ctool.c: 覆盖 /TP 的作用,将 ctool.c 作为 C 源文件编译
意义:
Q1: 明显是 C 语言程序 testproj.c,为何要以 C++ 方式编译?
A1: 利用 C++ 的严格类型规则检查。C++ 比 C 拥有更严格的类型规则,以 C++ 的方式编译,如果程序中有类型安全 (type safety) 的 BUG,编译会报错或告警。案例:用 DDK 开发 Windows 驱动时,均是 C 程序,但通常以 .cpp 命名源文件,或者使用 /TP 编译选项
Q2: 在全部用 C++ 方式编译的情况下,为何 ctool.c 又用 /Tc 指明以 C 方式编译?
A2: 这种情况应用在:需要用 C 的约定进行链接 (C Linkage),例如 TestProj 生成的可执行文件是需要导出 C 链接约定的 ctool() 函数的动态链接库 (DLL)
Q3: 为何一定非要以 C 链接约定,而不以 C++ 链接约定导出函数呢?
A3: 这很大程度上属于设计、人为规定和版本兼容问题,两种情况:(1). 以 C 链接约定导出函数是在很早的时候由 DLL 用户和 DLL 提供者共同商榷而定的(或是 DLL 提供者单方面规定的),由于长期的使用和影响,二进制接口不能随便更改 (2). DLL 用户程序用 C 语言开发,如果 DLL 导出 C++ 链接约定的函数,不便使用
由于 Q2、Q3 原因,假定 TestProj 是 DLL 工程,编译命令修改如下:
/LD: 传递给链接器 /DLL 链接选项,以生成 DLL,并以 .dll 为扩展名,即具有 /link /DLL /OUT:testproj.dll 的作用
而 ctool.h 大致如下:
问题:如果 testproj.c 中调用了 ctool(),上面的编译命令无法成功,在链接阶段报错
原因:Q2 中说明在动态链接中需要保持一致的链接约定,调用者和被调者(实现方)需要用一样的 C Linkage 或 C++ Linkage 才行。其实静态链接也同样需要遵守这个规则,main.obj 可以调用 tool.obj 中的 tool() 函数,是因为它们都使用 C++ Linkage,链接时 main.obj 和 tool.obj 对 tool() 的修饰名都是 ?tool@@YAXXZ。而 ctool.obj 是以 C 方式编译的,对于 ctool() 函数,main.obj 寻找的是 C++
修饰名的 ?ctool@@YAXXZ,但 ctool.obj 中只有 C 修饰名的 _tool,两者自然无法链接
调用和链接约定
三个概念:
-
调用约定 (Calling Convention):用来规定函数调用时,参数入栈顺序、谁负责退栈、修饰名等规则的约定,如 __cdecl、__stdcall、__thiscall,详细说明见 MSDN:Calling Conventions
-
修饰名 (Decorated Name):源程序中函数的原始名经过编译后,在二进制模块内部转换为另一个名字,称为修饰名,二进制模块间的此函数的引用、查找等,都以修饰名进行
为什么二进制模块内部需要修饰名?(修饰名的作用)
1. 不同调用约定的函数的修饰名是不一样的,修饰名的作用之一就是标识不同的调用约定
2. 同一调用约定情况下,C 与 C++ 方式编译所得修饰名不一样。C++ 为了支持函数名重载,将函数的参数类型、返回类型等信息编码成字符,和原函数名拼成修饰名。修饰名的另一作用是支持函数重载
参考 MSDN:
-
链接约定 (Linkage):链接约定是调用约定与修饰名在链接时的具体表现,如 C linkage 和 C++ linkage
参考 MSDN:
操作修饰名的一个主要时机是链接阶段,链接器在各二进制模块间查找引用(被调用)的函数修饰名,组装起它们间的函数调用,无论是 (1). 静态链接,二进制模块 obj、lib 等,还是 (2). 动态链接,二进制模块 exe、dll 等
多数情况下程序员不需要直接操作修饰名,只要注意:修饰名和链接约定的不一致导致链接失败,务必保持一致就行了,具体的修饰名转换、查找操作交给链接器,不用操心
但如果在汇编源程序中引用 C/C++ 源程序中的函数,就需要显式指定修饰名,而非原始名,因为修饰名才是存在于二进制模块中的“函数真名”,而汇编不是编译,不吃调用约定那套东西(换句话说,你得手工实现调用约定)
编译示例 2
在示例 1 的基础上修改 ctool.h:
仍采用示例 1 的编译命令:
解释:
- extern "C": 虽然以 C++ 方式编译 [testproj.c include=> ctool.h],但对 ctool() 函数使用 C 链接约定,链接时 main.obj 和 ctool.obj 就会有相同的 C 修饰名 _ctool
- __cplusplus 的条件编译:因为以 C 方式编译 [ctool.c include=> ctool.h],而 C 语法中没有 extern "C",只有 extern,所以用 C++ 语言标准预定义宏 __cplusplus 和条件编译跳过 extern "C"
编译示例 3
在示例 2 编译命令的基础上去掉 /Tc ctool.c:
等价于:
虽然成功,但这与示例 2 是有区别的:
(1). 示例 2 中以 C 方式编译 [ctool.c include=> ctool.h],而这里以 C++ 方式编译它,所以执行了 C++ 的严格类型检查,只是将 ctool() 变为 C 的链接约定
(2). 如果 ctool.c 中有其它函数,又没用 extern "C",则它们会用 C++ 的修饰名
这种情况下,ctool.h 中的 __cplusplus 条件编译可以去掉,但为了 (ctool.h, testproj.dll) 发布后,也能被 C 程序使用,通常保留 __cplusplus 条件编译
分享到:
相关推荐
函数调用约定与函数名称修饰规则-很实用 使用C/C++语言开发软件的程序员...本文分别对C和C++这两种编程语言的函数调用约定和函数名修饰规则进行详细的解释,比较了它们的异同之处,并举例说明了以上问题出现的原因。
上面两个都是C风格的强制类型转换,C++还增加了一种转换方式,比较一下上面和下面这个书写方式的不同: long int el=123; short i=int (el); float m=34.56; int i=int (m); 使用强制类型转换的最大好处...
IvorHorton还著有关于C、C++和Java的多部入门级好书,如《C语言入门经典(第4版)》和《C++入门经典(第3版)》。 译者 杨浩,知名译者,大学讲师,从事机械和计算机方面的教学和研究多年,发表论文数篇,参编和翻译的...
第一章:范例 在这一章里将提供三个范例来说明如何...用户只需要简单地编译、链接和执行。其次,使用Borland C/C++产 生的80186的目标代码(实模式,在大模式下编译)与所有Intel、AMD、Cyrix 公司的80x86 CPU 兼容。
笔者之所以在本书一开始就写这一章是为了让读者尽快开始...用户只需要简单地编译、链接和执行。其次,使用Borland C/C++产生的80186的目标代码(实模式,在大模式下编译)与所有Intel、AMD、Cyrix公司的80x86 CPU兼容。
和图像库(用于编写游戏),还可以使用标准C语言函数库里的函数(调用静态库形式链接),也就是说NB可以在编译时链接所有用标准C语言编写的静态库(LIB)做为函数功能扩展,并且还可以调用WIN32API的大部分函数,...
中文 liblcl 一个通用的跨平台GUI库,核心使用Lazarus LCL。 已支持语言: go: c/c++: 完成度较高的语言: ...注: 如果在liblcl源代码ExtDecl.inc文件中启用了UsehandleException编译指令,则不再需要MySyscall
在这一章里将提供三个范例来说明如何使用 µC/OS-II。...用户只需要简单地编译、链接和执行。其次,使用Borland C/C++产生的80186的目标代码(实模式,在大模式下编译)与所有Intel、AMD、Cyrix公司的80x86 CPU兼容。
第一章:范例 在这一章里将提供三个范例来说明如何使用 ...用户只需要简单地编译、链接和执行。其次,使用Borland C/C++产生的80186的目标代码(实模式,在大模式下编译)与所有Intel、AMD、Cyrix公司的80x86 CPU兼容。
第一章 范例 在这一章里将提供三个范例来说明如何...用户只需要简单地编译、链接和执行。其次,使用Borland C/C++产 生的80186 的目标代码(实模式,在大模式下编译)与所有Intel、AMD、Cyrix 公司的80x86 CPU 兼容。
第一章:范例 在这一章里将提供三个范例来说明如何...用户只需要简单地编译、链接和执行。其次,使用 Borland C/C++产 生的 80186 的目标代码(实模式,在大模式下编译)与所有 Intel、AMD、Cyrix 公司的 80x86 CPU 兼容。
支持静态链接其它编程语言(如C/C++、汇编等)编译生成的静态库(.LIB或.OBJ),但仅限于COFF格式,支持cdecl和stdcall两种函数调用约定。 使用说明如下:函数声明和调用方法与DLL命令一致;“库文件名”以....
1. 如果你只用C语言,那么必然以C文件创建DLL(自动编出C符号名),考虑到潜在的C++用户(此类用户多以静态调用方式使用DLL,因而需要看到其函数声明),我们还需要使用EXTERN_C关键字(详见上面的讨论)。...
支持静态链接其它编程语言(如C C++ 汇编等)编译生成的静态库( LIB或 OBJ) 但仅限于COFF格式 支持cdecl和stdcall两种函数调用约定 使用说明如下:函数声明和调用方法与DLL命令一致;“库文件名”以 lib或 obj为...
导出函数可以为多种调用约定,比如:Stdcall(标准WINAPI)、 Cdecl(兼容C语言)、Pascal 。可供给其它语言调用。 四、静态链接库。供给标准的C语言调用链接。就是说NB的静态库是兼容C语言的LIB,互相通用。 为了...
导出函数可以为多种调用约定,比如:Stdcall(标准WINAPI)、 Cdecl(兼容C语言)、Pascal 。可供给其它语言调用。 四、静态链接库。供给标准的C语言调用链接。就是说NB的静态库是兼容C语言的LIB,互相通用。 为了...
3.1 用C语言还是用C++语言 3.1.1 调用约定 3.1.2 函数的导出名 3.1.3 运行时函数的调用 3.2 用DDK编译环境编译驱动程序 3.2.1 编译版本 3.2.2 nmake工具 3.2.3 build工具 3.2.4 makefile...
3.1 用C语言还是用C++语言 3.1.1 调用约定 3.1.2 函数的导出名 3.1.3 运行时函数的调用 3.2 用DDK编译环境编译驱动程序 3.2.1 编译版本 3.2.2 nmake工具 3.2.3 build工具 3.2.4 makefile...
3.1 使用Visual C/C++编译链接工具 26 3.1.1 编译器cl.exe 27 3.1.2 资源编译器rc.exe 31 3.1.3 链接器link.exe 32 3.1.4 其他工具 38 3.1.5 编译链接工具依赖的环境变量 39 3.1.6 示例:使用/D选项...