Forum: Compiler & IDEs Problem durch Optimierung -Os


von Torsten W. (torsten_w)


Angehängte Dateien:

Lesenswert?

Hi,

ich arbeite gerade an einem CAN Bootloader, der auf einem atmega32 
laufen soll, welcher dazu einen MCP2515 via SPI verwendet, um mit dem 
Bus zu kommunizieren.
Um die zum Flashen erforderlichen Daten auf den Bus zu bekommen verwende 
ich einen atmega168, der ebenfalls einen MCP2515 via SPI verwendet, 
mittels UART die Daten von meinem Computer gesendet bekommt und somit 
sozusagen ein Gateway darstellt.
Momentan schreibe ich den "Bootloader" noch in den ganz normalen 
Programmspeicher, da ich mich bis jetzt primär um das implementieren der 
Datenübertragung gekümmert habe. Da die Datenübertragung nun aber 
fehlerfrei funktioniert wollte ich mich an die eigentlichen Funktionen 
eines Bootloaders machen, nämlich dem schreiben in den Speicher.

Da ich bis jetzt immer mit -O0 die Optimierung deaktiviert habe, hat 
sich mein Code auf 7kbyte aufgebläht. Um die verfügbaren 4kbyte Boot 
Section des atmega32 nicht zu überschreiten möchte ich den Schalter -Os 
verwenden um mit Fokus auf Speicherplatz zu optimieren.

Durch diese Änderung entstehen jedoch einige Probleme, die ich als 
zugegebener Amateur im Bereich Mikrocontroller und C nicht ganz 
nachvollziehen kann. Mir ist bekannt, dass durch die Optimierung in 
vielen Fällen Dinge übergangen werden, weil der Compiler denkt, dass 
gewisse Sachen sich nicht ändern können usw. Bis jetzt bin ich aber 
davon ausgegangen, dass sich dies nur auf globale Variablen in 
Verbindung mit Interrupts beschränkt. Im Anhang hab ich mal den gesamten 
Code angehangen, durch ausprobieren konnte ich aber schon feststellen, 
dass es in folgenden Funktionen / Prozeduren hapert.
Ich kann mir aber leider nicht erklären, wo genau die Fehler sind und 
hoffe, dass ihr mir dabei helfen könnt.

Folgende Prozedur verwende ich im Bootloader um während des Empfangens 
der Daten, Daten an das UART-CAN Gateway zurück zu senden, die dann auf 
der UART Schnittstelle ausgegeben werden. In erster Linie dient es in 
der Testphase zum überprüfen der Daten. Im Moment verwende ich es jedoch 
um einfach die Funktionalität der Kommunikation über CAN zu testen.
Leider funktioniert es im Moment nicht richtig.
In meinem Terminal werden korrekterweise die ersten 7 Zeichen aus 
'input' ausgegeben, dann bleibt die Prozedur jedoch in der 
'while(escape_flag == 0)' schleife stecken.
Zum Verständnis muss ich noch sagen, dass ich mir damit zuerst das 
erfolgreiche empfangen und Verarbeiten der Daten vom UART-Gateway 
quittieren lasse um einen Buffer-Overflow im CAN Controller des UART-CAN 
Gateways zu verhindern, woraufhin weiter gesendet wird.
Dies geschieht durch das Empfangen einer CAN-Botschaft (das empfangen 
einer Nachricht signalisiert der CAN-Controller über das auf LOW ziehen 
von 'PD7') mit der ID = 1 und dem Wert 
'BOOTLOADER_MESSAGE_CLEAR_TO_SEND' im ersten Datenfeld der 
CAN-Botschaft.
1
void write_bootloader_message_buffered_to_can(uint16_t length, char *input){
2
  static uint16_t offset = 0;
3
  uint8_t loops = (length / 7)+1;
4
5
  for(uint8_t i=0;i<loops;i++){
6
    uint8_t len = 7;
7
    if(i+1 == loops){
8
      len = length % 7;
9
    }
10
    
11
    char *data = malloc(sizeof(char)*(len+1));
12
    memcpy(data+1,input+offset, (size_t)len);
13
    data[0] = BOOTLOADER_MESSAGE;
14
    offset += len;
15
    
16
    write_message_to_can(DEVICE_ID,len+1,data);
17
    free(data);
18
19
    uint8_t escape_flag = 0;
20
    while(escape_flag == 0){
21
      
22
      if((PIND & (1<<PD7)) == 0){
23
        Message *message;
24
        handle_interrupt(message);
25
        //escape_flag = 1;
26
        if((message->id == 1) && (message->data[0] == BOOTLOADER_MESSAGE_CLEAR_TO_SEND)){
27
          escape_flag = 1;
28
        }
29
      }
30
    }
31
  }
32
  offset = 0;
33
}
(File: can_interface.c Line:63)

Genau hier tritt jedoch das Problem auf. Die ID wird fälschlicherweise 
mit einem wert von 30-40 angegeben (kann es leider nicht genau sagen, da 
ich es mir durch eine blinkende LED ausgeben lassen musste), den Inhalt 
von 'data[0]' habe ich mir gar nicht erst anzeigen lassen.
Ich vermute, dass in der 'handle_interrupt(message);' welche die Daten 
per SPI aus dem MCP2515 ausließt ein Konflikt mit der Optimierung 
auftaucht, da das Programm bis zu diesem Punkt problemlos arbeitet.

Die 'handle_interrupt(message);' sieht wie folgt aus und verwendet 
einige weitere Funktionen / Prozeduren aus 'can2515.c':
1
uint8_t handle_interrupt(Message *message)
2
{
3
    uint8_t status = read_rx_status();
4
5
  set_cs(LOW);
6
7
  if(status & (1<<3)){
8
    message->rtr = 1;
9
  }
10
    if (status & (1 << 6)) {
11
    spi_putc(SPI_READ_RX);
12
    get_message_from_buffer(0, message);
13
    set_cs(HIGH);
14
    return MESSAGE_IN_BUFFER_0;
15
    } else if (status & (1 << 7)) {
16
    spi_putc(SPI_READ_RX | 0x04);
17
    get_message_from_buffer(1, message);
18
    set_cs(HIGH);
19
    return MESSAGE_IN_BUFFER_1;
20
    }else{
21
    set_cs(HIGH);
22
    return NO_MESSAGE;
23
  }
24
  
25
}
(File: can2515.c Line: 114)


Da ich 'can2515.c' für Testzwecke auch in anderen Programmen verwendet 
habe und z.B. auch Grundstein des CAN-UART Gateway Programms ist, 
befinden sich die Dateien 'can2515.c/h', 'spi.c/h' und 'regs.h' nicht 
direkt in diesem Projekt sondern werden gesondert als Library gebaut und 
dann eingebunden. Ich hab auch das Makefile dafür in den Anhang gepackt.

Ich arbeite auf einem Linux System und und verwende avr-gcc 4.3.5.

Ich hoffe ich habe bei dem vielen Text nichts wichtiges vergessen und 
bedanke mich jetzt schon mal fürs lesen.
Ich poste natürlich gerne weitere wissenswerte Informationen.


Gruß,
toti

von Stefan E. (sternst)


Lesenswert?

1
        Message *message;
2
        handle_interrupt(message);
Du hast gar keinen Speicher für deine Message reserviert. message ist 
ein Pointer, der einfach irgendwo in den Speicher zeigt. Wenn das mit 
-O0 funktioniert hat, war das purer Zufall.

Richtig wäre z.B.:
1
        Message message;
2
        handle_interrupt(&message);

von (prx) A. K. (prx)


Lesenswert?

Sowas sollte der Compiler eigentlich anzeigen. Warnungen ignoriert?

von Stefan E. (sternst)


Lesenswert?

Noch eine kleine Extrabemerkung:
Wozu das umständliche malloc/free für data? Warum nicht einfach 
data[8]? Ist doch egal, wenn im letzten Schleifendurchlauf nicht alle 8 
gebraucht werden.

von Torsten W. (torsten_w)


Lesenswert?

Hi,

so viel zum Thema Amateur in C ;) Vielen Dank, das wars wirklich. Ich 
weiß nicht wirklich was mich da geritten hat. Ich glaube ich bin 
dummerweise davon ausgegangen, dass der Compiler bei einem 'struct' 
anders handelt und den Speicher reserviert. Dummer Gedanke, der dann 
durch den Erfolg beim Verzicht auf Optimierung auch noch gefördert 
wurde.
Bezüglich Warnungen war dar Hinweis darauf, dass ohne Optimierung die 
Funktionen aus dem Header 'delay.h' nicht funktionieren würden das 
einzige. Beim Compilen mit -Os lief der Compiler ohne eine einzige 
Warnung durch. An der Stelle würde mich jetzt auch interessieren, hätte 
der Compiler ein 'Warning' ausgeben müssen?
Was mich auch noch interessieren würde ist, ob es wirklich reiner Zufall 
war, dass es ohne Optimierung funktioniert hat oder ob es daran liegt, 
dass der Compiler um Rechenzeit während des Compilens zu sparen einfach 
pauschal immer Speicher mit anlegt? Würde ja eigentlich auch keinen Sinn 
machen oder?

EDIT: Bezüglich des mallocs/free stimme ich völlig zu. Ich hatte vor an 
möglichst allen Stellen darauf zu verzichten um mir den Speicherplatz 
von malloc() ganz zu sparen. Der soll ja auch nicht wenig sein.

Nochmal vielen Dank,
toti

von Klaus W. (mfgkw)


Lesenswert?

Torsten W. schrieb:
> Beim Compilen mit -Os lief der Compiler ohne eine einzige
> Warnung durch. An der Stelle würde mich jetzt auch interessieren, hätte
> der Compiler ein 'Warning' ausgeben müssen?

Kompilierst du mit -Wall -Wextra (falls gcc, sonst halt entsprechende 
Optionen bei deinem Compiler)?

von Torsten W. (torsten_w)


Lesenswert?

Mit -Wall wird es mir jetzt angezeigt. Sogar noch drei weitere Warnings. 
Danke

von Stefan E. (sternst)


Lesenswert?

Torsten W. schrieb:
> Was mich auch noch interessieren würde ist, ob es wirklich reiner Zufall
> war, dass es ohne Optimierung funktioniert hat

Ja, war es. Wahrscheinlich hat dort der Pointer einfach nur in einen 
ansonsten unbenutzten Bereich des RAM gezeigt.

von Klaus W. (mfgkw)


Lesenswert?

Torsten W. schrieb:
> Mit -Wall wird es mir jetzt angezeigt. Sogar noch drei weitere Warnings.

Daraus kann man messerscharf schließen, daß es generell eine gute Idee
ist, mit -Wall und am besten auch gleich mit -Wextra zu kompilieren,
und sich um die Warnungen auch zu kümmern bzw. sie mit Sinn und
Verstand zu beseitigen.

Z.B. wird bei deinem Programm (-teil) nicht so recht klar, wieso
du für normale Strings unsigned char[] nimmst.
De Verwendung von signed/unsigned-Varianten für ganze Zahlen ist
ebenfalls immer eine Überlegung wert.
Weiterhin übergibst du einen nichtkonstanten Zeiger für den String,
was sich mir nicht erschließt.
Gleich vorweg: "funktioniert doch so" ist die dämlichste Ausrede :-)

von Torsten W. (torsten_w)


Lesenswert?

Hi,

auf "funktioniert doch" wollte ich mich heute mal nicht rausreden ;) . 
Mein Problem bezüglich der Datentypen ist, dass ich programmiertechnisch 
eher aus dem Bereich Java komme, wo man sich über solche Sachen ja eher 
weniger Gedanken macht und einem als Programmierer auch eine ganze Menge 
Arbeit abgenommen wird.
Aus diesem Grund tue ich mich mit der Menge an Datentypen (im Vergleich 
zu Java), die einem bei c zur Verfügung stehen noch etwas schwer und 
muss noch reichlich Erfahrung sammeln was das angeht.
Die Erfahrung, dass man mit 'Warnings' nicht scherzen sollte habe ich im 
Vorfeld bereits gemacht und angefangen diese sehr ernst zu nehmen. Das 
Vergessen von -Wall war dabei eher ein Versehen aus Mangel an Erfahrung, 
da dies auch mein erstes aufwändigeres Projekt in C ist.

Was genau meinst du im Bezug auf die Verwendung von 'unsigned char[]' 
und in welchem Teil genau?

Im Hinblick auf das Übergeben des Pointers für den String hätte ich doch 
bei einer Deklaration des Parameters als 'const' den Vorteil, dass ich 
wegen der Read-Only Übergabe nicht aus Versehen in den Daten 
rumschreiben könnte oder? Gibt es noch andere Aspekte in aus deren Sich 
das sinnvoll ist? In diesem Punkt muss ich aber leider zugeben, dass ich 
da bis jetzt mit der Motivation "funktioniert doch so" gehandelt habe ;)

Gruß,
toti

von Klaus W. (mfgkw)


Lesenswert?

Torsten W. schrieb:
> Was genau meinst du im Bezug auf die Verwendung von 'unsigned char[]'
> und in welchem Teil genau?

Sorry, das hatte ich mit einem anderen Thread verwechselt :-(

Der Sinn meiner Aussage bleibt aber natürlich ...

Torsten W. schrieb:
> Im Hinblick auf das Übergeben des Pointers für den String hätte ich doch
> bei einer Deklaration des Parameters als 'const' den Vorteil, dass ich
> wegen der Read-Only Übergabe nicht aus Versehen in den Daten
> rumschreiben könnte oder? Gibt es noch andere Aspekte in aus deren Sich
> das sinnvoll ist?

Ein weiterer Vorteil ist, daß der Compielr von Fall zu Fall
deutlich besseren Code erzeugen kann.

Außerdem kann man eine Funktion, die selber durch const zusichert,
einen String nicht zu ändern, wiederum aus einer anderen aufrufen,
die selbiges zusichert.
Ein const-String beim Aufrufer dürfte nämlich strenggenommen nicht
als Parameter an eine Funktion übergeben werden, die den als
nicht-const bekommt.

Torsten W. schrieb:
> In diesem Punkt muss ich aber leider zugeben, dass ich
> da bis jetzt mit der Motivation "funktioniert doch so" gehandelt habe ;)

Wie gesagt, meinte ich dich eigentlich gar nicht, aber war trotzdem
ein Treffer :-)

von Stefan E. (sternst)


Lesenswert?

Klaus Wachtler schrieb:
> Torsten W. schrieb:
>> Im Hinblick auf das Übergeben des Pointers für den String hätte ich doch
>> bei einer Deklaration des Parameters als 'const' den Vorteil, dass ich
>> wegen der Read-Only Übergabe nicht aus Versehen in den Daten
>> rumschreiben könnte oder? Gibt es noch andere Aspekte in aus deren Sich
>> das sinnvoll ist?
>
> Ein weiterer Vorteil ist, daß der Compielr von Fall zu Fall
> deutlich besseren Code erzeugen kann.
>
> Außerdem kann man eine Funktion, die selber durch const zusichert,
> einen String nicht zu ändern, wiederum aus einer anderen aufrufen,
> die selbiges zusichert.
> Ein const-String beim Aufrufer dürfte nämlich strenggenommen nicht
> als Parameter an eine Funktion übergeben werden, die den als
> nicht-const bekommt.

Nur mal so nebenbei:
Das gilt natürlich nicht nur für Strings, sondern für jede Art von 
Pointer, egal worauf er zeigt.

von Torsten W. (torsten_w)


Lesenswert?

das hab ich mir schon gedacht und es gleich mal bei allen Funktionen die 
nur lesend auf die ihnen übergebenen Zeiger zugreifen angepasst.
Danke nochmal.

von Klaus W. (mfgkw)


Lesenswert?

Torsten W. schrieb:
> nur lesend auf die ihnen übergebenen Zeiger zugreifen

zur Sicherheit: ich rede von einem const für das, worauf
die Zeiger zeigen, nicht für die Zeiger:
1
void f( char *s /* Zeiger auf char */ )
2
{
3
    ++s;    // s darf verändert werden
4
    ++(*s); // *s auch
5
}
6
7
void fconst( const char *s /* Zeiger auf konstante char */ )
8
{
9
    ++s;       // s darf verändert werden
10
    // ++(*s); // FALSCH! *s ist konstant
11
}
12
13
...
14
   char tmp[10] = "Hallo"; // 10 änderbare char
15
   f( tmp );          // ok
16
   fconst( tmp );     // ok (Funktion dürfte Zeichen ändern,
17
                      // tut es aber nicht)
18
   f(( "Hallo" );     // FALSCH! "Hallo" ist eine Konstante,
19
                      // f() sichert aber nicht zu, den String
20
                      // nicht zu ändern!
21
   fconst( "Hallo" ); // ok

von Torsten W. (torsten_w)


Lesenswert?

Ja, so meinte ich das auch ;) Danke.

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.