在 C++11 中,string::c_str() 指向的数组中的字符可以改变吗?

Posted

技术标签:

【中文标题】在 C++11 中,string::c_str() 指向的数组中的字符可以改变吗?【英文标题】:In C++11, can the characters in the array pointed to by string::c_str() be altered? 【发布时间】:2013-08-07 20:54:48 【问题描述】:

std::string::c_str() 返回一个指向数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),表示字符串对象的当前值。

在 C++98 中,要求“程序不得更改此序列中的任何字符”。这是通过返回一个 const char* 来鼓励的。

在 C++11 中,“返回的指针指向字符串对象当前用于存储符合其值的字符的内部数组”,我相信不修改其内容的要求已被删除。这是真的?

这段代码在 C++11 中可以吗?

#include<iostream>
#include<string>
#include<vector>
using namespace std;

std::vector<char> buf;

void some_func(char* s)

    s[0] = 'X'; //function modifies s[0]
    cout<<s<<endl;


int main()

    string myStr = "hello";
    buf.assign(myStr.begin(),myStr.end());
    buf.push_back('\0');
    char* d = buf.data();   //C++11
    //char* d = (&buf[0]);  //Above line for C++98
    some_func(d);   //OK in C++98
    some_func(const_cast<char*>(myStr.c_str())); //OK in C++11 ?
    //some_func(myStr.c_str());  //Does not compile in C++98 or C++11
    cout << myStr << endl;  //myStr has been modified
    return 0;

【问题讨论】:

c_str() 仍然是 const char* 幸运的是不可变的,对应于可缓存的结果。 你为什么还需要这个,&amp;myStr.front() 有什么问题? &amp;myStr[0] 也可以 @AndreKostur 是的,C++11 要求字符串连续存储在内存中。所以通过指向第一个的指针修改一系列字符是可以的,只要你don't modify the terminating NULL character。 ……虽然,回顾问答和标准,实际上没有办法获得指向可修改范围的指针;实际上,即使对于非常量指针,字符也是const。连续性仅保证您可以将字符串作为数组读取。但是您不能假设终止符存在,除非在调用 c_str 之后和调用任何非常量成员函数之前。 (编辑:啊,这在 C++14 中已修复,因此您可以修改除终止符之外的任何内容,终止符由 operator[] 为不小于 size() 的任何索引生成并返回,即它返回一个假引用。) 【参考方案1】:

3 要求:程序不得更改存储在字符数组中的任何值。

从草案 n3337 (The working draft most similar to the published C++11 standard is N3337) 开始,该要求仍然存在

【讨论】:

我已验证这在已发布的 C++11 标准 (ISO/IEC 14882-2011) 中。【参考方案2】:

在 C++11 中,是的,c_str() 的限制仍然有效。 (注意返回类型是const,所以这个函数实际上没有特别的限制。你程序中的const_cast是一个很大的危险信号。)

但至于operator[],它似乎只是由于编辑错误而生效。由于 C++14 的标点符号更改,您可以对其进行修改。所以解释有点取决于你。当然,这样做很常见,以至于没有任何库实现敢于破坏它。

C++11 措辞:

返回:*(begin() + pos) 如果 pos

C++14 措辞:

返回:*(begin() + pos) 如果 pos

您可以将 c_str() 作为只读引用传递给需要 C 字符串的函数,正如其签名所暗示的那样。期望读写引用的函数通常期望给定缓冲区大小,并且能够通过在该缓冲区中写入NUL 来调整字符串的大小,std::string 实现实际上并不支持。如果你想这样做,你需要 resize 字符串包含你自己的 NUL 终止符,然后传递 &amp; s[0] 这是一个读写引用,然后 resize 再次删除你的 NUL 终止符并将终止的责任交还给图书馆。

【讨论】:

+1 我一直认为引用的值不应修改部分只适用于otherwise一半,主要是因为不会如果 operator[] 涵盖了整个事情,则需要 const 和非 const 重载,但我现在看到了歧义。 @Praetorian 更糟糕的是因为 C++03 指定了 operator[]() 路由通过 data(),这表明内置的不安全 const_cast。如果 C++11 通过编辑错误添加了一个意外限制,那会更清楚,但错误保留了一个有缺陷的规范。【参考方案3】:

我想说,如果 c_str() 返回一个 const char *,那么它就不行,即使它可以被语言律师认为是一个灰色区域。

我的看法很简单。该方法的签名声明它返回的指针不应用于修改任何内容。

此外,正如其他评论者所指出的那样,还有其他方法可以做同样的事情而不违反任何合同。所以这样做肯定不行。

也就是说,Borgleader 发现语言仍然说它不是。

【讨论】:

【参考方案4】:

我已验证这在已发布的 C++11 标准中

谢谢

&myStr.front() 有什么问题?

string myStr = "hello";
char* p1 = const_cast<char*>(myStr.c_str());
char* p2 = &myStr.front();
p1[0] = 'Y';
p2[1] = 'Z';

看起来指针 p1 和 p2 完全一样。由于“程序不应更改存储在字符数组中的任何值”,因此上面的最后两行似乎都是非法的,并且可能很危险。

此时,我要回答我自己的问题的方式是将原始 std::string 复制到向量中,然后将指向新数组的指针传递给任何可能更改字符的函数是最安全的。

我希望这一步在 C++11 中可能不再需要,因为我在原始帖子中给出了原因。

【讨论】:

-1:你做了一个const_cast。为什么这还不足以说明这是一个坏主意?仅仅因为“指针 p1 和 p2 完全相同”并不意味着您应该假设它们总是会如此。只需使用front&amp;[0],不要再对编译器撒谎了。你的编码风格很糟糕;请以正确的方式做。 不需要const_cast,应该尽量避免;使用front()operator[] 获取对第一个元素的引用。此外,只要您确保字符串足够大可以写入,并且您不modify the terminating NULL character,就不需要将字符串复制到向量中来修改它 @user2662157: “标准是“程序不得更改存储在字符数组中的任何值。”” 它说对于数组 c_str 返回,一般不适用于std::string。上下文很重要。 @user2662157: "或相同的指针" 不,这不是标准的工作方式。该标准不关心指针是否恰好与另一个指针的值相同。标准说明了它所说的。禁止通过c_str 返回的指针修改字符串。另一个函数可能会返回一个您允许修改的指针。这两个指针可能具有(甚至必须具有)相同的指针值这一事实与标准所说的完全无关。您可以通过其中一个进行修改,而不能通过另一个进行修改。 我很抱歉。我的最后两个 cmets 是错误的。在这种情况下,c_str 可能会在返回指针之前修改内部数组(特别是终端 '\0")。所以说另一个指针具有相同的值是完全不相关的,这是完全正确的。

以上是关于在 C++11 中,string::c_str() 指向的数组中的字符可以改变吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++string类型与C语言字符数组的转换 std::string.c_str()函数

在临时字符串上使用 string::c_str [重复]

std::string::c_str() 结果的生命周期是多少?

std::string::c_str() 结果的生命周期是多少?

为啥我仍然可以在字符串范围之外访问 std::string::c_str() 返回的 char 指针? [复制]

深拷贝 std::string::c_str() 到 char * [重复]