【问题标题】:snprintf() prints garbage floats with newlib nanosnprintf() 使用 newlib nano 打印垃圾浮点数
【发布时间】:2015-02-26 15:15:15
【问题描述】:

我正在运行一个带有 ARM Cortex-M3 (STM32F205) 的裸机嵌入式系统。当我尝试将 snprintf() 与浮点数一起使用时,例如:

float f;

f = 1.23;
snprintf(s, 20, "%5.2f", f);

我在s 收到垃圾邮件。该格式似乎受到尊重,即垃圾是一个格式良好的字符串,带有数字、小数点和两个尾随数字。但是,如果我重复 snprintf,字符串可能会在两次调用之间发生变化。

浮点数学似乎不适用,snprintf 适用于整数,例如:

snprintf(s, 20, "%10d", 1234567);

我使用newlib-nano 实现和-u _printf_float 链接器开关。编译器是arm-none-eabi-gcc

我确实非常怀疑内存分配问题,因为整数在打印时没有任何问题,但浮点数的行为就好像它们在这个过程中被破坏了一样。 printf 系列函数使用浮点数调用 malloc,而不是整数。

我在此上下文中使用的唯一不属于newlib 的代码是我的_sbrk(),这是malloc 所要求的。

caddr_t _sbrk(int incr)
{
  extern char _Heap_Begin; // Defined by the linker.
  extern char _Heap_Limit; // Defined by the linker.

  static char* current_heap_end;
  char* current_block_address;

  // first allocation
  if (current_heap_end == 0)
      current_heap_end = &_Heap_Begin;

  current_block_address = current_heap_end;

  // increment and align to 4-octet border
  incr = (incr + 3) & (~3);
  current_heap_end += incr;

  // Overflow?
  if (current_heap_end > &_Heap_Limit)
    {
    errno = ENOMEM;
    current_heap_end = current_block_address;
    return (caddr_t) - 1;
    }

  return (caddr_t)current_block_address;
}

据我所知,这应该可行。似乎没有人用负增量调用它,但我想这是由于 newlib malloc 的设计。唯一有点奇怪的是,第一次调用_sbrk 的增量为零。 (不过这可能只是malloc对堆起始地址的好奇。)

堆栈不应与堆发生冲突,因为两者大约有 60 KiB RAM。链接描述文件可能很疯狂,但至少堆和堆栈地址似乎是正确的。

【问题讨论】:

  • 请注意,这些是doubles,而不是floats。不知道这是否重要,无论如何您都不能将float 传递给snprintf()
  • snprintf() 的原型是int snprintf(char *restrict s, size_t n, const char *restrict format, ...); 你没有根据原型调用函数。您是否#include <stdio.h>编译时启用了所有警告
  • 确定您的真实代码使用的是snprintf() 而不是sprintf()
  • @unwind:很好,但 AFAIK printf 是一个可变参数函数,自动将浮点数提升为双精度数。 (至少gcc 不会抱怨带有迂腐设置的格式。)对于我的原始示例(字面常量1.23),参数无论如何都是双精度的,但在修改后的示例中它是单精度浮点数。我不确定newlib nano 的内部结构,我猜它宁愿将浮点数保持为浮点数,因为双精度在嵌入式系统中非常费力(但这只是一个猜测)。
  • 我对实现细节不太熟悉,但以下引起了一些轻微的怀疑:%f 通常涉及将float 转换为double; EABI 要求 double 使用 8 字节对齐;您的 _sbrk() 仅对其分发的内容强制执行 4 字节对齐。这有多重要可能取决于printf()malloc() 的胆量,但应该可以直接进行试验。

标签: c arm embedded printf newlib


【解决方案1】:

由于其他人可能会被同一个错误咬伤,我发布了我自己问题的答案。但是,建议正确答案的是 @Notlikethat 的评论。

这是你不可偷窃的教训。我借用了 STMCubeMX 代码生成器附带的 gcc 链接器脚本。不幸的是,脚本和启动文件已损坏。

原链接描述文件的相关部分:

_estack = 0x2000ffff;

及其在启动脚本中的对应物:

Reset_Handler:  
  ldr   sp, =_estack     /* set stack pointer */
...

g_pfnVectors:
  .word  _estack
  .word  Reset_Handler
...

第一个中断向量位置(0 处)应始终指向启动堆栈顶部。当到达复位中断时,它也会加载堆栈指针。 (据我所知,后者是不必要的,因为无论如何硬件都会在调用重置处理程序之前从第 0 个向量重新加载 SP。)

Cortex-M 堆栈指针应始终指向堆栈中的最后一项。启动时堆栈中没有项目,因此指针应指向实际内存上方的第一个地址,在本例中为 0x020010000。使用原始链接描述文件,堆栈指针设置为 0x0200ffff,这实际上导致 sp = 0x0200fffc(硬件强制字对齐堆栈)。在此之后,堆栈错位 4。

我通过删除_estack 的常量定义并将其替换为_stacktop 来更改链接器脚本,如下所示。内存定义以前就在那里。我更改名称只是为了查看值的使用位置。

MEMORY
{
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 128K
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
}

_stacktop = ORIGIN(RAM) + LENGTH(RAM);

在此之后,_stacktop 的值是 0x20010000,我的数字浮动得很漂亮......任何使用双长度参数的外部(库)函数都可能出现同样的问题,因为 ARM Cortex ABI 声明堆栈必须是调用外部函数时对齐到 8 个八位字节。

【讨论】:

  • 与 Nucleo F401 的 CubeMX 上的 STM32 示例相同的问题,您为我节省了很多时间。谢谢
  • 我的 psoc 也遇到了同样的问题,它也使用 newlib-nano。您是否有机会详细说明如何修复它。我之前没有调整过链接器脚本
【解决方案2】:

snprintf 接受大小作为第二个参数。你可能想看看这个例子http://www.cplusplus.com/reference/cstdio/snprintf/

/* snprintf example */
#include <stdio.h>

int main ()
{
  char buffer [100];
  int cx;

  cx = snprintf ( buffer, 100, "The half of %d is %d", 60, 60/2 );

  snprintf ( buffer+cx, 100-cx, ", and the half of that is %d.", 60/2/2 );

  puts (buffer);

  return 0;
}

【讨论】:

  • 很好的答案,是的,我真的会犯那个错误......但在那种情况下,gcc 会(再次)告诉我我很愚蠢。所以,这一次问题似乎在newlib 的某个更深的地方——或者可能在链接器脚本中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-28
  • 2015-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-06
  • 1970-01-01
相关资源
最近更新 更多