通过strdup()和malloc()构造字符串有什么实际区别吗?

3

假设我想编写一个生成字符串的函数,虽然我可以设置字符串的上限大小,但事先不知道字符串会占用多少空间。我可以考虑两种方式来解决这个问题:

char *ParametersAsString_1(int temperature, float pressure)
{
    char buffer1[128];
    snprintf(buffer1, 128, "temperature: %d; pressure: %g",
        temperature, pressure);
    return strdup(buffer1);
}

char *ParametersAsString_2(int temperature, float pressure)
{
    char *buffer2 = malloc(128);
    snprintf(buffer2, 128, "temperature: %d; pressure: %g",
        temperature, pressure);
    return buffer2;
}

我所能看到的唯一区别是,第二种形式可能会浪费一些内存:它为buffer2使用128字节,该变量在其整个存在期间都会使用这些字节。第一个函数使用128字节来存储buffer1,加上字符串“actually”实际使用的任何内存,但是当buffer1从堆栈中移除时,将仅使用实际需要的字符串内存。

如果字符串的寿命很长并且会有大量字符串,那么第一个函数似乎更好。是否有其他原因可以优先选择其中一种形式?(这是一个学术问题;我并不真正处于使用多90字节会有影响的情况下。)


1
你的方法有漏洞——snprintf()可能不会用\0终止你的字符串。去读一下手册或者其他资料吧... - user405725
6
@LBg 对于这个问题,你修改的代码有道理,但是它有着显著的不同...这种区别应该只由提问者自己编辑,因为你不能确定他原本想表达的意思是否和你修改后的一致。也就是说,你怎么知道提问者并没有真正使用你修改后的错误代码呢? - mah
1
@VladLazarenko snprintf() 总是终止字符串,你可能在想 strncpy()。这是 manpage 中的第二段。 - Dave
1
@VladLazarenko:不是这样的——snprintf总是会在缓冲区中加入空终止符(除非缓冲区大小为0)。 - Chris Dodd
1
可能的实际差异在于,malloc是标准C函数,比strdup更具可移植性。 - Mike Kinghan
显示剩余14条评论
4个回答

4

您也可以使用asprintf。缓冲区将分配所需的大小...(当不再需要时,应该将此指针传递给free()来释放分配的存储空间)

注:asprintf是一个C函数,用于动态地分配字符串并格式化输出到该字符串中。
char*   ParametersAsString_3(int temp, float pres) {                                                                                                                                        
    char* buffer;                                                                                                                                                                         

    asprintf(&buffer, "temperature: %d; pressure: %g", temp, pres);                                                                                                                           
    return (buffer);                                                                                                                                                                          
}

3
严格来说,strdup()和asprintf()都不属于C语言;它们是特定于平台的C扩展。而且,OP没有指定任何平台。 - Nemo
1
True。对于标准解决方案,请参见我的答案 - Shahbaz
谢谢提到asprintf();我以前从未听说过。不过,由于它是非标准的,我将采用Shahbaz的答案,这是可移植的(尽管它可能在幕后使用与asprintf()相同的代码)。 - bdesham

4
如果您想要在不知道长度的情况下使用最小内存,那么解决方案在于特殊使用`snprintf`。根据C11标准:
``` 7.21.6.5 2. .. 如果n为零,则不写入任何内容,可以将s设置为null指针... 3. snprintf函数返回本应写入的字符数(不包括终止空字符),如果发生编码错误则返回负值... ```
这意味着如果您编写:
int size = snprintf(NULL, 0, "format string", arguments);

您将会得到一个负值,表示错误(不太可能),或者得到一个正值,表示该字符串的最终大小,但并不实际写入该字符串。

因此:

int size = snprintf(NULL, 0, "temperature: %d; pressure: %g", temperature, pressure);
/* error checking */
char *str = malloc((size + 1) * sizeof(*str));
/* error checking */
sprintf(str, "temperature: %d; pressure: %g", temperature, pressure);
return str;

请注意,strdupasprintf不是标准函数。前者是POSIX扩展,后者是GNU扩展。
此解决方案将为您提供无限制的字符串,因此非常有用(您无需截断字符串)。但是,如果您想要剪切字符串,只需在size过大时分配较小的内存,并使用snprintf以适当的大小在缓冲区中创建(剪切)字符串。
如果您想要更安全、更具未来性并避免重复代码,则可以使用宏:
#define YOUR_LIB_YOUR_FUNC_NAME_SNPRINTF(s, n)               \
             snprintf(s, n, "temperature: %d; pressure: %g", \
             temperature, pressure)

int size = YOUR_LIB_YOUR_FUNC_NAME_SNPRINTF(NULL, 0);
/* error checking */
char *str = malloc((size + 1) * sizeof(*str));
/* error checking */
YOUR_LIB_YOUR_FUNC_NAME_SNPRINTF(str, size + 1)
return str;

#undef YOUR_LIB_YOUR_FUNC_NAME_SNPRINTF

事实上,稍微修改一下这个函数以配合 vsnprintf 的使用,就可以得到一个 asprintf 的实现,您可以在需要的地方使用它。

当某人在明年编辑此代码并更改了一个格式字符串但忽略更改其他字符串时,就会出现缓冲区溢出。我会为第二个调用使用snprintf(缓冲区溢出值得偏执编程),并可能将格式字符串因子化。否则回答很好。 - Nemo
@nemo,没错。我自己不喜欢这种方法的唯一一点是需要重新编写 snprintf。无论如何,最好不要将格式字符串单独分离出来,因为如果它是字面值,编译器会帮你检查格式字符串中是否存在错误。但是可以用另一种方式实现。我马上进行编辑。 - Shahbaz

1
关于标题中的问题:strdup() 使用 malloc()。这意味着在使用完 strdup 后应该使用 free()。
至于示例,第二个函数分配了内存但没有释放,而第一个函数没有,所以忘记第二个函数。不过,对于第一个函数,一旦不再需要它,应该释放函数的结果。
编辑:问题已经被编辑,因此答案也必须进行编辑 :)
在问题被编辑之前,第二个函数以 strdup(buffer2) 结尾。我的意思是,在我的答案中,第二个函数泄漏了为 buffer2 分配的内存。两个函数都返回应该在之后释放的地址,但第二个函数会导致额外的泄漏。

第一个示例也分配内存而不释放,原因正如您在答案中提到的那样:strdup()使用malloc()。除非您指的是问题的pre-LBg编辑(其中包括第二个示例中的内存泄漏)? - mah
在问题被编辑之前,第二个函数以strdup(buffer2)结束。我的意思是,在我的答案中,第二个函数泄漏了为buffer2分配的内存。那时候,这两个函数都返回应该在之后释放的地址,但第二个函数会导致额外的泄漏。 - Bartosz Marcinkowski

0

strdup在内部调用了mallocmemcpy。因此,函数1在调用memcpy函数时会有额外的开销。

因此,考虑到@shahbaz指出的原因,函数2看起来是更好的选择。


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