在clang中工作时在gcc上拥有自己的元组实现segfaults

时间:2019-03-28 18:57:14

标签: c++

作为练习,我试图为C ++ 17提出一个元组实现。显然,有些麻烦,因为我猜Clang能够正常工作,但是GCC segfaults。首先是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");
} 

我也要说Clang有时也会出现段错误。简而言之,这段代码确实有一些不好的地方。罪魁祸首是元组的push_back实现。我想返回一个新的元组,其中包含旧元组的副本和最后添加的一个元素。我相信,如果您看一下第一个元组示例,我尝试在其中打印第一个元素,那就是我得到的段错误。

pb_impl通过为每个元素调用make_tuple来调用get,这将返回对旧元组元素的引用。然后,如果您尝试像我在以下方法中那样使用临时工,就会出现问题:

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));

解决了问题,但我不明白为什么,也不知道这是否偶然。

2 个答案:

答案 0 :(得分:5)

这是罪魁祸首:

template <typename... Args>
constexpr auto make_tuple(Args&&... args) {
    return tuple<Args...>{std::forward<Args>(args)...};
}

您要返回的元组由传递给make_tuple的类型组成,但这些都是引用。您不想要这个。您需要创建实际值的元组,并使用类似tie的函数来创建引用元组。

这是写make_tuple的唯一明智的方法,也是standard one works的写法:

  

对于类型...中的每个Ti,在VTypes ...中对应的Vi类型为   std :: decay :: type,除非应用std :: decay导致   某些X类型的std :: reference_wrapper,在这种情况下推导   类型是X&。

答案 1 :(得分:1)

评论太长,但这是-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