使用malloc分配空间并返回字符串?

5
我正在创建一个返回字符串的函数。字符串的大小在运行时已知,因此我计划使用 malloc(),但我不想让用户在使用我的函数返回值后负责调用 free()
如何实现这一点?其他返回字符串(char *)的函数是如何工作的(例如 getcwd()_getcwd()GetLastError()SDL_GetError())?
6个回答

6
你的挑战是需要释放资源(即导致free()发生)。
通常情况下,调用者通过直接调用free()(例如查看strdup用户工作方式)或调用您提供的包装free的函数来释放分配的内存。例如,您可能要求调用者调用foo_destroy函数。正如另一篇文章所指出的那样,您可以选择将其封装在不透明的struct中,但这并不是必需的,因为即使没有它(例如用于资源跟踪),拥有自己的分配和销毁函数也很有用。
然而,另一种方法是使用某种形式的清理函数。例如,在分配字符串时,您可以将其附加到池中分配的资源列表中,然后在完成后简单地释放该池。这就是apache2与其apr_pool结构一起工作的方式。通常情况下,在该模型下,您不会特别地free()任何东西。请参见此处和(更易于阅读的)此处
在C中,您不能直接确定对象的最后一个“引用”何时超出范围并释放它,因为您没有引用,只有指针。
最后,您问现有函数如何返回char *变量:
一些(例如strdupget_current_dir_name和某些情况下的getcwd)期望调用者进行释放。
一些(例如strerror_r和其他情况下的getcwd)期望调用者传入足够大小的缓冲区。
有些都是:从getcwd手册页中:
作为POSIX.1-2001标准的扩展,Linux(libc4、libc5、glibc)getcwd()使用malloc(3)动态分配缓冲区,如果bufNULL。在这种情况下,分配的缓冲区具有长度size,除非size为零,此时buf分配为必要的大小。调用者应该free(3)返回的缓冲区。
  • 有些函数使用内部静态缓冲区,因此不可重入/线程安全(糟糕 - 不要这样做)。参见 strerror 以及为什么发明了 strerror_r

  • 有些函数仅返回常量指针(因此可重入),无需释放。

  • 有些函数(如 libxml)需要使用单独的 free 函数(在本例中是 xmlFree()

  • 有些函数(如 apr_palloc)依赖于上述池技术。


1
我喜欢这个答案,+1,谢谢!特别是池的方式,那可能是一个好主意。你有关于我提到的函数如何工作的任何想法吗?它们都返回一个char*,不需要释放它。 - cdonts
我已经更新了上面的内容,并添加了一些示例。getcwd 取决于调用约定。我的 Linux 开发机器没有其他函数的 man 页面,但我相信 API 的适当文档将存在。 - abligh

1

在C语言中没有办法做到这一点。您必须传递一个带有大小信息的参数,以便可以在被调用的函数中调用malloc()free(),或者调用函数必须在调用malloc()后调用free()

许多面向对象的语言(例如C ++)以这种方式处理内存,以实现您想要的功能,但C语言不支持。

编辑

通过大小信息作为参数,我指的是让被调用的函数知道您正在传递的指针拥有多少字节的内存。如果字符串已经被分配了一个值,可以通过直接查看被调用的字符串来完成这个操作,例如:

char test1[]="this is a test";
char *test2="this is a test";  

当以如下方式调用时:

readString(test1); // (or test2) 

char * readString(char *abc)
{
    int len = strlen(abc);
    return abc;
}

这两个参数都会导致 len = 14

但是如果您创建一个未填充的变量,例如:

char *test3; 

为了示例,分配相同数量的内存,但不要填充它,例如:
test3 = malloc(strlen("this is a test") +1);  

被调用的函数无法知道已分配了哪些内存。在 readString() 的第一个原型中,变量 len 会等于 0。但是,如果你将 readString() 的原型更改为:

readString(char *abc, int sizeString);  Then size information as an argument can be used to create memory:    

void readString(char *abc, size_t sizeString)
{
    char *in;
    in = malloc(sizeString +1);

    //do something with it  
    //then free it
    free(in);

}  

例子调用:

int main()
{
     int len;
     char *test3;

     len =  strlen("this is a test") +1; //allow for '\0'  
     readString(test3, len);
     // more code
     return 0;
}

谢谢您的回答。您能否举个例子,说明“传递带有大小信息的参数,以便在被调用的函数中调用malloc和free函数”是如何工作的? - cdonts
@cdonts - 请参见编辑。大小信息可以从参数本身(已初始化和填充的字符串)中导出,也可以作为单独的参数,例如分配给变量的表示内存分配的size_t变量,但是对于尚未填充的变量。以上两种情况都简要解释了一下。 - ryyker

1
许多库强制用户处理内存分配。这是一个好主意,因为每个应用程序都有其自己的对象生命周期和重用模式。对于库来说,尽可能少地做出关于其用户的假设是很好的。
例如,一个用户想要像这样调用您的库函数:
for (a lot of iterations)
{ 
    params = get_totally_different_params();
    char *str = your_function(params);
    do_something(str);
    // now we're done with this str forever
}

如果您的库每次都使用 malloc 分配字符串,那么它将浪费大量调用 malloc 的工作,而且如果 malloc 每次选择不同的块,则可能会显示出较差的缓存行为。根据您的库的具体情况,您可以像这样做:
int output_size(/*params*/);
void func(/*params*/, char *destination);

其中destination的大小必须至少为output_size(params),或者您可以像socket recv API一样执行其他操作:

int func(/*params*/, char *destination, int destination_size);

返回值为:

< desination_size: this is the number of bytes we actually used
== destination_size: there may be more bytes waiting to output

这些模式在重复调用时表现良好,因为调用者可以一遍又一遍地重复使用同一块内存,而无需进行任何分配。

感谢您的回答。 - cdonts

0

你可以将它包装在一个不透明的结构体中。

让用户可以访问到指向你的结构体的指针,但不能访问其内部。创建一个释放资源的函数。

void release_resources(struct opaque *ptr);

当然用户需要调用该函数。


谢谢您的回答,但我想避免用户调用释放资源函数。请看更新! - cdonts

0

你可以跟踪已分配的字符串,并在 atexit 程序中释放它们(http://www.tutorialspoint.com/c_standard_library/c_function_atexit.htm)。在下面的示例中,我使用了一个全局变量,但如果你有一个简单的数组或列表,也可以使用它。

#include <stdlib.h>
#include <string.h>
#include <malloc.h>
char* freeme = NULL;
void AStringRelease(void)
{
    if (freeme != NULL)
        free(freeme);
}
char* AStringGet(void)
{
    freeme = malloc(20);
    strcpy(result, "A String");
    atexit(AStringRelease);
    return freeme;
}

0

在 C 语言中无法做到这一点。

返回一个指针,由调用函数的人来调用 free

或者使用 C++。例如 shared_ptr 等。


感谢回答,但其他函数是如何工作的?(请参见示例) - cdonts

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