如何返回 std::string.c_str() 的值

40
我有一个方法,它返回一个常量字符指针。它使用了一个std::string,并最终返回它的c_str()字符指针。
const char * returnCharPtr()
{
    std::string someString;

    // Some processing!

    return someString.c_str();
}

我从Coverity工具得到了一个报告,上述用法不好。我已经搜索过了,发现char指针在someString被销毁后会立即失效。
鉴于此,如何解决这个问题?如何准确返回一个char指针?
返回std::string可以解决这个问题。但我想知道是否还有其他方法可以做到这一点。

2
你不能返回指向局部对象的指针。请参见这个 - ApproachingDarknessFish
9
这种情况正是 std::string 这样的东西被发明出来的主要原因。你所发明的任何东西都几乎不可避免地会成为 1)std::string 已经做过的重复品,或者2)有缺陷的产品。 - Jerry Coffin
@Mr.C64 移除[C]标签改变了问题的含义。C++/C 互操作性问题与 C++ 问题非常不同,并且会使返回 const char* 更加合理。您有充分的理由移除[C]标签吗? - Yakk - Adam Nevraumont
@user3210526,你是在与C代码进行互操作吗?如果是这样,返回的 char* 的生命周期在C代码中如何管理?如果不是,为什么要在帖子上打上[C]标签? - Yakk - Adam Nevraumont
11个回答

25

这段代码发生了什么:

const char * returnCharPtr()
{
    std::string someString("something");
    return someString.c_str();
}
  1. 创建一个std::string的实例 - 这是一个具有自动存储期的对象。
  2. 返回该字符串内部存储的指针。
  3. 对象someString被销毁,并清理其内部存储的内存。
  4. 调用该函数的调用者收到一个悬空指针(无效指针),这将导致未定义的行为。

最佳解决方案是返回一个对象

std::string returnString()
{
    std::string someString("something");
    return someString;
}

在调用函数时,请不要这样做:

const char *returnedString = returnString().c_str();

由于返回的 std::string 销毁后,指向 returnedString 的指针仍然存在悬垂指针问题。因此,应该存储整个 std::string

std::string returnedString = returnString();
// ... use returnedString.c_str() later ...

3
为什么要使用 const?现在它就无法被移动了。 - juanchopanza
我遇到了这样的情况:returnString().c_str() == 0(返回的字符串是“m”),但如果我保存返回值,然后在临时变量上调用c_str(),它就可以工作。有什么想法吗? - Rapnar
1
如果想要覆盖std::exceptionwhat()虚函数,以返回任何不是字面字符串的内容,例如返回一些额外的相关运行时信息字符串,那么该怎么办呢?这时就需要使用char*了。我所能想到的唯一解决方案是创建一个静态的std::string,然后使用c_str()来避免返回悬空指针。但这似乎是一个太丑陋的解决方案,而且对于只需要打印一次的字符串来说,我讨厌使用static生命周期。 - Yuval
1
@Yuval 无法将其设置为 static,因为这样不会保证线程安全。规范的解决方案是在异常类中使用常规的 std::string,填充它,并返回该字符串的 c_str()。只要您的异常对象存在,它就会一直存在,符合 what() 接口的规范。 - Yakov Galka
有趣的是,您推荐了OP明确不想要的东西;-) - Welgriv
显示剩余2条评论

17

在C++中,最简单的方法就是返回一个std::string(由于像RVO和C++11移动语义这样的优化,它也非常高效):

std::string returnSomeString()
{
    std::string someString;

    // some processing...

    return someString;
}
如果您确实需要一个原始的 C char* 指针,您可以在返回的值上调用.c_str(),例如:
// void SomeLegacyFunction(const char * psz)

// .c_str() called on the returned string, to get the 'const char*'
SomeLegacyFunction( returnSomeString().c_str() );

如果你确实想要从函数中返回一个char*指针,你可以在堆上动态地分配字符串内存(例如使用new[]),并返回该指针:

// NOTE: The caller owns the returned pointer,
// and must free the string using delete[] !!!
const char* returnSomeString()
{
    std::string someString;

    // some processing...

    // Dynamically allocate memory for the returned string
    char* ptr = new char[someString.size() + 1]; // +1 for terminating NUL

    // Copy source string in dynamically allocated string buffer
    strcpy(ptr, someString.c_str());

    // Return the pointer to the dynamically allocated buffer
    return ptr;
}

另一种选择是将目标缓冲区指针和缓冲区大小(以避免缓冲区溢出!)作为函数参数提供:

void returnSomeString(char* destination, size_t destinationSize)
{
    std::string someString;

    // some processing...

    // Copy string to destination buffer.
    // Use some safe string copy function to avoid buffer overruns.
    strcpy_s(destination, destinationSize, someString.c_str());
}

3
值得注意的是,第二个例子可能不是一个好主意。调用者不会期望他们必须删除该指针,并且很可能会导致内存泄漏。 - marsh
3
@marsh 需要调用者自己检查是否拥有返回的指针。 - siwmas
@FabioTurati 谢谢。当然,我是指字符串大小,而不是指针。已修复。 - Mr.C64

9

由于这个问题标记为C,因此请执行以下操作:

#define _POSIX_C_SOURCE 200809L
#include <string.h>

const char * returnCharPtr()
{
  std::string someString;

  // some processing!.

  return strdup(someString.c_str()); /* Dynamically create a copy on the heap. */
}

如果函数返回值已经没有用处了,不要忘记使用free()来释放它。


1
我没有看到 C 标志,它改变了吗? - Yakk - Adam Nevraumont
1
@Yakk:原始帖子(http://stackoverflow.com/revisions/22330250/1)带有C标签。 - alk
谢谢,由于某种类似但更加复杂的问题导致的一个错误,我浪费了整个下午。不管怎样,非常感谢。 - Tommaso Thea Cioni
1
@alk 让调用者释放内存不是一个好主意。 :( - John

5

好的,COVERITY是正确的。你当前的方法会失败的原因是因为在函数内部创建的std::string实例只有在函数运行期间才有效。一旦程序离开函数的作用域,std::string的析构函数就会被调用,这将是你的字符串的终结。

但如果你想要一个C字符串,怎么样...

const char * returnCharPtr()
{
    std::string someString;

    // some processing!.

    char * new_string = new char[someString.length() + 1];

    std::strcpy(new:string, someString.c_str());

    return new_string;
}

但是等等……这几乎就像返回一个std::string一样,不是吗?
std::string returnCharPtr()
{
    std::string someString;

    // some processing!.

    return new_string;
}

这将把您的字符串复制到函数作用域之外的新字符串中。它可以工作,但确实会创建一个新副本。

由于返回值优化,这将不会创建副本(感谢所有纠正!)。

因此,另一个选项是将参数作为参数传递,这样您就可以在函数中处理字符串,但不会创建新副本:

void returnCharPtr(std::string & someString)
{
    // some processing!.
}

如果你想使用C-Strings,你需要注意字符串的长度:

void returnCharPtr(char*& someString, int n) // a reference to pointer, params by ref
{
    // some processing!.
}

4
不要返回右值引用,它和左值引用有相同的问题。即使在 C++11 之前,(N)RVO 可以处理昂贵的返回拷贝,而在 C++11 中,如果可以移动对象并且 (N)RVO 不起作用,对象也会被自动移出。请注意不要改变原文意思。 - chris
1
你刚刚犯了你指责 OP 的同样的错误!</笑话> Rvalue 引用仍然是引用,返回一个并不改变它仍然是对本地变量的引用这一事实。 - R. Martinho Fernandes
补充一下Chris所说的,你返回一个右值引用的代码甚至无法编译,你需要return move(new_string);(然后你需要处理悬空引用)。而且你的C字符串示例根本没有意义;函数接受一个指向const的指针,但意图是对输入字符串进行操作?此外,该签名假定调用者知道结果的长度。 - Praetorian
另外进行一处修正:在你的第一个例子中,new_string 的长度少了1(空字符)。 - stefaanv
这将把你的字符串复制到函数范围之外的新字符串中。它可以工作,但它确实创建了一个新的字符串副本。不,那并不是复制字符串,它相当高效地移动了字符串(实际上,代码省略了移动操作,但这是另一个帖子的话题)。 - Yakk - Adam Nevraumont
显示剩余2条评论

3

其他答案没有提到的解决方案。

如果您的方法是一个类的成员,可以这样写:

class A {
public:
    const char *method();
};

如果类实例将在指针的有用期限之外继续存在,您可以这样做:

class A {
public: 
    const char *method() {
        string ret = "abc";
        cache.push_back(std::move(ret));
        return cache.last().c_str();
    }
private:
    vector<string> cache; //std::deque would be more appropriate but is less known
}

这样指针将在A被销毁之前有效。

如果该函数不是类的一部分,它仍然可以使用一个类来存储数据(例如函数的静态变量或可全局引用的外部类实例,甚至可以是类的静态成员)。可以采取机制在一定时间后删除数据,以避免永久保留。


我来这里补充一下答案。为了解决缓存大小的问题,将缓存设置为固定大小,并将其用作循环缓冲区。如果用户嵌套的函数调用比缓存处理的能力更深,则可能会导致意外情况,但这可能是可以接受的记录限制。我发现缓存大小通常为8就足够了。 - Dúthomhas

3
最好的方法是返回一个自动进行内存管理的std::string。如果你确实想返回一个指向由returnCharPtr内分配的一些内存的const char*,那么它必须由其他人显式地释放。
建议使用std::string

2
问题在于someString在函数结束时被销毁,而函数返回指向不存在的数据的指针。
不要返回可能在使用返回的字符指针之前被销毁的字符串的.c_str()
而应该...
const char* function()
{
    std::string someString;
    // some processing!
    return someString.c_str();
}

//...

useCharPtr(function());

使用

std::string function()
{
    std::string someString;
    // some processing!
    return someString;
}

//...

useCharPtr(function().c_str());

2
如果你有更改returnCharPtr返回值的自由,那么请将其更改为std::string。这是返回字符串最干净的方法。如果不能更改,则需要为返回的字符串分配内存,并从std:: string 复制到其中,并返回分配的内存的指针。您还需要确保在调用函数中删除内存。由于调用者将负责释放内存,因此我建议将返回值更改为char*
char* returnCharPtr() 
{
    std::string someString;

    // some processing!.

    char* cp = new char[someString.length()+1];
    strcpy(cp, someString.c_str());
    return cp;
}

2
你的选择是:
返回 std::string 将一个缓冲区传递给 returnCharPtr(),该缓冲区将保存新的字符缓冲区。这需要你验证提供的缓冲区是否足够大以容纳字符串。
returnCharPtr() 中创建一个新的 char 数组,将缓冲区复制到新数组中并返回指向它的指针。这要求调用者显式地对使用 new 创建的对象进行 delete [],或立即将其放入智能指针类中。
如果你返回一个智能指针,这个解决方案会更好,但直接返回一个 std::string 更加合理。
选择第一种方法;返回 std::string。 这是最简单和最安全的选择。

1
你可以传递一个指向你的字符串的指针,并直接让该方法操纵它(即完全避免返回)。
void returnCharPtr(char* someString)
{    
    // some processing!
    if(someString[0] == 'A')
       someString++;
}

1
这假设调用者知道字符串的长度,而这往往并非大多数情况。 - Praetorian

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