将动态分配的内存从C ++返回到C.

时间:2008-11-12 08:18:50

标签: c++ c memory-management

我有一个必须可以从C等使用的DLL,所以我不能像正常人那样使用字符串对象等,但我不确定如何安全地执行此操作..

const char *GetString()
{
    std::stringstream ss;
    ss << "The random number is: " << rand();
    return ss.str().c_str();
}
当ss从堆栈中掉落时,c字符串会被破坏吗?我假设是这样......

另一种选择可能是在堆上创建一个新字符串,但是要解除分配的是什么?

const char *GetString()
{
    std::stringstream ss;
    ss << "The random number is: " << rand();
    char *out = new char[ss.str().size()];
    strcpy(ss.str().c_str(), out);
    return out;//is out ever deleted?
}

指向其他东西以及字符串的指针也是如此。

10 个答案:

答案 0 :(得分:8)

第一个变体不起作用,因为你将一个指针返回到一个堆栈对象,它将被销毁。 (更确切地说,你返回一个指向堆内存的指针,它将被删除()。)更糟糕的是,它甚至可能工作一段时间,如果没有人覆盖内存,使得调试非常困难。

接下来,除非返回指向静态字符串的指针,否则不能返回const char *:

const char *GetString()
{
    return "a static string in DATA segment - no need to delete";
}

您的第二个变体存在将使用new()分配的内存返回到将调用free()的C程序的问题。那些可能不兼容。

如果将字符串返回给C,则有两种方法:

char *GetString()
{
    std::stringstream ss;
    ss << "The random number is: " << rand();
    return strdup( ss.str().c_str() ); // allocated in C style with malloc()
}

void foo()
{
    char *p = GetString();
    printf("string: %s", p));
    free( p ); // must not forget to free(), must not use delete()
}

或:

char *GetString(char *buffer, size_t len)
{
    std::stringstream ss;
    ss << "The random number is: " << rand();
    return strncpy(buffer, ss.str().c_str(), len); // caller allocates memory
}

void foo()
{
    char buffer[ 100 ];
    printf("string: %s", GetString(buffer, sizeof( buffer ))); // no memory leaks
}

取决于您的记忆处理政策。

通常,您不能在C ++中返回指针或对自动对象的引用。这是许多C ++书籍中常见的错误之一。

答案 1 :(得分:3)

多年来,C将其归结为2种标准方法:

  • 来电者通过缓冲区 这有三个版本。
    版本1:传递缓冲区和长度 版本2:文档指定了预期的最小缓冲区大小 第3版:飞行前。函数返回所需的最小缓冲区。调用者第一次使用NULL缓冲区调用两次。
    • 示例:read()
  • 使用在下次调用之前有效的静态缓冲区。
    • 示例:tmpname()

一些非标准的内存返回你必须明确释放的内存

    浮现在脑海中。
  • strdup() 常见的扩展但实际上不在标准中。

答案 2 :(得分:1)

第一个实际上不起作用,因为stringstream在销毁时释放它的空间。因此,如果您尝试取消引用该指针,则程序很可能会崩溃。

您提到的第二个选项是它通常如何完成,并且需要该函数的用户来释放空间。如果这是一个使用该函数的C程序,请确保使用malloc()分配并使用free()自由分配

另一种选择是返回静态字符数组的地址。如果事先知道长度的上限,则这是相关的。更重要的是,只有在不可能同时从两个不同的线程调用该函数时才应该使用它,因为使用静态数组本质上使您的函数不是reentrant

答案 3 :(得分:1)

很明显,无论何时返回指向函数内部分配的内存的指针,解除分配必须来自外部,除非您使用垃圾收集。如果您不想这样做,请在调用GetString()之前分配一个字符缓冲区并将原型更改为

int get_string(const char * buffer);

然后填满缓冲区。但是将一个点返回到malloced数据是可以的。

答案 4 :(得分:0)

如果将ss声明为静态,则可以避免此问题。如果您的程序在单线程环境中运行,这可能是一个很好的解决方案。

答案 5 :(得分:0)

如果要安全地返回它,必须在堆上分配字符串,并使用malloc()i.s.o进行分配。编写C函数时使用new()。

当你返回指针时(并且,与C ++不同,在C中你没有多次真正的选择),解除分配始终是一个问题。没有确切的解决方案。

我在相当一些API中看到的一种处理方法是调用所有函数

CreateString()

当调用者需要释放内存时,

GetString()

那不是问题。

这当然是万无一失的,但是如果给予足够的纪律,这是我看到的最诚实的方法......

答案 6 :(得分:0)

如果线程安全性不重要,

const char *GetString()
{
    static char *out;
    std::stringstream ss;
    ss << "The random number is: " << rand();
    delete[] out;
    char *out = new char[ss.str().size()];
    strcpy(ss.str().c_str(), out);
    return out;//is out ever deleted?
}

然后该函数可以接管解除分配字符串的责任。

如果线程安全很重要,

然后最好的方法是将其作为参数传递,如,

void GetString(char *out, int maxlen);

我观察到当旧的非线程安全API更改为线程安全时会发生这种情况。

答案 7 :(得分:0)

调用函数后,您将希望调用者负责字符串的内存(尤其是对其进行解除分配)。除非你想使用静态变量,但有龙!干净利落的最好方法是让调用者首先分配内存:

void foo() {
  char result[64];
  GetString(result, sizeof(result));
  puts(result);
}

然后GetString应如下所示:

int GetString(char * dst, size_t len) {
  std::stringstream ss;
  ss << "The random number is: " << rand();
  strncpy(ss.str().c_str(), dst, len);
}

传递最大缓冲区长度并使用strncpy()将避免意外覆盖缓冲区。

答案 8 :(得分:0)

到目前为止,答案并没有解决一个非常重要的问题,即如果结果所需缓冲区的长度未知并且可以在调用之间进行更改,即使使用相同的参数(例如从中读取值),该怎么办?数据库),所以我提供了我认为是处理这种情况的最佳方法。

如果事先不知道大小,请考虑将回调函数传递给函数,函数接收const char*作为参数:

typedef void (*ResultCallback)( void* context, const char* result );

void Foo( ResultCallback resultCallback, void* context )
{
     std::string s = "....";
     resultCallback( context, s.c_str() );
}

ResultCallback的实现可以分配所需的内存并复制result指向的缓冲区。我假设是C所以我没有明确地向/ void*投掷。

void UserCallback( void* context, const char* result )
{
    char** copied = context;
    *copied = malloc( strlen(result)+1 );
    strcpy( *copied, result );
}

void User()
{
    char* result = NULL;

    Foo( UserCallback, &result );

    // Use result...
    if( result != NULL )
        printf("%s", result);

    free( result );
}

这是最便携的解决方案,即使是最难以提前知道返回字符串大小的最棘手的情况。

答案 9 :(得分:0)

随着时间的推移,有各种方法可以从函数中返回可变数量的数据。

  1. 来电者通过缓冲区。
    1. 记录了必要的大小但没有通过,Undefined Behavior Undefined Behaviorstrcpy()
    2. 记录并传递必要的大小,错误由返回值表示:strcpy_s()
    3. 必要的大小未知,但可以通过调用缓冲区长度为0的函数来查询:snprintf
    4. 必要的大小是未知的,无法查询,只要返回传递大小的缓冲区即可。如有必要,必须另外打电话来完成剩下的工作:fread
    5. 必要的大小未知,无法查询,传递的缓冲区太小Undefined Behavior。这是一个设计缺陷,因此在较新版本中不推荐使用该功能,此处仅提及完整性:gets
  2. 来电者通过回调:
    1. callback-function获取一个context-parameter:qsort_s
    2. callback-function没有上下文参数。获取上下文需要魔术:qsort
  3. 调用者传递一个分配器:在C标准库中找不到。所有支持分配器的C ++容器都支持它。
  4. Callee合约指定解除分配器。调用错误的是madness (UB)fopen - &gt; fclose strdup - &gt; free
  5. Callee返回一个包含deallocator的对象:COM-Objects std::shared_ptr
  6. Callee使用内部共享缓冲区:asctime
  7. 通常,每当用户必须猜测尺寸或在手册中查找时,他有时会弄错。如果他没有弄错,后来的修改可能会使他的细心工作无效,所以他曾经是对的并不重要。无论如何,这种方式是{{3}}。

    对于其他人,请选择最舒适,最有效的方式。