snprintf() 使用 newlib nano 打印垃圾浮点数
snprintf() prints garbage floats with newlib nano
我是 运行 带有 ARM Cortex-M3 (STM32F205) 的裸机嵌入式系统。当我尝试将 snprintf()
与浮点数一起使用时,例如:
float f;
f = 1.23;
snprintf(s, 20, "%5.2f", f);
我把垃圾放进了 s
。格式似乎受到尊重,即垃圾是一个格式正确的字符串,包含数字、小数点和两个尾随数字。但是,如果我重复 snprintf
,字符串可能会在两次调用之间发生变化。
浮点数学似乎以其他方式工作,snprintf
与整数一起工作,例如:
snprintf(s, 20, "%10d", 1234567);
我使用带有 -u _printf_float
链接器开关的 newlib-nano
实现。编译器是 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。链接器脚本可能很疯狂,但至少堆和堆栈地址似乎是正确的。
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;
}
由于其他人可能会被同样的虫子咬到,所以我 post 回答了我自己的问题。然而,@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 堆栈指针应始终指向堆栈中的最后一项。启动时堆栈中没有任何项目,因此指针应指向实际内存上方的第一个地址,在本例中为 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 个八位字节。
我是 运行 带有 ARM Cortex-M3 (STM32F205) 的裸机嵌入式系统。当我尝试将 snprintf()
与浮点数一起使用时,例如:
float f;
f = 1.23;
snprintf(s, 20, "%5.2f", f);
我把垃圾放进了 s
。格式似乎受到尊重,即垃圾是一个格式正确的字符串,包含数字、小数点和两个尾随数字。但是,如果我重复 snprintf
,字符串可能会在两次调用之间发生变化。
浮点数学似乎以其他方式工作,snprintf
与整数一起工作,例如:
snprintf(s, 20, "%10d", 1234567);
我使用带有 -u _printf_float
链接器开关的 newlib-nano
实现。编译器是 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。链接器脚本可能很疯狂,但至少堆和堆栈地址似乎是正确的。
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;
}
由于其他人可能会被同样的虫子咬到,所以我 post 回答了我自己的问题。然而,@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 堆栈指针应始终指向堆栈中的最后一项。启动时堆栈中没有任何项目,因此指针应指向实际内存上方的第一个地址,在本例中为 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 个八位字节。