叮当中有一个漂亮的计数器惯用法或格式错误的静态订单惨败?

问题描述 投票:3回答:1

以下代码使用clang崩溃(x86_64-pc-linux-gnu上版本5.0.0-3〜16.04.1,但在gcc(9.2.0)上可以正常工作。

struct Registry {
    static int registerType(int type) {
        std::cout << "registering: " << type;
        return type;
    }
};

template<typename T>
struct A {
    static int i;
};

template<typename T>
int A<T>::i = Registry::registerType(9);

int main() {
    std::cout << A<int>::i << std::endl;    
}

叮当声崩溃,是由于地址清除程序造成的:

ASAN:DEADLYSIGNAL
=================================================================
==31334==ERROR: AddressSanitizer: SEGV on unknown address 0xffffffffffffffe8 (pc 0x7f5cc12b0bb6 bp 0x7ffdca3d1a20 sp 0x7ffdca3d19e0 T0)
==31334==The signal is caused by a READ memory access.
    #0 0x7f5cc12b0bb5 in std::ostream::sentry::sentry(std::ostream&) /root/orig/gcc-9.2.0/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream.tcc:48:31
    #1 0x7f5cc12b11e6 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) /root/orig/gcc-9.2.0/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream_insert.h:82:39
    #2 0x4197a7 in __cxx_global_var_init.1 (/tmp/1576534654.656283/a.out+0x4197a7)
    #3 0x514eac in __libc_csu_init (/tmp/1576534654.656283/a.out+0x514eac)
    #4 0x7f5cc02847be in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:247
    #5 0x419858 in _start (/tmp/1576534654.656283/a.out+0x419858)

这是用lang语的漂亮计数器惯用语的错误,还是格式错误的静态订单惨败的示例?


编辑

在接受答案之后,问题可以改为:

  • 可能是全局ostream对象std::cout没有正确初始化吗?
  • 即使我们包括了iostream并且我们正确使用了std::cout,是否存在允许编译器不初始化std::cout的有效情况?
  • 是否存在用例cout << "foo"崩溃不是编译器错误?

为了避免破坏者,我只是暗示答案是。可能会发生这种情况,但是请不要担心有解决方法。要查看更多信息,请关注the accepted answer below

也可以按照公认的答案,将所讨论的情况缩小到更基本的情况:

int foo() {
    std::cout << "foo";
    return 0;
}

template<typename T>
struct A {
    static int i;
};

template<typename T>
int A<T>::i = foo();

int main() {
    (void) A<int>::i;    
}

that crashes on the said clang version(而且看起来很合理!)。

c++ static ostream
1个回答
4
投票

不幸的是,该代码具有未指定的行为。原因类似于(如果不是通常的定义)静态初始化顺序失败。

std::cout对象和在<iostream>中声明的其他类似对象在初始化std::ios_base::Init类型的第一个对象之前不得使用。包括<iostream>定义(或就像定义一样)具有静态存储持续时间([iostream.objects.overview]/3)的该类型的非本地对象。在大多数情况下,即使在动态初始化期间使用std::cout和Friends时,这也可以满足要求,因为Init定义在转换单元中通常比任何其他非本地静态存储对象定义更早。

但是,[basic.start.dynamic]/1

如果变量是隐式或显式实例化的特殊化,则动态地初始化具有静态存储持续时间的非局部变量是无序的,..

因此,尽管std::ios_base::Init中定义的<iostream>对象的初始化(有效地)是有序的,但是A<int>::i的初始化是无序的,因此这两个初始化不确定地排序。因此,我们不能指望此代码有效。

如注释中提到的@walnut,可以通过在使用std::ios_base::Init之前动态初始化A<int>::i期间强制另一个std::cout对象进行初始化来更正代码,>

struct Registry {
    static int registerType(int type) {
        static std::ios_base::Init force_init;
        std::cout << "registering: " << type;
        return type;
    }
};

0
投票

实际上我所说的并不是一个错误。

© www.soinside.com 2019 - 2024. All rights reserved.