C ++ 11初始化程序列表失败-但仅在长度为2的列表上


71

我找到了一个模糊的日志记录错误,发现长度为2的初始化列表似乎是特例!这怎么可能?

该代码是使用Apple LLVM 5.1版(clang-503.0.40)编译的CXXFLAGS=-std=c++11 -stdlib=libc++

#include <stdio.h>

#include <string>
#include <vector>

using namespace std;

typedef vector<string> Strings;

void print(string const& s) {
    printf(s.c_str());
    printf("\n");
}

void print(Strings const& ss, string const& name) {
    print("Test " + name);
    print("Number of strings: " + to_string(ss.size()));
    for (auto& s: ss) {
        auto t = "length = " + to_string(s.size()) + ": " + s;
        print(t);
    }
    print("\n");
}

void test() {
    Strings a{{"hello"}};                  print(a, "a");
    Strings b{{"hello", "there"}};         print(b, "b");
    Strings c{{"hello", "there", "kids"}}; print(c, "c");

    Strings A{"hello"};                    print(A, "A");
    Strings B{"hello", "there"};           print(B, "B");
    Strings C{"hello", "there", "kids"};   print(C, "C");
}

int main() {
    test();
}

输出:

Test a
Number of strings: 1
length = 5: hello

Test b
Number of strings: 1
length = 8: hello

Test c
Number of strings: 3
length = 5: hello
length = 5: there
length = 4: kids

Test A
Number of strings: 1
length = 5: hello

Test B
Number of strings: 2
length = 5: hello
length = 5: there

Test C
Number of strings: 3
length = 5: hello
length = 5: there
length = 4: kids

我还应该补充一点,测试b中的假字符串的长度似乎不确定-它总是大于第一个初始化程序的字符串,但是从大于第一个字符串的长度到两个字符串的总长度之间变化在初始化程序中。


5
为什么双括号?
克里斯

2
我将调查与向量的构造,特别是迭代器和迭代器一个互动
欢呼和心连心。-Alf 2014年

4
得到它了。我来回答
克里斯,克里斯

1
它随着VIsual C ++崩溃,这是UB在工作的证据,这是构造函数交互的证据。
干杯和健康。-阿尔夫

1
更奇怪的是,当实例化Stringsin main时,该程序将引发异常,但是当您注释掉print()in中的调用时,该程序将消失test()。我认为有一些UB正在进行。- coliru.stacked-crooked.com/a/bf9b59160c6f46b0
0x499602D2

Answers:


76

介绍

想象一下以下声明和用法:

struct A {
  A (std::initializer_list<std::string>);
};

A {{"a"          }}; // (A), initialization of 1 string
A {{"a", "b"     }}; // (B), initialization of 1 string << !!
A {{"a", "b", "c"}}; // (C), initialization of 3 strings

在(A)和(C)中,每个c样式字符串都导致一个(1)的初始化。 std :: string,但是,正如您在问题中所指出的,(B)不同。

编译器发现可以使用begin-end-iterator构造std :: string,并且在解析语句(B)时,与使用和作为两个元素的单独初始化器相比,它更喜欢这种构造。"a""b"

A { std::string { "a", "b" } }; // the compiler's interpretation of (B)

:该类型的"a""b"char const[2],这样一种类型,可以隐含衰变成一个char const*,指针型,适用于像一个iterator表示无论是开始还是结束创建的std :: string ..我们必须小心:我们导致不确定的行为因为调用所述构造函数时两个指针之间没有(保证的)关系。


说明

当您调用使用双括号的std :: initializer_list的构造函数时{{ a, b, ... }},有两种可能的解释:

  1. 外括号表示构造函数本身,内括号表示参与std :: initializer_list的元素,或者:

  2. 外部花括号指向std :: initializer_list,而内部花括号表示内部元素的初始化。

最好在可能的情况下执行2),并且由于std::string有一个构造函数带有两个迭代器,因此当您具有时,将被调用std::vector<std::string> {{ "hello", "there" }}

进一步的例子:

std::vector<std::string> {{"this", "is"}, {"stackoverflow"}}.size (); // yields 2

请勿使用双花括号进行此类初始化。


感谢您充实它,而不是在我发布之前您还没有发现它:)
chris

@chris需要花费一些时间来修复更好的格式,并且一如既往地使我比其他所有格式都慢:P
FilipRoséen-refp 2014年

是的,在给出答案后,我可能会很好地格式化我的格式,但是现在我基本上是要复制您的帖子了:p,我只是链接到您的帖子,尽管希望您的帖子被接受了。@tom,提示提示
克里斯

1
该类型"a"不是 const char*,这是const char[2],这是自由兑换const char*
Mooing Duck

@MooingDuck非常好点,谢谢。对新的措词感到满意?
FilipRoséen-refp

20

首先,除非我缺少明显的东西,否则这是未定义的行为。现在让我解释一下。向量是从字符串的初始化列表中构造的。但是,此列表仅包含一个字符串。该字符串由内部字符串组成{"Hello", "there"}。怎么样?使用迭代器构造函数。本质上,for (auto it = "Hello"; it != "there"; ++it)正在形成一个包含的字符串Hello\0

有关简单示例,请参见此处。尽管UB有足够的理由,但似乎第二个文字正好位于第一个文字之后。作为奖励,这样做"Hello", "Hello",您可能会得到长度为0的字符串。如果您在这里不懂任何内容,建议阅读Filip的出色答案


…如果编译器决定将"there"地址放在比更低的地址"Hello",则会导致崩溃。
Potatoswatter 2014年

3
哈哈!它必须是未定义的行为。但是,等等,为什么没有无限循环呢?答:由于编译器的想法,这两个字符串或多或少地连续排列在内存中!
Tom Swirly 2014年

@Potatoswatter,是的,这确实很有趣。我发现做"Hello", "Hello"了长度为0的字符串
克里斯-

我现在去吃饭。在鼓励我编辑它之前,我不会将其标记为正确,但是,我敢肯定,您是对的... :-)
Tom Swirly 2014年

@chris:取决于编译器设置,它们的长度可能为零或任何其他长度
Mooing Duck
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.