软件|VS 17.5 预览版2:/Gw链接开关的标准一致性改进

软件|VS 17.5 预览版2:/Gw链接开关的标准一致性改进

文章图片

软件|VS 17.5 预览版2:/Gw链接开关的标准一致性改进

文章图片

软件|VS 17.5 预览版2:/Gw链接开关的标准一致性改进

文章图片

软件|VS 17.5 预览版2:/Gw链接开关的标准一致性改进

文章图片


/Gw 链接开关可以告诉链接器优化代码中的全局数据 , 从而减小最终生成的二进制文件的大小 。 在 Visual Studio 17.5 预览版2中 , 我们新增了一个新的标志:/Zc:checkGwOdr[-
, 目的是在使用 /Gw 开关的时候改进对 C++ 标准的一致性支持 。
在之前的版本中 , 当使用 /Gw 开关的时候 , 某些单定义规则(ODR)冲突会被忽略 , 并且不会导致错误 。 有了这个新标志之后 , 如果发生这种情况的时候 , VS 将会报告错误 。 如果你正在使用 /Gw , 则我们推荐使用 /Zc:checkGwOdr 这个标志 。 默认情况下 , 这个标志是被关闭的 。 在将来的新版本 VS 中 , 我们可以改变这个默认设置 。
如果你想了解关于ODR的解释 , /Gw 开关 , 以及有关这个问题的更多信息 , 请继续阅读 。
让我们来看看下面三个定义 , 先建立对这个问题的基本背景知识:
1. 首先是 COMDAT , 简短的描述是 COMDAT 是可以放置数据的额外段 , 以使链接器能够潜在地从二进制文件中折叠出所述数据 。 重要的是 , 这些部分标有如何处理重复项的策略 。 有关 COMDAT 历史的更深入探讨 , 请参阅 Raymond 的博客文章 , 其中涵盖了它们的使用和历史 。
2. 接下来 , /Gw 开关是做什么的?该开关使编译器能够将全局数据放入 COMDAT 中 。 这使我们能够优化未引用的全局变量 , 或通过它们的 COMDAT 部分合并相同的全局变量 。
3. 关于单定义规则(ODR) , 可以在网上找找相关的信息 。
有了上面的背景知识 , 下面我们来看一个简单的例子:

如果不使用 /Gw 开关 , 编译上面的代码会产生如下的编译错误:

由于我们在 odr.h 中定义了 MyGlobal , 因此我们最终在 foo.obj 和 bar.obj 中都有一个定义 , 导致链接器报告 ODR 违规 。 现在 , 如果我们使用 /Gw 编译:

我们最终没有出现错误 , 那么发生了什么?它最终回到上面提到的 COMDAT 标志 。 查看 obj 的头文件 , 我们可以看到 MyGlobal 确实被放置在 COMDAT 中:

这没有显示的是这个COMODAT已被标记为PICKANY 。 因此 , 当找到多个可以合并的候选定义时 , 链接器会任意选择其中一个并丢弃其余定义 。 不过这很奇怪 , 当启用 /Gw 时 , 此 COMDAT 在创建时被标记为 NOMATCH 。 NOMATCH , 顾名思义 , 意味着如果找到重复项 , 链接器应该引发错误 , 这正是我们想要的 。 那么 , 出了什么问题呢?
这里的关键是MyGlobal的定义包括零赋值 。 这会导致另一个优化启动 。 由于此全局初始化为零 , 我们注意到它可以移动到 .bss 部分 。 由于如果此全局数据位于 .bss 中 , 则不必存储此全局数据 , 因此移动 COMDAT 可以减小对象文件大小 。 不幸的是 , 当我们移动COMCTAT时 , 标志从NOMATCH重置为PICKANY , 导致我们的错误 。
从 17.5 预览版 2 开始 , 你现在可以使用新标志来确保不会在意外使用 /Gw 时隐藏这些 ODR 违规:

暴露错误后 , 我们可以在头文件中创建全局 extern , 并将定义移动到其中一个 cpp 文件以解决问题:

或者 , 对于 C++17 及更高版本 , 可以在定义上使用内联说明符 (inline specifier)。