Ich habe eine Frage zu Header-Dateien.
Ich möchte mein Programm nur etwas übersichtlicher gestalten, daher
würde ich gerne ein paar Sachen in Header-Dateien auslagern.
Meine Frage ist: Muss ich hier immer eine .c und eine .h und daraus eine
Objekt-Datei machen?
Es funktioniert ja auch, wenn ich einfach sowohl Definitionen und
Funktionen in eine .h auslagere und diese beim Starten lade. Ist ja
quasi ne reine Textkopie in meinen eigentlichen Quelltext. Es geht mir
nicht darum, die Module für andere Sachen verfügbar zu machen, sondern
die bleiben bei dem Programm hier. Ich check das mit den Headern nur
noch nicht so. Hier die Variablen, da das, da das.
Also mal als Beispiel:
1
#include<stdint.h>
2
3
...
4
#define XYZ 100
5
#define ABC 300
6
...
7
8
voidfunktion_1(void);
9
int16_tfunktion_2(uint8_tbeispiel);
10
...
11
12
voidmain(void)
13
{
14
machedies...;
15
machedas...;
16
17
funktion_1();
18
...
19
}
20
21
voidfunktion_1(void)
22
{
23
machenix;
24
}
25
26
int16_tfunktion_2(uint8_tbeispiel)
27
{
28
returnbeispiel++;
29
}
30
[c]
31
32
Wennichnunfolgendesineiner.hauslagere:
33
34
[c]
35
#ifndef MYHEADER_H
36
#define MYHEADER_H
37
38
#define XYZ 100
39
#define ABC 300
40
41
voidfunktion_1(void);
42
int16_tfunktion_2(uint8_tbeispiel);
43
44
voidfunktion_1(void)
45
{
46
machenix;
47
}
48
49
int16_tfunktion_2(uint8_tbeispiel)
50
{
51
returnbeispiel++;
52
}
53
54
#endif
Und dann in meinem Hauptprogramm:
1
#include<stdint.h>
2
#include"myheader.h"
3
4
voidmain(void)
5
{
6
machedies...;
7
machedas...;
8
9
funktion_1();
10
...
11
}
Spricht da was gegen? Ich raff halt nicht, was ich wie in den Header und
was in die extra C-Datei schreiben muss. Das geht bestimmt schief - dazu
dann noch Variablen mit extern usw.
Hans schrieb:> Ich habe eine Frage zu Header-Dateien.>> Ich möchte mein Programm nur etwas übersichtlicher gestalten, daher> würde ich gerne ein paar Sachen in Header-Dateien auslagern.>> Meine Frage ist: Muss ich hier immer eine .c und eine .h und daraus eine> Objekt-Datei machen?
Müssen tust du gar nichts.
Ein C Compiler ist ja kein Scharfrichter, der dir die Rübe abhackt.
> Es funktioniert ja auch, wenn ich einfach sowohl Definitionen und> Funktionen in eine .h auslagere und diese beim Starten lade.
Soweit so gut. Bis dann die Projekte größer werden, du dieselbe
Header-Datei in 2 andere *.c Files inkludieren musst und dann
funktioniert es nämlich nicht mehr.
> die bleiben bei dem Programm hier. Ich check das mit den Headern nur> noch nicht so.
Ist ganz einfach:
Jedes Software-Modul bekommt sein eigenes C-File, wird extra kompiliert
und gehört zum Projekt.
Damit andere Teile des Programms bescheid wissen, was dieses Modul so an
Funktionalität anbietet, inkludieren sie ein Header File, in dem genau
diese Information (und nur diese Information) enthalten ist.
> Wenn ich nun folgendes in einer .h auslagere:>> #ifndef MYHEADER_H> #define MYHEADER_H>> #define XYZ 100> #define ABC 300>> void funktion_1( void );> int16_t funktion_2 (uint8_t beispiel);
Bis hier hier ist es ok.
>> void funktion_1( void )> {> mache nix;> }>> int16_t funktion_2 (uint8_t beispiel)> {> return beispiel++;> }
Die Funktionen selbst haben (ausser in Ausnahmefällen) nichts im Header
File zu suchen. Wenn sie aber im Header File sind, müssen sie inline
markiert sein.
> Spricht da was gegen? Ich raff halt nicht, was ich wie in den Header und> was in die extra C-Datei schreiben muss.
ALs Grundregel:
Die Implementierungen der Funktionen gehören in ein C-File.
Im Header sind nur die Funktionsprototypen.
Und daraus folgt dann alles andere. Funktionsprototypen benötigen
eventuell Strukturdeklarationen, also gehören die auch ins Header File,
etc. etc.
hallo Hans
in eine .h datei schreibt man keinen programm-code.
das schreibst du in eine .c datei.
hast also 2 zusaetzliche dateien, z.b.
teilprog1.c
teilprog1.h
und in teilprog1.h definierst du nur die unterprgramme mit extern
extern void funktion_1( void );
und im haupt-teil wird nur das .h included
#include "teilprog1.h"
that's it.
schoene gruesse
hans
--
Ein #include fügt effektiv den Inhalt der angegebenen Datei an dieser
Stelle im Quelltext ein. Also wenn der Inhalt an die Stelle kopiert
funktionieren würde, funktioniert es auch so.
Nur ist das nicht dafür gedacht, Quelltext aufzuteilen. Dafür packt man
das in mehrere C Quelltexte, die separat übersetzt und dann zusammen
gelinkt werden. Dann braucht man natürlich wieder Header für die
gemeinsamen Deklarationen. Aber ein Header, der nur für eine einzige C
Datei existiert, sollte eigentlich nicht existieren.
OK Karl-Heinz, danke schonmal für die Antwort.
Darf ich dich evtl. mal mit einm Beispiel belästigen, damit ich es evtl.
ein für allemal verstanden habe?
Also ich habe für meinen ADC eine State-Machine geschrieben, die mir
nebenbei die Daten aktualisiert. Sie benötigt aber halt im
Gesamtprogramm auch ein paar Definitionen. Folgendes gehört dazu:
DAC_SM.state=DAC_START_TRANSMISSION;// Change state-machine state
72
}
73
74
caseDAC_START_TRANSMISSION:// Start transmission of data
75
//...
76
}
77
}
78
79
80
// ISR
81
voiddac_RX_SM(void)
82
{
83
switch(DAC_SM.state)// Determine state of RX-SM
84
{
85
caseDAC_TRANSMITTING:// Trasnmission is active
86
{
87
DAC_SM.bytecounter++;// Advance bytecounter
88
89
if(DAC_SM.bytecounter<3)// Not all bytes sent yet
90
{
91
// ...
92
}
93
}
94
}
95
}
Was mache ich jetzt in eine .h und was in eine .c? Ich habe am meisten
Bedenken bei dem struct. Da habe ich im iNet gelesen, ich müsse hier
oder da ein extern voranstellen?
Hans schrieb:> Was mache ich jetzt in eine .h und was in eine .c?
Fang damit an, dich zu fragen, welche Funktionen von ausserhalb
zugänglich sein müssen. Die stellst du erst mal in das Header File
Ich hab mal das hier identifiziert
1
#ifndef DAC_INCLUDED
2
#define DAC_INCLUDED
3
4
voiddac_update(void);
5
voiddac_RX_SM(void);
6
7
#endif
mehr muss main nicht wissen, um die State Machine einsetzen zu können.
Insbesondere muss main nicht wissen, dass es eine Struktur gibt, oder
dass du da mittels #define irgendwelche Konstanten festgelegt hast.
In den Funktionsprototypen kommen auch keine Argumente vor, die
irgendwelche Strukturen oder sonstiges Wissen erfordern.
Damit ist dieses Header File in sich vollständig.
Wenn in main.c dieses Header File inkludiert wird, dann hat man in main
alles was man braucht.
Alles andere bleibt beim DAC Code im eigenen C-File.
Warum? Weil das ausserhalb dieses C-Files niemanden etwas angeht.
DAC_SM.state=DAC_START_TRANSMISSION;// Change state-machine state
52
}
53
54
caseDAC_START_TRANSMISSION:// Start transmission of data
55
//...
56
}
57
}
58
59
60
// ISR
61
voiddac_RX_SM(void)
62
{
63
switch(DAC_SM.state)// Determine state of RX-SM
64
{
65
caseDAC_TRANSMITTING:// Trasnmission is active
66
{
67
DAC_SM.bytecounter++;// Advance bytecounter
68
69
if(DAC_SM.bytecounter<3)// Not all bytes sent yet
70
{
71
// ...
72
}
73
}
74
}
75
}
Fertig.
> Ich habe am meisten> Bedenken bei dem struct. Da habe ich im iNet gelesen, ich müsse hier> oder da ein extern voranstellen?
Nachdenken. Dein Leitfaden ist nicht das iNet, sondern die
Fragestellung: Wer muss ausserhalb des C-Files (mit der Implementierung)
was wissen?
In deinem Fall muss ein Verwender des DAC nur die beiden Funktionen
kennen. Alles andere muss er nicht wissen. Das bleibt daher beim DAC
Code im eigenen C-File. Das C-File bildet sowas wie eine Hülle, eine
Grenze, an der die Sichtbarkeit von Dingen endet. Enden sollte; aber
dazu müsste man zb File-globale Variablen static machen. Das sieht dann
so aus
1
.....
2
structdacData
3
{
4
uint8_tstate;// Actual state of state-machine
5
uint32_tdac_value;// Value to be written to DAC
6
uint8_tsend_bytes[3];// Buffer for bytes to send
7
uint8_tbytecounter;// Counter for interrupt-driven communication
8
uint8_thold_state_machine;// If set, SM stops after last transmission
9
uint8_ttx_buffer_error;// Counts errors if TX-buffer not empty
10
};
11
12
staticvolatilestructdacDataDAC_SM={DAC_START_SM,0x8000,{0,0,0},0,FALSE,0};// Init SM
13
....
Jetzt ist die Sichtbarkeit der Strukturvariablen auf das C-File
begrenzt. Es ist nun nicht mehr möglich sich von ausserhalb des DAC.C
auf diese Variable zu beziehen und an ihr herumzupfuschen. Und genau so
soll es ja auch sein.
(Und gewöhn dir diese anonymen Strukturen und das definieren von
Variablen zusammen mit der Strukturdefinition gleich wieder ab. Das ist
es nicht wert und in 95% aller Fälle kann man dieses Schema sowieso so
nicht einsetzen).
Den Teil mit den MCU-SPECIFIC DEFINITIONS hab ich ebenfalls in DAC.C
verschoben, weil dort etwas davon verwendet wird. Wenn du diese Makros
in main.c auch brauchen würdest, dann würde man diese #define wiederrum
in ein eigenes Header File verfrachten und dieses dann in DAC.C und in
MAIN.C inkludieren.
Das Prinzip heißt 'Information Hiding'. Du willst soviel Information wie
möglich in einem C-File 'verstecken' und nur über das Header File das
preisgeben, was du unbedingt preisgeben musst, damit anderer Code diese
Funktionalität benutzen kann. Alles was für diesen Zweck nicht notwendig
ist, hat im Header File erst mal nichts verloren.
Hallo Karl-Heinz!
Vielen Dank für deine Ausführliche Erklärung!
Karl Heinz Buchegger schrieb:> Es ist nun nicht mehr möglich sich von ausserhalb des DAC.C> auf diese Variable zu beziehen und an ihr herumzupfuschen.
Ich benötige jedoch Zugriff auf dieses struct, da ich den Wert
"dac_value" ja in das struct eintragen muss, damit es übertragen wird.
Kann ich die Variable denn dann auch nicht mehr beschreiben?
Hans schrieb:> Hallo Karl-Heinz!>> Vielen Dank für deine Ausführliche Erklärung!>> Karl Heinz Buchegger schrieb:>> Es ist nun nicht mehr möglich sich von ausserhalb des DAC.C>> auf diese Variable zu beziehen und an ihr herumzupfuschen.>> Ich benötige jedoch Zugriff auf dieses struct, da ich den Wert> "dac_value" ja in das struct eintragen muss, damit es übertragen wird.> Kann ich die Variable denn dann auch nicht mehr beschreiben?
Nein, kannst du nicht, sollst du auch nicht.
Schreib dir eine Funktion, die das tut.
So Karl-Heinz, ich hoffe du schaust hier nochmal rein. Ich habe jetzt
mal einiges umgemodelt.
Eine Frage habe ich vorab jedoch noch:
Wenn ich in dieser .h-Datei jetzt folgendes stehen habe:
1
#ifndef DAC_FUNCTIONS_H
2
#define DAC_FUNCTIONS_H
3
4
voiddac_update(void);
5
voiddac_rx_sm(void);
6
uint8_tdac_update_value(uint32_tdac_value);
7
voiddac_start_sm(void);
8
voiddac_stop_sm(void);
9
uint8_tdac_return_sm_status(void);
10
11
#endif // DAC_FUNCTIONS_H
Hier muss ich doch jtzt ebenfalls die <stdint.h> includieren, da ich
uint8_t nutze, oder? Einmal mehr includieren stört ja eh keinen,
richtig? Solange die doppelte Einbindung nicht vorkommt.
Hans schrieb:> Wenn ich in dieser .h-Datei jetzt folgendes stehen habe:>
1
>#ifndefDAC_FUNCTIONS_H
2
>#defineDAC_FUNCTIONS_H
3
>
4
>voiddac_update(void);
5
>voiddac_rx_sm(void);
6
>uint8_tdac_update_value(uint32_tdac_value);
7
>voiddac_start_sm(void);
8
>voiddac_stop_sm(void);
9
>uint8_tdac_return_sm_status(void);
10
>
11
>#endif// DAC_FUNCTIONS_H
12
>
> Hier muss ich doch jtzt ebenfalls die <stdint.h> includieren, da ich> uint8_t nutze, oder?
Ganz genau.
Jedes Header File soll in sich vollständig sein und selbst alles
inkludieren was es braucht. Du entlastest damit denjenigen, der dac.h
inkludiert. Er braucht sich dann nicht darüber Gedanken zu machen,
welche anderen Header Vorraussetzung zum inkludieren von dac.h sind.
Wobei man bei Systemheadern meistens eine Ausnahme macht. Zumindest
solange, solange man davon ausgehen kann, dass dieser Systemheader
sowieso schon inkludiert sein wird. stdint.h könnte man da dazurechnen.
Schadet aber auch nicht, wenn du ihn im Header File inkludierst.
Denn: Wenn dein Header File damit beginnt, dass es selbst erst mal 268
andere Files included, dann
* dient das nicht gerade der Übersicht
* läuft man Gefahr, ungewollt einen Kreisbezug zu erhalten:
A.h includiert B.h
und B.h includiert A.h
> Einmal mehr includieren stört ja eh keinen,> richtig? Solange die doppelte Einbindung nicht vorkommt.
Dieses potentielle Problem wird ja durch die Include Guards entschärft.
#define BIT_0 0x01 // Wird hier noch nicht benoetigt
8
#define BIT_1 0x02
9
#define BIT_2 0x04
10
#define BIT_3 0x08
11
#define BIT_4 0x10
12
#define BIT_5 0x20
13
#define BIT_6 0x40
14
#define BIT_7 0x80
15
16
#endif // GENERAL_DEFINITIONS_H
Ich hoffe, ich habe es jetzt dann so einigermaßen verstanden.
Jetzt muss ich die nurnoch einzeln compilieren, mal sehen, wie das
klappt.
Wieso muss in der dac_functions.c die dac_functions.h eingebunden
werden?
Die case-Durchfaller bei
case DAC_START_SM: // Restart
state-machine
und
case DAC_BUILD_TRANSMIT_DATA: // Build data-bytes
sind Absicht?
In solchen Fällen schreib ich mir das in einem Kommentar dazu, dass hier
der break absichtlich fehlt. Denn in 2 Monaten weiß ich nicht mehr, ob
ich den einfach nur vergessen habe oder ob ich ihn absichtlich
weggelassen habe, weil die Behandlung eines case Punktes auch die
Abarbeitung des nächsten case-Punktes beinhaltet. Das ist nämlich
besonders dann interessant, wenn ein Schlauberger anfängt die cases
umzusortieren. Dann landet der Fallthrough plötzlich in einem ganz
anderen case.
> Wieso muss in der dac_functions.c die dac_functions.h> eingebunden werden?
Du musst nicht.
Aber stell dir vor was passiert, wenn du im Header File hast
void foo( double i );
und in der IMplementierung steht
void foo( int i )
{
...
}
Inkludiert das C-File sein eigenes Header File, dann kann der Compiler
das prüfen und gegebenenfalls melden. Includierst du es nicht, dann hast
du gerade deine Sicherung gegen solche Fehler weggeworfen.
Karl Heinz Buchegger schrieb:> Die case-Durchfaller
Ja, in diesem Fall sind sie Absicht, weil es danach direkt losgehen
kann. Aber hast evtl. Recht, das könnte man dazu schreiben.
Karl Heinz Buchegger schrieb:> Inkludiert das C-File sein eigenes Header File, dann kann der Compiler> das prüfen und gegebenenfalls melden. Includierst du es nicht, dann hast> du gerade deine Sicherung gegen solche Fehler weggeworfen.
OK, alles klar! Vielen Dank!!!!!!