为啥以下代码使用clang而不是gcc编译

Posted

技术标签:

【中文标题】为啥以下代码使用clang而不是gcc编译【英文标题】:Why does the following code compile using clang but not gcc为什么以下代码使用clang而不是gcc编译 【发布时间】:2020-04-22 02:02:11 【问题描述】:
#include <iostream>
#include <unordered_map>
#include <string>

struct tree_node 
  // tree_node() : attrib_val"null" 
  std::unordered_map<std::string, tree_node> child;
;
int main(int argc, char const *argv[])

  return 0;

这段代码在我的 mac 上用 clang 编译得很好:

$ g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

$ g++ -std=c++11 test.cpp
$ 

在我的 linux 机器上,使用 gcc 9.1.0,我收到以下错误:

In file included from /usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_algobase.h:64,
                 from /usr/um/gcc-9.1.0/include/c++/9.1.0/bits/char_traits.h:39,
                 from /usr/um/gcc-9.1.0/include/c++/9.1.0/ios:40,
                 from /usr/um/gcc-9.1.0/include/c++/9.1.0/ostream:38,
                 from /usr/um/gcc-9.1.0/include/c++/9.1.0/iostream:39,
                 from test.cpp:1:
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_pair.h: In instantiation of ‘struct std::pair<const std::__cxx11::basic_string<char>, tree_node>’:
/usr/um/gcc-9.1.0/include/c++/9.1.0/ext/aligned_buffer.h:91:28:   required from ‘struct __gnu_cxx::__aligned_buffer<std::pair<const std::__cxx11::basic_string<char>, tree_node> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:233:43:   required from ‘struct std::__detail::_Hash_node_value_base<std::pair<const std::__cxx11::basic_string<char>, tree_node> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:264:12:   required from ‘struct std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, tree_node>, true>’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable_policy.h:2016:13:   required from ‘struct std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, tree_node>, true> > >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/hashtable.h:173:11:   required from ‘class std::_Hashtable<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, tree_node>, std::allocator<std::pair<const std::__cxx11::basic_string<char>, tree_node> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char> >, std::hash<std::__cxx11::basic_string<char> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >’
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/unordered_map.h:105:18:   required from ‘class std::unordered_map<std::__cxx11::basic_string<char>, tree_node>’
test.cpp:7:46:   required from here
/usr/um/gcc-9.1.0/include/c++/9.1.0/bits/stl_pair.h:215:11: error: ‘std::pair<_T1, _T2>::second’ has incomplete type
  215 |       _T2 second;                /// @c second is a copy of the second object
      |           ^~~~~~
test.cpp:5:8: note: forward declaration of ‘struct tree_node’
    5 | struct tree_node 

由于某种原因,它不喜欢 tree_node 作为 unordered_map 中的值。

【问题讨论】:

你确定这是在 clang 上编译的吗?不应该因为 tree_node 在声明 child 时是不完整的类型 它 doesn't 在 clang 10.0 上编译。 @cigien 是的。我刚刚用我的终端输出编辑了 OP。 @cigien 另外,我不知道不完整的类型会造成问题。我在其他代码中也做过几次,不记得看到过意外行为。 您尝试过其他标准吗?如果你 man gcc 有 7 个或 8 个,你可以试试。它非常灵活。 【参考方案1】:

这是未定义的行为,[res.on.functions]/2.5

[如果]在实例化模板组件或评估概念时将不完整的类型 ([basic.types]) 用作模板参数,则效果未定义,除非该组件特别允许。

这是一个烦人的情况,我基本上必须证明否定的答案才能有效,但我发现标准中没有提到允许您在 std::map 中使用不完整类型的异常的地方。因此,这个程序可以做任何事情。特别是,虽然 Clang 现在编译它,但它可能在未来的任何时候停止工作,而且编译后的代码 map 特化不能正常工作也有可能。 某些容器,尤其是std::vector,有一个子句允许它们在正确的条件下以不完整的类型实例化。但是这种情况是未定义的行为,因此编译器没有义务警告您或错误。以某种方式更改您的程序以避免这种情况。我相信以下内容是合法的,不会强迫您存储太多额外的指针。

struct tree_node 
  std::unique_ptr<std::unordered_map<std::string, tree_node>> child;
;

std::unique_ptr 是一般禁止规则的一个例外——可以将其实例化为不完整的类型(但它的一些成员并没有那么松懈)。我相信这意味着在tree_node 的定义中tree_node 的特化需要完成的点上,std::unordered_map&lt;std::string, tree_node&gt; 不需要是完整的,因此std::unordered_map 特化不会被触发并且UB被避免,因为tree_node 不需要完整。请注意,您仍然可以毫无顾虑地编写构造函数、函数、析构函数等,因为所有这些定义都隐式移出类定义并在类定义之后,并且tree_node 在类定义结束后变得完整。

【讨论】:

是的,你是对的,在这种情况下是 UB。很好的答案:) 使child 成为unique_ptr 而不是子元素的第二个值unique_ptr 有什么好处,即std::unordered_map<:string std::unique_ptr tree_node>>> 孩子?如果没有更多关于我正在使用这个结构做什么的信息,不确定这个问题是否可以回答。 老实说,一个猜测。 unordered_map 已经将其所有数据存储在堆上,位于指针后面。将tree_node 设为值类型意味着它可以将tree_nodes 直接存储在map 结构中,而不必经过更多的间接访问。这不应该被添加在结构顶部的“初始”指针完全抵消。例如。如果您对map 进行循环,我的建议希望意味着您只需取消引用“无关的”unique_ptr 一次,而您的建议绝对意味着您必须为每个元素取消引用它。 另外,我想界面更好。特别是,*child 是一个std::unordered_map&lt;std::string, node_type&gt; 左值,这在概念上是目标。将指针放在里面会使“不可能”。【参考方案2】:

变化:

struct tree_node 
  // tree_node() : attrib_val"null" 
  std::unordered_map<std::string, tree_node> child;
;

收件人:

struct tree_node 
  // tree_node() : attrib_val"null" 
  std::unordered_map<std::string, tree_node *> child;
;

使编译问题消失。它是否对你想要的有任何反映?

【讨论】:

这是一个简单的类型转发,不是 OP 要求的。 OP 询问为什么 clang 编译代码而 g++ 没有。上面的更改允许编译。潜在的问题可能是系统之间的地图实现不同;正如在 linux 上安装 clang 所证明的那样,并注意到它有同样的问题(如果稍微不那么精神病错误雪崩)。 也就是说,macos 实现解析为 tree_node 内不需要定义 tree_node 的结构(即使用指针);因此可以编译;而 gnu/linux 需要一个已解析的结构。它不是转发;只是指针类型不需要结构定义;只是标签。

以上是关于为啥以下代码使用clang而不是gcc编译的主要内容,如果未能解决你的问题,请参考以下文章

在 MacOS 上使用 gcc-8 而不是 clang 编译 Qt5

为啥在编译期间不使用 GCC 选项 -Os?

为啥这个结构文字在 VS2013 中通过地址而不是 gcc/clang 时会损坏?

强制mac使用GCC,而不是clang

为啥 clang 和 gcc 在这个虚拟继承代码上存在分歧?

使用 clang 而不是 gcc 安装犰狳