vsnprintf在AVR ATmega2560上的奇怪行为

时间:2016-07-21 22:19:56

标签: c printf avr stdio

我正在开发AVR平台。 avr-libc不提供asprintf()。我试图引入我的项目的库需要它。有用的是,同一个库包含一个实现(如下)。不幸的是,它提供了奇怪的结果。具体来说,我已经确定vsnprintf()的返回码永远不正确;而不是随机的结果,我似乎总是看到连续调用时不正确值(5,1等)的相同进展。

对此功能的调用是:asprintf(&str, "%s[%d]", name, val);str是调用函数中堆栈的char*name是一个简单的短文本描述符,我已经验证了它不是null而不是零长度。 val是一个简单的索引,在调用asprintf()的循环中递增。此调用构建的结果字符串应为7个字符长(不包括空终止符)。整个图书馆都会调用asprintf()。我对包含库的使用只在这个单循环中执行它(两次)。如果我删除此调用并将结果替换为虚拟值,则库将按预期完成其工作。在ret的实现中将缓冲区分配给解除引用的asprintf()指针时,执行似乎崩溃了。

尽管有大量关于缓冲区,指针解除引用和管理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;
}

4 个答案:

答案 0 :(得分:1)

根据以前的评论,事实证明asprintf在没有范围内可见原型的情况下被调用。在通话之前添加必要的声明解决了问题。

根本原因是C需要 variadic 函数才能在使用前声明正确的原型。

例如,Are prototypes required for all functions in C89, C90 or C99?提到了这一点。

  

对可变参数函数(如printf或scanf)的任何调用都必须具有可见原型

comp.lang.c FAQ list - Question 15.1提供了关于“ why ”的一些见解。

  

问:我听说你必须#include< stdio.h>在调用printf之前。为什么呢?

     

A:因此,适用于printf的原型将在范围内。

     

编译器可以对接受可变长度参数列表的函数使用不同的调用序列。 (如果使用可变长度参数列表的调用效率低于使用固定长度的调用,则可能会这样做。)因此,原型(使用省略号符号``...''表示参数列表是变量的每当调用varargs函数时,length必须在范围内,以便编译器知道使用varargs调用机制。

答案 1 :(得分:0)

您是否检查了

的定义
va_start

宏?

这可能会导致问题,因为如果没有正确定义,参数的地址可能指向格式指针的“后面”。如果没有问题,那么问题可能出在vsnprintf实现中,而不是。

答案 2 :(得分:0)

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;
}

根据您的需要,您可以重新使用文件描述符。

答案 3 :(得分:0)

我今天遇到了这个问题,正如dxiv回答的那样,关键是可见原型。但是我想扩展一下:不仅仅是可变参数不能正常工作。在我的例子中,构建了项目并调用了函数,但没有一个参数正常工作。这是一个非常简单的示例。 (函数uprintf()是一个自定义函数,用于通过UART打印输出。)

spy <- read.csv("http://ichart.finance.yahoo.com/table.csv?s=SPY")

从main()调用原型隐藏:

    void log_console(  const char  * fmtstring,... )
{
    uprintf("Start of  log_console\n");
    uprintf(fmtstring);
}

在这种情况下,输出为:

//void log_console(  char const * fmtstring,... );
log_console("Test message to console  =======\n");
uprintf("After test message to console\n");

这表示正在调用该函数,但未正确定义fmtstring。但原型可见:

Start of  log_console
After test message to console

该函数现在可以访问传入参数,并且写入UART的字符串符合预期:

void log_console(  char const * fmtstring,... );
log_console("Test message to console  =======\n");
uprintf("After test message to console\n");

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