繁体   English   中英

vsnprintf在AVR ATmega2560上的奇怪行为

[英]Odd behavior of vsnprintf on AVR ATmega2560

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

该函数的调用是: asprintf(&str, "%s[%d]", name, val); str是调用函数内堆栈上的char* name是一个简单的短文本描述符,我已经证实它不是null且长度也不是零。 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;
}

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

根本原因是C要求可变参数函数在使用前必须声明适当的原型。

例如, 在C89,C90或C99中的所有功能是否都需要原型吗?

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

comp.lang.c常见问题解答列表(问题15.1 )中提供了有关“ 为什么 ”的一些见解。

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

答:这样就可以为printf设计一个合适的原型。

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

您是否检查了

va_start

宏?

可能这可能会引起问题,因为如果参数的定义不正确,则参数的地址可能会指向格式指针“后”的某个位置。 如果可以的话,那比你说的要大,问题可能出在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;
}

根据您的需求,您也许可以重用文件描述符。

我只是在今天遇到了这个问题,正如dxiv回答的那样,关键是可见的原型。 但是,我想对此进行扩展:不仅仅是可变参数无法正常工作。 在我的情况下,已构建项目并调用了该函数,但是所有参数均无法正常工作。 这是一个非常简单的示例来演示。 (函数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 

原型到位后,这个简单的演示示例就起作用了,使用(...)参数的完整的实际功能也起作用了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM