只有短字符串才能使用 c_str() 函数。

8

我正在使用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 的注释,它会正确地打印:

castString: hello

这是怎么可能的?

5个回答

17

您正在调用 getString() 成员函数返回的临时字符串对象上的 c_str。由 c_str() 返回的指针只在原始字符串对象存在期间有效,因此当您在赋值 castString 的行末时,它最终成为一个悬空指针。从官方意义上讲,这会导致未定义的行为。

那么为什么短字符串可以工作呢?我怀疑您看到的是短字符串优化的效果,一种优化方式,在长度小于某个值的字符串中,字符数据被存储在字符串对象本身的字节中,而不是在堆中。可能返回的临时字符串存储在栈上,因此当它被清理时,没有发生任何释放,并且指向过期字符串对象的指针仍然保留着旧的字符串字节。这似乎与您看到的一致,但这仍然不意味着您所做的是一个好主意。 :-)


谢谢,我明白了!有趣的是,在我其他系统上,这段代码运行得非常好,已经运行了几个月了。只是现在我更新了Ubuntu从14.04到15.10并重新安装了所有东西,它突然变了。我想那就是未定义的行为... - Potaito
谢谢您的详细解释。您能否解释一下您所说的“不是一个好主意”是什么意思?或者您是在指我错误地对由函数返回的对象调用c_str()函数? - Potaito
我猜简单的解决方法是:std::cout << "castString: " << (const unsigned char *)box.getString().c_str() << std::endl; - chqrlie
1
@potAito 有趣的是,这段代码在我另一个系统上运行了数月而没有问题 -- 欢迎来到C++编程的世界,在这里,能够工作多年的程序可能会存在错误,只有通过对代码进行简单更改或在其他系统上运行才能发现。 - PaulMcKenzie
@potAito:确实,不能将c_str()的返回值存储在临时对象中以供稍后使用。 - chqrlie
显示剩余2条评论

6
box.getString()是一个匿名临时对象。在变量生命周期结束后,c_str()就无效了。
所以,在你的情况下,当你到达std::cout时,c_str()已经失效了。读取指针内容的行为是未定义的。
(有趣的是,由于std::string以不同的方式存储短字符串,你的短字符串的行为可能会有所不同。)

2
我会把“长度”替换成“生命”。 - NathanOliver
谢谢你的帮助,我现在明白了! - Potaito

6

当你通过值返回时

box.getString()是一个临时的对象,因此

box.getString().c_str()只在表达式期间有效,之后它就成为了悬挂指针。

您可以通过以下方式解决这个问题:

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

感谢您的帮助! - Potaito
只是为了明确:这个修复问题的原因是因为现在getString()的返回值是对StringBox持有的_text的引用(而不是你代码中的匿名临时对象),所以.c_str()的结果在_text的生命周期内或任何其他修改_text的操作中都是有效的。 - Andre Kostur

5

box.getString() 产生一个临时对象。调用 c_str() 函数得到这个临时对象的指针。当临时对象被销毁,立即之后指针就会失效,也就是成为了悬垂指针

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


5
首先,无论字符串的长度如何,您的代码都存在UB问题:在字符串的末尾。
castString = (const unsigned char *)box.getString().c_str();
< p > getString 返回的字符串已被销毁,castString 是对销毁的字符串对象内部缓冲区的悬空指针。

你的代码“运行”得起来的原因可能是小字符串优化:短字符串通常保存在字符串对象本身中,而不是保存在动态分配的数组中,显然,在你的情况下,该内存仍然可以访问且未修改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接