C snprintf()使用newlib nano打印垃圾浮动

C snprintf()使用newlib nano打印垃圾浮动,c,arm,embedded,printf,newlib,C,Arm,Embedded,Printf,Newlib,我正在运行一个带有ARM Cortex-M3(STM32F205)的裸机嵌入式系统。当我尝试使用带有浮点数的snprintf()时,例如: float f; f = 1.23; snprintf(s, 20, "%5.2f", f); snprintf(s, 20, "%10d", 1234567); 我把垃圾放入s。这种格式似乎很受欢迎,即垃圾是一个格式良好的字符串,包含数字、小数点和两个尾随数字。但是,如果我重复snprintf,字符串可能会在两次调用之间发生变化 浮点数学似乎在其他方

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

float f;

f = 1.23;
snprintf(s, 20, "%5.2f", f);
snprintf(s, 20, "%10d", 1234567);
我把垃圾放入
s
。这种格式似乎很受欢迎,即垃圾是一个格式良好的字符串,包含数字、小数点和两个尾随数字。但是,如果我重复
snprintf
,字符串可能会在两次调用之间发生变化

浮点数学似乎在其他方面起作用,
snprintf
与整数一起工作,例如:

float f;

f = 1.23;
snprintf(s, 20, "%5.2f", f);
snprintf(s, 20, "%10d", 1234567);
我将
newlib nano
实现与
-u\u printf\u float
链接器开关一起使用。编译器是
arm none eabi gcc

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

我在此上下文中使用的唯一不属于
newlib
的代码是我的
\u 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
的设计。唯一有点奇怪的是,对
\u sbrk
的第一次调用的增量为零。(但这可能只是malloc对堆的起始地址的好奇。)


堆栈不应该与堆发生冲突,因为这两个堆栈大约有60 KiB的RAM。链接器脚本可能很疯狂,但至少堆和堆栈地址似乎是正确的。

snprintf接受大小作为第二个参数。您可能想看一下这个例子

/*snprintf示例*/
#包括
int main()
{
字符缓冲区[100];
int-cx;
cx=snprintf(缓冲区,100,“一半%d为%d”,60,60/2);
snprintf(缓冲区+cx,100 cx“,其中一半为%d.”,60/2/2);
放置(缓冲);
返回0;
}

由于可能会有其他人被同一个bug咬到,我发布了我自己问题的答案。然而,正是@Notlikethat的评论给出了正确的答案

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

原始链接器脚本的相关部分:

_estack = 0x2000ffff;
以及启动脚本中的对应项:

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

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

Cortex-M堆栈指针应始终指向堆栈中的最后一项。启动时,堆栈中没有任何项,因此指针应指向实际内存上方的第一个地址,在本例中为0x020100000。对于原始链接器脚本,堆栈指针设置为0x0200ffff,这实际上导致sp=0x0200fffc(硬件强制字对齐堆栈)。在此之后,堆栈的偏移量为4

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

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

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

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

请注意,这些八位字节是
double
s,而不是
float
s。不知道这是否重要,您无论如何都不能将
浮点值传递给
snprintf()
。的原型是
int snprintf(char*restrict s,size\u t n,const char*restrict format,…)您没有根据原型调用函数。您是否
#包含
并在启用所有警告的情况下编译?确保您真正的代码使用的是
snprintf()
而不是
sprintf()
?@unwind:这是一个好的观点,但AFAIK
printf
是一个可变函数,它会自动将浮点值提升为双倍值。(至少,
gcc
不会抱怨其迂腐设置的格式。)在我的原始示例(literal constant
1.23
)中,参数无论如何都是双精度的,但在修改后的示例中,它是单精度浮点。我不确定newlib nano的内部结构,我想它更愿意将浮点数作为浮点数来保存,因为在嵌入式系统中,双精度浮点运算非常费力(但这只是一个猜测)。我对实现细节不太熟悉,但以下几点引起了一些轻微的怀疑:
%f
通常涉及将
浮点
转换为
双精度
;EABI要求对
double
进行8字节对齐;您的
\u sbrk()
仅对其分发的内容强制执行4字节对齐。这有多重要可能取决于
printf()
malloc()
的胆量,但它应该很容易进行实验。答案很好,是的,我可能真的犯了那个错误。。。但是在那种情况下,
gcc
会(再次)告诉我我很愚蠢。因此,这一次的问题似乎在
newlib
-或者可能在链接器脚本中更深层次。NucleoF401的CubeMX上的STM32示例也存在同样的问题,您为我节省了很多时间。谢谢,我的psoc也有同样的问题,它也使用了newlib nano。您是否有机会详细说明如何修复它。我以前没有调整过链接器脚本