【问题标题】:Modifying underlying char array of a c++ string object修改 C++ 字符串对象的底层 char 数组
【发布时间】:2011-04-20 11:00:11
【问题描述】:

我的代码是这样的:

string s = "abc";
char* pc = const_cast<char*>( s.c_str() );
pc[ 1 ] = 'x';
cout << s << endl;

当我使用 GCC 编译上面的 sn-p 时,我得到了预期的结果 "axc"。我的问题是,以这种方式修改 C++ 字符串的底层 char 数组是否安全且可移植?或者可能有其他方法可以直接操作字符串的数据?

仅供参考,我的目的是编写一些可以被 C 和 C++ 调用的纯 C 函数,因此,它们只能接受 char* 作为参数。从char* 到字符串,我知道涉及复制,处罚是不利的。那么,任何人都可以提出一些建议来处理这种情况。

【问题讨论】:

    标签: c++ arrays string


    【解决方案1】:

    对于第一部分,c_str() 返回const char*,这就是它所说的意思。在这种情况下,const_cast 实现的所有功能就是编译您的未定义行为。

    到第二部分,在 C++0x 中,std::string 保证具有连续存储,就像 C++03 中的 std::vector。因此,只要字符串不为空,您就可以使用 &amp;s[0] 获取 char* 以传递给您的函数。在实践中,目前正在积极开发的所有string 实现都已经具有连续存储:在标准委员会会议上进行了一次民意调查,没有人提供反例。因此,如果您愿意,现在可以使用此功能。

    然而std::string 使用与 C 风格字符串完全不同的字符串格式,即它是数据+长度而不是 nul 终止。如果从 C 函数中修改字符串数据,则无法更改字符串的长度,并且如果没有c_str(),则无法确定末尾是否有 nul 字节。而std::string 可以包含作为数据一部分的嵌入式 nuls,因此即使您确实找到了一个 nul,但在不知道长度的情况下您仍然不知道您已经找到了字符串的结尾。在能够对两种不同类型的数据进行正确操作的函数中,您能做的事情非常有限。

    【讨论】:

      【解决方案2】:

      (a) 这不一定是底层字符串。 std::string::c_str() 应该是底层字符串的副本(尽管 C++ 标准中的一个错误意味着,实际上,它通常不是……我相信这在 C++0x 中已修复)。

      (b) const_cast 去掉常量只会破解变量类型:实际对象仍然是 const,而你修改它是未定义的行为——非常糟糕。

      简单地说,不要不要这样做。


      你能用&amp;myString[0]吗?它有一个非常量版本;话又说回来,它被声明与没有非常量版本的data()[0] 相同。手头有不错的图书馆参考资料的人可以解决这个问题。

      【讨论】:

      • 那么,&mystring[0] 是安全的方式吗?
      • @Need4Steed:有点。在 C++98/C++03 中,字符串内容在技术上不能保证是连续的......但是,标准中的错误意味着所有主流实现 do 无论如何都会使其连续,这在 C++0x 中成为标准。 (请注意,您获得的指针确实 not 指向一个以 null 结尾的 char 数组,因此您也必须传递长度。)
      • 是的,采用最新标准。并且没有已知的实现。但请注意不要超出保留的长度。
      • &@Coder:非常感谢!这正是我想知道的。
      • [string.require] 21.4.1.5 "basic_string 对象中的 char 类对象应连续存储。也就是说,对于任何 basic_string 对象,标识 &*(s.begin() + n) == &*s.begin() + n 应适用于所有 n 值,例如 0
      【解决方案3】:

      显而易见的答案是否定的,这是未定义的行为。在另一 手,如果你这样做:

      char* pc = &s[0];
      

      在今天的实践中,您可以访问基础数据,并且 在 C++11 中保证。

      【讨论】:

        【解决方案4】:

        正如其他人所说,它不可移植。但还有更多的危险。一些 std::string 实现(我知道 GCC 会这样做)使用 COW(写入时复制)。

        #include <iostream>
        #include <string>
        
        int main()
        {
        
            std::string x("abc");
            std::string y;
            y = x; // x and y share the same buffer
        
            std::cout << (void*)&x[0] << '\n';
            std::cout << (void*)&y[0] << '\n';
        
            x[0] = 'A'; // COW triggered
        
            // x and y no longer share the same buffer
            std::cout << (void*)&x[0] << '\n';
            std::cout << (void*)&y[0] << '\n';
        
            return 0;
        }
        

        【讨论】:

        • 并非所有std::strings 都使用写时复制语义。当您复制std::string 时,某些实现会深度复制底层字符数组。在任何情况下,都不应该依赖这样的实现细节。
        • 我希望第一个 &amp;x[0] 取消共享缓冲区,因为它无法判断我是否存储指针并在以后使用它 char* p = &amp;x[0]; ...; *p = 'X'; y[0] 现在是什么?
        【解决方案5】:

        这依赖于未定义的行为,因此不可移植。

        【讨论】:

          【解决方案6】:

          这取决于您的操作系统。在 GNU libc 库中,std::string 是使用 copy-on-write (CoW) pattern 实现的。因此,如果多个std::string 对象最初包含相同的内容,则它们在内部都将指向相同的数据。因此,如果您在问题中显示的方法中修改其中任何一个,所有(看似)不相关的 std::string 对象的内容都会改变。

          在 Windows 上,我认为实现不使用 CoW,我不确定那里会发生什么。

          无论如何,这是未定义的行为,所以我会远离它。很有可能,即使你让它正常工作,你最终也会开始遇到非常难以追踪的错误。

          【讨论】:

            【解决方案7】:

            你不应该弄乱底层字符串。归根结底,字符串是一个对象,你会这样弄乱其他对象吗?

            您是否分析过您的代码以查看是否存在惩罚。

            【讨论】:

              猜你喜欢
              • 2013-10-22
              • 1970-01-01
              • 1970-01-01
              • 2023-03-11
              • 1970-01-01
              • 1970-01-01
              • 2017-06-15
              • 2017-04-23
              相关资源
              最近更新 更多