【问题标题】:Is `string.assign(string.data(), 5)` well-defined or UB?`string.assign(string.data(), 5)` 是定义明确的还是 UB?
【发布时间】:2020-02-13 16:44:35
【问题描述】:

一位同事想写这个:

std::string_view strip_whitespace(std::string_view sv);

std::string line = "hello  ";
line = strip_whitespace(line);

我说返回string_view 让我感到不安先验,而且这里的别名在我看来就像是UB。

我可以肯定地说line = strip_whitespace(line) 在这种情况下等同于line = std::string_view(line.data(), 5)。我相信会调用string::operator=(const T&) [with T=string_view],它被定义为等同于line.assign(const T&) [with T=string_view],它被定义为等同于line.assign(line.data(), 5),它被定义为这样做:

Preconditions: [s, s + n) is a valid range.
Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).
Returns: *this.

但这并没有说明出现混叠时会发生什么。

我昨天在 cpplang Slack 上问了这个问题,得到的答案好坏参半。在这里寻找超级权威的答案,和/或对真实库供应商实施的实证分析。


I wrote test cases 代表string::assignvector::assigndeque::assignlist::assignforward_list::assign

  • Libc++ 使所有这些测试用例都能正常工作。
  • Libstdc++ 使它们都可以工作,除了 forward_list,它会出现段错误。
  • 我不知道 MSVC 的库。

libstdc++ 中的段错误让我希望这是 UB;但我也看到 libc++ 和 libstdc++ 至少在常见情况下会付出巨大努力。

【问题讨论】:

  • 您是否使用 ASan 编译测试用例和/或在 Valgrind 下运行它们?这将消除代码是否会导致访问冲突的猜测,尽管它可能仍然在实践中起作用,而不是按照定义。
  • "如果 basic_string 的任何成员函数或运算符抛出异常,则该函数或运算符对 basic_string 对象没有其他影响。" -- 这会强制在现有存储被释放之前进行存储分配,因此如果分配失败,则会引发异常,而不会更改*this。但我认为没有什么可以阻止现有存储被重用,在这种情况下,这变得未指定,因为复制存储的语义是未指定的。
  • 对于提到的序列容器,肯定是UB,因为在[tab:container.seq.req]中违反了assign的前提条件。

标签: c++ stl undefined-behavior


【解决方案1】:

除非有几个你的例外,否则在字符串 assign 上调用非常量成员函数(即 assign)[...] 指针 [...] 指向其元素。这违反了assign 上的precondition [s, s + n) 是一个有效范围,因此这是未定义的行为。

请注意,string::operator=(string const&) 具有专门用于使自分配成为无操作的语言。

【讨论】:

  • 那么究竟什么是无效点以及需要满足前置条件的点呢?答案似乎是假设在调用成员函数之后前提条件必须成立。
  • @walnut 我不是语言律师(也不是具有特别扩展的 C++ 知识的人),但是当我们反转您的场景时,我们可以提出一个问题 - 范围是否可以在 期间无效 assign 的执行?如果是,那么我们必须在 assign 的实现中设置一个特定点 inside 来标记何时会发生失效,我相信这不是 C++ 会做的事情。不过我可能是错的。
  • @Fureeish 我也不知道,但请参阅例如LWG issue 526,关闭为“不是缺陷”,在其关闭建议中提到,如果value 进入向量本身,std::vector::insert(iterator pos, const T& value) 必须工作,因为标准没有指定允许它不工作,即使该引用可能被调用无效。
  • @walnut "必须工作,因为标准不允许它不能工作。" - 喜欢它 。 Sooo...值得问一下在实践中会发生什么吗?在这种情况下,是否需要实现参数的副本?你怎么能现实地实现它..?我听说过标准要求编译器做不可能的事情——这是其中一种情况吗?无论如何,感谢您的评论!
  • @Fureeish 实际上,我之前的(现已删除)示例实际上并没有测试我想要测试的内容。 Here 是一个固定示例,表明 libc++ 和 libstdc++ 在根据需要进行重新分配之前实际上都会进行复制。
猜你喜欢
  • 2016-04-06
  • 2014-04-12
  • 1970-01-01
  • 2019-02-21
  • 2018-12-05
  • 2021-10-23
  • 2019-07-28
  • 2011-02-08
相关资源
最近更新 更多