你如何安全地调用 vsnprintf()?
How do you call vsnprintf() safely?
我正在将一些非常旧的 (> 10y) C 代码移植到现代 Linux。我在自定义编写的 vsnprintf() 包装器中遇到分段错误(显然它的任务是检测重复的输出字符串并将其保留):
char* strVPrintf(const String fmt, va_list ap)
{
/* Guess we need no more than 50 bytes. */
int n, size = 50;
char* p = (char*)memMalloc(size), q;
while (1) {
/* Try to print in the allocated space. */
n = vsnprintf(p, size, fmt, ap);
/* If that worked, return the string. */
if (n > -1 && n < size) {
break;
}
/* Else try again with more space. */
if (n > -1) /* glibc 2.1 */
size = n + 1; /* precisely what is needed */
else /* glibc 2.0 */
size *= 2; /* twice the old size */
p = memRealloc(p, size);
}
q = strRegister(p);
memFree(p);
return q;
}
作者似乎假设标准 vsnprintf()
函数 returns 写入的字符数,如果没有收到足够的 returns 一个标记值21=] 格式化所有参数。这意味着您可以猜测缓冲区大小并在必要时增加它。
但是在我的系统上(Ubuntu 14.04,glibc 2.19)vnprintf 在使用提供的 space 参数过多的情况下调用时会导致分段错误。 snprintf()
家族的语义在此期间发生了巨大变化吗?确保您为其提供足够缓冲的现代方法是什么 space?
据我了解,其目的是检测 sprintf
将输出字符串完全写入缓冲区所需的大小。有一个函数可以为您执行此操作:asprintf
(或此处的 vasprintf
)。
原型:
int vasprintf(char **strp, const char *fmt, va_list ap);
使用方法如下:
String strVPrintf(const String fmt, va_list ap)
{
char *ans;
int n;
n = vasprintf(&ans, fmt, ap);
// do the checks
return ans;
}
有了这个功能,我想你不再需要这个包装器了。
不确定你的,但我关于可变参数列表的手册页说:
COMPATIBILITY
These macros are not compatible with the historic macros they replace. A
backward compatible version can be found in the include file <varargs.h>.
你说的是很老的代码,可能这个例程收到的va_list不是vsnprintf
期望的va_list。您应该首先尝试用一个 header 提取所有参数,然后用另一个确定(通常 vsnprintf
与 stdarg.h 兼容)
这是在除 SunOS 4(已过时 20 年)之外的所有操作系统上使用 snprintf
和 vsnprintf
的正确方法,因此您的问题出在其他地方。
我会做一个纯粹的猜测并说我几乎可以肯定你的问题是你将 va_list ap
传递给 vsnprintf
消耗它并且那么你希望它在下次调用时被重置。这是不正确的,很多年前就已经停止在 gcc 中工作了(因为它只在某些架构上工作)。
变化:
n = vsnprintf(p, size, fmt, ap);
收件人:
va_list apc;
va_copy(apc, ap);
n = vsnprintf(p, size, fmt, apc);
va_end(apc);
看看是否有帮助。
下面是一个简单的测试,看看发生了什么:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void
foo(const char *fmt, va_list ap)
{
#ifdef BAD
vprintf(fmt, ap);
#else
va_list apc;
va_copy(apc, ap);
vprintf(fmt, apc);
va_end(apc);
#endif
vprintf(fmt, ap);
}
void
bar(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
foo(fmt, ap);
va_end(ap);
}
int
main(int argc, char **argv)
{
bar("foo %s\n", "bar");
return 0;
}
当 运行 我得到这个:
$ cc -o foo foo.c && ./foo
foo bar
foo bar
$ cc -DBAD -o foo foo.c && ./foo
foo bar
foo ����
我正在将一些非常旧的 (> 10y) C 代码移植到现代 Linux。我在自定义编写的 vsnprintf() 包装器中遇到分段错误(显然它的任务是检测重复的输出字符串并将其保留):
char* strVPrintf(const String fmt, va_list ap)
{
/* Guess we need no more than 50 bytes. */
int n, size = 50;
char* p = (char*)memMalloc(size), q;
while (1) {
/* Try to print in the allocated space. */
n = vsnprintf(p, size, fmt, ap);
/* If that worked, return the string. */
if (n > -1 && n < size) {
break;
}
/* Else try again with more space. */
if (n > -1) /* glibc 2.1 */
size = n + 1; /* precisely what is needed */
else /* glibc 2.0 */
size *= 2; /* twice the old size */
p = memRealloc(p, size);
}
q = strRegister(p);
memFree(p);
return q;
}
作者似乎假设标准 vsnprintf()
函数 returns 写入的字符数,如果没有收到足够的 returns 一个标记值21=] 格式化所有参数。这意味着您可以猜测缓冲区大小并在必要时增加它。
但是在我的系统上(Ubuntu 14.04,glibc 2.19)vnprintf 在使用提供的 space 参数过多的情况下调用时会导致分段错误。 snprintf()
家族的语义在此期间发生了巨大变化吗?确保您为其提供足够缓冲的现代方法是什么 space?
据我了解,其目的是检测 sprintf
将输出字符串完全写入缓冲区所需的大小。有一个函数可以为您执行此操作:asprintf
(或此处的 vasprintf
)。
原型:
int vasprintf(char **strp, const char *fmt, va_list ap);
使用方法如下:
String strVPrintf(const String fmt, va_list ap)
{
char *ans;
int n;
n = vasprintf(&ans, fmt, ap);
// do the checks
return ans;
}
有了这个功能,我想你不再需要这个包装器了。
不确定你的,但我关于可变参数列表的手册页说:
COMPATIBILITY
These macros are not compatible with the historic macros they replace. A backward compatible version can be found in the include file <varargs.h>.
你说的是很老的代码,可能这个例程收到的va_list不是vsnprintf
期望的va_list。您应该首先尝试用一个 header 提取所有参数,然后用另一个确定(通常 vsnprintf
与 stdarg.h 兼容)
这是在除 SunOS 4(已过时 20 年)之外的所有操作系统上使用 snprintf
和 vsnprintf
的正确方法,因此您的问题出在其他地方。
我会做一个纯粹的猜测并说我几乎可以肯定你的问题是你将 va_list ap
传递给 vsnprintf
消耗它并且那么你希望它在下次调用时被重置。这是不正确的,很多年前就已经停止在 gcc 中工作了(因为它只在某些架构上工作)。
变化:
n = vsnprintf(p, size, fmt, ap);
收件人:
va_list apc;
va_copy(apc, ap);
n = vsnprintf(p, size, fmt, apc);
va_end(apc);
看看是否有帮助。
下面是一个简单的测试,看看发生了什么:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void
foo(const char *fmt, va_list ap)
{
#ifdef BAD
vprintf(fmt, ap);
#else
va_list apc;
va_copy(apc, ap);
vprintf(fmt, apc);
va_end(apc);
#endif
vprintf(fmt, ap);
}
void
bar(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
foo(fmt, ap);
va_end(ap);
}
int
main(int argc, char **argv)
{
bar("foo %s\n", "bar");
return 0;
}
当 运行 我得到这个:
$ cc -o foo foo.c && ./foo
foo bar
foo bar
$ cc -DBAD -o foo foo.c && ./foo
foo bar
foo ����