Forum: Compiler & IDEs Stack-Überlauf durch externe Variable?


von flyingwolf (Gast)


Lesenswert?

Hallo Leute.
Ich habe gestern in meinem inzwischen nicht mehr ganz so kleines 
Programm (für einen 90CAN128) eine existierende case - Schleife um 4 
Anweisungen ergänzt.
Die Anweisungen in der Schleife sehen etwa alle gleich aus.

case 23:       strcpy((char*)&bufferstring[0],(char*)&serialstring[4]);
var23 = atoi(bufferstring);
write_var23();
send_string_uart0("OK\n\r");
break;

Alle Variablen in dieser Funktion kommen aus dem main.c und sind dort 
global definiert. Am Anfang dieser Funktion befindet sich entsprechend 
noch die Deklaration dieser Variablen

extern int var23;

Danach ging in dem Programm nix mehr. Das Display flackert nur noch 
wild, gelegentlich gibt es einen Reset.
Die Case-Anweisung wird zu diesem Zeitpunkt nicht durchlaufen.
Ich tippe mal auf Stack-Überlauf, denn wenn ich die Zeilen wieder 
entferne geht alles wie gewohnt.
Das führt uns zu folgenden Fragen:
Extern deklarierte Variable verbrauchen Programmspeicher?
Permanent?
Wie sieht es dann mit lokalen Variablen aus?
Wenn externe Variable sich am Programmspeicher bedienen, gibt es dann 
auch einen Weg, dies zu verhindern?

von Oliver (Gast)


Lesenswert?

>Extern deklarierte Variable verbrauchen Programmspeicher?
Ja

>Permanent?
Ja

>Wie sieht es dann mit lokalen Variablen aus?
Radio Eriwan. Die benötigen entweder Programmspeicher, solange sie 
existieren, oder sie werden in einem Prozessorregister gehalten (wenn 
sie klein genug sind, und ausreichen Register im Prozessor frei sind).

>Wenn externe Variable sich am Programmspeicher bedienen, gibt es dann
>auch einen Weg, dies zu verhindern?
Nicht wirklich. Das Wesen einer Variablen ist nunmal, einen 
Speicherbereich darzustellen.

Oliver

von flyingwolf (Gast)


Lesenswert?

<<Nicht wirklich.>>

Und wenn ich alles ins Main.c stopfe mit dem Pferdefuß einen 
unübersichtlichen Programmcode zu erzeugen aber dafür den Speicher zu 
sparen?

von Wolfram (Gast)


Lesenswert?

ich tippe auf
>strcpy((char*)&bufferstring[0],(char*)&serialstring[4]);
Überprüfe die Länge und die 0 Terminierung.

von flyingwolf (Gast)


Lesenswert?

@ Wolfram
Der Programmteil wird nicht ausgeführt. Es kommt schon vorher zum 
Fehler, ohne das 
strcpy((char*)&bufferstring[0],(char*)&serialstring[4]); überhaupt zur 
Ausführung kommt.

von Oliver (Gast)


Lesenswert?

>Und wenn ich alles ins Main.c stopfe mit dem Pferdefuß einen
>unübersichtlichen Programmcode zu erzeugen aber dafür den Speicher zu
>sparen?

Es gibt nur ein Programm, und nur ein Ram. Da ist alles drin. Es ist 
völlig egal, ob du deinen Surce-Code in eine oder in 100 Dateien 
schreibst, und es ist auch völlig egal, wie oft du darin eine Variable 
als extern derklarierst.

int var23; benötigt exakt 2 byte Ram.
extern int var23; benötigt gar nichts, das gibt dem Compiler nur die 
Möglichkeit, diese Source-Datei zu Kompilieren, mit dem Hinweis, das 
var23 irgendwo anders deklaiert wird. Der Linker macht dann den Rest.

Wenn du allerdings gar keine Variablen hinzugefügt hast (var23 gabs 
schon vorher), dann darf eigentlich nichts passieren. Daher ist die von 
Wolfram geäusserte Vermutung, daß da igend was anderes queschiesst, gar 
nicht so abwegig.

Jetzt ist Hardcore-Debugging angesagt.

Oliver

von flyingwolf (Gast)


Lesenswert?

<<int var23; benötigt exakt 2 byte Ram.
extern int var23; benötigt gar nichts, >>

Ich merke, ich habe mich bei der Fragestellung missverständlich 
ausgedrückt, entsprechnd Deine erste Antwort missverstanden.

Das die Case Schleife schuld ist, kann ich mir nicht vorstellen. 
Immerhin stehen schon 22 identische Anweisungen davor, nur eben für 
var01 - var22.

von Oliver (Gast)


Lesenswert?

Ok, da hatte ich die Frage anders verstanden.

Aber trotzdem: Ein xx128 hat 4kB SRAM. Das bekommst du mit 
Integer-Variablen so schnell nicht voll, und auch 
Funktionsverschachtelung führt da so schnell nicht zu Problemen.
Entweder hast du da riesige Arrays, sowas wie
1
int LCD_BUFFER[240][128]; // :-))
oder Stringkonstanten im Ram, anstatt im Flash, oder so etwas.

Und die Vermutung mit amoklaufenden Stringfunktionen ist auch noch nicht 
vom Tisch.

Oliver

von flyingwolf (Gast)


Lesenswert?

Nun, es ist einiges von diesem Zeug darin,

send_string_uart0("OK\n\r");
aber das sollte doch im Flash stehen, oder?

Und es gibt noch ein großen String für die variablen Ausgaben.
char sendstring[40];
Das ist aber schon die größte und die wird ja auch nicht beeinflusstr 
von dem Code in der Case-Schleife

Ein paar von den varXX sind floats, aber das kann es ja auch nicht sein. 
...

von Karl H. (kbuchegg)


Lesenswert?

flyingwolf wrote:
> Nun, es ist einiges von diesem Zeug darin,
>
> send_string_uart0("OK\n\r");
> aber das sollte doch im Flash stehen, oder?

Nein. Warum sollte es?
Die send_string_uart0 muss ganz anders aussehen, wenn der String
im Flash liegt.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Vereinfachung_f.C3.BCr_Zeichenketten_.28Strings.29_im_Flash

>
> Und es gibt noch ein großen String für die variablen Ausgaben.
> char sendstring[40];
> Das ist aber schon die größte und die wird ja auch nicht beeinflusstr
> von dem Code in der Case-Schleife
>
> Ein paar von den varXX sind floats, aber das kann es ja auch nicht sein.
> ...


Dann könnten es auch noch sonstige Fehler im Programm sein, die
sich jetzt bemerkbar machen.

von Rolf Magnus (Gast)


Lesenswert?

> send_string_uart0("OK\n\r");
> aber das sollte doch im Flash stehen, oder?

Nein. Die Strings werden im RAM gespeichert. Das geht nicht anders, weil 
der Prozessor auf Assembler-Ebene unterschiedliche Instruktionen für den 
Zugriff auf RAM und Flash braucht. Du kannst sie mit PROGMEM als globale 
Variablen definieren und eine zweite Sendefunktion schreiben, um das zu 
umgehen.
Was sagt denn avr-size, wieviel RAM statisch belegt ist?

von gast (Gast)


Lesenswert?

Hallo  flyingwolf,
schau dir doch mal diesen thread an. (und besonders die Lösung am Ende)

Beitrag "Re: Speicherproblem"

Gruß

von flyingwolf (Gast)


Lesenswert?

Verstehe ich das richtig?

bei z.B.
if (irgendwas == 1) send_string_uart0("Press any key to continue");

landet der ganze Text automatisch im SRAM, auch wenn die If-Anweisung 
gar nicht ausgeführt wird?

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


Lesenswert?

Einzige Ausnahme: es ist schon zur Compilezeit bekannt, dass die
Anweisung nie ausgeführt werden kann.

von flyingwolf (Gast)


Lesenswert?

Umpf!
Nun, dann weiss ich zumindest wo mein Speicher hin ist.
Wenn ich das Tutorial jetzt richtig deute sollte ich gar nicht ganz 
unbedarft

send_string_uart0("Press any key to continue");

schreiben, sondern die Texte in hunderten von Variablen a la

char StringImFlash1[] PROGMEM = "Press any key to continue";

deklarieren und deren Ausgabe dann durch

send_string_uart0(StringImFlash1);
erzeugen?
Gibt es noch eine bestimmte Regel an die ich mich dabei halten muss, 
oder genügt es in der entsprechenden Funktion die Texte als Lokale 
Variable zu definieren und dann in der oben angezeigten Weise 
auszugeben?

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


Lesenswert?

Mit bisschen Geschick kannst du das einfacher schreiben als:
1
send_string_uart0_P(PSTR("Press any key to continue"));

Der _P-Suffix ist dabei die übliche Kennzeichnung dafür, dass die
Funktion einen progmem-String bekommt.  Die Funktion ist dabei
selbst dafür verantwortlich, die Daten aus dem Progmem zu ziehen
(letztlich mittels LPM-Anweisungen).

Der PSTR-Makro ist ein wenig Magie, die den String in den Progmem
legt und dessen Adresse gleich zurückgibt.  Der String selbst bildet
dabei sowas wie eine anonyme Array-Variable.

von Werner B. (Gast)


Lesenswert?

Aber bevor wir dich noch mehr verwirren arbeite zuerst mal das Oben von 
Karl heinz Buchegger angeführte Tutorial durch. Dann wird dir 
hoffentlich vieles klarer.

von flyingwolf (Gast)


Lesenswert?

Erst einaml vielen Dank. Da habe ich doch wieder viel dazugelernt. Das 
werden wir jetzt mal vertiefen. Zuerst in der einfachen Methode zu Fuss 
und dann schauen wir mal ob wir das mit der Magie hinbekommen, aber das 
wird wohl ein paar Tage brauchen

von Karl H. (kbuchegg)


Lesenswert?

flyingwolf wrote:
> Erst einaml vielen Dank. Da habe ich doch wieder viel dazugelernt. Das
> werden wir jetzt mal vertiefen. Zuerst in der einfachen Methode zu Fuss
> und dann schauen wir mal ob wir das mit der Magie hinbekommen, aber das
> wird wohl ein paar Tage brauchen


Das schöne ist, dass du die Magie nicht erfinden
musst.

Du musst nur eine klitzekleine Änderung in deiner send.....
Funktion machen, vor den String ein PSTR und schon bist
du fertig.


Noch ein Hinweis:
Lass um Gottes Willen deine jetzige send.... Funktion in Ruhe
und mache eine zweite Funktion, die du laut üblicher Konvention

send_string_uart0_p

nennst.

von flyingwolf (Gast)


Lesenswert?

<< Du musst nur eine klitzekleine Änderung in deiner send.....
Funktion machen, vor den String ein PSTR und schon bist
du fertig.>>

Ja schon, aber woher hätte ich vorhin ahnen können, dass es so einfach 
ist ;-)
Ich habe mich ja auch gleich noch an meiner LCD-Routine vergangen um 
selbigem Problem gleich mit auf den Leib zu rücken. Dabei habe ich mich 
dieser wunderbaren Vorlage aus dem Tutorial bedient, auch wenn es einen 
Moment gedauert hat, bis der Groschen fiel. Nun die Frage

Natürlich hat der Compiler (und nicht ganz zu unrecht) was zu meckern an 
der Zeile
while (charcounter = pgm_read_byte(data))
und bemerkt in seiner nüchternen Art
uart.c:87: warning: suggest parentheses around assignment used as truth 
value
Kann ich dem Compiler irgendwie sagen, das er an dieser Stelle mal 
ausnahmsweise nicht meckern soll?

von Karl H. (kbuchegg)


Lesenswert?

> while (charcounter = pgm_read_byte(data))
> suggest parentheses around assignment used as truth value


erfüll doch dem Compiler seinen Wunsch :-)

Worum gehts denn da.
Ein, besonders bei Einstegern, beliebter Fehler ist es,
wenn man für einen Vergleich anstelle von == ein einfaches
= schreibt.

Da wird dann ein simples

  sscanf( "%d", &Count );

  if( Count = 1 )
    printf( "Sie haben 1 eingegeben" );

schnell zum Drama.

Daher warnen manche Compiler vor solchen Konstrukten.
Wohlwissend, dass es in C durchaus Konstruktionen gibt,
bei denen man tatsächliche eine Zuweisung haben möchte.

Man kann natürlich auch die Langform schreiben:

   while( ( charcounter = pgm_read_byte(data) ) != 0 ) {
     ...
   }

und hat damit auch für den neuesten Einsteiger klar gemacht was
Sache ist (oder auch nicht). Aber gcc gibt sich schon zufrieden
wenn du einfach nur die Klammern machst, der explizite Vergleich
muss nicht sein:

   while( ( charcounter = pgm_read_byte(data) ) ) {

dann glaubt dir der gcc, dass du wirklich einen Zuweisung
meinst.



von flyingwolf (Gast)


Lesenswert?

Super.
Und damit ein erneutes, ganz herzliches Danke (an alle) und eine gute 
Nacht. ;-)

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.