在 gcc 上自己的元组实现段错误,同时在 clang 中工作
own tuple implementation segfaults on gcc while works in clang
作为练习,我正在尝试为 C++17 提出一个元组实现。显然,有一些可疑的东西,因为 Clang 能够正常工作,我猜,但 GCC 段错误。首先是 MVCE:
#include <utility>
#include <string>
#include <type_traits>
#include <iostream>
namespace cho {
template<std::size_t N, typename T, typename... types>
struct get_Nth_type
{
using type = typename get_Nth_type<N - 1, types...>::type;
};
template<typename T, typename... types>
struct get_Nth_type<0, T, types...>
{
using type = T;
};
template<std::size_t N, typename... Args>
using get_type = typename get_Nth_type<N, Args...>::type;
template <std::size_t I, typename T>
struct tuple_leaf {
static auto constexpr ix = I;
T elem;
};
template<class Seq, class... Ts> struct tuple_impl;
template<size_t... Ix, class... Ts>
struct tuple_impl<std::index_sequence<Ix...>, Ts...> : tuple_leaf<Ix, Ts>... { };
template<typename... Ts>
struct tuple : tuple_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...> {};
template <std::size_t I, typename... Ts>
constexpr auto& get(tuple<Ts...> const& t) {
using T = get_type<I, Ts...>;
return static_cast<tuple_leaf<I, T>&>(const_cast<tuple<Ts...>&>(t)).elem;
}
template <std::size_t I, typename Ts>
struct tuple_element;
template <std::size_t I, typename... Ts>
struct tuple_element<I, tuple<Ts...>> {
using type = get_type<I, Ts...>;
};
template <std::size_t I, typename Tuple>
using tuple_element_t = typename tuple_element<I, Tuple>::type;
template <typename... Args>
constexpr auto make_tuple(Args&&... args) {
return tuple<Args...>{std::forward<Args>(args)...};
}
template <typename Tuple, typename T, std::size_t... Ix>
auto constexpr pb_impl(Tuple const& t, T v, std::index_sequence<Ix...> const&) {
return cho::make_tuple(((void)(0), get<Ix>(t))..., v);
}
template <typename T, typename... Ts>
auto constexpr push_back(tuple<Ts...> const& t, T const& v) {
return pb_impl(t, std::move(v), std::make_index_sequence<sizeof...(Ts)>());
}
}
int main() {
auto spt = cho::push_back(cho::make_tuple(42, 3.14f), std::string("e"));
std::cout << cho::get<0>(spt) << "n";
cho::get<2>(spt) = std::string("pi");
static_assert(std::is_same_v<cho::tuple_element_t<0, decltype(spt)>, int&>);
auto st2 = cho::make_tuple(42, 3.14f);
static_assert(std::is_same_v<cho::tuple_element_t<0, decltype(st2)>, int>);
auto spt2 = cho::push_back(st2, std::string("e"));
cho::get<2>(spt2) = std::string("pi");
}
我还要说,叮当有时也会有段错误。所以,简而言之,这段代码有一些非常糟糕的地方。罪魁祸首是元组push_back
实现。我想返回一个新元组,其中包含旧元组的副本和添加到末尾的一个元素。如果你看一下第一个元组示例,我尝试打印第一个元素,我相信这就是我得到段错误的地方。
pb_impl
通过为每个元素调用get
来调用make_tuple
,这将返回对旧元组元素的引用。如果您尝试像我一样使用临时文件,这将有问题:
auto spt = cho::push_back(cho::make_tuple(42, 3.14f), std::string("e"));
在这里,push_back
将获得一个临时的元组来处理,并将引用复制到生成的元组。然后spt
将保存对已破坏内存部分的引用。我在这里做错了什么?如果我传递了一个临时元组,如何在pb_impl
中复制元组的内容?
而且,我不确定您是否能够复制,但是,为什么 Clang 大部分时间都能正常工作,打印 42 没有任何段错误?
请注意,更改此行:
return cho::make_tuple(((void)(0), get<Ix>(t))..., v);
对此
return cho::make_tuple(((void)(0), get<Ix>(t))..., std::move(v));
解决了问题,但我不明白为什么,也不知道这是否是偶然的。
这是罪魁祸首:
template <typename... Args>
constexpr auto make_tuple(Args&&... args) {
return tuple<Args...>{std::forward<Args>(args)...};
}
您要返回的元组由传递给make_tuple
的类型组成,但这些是引用。你不想要这个。您需要制作实际值的元组,并使用类似tie
函数来制作引用元组。
这是唯一理智的make_tuple
写法,这也是标准的工作方式:
中的每个Ti,VTypes中的相应类型Vi...是 std::d ecay::type 除非应用 std::d ecay 导致 std::reference_wrapper 对于某些类型 X,在这种情况下,推导出 类型为 X&。
评论太长了,但这是 -fsanitize=undefined 和 -fsanitize-address 的输出:
$ /llvm/8.0.0/bin/clang++ deleteme.cpp -fsanitize=undefined -std=c++17
$ ./a.out
42
/llvm/8.0.0/bin/../include/c++/v1/string:2317:12: runtime error: reference binding to null pointer of type 'std::__1::basic_string<char>'
$ /llvm/8.0.0/bin/clang++ deleteme.cpp -fsanitize=address -std=c++17
$ ./a.out
=================================================================
==32167==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffee3c52820 at pc 0x00010bfae2f2 bp 0x7ffee3c527b0 sp 0x7ffee3c527a8
READ of size 4 at 0x7ffee3c52820 thread T0
#0 0x10bfae2f1 in main (a.out:x86_64+0x1000012f1)
#1 0x7fff56c39014 in start (libdyld.dylib:x86_64+0x1014)
Address 0x7ffee3c52820 is located in stack of thread T0 at offset 96 in frame
#0 0x10bfade3f in main (a.out:x86_64+0x100000e3f)
This frame has 12 object(s):
[32, 56) 'spt'
[96, 104) 'ref.tmp' <== Memory access at offset 96 is inside this variable
[128, 132) 'ref.tmp1'
[144, 148) 'ref.tmp2'
[160, 184) 'ref.tmp3'
[224, 248) 'ref.tmp7'
[288, 296) 'st2'
[320, 324) 'ref.tmp12'
[336, 340) 'ref.tmp13'
[352, 376) 'spt2'
[416, 440) 'ref.tmp16'
[480, 504) 'ref.tmp19'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope (a.out:x86_64+0x1000012f1) in main
Shadow bytes around the buggy address:
0x1fffdc78a4b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4f0: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 f2
=>0x1fffdc78a500: f2 f2 f2 f2[f8]f2 f2 f2 f8 f2 f8 f2 f8 f8 f8 f2
0x1fffdc78a510: f2 f2 f2 f2 f8 f8 f8 f2 f2 f2 f2 f2 f8 f2 f2 f2
0x1fffdc78a520: f8 f2 f8 f2 f8 f8 f8 f2 f2 f2 f2 f2 f8 f8 f8 f2
0x1fffdc78a530: f2 f2 f2 f2 f8 f8 f8 f3 f3 f3 f3 f3 00 00 00 00
0x1fffdc78a540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==32167==ABORTING
Abort trap: 6
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 检查工作正常的 CXX 编译器:/cygdrive/c/cygwin64/bin/clang++ -- 已损坏
- C++指向成员的指针的类内初始化会使 MSVC 失败(但 GCC/Clang 工作)
- 在 GCC 中工作的外行构造函数模板在 Clang 中失败
- GCC 和 clang 抛出"no matching function call"但 msvc (cl) 按预期编译和工作
- 在 gcc 上自己的元组实现段错误,同时在 clang 中工作
- dynamic_cast 在 clang 上无法跨模块边界工作
- 继承默认构造函数在 gcc 中失败并在 clang 中工作,哪个有错误?
- 构造函数上的SFINAE在VC2017中工作,但在clang / gcc中不起作用
- 无限递归模板实例化使用clang时GCC工作正常
- "Typedef redefinition with different types" Clang中关于工作MSVC代码的错误
- 变量args SFINAE默认构造函数在clang中工作,但在Visual Studio 2015中失败
- Clang+VS2013:包含c++标头时出错(vs2012:正在工作)
- SFINAE 不在 llvm/clang 上工作
- cpp 模板<>使用 clang 格式 3.6 的间距无法按预期工作
- 对于Clang和Codegen的MSVC 2015,我如何让ast dump工作
- Clang 无法在 std::experimental::optional(可选)中正常工作
- 派生类的特化std::hash可以在gcc中工作,而不是在clang中
- 带有inttype的枚举类的Printf不能在clang中工作
- 如何让c++ 0x / c++ 11风格的基于范围的for循环与clang一起工作