【问题标题】:Why does I/O related setting cause segmentation fault?为什么 I/O 相关设置会导致分段错误?
【发布时间】:2021-02-18 17:10:41
【问题描述】:

这是一个非常奇怪的错误,我不明白为什么会这样。

我正在通过编写一些通用 I/O 读取器/写入器实现来练习 SFINAE 概念。在我的本地机器上,一切正常,没有任何分段错误。但是,当我尝试在Wandbox 或其他一些在线编译器站点上测试相同的代码时,我收到SIGABRTSegmentation fault 错误和Runtime error: exit code is 2147483647 之类的东西。我在本地机器上通过 gdb 运行我的代码,我没有收到任何信号或分段错误。我确保我使用的所有编译标志在我的本地设备和在线编译器上都是相同的。我已经使用 GCC 9.2.0 和 GCC 10.2.0 进行了测试,但在这两种情况下,只有在线编译器会出现问题。

我正在测试导致分段错误的代码:

#include <iostream>
#include <vector>
#include <tuple>
#include <utility>
#include <type_traits>

namespace io::utility {

    template <typename T, typename = void>
    struct is_istream_streamble
        : std::false_type
    { };

    template <typename T>
    struct is_istream_streamble <T, std::void_t <decltype(std::cin >> std::declval <T&> ())>>
        : std::true_type
    { };

    template <typename T>
    constexpr bool is_istream_streamble_v = is_istream_streamble <T>::value;

    template <typename T, typename = void>
    struct is_ostream_streamble
        : std::false_type
    { };

    template <typename T>
    struct is_ostream_streamble <T, std::void_t <decltype(std::cerr << std::declval <T> ())>>
        : std::true_type
    { };

    template <typename T>
    constexpr bool is_ostream_streamble_v = is_ostream_streamble <T>::value;

    template <typename T, typename = void>
    struct has_begin_iterator
        : std::false_type
    { };

    template <typename T>
    struct has_begin_iterator <T, std::void_t <decltype(std::declval <T> ().begin())>>
        : std::true_type
    { };

    template <typename T>
    constexpr bool has_begin_iterator_v = has_begin_iterator <T>::value;

    template <typename T, typename = void>
    struct has_end_iterator
        : std::false_type
    { };

    template <typename T>
    struct has_end_iterator <T, std::void_t <decltype(std::declval <T> ().end())>>
        : std::true_type
    { };

    template <typename T>
    constexpr bool has_end_iterator_v = has_end_iterator <T>::value;
}

namespace io {

    class reader {
      private:
        std::istream& stream;
      public:
        reader (std::istream& stream = std::cin) : stream (stream) { }

        template <typename T>
        std::enable_if_t <utility::is_ostream_streamble_v <T>, reader&>
        operator () (T& object)
        { return stream >> object, *this; }

        template <typename T>
        std::enable_if_t <!utility::is_ostream_streamble_v <T> && utility::has_begin_iterator_v <T> && utility::has_end_iterator_v <T>, reader&>
        operator () (T& object) {
            for (auto& element : object)
                (*this)(element);
            return *this;
        }

        template <typename A, typename B>
        reader& operator () (std::pair <A, B>& pair)
        { return (*this)(pair.first, pair.second); }

        template <size_t N, typename...T>
        std::enable_if_t <(sizeof...(T) <= N), reader&>
        operator () (std::tuple <T...>&)
        { return *this; }

        template <size_t N, typename...T>
        std::enable_if_t <(sizeof...(T) > N), reader&>
        operator () (std::tuple <T...>& tuple)
        { return (*this)(std::get <N> (tuple)), operator () <N + 1, T...> (tuple); }

        template <typename...T>
        reader& operator () (std::tuple <T...>& tuple)
        { return operator () <0, T...> (tuple); }

        reader& operator () ()
        { return *this; }

        template <typename A, typename...B>
        reader& operator () (A& object, B&...objects)
        { return (*this)(object)(objects...); }
    };

    class writer {
      private:
        std::ostream& stream;
      public:
        writer (std::ostream& stream = std::cerr) : stream (stream) { }

        template <typename T>
        std::enable_if_t <utility::is_ostream_streamble_v <T>, writer&>
        operator () (const T& object)
        { return stream << object, *this; }

        template <typename T>
        std::enable_if_t <!utility::is_ostream_streamble_v <T> && utility::has_begin_iterator_v <T> && utility::has_end_iterator_v <T>, writer&>
        operator () (const T& object) {
            for (const auto& element : object)
                (*this)(element, ' ');
            return *this;
        }

        template <typename A, typename B>
        writer& operator () (const std::pair <A, B>& pair)
        { return (*this)(pair.first, " ", pair.second); }

        template <size_t N, typename...T>
        std::enable_if_t <(sizeof...(T) <= N), writer&>
        operator () (const std::tuple <T...>&)
        { return *this; }

        template <size_t N, typename...T>
        std::enable_if_t <(sizeof...(T) > N), writer&>
        operator () (const std::tuple <T...>& tuple)
        { return (*this)(std::get <N> (tuple), ' '), operator () <N + 1, T...> (tuple); }

        template <typename...T>
        writer& operator () (const std::tuple <T...>& tuple)
        { return operator () <0, T...> (tuple); }

        writer& operator () ()
        { return *this; }

        template <typename A, typename...B>
        writer& operator () (const A& object, const B&...objects)
        { return (*this)(object)(objects...); }
    };
}

using reader = io::reader;
using writer = io::writer;
reader read  (std::cin);
writer write (std::cout);

auto main () -> int32_t {

    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.precision(10);
    std::cerr.precision(10);
    std::cout << std::fixed << std::boolalpha;
    std::cerr << std::fixed << std::boolalpha;

    int n;
    read(n);
    std::vector <int> a (n);
    read(a);
    write(a);

    return 0;
}

令人惊讶的是,只需删除 std::ios::sync_with_stdio(false); 就可以使一切顺利进行,在线编译器上不会出现任何错误。我花了一个小时尝试愚蠢的事情来弄清楚这一点。但我不明白为什么会这样。保持 stdio 同步如何解决我的问题?非常感谢您的解释。

【问题讨论】:

  • 你给你的程序提供什么输入?调试器显示什么?请添加所有需要的#includes - 猜测它们并不好玩。 auto main () -&gt; int32_t ?为什么int32_t
  • 未定义行为的一个有趣的方面是,对代码进行看似无关的更改会导致症状消失。问题不太可能出现在sync_with_stdio(false) 电话中。然而,删除调用很可能会改变整个程序的内存组织 - 并且未定义行为的症状也会改变。
  • 那个需要引用。我找不到任何reference to that style。您的返回类型与预期不符,我不确定这与传统的 int main() 相比有什么优势,尤其是考虑到它更加冗长。有那么一刻,我以为这是 Rust,但它是 C++。
  • @LostArrow - main() 需要返回 intint 不需要是 int32_t
  • auto main () -&gt; int32_t 是错误的。语言要求它是int main()int main(int argc, char** argv)(尽管您可以根据需要更改参数名称)。

标签: c++ segmentation-fault c++17


【解决方案1】:

一般来说,你在命名全局变量时运气不好。这可能是我想出的最好的 MCVE:

#include <iostream>
struct W { W(int) {} };
W write(0);
int main() {
    std::ios::sync_with_stdio(false);
    std::cout << "b" << std::endl;
    return 0;
}

使用g++ -O0 -ggdb3 -fsanitize=address ./1.cpp &amp;&amp; ./a.out 编译该代码会导致:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==156688==ERROR: AddressSanitizer: SEGV on unknown address 0x5644237db360 (pc 0x5644237db360 bp 0x625000000100 sp 0x7ffd2d2e98b8 T0)
==156688==The signal is caused by a READ memory access.
==156688==Hint: PC is at a non-executable region. Maybe a wild jump?
    #0 0x5644237db360 in write (/tmp/a.out+0x4360)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/tmp/a.out+0x4360) in write
==156688==ABORTING

write()read() 是标准 POSIX 函数,用于与内核通信以执行某些 I/O 操作。您定义的write 符号没有损坏,它覆盖 glibc 提供的标准write 函数。当std::ios::sync_with_stdio 被调用时,这使得libstdc++ 调用write 系统调用,但链接器将该符号not 解析为glibc 库中定义的write 符号,但解析为你的符号在里面的定义你的文件。

$ gdb ./a.out
[...]
Program received signal SIGSEGV, Segmentation fault.
0x0000555555558a60 in write ()
(gdb) bt
#0  0x0000555555558a60 in write ()
#1  0x00007ffff749c49f in (anonymous namespace)::xwrite (__n=2, __s=0x625000000100 "b\n", '\276' <repeats 198 times>..., __fd=1) at basic_file.cc:120
#2  std::__basic_file<char>::xsputn (this=this@entry=0x7ffff75b0c08 <__gnu_internal::buf_cout+104>, 
    __s=__s@entry=0x625000000100 "b\n", '\276' <repeats 198 times>..., __n=__n@entry=2) at basic_file.cc:325
#3  0x00007ffff74dd025 in std::basic_filebuf<char, std::char_traits<char> >::_M_convert_to_external (
    this=this@entry=0x7ffff75b0ba0 <__gnu_internal::buf_cout>, __ibuf=0x625000000100 "b\n", '\276' <repeats 198 times>..., __ilen=2)
    at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/fstream.tcc:642
#4  0x00007ffff74dd46f in std::basic_filebuf<char, std::char_traits<char> >::overflow (this=0x7ffff75b0ba0 <__gnu_internal::buf_cout>, __c=-1)
    at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/streambuf:536
#5  0x00007ffff74db1ad in std::basic_filebuf<char, std::char_traits<char> >::sync (this=<optimized out>)
    at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/fstream.tcc:1020
#6  std::basic_filebuf<char, std::char_traits<char> >::sync (this=<optimized out>)
    at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/fstream.tcc:1012
#7  0x00007ffff7503d83 in std::basic_streambuf<char, std::char_traits<char> >::pubsync (this=<optimized out>)
    at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/streambuf:278
#8  std::ostream::flush (this=0x555555558900 <std::cout@@GLIBCXX_3.4>)
    at /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream.tcc:219
#9  0x000055555555532f in main () at ./1.cpp:8
(gdb) pt &write
type = struct W {
  public:
    W(int);
} *
(gdb) p &write
$2 = (W *) 0x555555558a60 <write>
(gdb) p &__libc_write
$3 = (<text variable, no debug info> *) 0x7ffff671df20 <write>

到目前为止,我不明白为什么write 没有被破坏(不应该吗?)。最简单的解决方案是将全局变量保存在命名空间中:

namespace my {
    W write(0);
}
// results in:
// $ nm ./a.out | grep write
// 0000000000004191 B _ZN2my5writeE  - OK!

或者在writeread 变量声明前添加static。或者选择其他名称,不同于 POSIX 提供的名称。或者将变量声明移到 int main() 函数内部。

【讨论】:

  • write 不是函数。只有函数名称被破坏。变量名不是(你不能重载变量)。
猜你喜欢
  • 2015-10-19
  • 2011-11-22
  • 2021-07-18
  • 2020-04-15
  • 1970-01-01
  • 2014-04-05
  • 2020-10-28
  • 1970-01-01
  • 2017-08-18
相关资源
最近更新 更多