Forum: Compiler & IDEs Frage zu Mikrocontroller Programmierung


von Steffen (Gast)


Lesenswert?

Hallo,

ich beschäftige mich gerade ein wenig mit der Programmierung von 
Mikrocontrollern. Habe hier eien ATmega und spiele ein wenig damit rum. 
Taste mich mit Beispielcode etwas an die Sache heran.

Ich in einigen Beispielcodes in Quellcodedateien etwas gefunden wie:

#ifndef CONFIG_H
#define CONFIG_H

in einer Headerdatei.

Mir ist allerdings noch nicht klar, was das bedeutet / bewirkt.

Wäre klasse, wen mir da jemand etwas zu sagen könnte.

Besten Dank schonmal.

Gruß
Steffen

: Verschoben durch User
von Jan B. (berge)


Lesenswert?

Moin,

das ist eine Frage zu C bzw. Präprozessoranweisungen. Lies mal z.B. 
hier. http://de.wikipedia.org/wiki/C-Pr%C3%A4prozessor

Liebe Grüße,
Jan

von Dennis S. (eltio)


Lesenswert?

Stichwort "Include-Guard". Das ist aber keine µC-spezifische Sache 
sondern in den Programmiersprachen C und C++ verbreitet.

Gruß Dennis

von Bitflüster (Gast)


Lesenswert?

Steffen schrieb:

> #ifndef _CONFIG_H_
> #define _CONFIG_H_
>
> in einer Headerdatei.
>
> Mir ist allerdings noch nicht klar, was das bedeutet / bewirkt.

Hm. Also das erste ist ein sogenannter "Include-Guard" - eine 
"Einfüge-Wächter". Es geht darum, das man den Fehler der mehrfachen 
Definitionen vermeiden will. Du wirst in anderen C-Files des selben 
Projektes so etwas wie:
1
#ifndef _CONFIG_H_
2
#include "confi.h"

finden.

Falls also, _CONFIG_H schon definiert ist, was wiederum bedeutet, dass 
die H-Datei schon includet worden ist, dann wird sie nicht erneut 
includet.

Deine zweite Frage, nach dem "/" kann ich mir nicht recht erklären.
Das Zeichen hat nichts mit dem Präprozessor zu tun.
Es ist einfach der Divisions-Operator.

von Dennis S. (eltio)


Lesenswert?

Bitflüster schrieb:
> Deine zweite Frage, nach dem "/" kann ich mir nicht recht erklären.
> Das Zeichen hat nichts mit dem Präprozessor zu tun.
> Es ist einfach der Divisions-Operator.

Ersezte "/" durch "beziehungsweise"... ;-)

von Timmo H. (masterfx)


Lesenswert?

Bitflüster schrieb:
> Du wirst in anderen C-Files des selben
> Projektes so etwas wie:
> #ifndef _CONFIG_H_
> #include "confi.h"
>
> finden.

Genau das ist dann eben nicht mehr notwendig. Alle Dateien die etwas aus 
der config.h benötigen includen diese einfach, der "Include-Guard" sorgt 
eben von ganz alleine dafür, dass es nicht mehrfach die config.h 
includiert wird.

von Quack+ (Gast)


Lesenswert?

Bitflüster schrieb:
> Falls also, _CONFIG_H schon definiert ist, was wiederum bedeutet, dass
> die H-Datei schon includet worden ist, dann wird sie nicht erneut
> includet.

Normalerweise verhindert man im .h-File, dass der Inhalt mehrfach 
verwendet wird, indem man den Include-Guard um den ganzen Inhalt macht. 
Damit kann das File problemlos mehrfach eingefuegt werden und man spart 
sich den Check vor jedem #include.

von Borislav B. (boris_b)


Lesenswert?

Heutzutage verwendet man stattdessen "#pragma once".

Das leistet das gleiche, benötigt weniger Code und kompiliert u.U. 
schneller. Alle relevanten Compiler sollten das unterstützen...

: Bearbeitet durch User
von Kai S. (zigzeg)


Lesenswert?

Bitflüster schrieb:
> Du wirst in anderen C-Files des selben
> Projektes so etwas wie:
> #ifndef _CONFIG_H_
> #include "confi.h"
>
> finden.

Hm, normalerweise macht man das nicht in den C Dateien, da es schon im 
Header gemacht wird:

http://de.wikipedia.org/wiki/Include-Guard

Manche Compiler (GCC) haben sogar eine extra Optimierung eingebaut ein 
include Guard zu erkennen, und das haeufige Oeffnen des Header-Files zu 
sparen.

ZigZeg

von Bitflüsterer (Gast)


Lesenswert?

Timmo H. schrieb:
> Bitflüster schrieb:
>> Du wirst in anderen C-Files des selben
>> Projektes so etwas wie:
>> #ifndef _CONFIG_H_
>> #include "confi.h"
>>
>> finden.
>
> Genau das ist dann eben nicht mehr notwendig. Alle Dateien die etwas aus
> der config.h benötigen includen diese einfach, der "Include-Guard" sorgt
> eben von ganz alleine dafür, dass es nicht mehrfach die config.h
> includiert wird.

Nun. Ich hätte die zweite Möglichkeit auch erwähnen können. Aber das 
Beispiel des TO ließ beide Möglichkeiten offen. Und ich lasse lieber 
einem Anfänger die Möglichkeit etwas selbst herauszufinden, als ihm 
etwas vorzusagen.

"Nicht notwendig" oder nicht, ist jedenfalls hier nicht entscheidbar. 
Siehe: http://c2.com/cgi/wiki?RedundantIncludeGuards

von Karl H. (kbuchegg)


Angehängte Dateien:

Lesenswert?

Da du ATMega erwähnt hast, gehe ich davon aus, dass die ein Atmel-Studio 
installiert hast.
Hol dir mal aus dem Anhang das beiliegende Projekt und lade es ins 
Atmel-Studio

Was geht hier vor.

Zunächst mal gibt es da ein main.c
1
#include <avr/io.h>
2
3
#include "Header1.h"
4
#include "Header2.h"
5
6
int main(void)
7
{
8
  struct test t1;
9
  struct check t2;
10
11
  while(1)
12
  {
13
  }
14
}

Da in main.c 2 Strukturen benutzt werden, muss es dafür Definitionen 
geben. Die Defintion für die struct test steckt in header1.h. Hier wird 
also erklärt, was man sich unter einem derartigem Strukturobjekt 
vorzustellen hat
1
struct test {
2
  int a;
3
};
Da diese Definition in Header1.h drinnen steht, gibt es 
konsequenterweise einen entsprechenden #include, damit diese Header 
Datei beim Compilieren von main.c mit hereingezogen wird und somit dem 
Compiler beim Anlegen der Variable t1 bekannt ist, wieviel Speicherplatz 
er dafür reservieren muss. So ein #include macht ja im Grunde nichts 
anderes, als die Zeile mit dem #include, durch den Inhalt der 
angegebenen Datei zu ersetzen. Aus Sicht dieses #include ist das nichts 
anderes als ob der Compiler anstatt dem originalen main.c eine temporäre 
Datei mit diesem Inhalt ...
1
#include <avr/io.h>
2
3
// #include "Header1.h"
4
struct test {
5
  int a;
6
};
7
8
#include "Header2.h"
9
10
int main(void)
11
{
12
  struct test t1;
13
  struct check t2;
14
15
  while(1)
16
  {
17
  }
18
}
... übersetzt hätte. Genau das hat der Präprozessor aus der Zeile mit 
dem #include gemacht.

Was ist mit struct check?
Auch dafür gibt es eine Header Datei, in der erklärt wird, was man sich 
unter diesem struct vorzustellen hat
1
#include "Header1.h"
2
3
struct check {
4
  struct test a;
5
  int         b;
6
};
Diesmal besteht so ein Strukurobjekt aus 2 Membern, a und b. Wobei der 
Member a ein struct test Objekt sein soll. Getreu dem Motto "Jede Datei 
soll in sich vollständig sein", includiert diese Header Datei auch die 
Header1.h, in der ja beschrieben ist, wie ein struct test Objekt 
auszusehen hat. Soweit so gut.
Aber was bedeutet das für main.c?
Nun ganz einfach. lass uns wieder den Inhalt von Header2.h anstelle der 
Zeile mit dem #include einsetzen. Damt ergibt sich
1
#include <avr/io.h>
2
3
// #include "Header1.h"
4
struct test {
5
  int a;
6
};
7
8
// #include "Header2.h"
9
#include "Header1.h"
10
11
struct check {
12
  struct test a;
13
  int         b;
14
};
15
16
int main(void)
17
{
18
  struct test t1;
19
  struct check t2;
20
21
  while(1)
22
  {
23
  }
24
}

Durch die Ersetzung ist eine weitere Zeile mit einem #include 
entstanden, die natürlich ebenfalls durch die Ersetzung aufgelöst wird 
....
1
#include <avr/io.h>
2
3
// #include "Header1.h"
4
struct test {
5
  int a;
6
};
7
8
// #include "Header2.h"
9
// #include "Header1.h"
10
struct test {
11
  int a;
12
};
13
14
struct check {
15
  struct test a;
16
  int         b;
17
};
18
19
int main(void)
20
{
21
  struct test t1;
22
  struct check t2;
23
24
  while(1)
25
  {
26
  }
27
}

Tja. Jetzt sieht man aber schon das Dilemma. Über den Umweg Header2.h 
ist die Datei Header1.h 2 mal im Endergebnis repräsentiert. Als Folge 
davon gibt es 2 Definitionen darüber, wie ein struct Test Objekt 
auszusehen hat. Die beiden Definitionen stimmen zwar überein, trotzdem 
ist das in C verboten. Du darfst von einer Struktur immer nur eine 
Definition haben!

Und genau an dieser Stelle setzten die Include Guards an. Ergnänzt man 
den Code von Header1.h mit einem Include Guard, so wie hier
1
#ifndef HEADER1_INCLUDED
2
#define HEADER1_INCLUDED
3
4
struct test {
5
  int a;
6
};
7
8
#endif

dann sieht die Situation anders aus. Wenn sich der Präprozessor das 
original main.c vornimmt, dann stösst er wie immer auf den #include. Die 
Eresetzung der Zeile mit dem Inhalt ergibt ...
1
#include <avr/io.h>
2
3
// #include "Header1.h"
4
#ifndef HEADER1_INCLUDED
5
#define HEADER1_INCLUDED
6
7
struct test {
8
  int a;
9
};
10
11
#endif
12
13
#include "Header2.h"
14
15
int main(void)
16
{
17
  struct test t1;
18
  struct check t2;
19
20
  while(1)
21
  {
22
  }
23
}

Bearbeitet der Präprpzessor den Text weiter, dann stösst er auf die 
Fragestellung
1
#ifndef HEADER1_INCLUDED
Nun, das Makro HEADER1_INCLUDED ist zu diesem Zeitpunkt noch nicht 
defininiert, also wird der Teil innheralb des #ifndef im Quelltext 
belassen. Dieser Teil beinhaltet aber ein
1
#define HEADER1_INCLUDED
womit das Makro definiert wird. In weiterer Folge gibt es also dieses 
Makro.

Stösst der Präprozessor auf den nächsten Include, dann wird wie immer 
der Inhalt der Datei Header2 hereingezogen
1
#include <avr/io.h>
2
3
// #include "Header1.h"
4
#ifndef HEADER1_INCLUDED
5
#define HEADER1_INCLUDED
6
7
struct test {
8
  int a;
9
};
10
11
#endif
12
13
// #include "Header2.h"
14
#include "Header1.h"
15
16
struct check {
17
  struct test a;
18
  int         b;
19
};
20
21
int main(void)
22
{
23
  struct test t1;
24
  struct check t2;
25
26
  while(1)
27
  {
28
  }
29
}

wodurch auch der 2.te #include für Header1.h wirksam wird. Erneut wird 
Header1 hereingezogen
1
#include <avr/io.h>
2
3
// #include "Header1.h"
4
#ifndef HEADER1_INCLUDED
5
#define HEADER1_INCLUDED
6
7
struct test {
8
  int a;
9
};
10
11
#endif
12
13
// #include "Header2.h"
14
// #include "Header1.h"
15
#ifndef HEADER1_INCLUDED
16
.....
aber diesmal ist bei der weiteren Auswertung das Makro HEADER1_INCLUDED 
bereits definiert. Durch die erste Inklusion wurde es ja definiert.
Da damit die Fragestellung #ifndef mit 'Nein' zu beantworten ist, wird 
der zwischen #ifndef und #endif stehende Text nicht mit in das temporäre 
Zwischenergebnis mit übernommen. Als Zwischenergebnis entsteht also
1
#include <avr/io.h>
2
3
// #include "Header1.h"
4
#ifndef HEADER1_INCLUDED
5
#define HEADER1_INCLUDED
6
7
struct test {
8
  int a;
9
};
10
11
#endif
12
13
// #include "Header2.h"
14
// #include "Header1.h"
15
#ifndef HEADER1_INCLUDED
16
#endif
17
18
struct check {
19
  struct test a;
20
  int         b;
21
};
22
23
int main(void)
24
{
25
  struct test t1;
26
  struct check t2;
27
28
  while(1)
29
  {
30
  }
31
}

Und jetzt ist dir struct test nur einmal definiert.

ANzumerken ist noch, dass die #-Zeilen vom Präprozessor natürlich 
komplett entfernt werden. Ich hab sie nur der Anschaung wegen drinnen 
gelassen. Der eigentliche C-Compiler kriegt das hier
1
... an dieser Stelle der komplette Inhalt von avr/io.h
2
... inklusive aller weiteren Includes
3
4
struct test {
5
  int a;
6
};
7
8
struct check {
9
  struct test a;
10
  int         b;
11
};
12
13
int main(void)
14
{
15
  struct test t1;
16
  struct check t2;
17
18
  while(1)
19
  {
20
  }
21
}
zu sehen und das ist offensichtlich ein korrektes C Programm, in dem 
nichts doppelt vorkommt.

von Bitflüsterer (Gast)


Lesenswert?

Die Antwort mit dem #pragma once ist für einen Anfänger irreführend 
oder, wenn nicht irreführend, so doch problematisch. Ausserdem hat der 
TO danach garnicht gefragt.

Problematisch ist sie jedenfalls. Siehe dazu: 
https://en.wikipedia.org/wiki/Pragma_once Das Problem ist, dass es unter 
Umständen für den Compiler nicht einfach ist, die Dateien zu 
identifizieren.

Nun ist also aus einer erstmal einfachen Frage bezüglich eines 
möglichen Konstruktes eines Include-Guards eine wesentlich 
kompliziertere Frage geworden, da er mit drei verschiedene Varianten 
mit ihren jeweiligen Nachteilen und Vorteilen konfrontiert ist.
Die Gelegenheit aber das selbst zu entdecken, ist ihm genommen.

Und alles nur weil zwei Schlaumeier unbedingt was antworten wollten. :-) 
Schade.

von Steffen (Gast)


Lesenswert?

Hallo zusammen,

vielen Dank, für die schnellen und teils wirklich ausführlichen 
Erläuterungen!

Das hat mir weitergeholfen.

Vielen Dank!

Grüße

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.