QOR 编译器方面






4.33/5 (2投票s)
开源,目前在 Windows、VC6、VSExpress2005、VSExpress2008、VS2012Desktop、CodeBlocks 12.11(带 MinGW GCC 4.7.1)、Rad Studio 2010 XE3 上进行了测试;开源 Suse Linux Netbeans 7.1(带 GCC 4.6.2)和 7.2.1(带 Clang 3.0)。
引言
CompilerQOR
是 Querysoft Open Runtime 的一部分,它是世界上第一个面向方面的、开源的、跨平台的框架,几乎完全用 C++ 编写。
CompilerQOR
是一个小型但至关重要的 QOR 方面。它的工作是确保 CompilerQOR
本身以外的任何代码都不必知道或关心是用哪个 C++ 编译器来构建的。这并不意味着代码可以不考虑编译器的限制而编写,只是说正在考虑的特定限制与使用哪个编译器无关。
CompilerQOR 解决的问题类型
如果您看过大量开源软件,您就会发现很多类似这样的东西
#ifdef __COMPILER_A
...
# if( __COMPILER_A_VERSION > 5006 )
...
# else
...
#else
# if ( __COMPILER_VERION < 1400 )
# ...
# else
....etc
这里通常发生的情况是,原始代码使用了 C++ 语言或编译器的某些特性,例如模板、命名空间、_UBER_FUNKY_BUILTIN_MACRO_
等,而并非所有编译器的支持。想要使用该代码的人们已经围绕特定编译器特定版本的特性 X 的支持情况进行了修补。太好了,现在它对很多人都有效,但不幸的是,它也变成了一个难以阅读的混乱不堪的东西,充斥着关于不同编译器而与原始算法无关的重要元数据,并且这些元数据本身无法重用。它可能有效,但从设计、维护、可读性或重用性的角度来看,这简直是一场灾难。
如果您认为上面的例子不足以引起您的重视,那么请看看 Make
的源代码,这个普遍存在的构建工具(但也不会持续太久),否则您可能会失明而错过本文的其余部分。
QOR 的不同之处
CompilerQOR 的作用是让我们能够将上述情况替换为
#if __QCMP_SUPPORTS( FEATURE_X )
...
else
...
#endif
不仅如此,所有关于每个编译器版本的功能和限制的累积知识都集中在一个地方。它可以被找到,也可以被重用。
另一个优点是,上面的代码现在可以与新编译器一起工作,即使是那些在其编写时还不存在的编译器。最多只需要更新 CompilerQOR,但如果特性 X 是一个相当普通的功能,所有新编译器都应该支持,那么 CompilerQOR 将默认支持它,几乎不需要进行任何更改。任何运行软件项目的人都能看到潜在的优势;降低成本和复杂性,提高可移植性,延长产品寿命,并鼓励标准遵从。
以前不是做过吗?
当然,以前已经做过了。没有原创代码。Boost 库就是这样做的,STLSoft 也是,许多其他出色的项目(如 llvm/clang/libc++)的配置文件头文件都有特性宏来指定对编译器之间差异的支持。CompilerQOR 建立在这些巨人的肩膀之上,而无需将它们四处携带。
就这些吗?
C++ 语言特性至关重要,但绝非编译器之间变化的唯一因素。存在以下方面的差异:
然后是应用程序二进制接口 (ABI),即构建的代码和数据结构在内存中的实际布局方式、v-table 差异、与加载器二进制格式(PE 或 ELF)的集成,以及用于运行时类型信息 (RTTI)、异常、安全和引导的代码注入。
所有这些事情都或多或少地属于 CompilerQOR 的范畴。随着 QOR 的发展,它将不仅仅是一组简单的头文件,但目标保持不变。如果“它”因编译器而异,“它”就属于 CompilerQOR,如果“它”只因编译器而异,“它”就只属于 CompilerQOR。其他任何东西都不应该依赖于当前使用的编译器。这就是使 CompilerQOR 成为一个 **方面** 而不仅仅是一个库的原因。使用的编译器会影响源代码树中的所有代码,但我们在一个地方处理这个横切关注点。
编译器识别
CompilerQOR 的首要任务几乎是弄清楚正在使用哪个编译器来编译它,这样它就可以将该编译器引导到包含文件树中的自己的正确路径。这与 Boost 库中的做法相同,输入一些来自其他地方的代码片段。每种不同类型的编译器都可以通过它们自动提供的预定义预处理器宏来检测,例如
# if defined __CODEGEARC__
# define __QCMP_COMPILER __QCMP_CODEGEAR //CodeGear - must be checked for before Borland
...
__CODEGEAR__
的预定义是我们检测到正在使用 Embarcadero 编译器的决定性标志。但正如注释所示,这些编译器也定义了 __BORLANDC__
,这是旧版 Borland 编译器的测试宏,所以我们必须先检查 CodeGear 来确定是哪个。抛开这些棘手的问题,这没有什么高深之处,只是一堆 if..else 预处理器逻辑。
特性
现在我们知道了是哪个编译器在查看代码,我们可以说明哪些特性可用,哪些不可用。我们通过将我们知道的所有特性集中在一个地方——“include/CompilerQOR/Common/CompilerFeatures.h”——来完成此操作,然后通过在编译器不支持它们的编译器特定头文件中 #undef 它们来完成。我们之所以这样做,有几个原因。特性列表集中在一个地方。每个编译器头文件都明确指出了该编译器不足的地方。这些很可能是导致兼容性问题的因素,因此我们希望将它们明确地与每个编译器关联起来。
为了检测特性是否受支持,提供了 __QCMP_SUPPORTS( _FEATUREX )
宏,用于当您想说的情况:
#if __QCMP_SUPPORTS( _FEATUREX )
...
#endif
如果您想对特性开关做更复杂的处理,例如将其作为条件包含在 QOR_PP_IF( condition, truecase, falsecase )
中,那么您需要使用 __QCMP_FEATURE_TEST( _FEATUREX )
,它会展开为不受支持特性的 0
和受支持特性的 1
。
这使用了宇宙中最糟糕的官方宏技巧,我在这里重现它只是为了说明我不认为我发明了它,请不要因为这个而起诉我或对我发动宗教审判。
# define __QCMP_FEATURE_TEST( _X ) __QCMP_FEATURE_TEST2( QOR_PP_CAT( __QCMP_FEATURE_TEST_, _X ), (0) )
# define __QCMP_FEATURE_TEST_1 0)(1
# define __QCMP_FEATURE_TEST2( _A, _B ) QOR_PP_SEQ_ELEM( 1, (_A)_B )
永远不要这样做!不要问我关于它的事情,也不要把它归功于我。说到这里就够了。
示例代码中定义的特性集非常小。仅够 CompilerQOR 本身编译,并对这些特性进行测试。还需要许多额外的特性才能处理所有编译器之间的所有差异。这项工作是持续的研究,尽管它已经远远超出了示例代码的范围,但尚未确定最终的明确列表。为了简化起见,我在示例代码中减少了列表,这样它就有很大的机会与未来版本兼容,也就是说,已经发布的内容不会在后续版本中突然消失。
预处理器
与本文一起的示例代码是 Boost 预处理器库的完整移植,并添加了一些已注释的修改。Boost 在线提供了完整的文档,我并没有编写他们的 PP 库,所以我们将在这里只处理 flow.h
中添加的部分。
QOR_PP_include_IF( condition, file )
基于 condition
的宏展开提供条件包含。如果 condition
展开为 1
,则包含 file
;否则,包含一个空文件,从而有效地取消包含。QOR_PP_include_IF
的用法如下: #include QOR_PP_include_IF( SOME_CONDITIONAL_MACRO, "include/ConditionalHeader.h" )
内建函数
内建函数是编译器具有内部代码副本的内置函数。代码不在库中,而在编译器本身中,并在编译过程中作为内联代码注入您的程序。内建函数可能非常有用,但也可能严重影响编译器之间的可移植性,因为并非每个编译器都以完全相同的方式提供它们。更改编译器,突然间您调用的函数就不再存在了。
我们可以完全禁止在 QOR 中使用内建函数,实际上在 CompilerQOR 之外我们也是这样做的,但那样就无法利用它们了。CompilerQOR 通过说服当前编译器将所有可用的内建函数注入 CompilerQOR 并为每个函数设置预处理器常量来解决这个问题,以便所有其他代码都可以找出特定的内建函数是否可用。更好的是,内建函数不会污染全局命名空间。在 CompilerQOR 本身之外,它们以 CCompiler
类的成员函数的形式出现。在示例代码中,这已作为概念验证实现了 Microsoft VC++ 编译器。以下是内建函数 memcpy
的工作方式:
在编译器识别导致预处理器包含 Microsoft 编译器的头文件之后
__QCMP_BUILTINS_HEADER
被定义为包含内建函数声明的文件名,例如:#define __QCMP_BUILTINS_HEADER "CompilerQOR/MSVC/VC6/Builtins.h"
__QCMP_UNBUILTINS_HEADER
被定义为包含指示编译器不要注入每个内建函数的文件名,例如:#define __QCMP_UNBUILTINS_HEADER "CompilerQOR/MSVC/VC6/UnBuiltins.h"
最后,__QCMP_BUILTINS_INC
被定义为包含 CCompiler 成员函数的文件名,其中每个函数都是内建函数的包装器,例如:#define __QCMP_BUILTINS_INC "ComilerQOR/MSVC/VC6/Builtins.inl"
稍后,通用的 Compiler.h 头文件类会在 extern "C"
部分中 `#include` `__QCMP_BUILTINS_HEADER`,如下所示,如果正在构建 CompilerQOR:
extern "C"
{
#include __QCMP_BUILTINS_HEADER
}
这会展开为:
extern "C"
{
void* memcpy( void* dest, const void* src, size_t count );
...
}
附加的条件是,只有在正在构建 CompilerQOR 本身时才包含此项,这样其他包含此头文件的库就不会看到 memcpy
的全局命名空间声明。
现在我们已经声明了全局命名空间 memcpy
函数,但尚未定义它。然后,在 CCompiler
类声明本身内部,再次包含相同的头文件。这次没有 extern "C"
。
class CCompiler : public CCompilerBase
{
...
# include __QCMP_BUILTINS_HEADER
...
}
这会创建一个与每个可用内建函数匹配的成员函数声明:
class CCompiler : public CCompilerBase
{
...
void* memcpy( void* dest, const void* src, size_t count );
...
}
在主 CompilerQOR.cpp 文件中,CCompiler
成员函数通过包含 __QCMP_BUILTINS_INC
来实现,如下所示:
//--------------------------------------------------------------------------------
namespace nsCompiler
{
#ifdef __QCMP_BUILTINS_INC
# include __QCMP_BUILTINS_INC
#endif
...
这会展开为:
//--------------------------------------------------------------------------------
namespace nsCompiler
{
//--------------------------------------------------------------------------------
#pragma intrinsic(memcpy)
//--------------------------------------------------------------------------------
void* CCompiler::memcpy( void* dest, const void* src, size_t count )
{
return ::memcpy( dest, src, count );
}
...
pragma 指示编译器在遇到未定义的内建函数引用时使用内建版本。我们没有定义它,因此 CCompiler::memcpy
函数将获得一个注入的内建函数副本。
在此之后,将包含最终的预处理器定义头文件,作用域为全局:
#include #ifdef __QCMP_UNBUILTINS_HEADER
# include __QCMP_UNBUILTINS_HEADER
#endif
这会展开为:
...
#pragma function(memcpy)
...
这指示编译器停止使用 memcpy
的内建形式,对于编译单元的其余部分,它将再次成为一个未定义的函数,或者直到 C 库再次定义它,但这又是另一篇文章的内容了。
在所有这些废话之后,我们最终得到一个 CCompiler
类,其中包含作为编译器内建函数调用的成员函数。唯一的外部接口是导出的 CCompiler
类。
为了稍后使用它,我们还需要做一件事,那就是为每个函数定义一个宏,以便我们知道它是否可用。这在 __QCMP_BUILTINS_HEADER
文件中完成,为我们提供了:#define __QCMP_DECLS_MEMCMP 1
,以后可以对其进行测试。
代码复杂度限制
编译器在处理名称长度和一次可以保留的各种类型定义数量方面有所不同。它们在预处理器可以处理的宏展开级别数量以及模板展开的复杂程度(在出现令人讨厌的“内部编译器错误”而毁掉您下午之前)方面也有所不同。其中一些限制在预处理器库中进行了检查,随着 QOR 的增长,其他限制无疑也会被发现。开关和 pragma
许多编译器支持在编译期间从代码内部输出消息。这有助于跟踪包含哪些文件、根据配置做出了哪些编译选择,以及问题出在哪里以及为什么。CompilerQOR 支持诊断消息,这些消息可以为每个编译单元打开和关闭。一个编译单元是 .cpp 文件及其包含的所有头文件。这适用于最近的 Microsoft、GCC 和 Clang 编译器,包括 MinGW GCC;其他编译器在自定义消息方面将保持沉默。工作原理如下:#ifndef NDEBUG //Debug build
# define __QCMP_REPORTCONIG 1 //Report configuration items during compilation where supported
#endif
将这些行作为 .cpp 文件的第一行(在任何 include 之前)插入,意味着当预处理器遇到类似以下的行时:
__QCMP_MESSAGE( "Compiler runtime type information enabled." )
您应该会在构建控制台上看到“Compiler runtime type information enabled”的输出。当然,这在一定程度上取决于您使用的 IDE。它适用于 Visual Studio 的 .Net 版本以及最近的 CodeBlocks 和 Netbeans IDE。
__QCMP_MESSAGE
宏,就像这里讨论过的所有其他宏一样,以及预处理器库中隐藏在“details”头文件中的那些宏,都可以安全地用于包含 CompilerQOR.h 的任何代码中。
选项和扩展
许多编译器支持一些扩展,尽管它们最初未在 C++ 语言标准中指定。其中包括运行时类型信息 (RTTI) 和 C++ 异常,据我所知,它们现在以某种形式已成为标准,但当然,仍然存在大量遗留实现。CompilerQOR 通过检测和报告其可用性,使其他库能够可移植地使用这些扩展。每个扩展都由一个定义来指定,如下所示:
#define RunTimeTypeInformation_QCMPSUPPORTED 1
。混合大小写名称将扩展与语言特性区分开来。
要测试扩展,请使用去掉 _QCMPSUPPORTED
后缀的名称作为 __QCMP_EXTENSION( _X )
的参数。
例如:__QCMP_EXTENSION( RunTimeTypeInformation )
如果扩展不可用,将展开为 0;如果扩展可用,将展开为 1。
还有一些选项可以为某些编译器预处理器预定义,以更改编译模式。这些通常必须在代码本身之外的 IDE 或 Makefile 中设置。使用 CompilerQOR,您可以选择这样做,或者在大多数情况下,您可以在单个配置文件头文件中定义所有内容,效果一样好。构建 CompilerQOR 及其任何使用它的东西的配置如下所示:
如果 __QOR_CONFIG_HEADER
在 IDE 或 Makefile 中在代码之外定义为文件名,则包含该文件以控制配置。如果未定义,则使用示例代码中可以看到的 "DefaultConfig.h"
。可以配置以下内容:
__QCMP_REPORTCONIG
此宏的默认值(我们已经见过)可以设置为全局打开或关闭编译期间的默认输出。__QCMP_REPORTDEFECITS
的工作方式类似于__QCMP_REPORTCONIG
,但专门用于代码中的 TODO: 标记。__QOR_FUNCTION_CONTEXT_TRACKING
这是 CodeQOR 库的一个设置,将在本系列文章的第 3 篇中介绍。__QOR_CPP_EXCEPTIONS
这会启用和禁用那些烦人的异常。仅仅因为您的编译器支持它们,并不意味着您总是想使用它们。这在示例代码中不支持。__QOR_ERROR_SYSTEM
这也是 CodeQOR 库的一个设置,稍后将介绍。__QOR_PERFORMANCE
一个介于 0 和 10 之间的数字,在一些地方用于确定我们应该花多少精力进行检查。设置为 0 时,您几乎进入了 lint 或 valgrind 的领域;设置为 10 时,则可以不顾一切地随意进行。__QOR_UNICODE
对于 Microsoft 编译器来说这很重要,因为它们有不同的 Unicode 和多字节模式。在 Microsoft 编译器或任何您发现支持类似 Unicode 构建的编译器上,将此定义为1
以进行UNICODE
构建。__QOR_PARAMETER_CHECKING_
这是 CodeQOR 库的另一个设置,我们尚未见过。
欢迎您尝试配置并告诉我任何组合是否无效,但这对于本文的示例代码几乎没有影响。__QCMP_COMPILER
的定义将覆盖自动编译器识别,并“假装”使用了不同的编译器。在极少数情况下,这可能有用,但请不要期望它能普遍适用。__QCMP_COMPILER
的值可以在 "include/CompilerQOR/Common/Compilers.h"
头文件中找到。
内置类型
我们都熟悉 C++ 的基本类型,如 int
、char
、volatile unsigned long long
?然而,语言本身对于 int
的确切含义,更不用说 long double
,仍然令人望而生畏地模糊。为了能够轻松地在编译器之间迁移代码,我们需要一套我们可以信赖的、大小始终相同的类型。我们当然不能保证字节的存储顺序总是相同的,因为这取决于硬件,但我们暂时将这个问题留给硬件抽象。只有当我们希望在具有不同架构的系统之间共享二进制文件时,它才会真正带来麻烦。
CompilerQOR
在 CCompiler
类中定义了一组类型,这些类型必须以某种形式从每个支持的编译器中可用。如果编译器不原生提供这些类型,那么我们就需要用 typedef 来模拟它们,以便客户端代码可以依赖于始终存在相同的类型。每种基本类型都有 const 和 volatile 限定的变体,有些还有有符号和无符号。这是 char
的集合:
typedef signed char mxc_signed_char;
typedef const signed char mxc_c_signed_char;
typedef volatile signed char mxc_v_signed_char;
typedef unsigned char mxc_unsigned_char;
typedef const unsigned char mxc_c_unsigned_char;
typedef volatile unsigned char mxc_v_unsigned_char;
在这里,我们为没有内置 wchar_t
类型的编译器模拟了它:
typedef mxc_unsigned_short mxc_wchar_t;
typedef mxc_c_unsigned_short mxc_c_wchar_t;
typedef mxc_v_unsigned_short mxc_v_wchar_t;
这些类型被拉回到 "CompilerTypes.h" 的全局命名空间中,该文件还充当了构建时检查,以确保 CCompiler
类已全部定义它们:
typedef nsCompiler::CCompiler::mxc_unsigned__int64 Cmp_unsigned__int64;
typedef nsCompiler::CCompiler::mxc_c_unsigned__int64 Cmp_C_unsigned__int64;
typedef nsCompiler::CCompiler::mxc_v_unsigned__int64 Cmp_V_unsigned__int64;
所有这些类型最终都有一个 Cmp_qualifier_type
形式。上面是一个有大小的类型的示例,其中第二个下划线重复两次并附加了位大小。虽然我们可以接受 long double
大小的变化,但这些有大小的类型确实必须可靠地准确表示它们所说的内容。
CompilerQOR
提供了 8、16、32 和 64 位有符号和无符号整数及其 const
和 volatile
变体的有大小的类型。
与架构字大小相关的类型也很有用,ComilerQOR
将 Cmp_int_ptr
和 Cmp_uint_ptr
定义为与当前架构上的指针大小完全相同的整数类型。最后,Cmp__int3264
在 32 位机器上始终为 32 位,在 64 位机器上始终为 64 位,即使某些奇怪的寻址限制或扩展改变了 _ptr 类型,而 byte
始终是 8 位无符号位。(我一直认为“byte”不是基本类型是一个非常糟糕的缺陷,所以我偷偷地将其添加进去了。)
QOR 在大多数情况下使用普通的 C++ 类型,但每当您需要确保 64 位类型可用,或者一个变量足够大以容纳一个地址时,Cmp_ 类型就很有用。它们都拥有单一标记的连续名称,甚至 Cmp_V_unsigned_long_long
在类型名称需要经过递归预处理器宏展开时也很有用,在这种情况下,volatile unsigned long long
可能会被解释为 4 个参数而不是 1 个。
还有更多
这样就剩下 ABI、v-table 访问、二进制映像格式、RTTI、异常、SEH、安全和引导,这仍然很多。这些事情通常由 libsupc++ 或 MSVCRT 等支持库处理,目前本文的示例代码也必须依赖它们。在后续文章中,我们将看到为什么这并非总是可能或可取的,以及如何自己实现其中一些功能,这样编译器就无需这样做了。如果您曾经梦想过在调试器中一步步跟踪动态转换,那么您需要关注 Windows 编译器支持文章,它最终会发布。小菜一碟
这就是 CompilerQOR 所涵盖的以及它未涵盖的内容。现在让我们深入细节,一步步添加对全新编译器的支持。
步骤 1
在代码中搜索或 grep
注意:在此处添加新编译器支持
这将为您提供需要进行基本编辑的位置列表。
添加一个 __QCMP_MYCOMPILER
定义,一个 __QCMP_COMPILERHEADER
路径定义以引用您的 MyCompiler.h 文件,并将 MyCompiler.h 添加到您用来构建 CompilerQOR 的任何 IDE 项目或 Makefile 中。
步骤 2
设置所有仅在新编译器使用时包含的特定定义。
这些都放在 MyComiler.h 或从那里包含的文件中。要确定需要设置什么,可以参考最相似的已支持编译器的头文件作为起点,或者复制示例代码中提供的 Template.h 文件以开始。如果您是编译器专家,那么几分钟之内就能准备好一切。如果不是,那么几分钟之内您就会有很多问题,例如,“我的编译器如何处理警告?”以及 __declspec(naked)
的正确等价物是什么?我可能帮不了太多忙,如果我知道,我可能自己已经添加了对您编译器的支持,但 CodeProject 的成员以及手册和 Google 可能会有所帮助。此时,请将所有特性保持开启状态,除非您确定它们不受支持。不支持的特性将在步骤 4 中自动检测到。
步骤 3
使用您新编译器构建 CompilerQOR 作为静态库。第一次构建很可能出错。关键在于错误发生的位置。如果您在新的 "MyCompiler.h 文件中或在包含 "MyCompiler.h" 之后的通用代码中遇到错误,那么您需要进行修复。如果您在包含 "MyCompiler.h" 之前就遇到了错误,那么很可能需要我来修复。我关于什么是“通用”代码的某个假设(以为它将得到所有编译器的支持)对您的情况来说是错误的。请告知我,因为这些问题通常都可以修复。
步骤 4
示例代码提供了构建静态库 StaticCompilerQOR 的项目,以及一个链接静态库的可执行文件 TestCmp。TestCmp 包含一系列针对当前编译器指定的 CompilerQOR 特性的编译时和运行时测试。运行 TestCmp 将输出运行时测试的结果。如果您正在运行它,那么编译时测试就已经通过了。按几次 <return> 键以完成整个过程,否则它将无限期等待。
像往常一样构建和运行 TempCmp。如果构建失败,失败信息应指示需要在 "MyCompiler.h" 中添加或更改什么内容才能修复。此时您可能需要 #undef
某些特性。一旦能够构建,我们就差不多完成了,运行它并检查是否有任何测试失败。类型大小目前我们做不了太多,但其他任何测试失败都表明需要 #undef
的特性。
现在您已经有了另一个编译器的基本支持,并且任何为了充分利用 CompilerQOR 特性和扩展检查而编写的代码都有很大机会在该编译器上运行。
考虑到即使将一个合理大小的源树从同一个编译器的某个版本迁移到另一个版本也可能需要数天时间,因此节省的成本很容易证明所花费的时间是合理的。
关于示例项目的说明
由于支持的编译器和构建系统数量众多,因此仅设置了 **Debug** 构建配置。开箱即用地,**Release** 构建很可能无法工作。如果您想要一个,则需要仔细检查相关 Debug 配置后,在 Release 配置中设置相应的选项。
Linux 构建将在检查类型大小时报告失败。这本身并非错误,但它确实指出了 Linux 和 Windows 编译器之间一个相当糟糕的不一致性,即使两者都是基于 GCC 的。目前我们没有 SystemQOR 库来为我们处理操作系统差异,因此 TestCmp 只能有一个“正确”值的集合来进行检查。欢迎对这个问题提出“永久”解决方案的建议。
结论
CompilerQOR 的这个初始版本显然只是一个开始,距离充分发挥其潜力还有很长的路要走。我并不拥有或无法访问世界上各种各样的 C++ 编译器,所以这需要您来参与。如果您的编译器在 CompilerQOR 中支持不足、不支持甚至完全不被识别,那么 QOR 需要您的输入。您使 QOR 变得更好,它使每个人的生活都变得更好。这正是开源应该运作的方式。本文附带的代码也可以在线找到,网址为 Source Forge,您可以在那里为 QOR 做出贡献。
现在我们已经抽象了编译器,我们可以感到很自豪,但我们还没有抽象的机器之间的所有差异呢?世界充满了 32 位和 64 位架构的混合,当这些消失时,有人会跳到 128 位。如果我们不能轻松应对这些差异,我们当然不是在创造奇迹。然后还有 MMX 和 SSE 等等。看起来我们需要一个 ArchQOR。
致谢
由于文章和源代码中提到了大量编译器产品,其中许多是商标。
Microsoft、Windows、Visual C++ 和 Visual Studio 是 Microsoft 的商标。
Embarcadero 和 CodeGear 是 Embarcadero 的商标。
提及的所有其他商标和商号均被承认是其各自所有者的财产,并且他们对本文或相关源代码的任何内容概不负责。
我特别要感谢 CodeProject 休息室的常客和偶尔光顾的访客,他们帮助我确定了支持哪些编译器以及提供了必要的鼓励,让我能够以七种不同的 IDE 完成七次同样的工作,结果才意识到还需要再做一次。真是的!
历史
- 初始版本 - 2013/03/02