【问题标题】:Does "&s[0]" point to contiguous characters in a std::string?“&s[0]”是否指向 std::string 中的连续字符?
【发布时间】:2010-12-31 12:18:54
【问题描述】:

我正在做一些维护工作,遇到了类似以下情况:

std::string s;
s.resize( strLength );  
// strLength is a size_t with the length of a C string in it. 

memcpy( &s[0], str, strLength );

我知道使用 &s[0] 如果它是 std::vector 是安全的,但这是对 std::string 的安全使用吗?

【问题讨论】:

  • 使用 &s[0] 是可以的, memcpy() 可以说不那么好。为什么不简单地进行赋值,或者使用字符串的 assign() 成员函数?
  • @Neil Butterworth,这就是我在查看这段代码时问自己的问题...... ;)
  • 随着C++编程经验的积累,你会越来越避免使用memsetmemcpy,并学习其中的道理。这是一个可以增加您体验的内容。

标签: c++ memcpy stdstring c++03


【解决方案1】:

在 C++98/03 标准下,不保证 std::string 的分配是连续的,但 C++11 强制它是连续的。在实践中,我和Herb Sutter 都不知道不使用连续存储的实现。

请注意,&s[0] 始终保证符合 C++11 标准,即使在长度为 0 的字符串情况下也是如此。如果您使用str.begin()&*str.begin(),则无法保证,但对于&s[0],标准将operator[] 定义为:

返回:如果是pos < size(),则为*(begin() + pos),否则是对T类型对象的引用,其值为charT();引用的值不得修改

继续,data() 定义为:

返回: 一个指针p 使得p + i == &operator[](i) 对应[0,size()] 中的每个i

(注意范围两端的方括号)


注意:预标准化 C++0x 不保证 &s[0] 可以使用零长度字符串(实际上,这是明确未定义的行为),并且解释了此答案的旧版本这;这已在后来的标准草案中得到修复,因此答案已相应更新。

【讨论】:

  • 过去几个月我没有遵循该标准,但我的印象是这仍处于 0x 草案中,因此实际上还不需要(或者如果图书馆选择仅在 '03 年实施)。
  • Sutter 在对该帖子的评论中说,“当前的 ISO C++ 确实需要 &str[0] 提供指向连续字符串数据的指针(但不一定以空值结尾!)”,这将在事实上,使 OP 的用法正确。但是,我在标准中找不到任何说明(至少它不在 21.3.4 lib.string.access 中)。
  • 我认为这可能是对的; std 缺陷 530 说 operator[] 是连续的,但不保证迭代器接口是连续的,并引用 23.4.4。我正在挖掘我的标准来检查。
  • 我跳过了 Sutter 帖子中的缺陷链接,这就是我错过它的原因。无论如何,缺陷说“我们几乎已经需要连续性”(关键词:几乎),我看不出它对 multiset 的引用是如何相关的(basic_string 是一个带有随机访问迭代器的序列)。然而,我认为重要的一点是“鉴于 data() 的存在,以及 operator[] 的定义以及数据方面的定义,我认为不可能编写一个有用且符合标准的不连续的基本字符串。”
  • James:几乎是因为s[s.length()] 的空值不必是连续的。 &s[n] + 1 == &s[n + 1] 必须为所有 n 其中0 <= n < s.length() - 1 为真。 21.3.4/1 要求 s[n] 必须返回与 s.data()[n] 相同的对象(对于 n
【解决方案2】:

从技术上讲,不需要,因为 std::string 不需要将其内容连续存储在内存中。

然而,在几乎所有的实现中(我知道的每一个实现),内容都是连续存储的,这会“工作”。

【讨论】:

  • 你能找出一些它不起作用的实现吗?
  • 不。但如果你愿意,你可以做出这样的实现。
  • @Neil:你有那个 TC 的链接/参考吗?
  • Aargh - 对不起,脑子进水了 - 我想的是矢量,而不是字符串。全面道歉。
  • 没问题。不过,我仍然对 Sutter 谈论的关于 &str[0] 的内容感到好奇(参见我对 Todd 回答的评论)。
【解决方案3】:

使用安全。我认为大多数答案曾经是正确的,但标准发生了变化。引用 C++11 标准,basic_string 一般要求 [string.require],21.4.1.5,说:

basic_string 对象中的类字符对象应连续存储。也就是说,对于任何 basic_string 对象 s,标识 &*(s.begin() + n) == &*s.begin() + n 应适用于 n 的所有值,例如 0

在此之前,它说所有迭代器都是随机访问迭代器。这两个位都支持您的问题的使用。 (此外,Stroustrup 显然在他的最新著作中使用了它;))

在 C++11 中完成此更改并非不可能。我似乎记得当时为向量添加了相同的保证,该版本还获得了非常有用的 data() 指针。

希望对您有所帮助。

【讨论】:

  • 问题是 c++11 之前的问题(它被标记为这样)。你是对的,c++11 正式保证这样做是安全的。
【解决方案4】:

读者应该注意,这个问题是在 2009 年提出的,当时 C++03 标准是当前的出版物。此答案基于该版本的标准,其中std::strings 保证使用连续存储。由于这个问题不是在特定平台(如 gcc)的上下文中提出的,因此我对 OP 的平台不做任何假设——特别是,它是否使用了string 的连续存储。

合法吗?也许,也许不是。安全的?可能,但也可能不是。好代码?好吧,我们不要去那里......

为什么不直接做:

std::string s = str;

...或:

std::string s(str);

...或:

std::string s;
std::copy( &str[0], &str[strLen], std::back_inserter(s));

...或:

std::string s;
s.assign( str, strLen );

?

【讨论】:

  • std::string s (str, strLen);(在嵌入空值或缺少空值终止的情况下,最短形式与问题中的原始行为相同。)
  • @Downvoter:请注意,这个问题是在 2009 年提出的,属于 C++03 标准。如果您因为质疑我回答的技术准确性或其他原因而投反对票,我将不胜感激。
【解决方案5】:

这通常安全,无论内部字符串序列是否连续存储在内存中。除了连续性之外,可能还有许多其他实现细节与std::string 对象如何存储受控序列有关。

一个真正的实际问题可能是以下。 std::string 的受控序列不需要存储为以零结尾的字符串。然而,在实践中,许多(大多数?)实现选择将内部缓冲区增大 1 并将序列存储为以零结尾的字符串,因为它简化了 c_str() 方法的实现:只需返回一个指向内部缓冲区的指针,然后你完成了。

您在问题中引用的代码不会将数据复制到内部缓冲区中以零终止。很可能它根本不知道这个std::string 的实现是否需要零终止。很可能它依赖于在调用resize 之后用零填充内部缓冲区,因此由实现分配给零终止符的额外字符方便地预设为零。所有这些都是一个实现细节,这意味着该技术依赖于一些相当脆弱的假设。

换句话说,在某些实现中,您可能不得不使用strcpy,而不是memcpy 来强制数据进入这样的受控序列。而在其他一些实现中,您必须使用memcpy 而不是strcpy

【讨论】:

  • 在调用resize 之后,您可以非常确定内部字符串是否按照实现的要求以空值结尾。在调用resize 之后,毕竟你必须有一个有效的 n 个字符的字符串(根据需要用零个字符填充)。 - 但是,它表明对std::string 类缺乏理解:memcpy 的使用要么是出于无知,要么是出于对性能的误导尝试(因为resize 调用代码最终将值分配给缓冲区两次)。
  • @UncleBens:我不明白你的第一句话。无论如何,是的,语言标准保证大小增加的resize 调用用零填充字符串。但是,标准只保证填充到请求的大小(在这种情况下为strLength),但如果实现分配了一个,标准中不能保证该额外字符。
  • 从 C++11 开始,当字符串不为空时,内部缓冲区要求以空值结尾,因为data()c_str() 都是要求返回相同的缓冲区,c_str()要求始终返回指向空终止缓冲区的指针(data() 允许在以下情况下返回 nullptr空的)。在 C++11 之前,内部缓冲区不需要以 null 终止(甚至是连续的),但大多数实现是因为它简化了 c_str() 的实现
【解决方案6】:

代码可能有效,但更多的是靠运气而不是判断,它对实现做出了无法保证的假设。我建议确定代码的有效性是无关紧要的,而它是一个毫无意义的过度复杂化,很容易简化为:

std::string s( str ) ;

或者如果分配给现有的 std::string 对象,只需:

s = str ;

然后让std::string自己决定如何实现结果。如果您打算诉诸这种废话,那么您最好不要使用 std::string 并坚持使用,因为您正在重新引入与 C 字符串相关的所有危险。

【讨论】:

  • 我实际上不能确定分配的字符串是否以空值结尾。所以我能做的最好的可能是 s.assign(ptr, ptrLength);我认为这仍然是一个改进。
  • 使用构造函数形式:std::string s (str, strLen);
猜你喜欢
  • 2016-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-01
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
相关资源
最近更新 更多