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

我有一个必须可以从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? } 

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

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

接下来,除非返回指向静态字符串的指针,否则不能返回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 ++书籍中常见的错误之一。

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

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

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

  • 想到了strdup()。
    常见的扩展但实际上不在标准中。

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

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

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

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

int get_string(const char * buffer);

然后填满缓冲区。 但是返回一个指向malloced数据的点很好。

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

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

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

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

 CreateString() 

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

 GetString() 

什么时候不是问题。

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

如果线程安全不重要,

 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更改为线程安全时会发生这种情况。

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

 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()将避免意外覆盖缓冲区。

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

如果事先不知道大小,请考虑将回调函数传递给函数,函数接收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 ); } 

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

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

  1. 来电者通过缓冲区。
    1. 必要的大小是记录的,不会传递,太短的缓冲区是未定义的行为 : strcpy()
    2. 记录并传递必要的大小,错误由返回值发出信号: strcpy_s()
    3. 必要的大小是未知的,但可以通过调用缓冲区长度为0的函数来查询: snprintf
    4. 必要的大小是未知的,无法查询,只要返回传递大小的缓冲区。 如果需要,必须进行额外的调用以获得其余的: fread
    5. 必要的大小是未知的,无法查询,并且传递太小的缓冲区是未定义的行为 。 这是一个设计缺陷,因此在较新版本中不推荐使用该函数,为了完整性,这里仅提及: gets
  2. 呼叫者通过回调:
    1. callback-function获取一个context-parameter: qsort_s
    2. callback-function没有上下文参数。 获取上下文需要魔术: qsort
  3. 调用者传递一个分配器:在C标准库中找不到。 但是,所有支持allocator的C ++容器都支持它。
  4. Callee合约指定解除分配器。 调用错误的是Undefined Behavior : fopen – > fclose strdup – > free
  5. Callee返回一个包含deallocator的对象:COM-Objects std::shared_ptr
  6. Callee使用内部共享缓冲区: asctime

一般来说,每当用户不得不猜测尺寸或在手册中查找时,他有时会弄错。 如果他没有弄错,后来的修改可能会使他的小心工作无效,所以他曾经是对的并不重要。 无论如何,这种方式就是疯狂(UB) 。

其余的,选择最舒适,最有效的。