Forum: Mikrocontroller und Digitale Elektronik Unbenutzte Funktionen beim kompilieren


von gfl (Gast)


Lesenswert?

Hallo
Ich benutzte avr-g++ und avr-gcc zum Kompilieren und avrdude um 
Programme auf einen atmega328p zu übertragen (auf einem Arduino Uno, 
aber ohne Arduino-IDE).
1. Ich laß, dass beim Kompilieren alle Funktionen die in einer 
Headerdatei deklariert werden (und damit ihre Definitionen in der 
entsprechenden *.c bzw. *.cpp Datei) mit in die Zieldatei eingebunden 
werden, auch wenn im Programcode diese Funktionen garnicht benutzt 
werden. Stimmt das? Währe es also besser, um den Progarmcode zu 
minimieren, diese Funktionen raus zu schmeissen?
2. Weiter las ich, dass wenn man Klassen benutzt, der Compiler wirklich 
nur die Dinge einbindet, die wirklich gebraucht werden und unbenutztes 
außen vor lässt. Stimmt das? Wäre es also besser, alles mit Klassen zu 
machen um Programmcode zu minimieren?
3. Wie sieht es dann mit struct aus, die ich wie eine Klasse benutzen 
würde, um in C bleiben zu können?
4. Ich las, dass man Makros nicht für Konstanten benutzen soll, weil der 
Compiler dafür keine Fehlererkennung anwenden kann: Also lieber
const uint8_t meine_Konstante = 1;
statt
#define meine_Konstante 1
Stimmt das?
5. Wie sieht es dann mit der Speicherbelegung aus? Das Makro verbraucht 
ja keinen Speicherplatz. Verbraucht eine Konstante Speicherplatz?

Ich weiß, dass es hier nicht um kByte geht, aber mich interessiert das 
jetzt mal, weil ich an einem Punkt in meinem Hobby AVR-Programmierung 
stehe, wo ich die Arduino-IDE-Welt verlasse und ich denke, dass auch die 
Antworten auf solche Fragen mich weiter bringen.

BTW: Ich habe einige Zeit mit dem QT-Creator Desktop-GUIs gebastelt und 
habe mich immer gewundert, warum aus 10k Quellcode ein 10MB großes 
Programm wird. Hat das mit den Antworten auf Frage 1 und 2 zu tun?

Danke für die Antworten im Voraus.
gfl

von Programmierer (Gast)


Lesenswert?

gfl schrieb:
> Hallo
> Ich benutzte avr-g++ und avr-gcc zum Kompilieren und avrdude um
> Programme auf einen atmega328p zu übertragen (auf einem Arduino Uno,
> aber ohne Arduino-IDE).
> 1. Ich laß, dass beim Kompilieren alle Funktionen die in einer
> Headerdatei deklariert werden (und damit ihre Definitionen in der
> entsprechenden *.c bzw. *.cpp Datei) mit in die Zieldatei eingebunden
> werden, auch wenn im Programcode diese Funktionen garnicht benutzt
> werden. Stimmt das?

Jein. Zunächst werden alle Funktionen, die definiert wurden, 
eingebunden. Es spielt keine Rolle, ob sie in einer Header-oder 
Source-Datei eingebunden wurden (der Compiler kennt den Unterschied 
sowieso nicht), und auch nicht ob sie zuvor deklariert wurden oder 
nicht.

>  Währe es also besser, um den Progarmcode zu
> minimieren, diese Funktionen raus zu schmeissen?

Nein. Der GCC+LD kann nicht benutzte Funktionen (und globale Variablen) 
automatisch entfernen. Dazu übergibst du beim Kompilieren 
"-ffunction-sections -fdata-sections" und beim Linken 
"-Wl,--gc-sections".

> 2. Weiter las ich, dass wenn man Klassen benutzt, der Compiler wirklich
> nur die Dinge einbindet, die wirklich gebraucht werden und unbenutztes
> außen vor lässt. Stimmt das?

Memberfunktionen werden hier genau so behandelt wie freie Funktionen, 
also siehe 1. Membervariablen werden immer mit eingebunden, d.h. 
verbrauchen Speicherplatz.

>Wäre es also besser, alles mit Klassen zu
> machen um Programmcode zu minimieren?

Es ist sinnvoll, viel mit Klassen zu arbeiten, um die Programmstruktur 
und Wiederverwendbarkeit zu verbessern. Mit Performance hat das nichts 
zu tun.

> 3. Wie sieht es dann mit struct aus, die ich wie eine Klasse benutzen
> würde, um in C bleiben zu können?

Gilt genau wie Klassen in C++, Elemente eines C-Struct werden nicht 
entfernt falls nie benutzt.

> 4. Ich las, dass man Makros nicht für Konstanten benutzen soll, weil der
> Compiler dafür keine Fehlererkennung anwenden kann: Also lieber
> const uint8_t meine_Konstante = 1;
> statt
> #define meine_Konstante 1
> Stimmt das?

Ja. Besser allerdings mit "static const", und in C++ mit "static 
constexpr".

> 5. Wie sieht es dann mit der Speicherbelegung aus? Das Makro verbraucht
> ja keinen Speicherplatz. Verbraucht eine Konstante Speicherplatz?

Prinzipiell schon, der Compiler kann sie aber in Instruktionen hinein 
optimieren. Ein Makro an sich verbraucht keinen Platz, aber bei der 
Verwendung muss der Compiler die Daten natürlich irgendwie ablegen - das 
Ergebnis ist meist gleich zum "static const".

> BTW: Ich habe einige Zeit mit dem QT-Creator Desktop-GUIs gebastelt und
> habe mich immer gewundert, warum aus 10k Quellcode ein 10MB großes
> Programm wird. Hat das mit den Antworten auf Frage 1 und 2 zu tun?

Hast du statisch gelinkt? Qt ist ein riesiges Framework, das verbraucht 
nunmal viel Speicher.

von Programmierer (Gast)


Lesenswert?

Programmierer schrieb:
> Dazu übergibst du beim Kompilieren
> "-ffunction-sections -fdata-sections" und beim Linken
> "-Wl,--gc-sections".

PS: Die Arduino-IDE und auch andere IDEs machen das schon automatisch, 
d.h. dort werden nicht benötigte Funktionen, funktionslokale statische 
Variablen sowie globale Variablen bereits entfernt.

von Fragender (Gast)


Lesenswert?

> Ja. Besser allerdings mit "static const", und in C++ mit "static
> constexpr".

Warum static?

von EAF (Gast)


Lesenswert?

Programmierer schrieb:
> PS: Die Arduino-IDE und auch andere IDEs machen das schon automatisch,
> d.h. dort werden nicht benötigte Funktionen, funktionslokale statische
> Variablen sowie globale Variablen bereits entfernt.

Das geht manchmal auch zu weit!
Drum kennt GCC das  __attribute__((used))

von Programmierer (Gast)


Lesenswert?

Fragender schrieb:
> Warum static?

Weil der Compiler dann weiß, dass die Variable nicht in anderen 
Translation Units benutzt wird, und kann sie somit wegoptimieren falls 
möglich, und z.B. als Immediate-Wert in eine Instruktion kodieren. Das 
geht natürlich nicht wenn man einen Zeiger auf die Variable bildet (und 
später erst dereferenziert). Zeiger auf Macros kann man natürlich nicht 
bilden, auf Variablen/Konstanten aber schon - somit ermöglichen diese 
eine größere Flexibilität, während der Compiler automatisch dafür sorgt 
dass nur dann Speicher verbraucht wird, wenn es nicht anders geht.

EAF schrieb:
> Das geht manchmal auch zu weit!

Aber nicht in "normalem" Code, nur ISR-Vektoren/Startup-Code o.ä.

von Fragender (Gast)


Lesenswert?

static macht nicht unsichtbar wenn in einer .h verwendet? Hab das in 
main.cpp probiert, man kann die Variable dennoch verwenden. Sollte man 
nicht lieber namespace nehmen?

von Programmierer (Gast)


Lesenswert?

Fragender schrieb:
> static macht nicht unsichtbar wenn in einer .h verwendet?

Doch. Wie gesagt, der Compiler kennt den Unterschied zwischen .c / .cpp 
und .h nicht. Bei der Verwendung von "#include" wird einfach nur der 
Inhalt der inkludierten Datei dorthin "kopiert". Der Compiler sieht nur 
einen einzelnen langen Text.

Fragender schrieb:
> Hab das in
> main.cpp probiert, man kann die Variable dennoch verwenden.

Wenn mehrere .c / .cpp Dateien die gleiche .h-Datei inkludieren, welche 
eine "static const"-Variable enthält, bekommst du letztendlich mehrere 
Variablen gleichen Namens. Der Compiler weiß gar nicht dass diese 
mehreren Variablen auf die selbe Codestelle zurückgehen, weil er beim 
Compilieren der einzelnen .c-Dateien gar nicht weiß dass eine 
Header-Datei im Spiel ist. Würdest du in diesem Fall das "static" 
weglassen, würdest du "Multiple-Definition"-Errors vom Linker bekommen. 
Eine "static const"-Variable in einem Header zu definieren ist aber gar 
nicht so verkehrt, so kannst du sie in jeder Source-Datei nutzen und der 
Compiler optimiert sie jedes Mal weg. Nur bei größeren Datentypen sollte 
man das nicht so machen.

von EAF (Gast)


Lesenswert?

Fragender schrieb:
> static macht nicht unsichtbar wenn in einer .h verwendet? Hab das in
> main.cpp probiert, man kann die Variable dennoch verwenden.

Ein als static definierte Variable ist nur für die Übersetzungseinheiten 
sichtbar, in welcher sie definiert ist.

Der Hauptzweck:
Weniger Namenskollisionen.
z.B. darf jede Einheit eine eigene mit "count" benannte Variable 
besitzen.
Und ja, mit anonymen Namensräumen kann man ähnliches erreichen.

von Fragender (Gast)


Lesenswert?

Kann nicht sein. Eine Variable in einer .cpp ist in main.cpp nicht 
sichtbar und auch nicht benutzbar. Die ist immer lokal und der 
Übersetzungseinheit vorenthalten.

staticTest.h
1
#pragma once
2
#include <avr/io.h>
3
#include <stddef.h>

staticTest.cpp
1
#include "staticTest.h"
2
static constexpr uint8_t var {100};   // woanders nicht benutzbar

Nächster Test ohne .cpp

staticTest.h
1
#pragma once
2
#include <avr/io.h>
3
#include <stddef.h>
4
constexpr uint8_t var {100};  // überall benutzbar

staticTest2.h
1
#pragma once
2
#include <avr/io.h>
3
#include <stddef.h>
4
constexpr uint8_t var {100};  // überall benutzbar

Ich kann in main.cpp die Variable 'var' verwenden egal ob static oder 
nicht static. Warnungen oder Fehler gibts auch keine. Diese Variable 
'var' wird  in einem delay zum Led blinken verwendet. Kann also nicht 
unbenutzt wegoptimiert werden. Irgendwie stimmt nicht. Hier müßte eine 
Fehlermeldung kommen.

von Programmierer (Gast)


Lesenswert?

Fragender schrieb:
> Ich kann in main.cpp die Variable 'var' verwenden egal ob static oder
> nicht static. Warnungen oder Fehler gibts auch keine.

Zeig mal alle dabei involvierten Header und Source-Dateien. Aus deinem 
Beitrag ist die genaue Konstellation nicht ersichtlich.

von EAF (Gast)


Lesenswert?

Fragender schrieb:
> constexpr uint8_t var {100};  // überall benutzbar

Verwendet der Compiler wie Literale!
Ähnlich:
#define var 100

von Programmierer (Gast)


Lesenswert?

EAF schrieb:
> Ähnlich:

Überhaupt nicht ähnlich, "#define" ist eine stupide Textersetzung, 
"constexpr" definiert eine C++-Variable mit Scope, Typ, ggf. Speicher 
und eben Compiletime-Evaluation.

von EAF (Gast)


Lesenswert?

Programmierer schrieb:
> Überhaupt nicht ähnlich,

Doch ähnlich!
So ähnlich, dass im Kompilat kein Unterschied zu erkennen ist.

von Fragender (Gast)


Lesenswert?

Zwischen define und constexpr liegen Welten auf dem Weg zum Kompiltät.
Ich bereite es für euch auf ...

von Programmierer (Gast)


Lesenswert?

EAF schrieb:
> So ähnlich, dass im Kompilat kein Unterschied zu erkennen ist.

Keinswegs:
1
#define var 100
2
3
void foo (int) {
4
    std::cout << "int" << std::endl;
5
}
6
7
void foo (uint8_t) {
8
    std::cout << "uint8_t" << std::endl;
9
}
10
11
int main () {
12
    foo (var);
13
}

Gibt "int" aus, aber mit
1
constexpr uint8_t var {100};

ist die Ausgabe "uint8_t".

von EAF (Gast)


Lesenswert?

Programmierer schrieb:
> Keinswegs:

Hier dreht es sich um Optimierung und Namensräume.
Und nicht darum, dass int der default Type für numerische Literale ist.

1
// C++ #define var byte(100)
2
#define var ((uint8_t)100)
3
void foo (int) {
4
    std::cout << "int" << std::endl;
5
}
6
void foo (uint8_t) {
7
    std::cout << "uint8_t" << std::endl;
8
}
9
int main () {
10
    foo (var);
11
}

von Fragender (Gast)


Lesenswert?

staticTest.h
1
#pragma once
2
#include <avr/io.h>
3
#include <stddef.h>
4
       constexpr uint16_t var {100};
5
static constexpr uint16_t varStatic {100};

staticTest2.h
1
#pragma once
2
#include <avr/io.h>
3
#include <stddef.h>
4
       constexpr uint16_t var {100};
5
static constexpr uint16_t varStatic {100};

main.cpp
1
#define F_CPU 16000000UL
2
3
#include <avr/io.h>
4
#include <util/delay.h> 
5
#include <stddef.h>
6
#include "staticTest.h"
7
#include "staticTest2.h"
8
9
int main(void)
10
{  
11
  DDRB = _BV(0);
12
    
13
    while (1) 
14
    {
15
    PORTB |= _BV(0); 
16
    _delay_ms(50+var+varStatic);
17
    PORTB &= ~_BV(0); 
18
    _delay_ms(500+var+varStatic);
19
    }
20
}

Fehlermeldungen soweit okay, hatte vorhin was falsch gemacht
1
'constexpr const uint16_t var' previously defined here        > staticTest.h
2
3
'constexpr const uint16_t varStatic' previously defined here  > staticTest.h
4
5
redefinition of 'constexpr const uint16_t var'       > staticTest2.h
6
7
redefinition of 'constexpr const uint16_t varStatic' > staticTest2.h

Ändere ich die Variablennamen in staticTest2.h
1
#pragma once
2
3
#include <avr/io.h>
4
#include <stddef.h>
5
6
       constexpr uint16_t var2 {100};
7
static constexpr uint16_t varStatic2 {100};
und entsprechend in main.cpp
1
int main(void)
2
{  
3
   DDRB = _BV(0);
4
    
5
    while (1) 
6
    {
7
    PORTB |= _BV(0); 
8
    _delay_ms(50+var+varStatic);
9
    PORTB &= ~_BV(0); 
10
    _delay_ms(500+var2+varStatic2);
11
    }
12
}

Gibt keine Fehler. Hier hatte ich gedacht das static lokal macht. Klarer 
Denkfehler wegen Headerfile Inkludierung. Hattest du schon erklärt. Ich 
halte das static für verwirrend je nachdem wo es steht, dann lieber 
gleich namespace zum kapseln.

Nochmal zum mitmeißeln. Wenn 'static const' in einem Headerfile vewendet 
wird und diese Variable nirgends woanders verwendet wird, dann kann der 
Compiler das optimieren? Aber wehe man verwendet sie irgendwo?
Nur benötigt man dazu eigentlich nicht noch export?

von gfl (Gast)


Lesenswert?

Vielen Dank für die erhellenden Antworten. Ich muss allerdings noch mal 
darüber nachdenken, bis ich alles verstanden habe. Jedoch:
Programmierer schrieb:
> Eine "static const"-Variable in einem Header zu definieren ist aber gar
> nicht so verkehrt, so kannst du sie in jeder Source-Datei nutzen und der
> Compiler optimiert sie jedes Mal weg.
????
Der Compiler optimiert sie weg, wenn sie nicht benutzt wird? Oder? Das 
habe ich jetzt nicht verstanden.

von EAF (Gast)


Lesenswert?

gfl schrieb:
> Der Compiler optimiert sie weg, wenn sie nicht benutzt wird? Oder? Das
> habe ich jetzt nicht verstanden.

Er setzt sie so ein, wie er ein Literal einsetzen würde.
Der Wert taucht dann schon noch im Programmcode auf. Aber nicht im RAM 
als (readonly) Variable.

Es sei denn, man richtet einen Zeiger darauf, oder fordert sonstwie die 
Adresse der Variablen an.

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.