Newsgroup: it.hobby.elettronica.digitale
Titolo: sscanf

Ciao,
domanda banale ma mi perdo qualcosa. ATmega644, WinAVR.
char linebuf[80], cmd[5];
for (;;) {
    putchar('>', stdout);
    fgets(linebuf, sizeof linebuf - 1, stdin);
    if (sscanf(linebuf, "%4s", cmd) > 0) {
       if (strcmp(cmd, "LOGI") == 0) {
          // bla bla bla
       }
    }
}
In pratica dopo il prompt '>' mi metto in attesa dell'input utente. 
Memorizzo l'intera riga in linebuf e guardo i primi 4 caratteri 
salvandoli in cmd.
1) Al primo giro scrivo qualcosa, verifico che sia correttamente 
memorizzato in linebuf e poi in cmd. Ok.
2) Al secondo giro mi si ripresenta il prompt: do invio senza scrivere 
nulla.
Linebuf è vuoto (ovvero ha '\0' in linebuf[0]) ma la sscanf mi salva 
ancora in cmd il precedente valore! E ritorna 1 ad indicare che ha 
effettuato la conversione con successo.
Ma come è possibile se linebuf è lungo 0? Ho provato a farcire di printf 
e mi hanno confermato questo comportamento.
Ovviamente vorrei che sscanf convertisse *solo* se linebuf contiene 
qualcosa....
Dove sbaglio?
Grazie :)
Marco / iw2nzm

 effettuato la conversione con successo.
C'è qualcosa che non mi torna. La sscanf non può salvare in cmd il 
precedente valore perché non lo conosce. E' più probabile che non 
tocchi cmd che quindi continua a contenere ciò che aveva prima.
La sscanf in questo caso dovrebbe aver ritornato -1 e infatti, il tuo 
codice compilato col gcc su linux funziona perfettamente.
Mi vengono in mente 2 cose:
  1. Il codice che hai postato non è quello che dà il bug; hai tolto 
delle parti che ritenevi superflue ma invece il bug risiede lì.
  2. Problemi di stack overflow o puntatori in giro.
Propendo però di più per la prima ipotesti.
Facci sapere,
Ciao

  2. Problemi di stack overflow o puntatori in giro.
Ho appena iniziato a scrivere il codice quindi non c'è niente altro in 
giro. La base del programma è appunto questa specie di femto DOS, ovvero 
riconoscere qualche comando con eventuali parametri e chiamare le 
funzioni necessarie.
Non ho puntatori né sono al limite con la dimensione degli array.
Ad ogni modo per sicurezza dopo la firma metto la copia completa del 
codice attualmente in esecuzione sulla scheda, magari mi perdo 
effettivamente qualcosa!
Ciao e buona domenica!
Marco / iw2nzm
--
#include <avr/io.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
#include "uart.h"
#include "DS1305.h"
#define	CS_MEM	PB3
#define CS_IR	PD4
#define	read_cin()		((PINC >> 2) & 0x3F)
#define	read_cout()		((PINA >> 2) & 0x3F)
#define	disable_uart() 	(PORTD |= _BV(PD4))
#define	enable_uart()	(PORTD &= ~_BV(PD4))
#define led0_on()		(PORTB |= _BV(PB0));
#define led0_off()		(PORTB &= ~_BV(PB0));
#define led1_on()		(PORTB |= _BV(PB1));
#define led1_off()		(PORTB &= ~_BV(PB1));
enum {NONE = 0, RDSO, RWSO, PRSO} __attribute__((packed)) session_type = 
NONE;
enum {LOGI = 0, CIDW, CIDR, PCRD,		// commands
	  PCRS, PCWR, HCRD, HCRS, HCWR,
	  ETRD, ETRS, PWDS, INIT, TEST,
	  SMNS, RMNS, DUMP, BEHW};
enum {FALSE = 0, TRUE};
char linebuf[80];
uint16_t	ci[6], co[6];
uint16_t	hi[6], ho[6];
char		wmid[8], wmpwd[8], avrid[8], cid[8];
uint16_t ee_ci[6]    		__attribute__((section(".eeprom"))) = {0, 0, 0, 
0, 0, 0};
uint16_t ee_co[6]    		__attribute__((section(".eeprom"))) = {0, 0, 0, 
0, 0, 0};
uint16_t ee_hi[6]  			__attribute__((section(".eeprom"))) = {0, 0, 0, 0, 
0, 0};
uint16_t ee_ho[6]   		__attribute__((section(".eeprom"))) = {0, 0, 0, 0, 
0, 0};
char	 ee_avrid[8]		__attribute__((section(".eeprom"))) = "";
char	 ee_cid[8]      	__attribute__((section(".eeprom"))) = "";
char	 ee_wmpwd[16][8]	__attribute__((section(".eeprom"))) = {"", "", "", 
"", "", "", "", "", "", "", "", "", "", "", "", ""};
FILE uart_str = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, 
_FDEV_SETUP_RW);
void 	WDT_off();
void 	read_eeprom();
void 	save_eeprom();
uint8_t	blank_check();
int8_t  grant_access();
int main() {
	uint8_t blank;
	int8_t  c_user, p;
	char	cmd[5];
	DDRA   = 0b00000000;
	PORTA  = 0b00000000;
	DDRB   = 0b10101011;
	PORTB  = 0b00000000;
	DDRC   = 0b00000000;
	PORTC  = 0b00000000;
	DDRD   = 0b00110010;
	PORTD  = 0b00000000;
	SPCR = _BV(SPE) | _BV(MSTR) | _BV(CPOL) | _BV(CPHA) | _BV(SPR0);
	uart_init();
	//enable_uart();
	//DS1305_init();
	//DS1305_trickle(ONE_DIODE_4K);
	stdout = stdin = &uart_str;
	WDT_off();
     sei();
	//blank = blank_check();
	for (;;) {
		uart_putchar('>', stdout);
       	fgets(linebuf, sizeof linebuf - 1, stdin);
			printf("sscanf %d\n", sscanf(linebuf, "%4s", cmd));
			printf("cmd: %s\n", cmd);
/*			if (strcmp(cmd, "LOGI") == 0) {
				sscanf(linebuf, "%*s %7s %7s", wmid, wmpwd);
				c_user = grant_access();
				if (c_user == -1) {
					printf("Wrong password.\n");
				} else {
					printf("Login succeeded: user #%d\n", c_user);
				}
			}*/
	}
}
void WDT_off() {
	wdt_reset();
	MCUSR &= ~(1<<WDRF);
	WDTCSR |= (1<<WDCE) | (1<<WDE);
	WDTCSR = 0x00;
}
void read_eeprom() {
}
void save_eeprom() {
}
uint8_t blank_check() {
	char value[8];
	uint8_t i, blank = TRUE;
	eeprom_read_block(value, ee_avrid, 8);
	for (i = 0; i < 8; i++) if (value[i] != 0) blank = FALSE;
	return blank;
}
int8_t grant_access() {
	char	user[8];
	uint8_t i;
	for (i = 0; i < 16; i++) {
		eeprom_read_block(user, ee_wmpwd[i], 8);
		if (strcmp(user, wmpwd) == 0) return i;
	}
	return -1;
}

     if (sscanf(linebuf, "%4s", cmd) > 0) {
sscanf() restituisce 1 perché ha processato con successo il primo % che ha
trovato (una stringa vuota non rappresenta un errore). Ed effettivamente fa
proprio come dici tu, quando incontra stringhe vuote non aggiunge il '\0'
nel buffer di destinazione, che quindi mantiene il valore precedente. La
soluzione è semplice: azzera tu stesso ogni volta il buffer di destinazione
prima di chiamare la sscanf(), poi oltre al fatto che la funzione
restituisca >0 controlla anche che il buffer di destinazione non sia vuoto.
-- 
emboliaschizoide.splinder.com

 nel buffer di destinazione, che quindi mantiene il valore precedente.
Non mi torna molto.
O la sscanf ha parsato correttamente il campo, o non lo ha parsato. 
Non può aver letto una stringa vuota e però non assegnarla a cmd.
La documentazione POSIX recita:
"Upon successful completion, these functions shall return the number 
of successfully matched and *assigned* input items; this number can be 
zero in the event of an early matching failure. If the input ends 
before the first matching failure or conversion, EOF shall be 
returned. If a read error occurs, the error indicator for the stream 
is set, EOF shall be returned, and errno shall be set to indicate the 
error."
Quindi mi aspetto che se alla sscanf gli passo una stringa lunga 0 
(con solo il '\0') essa possa avere 3 comportamenti:
-1 Ritorna 1 e mi mette in cmd una stringa vuota.
-2 Ritorna 0 e non tocca cmd.
-3 Ritorna EOF(-1) e non tocca cmd.
Il codice di Marco compilato su Linux si comporta come ci si aspetta 
(caso 3).
Ci deve essere qualcos'altro oppure l'implementazione di scanf 
nell'avrlibc è buggata.
Ciao

 nell'avrlibc č buggata.
Temporaneamente ho risolto aggiungendo i controlli sul buffer e 
azzerando cmd a ogni ciclo. Magari piů tardi provo a installare il gcc 
per windows e vedo come si comporta.
Ciao!
Marco / iw2nzm

 ha scritto:

In effetti il comportamento è strano, io quando ho dei dubbi o dei problemi su
quello che mi combina il compilatore C vado in debug a vedere quello che mi ha
creato in codice asm.
Non dovrebbe essere difficile capire cosa ti ha messo e perchè la sscanf()
ritorna 1.
La prova non posso farla io adesso perchè sono fuori sede.
--
ciao
  Stefano

 (caso 3).
Mi sono accorto di non essere stato chiaro nel messaggio precedente. Sono
d'accordo che quello che descrivi tu sia il comportamento corretto, io
stavo solo cercando di descrivere la logica che apparentemente animava
quella particolare implementazione di scanf(). :-)
E' comune per i piccoli microcontrollori che svariate funzioni della libc
siano implementate in maniera un po' agricola; ad esempio le tipiche
implementazioni standard della sscanf() consumano un sacco di memoria, per
cui immagino che sia la prima su cui cala la mannaia. Io stesso ho avuto
problemi con la sscanf() implementata su un DSP a 16 bit della Texas
Instruments; già di suo era un surrogato sub-ottimo, e ho dovuto comunque
modificare i sorgenti della libc a mano altrimenti non ci stavo.
Un consiglio buono in generale è quello di non usare mai la sscanf(). Oltre
alle già citato motivazioni di spazio e di problemi di standard, c'è anche
il fatto che è intrinsecamente 'unsafe', prona a buffer overflow e a
malfunzionamenti difficili da interpretare, specie con un piccolo micro che
non dispone di funzioni di debugging avanzate.
-- 
emboliaschizoide.splinder.com

 non dispone di funzioni di debugging avanzate.
Apprezzo il consiglio, ma a questo punto sono "costretto" a chiedere 
secondo te (voi) quale sia la strada migliore per realizzare un semplice 
interprete dei comandi. Io pensavo di generalizzare il tutto per avere 
una sorta di cmd, argc, argv[] cioè sapere quale comando è stato dato e 
quali e quanti parametri (se ve ne sono).
L'idea sarebbe di avere una lista dinamica che associ un comando a una 
funzione. Con un paio di puntatori si dovrebbe poter gestire il tutto 
senza troppi sforzi.
Però dal punto di vista della bassa manovalanza, quale è la strada più 
efficace per cui data una stringa composta da caratteri ascii e spazi 
isolare e memorizzare ciascun campo?
Marco / iw2nzm
ps. non so se riuscirò a rispondere nei prossimi giorni: sarò fuori per 
lavoro e qmail non ne vuole sapere di leggere i NG :(
Proverò da google!

 isolare e memorizzare ciascun campo?
Dovrebbe esistere la strtok(), però si tiene dentro il contesto tra 
una chiamata e la successiva. Noi per questo ce ne siamo riscritta una 
simile, che comunque sono solo 10 righe di codice.
Alla fine abbiamo un parser (altre 20 righe) che a seconda del formato 
specificato converte e assegna i token.
Ciao