潜在的升级问题概述 (Visual C++)
多年来,Microsoft C++ 编译器已历经多次更改,而 C++ 语言本身、C++ 标准库、C 运行时 (CRT) 以及其他库(如 MFC 和 ATL)亦然。 因此,在从 Visual Studio 的早期版本升级应用程序时,可能遇到编译器和链接器错误,以及先前已完全编译的代码中的警告。 原始基本代码越早,发生此类错误的可能性越大。 本概述总结了可能遇到的最常见类型的问题,并提供有关详细信息的链接。
注意
在过去,我们建议要以增量方式执行跨多版本 Visual Studio 的升级,一次执行一个版本。 现在我们不再推荐这种方法。 我们发现,无论基本代码有多早,采用升级至最新版本 Visual Studio 的方法几乎总是更简单。
可将有关升级过程的问题或评论发送至 vcupgrade@microsoft.com。
库和工具集依赖项
注意
本部分适用于使用 Visual Studio 2013 及更低版本生成的应用程序和库。 Visual Studio 2015、Visual Studio 2017 和 Visual Studio 2019 中使用的工具集是二进制兼容的。 有关详细信息,请参阅 Visual Studio 版本之间的 C++ 二进制兼容性。
将应用从 Visual Studio 2013 或更低版本升级到更高版本时,通常建议且有必要升级该应用链接到的所有库和 DLL。 这要求你拥有访问源代码的权限,或库供应商提供使用相同的主版本编译器进行编译的新二进制文件。 若下列条件之一为 true,则可跳过此部分,此部分介绍有关二进制兼容性的详细信息。 如果不存在任一情况,则这些库可能无法在已升级的应用中运行。 此部分的信息将帮助你了解是否可以继续进行升级。
工具集
.obj
和 .lib
文件格式都定义完善且很少更改。 有时会对这些文件格式进行添加,但这些添加通常不会影响较新工具集使用由较旧工具集生成的对象文件和库的能力。 但如果使用 /GL
(全程序优化)进行编译,则是大型例外情况。 如果使用 /GL
进行编译,则生成的对象文件只能使用生成它时所用的同一工具集进行链接。 因此,如果使用 /GL
和 Visual Studio 2017 (v141) 编译器生成对象文件,则必须使用 Visual Studio 2017 (v141) 链接器对其进行链接。 这是因为对象文件中的内部数据结构在工具集的主要版本中不稳定。 较新的工具集不了解较旧的数据格式。
C++ 不具备稳定的应用程序二进制接口 (ABI)。 但是,Visual Studio 为版本的所有次要版本维护稳定的 C++ ABI。 Visual Studio 2015 (v140)、Visual Studio 2017 (v141)、Visual Studio 2019 (v142) 和 Visual Studio 2022 (v143) 工具集仅在其次要版本中有所不同。 它们都具有相同的主版本号,即 14。 有关详细信息,请参阅 Visual Studio 版本之间的 C++ 二进制兼容性。
如果对象文件具有包含 C++ 链接的外部符号,则该对象文件可能无法与其他主版本工具集生成的对象文件正确链接。 有许多可能的结果:链接可能会完全失败(例如,如果名称修饰已更改)。 链接可能会成功,但应用可能会在运行时失败(例如,如果类型布局更改)。 或者应用可能会继续工作并且不会出错。 另请注意,尽管 C++ ABI 不稳定,但 COM 所需的 C ABI 和 C++ ABI 子集是稳定的。
如果链接到导入库,保留 ABI 兼容性的 Visual Studio 可再发行库的更高版本可在运行时使用。 例如,如果使用 Visual Studio 2015 Update 3 工具集编译和链接应用,则可以使用任何更高版本的可再发行组件。 这是因为 2015、2017、2019 和 2022 库保留了二进制后向兼容性。 反之则不然:可再发行组件不可用于较低版本的工具集,而只能用于生成代码的任何组件。
库
如果 #include
特定版本的头文件,则必须将生成的对象文件链接到相同版本的库。 因此,举例而言,如果源文件包含 Visual Studio 2015 Update 3 <immintrin.h>
,则必须链接到 Visual Studio 2015 Update 3 vcruntime
库。 同样地,如果源文件包含 Visual Studio 2017 版本 15.5 <iostream>
,则必须使用 Visual Studio 2017 版本 15.5 标准 C++ 库 msvcprt
进行链接。 不支持混合与匹配。
对于 C++ 标准库,从 Visual Studio 2010 起,通过在标准标头中使用 #pragma detect_mismatch
显示禁止混合与匹配。 如果尝试链接不兼容的对象文件,或尝试与错误的标准库链接,链接将失败。
从不支持旧 CRT 版本的混合与匹配,但通常可以这样做,因为 API 外围应用不会随时间发生太大的变化。 通用 CRT 中断了后向兼容性,以便我们将来维护向后兼容性。 将来我们不打算引入新的、已进行版本管理的通用 CRT 二进制文件。 现有的通用 CRT 已更新就绪。
为了提供与使用早期版本的 Microsoft C 运行时标头编译的对象文件(和库)的部分链接兼容性,我们提供库 legacy_stdio_definitions.lib
,用于 Visual Studio 2015 及更高版本。 此库为大部分从通用 CRT 删除的函数和数据导出提供兼容性符号。 legacy_stdio_definitions.lib
提供的兼容性符号集足以满足大多数依赖项,包括 Windows SDK 所包含的库中的所有依赖项。 但是,某些没有兼容性符号的符号已从 Universal CRT 中删除。 这些符号包括某些函数(例如 __iob_func
)和某些数据导出(例如 __imp___iob
、__imp___pctype
、__imp___mb_cur_max
)。
如果静态库由较旧版本的 C 运行时标头生成,建议按以下顺序采取下列操作:
使用新版本的 Visual Studio 和通用 CRT 标头重新生成静态库,以支持与通用 CRT 的链接。 此方法是完全受支持的选项,也是最佳选项。
如果无法(或不希望)重新生成静态库,则可以尝试使用
legacy_stdio_definitions.lib
进行链接。 如果它满足静态库的链接时依赖关系,则你需要全面测试静态库在二进制文件中的使用。 确保它不会受到对通用 CRT 所做的任何行为更改的负面影响。legacy_stdio_definitions.lib
可能不满足静态库的依赖关系,或者由于行为更改,该库无法与通用 CRT 一起使用。 在这种情况下,我们建议将静态库封装在某个 DLL 中,并与所需版本的 Microsoft C 运行时相链接。 例如,如果静态库是使用 Visual Studio 2013 生成的,则也可以使用 Visual Studio 2013 工具集和 C++ 库来生成此 DLL。 通过将库构建到 DLL 中,可封装实现的详细信息(此详细信息是在特定版本 Microsoft C 运行时上的它的依赖项)。 请注意,DLL 接口不会泄露有关它使用哪个 C 运行时的详细信息,例如,如果它返回一个跨越 DLL 边界的FILE*
,或者调用方必须free
分配的malloc
指针。
在单个进程中使用多个 CRT 本身并没有问题。 (事实上,大多数进程会加载多个 CRT DLL。例如,Windows 操作系统组件依赖于 msvcrt.dll
,而 CLR 依赖于它自己的专用 CRT。)将不同 CRT 的状态混合时,就会显示问题。 例如,不应使用 msvcr110.dll!malloc
分配内存并尝试使用 msvcr120.dll!free
释放该内存,并且不应尝试使用 msvcr110!fopen
打开文件并尝试使用 msvcr120!fread
从该文件中读取。 只要不混合不同 CRT 中的状态,就能安全地在单个进程中加载多个 CRT。
有关详细信息,请参阅将代码升级到通用 CRT。
项目设置导致的错误
若要开始升级进程,请在最新版本的 Visual Studio 中打开旧的 project/solution/workspace。 Visual Studio 将基于旧的项目设置创建新项目。 检查旧项目是否具有库路径或包含硬编码到非标准位置的路径。 当项目使用默认设置时,编译器可能看不到这些路径中的文件。 有关详细信息,请参阅链接器 OutputFile 设置。
一般情况下,现在是组织项目代码的绝佳时机,以便简化项目维护,并尽可能加快已升级代码的生成。 如果已恰当组织源代码,且在 Visual Studio 2010 或更高版本中对旧项目进行编译,则可手动编辑新项目文件,以支持在旧编译器和新编译器上进行编译。 下面的示例演示了如何为 Visual Studio 2015 和 Visual Studio 2017 进行编译:
<PlatformToolset Condition="'$(VisualStudioVersion)'=='14.0'">v140</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)'=='15.0'">v141</PlatformToolset>
LNK2019:无法解析的外部符号
对于无法解析的符号,可能需要修复项目设置。
如果源文件位于非默认位置,你是否将路径添加到了项目的包含目录?
如果在
.lib
文件中定义了外部符号,你是否在项目属性中指定了 lib 路径?正确版本的.lib
文件是否位于此处?是否尝试链接到由其他版本的 Visual Studio 编译的
.lib
文件? 如果是,请参阅前面部分中关于库和工具集依赖项的内容。调用站点处的参数类型是否匹配现有的函数重载? 验证函数签名和调用函数的代码中的 typedef 的基础类型是否是你需要的类型。
若要解决无法解析的符号错误,可以尝试使用 dumpbin.exe
来检查二进制文件中定义的符号。 请尝试使用下面的命令行来查看在库中定义的符号:
dumpbin.exe /LINKERMEMBER somelibrary.lib
/Zc:wchar_t
(wchar_t
是本机类型)
(在 Microsoft Visual C++ 6.0 和更低版本中,wchar_t
未实现为内置类型。它在 wchar.h
中声明为 unsigned short
的 typedef。)C++ 标准要求 wchar_t
是内置类型。 使用 typedef 版本可能导致可移植性问题。 如果从早期版本的 Visual Studio 升级并遇到编译器错误 C2664(因为代码尝试将 wchar_t
隐式转换为 unsigned short
),则建议更改代码来修正错误,而不是设置 /Zc:wchar_t-
。 有关详细信息,请参阅 /Zc:wchar_t
(wchar_t 是本机类型)。
使用链接器选项 /NODEFAULTLIB
、/ENTRY
和 /NOENTRY
进行升级
通过 /NODEFAULTLIB
链接器选项(或“忽略所有默认库”链接器属性),可使链接器不在 CRT 等默认库中自动链接。 这意味着,每个库都必须作为输入单独列出。 “项目属性”对话框的“链接器”部分中的“附加依赖项”属性中,提供了此库列表。
在升级过程中,使用此选项的项目会出现问题,因为某些默认库的内容已重构。 因为每个库都需要在“附加依赖项”属性或链接器命令行中列出,所以需要使用所有当前名称来更新库列表。
下表显示自 Studio 2015 开始内容已更改的库。 若要升级,需要将第二列中的新库名称添加到第一列的库中。 其中有些库是导入库,但这应该没有什么影响。
如果你使用的是: | 需要使用以下库: |
---|---|
libcmt.lib |
libcmt.lib , libucrt.lib , libvcruntime.lib |
libcmtd.lib |
libcmtd.lib , libucrtd.lib , libvcruntimed.lib |
msvcrt.lib |
msvcrt.lib , ucrt.lib , vcruntime.lib |
msvcrtd.lib |
msvcrtd.lib , ucrtd.lib , vcruntimed.lib |
使用 /ENTRY
选项或 /NOENTRY
选项也会出现此问题,这两个选项也有绕过默认库的效果。
改进的语言一致性导致的错误
多年来,Microsoft C++ 编译器一直不断改进针对 C++ 标准的符合性。 在早期版本中编译的代码可能无法在较高版本的 Visual Studio 中编译。 这是因为编译器会正确地标记它之前已忽略或显式允许的错误。
例如,早期版本的 MSVC 引入了 /Zc:forScope
开关。 它允许不符合循环变量的行为。 现已弃用该开关,并可能在将来版本中将其删除。 我们强烈建议在升级代码时不要使用该开关。 有关详细信息,请参阅 /Zc:forScope-
已弃用。
升级时一个常见的编译器错误是将非常数参数传递到常数参数。 旧版本的编译器并不总是将其标记为错误。 有关详细信息,请参阅编译器的更严格转换。
有关特定符合性改进的详细信息,请参阅 Visual C++ 更改历史记录 2003 - 2015 以及 Visual Studio 中 C++ 的符合性改进。
<stdint.h>
整数类型相关的错误
<stdint.h>
标头定义了 typedef 和宏,它们保证在所有平台上具有指定长度,这与内置整型类型不同。 一些示例包括 uint32_t
和 int64_t
。 <stdint.h>
标头已添加到 Visual Studio 2010 中。 2010 之前编写的代码可能已经为这些类型提供了专用定义。 此外,这些定义可能并不始终与 <stdint.h>
定义一致。
如果错误为 C2371 且涉及 stdint
类型,这可能意味着该类型是在代码或第三方库文件中的标头中定义的。 升级时,应消除 <stdint.h>
类型的任何自定义定义,但应先将自定义定义与当前标准定义进行对比,确保不会引入新问题。
可按 F12(转到定义)查看有所述类型的定义位置。
/showIncludes
编译器选项在此处很有用。 在项目的“属性页”对话框中,选择“配置属性”>“C/C++”>“高级”页,并将“显示 Include 文件”设置为“是”。 然后重新生成项目。 输出窗口中会显示 #include
文件的列表。 每个标头在包含它的标头下都是缩进的。
涉及 CRT 函数的错误
多年来,针对 C 运行时进行了许多更改。 已添加函数的许多安全版本,也删除了函数的一些安全版本。 此外,如前文所述,Microsoft 的 CRT 实现在 Visual Studio 2015 中重构为新的二进制文件和相关的 .lib
文件。
如果错误涉及 CRT 函数,请搜索 Visual C++ 更改历史记录 (2003 - 2015) 或 Visual Studio 中 C++ 的符合性改进,查阅这些文章中是否包含任何附加信息。 如果错误为 LNK2019,请确保未删除该函数。 否则,如果你确认该函数仍存在,且调用代码正确,请检查项目是否使用了 /NODEFAULTLIB
。 如果是,则需要更新库列表以使用新的通用 (UCRT) 库。 有关详细信息,请参阅以上有关库和依赖项的部分。
如果错误涉及 printf
或 scanf
,请确保未在不包含 stdio.h
的情况下私下定义这两种函数中的任意一种。 如果是这样,请删除专用定义或链接到 legacy_stdio_definitions.lib
。 可以在“附加依赖项”属性中的“配置属性”>“链接器”>“输入”下的“属性页”对话框中对此库进行设置。 如果链接到 Windows SDK 8.1 或更低版本,请添加 legacy_stdio_definitions.lib
。
如果错误涉及格式字符串参数,这可能是由于编译器在强制执行标准方面更严格。 有关详细信息,请参阅更改历史记录。 请密切注意此处的任何错误,因为它们可能表示存在安全风险。
C++ 标准的更改导致的错误
C++ 标准发展的方式并不总是后向兼容。 C++11 引入了移动语义、新关键字以及其他语言和标准库功能。 这些更改可能会导致编译器错误,甚至导致不同的运行时行为。
例如,旧 C++ 程序可能包含 iostream.h
标头。 此标头已在早期版本的 C++ 中弃用,且最终从 Visual Studio 中彻底删除。 在这种情况下,需要使用 <iostream>
并重新编写代码。 有关详细信息,请参阅更新旧的 iostream
代码。
C4838:收缩转换警告
目前根据 C++ 标准,将无符号整型值转换为带符号整型值为收缩转换。 Visual Studio 2015 之前的编译器不会引发此警告。 检查每个事件,以确保缩短不会影响代码的正确性。
使用安全 CRT 函数的警告
多年来,已引入 C 运行时函数的多个安全版本。 尽管不安全的旧版本仍然可用,但建议更改代码以使用安全版本。 编译器将对使用不安全版本的行为发出警告。 可选择禁用或忽略这些警告。 要为解决方案中的所有项目禁用警告,请打开“视图”>“属性管理器”,选择要禁用警告的所有项目,然后右键单击所选项,并选择“属性”。 在“配置属性”>“C/C++”>“高级”下的“属性页”中,选择“禁用特定警告”。 选择下拉箭头,然后选择“编辑”。 在文本框中输入 4996。 (不要包含“C”前缀。)有关详细信息,请参阅移植以使用安全 CRT。
Windows API 或已过时 SDK 的更改导致的错误
多年来,已添加若干 Windows API 和数据类型,有时还会对其进行更改或删除。 此外,不属于核心操作系统的其他 SDK 会不时地添加或删除。 较旧的程序可能包含对已不存在的 API 的调用。 它们还可能包含对已不受支持的其它 Microsoft SDK 中 API 的调用。 你可能会看到有关缺少 Windows API 或旧版 Microsoft SDK 中的 API 的错误。 API 有可能被删除,或者由更新、更安全的功能取代。
Windows API 文档列出了支持的最低或最高操作系统版本。 有关特定 Windows API 的信息,请在桌面 Windows 应用程序的 API 索引中查找。
Windows 版本
在升级直接或间接使用 Windows API 的程序时,需要决定要支持的 Windows 最低版本。 在大多数情况下,Windows 7 是一个不错的选择。 有关详细信息,请参阅头文件问题。 WINVER
宏定义了设计用于运行程序的 Windows 旧版本。 若 MFC 程序将 WINVER
设置为 0x0501 (Windows XP),用户将收到警告,因为虽然编译器工具集本身具有 XP 模式,但 MFC 已不再支持 XP。 Visual Studio 2017 中已终止对 Windows XP 的编译器工具集支持。
有关详细信息,请参阅更新目标 Windows 版本和更多过时的头文件。
ATL / MFC
ATL 和 MFC 是相对稳定的 API,但偶尔对其进行更改。 有关详细信息,请参阅 Visual C++ 更改历史记 (2003 - 2015)、Visual Studio 中 Visual C++ 的新增功能以及 Visual Studio 中 C++ 的符合性改进。
已在 MSVCRTD.lib 中定义 LNK 2005 _DllMain@12
MFC 应用程序中可能发生此错误。 它指示 CRT 库和 MFC 库之间的顺序问题。 必须首先链接 MFC,以便提供 new
和 delete
运算符。 若要修复此错误,请使用 /NODEFAULTLIB
开关忽略以下默认库:MSVCRTD.lib
和 mfcs140d.lib
。 然后将这些相同的库添加为附加依赖项。
32 位和 64 位
如果原始代码针对 32 位系统编译,除了新建 32 位应用外,还可以选择创建 64 位版本。 一般情况下,应首先在 32 位模式下编译程序,然后再尝试使用 64 位模式。 针对 64 位进行编译是直截了当的方法,但在某些情况下,这可能暴露出 32 位版本中隐藏的一些 Bug。
此外,还应注意可能的编译时和运行时问题,这些问题与 printf
和 scanf
函数中的指针大小、时间和大小值以及大小特定的格式说明符相关。 有关详细信息,请参阅针对 64 位 x64 目标配置 Visual C++ 和 Visual C++ 64 位迁移的常见问题。 有关迁移的其他提示,请参阅适用于 64 位 Windows 的编程指南。
Unicode 和 MBCS/ASCII
在标准化 Unicode 之前,许多程序使用多字节字符集 (MBCS) 来表示未包含在 ASCII 字符集中的字符。 在较旧的 MFC 项目中,MBCS 是默认设置。 升级此类程序时,会看到建议改用 Unicode 的警告。 如果你认为因开发成本的原因而不值得转换至 Unicode,可以选择禁用或忽略此警告。 要为解决方案中的所有项目禁用警告,请打开“视图”>“属性管理器”,选择要禁用警告的所有项目,然后右键单击所选项,并选择“属性”。 在“属性页”对话框中,选择“配置属性”>“C/C++”>“高级”。 在“禁用特定警告”属性中展开下拉箭头,然后选择“编辑”。 在文本框中输入 4996。 (不要包含“C”前缀。)选择“确定”保存属性,然后选择“确定”保存更改。
有关详细信息,请参阅从 MBCS 移植到 Unicode。 有关 MBCS 与 Unicode 的一般信息,请参阅 Visual C++ 中的文本和字符串以及国际化。