繁体   English   中英

一个带有漂亮计数器成语的错误还是一个格式错误的静态订单惨败?

[英]A bug with the nifty-counter idiom or an ill-formed static order fiasco?

以下代码在使用 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)

这是 clang 中漂亮的计数器成语的错误,还是格式错误的静态初始化顺序失败的例子?


编辑

根据接受的答案,问题可以改写为:

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

为了避免剧透,我只是暗示答案是肯定的 这可能会发生,但不用担心有解决方法。 要查看更多信息,请遵循下面的已接受答案

同样按照公认的答案,有问题的案例可以缩小到一个更基本的场景:

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

在所说的 clang 版本上崩溃了(而且看起来是有道理的!)。

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM