修改C++字符串对象的基础字符数组

12

我的代码如下:

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

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

顺便说一下,我的意图是编写一些纯C函数,可以同时被C和C ++调用,因此它们只能接受char*作为参数。从char*转换为字符串,我知道会涉及复制,代价不利。那么,有人能给出一些处理这种情况的建议吗?

7个回答

6
对于第一部分,c_str()返回const char*,它的意思就是它所说的。在这种情况下,所有const_cast实现的仅仅是你的未定义行为编译通过。
对于第二部分,在C++0x中,std::string保证具有连续存储,就像C++03中的std::vector一样。因此,只要字符串不为空,您可以使用&s[0]来获取char*以传递给您的函数。在实践中,目前所有处于活动开发中的string实现都已经具备连续存储:在标准委员会会议上进行了一次调查,没有人提供反例。因此,如果您喜欢,现在可以使用此功能。
但是,std::string使用与C风格字符串基本不同的字符串格式,即数据+长度而不是以null结尾。如果您从C函数修改字符串数据,则无法更改字符串的长度,并且您无法确保末尾有一个null字节,除非使用c_str()。并且std::string可以包含作为数据一部分的内嵌null,因此即使您找到了一个null,如果不知道长度,则仍然不知道是否已经找到了字符串的末尾。您在既能正确操作不同类型数据的函数中所能做的非常有限。

5
(a) 这不一定是底层字符串。std::string::c_str() 应该是底层字符串的一个副本(尽管 C++ 标准中的一个错误意味着它经常不是这样... 我相信这在 C++0x 中已经修复)。
(b) 只有修改变量类型的 const_cast 是很糟糕的行为:实际对象仍然是 const,而你对其进行修改是未定义行为 - 非常糟糕。
简单地说,不要这样做。
你能使用 &myString[0] 吗? 它有一个非 const 版本;另一方面,它被声明为与没有非 const 版本的 data()[0] 相同。 有一个好的库参考资料的人可以澄清这一点。

1
那么,&mystring[0] 是安全的方式吗? - Need4Steed
1
@Need4Steed: 有点类似。在C++98 / C++03中,字符串内容在技术上并不保证是连续的...然而,标准中的一个错误意味着所有主流实现_都_使其连续,这在C++0x中得到了标准化。(请注意,您获得的指针_不_指向以空字符结尾的字符数组,因此您还需要传递长度。) - Lightness Races in Orbit
1
是的,使用最新的标准。目前没有已知的不支持该标准的实现。但要注意不要超出保留长度。 - Coder
@Coder:非常感谢!这正是我想知道的。 - Need4Steed
1
“basic_string”对象中的char类型对象必须被连续存储。也就是说,对于任何一个basic_string对象s,当0 <= n < s.size()时,恒有&*(s.begin() + n) == &*s.begin() + n成立。 - user90843
请注意,自C++17以来,data()函数有一个非const版本。 - kingsjester

4
显然的答案是否定的,这是未定义的行为。另一方面,如果你这样做:
char* pc = &s[0];

您可以访问底层数据,在今天的实践中,这在C++11中得到了保证。


3

正如其他人所说,它不具备可移植性。但是存在更多的危险。一些 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::string都使用写时复制语义。当您复制一个std::string时,有些实现会深度复制底层字符数组。无论如何,我们不应该依赖于这样的实现细节。 - In silico
1
我会期望第一个 &x[0] 解除缓冲区共享,因为它无法知道我是否存储了指针并在稍后使用了它 char* p = &x[0]; ...; *p = 'X'; 现在 y[0] 是什么? - Bo Persson

1

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


1

这取决于您的操作系统。在GNU libc库中,std::string使用写时复制(CoW)模式实现。因此,如果多个std::string对象最初包含相同的内容,则它们内部都指向相同的数据。因此,如果您按照您在问题中展示的方法修改其中任何一个,所有(看似)不相关的std::string对象的内容都会更改。

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

无论如何,这是未定义的行为,所以我建议避免使用它。即使您成功了,您最终也可能开始遇到非常难以跟踪的错误。


0

你不应该擅自更改底层字符串。归根结底,字符串是一个对象,你会这样随意更改其他对象吗?

你是否对代码进行了性能分析,看看是否存在惩罚。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接