Forum: Mikrocontroller und Digitale Elektronik double in string Probleme


von Matthias Peters (Gast)


Lesenswert?

Hallo Leute,

ich habe an meinen Arduino Mega ein Tastenfeld und ein LCD Display.

Ich lese zur Zeit zwei chars ein. Diese wandle ich in float um. Ich 
möchte nämlich mit GPS Koordinaten in Dezimalgrad rechnen.

Die Berechnung der Fließkommazahl möchte ich dann wieder auf den LCD 
Display ausgeben, deshalb das Ergebnis wieder in char umwandeln.

Wenn ich eingebe.

nummer = 200
nummer2 = 500
bekomme ich als Ergebnis: 201000.000 in der Ausgabe.

Vermutlich mindestens ein fehler drin. Seltsamerweise werden auch statt 
8 Stellen 10 ausgegeben, davon abgesehen stimmt das Ergebnis natürlich 
nicht.

Über Hilfe und Tips wäre ich sehr dankbar.



void loop(){

  char nummer[3], nummer2[3] , char num[3];
  float zahl1, zahl2, ergebnis;

//======== HIER EINGABE WEGGELASSEN======//

  zahl1= atof(nummer);        // Umwandlung und Berechnung
  zahl2= atof(nummer2);
  ergebnis= zahl1 + zahl2;
  dtostrf(ergebnis,8,3,num);


  lcd.setCursor(0,0);
  lcd.clear();
  lcd.print("Ergebnis");
  lcd.setCursor(0,1);
  lcd.write(num);


  delay(10000);
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Matthias Peters schrieb:
> char nummer[3], nummer2[3]

Wie viele Zeichen kann man diesen beiden Arrays speichern, wenn man sie 
als C-Strings benutzt?

Nein, nicht drei. Also lassen weder "500" noch "200" sich darin 
unterbringen.

von Matthias Peters (Gast)


Lesenswert?

Ich lese in einer for-Schleife nur drei Ziffern ein.
weis nicht, ob er eine Null am Ende dranhängt, aber selbst wenn, dann 
sind
2000 + 5000 != 201000 !

Hier die Eingabe:



lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Bitte Eingabe 1: ");

  for(int i =0; i<3;i++) // Eingabe Zahl 1
  {
    nummer[i] = eingabe();
    delay(100);
    lcd.setCursor(i,1);
    lcd.write(nummer[i]);
  }

von Matthias Peters (Gast)


Lesenswert?

Ach Mist verstehe. [0] [1] [2], wenn er am Ende eine Null reinschreibt 
habe ich nur 2 Stellen. Wie erklärt sich dann das dumme Ergebnis ?

von Matthias Peters (Gast)


Lesenswert?

Ho mann Sorry Jungs.

For-Schleife auf i < 2 reduziert und jetzt passt es!

Aber selbst wenn ich drei Zahlen eingebe mit der Null am Ende, habe ich 
ja keine Null eingegeben, wenn sie nicht mehr reinpasst.

Dann müsste als ergebnis ja trotzdem 70 rauskommen ?

von Genaulesender (Gast)


Lesenswert?

Matthias Peters schrieb:
> Ach Mist verstehe. [0] [1] [2], wenn er am Ende eine Null reinschreibt
> habe ich nur 2 Stellen. Wie erklärt sich dann das dumme Ergebnis ?

Von deinem Geschriebeben verstehe ich dass Du den unterschied von Null 
'0' zu NUL nicht intus hast.
Stimmts?

von Genaulesender (Gast)


Lesenswert?

> For-Schleife auf i < 2 reduziert und jetzt passt es!

Passt was?

Was steht jetzt in [2]?
Was muss zwingend in [2] stehen?

von Lars (rezyy)


Lesenswert?

Hallo,

zunächst: Strings in C werden mit '\0' (NUL) terminiert.

Um also in einem string eine "200" zu speichern, benötigst du schonmal 
mindestens 4 byte.

Außerdem wenn deine Ausgabe 8 Zeichen lang sein soll, dann muss auch da 
das Array dementsprechend angepasst werden.

von Kaj (Gast)


Lesenswert?

Ein C-Buch könnte wunderbar helfen...

von W.S. (Gast)


Lesenswert?

Matthias Peters schrieb:
> Ich lese zur Zeit zwei chars ein. Diese wandle ich in float um. Ich
> möchte nämlich mit GPS Koordinaten in Dezimalgrad rechnen.
>
> Die Berechnung der Fließkommazahl möchte ich dann wieder auf den LCD
> Display ausgeben, deshalb das Ergebnis wieder in char umwandeln.

Versuche doch bitte dich exakt auszudrücken: Was du einliest, sind 
vermutlichst nicht zwei char, sondern zwei Zeichenketten zu neudeutsch 
Strings. Wie in deinem Beispiel 200 und 500 eben. Dafür brauchst du 
jeweils so viel Platz in deinen jeweiligen Eingabe-Puffern, daß alle 
Zeichen plus ein abschließendes Nullbyte hineinpassen. Je 3 Byte sind 
viel zu wenig.  Ebenso brauchst du ausreichend Platz für deinen 
Ausgabepuffer. Erstmal weißt du nicht, was das Programm im Laufe seines 
Lebens so alles an Eingaben bekommt und du weißt auch nicht, was dann 
beim Ausgabe-Konvertieren draus wird. Es könnte nämlich dir passieren, 
daß aus Bereichsgründen dein Ausgabekonverter auf Exponentialdarstellung 
umschaltet. Das sieht dann etwa so aus:
1.234567E+21
Dafür würdest du also mindestens 13 Plätze im Puffer brauchen.
Es ist immer besser, sich vorher Gedanken über mögliche Puffergrößen für 
zu erwartende String-Längen zu machen, denn C hat ja keine 
Stringverarbeitung wie Pascal.

W.S.

von Matthias Peters (Gast)


Lesenswert?

Genaulesender schrieb:
> Matthias Peters schrieb:
>> Ach Mist verstehe. [0] [1] [2], wenn er am Ende eine Null reinschreibt
>> habe ich nur 2 Stellen. Wie erklärt sich dann das dumme Ergebnis ?
>
> Von deinem Geschriebeben verstehe ich dass Du den unterschied von Null
> '0' zu NUL nicht intus hast.
> Stimmts?

Ja genau das stimmt! :-)
Jetzt wäre das ja geklärt. Ich dachte halt, das dort nur eine '0' stehen 
müsste.

> For-Schleife auf i < 2 reduziert und jetzt passt es!

>>Passt was?

>>Was steht jetzt in [2]?
>>Was muss zwingend in [2] stehen?

Wenn ich '0' eintippe gibt es ja Probleme, da eine Zeichenkette mit '\0' 
abgeschlossen werden muss, wie oben bereits mehrfach erwähnt.
Das habe ich es ja zuerst nicht gemacht, was dann ja auch kein logisches 
Ergebnis lieferte.

char nummer[3];
nummer[0] = '7';
nummer[1] = '7';
nummer[2] = '0'; //Falsch

Dann habe ich statt alle drei Byte der Zeichenkette zu beschreiben, nur 
die ersten beiden mit Werten gefüllt:

chat nummer[3];
nummer[0] = '7';
nummer[1] = '7';

Da die Berechnung jetzt stimmt, muss '\0' ja automatisch an die letzte 
Stelle der Variable geschrieben worden sein, weil ich ja nichts 
reingeschrieben habe, oder bei der Definition der Variablen hat bereits 
das letzte Byte automatisch das Zeichen '\0' bekommen ?

Dann könnte ich es ja so machen, das ich einfach so viele Zeichen 
einlese, bis die Variable voll ist und dann in das letzte Byte explizit 
'\0' reinschreibe.

Probiere das mal aus. Schonmal danke für Eure Hilfe.
@ W.S Danke für den Hinweis mit den Puffer. Arbeite mich da Stück für 
Stück rein.

von Lars (rezyy)


Lesenswert?

Matthias Peters schrieb:
> Wenn ich '0' eintippe gibt es ja Probleme, da eine Zeichenkette mit '\0'
> abgeschlossen werden muss, wie oben bereits mehrfach erwähnt.
> Das habe ich es ja zuerst nicht gemacht, was dann ja auch kein logisches
> Ergebnis lieferte.

Richtig. Schau dir mal eine ASCII-Tabelle an. '0' entspricht 0x30, 
während die Terminierung '\0' 0x00 (NUL) enstpricht.

Matthias Peters schrieb:
> Da die Berechnung jetzt stimmt, muss '\0' ja automatisch an die letzte
> Stelle der Variable geschrieben worden sein, weil ich ja nichts
> reingeschrieben habe, oder bei der Definition der Variablen hat bereits
> das letzte Byte automatisch das Zeichen '\0' bekommen ?

Initialisier dein Array in dem Fall dann (Und achte immer auf deine 
nötigen Arraygrößen):
1
char nummer[3] = "77";

So initialisiert der Compiler nummer[2] automatisch mit '\0'.

Matthias Peters schrieb:
> Dann könnte ich es ja so machen, das ich einfach so viele Zeichen
> einlese, bis die Variable voll ist und dann in das letzte Byte explizit
> '\0' reinschreibe.

Ja. Auch hier: Achte darauf, dass das Array auch auf jeden Fall groß 
genug ist.

von Fritz G. (fritzg)


Lesenswert?

Und denke daran, falls du mal verreist, die Grad auch negativ sein 
können, dann brauchst du in der Ausgabe Platz für das "-" Zeichen.

von Rolf M. (rmagnus)


Lesenswert?

Matthias Peters schrieb:
> Da die Berechnung jetzt stimmt, muss '\0' ja automatisch an die letzte
> Stelle der Variable geschrieben worden sein, weil ich ja nichts
> reingeschrieben habe, oder bei der Definition der Variablen hat bereits
> das letzte Byte automatisch das Zeichen '\0' bekommen ?

Es kann auch einfach zufällig '\0' gewesen sein, weil bisher noch nichts 
anderes an diese Speicherstelle geschrieben worden ist. Bei der 
Definition wird in lokale Variablen nur dann was geschrieben, wenn man 
sie explizit initialisiert.

von A. H. (ah8)


Lesenswert?

Matthias Peters schrieb:
> Ach Mist verstehe. [0] [1] [2], wenn er am Ende eine Null reinschreibt
> habe ich nur 2 Stellen. Wie erklärt sich dann das dumme Ergebnis ?

Ist Dir das inzwischen klar? Das ist ein guter Test, ob Du die C-Strings 
verstanden hast. Dann ist die Antwort eigentlich offensichtlich. ;-)

von Matthias Peters (Gast)


Lesenswert?

char nummer[3];
nummer[0] = '2';
nummer[1] = '0';
A. H. schrieb:
> Matthias Peters schrieb:
>> Ach Mist verstehe. [0] [1] [2], wenn er am Ende eine Null reinschreibt
>> habe ich nur 2 Stellen. Wie erklärt sich dann das dumme Ergebnis ?
>
> Ist Dir das inzwischen klar? Das ist ein guter Test, ob Du die C-Strings
> verstanden hast. Dann ist die Antwort eigentlich offensichtlich. ;-)

Es ging ja um folgendes.

char num[3];
char nummer[3];
nummer[0] = '2';
nummer[1] = '0';
nummer[2] = '0';

char nummer2[3];
nummer[0] = '5';
nummer[1] = '0';
nummer[2] = '0';

float zahl = atof(nummer);
float zahl2 = atof(nummer2);
float ergebnis = zahl1 + zahl2;
dtostrf(ergebnis,8,3,num);

Als Ausgabe erhielt ich: 201000.000

Ich kann es mir nur so erklären, dass die Funktion atof die Zeichenkette 
so lange einliest, bis sie auf ein '\0' stößt, was bei mir nicht 
passiert, und deshalb über den reservierten Speicher hinaus irgendwann 
auf ein '\0' stößt. Vielleicht passiert das aber auch nicht, weil kein 
'\0' kommt und zahl wird mit irgendwelchen Werten gefüllt.

Dann ist die Fließkommazahl viel größer als 200.

Wenn ich das mit einer zweiten Zahl genau so mache und beide float 
Zahlen addiere bekomme ich dann so eine hohe Zahl heraus. Anders kann 
ich mir das nicht erklären.

Wenn man den Wert der float-Variable, also 201000.000 jetzt mal als 
"korrekt" annehmen würde, verstehe ich nicht, warum in der Ausgabe 
201000.000 angezeigt wird, ich habe der Zeichenkette num[3] ja 
prinzipiell nur 2 Plätze eingeräumt. Bedeutet ja, das die Funktion 
dtostrf den Speicherbereich erweitert ?
Komisch auch, das die 8 Stellen nicht passen.

von Matthias Peters (Gast)


Lesenswert?

edit:

char nummer2[3];
nummer2[0] = '5';
nummer2[1] = '0';
nummer2[2] = '0';

von Dirk B. (dirkb2)


Lesenswert?

Matthias Peters schrieb:
> Ich kann es mir nur so erklären, dass die Funktion atof die Zeichenkette
> so lange einliest, bis sie auf ein '\0' stößt,

Ja, tut sie.
Absolute Grundlagen über C-Strings.

Matthias Peters schrieb:
> Bedeutet ja, das die Funktion
> dtostrf den Speicherbereich erweitert ?

Nein, tut sie nicht.
Du als Programmierer bist dafür verantwortlich, dass der Platz 
ausreicht.

Lies es nach:
http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdlib_1ga060c998e77fb5fc0d3168b3ce8771d42.html

von A. H. (ah8)


Lesenswert?

Matthias Peters schrieb:
> Ich kann es mir nur so erklären, dass die Funktion atof die Zeichenkette
> so lange einliest, bis sie auf ein '\0' stößt, was bei mir nicht
> passiert, und deshalb über den reservierten Speicher hinaus irgendwann
> auf ein '\0' stößt.

Ganz genau. Jetzt musst Du nur noch daran denken, dass die drei Felder, 
die Du angelegt hast, im Speicher hintereinander stehen. Man kann sie 
also auch  als ein zusammenhängendes Feld betrachten und genau das 
machen die String-verarbeitenden Funktionen auch. Sie erhalten ja nur 
einen Pointer (siehe Pointer-Array-Beziehung) wissen daher nicht, wie 
groß Du das Feld deklariert hast. Auf die Positionen 0-2 des Feldes hast 
Du „200“ geschrieben. Das abschließende '\0' fehlt, wäre aber durch die 
nächste Aktion ohnehin überschrieben worden, den an die Positionen 3-5 
schreibst Du „500“. Im Speicher steht jetzt hintereinander also 
„200500“. In der darauf folgenden Position 6 muss zufällig ein '\0' 
stehen (oder ein anderes Zeichen, das atof zum Abbruch zwingt), denn 
das ist genau das, was das erste atof liest. Das zweite atof liest 
aus dem gleichen Speicher, nur beginnt es drei Positionen weiter hinten, 
also bei „500“. Und 200500+500 sind?

Du solltest das überprüfen können, indem Du Zahl und Zahl2 mal 
getrennt ausgibst.

PS: Das drei hintereinander deklarierte Felder tatsächlich einen 
zusammenhängenden Speicherbereich bilden muss nicht sein, es gibt da 
noch das Thema Alignment. Häufig ist es aber der Fall und das würde das 
Verhalten hier exakt erklären.

von A. H. (ah8)


Lesenswert?

Matthias Peters schrieb:

> Wenn man den Wert der float-Variable, also 201000.000 jetzt mal als
> "korrekt" annehmen würde, verstehe ich nicht, warum in der Ausgabe
> 201000.000 angezeigt wird, ich habe der Zeichenkette num[3] ja
> prinzipiell nur 2 Plätze eingeräumt. Bedeutet ja, das die Funktion
> dtostrf den Speicherbereich erweitert ?

Nein, das bedeutet es nicht, das kann ein Funktion gar nicht. Hast Du 
Dich schon mal gefragt, was ein Buffer-Overflow ist? Das hier ist einer, 
ein ganz klassischer.

Eine Funktion, die in den Speicher schreibt, kann, solange sie nur einen 
Pointer bekommt, genauso wenig wissen, wie groß der reservierte 
Speicherbereich ist, wie eine lesende Funktion dies kann. Sie muss sich 
darauf verlassen, dass der Speicherbereich, den Du reserviert hast, groß 
genug ist. Das kannst Du aber nur in den seltensten Fällen wirklich 
garantieren. Selbst wenn Du Dir vorher ausrechnest, wie viele Stellen 
ein Zahlenbereich höchstens haben kann, es kann ja sein, dass Dein Code 
mal auf eine andere Plattform portiert oder mit einem anderen Compiler 
übersetzt wird, und da können schon wieder ganze andere Werte gelten. 
Das war tatsächlich ein schwerer Design-Fehler früher 
C-Implementierungen. Heute sollte es eigentlich immer auch eine Variante 
der Funktion geben, der Du die Größe des Puffers mitgeben kannst und es 
ist definitiv besser, nur solche Funktionen zu verwenden. Denn anders 
hat ein Funktion keine Chance vernünftige Annahmen über die Größe des 
Puffers zu machen und das heißt, sie wird im Zweifel einfach über den 
Puffer hinaus schreiben. Das kann nicht nur zu undefiniertem Verhalten 
führen, denn in dem versehentlich überschrieben Speicher wird in der 
Regel ja etwas anders Wichtiges stehen, sondern ist, zumindest bei 
Funktionen, die die zu schreibenden Daten von außen erhalten, auch ein 
erhebliches Sicherheitsrisiko, denn es kann gegebenenfalls genutzt 
werden, um beliebige Daten in Bereiche zu schreiben, die später als Code 
interpretiert werden.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Matthias Peters schrieb:
> char num[3];
> char nummer[3];

Du hast hier sogenannte Auto-Variablen definiert. Deren Anfangswert (und 
damit deren Speicherstellen) sind undefiniert. Wenn Du Glück hast, 
steht da irgendwo mal ein NUL-Byte (ja, NUL mit einem L) drin.

Aber auf Glück darf ein Programmierer niemals spekulieren.

Damit dort eine dreistellige Zahl gespeichert wird, brauchst Du pro 
String 3+1 = 4 Bytes.

Schreibe also:
1
char num[4];
2
char nummer[4];

Jetzt weiter:

> nummer[0] = '2';
> nummer[1] = '0';
> nummer[2] = '0';

Wenn Du Glück hast, steht in nummer[3] bereits ein NUL, also '\0'. Die 
Chance ist aber nur 1/256, kannst Du also vergessen.

Deshalb musst Du schreiben:
1
nummer[0] = '2';
2
nummer[1] = '0';
3
nummer[2] = '0';
4
nummer[3] = '\0';

Oder auch kürzer:

strcpy (nummer, "200");

Über den Speicherplatzbedarf Deines dtostrf()-Aufrufs mach Dir bitte nun 
selbst Gedanken. Stelle dann einen entsprechend großen Buffer zur 
Verfügung. Dann klappt das auch.

von Matthias Peters (Gast)


Lesenswert?

Vielen Dank nochmals Leute, für die zum Teil sehr detaillierten 
Erklärungen!
Das ist echt Spitze !

Ihr habt mir wircklich sehr sehr geholfen :-)

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.