(这是关于visualstudio“14”CTP中C运行时(CRT)更改的两篇文章中的第一篇。本文论述了图书馆建筑的主要变化;第二篇文章将列举新特性、错误修复和突破性更改。)
对于Visual Studio(2002, 2003, 2005、2008, 2010, 2012和2013)的过去七个版本,Visual C++库已经被版本化,并且每个版本的库集都独立于其他版本的库集。例如,使用Visual C++ 2010使用DLL运行库构建的C++程序将依赖于MSVCR100.DLL和MSVCP100.DLL,而VisualC++ 2013所构建的C++程序将依赖于MSVCR120 .DLL和MSVCP120。
一方面,这种在每个版本中引入不同名称和完全独立的库集的模式使我们更容易添加新特性和修复bug。我们可以随时进行破坏性的更改,例如修复不一致或错误的行为,而不必担心破坏依赖于这些库的已发布版本的现有软件组件。
然而,我们经常听到您,我们的客户,这个模型是繁重的,在某些情况下,很难采用新版本的Visual C++,因为依赖于用较旧版本的Visual C++构建的模块,或者需要支持用VisualC++的特定版本构建的插件。
由于两个原因,这一问题近年来变得特别严重。首先,我们加快了visualstudio的发布进度,以便更频繁地提供新功能。其次,支持比台式机或笔记本电脑(如手机)小的设备变得非常重要,而在这些设备上积累非常相似的库的多个副本并不理想。
即使对我们来说,这种引入新版本库的模式有时也会很痛苦。对我们来说,修复已经发布的库版本中的bug是非常昂贵的,因为我们不再积极地处理这些版本的代码库,所以修复必须单独进行后端口和测试。结果是,我们通常只修复旧版本库中的严重安全漏洞。其他错误通常只在下一个主要版本中修复。
我们无法修复过去:已经发布的这些库的版本不会消失。但我们将努力在未来改进这一经验。这是一项重大任务,需要一段时间,但我们计划循序渐进,从…
CRT的重构
CRT位于Visual C++库堆栈的底部:其余的库依赖于它,实际上所有的本地模块也依赖于它。它包含两种内容:[1]C标准库和各种扩展,以及[2]进程启动和异常处理等所需的运行时功能。因为CRT位于堆栈的底部,所以它是开始稳定库的过程的合乎逻辑的地方。
从visualstudio“14”开始,我们将停止在visualstudio的每个版本中发布新版本的CRT。然而,在我们将在即将发布的版本中发布msvcr140.dll,然后在下一个版本中发布msvcr150.dll之前,我们将在visualstudio“14”中发布一个新的CRT,然后在随后的版本中更新该版本,保持现有程序的向后兼容性。
我们还致力于统一用于不同平台的crt。在VisualStudio2013中,我们为不同的平台构建了不同的CRT“风格”。例如,我们为桌面应用程序、Windows应用商店应用程序和Windows Phone应用程序提供了单独的CRT。我们这样做是因为不同平台上的windowsapi函数不同。
在Windows应用商店和Windows Phone应用程序中,只有Windows API的一个子集可供使用,因此我们需要以不同的方式实现某些功能,而无法实现其他功能(例如,Windows应用商店和Windows Phone应用程序中没有控制台,因此我们在CRT中不提供控制台I/O功能)。桌面应用程序的CRT必须在所有受支持的操作系统上运行(在Visual Studio 2013中,包括Windows XP),并且必须提供全套功能,包括遗留功能。
为了统一这些不同的CRT,我们将CRT分为三部分:
-
VCR运行时 (vcruntime140.dll):此dll包含进程启动和异常处理等操作所需的所有运行时功能,以及出于某种原因耦合到编译器的功能。我们 可以 将来需要对这个库进行突破性的更改。
-
应用程序 (appcrt140.dll):此dll包含所有平台上可用的所有功能。这包括堆、数学库、stdio和locale库、大多数字符串操作函数、时间库和一些其他函数。我们将保持CRT这一部分的向后兼容性。
-
桌面CRT (desktopcrt140.dll):此dll包含所有只能由桌面应用程序使用的功能。值得注意的是,这包括用于处理多字节字符串的函数、exec和spawn进程管理函数以及直接到控制台的I/O函数。我们将保持CRT这一部分的向后兼容性。
虽然我已经在列表中命名了发行版DLL,但是也有相当的调试DLL以及发行版和调试静态CRT库。通常的lib文件( msvcrt.lib
, libcmt.lib
等)构建时,新重构的CRT在构建时是旧CRT的替代品,只要 /nodefaultlib
未使用。
虽然我们在这个CTP的DLL中保留了版本号,但我们计划在visualstudio“14”最终发布之前将其从AppCRT和DesktopCRT中删除,因为我们将在适当的地方更新那些DLL。最后,我们仍在进行功能的最终打包,因此我们可能会在最终发布之前在dll之间移动内容。
Windows应用商店和Windows Phone应用程序只能使用VCRuntime和AppCRT的功能;桌面应用程序将能够使用所有这些功能以及桌面crt的功能。在第一个visualstudio“14”CTP中,所有应用程序都依赖于重构CRT的三个部分;这只是暂时的状况,最终会得到解决。
维修性问题
为了考虑以这种方式稳定库,我们必须解决的最大问题之一是可维护性问题。CRT是一个非常古老的代码库,许多源文件可以追溯到20世纪80年代。在代码的许多部分,几十年前有效且有用的优化技术不仅混淆了代码,使其难以维护,而且阻碍了现代编译器优化代码的能力。在其他领域,多年的附加功能和错误修复已经把曾经漂亮的C代码变成了可怕的维护噩梦。如果我们要考虑稳定这些库,以便能够在适当的地方更新它们,那么我们首先需要改进可维护性,否则我们将花费大量的成本来修复bug并在以后进行改进。
这个可维护性问题的“最佳”例子可以在 printf
函数族。CRT提供了142种不同的 printf
,但大多数行为对于所有函数都是相同的,因此有一组常见的实现函数来完成大部分工作。这些常见的实现函数都是在CRT源代码的output.c中定义的 (1) . 这个2696行文件有223个有条件编译的代码区域( #ifdef
, #else
等),其中超过一半是在一个单一的1400线功能。这个文件是以12种不同的方式编译的,以生成所有的通用实现函数。即使我们对这些函数进行了大量的测试,代码仍然非常脆弱,很难修改。
这不仅仅是一个理论问题。在VisualStudio2013中,我们添加了许多以前缺少的C99函数(请参见 帕特去年的博客文章 ). 然而,有许多事情我们无法实施。两个最明显的缺失特征是[1] snprintf
函数和[2]格式字符串增强,如 z
和 t
的长度修饰符 size_t
和 ptrdiff_t
类型。在产品周期的后期,我们开始考虑实现这些功能,并决定我们不能满怀信心地实现它们,因为我们没有破坏任何功能。
因此,作为CRT重构的一部分,我们已经做了大量的工作来简化和提高代码的质量,以便将来更容易添加特性和修复bug。我们已经把大部分CRT源转换成C++,使我们能够用更简单、更高级的C++构造来替换许多丑陋的C习语。当然,可公开调用的函数仍然声明为C函数( extern "C"
在C++中,它们仍然可以从C调用。但在内部,我们现在充分利用C++语言及其许多有用的特性。
通过引入几种特殊用途的智能指针和句柄类型,我们消除了代码中的大部分手动资源管理。巨大的功能被分割成更小的、可维护的部分。我们已经淘汰了75% (2) 条件编译预处理器指令的( #ifdef
, #else
通过将内部实现细节转换为使用C++特性,如模板和重载。我们已经将大多数CRT源文件转换为使用通用编码样式。
作为这项工作的一部分,我们已经完全重写了 printf
和 scanf
函数(现在没有 #ifdef
是的。这使我们能够为stdio库实现其余的C99特性,改进库中的正确性检查,并修复许多一致性错误和怪癖。同样重要的是,这项工作使我们能够发现并解决库中的大量性能问题。
在重构之前 sprintf
将格式化数据写入字符缓冲区的函数是通过将结果缓冲区包装到临时缓冲区来实现的 FILE
对象,然后遵从等价的 fprintf
功能。这是有效的,产生了正确的结果,但效率极低。将字符写入 FILE
我们需要小心处理许多情况,如缓冲区耗尽、行尾转换和字符转换。当向字符串写入字符时,我们应该能够简单地写入并递增结果指针。重构之后,我们能够很容易地发现这个性能问题,更重要的是,修复它。这个 sprintf
功能现在比以前的版本快了8倍。
这仅仅是一个例子,说明我们在哪里做了主要工作,以及这些工作如何帮助我们提高图书馆的质量。在下一篇文章中,我们将列举visualstudio“14”CTP中CRT的所有主要特性、错误修复和突破性更改,类似于 Stephan上周为STL写的东西 .
接下来呢?
我们即将完成CRT重构。毫无疑问,存在bug,我们鼓励您试用visualstudio“14”CTP,并报告在其上发现的任何bug 微软连接 . 如果您现在报告错误,那么我们很有可能在visualstudio“14”最终发布之前修复它们;感谢你们这些举报他们的人!
我们正在调查与其他图书馆进行类似稳定工作的机会。考虑到单独编译的STL组件(msvcp140.dll)也非常常用,我们正在考虑对该功能进行类似的稳定化。
注意,近期我们只考虑单独编译代码的稳定性。我们不打算对C++标准库类型或C++标题中的任何内联代码进行稳定性保证。例如,如果你通过 std::vector
对于一个函数,调用方和被调用方仍然需要使用相同的STL头和选项进行编译。有非常长期的努力,试图想出一个解决这个更普遍的问题;例如,参见萨特最近的C++标准化委员会提案。 N4028:定义一个可移植的C++ ABI .
詹姆斯·麦克内利斯(詹姆斯。mcnellis@microsoft.com ) 高级软件开发工程师Visual C++库
(1) 我们用visualstudio提供CRT的大部分源代码;您可以在VCcrtsrc下的visualstudio安装目录中找到它们。
(2) 在Visual Studio 2013中,有6830个 #if
, #ifdef
, #ifndef
, #elif
,和 #else
我们随产品提供的来源指令;在visualstudio“14”CTP中有1656个。这些数字不包括头文件中的指令,它们确实包括STL源文件,这些源文件在很大程度上不受重构工作的影响,因此这不是一个完美的度量,但它表明了已经完成的清理量。