【问题标题】:Does g++ meets std::string C++11 requirementsg++ 是否满足 std::string C++11 要求
【发布时间】:2014-02-21 06:55:36
【问题描述】:

考虑以下示例:

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

在视觉工作室 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 + 返回实际指针的组合可以并且破坏旧的应用程序(由于编码错误而应得的应用程序,但尽管如此)。

你同意我的观点吗?如果是,可以做些什么吗?有没有人知道如何在一个非常大的旧代码环境中处理它(一个发条规则来捕捉这个会很好)。

请注意,即使不强制转换常量,也可能通过调用 c_str、保存指针然后调用非 const 方法(这将导致写入)导致指针无效。
另一个没有抛弃 constness 的例子:

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;
}

【问题讨论】:

  • 您是说使用指向常量数据的指针(以及非常临时的指针)作为指向非常量数据的指针“非常流行”?我觉得很难相信。事实上,尝试修改常量数据会导致未定义的行为(参见例如this reference)。
  • @buc030 链接的问题完全回答了这个问题;任何 COW 实现都是无效的。没有必要为每个 COW 实现设置单独的问题,尤其是那些将问题与 const UB 违规混淆的问题。
  • 这不是“STL 中的错误”。这是 C++ 标准库(称为 libstdc++)的 GNU 实现的一个已知合规性问题。
  • @buc030, C++03 允许 c_str() 是一个不同的指针,但也允许它指向实际数据。您没有很好地研究,请阅读您链接到的最佳答案中的 cmets。在 G++ 中,它始终指向实际数据,因此在这方面没有变化。

标签: c++ string c++11 g++ std


【解决方案1】:

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 问题将在同一时间得到解决。

尽管如此,抛弃constness 然后变异几乎总是一个坏主意:虽然你是正确的,这在实践中应该是安全的,完全符合 C++11 的字符串实现,你是做出假设,这个问题证明你不能总是依赖这些假设来持有。因此,虽然您的代码示例可能很“流行”,但它在糟糕的代码中很流行,甚至现在也不应该编写。而且,当然,用 C++03 编写是完全无能的!

【讨论】:

  • 你是对的,抛弃 const 是一个坏主意,但现实是我们在不干净的环境中开发,使用非常旧的代码(谁知道谁写的)做这些坏事相当很多。此外,即使不强制转换 const,也可能通过调用 c_str、保存指针然后调用非 const 方法(这将导致写入)来导致指针无效。
  • @buc030:是的,当然,还有很多其他的方法可以导致UB。不过,旧代码很难成为借口:这在 C++ 中从不合法。
  • 其实我今天早些时候刚刚更新了那个错误的目标里程碑,它不会在 4.9 中修复,我们在 4.9 中仍然会有 COW 字符串
  • @JonathanWakely:在 Bugzilla 或 C++11 状态矩阵中,我找不到太多关于 libstdc++ 中字符串不合规的权威参考;还有其他地方我应该寻求增强这个答案吗?还是我对那个关于金钱的错误 53221 的猜测?
  • 巧合的是,我已经在更新 C++11 状态表以提及我们的 COW 实现的不一致性,并计划立即重新生成 onlinedocs HTML 页面。
【解决方案2】:

libstd++ 的实现不符合 C++11,但这并不意味着您的代码正确地保证了您期望的结果。

对存储在c_str() 返回的字符数组中的值进行任何修改都会导致未定义的行为。该标准明确表示:

21.4.7.1 basic_string 访问器

const charT* c_str() const noexcept;
const charT* data() const noexcept;
1返回: 一个指针 p 使得 p + i == &amp;operator[](i) 对于 [0,@ 中的每个 i 987654327@]。
2复杂性:恒定时间。
3要求:程序应不改变存储在字符数组中的任何值。

虽然上面我引用了 C++11,但 C++03 也是如此。


有没有人知道如何在一个非常大的旧代码环境中处理它(一个发条规则来捕捉这个会很好)。

希望您有一个不错的测试套件。否则,对大型遗留代码库进行重大更改是不切实际的。运行测试套件越容易、越快,修复代码就越容易、越快。

在一个非常大的代码库中,审核c_str() 的所有使用可能会非常昂贵。但是,取样并检查它的用途以及可以应用哪些特定的更正可以帮助您衡量问题的规模。根据我的经验,你可以期待各种各样的奇怪事情,但有些会更常见。

Valgrind、std::string 的调试实现和其他工具可以帮助识别一些可能导致真正错误的实例。首先解决这些问题是重中之重。修复可能涉及更新 API 以使其正确或具有明确定义的生命周期要求,以及将 c_str() 的使用切换为生成具有适当生命周期的 C 字符串的东西。您对代码的调查应该已经让您了解了各种生命周期要求和必要的 c 字符串创建实用程序。

c_str() 的其他用途可以随着时间的推移逐渐修改为较低优先级的辅助活动。

诸如在 clang 之上构建的用于重构或语义搜索的一些工具是识别问题和进行大规模更改的另一种选择,但是将遗留代码变成对 clang 工具而言足够合法的形式通常是一项艰巨的任务来处理它。 (Here's a talk 关于谷歌在这方面所做的一些工作。他们最近还就谷歌提供的这项技术的商品版本进行了更多的讨论。)


我经常很难说服人们“未定义的行为”实际上是一个问题,即使在实际上没有观察到不良影响的情况下也是如此。当您编写新代码时,请记住,如果您符合 C++ 规范,未来维护者的生活将会变得更加轻松。即使某些“坏”代码的特定实例现在不会给您带来问题,但随着编译器和库实现的变化,这可能会随着时间的推移而变化。即使规范发生变化,委员会也会仔细考虑对符合旧代码的影响。如果代码不符合标准,那么它真的没有得到任何考虑,你最终会遇到这样的问题。

【讨论】:

    【解决方案3】:

    g++ 是否满足std::string C++11 的要求?

    没有。

    在 C++11 之前,这不会造成大问题,因为 c_str 没有返回指向字符串对象所保存的实际数据的指针,因此更改它并不重要。

    这是不正确的,c_str 总是被允许返回实际数据,而这正是它在所有流行的 C++03 实现中所做的。

    但是在更改之后,这种 COW + 返回实际指针的组合可以并且破坏旧的应用程序(应用程序应该因为糟糕的编码而受到它的影响)。

    经过什么改变? G++ 没有改变它的std::string,所以如果你的旧程序被 G++ 破坏了,那么它总是被破坏了。

    请注意,即使不强制转换 const,也可能会通过调用 c_str、保存指针然后调用非 const 方法(这将导致写入)导致指针失效。

    您的第二个示例没有证明任何失效,因为在 COW 实现中,temp 仍然是一个有效的指针,而 x 存在。但是可以修改示例以使 temp 无效,这在 C++11 中是不允许的,[string.require]/6 表示在 C++11 中 y[0] 不允许使 @987654330 返回的指针无效@。

    【讨论】:

    • 无效我的意思是它指向一个不期望的值。即,如果您要打印它,您将不会获得与兼容实现相同的输出。请参阅 cout 行上方的注释。但答案很好,谢谢!
    • 好的,我明白了。标准库是指使指针和引用失效,具有不同的特定含义。
    【解决方案4】:

    当时其他答案是正确的,但截至目前,根据the GCC 5.x Change Log,gcc 5 提供的 libstdc++ 现在完全符合 C++11。

    【讨论】:

      猜你喜欢
      • 2019-09-15
      • 2021-12-08
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      • 2021-08-30
      • 2017-11-27
      • 2019-05-11
      • 2016-04-26
      相关资源
      最近更新 更多