【问题标题】:Odd behavior of vsnprintf on AVR ATmega2560AVR ATmega2560 上 vsnprintf 的奇怪行为
【发布时间】:2016-11-25 16:45:56
【问题描述】:

我正在开发 AVR 平台。 avr-libc 不提供asprintf()。我试图引入我的项目的库需要它。有用的是,同一个库包含一个实现(如下)。不幸的是,它提供了奇怪的结果。具体来说,我确定vsnprintf() 的返回码永远不会正确;在连续调用时,我似乎总是看到相同的错误值(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;
}

【问题讨论】:

  • progression of incorrect values (5, 1, etc.) upon successive calls 这些连续调用的输入是什么?如果您显示实际的 asprintf 呼叫,它会有所帮助。
  • 好点。更新了问题以描述如何调用此函数。
  • 是否在两个实现或调用它的模块包含的标头中声明了asprintf(使用正确的原型)?
  • @dxiv 我不得不挖掘一点。不,我在 any 标头中找不到使用原型声明的 asprintf()。在缺少此功能的平台上的构建中包含此功能的方法也没有扩展到相应头文件中的原型。当我将原型声明在它应该在的位置时,你瞧,错误消失了。您能否解释一下是什么触发了您认为这是可能的问题的想法?如果没有适当的原型,代码会在处理 var args 时破坏内存吗?
  • 很有可能会破坏堆栈。

标签: c printf avr stdio


【解决方案1】:

根据以前的 cmets,事实证明 asprintf 被调用时没有可见的原型在范围内。在调用之前添加必要的声明解决了这个问题。

根本原因是C 要求可变参数函数在使用前声明正确的原型。

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

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

comp.lang.c FAQ list - Question 15.1 提供了有关“为什么”的一些见解。

问:我听说你必须在调用 printf 之前#include 。为什么?

答:这样一个合适的 printf 原型就会在范围内。

对于接受可变长度参数列表的函数,编译器可能会使用不同的调用顺序。 (如果使用可变长度参数列表的调用比使用固定长度的调用效率低,它可能会这样做。)因此,原型(使用省略号符号“...”表示参数列表是可变的) length) 必须在任何时候调用 varargs 函数时都在作用域内,以便编译器知道使用 varargs 调用机制。

【讨论】:

  • 这很好。
【解决方案2】:

你有没有检查过的定义

va_start

宏?

这可能会导致问题,因为如果未正确定义参数的地址,它可能会指向格式指针“后面”的某个位置。如果没问题,比你说的那样,问题可能出在 vsnprintf 实现上。

【讨论】:

  • asprintf() 所在的库的作者在代码中的其他地方使用了 var args,需要使用 va_listva_start 等。这些其他函数似乎可以按预期工作。我已经能够将库调用的崩溃范围缩小到asprintf() 的内容。或者,更确切地说,问题似乎出现在这里。它可能是外部因素的汇合,以某种奇怪的方式出现在这个函数中。
【解决方案3】:

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

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

【讨论】:

  • 在 avr-libc 中,vnsprintf() 的实现主要是对vsprintf() 的封装。当我尝试修改 asprintf() 以直接调用 vsprintf() 时,我看到了相同的行为。
  • 在这种情况下,我会提交一个错误报告。如果你是对的,那么问题是深刻而根本的,项目会想要解决它。将您的最小示例转换为打印count N 次的程序,其中 N 来自命令行。这将要么说明你的错误(如果有的话),要么充分证明问题。
  • 问题原来是asprintf() 的实现缺少函数原型,旨在覆盖缺少它的平台。另一位评论者认为这是一种预感并且是正确的。在没有适当原型的情况下处理参数列表中的 var args 可能会破坏堆栈。
【解决方案4】:

我今天遇到了这个问题,正如 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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    • 1970-01-01
    • 2020-12-26
    • 2013-04-20
    • 2021-08-24
    • 1970-01-01
    相关资源
    最近更新 更多