2

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

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

4

10 回答 10

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++ 书籍中分析的常见错误之一。

于 2008-11-12T08:32:48.500 回答
3

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

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

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

  • strdup() 浮现在脑海中。
    通用扩展,但实际上不在标准中。
于 2008-11-12T12:08:22.340 回答
1

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

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

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

于 2008-11-12T08:28:14.733 回答
1

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

int get_string(const char* buffer);

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

于 2008-11-12T08:29:43.857 回答
0

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

于 2008-11-12T08:30:06.213 回答
0

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

当您返回指针时(并且,与 C++ 不同,在 C 中您很多时候没有真正的选择),释放总是一个问题。真的没有一个确定的解决方案。

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

CreateString()

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

GetString()

当这不是问题时。

这当然不是万无一失的,但如果有足够的纪律,这是我见过的最好的方法,说实话......

于 2008-11-12T08:30:28.413 回答
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 更改为线程安全时会发生这种情况。

于 2008-11-12T09:16:27.633 回答
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() 将避免意外覆盖缓冲区。

于 2008-11-12T09:28:13.740 回答
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 );
}

这是最便携的解决方案,甚至可以处理无法提前知道返回字符串大小的最棘手的情况。

于 2014-06-03T19:56:01.293 回答
0
于 2014-06-03T21:29:41.443 回答