mikrocontroller.net

Forum: Compiler & IDEs Fehler bei Variablendefinition im Header-File (AVR, GCC)


Autor: Karsten Donat (karstendonat)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

ich hab ein Problem mit der Variablendefinition im Headerfile.

Das Projekt ist eine digitale Uhr (eigentlich ein Teilprojekt). Die 
Uhrzeit wird in globalen Variablen in der Uhr.h gespeichert. Stellen 
kann man die Uhr über die RS232 (z.B. ATMEga IDE -> Debug -> RS232 -> 
Uhr stellen). Später soll das über eine DCF77 Platine von Reichelt und 
alternativ den HTML Header geschehen.

Das Problem ist, mal müssen die globalen Variablen in der Uhr.h mit 
"extern" und mal ohne definiert sein. Ändert man was am Quellcode (z.b. 
in der main.cpp) und kompiliert neu, kommt mal Fehler 1 und mal Fehler 
2. Manchmal gehts auch mehrmals nacheinander gut durch. Hängt wohl 
irgendwie an internen Verknüpfungen der obj Dateien. (ist auch so wenn 
man nichts an Code ändert der direkt mit der Uhr zu tun hat)

Fehler 1 (mit export):
.obj/main.o: In function `Update_Anzeige()':
C:\Developing\AVR\Test\Digital Uhr/main.cpp:78: undefined reference to 
`Update_Datum'
... (usw. auch für die andern Variablen)

Fehler 2 (ohne export):
.obj/Uhr.o: In function `__vector_11':
C:\Developing\AVR\Test\Digital Uhr/Uhr.cpp:11: multiple definition of 
`Update_Datum'
.obj/main.o:C:\Developing\AVR\Test\Digital Uhr/main.cpp:74: first 
defined here...

Zum testen des Problems gibts in der Uhr.h die Zeile
//#define mit_export

Das Display hängt etwas durcheinander an Port B und D an nicht 
aufeinanderfolgenden Pins. Die Pins sind aber im .h konfigurierbar. Im 
Sekundentakt blinkt noch eine LED. Controller ist ein ATMega 168.

WinAVR 25.05.2007
Windows Vista
ATMega IDE 2007 (Fehler tritt aber auch bei direktem Aufruf von make 
auf)

Ein Preview der IDE gibts unter http://www.KarstenDonat.de/AVR als 
Download.

Danke schonmal.

Ciao
Karsten
http://www.KarstenDonat.de/AVR

Autor: Florian Demski (code-wiz)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das Problem ist, dass wenn Du im Header-File direkt Variablen definierst 
(also ohne extern) und diese dann in verschiedenen Dateien einbindest, 
werden in jedem Modul die Variablen neu erstellt und er Linker hat dann 
Probleme, dass zusammenzuführen. --> Fehler 2

Die einzig sinnvolle Methode ist, die Variablen (alle) als extern in den 
Headern zu deklarieren und die eigentliche Definition in irgendeinem 
Modul zu machen.

Man unterscheidet Deklaration und Definition.

Deklaration (mit extern) sagt dem Compiler nur, dass irgendwo eine 
Variable mit dem angegebenen Datentypen und Namen existiert.

Definition (ohne extern) legt eine Variable mit diesem Namen im Speicher 
an.

Der Linker, der dann Deine Module zusammenführt, bekommt Probleme, wenn 
zwei globale Variablen mit gleichem Namen definiert wurden.

Das #include macht eigentlich nichts anderes, als den kompletten Inhalt 
der Header-Datei an der Stelle einzufügen, wo #include steht.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Eine einfache Möglichkeit solche Probleme zu vermeiden
ist folgende Vorgehensweise:

* Du legst globale Variablen grundätzlich in ein eigenes
  Header File aus.
* Du benutzt ein Makro EXTERN, welches im Headerfile definiert
  wird, wenn ein derartiges Makro noch nicht definiert wurde
* Du includierst dieses Headerfile überall, wo globale Variablen
  benutzt werden. Und zwar ohne dich um das Makro EXTERN zu
  kümmern, sprich das Headerfile definiert sich EXTERN selber
* In dem C File, in dem die main() ist, wird das Headerfile
  mit den globalen Variablen ebenfalls includiert. Allerdings
  wird hier eine Vorgabe fpr EXTERN gemacht
* Globale Variablen werden nicht im Headerfile initialisiert,
  sondern über Zuweisungen in main().

Konkret

globals.h
---------
#ifndef EXTERN
#define EXTERN extern
#endif

EXTERN int Global1;
EXTERN int Global2;
EXTERN char String1[80];

a.c
---
#include "globals.h"

void foo()
{
  Global1 = 5;
  Global2 = 7;
}

main.c
------
#define EXTERN
#include "globals.h"

int main()
{
  Global1 = 8;
}

Der Schluessel liegt beim Makro EXTERN. Es ermöglicht es, dass
dieselben Zeilen C Code im Headerfile sowohl zu einer Deklaration
als auch zu einer Definition werden können.

Gibt es vor dem inkludieren des Headerfiles kein Makro für EXTERN,
dann erzeugt sich das Headerfile selbst eines, indem sie EXTERN
zu 'extern' expandiert. Damit wird
EXTERN int Global1;
zu
extern int Global1;
und damit zu einer Deklaration. Deklarationen kannst du in einem
C-Programm beliebig viele haben, solange sie nur alle gleich
lauten. Eine Deklaration teilt dem Compiler nur mit, dass etwas
existiert und welchen Datentyp es hat.

In main.c ist die Situation aber etwas anders. Hier wird das
Makro EXTERN mit einem Leerstring vorbelegt. Das heist aber auch,
dass im Headerfile kein EXTERN Makri mehr erzeugt wird und die
Zeile
EXTERN int Global1;
expandiert in main.c (und nur dort) zu
int Global1;
also einer Definition. Eine Definition teilt dem Compiler ebenfalls
mit, dass etwas existiert und den Datentyp dazu. Gleichzeitig ist
eine Definition aber auch die Aufforderung an den Compiler,
Speicherplatz dafür zu Reservieren.

Deklaration  =   Das Teil existier irgendwo, heist aber so und so
                 und ist vom Datentyp so und so
Definition   =   Hier existiert das Teil tatsächlich.

Eine Deklaration ist also nur ein Verweis darauf, dass die Variable
irgendwo existiert, während eine Definition aussagt: und hier
ist jetzt das bewusste Teil, auf welches alle Deklarationen
verweisen.
Folgerichtig kannst du beliebig viele Deklarationen (also die
Dinger mit extern) haben aber immer nur 1 Definition.
Obiges Schema garantiert dir das.

Ach ja: Wird im globals.h irgendwat geändert, muss main.c
auf jeden Fall immer mitcompiliert werden, denn dort
werden die globalen Variablen ja angelegt.

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich finde die Lösung mit den EXTERN Macro ist nicht so empfehlenswert, 
weil man damit alle globalen Variablen in einem Header-File 
zusammenfassen muß und weil immer alles sichtbar ist.

Ich ziehe es normalerweise vor, globale Variablen im dem Module zu 
definieren in das sie logischerweise gehören,
z.B.

pwm.c   -> Funktionen und globale Variablen zur Puls-Weiten-Modulation.
pwm.h   -> deklariert Funktionen und globale Variablen in pwm.c

stepper.c  -> Funktionen und globale Variablen zu Schrittmotoren
stepper.h  -> deklariert Funktionen und globale Variablen in stepper.c

main.c     -> includiert pwm.h und stepper.h

Beim Kompilieren von pwm.c brauchen und sollen die restlichen globalen 
Variablen nicht sichtbar sein.
Größere Projekte mit C/C++ funktionieren sowieso nur auf diese Art, weil 
man sonst bei der kleinsten Änderung an den globalen Variablen immer 
alle Files neu kompilieren müsste.

Klaus

Autor: Karsten Donat (karstendonat)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank Euch dreien für die schnellen Antworten.

Ich hab jetzt extern im .h und im zugehörigen .cpp die Eigentliche 
Definition. Jetzt gehts.

Wohl mal wieder so n typischer Anfängerfehler ;-)

Mit dem Global ist für mich denk ich ungünstig da ich möglichst alles zu 
einem Modul in den entsprechenden .h und .cpp Dateien haben will.

Unsicher bin ich mir noch mit Konfigurationseinstellungen. Die hab ich 
im Moment weitestgehend in die config.h geschubst. Eine andere Idee wäre 
die main.h.

Ciao

Karsten

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn Variablen nur innerhalb eines Modules genutzt werden, dann gehört 
deren Definition in die *.c/*.cpp-Datei und gar nichts in die 
Headerdatei.

Auch empfiehlt es sich, in solchen Fällen die Variablen als "static" zu 
deklarieren, damit die Variablen für den Linker nicht sichtbar sind.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Falser wrote:
> Ich finde die Lösung mit den EXTERN Macro ist nicht so empfehlenswert,
> weil man damit alle globalen Variablen in einem Header-File
> zusammenfassen muß und weil immer alles sichtbar ist.

Du redest von globalen Variablen an sich, bzw. dem vorgeschlagenen
Mechanismus: "Alle Globalen in ein Headerfile".
Das EXTERN Makro ist lediglich ein Mechanismus um globale Variablen
mit geringem Aufwand handhabbar zu kriegen. Ob die jetzt in einem
Headerfile oder in mehreren sind, ändert nichts am Mechanismus.

>
> Ich ziehe es normalerweise vor, globale Variablen im dem Module zu
> definieren in das sie logischerweise gehören,
> z.B.
>
> pwm.c   -> Funktionen und globale Variablen zur Puls-Weiten-Modulation.
> pwm.h   -> deklariert Funktionen und globale Variablen in pwm.c
>
> stepper.c  -> Funktionen und globale Variablen zu Schrittmotoren
> stepper.h  -> deklariert Funktionen und globale Variablen in stepper.c
>
> main.c     -> includiert pwm.h und stepper.h
>
> Beim Kompilieren von pwm.c brauchen und sollen die restlichen globalen
> Variablen nicht sichtbar sein.

Du kannst dasselbe Schema mit dem EXTERN Makro machen.
Ob die globalen Variablen in einem Headerfile zusammengefasst
sind, oder wie du anmerkst nach Modulen geordnet in eigene
Headerfiles kommen, ändert nichts am Mechanismus.

Grundsätzlich sollte man überhaupt keine globalen Variablen benutzen.
Allerdings wird diese 'eherne Regel' der Programmierung für
µC etwas aufgeweicht, weil man ja schliesslich auch noch ein
paar Restriktionen durch die Plattform hat und nicht so urassen
kann, wie auf einem Desktop System.

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie gesagt, mir gefällt die Version mit dem EXTERN nicht.
Meine Gründe sind :
- Globale Variablen gehören nicht in ein einziges Quellfile, sondern zu 
dem Quellfile in das sie funktionell gehören.
- Es ist nicht so viel Aufwand, die Variablen im Quellfile nocheinmal 
anzuführen. Außerdem sind sie dann dort für den beim Programmieren 
sichtbar und hinter einem #include versteckt.
- Bei initialisierten globalen Variablen funktiert der Mechanismus nicht 
(höchstens mit Trickserei)
- Ich liebe hierarchische Designs :
z.B.
main.c verwendet Funktionen und globale Variablen aus stepper.c, 
includiert deshalb stepper.h
stepper.c verwendet Funktionen und globale Variablen aus hardware.c, 
includiert deshalb hardware.h und stepper.h (zur Kontrolle).
hardware.h includiert hardware.h (zur Kontrolle).

Wenn ich den Mechanismus mit dem EXTERN verwenden würde, müßte ich in 
main.c beide Header-Files includieren.

main.c :
#define EXTERN
#include "stepper.h"
#include "hardware.h"
...

main.c würde auch von Hardware.h abhängen und müßte bei einer Änderung 
von Hardware.h neu kompiliert werden. Das macht zwar bei kleinen Designs 
nichts aus, ist aber bei größeren Designs unerwünscht (und geht dem 
Prinzip einer Hierarchie zuwider).

Alternativ könnte man das stepper.c schreiben
#define EXTERN
#include "stepper.h"
#undef EXTERN
#include "hardware.h"
#include ....

--> Gefällt mir auch nicht.

Klaus



Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Eine Möglichkeit wäre es, das Macro "EXTERN" je nach Headerdatei anders 
zu benennen, und zwar den Dateinamen des zugehörigen Sourcefiles zu 
verwenden:

   stepper.c:

   #define STEPPER_C
   #include "stepper.h"
   #include "blafusel.h"
   ...


   stepper.h:

   #ifndef STEPPER_C
   #define EXTERN extern
   #endif

   EXTERN int irgendwas;

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.