2015-02-26 10 views
5

Sto eseguendo un sistema di metallo nudo con un ARM Cortex-M3 (STM32F205). Quando provo ad usare snprintf() con numeri float, per es .:snprintf() stampa garbage floats con newlib nano

float f; 

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

ho rifiuti in s. Il formato sembra essere onorato, cioè la spazzatura è una stringa ben formata con cifre, punto decimale e due cifre finali. Tuttavia, se ripeto lo snprintf, la stringa potrebbe cambiare tra due chiamate.

virgola mobile matematica sembra funzionare in caso contrario, e snprintf opere con numeri interi, ad es .:

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

io uso l'attuazione newlib-nano con l'interruttore -u _printf_float linker. Il compilatore è arm-none-eabi-gcc.

Ho un forte sospetto di problemi di allocazione della memoria, poiché gli interi sono stampati senza intoppi, ma i float si comportano come se fossero corrotti durante il processo. Le funzioni familiari printf chiamano malloc con float, non con numeri interi.

L'unico pezzo di codice non appartenente a newlib che sto usando in questo contesto è il mio _sbrk(), che è richiesto da 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; 
} 

Per quanto sono stato in grado di tracciare, questo dovrebbe funzionare. Sembra che nessuno lo chiami mai con incrementi negativi, ma suppongo che ciò sia dovuto al design del newlib malloc. L'unica cosa leggermente strana è che la prima chiamata a _sbrk ha un incremento pari a zero. (Ma questa potrebbe essere solo la curiosità di malloc sull'indirizzo di partenza dell'heap.)

Lo stack non deve entrare in collisione con l'heap, in quanto vi sono circa 60 KiB RAM per i due. Lo script linker potrebbe essere folle, ma almeno gli indirizzi heap e stack sembrano essere corretti.

+2

Si noti che quelli sono 'double's, non' float's. Non importa se è importante, non si può passare comunque un 'float' a' snprintf() '. – unwind

+4

Il prototipo di ['snprintf()'] (http://pubs.opengroup.org/onlinepubs/9699919799/functions/snprintf.html) è 'int snprintf (char * limita s, size_t n, const char * riduci il formato , ...); 'Non stai chiamando la funzione in base al prototipo. Hai '#include ' ** e compilato con tutti gli avvisi abilitati **? – pmg

+1

Sicuramente il tuo vero codice sta usando 'snprintf()' e non 'sprintf()'? – chux

risposta

9

Come può accadere che qualcun altro viene morso dallo stesso bug, ho posto una risposta alla mia domanda. Tuttavia, è stato il commento di @Notlikethat a suggerire la risposta corretta.

Questa è una lezione di Non devi rubare. Ho preso in prestito lo script gcc linker fornito con il generatore di codice STMCubeMX. Sfortunatamente, lo script insieme al file di avvio è rotto.

La parte rilevante dello script linker originale:

_estack = 0x2000ffff; 

e suoi omologhi script di avvio:

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

g_pfnVectors: 
    .word _estack 
    .word Reset_Handler 
... 

Il vettore posizione prima interruzione (a 0) deve sempre puntare al avvio impilare. Quando viene raggiunto l'interrupt di ripristino, carica anche il puntatore dello stack. (Per quanto posso dire, quest'ultimo non è necessario in quanto l'HW ricarica lo SP dal vettore 0 prima di chiamare il gestore di reset.)

Il puntatore dello stack Cortex-M deve sempre puntare all'ultima voce nella pila. All'avvio non ci sono elementi nello stack e quindi il puntatore dovrebbe puntare al primo indirizzo sopra la memoria effettiva, 0x020010000 in questo caso. Con lo script linker originale il puntatore dello stack è impostato su 0x0200ffff, che in effetti risulta in sp = 0x0200fffc (l'hardware forza lo stack allineato alla parola). Dopo questo, lo stack è disallineato di 4.

Ho modificato lo script del linker rimuovendo la definizione costante di _estack e sostituendolo con _stacktop come mostrato di seguito. Le definizioni di memoria erano lì prima. Ho cambiato il nome solo per vedere dove viene utilizzato il valore.

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

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

Dopo questo il valore di _stacktop è 0x20010000, e miei numeri galleggiante ben ... Lo stesso problema potrebbe verificarsi con qualsiasi funzione esterna (libreria) utilizzando parametri di doppia lunghezza, come ARM Cortex ABI afferma che la pila deve essere allineato a 8 ottetti quando si chiamano funzioni esterne.

1

snprintf accetta le dimensioni come secondo argomento. Si potrebbe desiderare di passare attraverso questo esempio 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; 
} 
+0

Buona risposta, e sì, avrei potuto davvero commettere quell'errore ... Ma in quel caso 'gcc' avrebbe (ancora una volta) detto che sono stupido. Quindi, questa volta il problema sembra trovarsi più in profondità in 'newlib' - o forse in script di linker. – DrV

+0

Lol .... ma puoi provarlo almeno una volta .... – theadnangondal