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

一旦原型就位,这个简单的演示示例就可以工作了,我的完整实际功能也是如此,它使用了 (...) 参数。