vsnprintf 在 AVR ATmega2560 上的奇怪行为
Odd behavior of vsnprintf on AVR ATmega2560
我在 AVR 平台上工作。 avr-libc 不提供 asprintf()
。我试图带入我的项目的库需要它。有用的是,同一个库包含一个实现(如下)。不幸的是,它提供了奇怪的结果。具体来说,我确定 vsnprintf()
的 return 代码永远不正确;而不是随机结果,我似乎总是看到连续调用时错误值(5、1 等)的相同进展。
这个函数的调用是:asprintf(&str, "%s[%d]", name, val);
。 str
是调用函数中堆栈上的 char*
。 name
是一个简单的短文本描述符,我已验证它不为空且长度不为零。 val
是一个简单的索引,它在调用 asprintf()
的循环中递增。此调用构建的结果字符串应为 7 个字符长(不包括空终止符)。 asprintf()
在整个库中被调用。我对包含库的使用只在这个单循环中执行过(两次)。如果我删除此调用并将结果替换为虚拟值,该库将按预期工作。在 asprintf()
.
的实现中将缓冲区分配给取消引用的 ret
指针时,执行似乎崩溃了
尽管对缓冲区、指针取消引用和管理 var args 进行了大量实验,但我无法使此功能正常运行。然而,在 OS X 上交叉编译它工作得很好。
我知道 vsnprintf()
的行为不一定在所有平台上都相同。也就是说,据我所知,这种用法应该按预期工作。
有什么想法吗?它可能是函数本身之外的东西吗?导致问题的某种 stdio 初始化或链接器库选项?
int asprintf(char **ret, const char *fmt, ...) {
va_list ap1;
va_list ap2;
int count;
va_start(ap1, fmt);
va_copy(ap2, ap1);
count = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
if(count > 0) {
char* buffer;
if (!(buffer = (char*)malloc(count+1))) {
return -1;
}
count = vsnprintf(buffer, count+1, fmt, ap2);
*ret = buffer;
}
va_end(ap2);
return count;
}
你检查过
的定义了吗
va_start
宏?
可能这会导致问题,因为 parametr 的地址可能指向某处 "behind" 如果未正确定义格式指针。如果没问题,不如你说的,问题可能出在vsnprintf的实现上。
stdio 不需要标准初始化。如果您的实现需要初始化,那么该信息有望包含在文档中。
如果你的vnsprintf坏了,你可以使用vsprintf。这是从 FreeTDS 抄袭的版本:
int
vasprintf(char **ret, const char *fmt, va_list ap)
{
FILE *fp;
if ((fp = fopen(_PATH_DEVNULL, "w")) == NULL)
return -1;
if ((fp == NULL) && ((fp = fopen(_PATH_DEVNULL, "w")) == NULL))
return -1;
len = vfprintf(fp, fmt, ap);
if (fclose(fp) != 0)
return -1;
if (len < 0)
return len;
if ((buf = malloc(len + 1)) == NULL) {
errno = ENOMEM;
return -1;
}
if (vsprintf(buf, fmt, ap) != len)
return -1;
*ret = buf;
return len;
}
根据您的需要,您或许能够重新使用文件描述符。
根据之前的评论,事实证明调用 asprintf
时没有范围内的可见原型。在调用之前添加必要的声明解决了这个问题。
根本原因是 C
需要 variadic 函数在使用前声明正确的原型。
例如在 Are prototypes required for all functions in C89, C90 or C99? 中提到了这一点。
any call to a variadic function (like printf or scanf) must have a visible prototype
关于“为什么”的一些见解在 comp.lang.c FAQ list - Question 15.1 给出。
Q: I heard that you have to #include <stdio.h> before calling printf. Why?
A: So that a proper prototype for printf will be in scope.
A compiler may use a different calling sequence for functions which accept variable-length argument lists. (It might do so if calls using variable-length argument lists were less efficient than those using fixed-length.) Therefore, a prototype (indicating, using the ellipsis notation ``...'', that the argument list is of variable length) must be in scope whenever a varargs function is called, so that the compiler knows to use the varargs calling mechanism.
我 运行 今天才进入这个,正如 dxiv 回答的那样,关键是可见的原型。但我想对此进行扩展:不仅仅是可变参数无法正常工作。在我的例子中,构建了项目并调用了函数,但是 none 的参数工作正常。这是一个非常简单的示例来演示。 (函数 uprintf() 是通过 UART 打印的自定义函数。)
void log_console( const char * fmtstring,... )
{
uprintf("Start of log_console\n");
uprintf(fmtstring);
}
从隐藏原型的 main() 调用:
//void log_console( char const * fmtstring,... );
log_console("Test message to console =======\n");
uprintf("After test message to console\n");
在这种情况下,输出为:
Start of log_console
After test message to console
这表明正在调用该函数,但未正确定义 fmtstring。但是原型可见:
void log_console( char const * fmtstring,... );
log_console("Test message to console =======\n");
uprintf("After test message to console\n");
该函数现在可以访问传入参数并且写入 UART 的字符串符合预期:
Start of log_console
Test message to console =======
After test message to console
一旦原型就位,这个简单的演示示例就可以工作了,我的完整实际功能也是如此,它使用了 (...) 参数。
我在 AVR 平台上工作。 avr-libc 不提供 asprintf()
。我试图带入我的项目的库需要它。有用的是,同一个库包含一个实现(如下)。不幸的是,它提供了奇怪的结果。具体来说,我确定 vsnprintf()
的 return 代码永远不正确;而不是随机结果,我似乎总是看到连续调用时错误值(5、1 等)的相同进展。
这个函数的调用是:asprintf(&str, "%s[%d]", name, val);
。 str
是调用函数中堆栈上的 char*
。 name
是一个简单的短文本描述符,我已验证它不为空且长度不为零。 val
是一个简单的索引,它在调用 asprintf()
的循环中递增。此调用构建的结果字符串应为 7 个字符长(不包括空终止符)。 asprintf()
在整个库中被调用。我对包含库的使用只在这个单循环中执行过(两次)。如果我删除此调用并将结果替换为虚拟值,该库将按预期工作。在 asprintf()
.
ret
指针时,执行似乎崩溃了
尽管对缓冲区、指针取消引用和管理 var args 进行了大量实验,但我无法使此功能正常运行。然而,在 OS X 上交叉编译它工作得很好。
我知道 vsnprintf()
的行为不一定在所有平台上都相同。也就是说,据我所知,这种用法应该按预期工作。
有什么想法吗?它可能是函数本身之外的东西吗?导致问题的某种 stdio 初始化或链接器库选项?
int asprintf(char **ret, const char *fmt, ...) {
va_list ap1;
va_list ap2;
int count;
va_start(ap1, fmt);
va_copy(ap2, ap1);
count = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
if(count > 0) {
char* buffer;
if (!(buffer = (char*)malloc(count+1))) {
return -1;
}
count = vsnprintf(buffer, count+1, fmt, ap2);
*ret = buffer;
}
va_end(ap2);
return count;
}
你检查过
的定义了吗va_start
宏?
可能这会导致问题,因为 parametr 的地址可能指向某处 "behind" 如果未正确定义格式指针。如果没问题,不如你说的,问题可能出在vsnprintf的实现上。
stdio 不需要标准初始化。如果您的实现需要初始化,那么该信息有望包含在文档中。
如果你的vnsprintf坏了,你可以使用vsprintf。这是从 FreeTDS 抄袭的版本:
int
vasprintf(char **ret, const char *fmt, va_list ap)
{
FILE *fp;
if ((fp = fopen(_PATH_DEVNULL, "w")) == NULL)
return -1;
if ((fp == NULL) && ((fp = fopen(_PATH_DEVNULL, "w")) == NULL))
return -1;
len = vfprintf(fp, fmt, ap);
if (fclose(fp) != 0)
return -1;
if (len < 0)
return len;
if ((buf = malloc(len + 1)) == NULL) {
errno = ENOMEM;
return -1;
}
if (vsprintf(buf, fmt, ap) != len)
return -1;
*ret = buf;
return len;
}
根据您的需要,您或许能够重新使用文件描述符。
根据之前的评论,事实证明调用 asprintf
时没有范围内的可见原型。在调用之前添加必要的声明解决了这个问题。
根本原因是 C
需要 variadic 函数在使用前声明正确的原型。
例如在 Are prototypes required for all functions in C89, C90 or C99? 中提到了这一点。
any call to a variadic function (like printf or scanf) must have a visible prototype
关于“为什么”的一些见解在 comp.lang.c FAQ list - Question 15.1 给出。
Q: I heard that you have to #include <stdio.h> before calling printf. Why?
A: So that a proper prototype for printf will be in scope.
A compiler may use a different calling sequence for functions which accept variable-length argument lists. (It might do so if calls using variable-length argument lists were less efficient than those using fixed-length.) Therefore, a prototype (indicating, using the ellipsis notation ``...'', that the argument list is of variable length) must be in scope whenever a varargs function is called, so that the compiler knows to use the varargs calling mechanism.
我 运行 今天才进入这个,正如 dxiv 回答的那样,关键是可见的原型。但我想对此进行扩展:不仅仅是可变参数无法正常工作。在我的例子中,构建了项目并调用了函数,但是 none 的参数工作正常。这是一个非常简单的示例来演示。 (函数 uprintf() 是通过 UART 打印的自定义函数。)
void log_console( const char * fmtstring,... )
{
uprintf("Start of log_console\n");
uprintf(fmtstring);
}
从隐藏原型的 main() 调用:
//void log_console( char const * fmtstring,... );
log_console("Test message to console =======\n");
uprintf("After test message to console\n");
在这种情况下,输出为:
Start of log_console After test message to console
这表明正在调用该函数,但未正确定义 fmtstring。但是原型可见:
void log_console( char const * fmtstring,... );
log_console("Test message to console =======\n");
uprintf("After test message to console\n");
该函数现在可以访问传入参数并且写入 UART 的字符串符合预期:
Start of log_console Test message to console ======= After test message to console
一旦原型就位,这个简单的演示示例就可以工作了,我的完整实际功能也是如此,它使用了 (...) 参数。