Hallo!
Das ist ein wahrscheinlich einfaches Thema aber das ganze ist so
unübersichtlich dass ich dennoch nachfragen muss.
Der Ursprung meines Problemes ist der, dass ich meinen
Mikrocontrollercode auf ein eventuelles Pinswapping vorbereiten wollte.
In den Beispielen von STM wird das eigentlich ganz elegant erledigt.
Aufgrunddessen hab ich jetzt beispielsweise sowas konstruiert:
config.h wäre dann die datei wo ich meine pins und ports eintrage:
in der config.h gelöst werden. (ja? nein?)
Kann mir jemand erklären wieso der linker trotz #ifndef muckt?
Gibt es eine Möglichkeit dass ich das nur mit einer headerfile machen
kann?
Ich würde mir echt gerne die config.c (und auch das notwendige
mitverlinken) und die doppelschreiberei ersparen...
Hier sind zwei Irrtümer maßgebend.
1. Der Linker "muckt" nicht, sondern reagiert sinnvoll auf dass, was Du
in C geschrieben hast.
2. Der Linker hat mit #ifdef uws. nichts zu tun. Das ist Sache des
Präprozessors.
Das #ifndef verhindert/befördert ja nur ob das config.h included wird
oder nicht. Der Hauptzweck dieses Konstrukts, ist es zu verhindern, das
include files mehrfach included werden.
Mit der Deklaration/Definition Deiner Variablen aber hat das nichts zu
tun. Da musst Du in der Tat eine Methode finden, wie Du zwischen
extern uint16_t DIGITAL_OUT_PIN;
und
const uint16_t DIGITAL_OUT_PIN[...
auswählst.
Das ist garnicht so schwer und benutzt im Grunde den selben Gedankengang
wie das obige #ifdef. Überleg mal selbst.
also, was ist eigentlich das Problem wenn ich das Array direkt in die
headerfile schreibe.
Der Compiler wird die header einmal aufrufen und danach ist das Array
global (oder eben nicht?) deklariert.
Ich denke was du mir sagen willst ist, dass das Array dann NUR für die
aufrufende Funktion deklariert ist.. (?)
Könnte das eine Lösung sein?
config.h enthält also auch:
1
#ifndef INIT_CONFIG_H_
2
constuint16_tDIGITAL_OUT_PIN[DIGITAL_OUTn]=
3
{
4
EN_FILTER_PIN,EN_MODEM_PIN,EN_SENSOR_PIN
5
};
6
#endif
7
8
#ifdef INIT_CONFIG_H_
9
externuint16_tDIGITAL_OUT_PIN;
10
#endif
Das würde heissen dass das Array zwar im ersten Durchlauf global
deklariert wird aber immer noch mit jedem weiteren Modul (.c Datei)
durch extern verbunden werden muss.
Das ist mir ein bisschen unklar. Wenn meine main.h die datei config.h
inkludiert, dann müsste doch das array global ohne weiteres "extern" zur
Verfügung stehen?
Wenn ich ein einzelnes Modul (z.B. GPIO.c) kompiliere dann ist
INIT_CONFIG_H_ ja noch nicht definiert und das Modul sollte doch ohne
Probleme kompilierbar sein.
Verne schrieb:> 1. Der Linker "muckt" nicht, sondern reagiert sinnvoll auf dass, was Du> in C geschrieben hast.
So kann man das natürlich auch "interpretieren".
Das Problem, das hier auftritt, ist, daß für jede Objektdatei, die mit
eingebundener Headerdatei erzeugt wird, jeweils eine Kopie des
const-Array angelegt wird. Wenn diese nicht als statisch definiert
sind, d.h. nur innerhalb der Objektdatei sichbar, dann beschwert sich
der Linker --zu Recht-- über mehrfach definierte Symbole.
Der gcc-Linker bietet die Möglichkeit, diese Definitionen zu überlagern
und zu einem zusammenzufassen (so daß die Warnung nicht mehr auftritt),
aber das kann auch Probleme erzeugen, wenn nämlich die Symbole
eigentlich unterschiedlich sein sollten. Dann verdeckt diese Möglichkeit
den Fehler, den man dann recht aufwendig suchen muss.
Daher sollte --meiner Ansicht nach-- auf diese Möglichkeit verzichtet
werden, zumal das eine gcc-Spezialität ist, und andere C-Compiler bzw.
deren Linker sowas nicht unterstützen.
In Headerdateien gehören nur Deklarationen, also Erklärungen, daß es
irgendwo ein Objekt mit diesen und jenen Eigenschaften gibt, aber
nicht die Definition des Objektes, die festlegt, daß es hier dieses
Objekt gibt.
1
constuint16_tDIGITAL_OUT_PIN[DIGITAL_OUTn]=
2
{
3
EN_FILTER_PIN,EN_MODEM_PIN,EN_SENSOR_PIN
4
};
Das ist eine Definition.
Die korrespondierende Deklaration sieht so aus:
1
externconstuint16_tDIGITAL_OUT_PIN[DIGITAL_OUTn];
"Include-Guards", also das Verpacken in #ifdef & Co. helfen nur, wenn
sie so genutzt werden, daß nur für eine C-Datei die Definition
vorhanden ist.
Dann aber kann man es auch richtig machen und Definition und Deklaration
trennen.
Nochwas: Variablennamen, wie Dein DIGITAL_OUT_PIN, gehören nicht in
Großbuchstaben geschrieben. Das ist Macros, also mit #define definierten
Dingen vorbehalten - und je nach Philosophie des Entwicklers einzelnen
enum-Werten sowie typedefs.
----
Anmerkung:
In einem durch einen langen Arbeitstag vielleicht begründbaren Anfall
geistiger Umnachtung habe ich, als ich das gestern schrieb, recht
konsequent Definition und Deklaration durcheinandergebracht.
Das habe ich jetzt korrigiert -- mit Dank an den aufmerksamen Leser
Stefan Ernst.
Rufus Τ. Firefly schrieb:> In Headerdateien gehören nur Definitionen, also Erklärungen, daß es> irgendwo ein Objekt mit diesen und jenen Eigenschaften gibt, aber> nicht die Deklaration des Objektes, die festlegt, daß es hier dieses> Objekt gibt.
Bedeutung von "Definition" und "Deklaration" ist genau anders herum.
@ dotm:
Ein #define wirkt sich nicht Modul übergreifend aus. Zweck der
Include-Guards ist ausschließlich, das mögliche mehrfache Einbinden
eines Headers in ein und das selbe Modul zu unterbinden.
Rufus Τ. Firefly schrieb:> In Headerdateien gehören nur Definitionen, also Erklärungen, daß es> irgendwo ein Objekt mit diesen und jenen Eigenschaften gibt, aber> nicht die Deklaration des Objektes, die festlegt, daß es hier dieses> Objekt gibt.
Das verstehe ich, in diesem Spezialfall jedoch wäre es einfacher, weil
dann der Hardwaredesigner (also eh auch ich...) nur eine Datei und zwar
config.h ändern muss.
Rufus Τ. Firefly schrieb:> Nochwas: Variablennamen, wie Dein DIGITAL_OUT_PIN, gehören nicht in> Großbuchstaben geschrieben. Das ist Macros, also mit #define definierten> Dingen vorbehalten - und je nach Philosophie des Entwicklers einzelnen> enum-Werten sowie typedefs.
jetzt hast du mich erwischt.
Rufus Τ. Firefly schrieb:> Wenn diese nicht als statisch deklariert> sind, d.h. nur innerhalb der Objektdatei sichbar, dann beschwert sich> der Linker --zu Recht-- über mehrfach definierte Symbole.
Was, wenn ich die Deklarationen der const Arrays ausserhalb des
Include-Guards in die header Datei schreibe, aber als static.
Dann müssten sie pro .o einmal zur Verfügung stehen. Wäre das eine
Lösung?
Auch Interessant ist das Macro aus dem Beitrag:
Beitrag "Re: Globale Variablen, mehrere Dateien"
1
#ifndef EXTERN
2
#define EXTERN extern
3
#endif
4
5
EXTERNintGlobaleVar1;
6
EXTERNintGlobaleVar2;
Aber ich denke sowas geht nur wenn ich die Variable nicht initialisiere.
Oder frisst der Compiler sowas:
dotm schrieb:> Was, wenn ich die Deklarationen der const Arrays ausserhalb des> Include-Guards in die header Datei schreibe, aber als static.> Dann müssten sie pro .o einmal zur Verfügung stehen. Wäre das eine> Lösung?
AAAAHH!!!:
Stefan Ernst schrieb:> Ein #define wirkt sich nicht Modul übergreifend aus. Zweck der> Include-Guards ist ausschließlich, das mögliche mehrfache Einbinden> eines Headers in ein und das selbe Modul zu unterbinden.
Stefan Ernst schrieb:> Rufus Τ. Firefly schrieb:>> In Headerdateien gehören nur Definitionen, also Erklärungen, daß es>> irgendwo ein Objekt mit diesen und jenen Eigenschaften gibt, aber>> nicht die Deklaration des Objektes, die festlegt, daß es hier dieses>> Objekt gibt.>> Bedeutung von "Definition" und "Deklaration" ist genau anders herum.
Richtig. Deklaration = ich erkläre dem Compiler, dass eine Variable oder
Funktion mit diesem Namen und Typ irgendwo existiert. Definition =
Ausprogrammieren einer Funktion, bzw. Anlegen (und ggf. Initialisieren)
einer Variablen im Speicher.
dotm schrieb:> Aber ich denke sowas geht nur wenn ich die Variable nicht initialisiere.> Oder frisst der Compiler sowas:>>
1
>externintmeine_lieblingszahlen[2]=
2
>{
3
>666,1337
4
>};
5
>
Um das noch zu beantworten:
Schreiben kannst du es. Aber das Problem an der Sache ist, dass eine
Initialisierung aus einer Deklaration eine Definition macht. D.h. durch
die Initialisierung fällt das 'extern' stillschweigend unter den Tisch.
Also:
Verne schreibt oben dass es möglich ist zwischen const und extern zu
schalten.
Ich denke aber dass das nicht möglich ist. Wie soll denn der Compiler
wissen dass schon ein Objekt mit dem array erstellt worden ist?
Stefan Ernst schrieb:> Bedeutung von "Definition" und "Deklaration" ist genau anders herum.
Oh, Mist. Hast recht. War ein langer Tag gestern -- manchmal macht das
Hirn schlapp.
Habe das in o.g. Geschreibsel von mir jetzt korrigiert, siehe auch die
dortige Anmerkung.
dotm schrieb:> Gibt es eine Möglichkeit dass ich das nur mit einer headerfile machen> kann?> Ich würde mir echt gerne die config.c (und auch das notwendige> mitverlinken) und die doppelschreiberei ersparen...
Ein möglicher Weg wäre, ein spezielles Präprozessorsymbol für die
"Umschaltung" zwischen Deklaration und Definition zu verwenden. Eine
einzelne Kombi-Deklaration/Definition sähe dann in etwa so aus:
1
#ifndef CREATEVAR
2
extern
3
#endif
4
constuint16_tDIGITAL_OUT_PIN[DIGITAL_OUTn]
5
#ifdef CREATEVAR
6
=
7
{
8
EN_FILTER_PIN,EN_MODEM_PIN,EN_SENSOR_PIN
9
}
10
#endif
11
;
Ist das Makro CREATEVAR definiert, wird die Variable definiert
(erzeugt), andernfalls als extern deklariert (bekannt gemacht).
In der Moduldatei, in der die Definitionen erzeugt werden sollen, muss
entsprechend vor dem Include ein #define CREATEVAR stehen.
1
#define CREATEVAR
2
#include"config.h"
In allen anderen Modulen darf es nicht definiert sein.
Grüße
Stefan
Hallo Stefan.
Danke, ich habe die Lösung sogar schon gelesen. Das Problem daran ist
dass ich stets das ganze Projekt neu builden muss. Wenn ich nur ein
Modul kompiliere, dann muss ich das wieder manuell setzen. Wie siehts
denn mit den build variables aus (in Eclipse kann ich die ja in den
Projekteinstellungen setzen). Kann ich die aus dem code heraus zur
Compilerlaufzeit ändern?
dotm schrieb:> Das Problem daran ist> dass ich stets das ganze Projekt neu builden muss.> Wenn ich nur ein> Modul kompiliere, dann muss ich das wieder manuell setzen.
Ich bin mir nicht sicher, ob da nicht noch ein Missverständnis vorliegt.
Das Makro CREATEVAR wird innerhalb der Headerdatei nicht definiert. Da
wird nur auf eine vorhergehende Definition abgefragt. Um ein Modul, das
den Header verwendet, neu zu übersetzen wird da also nichts geändert.
Die Definition von CREATEVARS erfolgt in genau einer C-Datei, nämlich
der, in der die Deklaration der Variablen erfolgen soll (ich nenn sie
jetzt mal "config.c") und zwar direkt vor der #include-Anweisung für
config.h.
In allen anderen C-Dateien, die die Deklarationen ("extern ...")
verwenden sollen, wird es nicht definiert. Da steht nur ein #include
"config.h".
An der Build-Umgebung musst du für diese Technik gar nichts anpassen. Es
reicht, wenn dort die normalen Quelltextabhängigkeiten bekannt sind.
Grüße
Stefan
Stefan Wagner schrieb:> Das Makro CREATEVAR wird innerhalb der Headerdatei nicht definiert. Da> wird nur auf eine vorhergehende Definition abgefragt. Um ein Modul, das> den Header verwendet, neu zu übersetzen wird da also nichts geändert.
Deshalb verwendet man besser in jeder Datei einen eindeutigen Namen,
z.B. so:
1
// Test.c ----------------------------
2
#define SRC_Test_C_ // SRC + Dateiname
3
#include"Test.h"
4
// ...
5
6
// Test.h ----------------------------
7
#ifndef TEST_H_
8
#define TEST_H_
9
10
#ifdef SRC_Test_C_
11
#define SRC_Test_C_INIT
12
#else
13
#define SRC_Test_C_ extern
14
#endif // SRC_Test_C_
15
16
// **** hier kommen die Deklarationen / Definitionen hin
17
18
#endif // TEST_H_
und an der Stelle **** z.B.
1
SRC_Test_C_intvar1;
2
3
SRC_Test_C_intvar2
4
#ifdef SRC_Test_C_INIT
5
=15
6
#endif
7
;
Dann kann man auch in der .h-Datei gleich erkennen, zu welchem Modul die
Variablen gehören (falls das nicht auch schon aus dem Namen der
Variablen ersichtlich ist).
Die entsprechenden Templates für die *.h und *.c Dateien lassen ich mir
von der IDE (Eclipse) erzeugen.
Zum Teufel!!
Das ist doch abstrus! Erstmal ein Auszug aus dem Code:
config.c:
1
#define CREATEVAR
2
#include"PAC20_config.h"
3
4
USART_TypeDef*COM_USART[COMn]=
5
{
6
MODEM_PORT
7
};
config.h
1
#define COMn 1
2
3
typedefenum
4
{
5
MODEM=0,
6
}COM_TypeDef;
7
8
#define MODEM_PORT USART1
9
10
#ifndef CREATEVAR
11
externUSART_TypeDef*COM_USART;
12
#endif
modem.c
1
#include"config.h"
2
3
voidModemInit(COM_TypeDefCOM)
4
{
5
USART_Cmd(COM_USART[COM],ENABLE);//Fehler!
6
}
So! der Compiler sagt aber bei ModemInit (die Zeile wo ich Fehler dazu
geschrieben hab):
error: #167: argument of type "USART_TypeDef" is incompatible with
parameter of type "USART_TypeDef *"
Was zum Teufel ist da falsch?
Ich rufe ModemInit so auf:
ModemInit(MODEM);
die enum löst modemInit mit MODEM = 0 auf.
COM_USART ist ein Array aus Pointern und an Stelle Null steht MODEM_PORT
also USART1
Wo denk ich da fehlerhaft? Ein virtuelles wiener Schnitzel für
denjenigen der mir da weiterhelfen kann.
Das "CREATEVAR" ist hier nicht nur überflüssig, sondern sogar
kontraproduktiv. Ohne dem hätte der Compiler dir beim Übersetzen von
config.c gleich gesagt, dass da zwei COM_USART mit inkompatiblen Typen
sind.
DIGITAL_OUT_SPEED
Das ist von der Nomenklatur her eine Konstante, hier aber der Name eines
Arrays. Funktioniert latürnich, ist aber nicht schön ;-)
Edit: Ach so, hat Rufus Τ. Firefly ja schon geschrieben.