分配一个指向临时对象的指针——如何防止它?

Posted

技术标签:

【中文标题】分配一个指向临时对象的指针——如何防止它?【英文标题】:Assigning a pointer to a temporary object -- how to prevent it? 【发布时间】:2013-12-27 00:51:42 【问题描述】:

我的班级有一个具有以下原型的方法:

std::string
Block::get_field(std::string rec_type, std::string field)  ... 

它从映射中检索一个值,将其转换为字符串,然后返回该字符串。

有人使用我的类如下:

char * buf = block.get_field(my_rec, my_field).c_str();

除非我弄错了,否则这是将 buf 设置为指向临时字符串。不久之后,当他查看 *buf 时,它已损坏。

显然我更希望我的用户写:

std::string buf = block.get_field(my_rec, my_field);

作为 Block 的作者,我如何防止此类滥用行为?或者有什么方法可以支持这种用法?

更新 1: 直到我更改了映射的实现,从直接保存值到保存“内存引用”——一个长度和一个指向缓冲区的指针,这个 bug 才被发现。但是get_field 总是返回一个字符串——不是string*string&——所以我认为它总是返回一个临时的。我不明白为什么它之前没有中断(我也很尴尬;我声称我的更改不会影响 API)。

我准备通知用户他必须修改他的代码,但我希望能够引用他违反的“规则”,并可能解释他之前是如何“走运”的.

更新 2: 似乎有可能(参考更新 1),刚才出现错误的原因是我的“幕后”更改要求我添加以下开关g++ 4.4.7:-std=gnu++0x,这可能影响了临时对象的回收方式。

【问题讨论】:

记录下来,然后上路。 std::stringstream 也有同样的问题。 这是一个直接的错误(在您的用户方面)。 嗯,返回隐式转换为 std::string 并在类型上禁止 .c_str() 的内容? 无需记录。我认为你的用户会知道 C++ 是公平的。否则,您需要在每个函数之前的注释中添加完整的语言教程。 @KerrekSB,我可以选择任何一种方式。我猜这和字符串流之一会很高兴提醒。不是要专门学习的东西,而是有助于减少错误的提醒。 【参考方案1】:

大多数情况下,您必须假设您的代码的用户是有能力的。

但这是一个政治世界。因此,有时您可能必须尽最大努力防止事故发生。例如,

#include <assert.h>
#include <string>

class StdString: public std::string

private:
    using std::string::c_str;
    using std::string::data;
public:
    StdString( char const* const s ): std::string( s )  assert( s != nullptr ); 
;

StdString foo()  return "Bah!"; 

int main()

    std::string const   a = foo();
    char const* const   b = foo().c_str();      //! Nyet.

也许为了更好的衡量标准,在那里加入一个移动构造函数,但通常完美的效率与完美的安全性和完美的清晰度相冲突,例如可以有 2 但没有 3。

【讨论】:

另一方面,char const* const b = a.c_str(); 是安全的,不是吗? 这个断言永远不会触发,不是吗? string 构造函数在获得空指针时已经抛出,或者至少此时它是 UB。 @KerrekSB:“它是 UB”是正确的可能性,“将已经抛出”是错误的可能性:不能保证抛出或失败,这就是 assert 的原因。 【参考方案2】:

你真的不应该阻止它,但如果你发现这种情况经常发生,你可以将原型更改为:

void
Block::get_field(std::string& ret, std::string rec_type, std::string field)  ... 

引用:std::string&amp; ret 将在调用您的方法之前强制 std::string 存在,而他们仍然可以做一些事情来破坏他们的记忆,这将发生在“他们的代码”中。像这种糟糕的代码这样简单的事情很难预防:

char * foo()

    std::string buffStr;
    block.get_field(bufStr, my_rec, my_field)
    return buffStr.c_str();         // DO NOT DO THIS - Example of bad code

但至少有一个内存检查器会找到他们的分配,而不是你的

更新 1: c_str() 返回的内存的生命周期是生成它的 string 的生命周期,除非 string 被更改,在这种情况下生命周期可能会结束。基本上,这是未定义的行为,有时可能会起作用,而其他情况则不起作用。您的更改可能已经改变了观察到的行为,但用户代码在任何时候都不符合。其他因素也可能破坏它。基本上,它总是坏掉,从你的描述看来没有理由道歉。

【讨论】:

所以,听起来他的错误的症结在于使用 c_str() 而不了解它的陷阱。 Stroustrup 谈到了保持对象的“不变性”的重要性,而 c_str() 允许您直接跳过接口并进入实现。那么,我猜 c_str() 应该不被接受。 @Chap 它本身不是 c_str(),虽然它发现了许多陷阱,但问题是 string“拥有”内存,所以你必须保留它。只要你能保证string 在附近,使用 c_str() 基本上是安全的。您发现的问题是,情况并非总是如此。 (人们也知道从 c_str() 返回时调用 strtok)

以上是关于分配一个指向临时对象的指针——如何防止它?的主要内容,如果未能解决你的问题,请参考以下文章

C++11:智能指针

smart-pointer

smart-pointer

smart-pointer

动态内存分配与指向它的指针变量

如何防止我的程序出现此分段错误(核心转储)?