为什么要使用asprintf()而不是sprintf()?

70

我很难明白为什么需要使用asprintf。在这里的手册中,它说:

asprintf()vasprintf() 函数是 sprintf(3)vsprintf(3) 的类比函数,除了它们会分配一个足够大以容纳输出(包括终止空字节)的字符串,并通过第一个参数返回指向它的指针。当不再需要时,应该将此指针传递给 free(3) 以释放已分配的存储。

这里是我试图理解的示例:

asprintf(&buffer, "/bin/echo %s is cool", getenv("USER"));

如果缓冲区分配一个足够大的字符串与使用char * =(string)有什么区别?


8
asprintf()vasprintf() 是GNU扩展函数。已添加GNU标签。 - alk
5
嗯,我想知道提问者是否在这里做练习:http://exploit-exercises.com/nebula/level02? - jordanpg
1
这个主题的一篇非常好的博客文章可以在这里找到:memory-management-in-c-and-auto ... 顺便说一句,整个博客都值得一读。 - antibus
@jordanpg OpenBSD 也有这个功能。NetBSD 也是如此。关于其历史,FreeBSD 的 man 页面提供了一些细节:“函数 asprintf() 和 vasprintf() 最初出现在 GNU C 库中。它们由 Peter Wemm 在 FreeBSD 2.2 中实现,但后来被 Todd C. Miller 从 OpenBSD 2.3 中替换为不同的实现。” - Cristian Ciupitu
2个回答

146
如果你使用sprintf()vsprintf(),你需要先分配一个缓冲区,并确保该缓冲区足够大,以容纳sprintf写入的内容。否则,sprintf()将会快乐地覆盖掉缓冲区末端之外的内存。
char* x = malloc(5 * sizeof(char));
// writes "123456" +null but overruns the buffer
sprintf(x,"%s%s%s", "12", "34", "56");

如果程序写入 '6' 和终止符 null 超出了分配给 x 的空间,那么它可能会破坏其他变量或导致分段错误。如果你幸运的话,它会践踏在已经分配的块之间的内存,并不会造成危害——这一次。这会导致间歇性错误——最难诊断的错误类型。最好使用像 ElectricFence 这样的工具,使溢出失败快速。

一个非恶意的用户提供过长的输入,可能会导致程序以意外的方式运行。恶意用户可以利用这个方法将他们自己的可执行代码注入系统。

防范这种情况的一种方法是使用 snprintf(),该函数将字符串截断为您提供的最大长度。

char *x = malloc(5 * sizeof(char));
int size = snprintf(x, 5, "%s%s%s", "12", "34", "56"); // writes "1234" + null

size 返回的是如果有足够空间,会被写入的字符串长度 -- 不包括结尾的空字符。

在这种情况下,如果 size 大于或等于 5,那么就知道发生了截断 - 如果不想截断,可以分配一个新字符串,然后再次尝试 snprintf()

char *x = malloc(BUF_LEN * sizeof(char));
int size = snprintf(x, 5, "%s%s%s", "12", "34", "56");
if (size >= BUF_LEN) {
    realloc(&x,(size + 1) * sizeof(char));
    snprintf(x, size + 1 , "%s%s%s", "12", "34", "56");
}

(这是一个相当简单的算法,但它说明了一点。它可能还存在错误,这进一步说明了问题 - 这些东西很容易搞砸。)

asprintf()可以为你完成这一步 - 计算字符串的长度,分配相应大小的内存,并将字符串写入其中。

char *x;
int size = asprintf(&x, "%s%s%s", "12", "34", "56");

不管什么情况,在你使用完 x 之后,都需要释放它,否则会导致内存泄漏:

free(x);

asprintf() 是隐式的 malloc(),因此您需要检查它是否工作,就像您会对 malloc() 或任何其他系统调用一样。

if (size == -1 ) {
   /* deal with error in some way */
}

请注意,asprintf()是libc的GNU和BSD扩展之一 - 您无法确定它在每个C环境中都可用。 sprintf()snprintf()是POSIX和C99标准的一部分。


2
此外,在C语言中,您不应该对malloc(以及其它相关函数)的结果进行强制类型转换。详情请参考:http://www.stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc。 - Spikatrix
等等,如果 size 是不包括终止空字符的写入字符数,那么你难道不应该使用 realloc((size+1) * sizeof(char)) 吗? - Joachim Sauer
@JoachimSauer 谢谢。啊,C语言 :) -- 至少它展示了为什么asprintf很有用。旧的方式真的很容易出错。 - slim
3
值得注意的是,asprintf不是C或POSIX标准的一部分,而是GCC和BSD的扩展。虽然很有用 - 这也是问题所问的 - 但应该充分意识到使用它会降低您的C代码的可移植性。http://linux.die.net/man/3/asprintf - Brian Bulkowski
我认为在 snprintf 代码片段中发现了几个问题:1. 您需要将 BUF_LEN 传递给第一个 snprintf(),而不是 5!2. 您需要执行 x = realloc(x, (size + 1))!realloc 接受指针,而不是指向指针的指针!3. 第二个 snprintf 调用应该是 (size+1),而不是 5! - wastl
显示剩余2条评论

21

好处是安全性。

许多程序允许系统漏洞发生,因为当填充用户提供的数据时,程序员提供的缓冲区会溢出。

使用 asprintf 为您分配缓冲区可以确保这种情况不会发生。

但是,您必须检查 asprintf 的返回值,以确保内存分配实际上成功了。请参见http://blogs.23.nu/ilja/2006/10/antville-12995/


我记得模糊地读过这个。这是使用asprintf的唯一原因吗? - Brandon Ling
2
@BrandonLing 嗯,在很多情况下,这也会使您的代码更短! - Alnitak
7
它可以消除代码重复 -- 很多时候,当你想要一个永不截断的sprintf,你被迫编写自己的函数来实现这个功能,现在你可以使用一个已经封装好的单一函数,在可移植性方面有所牺牲。 - Kerrek SB

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