Forum: Compiler & IDEs "Geisterwerte" in einer Variable trotz Initialisierung


von JayJay (Gast)


Lesenswert?

Hallo alle zusammen,

ich hab ein kleines Problem mit einer Variable, die ein gewisses 
eigenleben entwickelt hat.

Ich bastle gerade einen kleinen Taschenrechner und habe erstmal eine 
einfache Addition implementiert. Alles funktioniert prima... zumindest 
bis man versucht Zahlen über 999 miteinander zu addieren. Sobald ein 
Summand 4-stellig wird lässt sich etwas interessantes beobachten: In der 
nächsten Addition werden die beiden letzten Zahlen des vorherigen ersten 
Summanden ab der 2. eingegeben Zahl hinten angehängt! :-D Ich weiß wie 
bekloppt das klingt, aber mein lcd lügt mich nicht an. Ein Beispiel:

1. Addition
Eingabe:
1122 + 3
Ausgabe:
1125

2. Addition
Eingabe:
22 + 3
Ausgabe:
2225, weil 22 die letzten beiden Stellen aus der vorhergehenden Addition 
abbekommt und zu 2222 wurde.

Ich dachte natürlich es liegt daran, dass ich die Variablen oder den 
Zwischenspeicher nicht wieder auf 0 setzte, bevor ich die nächste 
Addition mache, aber daran liegt es ganz sicher nicht (s. Quellcode) und 
die letzten beiden Stellen werden ja auch erst angehängt, nachdem die 2. 
Zahl eingegeben wurde.

Mich macht das irgendwie ganz schön konfus... hier jetzt erstmal der 
Quellcode:
1
uint16_t add_zahl (char zahl, char ausg){  // Zahl an den Zwischenspeicher hängen
2
  if (flag == 1){                    // Bildschirm nach fertiger Rechnung löschen
3
    lcd_clear();
4
    flag =0;
5
  }
6
7
  lcd_string(ausg);                 // eingegebene Zahl ausgeben
8
  wert[i] = zahl;                   // Zahl an den Zwischensp. hängen
9
  i++;                              // Position im Zwischensp. erhöhen
10
  if(i > 5){                        // Abfrage ob eingegebene Zahl zu groß für 16-bit Variablen
11
    lcd_home();
12
    lcd_string("zu lang!");
13
    _delay_ms(2000);
14
    lcd_clear();
15
    i = 0;
16
  }
17
  return 0;
18
}
19
 
20
int main( void )
21
{
22
23
 
24
// unwichtiges Initialisierungsblabla
25
26
  int16_t a = 0;
27
  int16_t b = 0;
28
  char erg[8];
29
  sprintf(wert, "0");
30
    
31
    while(1){
32
    
33
    if( get_key_short( 1<<KEY1 )){          // Taster für die Eingabe von "1"
34
      add_zahl(49, "1");              // Übergabe von ASCII-Wert und der Zahl als char *
35
    }
36
    
37
               // weitere Taster
38
39
    if( get_key_short( 1<<KEY_PLUS )){      // Plus Taste
40
                a = atoi(wert);                 // in int umwandeln
41
      lcd_string("+");
42
      sprintf(wert, "0");             // Zwischenspeicher nullen
43
      i=0;        // Nummernlänge zurücksetzen
44
    }
45
46
    if( get_key_short( 1<<KEY_GLEICH )){    // Gleich Taste
47
      b = atoi(wert);                 // in int umwandeln
48
      b = a+b;                        // die Addition
49
      itoa(b, erg, 10);               // zurückwandeln
50
 
51
      set_cursor(0,2);
52
      lcd_string(erg);                // Ausgeben
53
      
54
      sprintf(wert, "0");             // alles auf 0 setzten
55
      i=0;
56
      flag = 1;
57
      a=0;
58
      b=0;
59
    }
60
61
  }
62
}

* kann man das nicht auch irgendwie einheitlich machen? Zumal der letzte 
Übergabewert beim compilieren immer die Warnung erzeugt:
"warning: passing argument 2 of 'add_zahl' makes integer from pointer 
without a cast"

Ich programmiere eigentlich Java und das auch nicht besonders viel, 
daher kann es gut sein, dass mein C Code wegen Unleserlichkeit und 
Umständlichkeit einigen Unmut bei euch hervorruft. Korrigiert mich bitte 
bei allem, was man auch einfacher lösen kann, schließlich lerne ich 
gerne dazu!

Gruß,
JayJay

von JayJay (Gast)


Lesenswert?

upps hatte die globalen Variablen vergessen:
1
char wert [5];
2
uint8_t i = 0;
3
uint8_t flag = 0;

von Stefan E. (sternst)


Lesenswert?

Typischer Fehler beim Umgang mit Strings.
Der String in wert[] wird nicht terminiert und außerdem ist das Array zu 
klein. Und du schreibst ggf. auch hinter das Array.

von JayJay (Gast)


Lesenswert?

So jetzt hats sichs ausgegeistert. Indem ich vor jeder Umwandlung noch 
eine Endekennung hinten ans Char-Array gepackt habe funktioniert alles 
wunderbar. Ich bin doch schon zu sehr an Java gewöhnt...

Ein bisschen Verwirrung haben noch die Funktionen atoi() und itoa() 
gestiftet, die ich in atol() und ltoa() geändert habe, damit ich mit 
32bit Variablen rechnen kann.

Ärgerlich ist nur, dass die beiden Funktionen keine Errormeldungen 
zurückgeben, wenn das Ergebnis außerhalb des Wertebreichs liegt. Bei 
normalem C sollte das eigentlich so sein, aber AVR GCC sagt in der 
Header-Datei stdlib.h:
"In contrast to

\code (int)strtol(s, (char **)NULL, 10); \endcode

this function does not detect overflow (\c errno is not changed and
the result value is not predictable), [blabla]"

wie strol() die Errormeldung bei strol aber genau funktioniert hab ich 
nicht so recht verstanden.

Für Fließkommazahlen gibt es schließlich atof() aber kein ftoa(). Wie 
soll ich Fließkommazahlen dann auf dem LCD ausgeben?

Naja und eine Frage bleibt: Gibts irgendeine elegante Möglichkeit diese 
doofen Warnungen loszuwerden:"warning: passing argument 2 of 'add_zahl' 
makes integer from pointer without a cast"

so long...

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

> Naja und eine Frage bleibt...
Ja, "1" ist ein String :-o
besser wäre
1
  add_zahl(49, '1');  // Übergabe von ASCII-Wert und der Zahl als char

Oder (wenn der Kommentar stimmen soll)
1
uint16_t add_zahl (char zahl, char* ausg){  // Zahl an den Zwischenspeicher hängen
2
:
3
  lcd_string(ausg);   ???? was ist der Uebergabewert an lcd_string()? ein Pointer?
4
:
5
  add_zahl(49, "1");  // Übergabe von ASCII-Wert und der Zahl als char Pointer

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

JayJay wrote:

> Ärgerlich ist nur, dass die beiden Funktionen keine Errormeldungen
> zurückgeben, wenn das Ergebnis außerhalb des Wertebreichs liegt. Bei
> normalem C sollte das eigentlich so sein,

Nein.  Du solltest dir den C-Standard mal ansehen...

> wie strol() die Errormeldung bei strol aber genau funktioniert hab ich
> nicht so recht verstanden.

Die ,,Meldung'' besteht daraus, eine 0 als Ergebnis der Konvertierung
zu liefern.  Falls das Ergebnis zu groß oder klein wird, wird
errno auf ERANGE gesetzt und die Konvertierung liefert LONG_MAX
bzw. LONG_MIN.

Die Erkennung eines Fehlers erfolgt, indem man die Adresse einer
Zeigervariablen bei endptr angibt.  Nach der Konvertierung, die 0
ergeben hat, schaut man dann nach, worauf der Zeiger zeigt.  Zeigt
er auf ein Zeichen '\0', dann war die Konvertierung (mit dem Wert
0) erfolgreich, da alle Zeichen bis zum Ende des Strings konvertiert
werden konnten.  Zeigt der Zeiger auf was anderes, dann ist das
das erste nicht mehr konvertierbare Zeichen.  Ob du nun einen String
"12x" als gültigen Wert 12 akzeptieren willst (endptr würde auf das
Zeichen 'x' zeigen) oder nicht, musst du mit deiner Applikation
vereinbaren.

All das ist Standard-C; das kannst du besser an einem PC ausprobieren
als auf einem Controller.  Wenn du verstanden hast, wie's funktio-
niert, übernimmst du dann den Code in den Controller.

> Für Fließkommazahlen gibt es schließlich atof() aber kein ftoa(). Wie
> soll ich Fließkommazahlen dann auf dem LCD ausgeben?

sprintf (dann bitte aber gegen die Gleitkommaversion linken), oder
als Einfach-Varianten dtostre/dtostrf (das sind private Erweiterungen
der avr-libc gegenüber dem Standard).

> Naja und eine Frage bleibt: Gibts irgendeine elegante Möglichkeit diese
> doofen Warnungen loszuwerden:"warning: passing argument 2 of 'add_zahl'
> makes integer from pointer without a cast"

Natürlich: sauberen Code schreiben.  Wenn dein zweites Argument ein
Zeiger ist, warum deklarierst du add_zahl dann so, dass sie einen
Integer übernimmt?

von JayJay (Gast)


Lesenswert?

So ich hab mich jetzt ein bisschen in Zeiger und Vektoren eingelesen. In 
der Tat gibt es keine Warnungen mehr und alles funktioniert prima, wenn 
der Funktionskopf so aussieht:
1
uint8_t add_zahl (char zahl, char *ausg){
und so aufgerufen wird:
1
add_zahl(49, "1");
Nur irgendwie verstehe ich nur so halb warum, denn der 
Dereferenzierungsoperator * sagt doch nur, dass auf den Inhalt der 
übergeben Adresse zugegriffen wird und wird deswegen so etwa in dieser 
Form benutzt:
1
void cquadrat(int *wert){
2
  *wert=(*wert)*(*wert);
3
}
4
5
void main (void){
6
  int x;
7
  scanf("%i",&x);
8
  cquadrat(&x);
9
  //irgendeine Ausgabe
10
}
Aber
1
add_zahl(49, "1");
 übergibt doch aber keine Adresse einer Variable sondern einen 
stinknormalen char. Oder behandelt C das wie char-Array der Länge 2 (
1
{'1',0}
) bei der Übergabe, sodass noch dereferenziert muss (schließlich ist ein 
char-Array ja immer nur die Adresse eines Variablenfeldes)?


> Nein.  Du solltest dir den C-Standard mal ansehen...

Hupps.. ich hab mir ne C++ Referenz angeschaut und gedacht in diesen 
elementaren Funktionen der stdlib hätte sich nix geändert... falsch 
gedacht wie sich rausstellt. ^^

so long...

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

>>> "1" <<<
Ist wie von dir beobachtet ein String = Char-Array {'1',0}.

Ein Char ist:
>>> '1' <<<

von Daniel -. (root)


Lesenswert?

1
void cquadrat(int *wert){
2
  *wert=(*wert)*(*wert);
3
}

Das ist eine grosse pfui kacka in C und C++. Wie es Java und C# 
behandeln
weiss ich nicht. C und C++ haben so etwas wie Sequenzpunkt, dh alle
Nebeneffekte klingen ab nach einem Sequenzpunkt.
Hier modifizierst du Quelle und Ziele in einem Sequenzpunkt ..
das ist analog zu i = i++;

weitere Fehlerquellen ähnlicher Art
int foo(int a, int b){return a*b;}
int x=1;
foo(x,++x);

Ich glaube die C Profis sind da sensibilisiert dadrauf und wenn sie
andere Sprachen lernen, dann fragen die sich wie macht es wohl Java
an der Stelle? Die meisten Javaprogrammierer achten viel weniger
dadrauf, meine Meinung.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Daniel -------- wrote:

> das ist analog zu i = i++;

Nein, es ist analog zu i = i * i, und das ist völlig legal.
Undefiniert wäre sowas wie:

*wert = (*wert)++ * (*wert);

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Daniel -------- wrote:
>
1
> void cquadrat(int *wert){
2
>   *wert=(*wert)*(*wert);
3
> }
4
>

Das ist doch ok -- im Gegensatz zu
1
i = i++;

oder
1
foo (a++, a++);

von Rolf Magnus (Gast)


Lesenswert?

> Aber
> add_zahl(49, "1");
> übergibt doch aber keine Adresse einer Variable

Doch - naja, nicht die einer Variablen, aber die eines "string literal".

> sondern einen stinknormalen char. Oder behandelt C das wie char-
> Array der Länge 2 ({'1',0}) bei der Übergabe,

Das wird nicht nur wie ein char-Array behandelt, sondern es ist eins. 
Bei der Übergabe an die Funktion wird dann ein Zeiger auf das erste 
Zeichen daraus. Wie sollte das auch sonst funktionieren? So ein String 
Literal ist immer gleich aufgebaut, egal wieviele Zeichen da drinstehen.
Wie du ja selbst oben schreibst, mußt du '1' draus machen, wenn du 
wirklich nur ein einzelnes Zeichen haben willst und kein Array.

> sodass noch dereferenziert muss (schließlich ist ein
> char-Array ja immer nur die Adresse eines Variablenfeldes)?

Das stimmt nicht. Ein char-Array ist nicht die Adresse, aber es kann in 
den meisten (aber nicht allen Fällen) genauso verwendet werden. Es ist 
wichtig, den Unterschied zu erkennen.

> Hupps.. ich hab mir ne C++ Referenz angeschaut und gedacht in
> diesen elementaren Funktionen der stdlib hätte sich nix geändert...
> falsch gedacht wie sich rausstellt. ^^

Eigentlich hat sich da auch nichts geändert. C++ übernimmt einfach die 
komplette Standardlib von C und hängt noch ein paar Sachen dazu.

von JayJay (Gast)


Lesenswert?

>Eigentlich hat sich da auch nichts geändert.

Auf cplusplus.com findet man für atoi() und seine Freunde:

>On success, the function returns the converted integral number as an int value.
>If no valid conversion could be performed, a zero value is returned.
>If the correct value is out of the range of representable values, INT_MAX >or 
INT_MIN is returned.


Hmm... ich finde diese ganzen Zeiger relativ kompliziert... Zwar gibt es 
auch bei Java Vektoren aber die benutzt man selten. Hier wird bei der 
Übergabe nicht auf Adressen oder ähnliches geachtet, es muss nix 
dereferenziert werden und das macht die geschichte angenehm einfach.

Ich denk mal das Thema ist damit abgeschlossen.

Danke für eure Hilfe.

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.