Forum: Mikrocontroller und Digitale Elektronik strtoul vs. atoi führende Null im String ein Problem?!


von L. N. (derneumann)


Lesenswert?

Hallo!

MCU: ESP8266 auf NodeMCU Board
IDE: Visual Micro für Visual Studio
Core: Arduino

Ich bin grade auf einen seltsamen Bug in meiner Firmware für mein 
aktuelles ESP8266 Projekt gestoßen, und zwar gebe ich per Webinterface 
die Möglichkeit, die Uhrzeit + Datum einzustellen.

Hierfür verwende ich die Standard ESP8266 Webserver Library für das 
Arduino Framework und übermittle von einem Formular die eingegebenen 
Daten (Uhrzeit + Datum) per HTTP POST an eine Funktion, die diese Daten 
dann verarbeitet.

Das Objekt Webserver ist von der Klasse ESP8266WebServer instanziert.

Ich erhalte mittels
1
Webserver.arg("mo");
 z.B. das Monat (Eingabe durch den User über das Formular von 1-12, 
wobei 01, 02, etc. auch möglich ist). Ich hab nur im INPUT Element im 
HTML Teil definiert, dass das eine Zahl von 1-12 sein soll. Keine 
Sanitychecks oder so.
Webserver.arg() retourniert einen String (WString.h).

Nun will ich diesen String in ein uint8_t konvertieren und habe das 
bisher folgendermaßen gemacht:
1
uint8_t mo = strtoul(Webserver.arg("mo").c_str(), NULL, 0);

Solange man keine führende Null eingibt, ist alles gut. wenn ich aber 
z.B. als Monat 09 eingebe, ist uint8_t mo gleich 0.

Mache ich das jedoch mit atoi:
1
uint8_t mo = atoi(Webserver.arg("mo").c_str());

...passt das Ergebnis.

Abgesehen davon, dass das bestimmt keine sichere Methode ist, 
Usereingaben zu übernehmen und von der Performance her ist das alles 
sicher auch nicht das schnellste: kann sich das wer erklären?

Liegt das an strtoul oder ist es wahrscheinlicher, dass bei mir noch 
irgendwo ein Fehler begraben liegt?

Ich hab im Arduino Core des ESP8266 nach strtoul gesucht, habe aber nur 
die Header Files gefunden. Schätze mal, dass diese Funktion nicht im 
Quellcode vorliegen wird...

Danke für euren Input!
Gruß

von Stefan E. (sternst)


Lesenswert?

Lucas N. schrieb:
> wenn ich aber
> z.B. als Monat 09 eingebe, ist uint8_t mo gleich 0.

Was korrekt ist.

Da du als Basis 0 angibst, ermittelt strtoul die Basis anhand des 
Zahlenformats selber. Führende Null bedeutet Oktal-Zahl. Die 9 ist keine 
gültige Oktal-Ziffer, also endet damit die Wandlung -> Ergebnis = 0.

von Carl D. (jcw2)


Lesenswert?

strtoul:
"... If the value of base is ​0​, the numeric base is auto-detected:
if the prefix is 0, the base is octal,
if the prefix is 0x or 0X, the base is hexadecimal,
otherwise the base is decimal..."

Works as designed!

Wobei ein Vorteil der strtoxyz Funktionen ist, daß man die Zahlenbasis 
mitgeben kann.


Edit:
Zu langsam getouched ;-)

: Bearbeitet durch User
von L. N. (derneumann)


Lesenswert?

OK, danke! Darüber hab ich auch schon was gelesen, war mir aber nicht 
ganz sicher ob das nun wirklich zutrifft.

Was wäre nun eine elegante Lösung für dieses Problem, oder ist atoi hier 
genauso gut?

Nehme auch gerne Vorschläge für Sanity checks & Co, was halt bei 
Usereingaben immer empfehlenswert ist, entgegen. Fürchte aber, dass das 
den Rahmen etwas sprengen würde.

Gruße

von Theor (Gast)


Lesenswert?

Hm. Was hast Du denn an der Erklärung nicht verstanden, die Du gelesen 
hast?

Unter bestimmten Bedingungen, - hier der Wert für base -, wird die 0 als 
Prefix verstanden. Im Umkehrschluss folgt, dass für andere Werte von 
Base, dass nicht der Fall ist, bzw. sein könnte (je nachdem welche 
Erklärung Du liest). Du musst also den Wert für base ändern.

An sich ist die Wahl von Basis 0 ohnehin kontra-intuitiv. Du gibst eine 
Zahl in Basis 10 ein; wählst als Basis aber 0.

Darüber denk mal ein bisschen nach, schlage ich vor.

von L. N. (derneumann)


Lesenswert?

oh mann, die uhrzeit.. sorry.

dh wenn ich als letzten parameter 10 mitgebe, bekomme ich das gewünschte 
ergebnis?

bin nicht mehr am pc, sonst würd ich es einfach ausprobieren, anstatt 
blöd zu fragen...

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Lucas N. schrieb:
> bin nicht mehr am pc, sonst würd ich es einfach ausprobieren, anstatt
> blöd zu fragen...

Für derartige kurze Spielereien gibt es online-C-Compiler.

https://www.jdoodle.com/c-online-compiler

(willkürlich ausgesuchter Google-Treffer)

von L. N. (derneumann)


Lesenswert?

cool, wusste ich nicht. danke!
trotzdem nix für mich, am handy, im halbschlaf ;-P

morgen ist auch ein tag.

von L. N. (derneumann)


Lesenswert?

Vielen Dank nochmal!
1
uint8_t mo = strtoul(Webserver.arg("mo").c_str(), NULL, 10);

hat funktioniert!

Wie würdet ihr denn Sanitychecks durchführen oder wie ist generell bei C 
und Mikrocontrollern mit Usereingaben vorzugehen? Ich erwähne 
absichtlich explizit Mikrocontroller, da man dort ja i.d.R. nicht die 
Ressourcen hat, die man auf einem PC hat und hier wohl einen Kompromiss 
aus Sicherheit und Leistung/Platzverbrauch finden muss, oder?

Oder soll ich hierfür einen neuen Thread erstellen?

von Stefan F. (Gast)


Lesenswert?

Du könntest damit anfangen, niemals mehr Daten zu verarbeiten, als in 
den Speicher passen. Das gilt ganz besonders für sämtliche String 
operationen. Die C-Library enthält einige Funktionen, die String mit 
beliebiger länge zurück liefern, die würde ich meiden.

Ganz besonders kritisch sehe ich hier die String Klasse im Arduino 
Umfeld bei Netzwerk-Kommunikation.

von L. N. (derneumann)


Lesenswert?

Stefan U. schrieb:
> Du könntest damit anfangen, niemals mehr Daten zu verarbeiten, als in
> den Speicher passen. Das gilt ganz besonders für sämtliche String
> operationen. Die C-Library enthält einige Funktionen, die String mit
> beliebiger länge zurück liefern, die würde ich meiden.
>
> Ganz besonders kritisch sehe ich hier die String Klasse im Arduino
> Umfeld bei Netzwerk-Kommunikation.

Hilf mir bitte kurz auf die Sprünge, siehst du in den von mir geposteten 
Zeilen so einen Fall?

Bei
1
uint8_t mo = strtoul(Webserver.arg("mo").c_str(), NULL, 10);
wäre ich davon ausgegangen, dass eigentlich nichts passieren kann. 
Höchstens das uint8_t könnte hier doch überlaufen, wobei das auch nicht 
zu einem Speicherüberlauf sondern nur zu falschen Ergebnissen im 
Programm führen würde.
Oder liege ich da völlig falsch? Falls ja, warum?

OK Moment, es hat gerade ding gemacht. Sollte ich zuerst mit z.B. 
memcpy Webserver.arg("mo") in ein Buffer definierter Größe kopieren und 
dann weiterarbeiten? Theoretisch könnte man ja per POST einen ewig 
langen String an diese Funktion schicken und c_str() sowie strtoul 
versuchen diese dann zu verarbeiten und brauchen jeglichen Speicher auf 
-> Booom.

Richtig?

Danke!

von Stefan F. (Gast)


Lesenswert?

> wäre ich davon ausgegangen, dass eigentlich nichts passieren kann.
> Höchstens das uint8_t könnte hier doch überlaufen,

Richtig.

> Theoretisch könnte man ja per POST einen ewig
> langen String an diese Funktion schicken

Das meine ich, das sind die interessanten Stellen. Gehe nicht einfach 
davon aus, daß der Web Request ins RAM passen wird. Du könntest den 
Request zum Beispiel Zeilenweise bis maximal 100 Zeichen* pro Zeile 
einlesen. Alles, was darüber hinaus geht, wird bis zum nächsten 
Zeilenumbruch ignoriert.

*) oder wie viel deine Anwendung auch immer erfordert.

So stellst du sicher, daß deine Anwendung bei richtiger Benutzung 
funktioniert und bei falscher Benutzung wenigstens nicht abschmiert.

von L. N. (derneumann)


Lesenswert?

Danke, werde den Code überall entsprechend ausbauen!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.