强制转换 c_str() 仅适用于短字符串

Casting c_str() only works for short strings

本文关键字:适用于 字符串 str 转换      更新时间:2023-10-16

我在C++中使用了一个 C 库并编写了一个包装器。有一次我需要将std::string转换为 c 样式字符串。有一个带有函数的类,它返回一个字符串。如果字符串较短,则强制转换返回的字符串有效,否则无效。下面是一个简单且简化的示例来说明该问题:

#include <iostream>
#include <string>
class StringBox {
public:
  std::string getString() const { return text_; }
  StringBox(std::string text) : text_(text){};
private:
  std::string text_;
};
int main(int argc, char **argv) {
  const unsigned char *castString = NULL;
  std::string someString = "I am a loooooooooooooooooong string";  // Won't work
  // std::string someString = "hello";  // This one works
  StringBox box(someString);
  castString = (const unsigned char *)box.getString().c_str();
  std::cout << "castString: " << castString << std::endl;
  return 0;
}

执行上面的文件会将其打印到控制台:

castString:

而如果我交换someString上的注释,它会正确打印

投线:你好

这怎么可能?

您正在对由 getString() 成员函数重新调整的临时字符串对象调用c_strc_str()返回的指针仅在原始字符串对象存在时才有效,因此在您分配castString的行的末尾,它最终会成为悬空指针。正式地,这会导致未定义的行为。

那么为什么这对短字符串有效呢?我怀疑您看到了短字符串优化的效果,这是一种优化,对于小于特定长度的字符串,字符数据存储在字符串对象本身的字节内而不是堆中。返回的临时字符串可能存储在堆栈上,因此在清理它时未发生解除分配,指向过期字符串对象的指针仍保存旧的字符串字节。这似乎与你所看到的一致,但它仍然不意味着你正在做的事情是一个好主意。:-)

box.getString()是一个

匿名的临时c_str()仅对变量的长度有效。

因此,在您的情况下,c_str()在您到达std::cout时无效。读取指针内容的行为未定义

(有趣的是,由于std::string以不同的方式存储短字符串,短字符串的行为可能会有所不同。

按值返回时

box.getString()是暂时的,所以

box.getString().c_str()仅在表达式期间有效,那么它是一个悬空指针。

你可以用

const std::string& getString() const { return text_; }

box.getString()生成一个临时的。调用 c_str() 会给你一个指向临时的指针。在临时不复存在后,即立即,指针无效,一个悬空的指针

使用悬空指针是未定义的行为。

首先,你的代码具有与字符串长度无关的 UB:

castString = (const unsigned char *)box.getString().c_str();

getString返回的字符串被销毁,castString是指向已销毁字符串对象的内部缓冲区的悬空指针。

您的代码"工作"小字符串的原因可能是小字符串优化:短字符串(通常)保存在字符串对象本身中,而不是保存在动态分配的数组中,显然该内存在您的情况下仍然可以访问且未修改。