Forum: Compiler & IDEs Problem bei Deklaration von globaler Char-Variable


von Thorsten B. (bear8234)


Angehängte Dateien:

Lesenswert?

Hallo Forum!

Ich habe hier ein Problem, welches ich mir nicht erklären kann. Vor
einiger Zeit hatte ich schon mal wegen dieses Problems nachgefragt
(siehe hier
[url]http://www.mikrocontroller.net/forum/read-2-260439.html#new[/url]).
Inzwischen habe ich weiter nach der Ursache geforscht und habe
herausbekommen, dass dieses Problem immer dann auftaucht, wenn ich
bestimmte globale char-variablen anlege. Daraufhin habe ich diesen
Funktionsteil ausgelagert in ein separates c-file. Als ich jetzt diesen
Teil im Hauptprogramm wieder eingebunden habe, ist das Problem erneut
aufgetreten. Ich habe das ganze auf einem ATmega32 laufen (sprich
generiert, auf den Controller geladen und mit angeschlossener Hardware
ausprobiert).

Wenn ich also den Teil ACList.h in Doorcontrol.c einbinde, erhalte ich
auf dem LCD-Display nur noch verstümmelte Anzeigen (die erstel Zeile
fehlt, da die Anzeigeroutine scr_show bei 1 statt bei 0 anfängt zu
zählen), außerdem wird die Anzeige andauernd neu aufgebaut (da das Flag
Refresh im Display-Struct durch die Anweisung Display.Refresh = 0 im
Hauptprogramm statt auf 0 auf 1 gesetzt wird).

Ich habe das komplette Projekt als ZIP angefügt. Hat irgendjemand eine
Idee, wo ich den Fehler mache (ich gehe mal stark davon aus, das es ein
Fehler meinerseits ist).

Gruss und vielen Dank schon mal

Thorsten

PS: Bitte nicht erschrecken über den Code, ich bin noch reichlicher
Anfänger, was C-Programmierung angeht.

von Patrick D. (oldbug) Benutzerseite


Lesenswert?

Also ich mach mal den Anfang.

1. aus 'ACList.h":
1
#include "stdint.h"

Dieses Headerfile solltest Du mit spitzen Klammern (<stdint.h>)
einbinden.

2. aus ACList.c
1
strcpy( &ActiveRecord.Codeword[0], "123456\0" );

Das mag sich jetzt Hart anhören, ist aber nicht ganz so schlimm zu
verstehen ;)
Kapitel Pointer, Arrays und Strings aus Deinem C-Buch (falls vorhanden)
nicht verstanden.
1
strcpy( ActiveRecord.Codeword, "123456" );

3. Globale Variablen und deren Initialisierung

Globale Variablen brauchen nicht explizit mit '0' Initialisiert
werden, wenn sie nicht das Attribut besitzen, nicht automatisch
Initialisiert zu werden.

4. Nochmal Kapitel 'Pointer, Arrays und Strings'
1
/*          Index: 0 1 2 3       */
2
/*         Inhalt: 0 0 0 0 == 4! */
3
char buffer[3] = "\0\0\0";

Ich erhebe keinen Anspruch auf Vollständigkeit...

von Thorsten B. (bear8234)


Lesenswert?

Hallo Patrick!

Vielen Dank für die aufgezeigten Punkte, habe ich sogleich im ganzen
Projekt umgesetzt... nur an dem Problem mit 0 und 1 ändert sich
nichts:

Aus DoorControl.c, Zeile 357ff:
1
Display.Last = Display.Now;
2
Display.Refresh = 0x0;

Lässt man sich anschließend Display.Refresh z.B. auf dem LCD ausgeben,
ist der Wert 1 und nicht 0. Hast du dazu vielleich auch eine Idee?

Ach ja, welches Buch würdest du in diesem Zusammenhang empfehlen?
Bisher habe ich nur Info's aus Online-Quellen verwendet, aber ein Buch
wäre schon besser ;-)

Gruss Thorsten

von Karl H. (kbuchegg)


Lesenswert?

Ich hab jetzt Deinen Code nicht im Detail analysiert.
Aber: Wenn Variablen seltsam ihren Wert aendern, dann
hat das meist eine von 2 Ursachen:
* irgendwo wird ein Array ueberlaufen.
* irgendwo zeigt ein Pointer ins Nirwana. Zufaellig halt
  auf diese Variable.

Da Du einen anderen beliebten Fehler gemacht hast, tippe
ich mal auf 1) Array Ueberlauf.

In C wird bei der Definition eines Arrays die Anzahl der
Elemente angegeben. Also:
char buffer[3];
richtet ein Array mit 3 Elementen ein.
In C wird aber bei 0 angefangen zu zaehlen. Also sind die
entsprechenden Array-Elemente:
  buffer[0], buffer[1], buffer[2]

Macht genau 3 Stueck. Der hoechste zulaessige Index in ein
Array ist also immer um 1 kleiner als die Anzahl die Du
angefordert hast.

Du hattest
char buffer[3] = "\0\0\0";

Was ist daran falsch?
Nun. Der String "\0\0\0" hat eine Laenge von 4 und nicht 3.
4 deswegen, weil jeder String in C immer mit einem '\0'
abgeschlossen wird. Auch dann wenn der String selbst aus
lauter '\0' besteht.

Du muesstest also schreiben
  char buffer[4] = "\0\0\0";

Hmm. Gefaellt noch nicht. Das Abzaehlen wie lang so ein String
ist, kann doch auch der Compiler uebernehmen. Der macht das
genauer und zuverlaessiger als die meisten Programmierer:

  char buffer[] = "\0\0\0";

Und schon delegierst Du das laestige Abzaehlen an den Compiler.

Soviel zu Arrays und was es mit den Zahlenangaben auf sich hat.
Wie gesagt: Untersuch mal Dein Program ob Du nie 'hinter' ein Array
schreibst. Wenn das passiert, kann es durchaus sein (falls der
Compiler eine Variable gleich hinter das Array im Speicher platziert),
dass dieser Überlauf dann genau in einer anderen Variablen landet.

von Thorsten B. (bear8234)


Lesenswert?

Hallo Karl Heinz,

ich kann dir soweit ja folgen und wenn dieser Effekt nicht zeitnah
auftreten würde, würde ich dir ja sofort zustimmen. Allerdings verstehe
ich nicht, wie dieser Effekt bei folgendem Code auftreten sollte:

Aus screens.c, Zeile 178ff:
1
  if( ScrNo < SCRRENS_H_COUNT )
2
  {
3
    for( RowNr = 0; RowNr < 4; RowNr++ )
4
    {
5
      TxtNr[RowNr] = pgm_read_byte( &pgmScreenLines[ScrNo][RowNr] );
6
7
      if( TxtNr[RowNr] > 0 )
8
      {
9
        UsrText[RowNr].ptrText = (char*)( pgm_read_word(
10
&pgmStrings[TxtNr[RowNr] - 1] ) );
11
        UsrText[RowNr].XPos = pgm_read_byte( UsrText[RowNr].ptrText );
12
        UsrText[RowNr].ptrText += 1;
13
        // String aus dem Programmspace in mybuffer kopieren...
14
        strcpy_P( TextBuffer, UsrText[RowNr].ptrText );
15
        // und nun auf dem LCD ausgeben
16
        lcd_xyputs( UsrText[RowNr].XPos, RowNr, TextBuffer );
17
      }
18
    }
19
    switch( PgSym )
20
    {
21
    case 1: lcd_xyputc( 19, 0, '\01' ); break;
22
    case 2: lcd_xyputc( 19, 0, '\02' ); break;
23
    case 3: lcd_xyputc( 19, 0, '\03' ); break;
24
    }
25
  }
In Zeile 3 dieser Funktion beginne ich eine for-schleife, die
eigentlich RowNr von 0 bis 3 hochzählen soll. Allerdings fängt das olle
Ding mit RowNr=1 an, daher erhalte ich auch diese verstümmelten
LCD-Ausgaben. Kann das auch irgendwie mit dem genannten Fehler
zusammenhängen?

Gruss Thorsten

von Karl heinz B. (heinzi)


Lesenswert?

Wie hast Du festgestellt, dass in der ersten Iteration
RowNr den Wert 1 hat?

Das ganze wird jetzt etwas schwierig, da Du eine Moeglichkeit
brauchst, den Inhalt einer Variablen anzuschauen ohne dabei
das LCD zu benutzen. Da musst Du Dir was dazu einfallen lassen.
Ich postuliere jetzt einfach mal, dass Du ueber printf das
ganze machen kannst.

Aendere mal wie folgt:

if( ScrNo < SCRRENS_H_COUNT )
{
  for( RowNr = 0; RowNr < 4; RowNr++ )
  {
    printf( "%d", RowNr );

    TxtNr[RowNr] = pgm_read_byte( &pgmScreenLines[ScrNo][RowNr] );

    printf( "%d", RowNr );

    if( TxtNr[RowNr] > 0 )
    {
      UsrText[RowNr].ptrText = (char*)( pgm_read_word(
                                           .....
      printf( "%d", RowNr );

      ...

d.h. nach jedem, aber auch wirklich jedem Funktionsaufruf
laesst Du Dir den Wert von RowNr anzeigen.
Wenn Du dann das Pgm laufen laesst, dann wird es eine Stelle
geben, an der RowNr 'von selbst' von 0 auf 1 springt.
In dieser Funktion musst Du dann ansetzen. Dort (oder in einer
Funktion die dort aufgerufen wird) ist was faul.

Ich weiss schon: Du moechtest von mir hoeren, dass da moeglicherweise
ein Fehler im Compiler ist. Klar, auch ein Compiler ist ein Program
und hat als solches klarerweise Fehler. Die Erfahrung zeigt aber,
dass es zu 99,9% Fehler im Benutzercode sind, und nicht Fehler
im Compiler. Das ein Newbie einen Compilerfehler findet, ist
mehr als unwahrscheinlich.

von Thorsten B. (bear8234)


Lesenswert?

Hallo Karl Heinz,

als erstes noch mal, ich gehe ganz fest davon aus, das dieser Effekt
auf einen Fehler in meinem Code zurückzuführen ist, nicht auf einen
Compiler-Bug...

Angesichts dessen, das in der Steuerung später eine serielle
Schnittstelle vorhanden sein soll, habe ich die entsprechenden
Funktionen jetzt bereits eingebunden und den Wert von RowNr dort
ausgegeben.

Nur ist der Fehler jetzt nicht mehr vorhanden! Meine Displays sind
einwandfrei und auch sonst funktioniert im Moment alles. Verstehen muss
man das wohl nicht, ich glaube jedoch, das der dicke Hund noch
irgendwann später kommt, denn *da schlummert mit Sicherheit noch ein
hübscher Bug* im Programm...  Vielen Dank erst mal für deine Hilfe.

Gruß Thorsten

von Unbekannter (Gast)


Lesenswert?


von Karl H. (kbuchegg)


Lesenswert?

Nun, der Fehler ist jetzt moeglichweise nicht mehr
sichtbar, aber ist noch vorhanden <g>

Alle Deine Symptome deuten darauf hin, dass du irgendwo
in Deinem Code was schlimmes angestellt hast. Veraenderst
Du Dein Program (wie zb durch Einfuegen von printf), so
beginnt der Fehler zu huepfen.

Solche Fehler sind extrem schwer zu finden. Da gehoert auch
immer ein gute Portion Glueck dazu, dass man an der richtigen
Stelle sucht. Denn dort wo Du den Fehler siehst und wo der
Fehler eigentlich entsteht, da liegen oft Welten dazwischen.

Du kannst mal folgende Starategie probieren:
Schmeiss Programmteile raus. Im Extremfall, fange bei 0 an
(wortwoertlich). Dann beginnst Du aus dem alten Program Teile
ins neue zu uebernehmen. Schau Dir jeden Teil extrem genau
und extrem gut an. Nach jeder Uebernahme heist es, das neue
Progranm auf Herz und Nieren durchzuchecken. Funktioniert auch
wirklich alles? Unter allen Umstaenden. Erst dann den naechsten
Programteil dazunehmen. Und wieder: zuerst auf die ueblichen
Verdaechtigen durchschauen: Array-Indizierungen und wilde Pointer.
Dann: testen, testen, testen. Dann den naechsten Teil. etc. etc.

Ja das dauert. Stell Dich mal auf laengere Debug-Zeiten ein.
Mein persoenliches Extrem, sind 10 Jahre(!). Solange hab ich
an einer Solid-Modelling-Bibliothek rumgetestet (die nicht von
mir stammt). Sie laeuft heute noch nicht fehlerfrei.

von Thorsten B. (bear8234)


Lesenswert?

Na du kannst einem ja Mut machen - in 10 Jahren wird die Steuerung wohl
keiner mehr brauchen ;-)

Also mal Spass beiseite,

irgendwie macht mir das nicht gerade Mut, was du da schreibst (auch
wenns warscheinlich nur die unverblümte Wahrheit ist). Ich denke mal,
das ich eine andere Strategie verfolgen werde. RESET AND RESTART, also
noch mal bei 0 anfangen, allerdings ohne einbeziehung des alten Codes.
Meine Vermutung ist, das ich bei Anpassen von "geborgtem Code"
irgendwo einen dicken Hund eingebaut habe (oder der vielleicht auch
schon drinn war...). Dieses Mal werde ich wohl nur die Prinzipien
übernehmen und den entsprechenden Code neu schreiben.

Eine Frage brennt mir aber noch unter den Nägeln. Wie stehst du
eigentlich zum Debugging in der IDE, sprich z.B. per HAPSIM die
benötigte Hardware simulieren und dann das Programm erst in späteren
Phasen auf der realen Hardware ausprobieren - oder würdest du eher zu
einem HW-Testsystem tendieren (also JTAG- bzw. ICE-Hardware und dann
von Anfang an auf der Zielhardware)?
Da mit zur Zeit nämlich solches nicht zur Verfügung steht, bin ich mit
der IDE + HAPSIM - Methode an Entwickeln.

von Unbekannter (Gast)


Lesenswert?

Mein Tip:

Auch bei Mikrocontrollern Hardware-Teile nicht in die Algorithmen
implementieren, sondern definierte Schnittstellen verwenden, so dass
die einzelnen Module problemlos getestet werden könnnen (automatische
Tests verwenden!).

Dann können die Algorithmen auch mit Memory-Profilern überprüft werden.
Dazu eignet sich z.B. auch sehr gut:

  http://valgrind.org/

von Karl H. (kbuchegg)


Lesenswert?

@Thorsten

Schoen, das Du das so locker siehst. Manchmal muss man
auf die Leute einreden, wie auf eine kranke Kuh, bis sie
endlich soweit sind, alles bisherige ueber Bord zu schmeissen
und neu anzufangen :-)

Das Debuggen mit IDE+Simulator ist fuer mich ok.
Es kommt zwar auf Deine reale Anwendung an, aber meist ist
es doch so, dass die eigentliche Ein/Ausgabe nur einen
kleinen Bruchteil der Anwendung ausmacht. Fuer diesen Teil
musst du letztendlich an die reale Hardware ran. Da hilft
alles nichts. Es ist aber schon sehr beruhigend, wenn man weis,
das der ganze Berechnungsteil bereits fertig durchgetestet
und (hoechstwahrscheinlich) keine schwerwiegenden Fehler mehr
enthaelt. Zeigt sich dann doch noch was, dann kann man den
bereits rigoros getesteten Teil als fehlerfrei annehmen und sich
voll auf die I/O konzentrieren.

Das Prinzip in der SW-Entwicklung das am besten funktioniert,
ist halt immer noch: Teilen, teilen, teilen.
D.h. Aufteilen des Gesamtproblems in einzelne Teilprobleme. Diese
(moeglicherweise auch losgeloest vom ganzen Rest) implementieren,
testen und erst dann die Einzelteile wieder zum Gesamten zusammen-
setzen.

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.