考虑以下示例:
int main()
{
string x = "hello";
//copy constructor has been called here.
string y(x);
//c_str return const char*, but this usage is quite popular.
char* temp = (char*)y.c_str();
temp[0] = 'p';
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cin >> x;
return 0;
}
在visual studio编译器和g ++上运行它。 当我这样做时,我得到了两个不同的结果 用g ++:
x = pello
y = pello
在visual studio 2010中:
x = hello
y = pello
差异的原因很可能是g ++ std :: string实现使用COW(写入时复制)技术而Visual Studio却没有。
现在,C ++标准(第616页表64)说明了字符串复制构造函数
basic_string(const basic_string& str):
效果:
data()
应该“指向数组的已分配副本的第一个元素,其第一个元素由str.data()
指向”
意思是不允许COW(至少在我的理解中)
怎么会这样?
g ++是否符合std::string
C ++ 11要求?
在C ++ 11之前,这并没有造成大问题,因为c_str
没有返回指向字符串对象所持有的实际数据的指针,因此更改它并不重要。但是在改变之后,COW +返回实际指针的组合可以打破旧的应用程序(应用程序因为编码不好而应该得到它)。
你同意我的意见吗?如果是,可以做些什么吗?有没有人知道如何在一个非常大的旧代码环境中使用它(一个发条规则来捕捉它会很好)。
请注意,即使没有强制转换constness,也可能通过调用c_str导致指针失效,保存指针然后调用非const方法(这将导致写入)。
另一个没有抛弃常量的例子:
int main()
{
string x = "hello";
//copy constructor has been called here.
string y(x);
//y[0] = 'p';
//c_str return const char*, but this usage is quite popular.
const char* temp = y.c_str();
y[0] = 'p';
//Now we expect "pello" because the standart says the pointer points to the actual data
//but we will get "hello"
cout << "temp = " << temp << endl;
return 0;
}
答案 0 :(得分:17)
你是对COW is disallowed。但据称GCC hasn't updated its implementation yet due to ABI constraints。最终设计为取代std::string
实施的新实施可以在ext/vstring.h
找到。
A bug in libstdc++'s std::string
,虽然不是这个,但不会进入GCC 4.9; Jonathan指出,到目前为止,它只针对vstring
进行了修复。那么,我的猜测是COW问题将在同一时间解决。
尽管如此,抛弃const
然后变异几乎总是一个坏主意:尽管你应该在实践中使用完全符合C ++ 11的字符串实现是安全的,但是你是正确的正在做出假设,这个问题证明你不能总是依赖这些假设。因此,虽然您的代码示例可能“受欢迎”,但它在糟糕的代码中很受欢迎,现在也不应该编写。当然,在C ++ 03中编写它是完全无能的!
答案 1 :(得分:5)
libstd ++的实现不符合C ++ 11,但这并不意味着您的代码正确地保证了您期望的结果。
执行任何操作以修改c_str()
返回的字符数组中存储的值会导致未定义的行为。标准明确说明了这一点:
21.4.7.1 basic_string访问者
const charT* c_str() const noexcept;
const charT* data() const noexcept;
1 返回:指针p,[{1,p + i == &operator[](i)
]中每个i
的{{1}}。
2 复杂性:恒定时间 3 要求:程序不得更改存储在字符数组中的任何值。
虽然上面我引用了C ++ 11,但对于C ++ 03也是如此。
有没有人知道如何在一个非常大的旧代码环境中使用它(一个发条规则来捕捉它会很好)。
希望你有一个不错的测试套件。对大型遗留代码库进行重大更改并不是真正实用的。运行测试套件越简单,越快,修复代码就越容易和快捷。
在非常大的代码库审核中,size()
的所有用途可能非常昂贵。然而,取样并检查它的用途是什么以及可以应用哪些具体的修正可以帮助您衡量问题的规模。根据我的经验,你可以期待各种奇怪的东西,但有些会更常见。
Valgrind,调试std :: string的实现以及其他工具可以帮助识别可能导致真正错误的一些实例。首先解决这些问题是首要任务。这些修复可能涉及将API更新为const-correct或具有明确定义的生命周期要求,以及将c_str()
的使用转换为生成具有适当生命周期的C字符串的内容。您对代码的调查应该告诉您生命周期要求的一般变化以及必要的c-string创建实用程序。
c_str()的其他用法可以随着时间的推移逐步修改为较低优先级的边活动。
一些基于clang构建的用于重构或语义搜索的工具是识别问题和进行大规模更改的另一种选择,但是为了让传统代码成为足够合法的clang工具,这通常是一项艰巨的任务处理它。 (Here's a talk关于谷歌在此方面所做的一些工作。最近他们就谷歌提供的这项技术的商品版本进行了更多的讨论。)
我经常很难说服人们即使在没有实际观察到不良影响的情况下,“未定义的行为”实际上也是一个问题。在编写新代码时,请记住,根据这种经验,如果符合C ++规范,未来维护者的生活将变得更加容易。即使某些特定的“坏”代码实例现在不会给您带来问题,随着编译器和库实现的改变,这可能会随着时间的推移而发生变化。即使规范发生变化,委员会也会仔细考虑对符合遗留代码的影响。如果代码不符合,那么它实际上没有得到任何考虑,你最终会遇到这样的问题。
答案 2 :(得分:3)
g ++是否符合
std::string
C ++ 11要求?
没有
在C ++ 11之前,这并没有造成大问题,因为
c_str
没有返回指向字符串对象所持有的实际数据的指针,因此更改它并不重要。
这是不正确的,c_str
总是被允许返回实际数据,这正是它在所有流行的C ++ 03实现中所做的。
但是在更改之后,COW +返回实际指针的组合可以打破旧应用程序(应用程序因为编码错误而应该得到它)。
经过什么改变? G ++没有改变它的std::string
所以如果你的旧程序被G ++破坏,那么它总是被打破。
请注意,即使没有强制转换constance,也可能通过调用
c_str
导致指针失效,保存指针然后调用非const方法(这将导致写入)。
您的第二个示例未演示任何失效,因为在COW实现中temp
仍然是x
存在时的有效指针。但是可以修改示例以使temp
无效并且在C ++ 11中不允许这样做,[string.require] / 6表示在C ++ 11 y[0]
中不允许使指针无效由c_str()
返回。
答案 3 :(得分:0)
当时其他答案都是正确的,但截至目前,相应于the GCC 5.x Change Log,gcc 5提供的libstdc ++现在完全符合C ++ 11标准。