SCCc 编译器下载,链接器源代码

编译器和链接器中的 Unicode 支持
您所需的主题如下所示。但此主题未包含在此库中。
编译器和链接器中的 Unicode 支持
Visual Studio 2013
本主题介绍 Visual C++ 生成工具中的 Unicode 支持。
命令行或编译器指令(如 #include)中指定的文件名中现在可能包含 Unicode 字符。
源代码文件
可按以下编码格式将 Unicode 字符输入到源代码文件中:
具有或不具有字节顺序标记 (BOM) 的 UTF-16 Little Endian
具有或不具有 BOM 的 UTF-16 Big Endian
具有 BOM 的 UTF-8
在编译期间,编译器以 UTF-16 格式将诊断输出到控制台。可在控制台显示的字符由控制台窗口属性决定。重定向到文件的编译器输出位于当前 ANSI 控制台代码页中。
链接器响应文件和 .DEF 文件
响应文件和 DEF 文件可以是具有字节顺序标记的 UTF-16,也可以是 ANSI。以前仅支持 ANSI。
.asm 和 .cod 转储
默认情况下,.asm 和 .cod 转储为 ANSI 格式,以便兼容 MASM。使用 /FAu 则输出 UTF-8。请注意,如果指定 /FAs,则将只是直接输出混合源文件,而且可能会显示为乱码,例如,当源代码为 UTF-8 并且未指定 /FAsu 时。
属性(默认为启用)在开发环境中启用 Unicode 文件名(请参见 )。
有些情况下需要更改此默认值,例如要修改开发环境以便使用不具有 Unicode 支持的编译器时。
本文是否对您有所帮助?
需要更多代码示例
翻译需要改进
(1500 个剩余字符)
感谢您的反馈作者:liigo
日期:2009/11
转载请注明出处:
  编译(compile)和链接(link),是计算机编程语言的通用处理系统。编译,是把程序源代码转换为目标文件;链接,是把目标文件转换为可执行文件。把编译和链接分成两个相对独立的子系统,是为了简化,是为了分而治之,也有基于通用性的考虑。编译器(compiler)的任务是把程序源代码编译为目标文件。通常每一种编程语言,都会有它自己的编译器,各种编译器的输出都是目标文件(.obj, .lib, .o, .a, ...)。链接器(linker)的任务是把编译器生成的目标文件经过一系列处理,最终生成可执行文件(.exe, .dll, .so, ...)。
  如果目标文件的规范是唯一的,所有编译器都输出特定格式的目标文件,链接器也都接收这种格式的目标文件,大概是一种比较理想的状态,编译器和链接器实现起来也会相对容易,而且通用性好,基至链接器只需有一个就行了,这也许是当初设计编译链接系统的初衷之一吧。但现实是很残酷的,目前来说,目标文件的格式不仅没有统一,反而分化的很厉害,粗略统计竟有逾十种之多,且互不兼容。编译器和链接器的开发者,往往只能选择支持一种或少数几种目标文件格式。通用链接器成为一种奢求,从来不曾出现过,这几乎完全背离了其设计初衷。
  好在小范围的应用通用链接器,还是可行的。例如,把D语言源代码编译为.obj目标文件,就能和C语言编译生成的其它.obj目标文件一起,用C语言的链接器链接生成EXE。又如一款新诞生的编程语言(就说易语言吧),不想重复开发专用链接器,可以考虑编译生成C语言格式的目标文件,进而得以使用现有的C语言链接器链接生成可执行文件,如此一来可大幅减少开发工作量,降低研发成本的同时还提高了系统的开放性。
  目标文件是沟通编译器和链接器的桥梁,它既是编译器的输出,又是链接器的输入;而&符号(symbol)&是目标文件中的核心元素,是编译系统和链接系统中最重要的操作对象。通俗的说,编译器的任务是&创建符号&(以及与符号相关向辅助设施),链接器的任务是&使用符号&(以及与符号相关向辅助设施)。
  下面结合我(liigo)的个人理解,说说目标文件的基本元素,及其内部结构。就以我目前接触最多的COFF格式的目标文件为例。
  目标文件中的基本元素有,符号(symbol)、重定位(relocation)、字串表(string-table)和节(section)。这里说的是逻辑概念。
  符号(symbol)是很什么?说不清楚,因为不好理解(对读者而言),也不好表达(对作者而言)。举例吧,假设程序源代码中有变量有常量有函数,那么编译之后那些变量常量函数都会各自成为一个符号,供它处引用。是不是可以把符号理解为&比变量常量函数更高层次上的抽象&呢?大概可以吧。正是因为符号是更高层次上的抽象,脱离了编程语言概念上的变量常量和函数,因而链接器才有可以做到与具体的编程语言无关。符号的主要属性有:名称(符号匹配完全基于名称文本),所属节(section)的序号,(符号实体)在节中的偏移,作用域(OBJ内部私有,或全局公开)。符号主要有两大类:一类是定义性质的(如变量定义、函数定义),其内容(如变量的值、函数体等)存储于指定的节中某个偏移处;另一类是声明性质的(如变量声明、函数声明),没有内容(因而不需要所属节、偏移等属性),链接器会根据名称在其它obj文件或其它lib文件中找到这个符号的定义。这里体现了链接器中&链接&二字的含义:一方声明(依赖、使用)一个符号,另一方定义这个符号,双方通过符号名称链接到一起。声明符号可以在定义符号之前,甚至在符号还没有定义的情况下。声明一个符号是编译器的行为,只是表示对该符号的依赖,相应的符号定义可以由他人(或编译器)在其他时间完成,只要链接器工作时能够(在其他目标文件中)找到定义就OK。从逻辑上说,符号通常指的是变量(变量的地址)和函数(函数可执行体首地址)。在OBJ中存储时,符号对应某个节(section)中的某处偏移;而在链接时(或链接的后期),符号则对应某个确定的内存地址(此地址由链接器指派,有了地址后才能执行后续的重定位操作)。符号在OBJ文件中是顺序存储的,所有符号的结构体组成一个数组,称为符号表。在OBJ文件内部,通常通过符号表中的索引(&=0)指代某个符号。如果指代其它OBJ中的符号呢?先在本OBJ内定义一个相同名称的&声明性质&的符号,然后通过符号索引指代本OBJ内的这个同名符号,将来链接器工作时,所有同名称的符号都被视为同一个实体并分派唯一的地址。
  节(section)是数据的容器,是存储数据的地方。节内存储的数据通常有:变量的值,常量的值,函数体,等。节的基本属性有:数据长度,数据在文件中的偏移,是否可读可写可执行,重定位表。在链接时,节总是作为一个整体参予链接的,它是不可分的。编译时节划分的比较小比较多,有利于链接时按需提取,有利于优化编译后的EXE或DLL的尺寸。分析VC6编译器生成的OBJ文件可知,一般一个函数会单独使用一个节(section)存储。如果看看C语言标准库的源代码,会发现它往往把一个函数写到一个单独的源文件中,这样编译时一个函数就会生成一个OBJ文件,尽量做到了细化。在OBJ中,所有节的节头(section-header)顺序存储形成一个数组,称为节头表或节表。通常通过OBJ文件内节表中的序号(&=1)指代某个节。
  重定位表(relocation)是从属于节(section的重要元素,用于修正节数据中的地址部分。分析编译器编译生成的函数代码的话,会发现它生成的不是完整的真正可执行的代码,而只是代码模板,其中涉及地址之处,往往简单的使用0x占位,同时在此处绑定一个符号(symbol)用于修正此地址。为什么会这样呢?因为在编译器工作时,它并不知道符号(变量、函数等)地址,可能该符号来自另一个OBJ(或另一个LIB),甚至连它有没有定义都无法知晓。编译器只能先留下空白给链接器。通俗的说,编译器出了一个完形填空的题目,要链接器解答。重定位表可以理解为编译器给链接器提供的信息,它是由多个重定位项组成的数组,其中每一个重定位的基本属性有:被修正地址在节数据中的偏移,用于提供地址的符号索引,重定位类型(绝对定位、相对定位等)。链接器工作时,根据重定位项中的符号索引得到符号名称,进而查询得到符号地址(链接器负责指派符号地址),根据被修正地址在节中的偏移以及节的地址(链接器负责指派节的地址)得到被修正地址的地址,再根据重定位类型,将符号的地址填过去。举个例子,C语言代码 int a = 1;,对变量赋值,编译结果(不考虑编译优化)可能是 mov dword ptr [0x], 0x,相应的X86指令序列为 C7 05 00 00 00 00 78 56 34 12,中间的四字节的0就是占位符,将来需要链接器把变量a的地址覆盖上去,这是绝对定位;再如C代码 f();,编译结果(不考虑编译优化)可能是 call dword ptr [0x],相应的X86指令序列为 FF 15 00 00 00 00,中间的四字节的0就是占位符,将来需要链接器把&函数f的地址与下一指令地址的差值&覆盖上去,这是相对定位的例子。具体是采用绝对定位还是相对定位还是其它定位方式,是由编译器生成的重定位表指定的,取决于编译器选择生成的指令代码。地址占位符也不见得一定是零,可以是任意数值(可正可负),表示相对目标地址的前后偏移量,链接器重定位时填写的地址其实是在此数值基础上与目标地址相加而得到的。以上说的是链接生成EXE或DLL时由链接器执行的重定位,将来DLL或EXE被载入时PE加载器还会执行一次重定位(重定位表由链接器生成,EXE中通常可省略),这两个阶段的重定位虽然细节上不同,但原理是一致的。
  字串表(string-table)是OBJ文件或LIB文件中的辅助设施,用于集中存储一些名称文本,如长度大于8字节的符号名称、段名称,以及长度大于15字节的链接成员(link member, 见于LIB中)的名称。字串表存在的目的主要是用于优化OBJ或LIB文件的尺寸。以符号名称为例,在OBJ中,一个符号所对应的结构体大小是固定的,共18字节,其中留出8个字节用于存储符号名称。如果符号名称比较短,小于等于8个字节,则直接存到这个结构体中(不存储C文本结尾字符'\0');如果符号名称长度大于8字节,则把名称存到字串表(string-table)中,然后把这个名称在字串表中的偏移记录到前面提到的8个字节区域处(在第一个字符前加'/'作为区分名称和偏移的标记)。
  至于LIB文件,相比OBJ就简单多了,它仅是OBJ文件的打包整理和索引,完整地包含了库中所有OBJ文件的内容,并提供了库中公开符号的名称索引表(根据一个符号名称可以快速查询到它是否在本库中定义,以及在哪个OBJ中定义)。在物理上,LIB文件的前面部分由三个固定的链接成员(linker member)组成,后面是顺序存储各OBJ文件内容(也称为linker member),每个链接成员均有一个数据头(header)。第一个固定链接成员(1st linker member),仅因兼容原因而保留,已被第二个固定链接成员(2nd linker member)取代,后者记录了符号名称索引信息和后面各OBJ成员的基本信息,第三个固定成员(3rd linker member)记录长文本(可能被省略)。
写的不是很条理,有点乱,请多包涵。liigo, 2009。
参考资料:
&&Microsoft Portable Executable and Common Object File Format Specification&&
阅读(...) 评论()gcode 代码编译器 G ,用vb编写 重要参考价值 Windows Develop 182万源代码下载-
&文件名称: gcode
& & & & &&]
&&所属分类:
&&开发工具: Visual Basic
&&文件大小: 1488 KB
&&上传时间:
&&下载次数: 128
&&提 供 者:
&详细说明:代码编译器 编译G代码,用vb编写,有重要参考价值-G code compiler to compile code, use vb to prepare, has an important reference value
文件列表(日期:~)(点击判断是否您需要的文件,如果是垃圾请在下面评价投诉):
&&G代码编译器&&...........\G代码编译器&&...........\...........\&&&&...........\...........\&&...........\...........\&&...........\...........\&&...........\...........\frmAbout.frx&&...........\...........\&&...........\...........\frmDocument.frx&&...........\...........\&&...........\...........\&&...........\...........\frmMain.frx&&...........\...........\&&...........\...........\frmSplash.frx&&...........\...........\&&...........\...........\G代码编译器.exe&&...........\...........\&&...........\...........\G代码编译器.vbp&&...........\...........\G代码编译器.vbw&&...........\...........\Module1.bas&&...........\...........\MSSCCPRJ.SCC&&...........\...........\东风汽车.NC&&...........\...........\&&...........\...........\&&...........\g代码编译器.rar
&[]:一般,勉强可用
&近期下载过的用户:
&相关搜索:
&输入关键字,在本站182万海量源码库中尽情搜索:
&[] - 对国际标准的ISO GM 数控机床加工代码进行解码和编码,并优化数控程序代码,压缩通讯信息量,提高通讯速度,给下位机(如DSP,FPEA)提供能够处理的数据
&[] - G代码分析
这个文件主要进行语法分析,主要包括以下几个方面
[1]判断字符地址(同种类型)时候有重复定义
[2]字符串提取数据,并把数据储存在语法分析变量数组里
[3]完整G代码匹配语句具体格式,其中格式合法性定义参照
&[] - 在VC 6.0平台下,读取G代码文件,利用Boost中Regex正则表达式逐行提取数控G代码中的数值,分析两行G代码之间各轴速度变化。提供保存分析结果功能
&[] - TC编的俄罗斯方块
&[] - 无线数据芯片设计 无线数据芯片设计 无线数据芯片设计
&[] - 用vb编写的编译器 用以实现确定有穷自动机中状态对的转换
&[] - 在VISUAL C ++平台上将AUTOCAD的DXF文件的二维图形数据转换为数控加工G代码,
&[] - 在数控机床的坐标文件,生成数控机床的代码
&[] - Cygnal 集成开发环境(IDE)是一套完整独立的软件程序它为设计者提供
了用于开发和测试项目的所有工具
程序的主要特点包括
全功能窗口字体可配置的编辑器
调试器具有设置断点观察点单步等功能
工具链接集成支持汇编器编译器和链接器
可定制的工具菜单用于集成其它编译器
&[] - 编译器是一种特殊的程序,它可以把以特定编程语言写成的程序变为机器可以运行的机器码。我们把一个程序写好,这时我们利用的环境是文本编辑器。通过 Visual C++ 的编程模型和编译器优化增强您的应用程序本文中的一部分内容基于 Visual Studio 预发布版本(以前的代号为“Whidbey”)。所有与该测试版有关的信息都保留更改权利。本文讨论: •为什么 C++ 是 .NET 的强大语言•如何在 .NET 中用 C++ 编程获得高性能•C++ 和 JIT 优化器的作用•延迟加载和 STL/CLI本文使用了以下技术:C++ 和 Visual Studio本页内容虽然 Microsoft® .NET Framework 确实能提高开发人员的工作效率,但许多人对托管代码的性能还是有些担忧。新版本的 Visual C++® 将会让您消除这些担忧。对于 Visual Studio® 2005,C++ 语法本身得到了很大的改进,从而使它编写更加迅速。另外,还提供了一个灵活的语言框架来与公共语言运行库 (CLR) 相交互以便于编写高性能的程序。许多编程人员认为 C++ 之所以能带来高性能,是因为它生成本机代码,但即使您的代码完全托管,仍然可以获得出众的性能。通过灵活的编程模型,C++ 不会让您束缚在面向过程编程、面向对象编程、可再生编程或者元编程。另一个常见的误解是:不管使用什么语言,在 .NET Framework 中都能获得同样好的性能 ― 通过各种编译器生成的 Microsoft 中间语言 (MSIL) 本质上是等同的。即使在 Visual Studio .NET 2003 中也无法这样,但在 Visual Studio 2005 中,C++ 编译器团队致力于确保优化本机代码多年所获得的所有经验都能够应用到托管代码优化上。C++ 为您提供充分的灵活性来进行更好的优化,比如进行高性能封送处理,这在其他语言中是无法做到的。此外,Visual C++ 编译器还生成任何 .NET 语言中最优化的 MSIL。结果是 .NET 中最优化的代码来自 Visual C++ 编译器。优化的MSIL在 .NET 环境中,编译分为两个不同的部分。第一部分为编程人员通过语言编译器(C#、Visual Basic? 或 Visual C++)进行编译和优化,以生成 MSIL。第二部分包括将 MSIL 送到实时 (JIT) 编译器或 NGEN,由它读取 MSIL 并随后生成优化的本机代码。显然,语言编译器和 JIT 是不可分离的组件,这意味着要生成好的代码,二者必须协同工作。Visual C++ 始终提供任何编译器的最高级优化设置。这在托管代码中也没有改变。甚至在 Visual C++ .NET 2003 中这一点也很明显,它只是通过用于生成 MSIL 代码的本机编译器开始启用优化。在 Visual C++ 2005 中,编译器可以对 MSIL 代码执行标准本机代码优化很大的子集。从基于数据流的优化到表达式优化,再到循环展开,这一切都包含在内。平台中的其他任何语言都无法做到这一级别的优化。在 Visual C++ .NET 2003 中,全程序优化 (Whole Program Optimization, WPO) 不支持使用 /clr 开关构建,但 Visual C++ 2005 为托管代码添加了这个功能。这个功能启用了跨模块优化,本文后面将会对其进行讨论。在 Visual C++ 2005 中,托管代码唯一不可用的一种优化是 Profile Guided Optimizations,虽然在以后的版本中可能可用。有关更多信息,请参阅 。JIT 和编译器优化交互Visual C++ 生成的优化代码提供给 JIT 或 NGEN 以生成本机代码。不管 Visual C++ 编译器生成的代码是 MSIL 还是非托管代码,生成代码的优化器还是十几年前就已开发并已进行调整的优化器。对 MSIL 代码的优化是对非托管代码进行优化的一个大子集。需要指出的是,允许的优化类随编译器生成的是可验证代码 (/clr:safe) 或非可验证代码 (/clr or /clr:pure) 的不同而不同。在少量的几种情况下,编译器会因为元数据或可验证性限制而无法完成操作,包括缩减运算量(将相乘转换成指针相加),以及将对一个类的私有成员的访问内联到另一个类的方法体中。Visual C++ 编译器生成 MSIL 代码之后,就可以交给 JIT 进行处理。JIT 读取 MSIL 并开始执行优化,这些优化对 MSIL 中的变化很敏感。一个 MSIL 指令序列也许能够很好地进行优化,但另一个(语义上等同的)序列却可能抑制优化。例如,寄存器分配是一个优化,在这个优化中,JIT 优化器试图将变量映射到寄存器中;寄存器是作为执行算术和逻辑运算的操作数使用的实际硬件。有时,语义上等同但采用两种不同方式编写的代码可能会使优化器在执行良好的寄存器分配上所花费的时间相差巨大。循环展开是一个可能导致 JIT 分配寄存器出现问题的转换的例子。C++ 编译器完成的循环展开可以公开更多的指令级并行,但也创建了更多活变量 (live variable),编译器需要使用它们来跟踪寄存器分配。CLR JIT 只能跟踪固定数目的寄存器分配变量;一旦需要跟踪的数目超出这个数目,它就开始将寄存器的内容移到内存中。因此,必须先后对 Visual C++ 编译器和 JIT 进行微调以生成最佳代码。Visual C++ 编译器负责进行的优化是那些对 JIT 来说太耗时的优化,以及那些在从 C++ 源代码编译为 MSIL 的编译过程中会造成太多信息丢失的优化。让我们看一下 Visual C++ 对托管代码的一些优化。公共子表达式消除和代数简化公共子表达式消除(Common subexpression elimination,CSE)和代数简化 (algebraic simplification) 是两个强大的优化,它们允许编译器在表达式级别执行一些基本优化,以便开发人员可以专注研究算法和体系结构。下面显示的代码片段分别作为 C# 和 C++ 编译;二者都是在 Release 配置下编译的。变量 a、b 和 c 从一个作为参数传递的数组复制到包含这段代码的函数中: int d = a + b *
int e = (c * b) * 12 + a + (a + b * c);
显示了 C# 编译器和 C++ 编译器通过这段代码生成的 MSIL,它们都启用了优化。C# 需要 19 条指令,而 C++ 只需 13 条。另外,您可以看到 C++ 代码可以对 b*c 表达式进行 CSE。该编译器可以对 a+a 进行代数简化,即改为生成 2*a,也可以对 (c*b)*12 + c*b 进行代数简化,即改为生成 (c*b)*13。我发现增加的这个 CSE 特别有用,因为我见过编程人员在实际的代码中没有进行这种代数简化。请参阅补充内容“”。 全程序优化Visual C++ .NET 对非托管代码添加了 WPO。而在 Visual C++ 2005 中,这个功能扩展到了托管代码。它不是一次编译和优化一个源文件,而是一次跨所有源文件和头文件进行编译和优化。现在编译器可以跨多个源文件执行分析和优化。例如,如果没有 WPO,编译器只能在单个编译域中内联函数。有了 WPO,编译器就可以从程序中的所有源文件内联函数。在以下的示例中,编译器可以做的事情包括跨编译器内联和常量传递,以及其他类型的过程间优化: // Main.cpp
MSDNClass ^MSDNObj = gcnew MSDNC
int x = MSDNObj-&Square(42);
// MSDNClass.cpp
int MSDNClass::Square(int x)
return x*x;
在这个示例中,Main.cpp 调用 Square 方法,而这个方法是另一个源文件中的 MSDNClass 的一部分。当编译时进行 /O2 优化,而不进行全程序优化时,Main.cpp 中产生的 MSIL 如下所示: ldc.i4.s
instance int32 MSDNClass::Square(int32)
您可以看到,它首先将值 42 加载到堆栈中,然后调用 Square 函数。作为对照,对于相同的程序,当编译时打开全程序优化时,则生成的 MSIL 如下所示: ldc.i4
它没有加载 42,也没有调用 Square 函数。相反,在全程序优化下,编译器可以内联来自 MSDNClass.cpp 的函数并进行常量传递。最终的结果只是一条简单的指令 ― 加载 42*42 的结果,十六进制表示为 0x6e4。 虽然 Visual C++ 编译器执行的一些分析和优化在理论上 JIT 编译器也可以执行,但对 JIT 编译器的时间限制使得这里提到的许多优化当前还无法实现。一般情况下,NGEN 会比 JIT 编译器更早实现这些类型的优化,因为 NGEN 没有 JIT 编译器必须面对的这类响应时间限制。64 位 NGEN 优化出于本文需要,我将 JIT 和 NGEN 统称为 JIT。对于 32 位版本的 CLR,JIT 编译器和 NGEN 执行的优化相同。但 64 位版本的却不是这样,在 64 位版本中,NGEN 比 JIT 所进行的优化明显更多。64 位的 NGEN 利用了这样的事实:它可以比 JIT 花费更多的时间进行编译,因为 JIT 的吞吐量直接影响应用程序的响应时间。我在本文特别提到了 64 位的 NGEN,因为它针对 C++ 风格的代码进行相对地微调,它进行的一些优化(例如双 Thunk 消除优化)对 C++ 起到很大的帮助,这些优化是其他 JIT 和 NEGN 所不具备的。32 位 JIT 和 64 位 JIT 分别是 Microsoft 中两个不同团队使用两种不同的代码基实现的。32 位 JIT 是由 CLR 团队开发的,而 64 位 JIT 是由 Visual C++ 团队开发的,而且基于 Visual C++ 代码基。因为 64 位 JIT 是由 C++ 团队开发的,所以它更加注重与 C++ 相关的问题。双 Thunk 消除64 位 NGEN 执行的最重要的优化之一就是所谓的双 thunk 消除。这个优化在带 /clr 开关编译的 C++ 代码中通过函数指针或虚拟调用解决了一个转换,这个转换发生在通过托管代码调用托管入口点的时候。(在 /clr:pure 或 /clr:safe 编译代码中不会发生这种转换。)发生这个转换是因为在 callsite 上函数指针和虚拟调用都没有足够的信息可以确定它们调用的是托管入口点 (MEP) 还是非托管入口点 (UEP)。为了向后兼容,始终选择 UEP。但如果托管 callsite 实际调用的是托管方法呢?在这种情况下,除了初始 thunk 从托管 callsite 进入 UEP 外,还会有一个 thunk 从 UEP 进入目标托管方法。这个托管-托管 thunk 过程通常称为双 thunk。64 位 NGEN 实现了对“从非托管到托管”的调用(即为反过来对托管代码 thunk 的调用),从而实现了优化。可以进行一个检查来确定是否是这种情况;如果是,它就会跳过这两个 thunk,并直接跳到托管代码,如图 2 所示。这样可以节省许多指令,在实际的代码建模基准中,我发现有 5-10% 的提高(在人为测试中,可以看到超过 100% 的性能提高)。图 2 双 Thunk 消除不过有一点需要注意,那就是这个优化只有在位于默认应用程序域时才生效。有一个很好的小规则,那就是记住,默认 AppDomain 通常会获得更好的性能。C++ InteropC++ interop 是用于本机托管 interop 的一种技术,它允许标准 C++ 代码带 /clr 开关编译,以便直接调用本机函数,而不用编程人员添加其他任何代码。当使用 /clr 开关时生成的代码是 MSIL(除了少数特例),并且数据可以是托管或非托管的(由用户指定数据的存放位置)。我倾向认为 C++ interop 是没有人知道的最重要的 .NET 功能。它是真正具有突破性的改革,但要真正了解 C++ interop 的强大之处还需要一定的时间。在其他与 .NET 兼容的语言中,要与本机代码进行 interop 需要您将本机代码放在一个 DLL 中,并使用 dllimport 调用带有显式 P/Invoke 的函数(或者其他一些与此类似的做法,取决于您使用的语言)。否则就必须使用笨重的 COM interop 访问本机代码。这明显不方便,而且经常会遇到性能比 C++ 差很多的情况。一般不认为 C++ interop 是 C++ 语言的性能特征,但正如您将看到的,C++ interop 所提供的灵活性和便利性却可以让您借助 CLR 获得更好的性能。单映像中的本机代码和托管代码Visual C++ 可以使编程人员(按逐函数方式)有选择地选择哪些函数是托管的,哪些是本机的。 这是通过 #pragma managed 和 #pragma unmanaged 实现的, 显示了其中一个例子。在许多计算量大的任务中,让核心函数进行本机编译而其他代码进行托管编译可以带来很大好处。在单个映像中,C++ 可以将托管代码和本机代码混合一起,通过本机函数调用托管函数(反之亦然)不需要特殊的语法。在这种粒度下,C++ 可以很轻松地控制从托管代码向本机代码的转换,反之亦然。当从托管代码向本机代码转换(或反向)时,执行的过程要经过由编译器/链接器生成的 thunk。这个 thunk 需要一定代价,编程人员都竭力避免付出这样的代价。有大量工作是在 CLR 中完成的,并且编译器会使转换的成本降到最低,但开发人员也可以通过降低这种转换的频率来帮助降低成本。图 4 的 A 部分中是一个 C++ 应用程序,它的部分代码 (Z.cpp) 经编译生成 MSIL (/clr),而其他部分(X.cpp 和 Y.cpp)经编译生成本机代码。在这个程序中,Y.cpp 和 Z.cpp 中有些函数经过来回多次调用。这会导致大量托管/本机转换,从而降低程序的执行速度。图 4 更改托管界限图 4 中的 B 部分显示了如何优化该程序来使托管/本机转换降至最少。其思想是确定常用接口,将它们都移到托管/本机界限的一侧,从而消除所有跨常用接口的转换。使用 Visual C++ 为 interop 提供的工具可以很轻松地完成这项工作。例如,要从图 4 中的 A 转到 B,只需要用 /clr 开关重新编译 Y.cpp。现在 Y.cpp 被编译为托管代码,从 Z.cpp 调用就不需要有从托管到本机的转换成本。当然,您也需要考虑从 Y.cpp 生成 MSIL 的相关性能代价,并确保这种折衷对应用程序有利。高性能封送处理封送处理是托管/本机 interop 中成本最高的方面之一。在 C# 和 Visual Basic .NET 等语言中,封送处理是在调用 P/Invoke 时 CLR 隐式完成的(使用默认封送拆收器或者在实现 IcustomMarshaler 时用自定义封送处理代码完成)。而在 C++ interop 中,编程人员可以在代码中认为合适的地方显式封送处理数据。这样做的好处是编程人员可以一次性将数据封送到本机数据,然后通过多次调用重用数据的封送处理结果,从而均摊封送处理成本。 显示了带 /clr 开关编译的代码片段。在这段代码中有一个 for 循环,在这个循环中调用本机函数 (GetChar)。在 中,采用 C# 实现相同的代码,并且通过调用 GetChar 来使 CsharpType 类封送处理到 NativeType,如下所示: class NATIVECODE_API NativeType {
NativeType();
char *theS
在 C++ 中,用户显式使用本机类型,因此不需要隐式封送。这种类型的优化所节省的成本相当大。在这个示例中,C++ 实现比 C# 实现快 18 倍。 具有 .NET 类型的模板和 STLVisual C++ 2005 中一些比较有趣的新性能特征是具有托管类型(包括 STL/CLI)的模板、与托管代码的全程序优化、延迟加载和确定性终止。Visual C++ .NET 2003 可以在本机类型上为模板生成 MSIL,但不能在该模板中将托管类型作为参数化类型使用。在 Visual C++ 2005 中,这个问题已经得到纠正,模板现在可以将托管类型或非托管类型作为参数。现在,模板的强大功能可以用于在 .NET 中编写的代码了(您还应该看一下 Blitz++ 和 Boost 库所做的工作)。C++ 标准模板库 (STL) 是库设计中一个重大革新。它可以让您很好地利用容器和算法而不会牺牲性能,这一点已获证实。在 Visual C++ .NET 2003 中,在模板中对托管类型的限制意味着没有托管类型的配套 STL。Visual C++ 2005 中除了具有这种限制外,还引入了 STL/CLI ― 一种已证实可处理托管类型的 STL 版本。.NET 中的基类库 (BCL) 最初在 .NET 中引入了容器,但 Visual C++ 小组中的计划是 STL/CLI 的性能会更加优越。如果您想对 STL/CLI 有更多了解,Visual C++ 开发人员中心中有一篇 Stan Lippman 所撰写的优秀文章 。有了 STL/CLI 后,您可以通过 STL 实现您喜欢的所有内容,包括矢量、列表、双端队列、映射、集合以及哈希映射和集合。您还可以获得排序、搜索、集合运算、内积和卷积等算法。STL/CLI 算法的一个惊人之处是可对本机和 STL/CLI 版本使用相同的实现。STL 的良好设计将通过可移植的强大的代码来让每个 C++ 编程人员受益。确定性帮助性能由于您可以使用强大的模式和术语库,编写有效的 C++ 变得轻松许多。其中许多模式和术语(包括 Resource Acquisition Is Initialization (RAII))使用 C++ 语言中一个称为确定性终止的功能。它的原则是当一个对象被 delete 操作符删除(对于堆栈分配对象)或处于作用域外(对于堆栈分配对象)时,就会调用该对象的析构函数。确定性终止可以挽救性能,因为一个对象占有资源的时间越长(比它真正需要的长),性能下降越多,因为其他对象试图获取相同的资源。使用 CLR 终止程序会导致终止程序代码在对象处于作用域外(假设释放锁的代码是该终止程序)但还没在对象的终止程序上调用终止线程时在某一个位置执行。显然,这样做并不理想,因为当编程人员期望执行终止线程时,它可能不执行。另外,与对象相关的内存直到终止程序执行后才会回收,这样会使程序对内存的要求增加。在基于 .NET 的代码中,一个有助于避免这种问题的常见术语是 Dispose 模式。要使用它,开发人员需要为他们的类实现一个 Dispose 方法,然后在不再需要对象时调用该方法。当 C++ 编程人员要对对象调用 delete 时,就可以同时在代码中调用这个方法,但即使在 C++ 中,这样也很容易出错且过于繁杂。诸如 C# 等语言添加了“using”构造,它有助于解决后面两个问题,但对于特殊情况,它也会很复杂且容易出错。相反,RAII 这个 C++ 术语自动获得和释放资源,而且不容易出错,因为编程人员不需要编写额外代码。Visual C++ .NET 2003 不支持堆栈分配 .NET 对象的确定性终止,但在 Visual C++ 2005 中支持这个功能。在 的上半部分,可以注意到类型为 Socket_t 的对象使用了基于堆栈的语法,并将具有基于堆栈的清除语义。这样,当在第三行产生一个异常时,会出现什么情况呢?对于基于堆栈的语义,会确定性地为 mainSock 运行析构函数,但由于还没在堆栈中创建 backupSock,所以没有对象可以析构。要编写语义上等同于 C# 的代码有些困难且容易出错;请参见的下半部分。当然,这个例子很小,但随着这种任务复杂度的增加,出现错误的可能性就会越大。延迟加载虽然 .NET Framework 为提高性能而进行了微调,但在启动时加载 CLR 还稍微有些延迟。在许多应用程序中,可能有一些代码路径和方案没有托管代码,特别是当通过 .NET 功能对现有的旧式程序进行改造时。在这些情况下,您的应用程序就应该不会有这种相关的启动延迟。您可以使用 Visual C++ 中现有的功能 ― DLL 的延迟加载来解决这个问题。其思想是只在实际需要使用 DLL 中的某些内容时才加载该 DLL。这种思想也可应用于加载内容为 .NET 程序集的 DLL。通过使用链接器选项 /DELAYLOAD:dll(通过它指定想要延迟加载的 .NET 程序集),除了可以延迟加载列出的 .NET 程序集外,还可以延迟加载 CLR(如果所有 .NET 程序集都延迟加载)。结果是,应用程序的启动速度可以完全像本机启动那么快,从而消除了托管应用程序最常见的弊端之一。为什么 dllexport 不能始终适用使用 __declspec(dllexport) 有它自己的缺陷。当您有两个映射(DLL 或 exe)都为托管映射,但通过 dllexport 而不是通过 #using 公开功能时,dllexport 的问题就会暴露。因为 dllexport 是一个本机构造,所以每次使用 __declspec(dllexport) 跨 DLL 边界调用时,都会先引发从托管到本机的转换,再引发从本机到托管的转换。这样就难以获得很好的性能。解决这种性能问题的选择很有限。没有简单的“开关”可以立刻让 __declspec(dllexport) 成为对托管代码没有相关 thunk 的构造。推荐的修复办法是将导出的功能包装在一个托管类型(引用或值类/结构)中,导入程序再通过导出 DLL 上的“#using”访问该类型,从而直接访问导出 DLL 中的功能。通过这种更改,当从托管客户端调用这段托管代码时就不需要进行转换。图 8 中对此做了说明,其中 A 部分显示了与使用 __declspec(dllexport) 相关的成本,B 部分显示了使用 #using 和将函数包装在 .NET 类型中所带来的优化。这种方法的一个潜在问题是导出 DLL 的非托管导入程序不能对 DLL 的功能进行 __declspec(dllimport)。这在进行更改之前应该加以考虑。图 8 降低 Thunk 成本图 8 的 A 部分显示了使用 __declspec(dllexport) 将托管函数公开给托管代码的转换路径。在 B 部分中,该函数被包装成托管类型,并使用 #using 来访问该函数。与 A 部分中的过程相比,其结果是省去了成本很高的 thunk。小结Visual Studio .NET 2002 引入了带有 Visual C++ 的 .NET Framework 已有很长的时间。C++ 使得编程人员编写高性能托管代码具有很大的灵活性,而且都是按 C++ 编程人员很自然的方式工作的。有许多语言可用于进行 .NET 编程;如果您想获得最大的性能,则 Visual C++ 是显而易见的选择。Kang Su Gatlin 是 Microsoft Visual C++ 团队的程序经理,他的大部分工作时间都在尝试寻找可让程序运行更快的系统方式。在到 Microsoft 工作之前,他从事高性能和网格计算。

我要回帖

更多关于 fortran编译器 的文章

 

随机推荐