【问题标题】:Converting std::string ** to char *** and it happens to work. How?将 std::string ** 转换为 char *** 并且它恰好可以工作。如何?
【发布时间】:2015-03-13 22:34:00
【问题描述】:

考虑以下代码:

std::vector<std::string> foo{{"blee"}, {"bleck"}, {"blah0000000000000000000000000000000000000000000000000000000000000000000000000000000000"}};
std::string *temp = foo.data();
char*** bar = reinterpret_cast<char***>(&temp);

for (size_t i = 0; i < foo.size(); ++i){
    std::cout << (*bar)[i] << std::endl;
}

显然这是粗略的代码,但它恰好可以工作。

http://ideone.com/2XAJYR

我想知道它为什么有效?有一些我不知道的奇怪的 C++ 规则吗?还是只是糟糕的代码和未定义的行为?

我将其中一个字符串设置得很大,以防进行一些小字符串优化。

改编自:Cast a vector of std::string to char***

【问题讨论】:

  • 字符串可能将指向其缓冲区的指针存储为第一个结构成员,因此其地址与字符串对象本身的地址相同。我不会说“它有效”;而是假装工作。
  • @TheParamagneticCroissant 是的,我也打赌,但我认为依靠创建应该始终有效的代码并不是一个好主意...
  • 我认为这与 std::vector 行为有关。它保证有连续的内存,甚至复制和移动它的数据来完成这个保证。如果将固定内存大小的静态 std::strings 放入其中,它们将被放置在连续的内存块中,您可以做这样的技巧:)。要检查它,你可以 push_back 向量中的另一个字符串,看看你以前的数据指针是否仍然有效。

标签: c++


【解决方案1】:

这是非常未定义的行为。

如果字符串实现恰好包含一个指向字符串数据的指针作为其唯一的数据成员,那么它似乎“工作”,因此string 的数组与char* 的数组具有相同的内存布局。至少有一种流行的实现 (GNU) 就是这种情况,但肯定不是您可以依赖的。

【讨论】:

  • 当您处理 C 库时,这是一个特别讨厌的问题。我见过std::string 作为void * 传递的一些情况,在有人决定使用clang 重新编译之前效果很好。
  • @Shep:如果您正在处理 C 库,请使用 c_str() 以明确定义的方式获取与 C 兼容的指针。在任何情况下,狡猾的双关语都是令人讨厌的。
  • @NeilKirk:正如我所说,如果 char* 指针是 only 数据成员,则它“有效”,因此 string 的数组具有相同的布局作为char* 的数组。如果它包含其他数据成员,它将爆炸。
  • Ooooooooo 现在它是有道理的。我假设大小和容量将是字符串对象本身的成员变量。
  • @JonathanMee:指针指向字符串数据,所以打印只是打印字符串。在字符串之前的内存中存储了更多信息,实现可以通过从指针中减去一些偏移量来访问这些信息。
【解决方案2】:

行为取决于您的 STL 实现(只需修改 std::vector 和 std::string 源代码)。有时,您有字符串 impl 存储(如其他参与者提到的)指向 chars 缓冲区的指针作为成员。

人们不应该依赖封装的实现细节,因为它会导致未定义的行为,这已经不是什么秘密了。

【讨论】:

    【解决方案3】:

    Neil Kirk 在对最初引发这一切的答案的评论中提到这一点后,我查了一下。

    stringbasic_string 在所有实现上的特化。

    现在我只能访问 Visual Studio 的 2013 版本的 xstring.h(此处 Microsoft 实现了 basic_string),因此对于其他版本或编译器可能会有所不同。但是在 xstring.h 中,basic_string 继承自 _String_alloc,而 _String_alloc 继承自 _String_val

    _String_val 实际上是继承链中第一个具有任何成员变量的变量。它的第一个成员变量_Bxunion,它将转换为stringchar*(不适用于wstring)。

    因此,当 string 在 Visual Studio 2013 上转换为 char* 时,它是一个 char*,它开始指向成员变量:_Bx 因为 _Bx 实际上是 '\0'-terminated char* 你可以 cout 它运行正常。

    现在我不知道,所有这些研究告诉我的是,_String_val 还包含一个大小变量 _Mysize 和一个保留大小 _Myres。如果其中任何一个在_Bx 之前在_String_val 中声明,这将在cout 每行输出的开头输出乱码。

    最后我承认,正如其他答案所提到的,这种行为取决于实现,并且可能无法跨不同的版本或平台工作。

    【讨论】:

      【解决方案4】:

      正如 The Parametric Croissant 的评论所暗示的那样,字符串类的 char[] 成员必须是第一个成员,以便字符串地址 == char[] 开头。

      我在标准中找不到任何明确提及的内容。 标准中的某些其他规则可能隐含地强加了这一规则,但我没有找到。

      因此你不应该依赖它。

      注意: 另一个更明显的必要性是 std::vector 提供连续的内存空间,但这是指定的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-14
        • 2013-07-18
        • 1970-01-01
        • 1970-01-01
        • 2013-08-26
        • 2013-03-20
        • 1970-01-01
        相关资源
        最近更新 更多