Wie organisiert ihr eure globalen Variablen ab einer mittleren Projektgröße ? Methode 1) Reservieren des Speicherplatzes in einer C Quelldatei mit häufiger Verwendeung derselben Variable. Alle anderen Quelldateien die darauf zugreifen möchten haben einen Header mit entsprechenden extern Deklarationen inkludiert. Methode 2) Variablen werden in einer eigenen global.c Quelldatei reserviert, welche dann zum Projekt hinzugelinkt wird. Alle Quelldateien welche darauf zugreifen, inkludieren dann eine Header Datei mit entsprechenden extern Deklarationen. Methode 3) um eine separate Pflege in verschiedenen Dateien bei Änderung einer Variable zu sparen gibt es noch diesen Ansatz: http://www.imb-jena.de/~gmueller/kurse/c_c++/c_globv.html Weitere Methoden - Vorteile - Nachteile ?
Ich mache das häufig folgendermaßen: 1) so wenig globale Variablen wie möglich 2) steht im Header:
1 | #ifdef main__
|
2 | #define EX
|
3 | #else
|
4 | #define EX extern
|
5 | #endif
|
3) globale Variablen werden dann in einem Header so deklariert: EX int variable; Der Header muss dann in main.c auch eingebunden werden. 4) in der main Datei steht dann einmal #define main__
die Methode von Klaus ist genau die vom Link. Für initialsierte Variablen haben die meisten Compiler Startup einen relativ ausgekocht kurzen Code. Das funktioniert dann mit initialisiergen Variablen nicht mehr bzw. gibt Warnungen beim Compilieren ?
hm, gute Frage mit der Initiallisierung... Wie wärs mit folgendem Hack:
1 | #ifdef main__
|
2 | #define EX
|
3 | #define INIT =
|
4 | #else
|
5 | #define EX extern
|
6 | #define INIT ;//
|
7 | #endif
|
Deklaration: EX int variable INIT 1; Ob das was für nen Obfuscated C Contest ist? ;)
J. V. schrieb: > Wie organisiert ihr eure globalen Variablen ab einer mittleren > Projektgröße ? Bei mir gibt es nur eine globale "Variable", das ist die einzige Instanz von Main, welche in Main.h als extern deklariert ist. Alle anderen globalen Resourcen sind nur ueber Main zugreifbar.
Die Variante 3 ist fast das schlimmste was ich je gesehen habe!!! Da bekommt man ja einen Brechreiz beim Lesen. Ich bevorzuge "Methode 1)". Da weiss man gleich zu welchem Modul die globale Variable hauptsächlich gehört und es gibt am wenigsten Probleme mit der Lesbarkeit.
Bislang konnte ich selbst bei großen Projekten globale Variablen immer vermeiden. Ein gutes Konzept was dabei hilft sind eigentlich Handles. Man kann dann die Funktionen in seinen C Modulen so auslegen, dass sie mit den Informationen in den Handles arbeiten. Die Handles selbst sind dann beispielsweise lokale Variablen der Funktion main().
> Ob das was für nen Obfuscated C Contest ist?
Leider nein - es tut nämlich nicht. Der Preprozessor streikt beim //,
d.h. er ersetzt INIT nur mit einem Strichpunkt und betrachtet die beiden
Schrägstriche als Kommentar zur #define Zeile. Zeigt auch bereits mein
Editor in der Einfärbung. Müsste es wohl sowas wie einen Trigraph oder
eine unsichtbare Klammer oder so was dafür geben. Selbst wenns ginge,
wäre wahrscheinlich das nächste Problem bei mehrzeiligen
Initialisierungen von Tabellen. (Vielleicht kehre ich doch zur Methode 2
zurück ?)
Im Übrigen gibt es bei Mikrokontrollern haufenweise Interrupts und
neuerdings genausoviel DMA Kanäle wo globale Variablen nützlich sind.
Dementsprechend mögen sich PC Programmierer nicht über die Verwendung
von global Variablen muckieren...
> Die Handles selbst sind dann beispielsweise lokale Variablen der > Funktion main() Und wo stecken die eigentlichen Daten?
Methode 3 hat auch den Nachteil, dass eine Initialisierung der Variablen nicht möglich ist, weil der Compiler extern und Initialisierung gleichzeitig anmeckern sollte. Der einzige "Vorteil" von Methode 3 ist, dass man die Zeile mit der Variable nur einmal schreiben muss. Im Zeitalter von Copy & Paste ist dieser Vorteil aber lächerlich.
>weil der Compiler extern und Initialisierung gleichzeitig anmeckern sollte.
mit -Wall gibts:
warning: 'TxBuffer' initialized and declared 'extern'
und später kotzt der Linker weil der Compiler das extern anstelle der
Initialsierung weggeschmissen hat:
.\objects\main.o:(.data+0x0): multiple definition of `TxBuffer'
.\objects\cpuinit.o:(.data+0x0): first defined here
Wenn man denn unbedingt diese "nur-einmal-schreiben"-Lösung haben möchte:
1 | #ifdef main__
|
2 | #define EX
|
3 | #define INIT(x) = (x)
|
4 | #else
|
5 | #define EX extern
|
6 | #define INIT(x)
|
7 | #endif
|
8 | |
9 | EX int i INIT(1); |
PS: Funktioniert dann aber auch nur für "simple" Initialisierungen.
klaus schrieb: > Bislang konnte ich selbst bei großen Projekten globale Variablen immer > vermeiden. Für Controllerprojekte können sie aber durchaus effektiver sein als das etagenweise Durchreichen von Zeigern.
>Für Controllerprojekte können sie aber durchaus effektiver sein als >das etagenweise Durchreichen von Zeigern. ich denke auch das globale variablen nicht immer nur schlecht sind. es hängt halt hauptsächlich an der disziplin des/der programmierer (ok, abgesehen von daten die man sich zerschießen kann durch z.b. einen zeiger der über ein array hinausschießt). methode 1) : so mache ich es auch immer. ist am zweckmäßigsten. ich benutze die extern-deklaration auch nur bei daten die wirklich nach außen hin freigegeben werden sollen, und alle anderen variablen die nur interne verwendung für ein modul finden sind nach außen hin nicht zugänglich. (ok da bin ich auch nicht immer so konsequent :-)), aber in den meisten fällen gibt es eh nur 2-3 module, inkl. des moduls in dem die daten definiert sind, die auf globale daten zugreifen müssen. methode 2) : die globalen daten in einer einzigen global.c zu organsieren halte ich für unzweckmässig, da man gerade wenn man bestimmte code-teile heraustrennen möchte immer die global.c anpacken muß, bzw. es erschwert die portierbarkeit einzelner module, da man immer in der global.c nachschauen muß welche variablen das betreffende modul denn verwendet. methode 3) : hab ich nicht so ganz kapiert ... ich weiß nicht ob der ansatz schon besprochen wurde. wenn mans ganz sauber machen will deklariert man in den entsprechenden modulen funktionen die es erlauben die daten des betreffenden moduls auszulesen bzw zu beschreiben. beispiel : modul.c : char data [100]; char data_read (char ptr) { return data [ptr]; // evtl. noch plausicheck von ptr } void data_write (char ptr, char data) { data [ptr] = data; // evtl. noch plausicheck von ptr und data } void data_do_irgendwatt (void) modul.h : char data_read (char ptr); void data_write (char ptr, char data); void data_do_irgendwatt (void); ein vorteil hier wäre das man plausi-checks machen kann und im fehlerfalle eine meldung ausgeben kann. außerdem verhindert man so das man sich durch falsche programmierung andere speicherbereiche zerschießt. es kostet aber auch entsprechend code. und je nachdem wieviele daten hat (und vor allem wenn man unterschiedliche datentypen hat, die ja alle über unterschiedliche funktionen implementiert werden müssen) kommt da einiges an funktionen (und somit overhead) zusammen. aber in mittleren bis großen projekten wo es nicht auf jedes byte ankommt ist das denke ich zu vernachlässigen. ich bin für methode 1 :-)
> PS: Funktioniert dann aber auch nur für "simple" Initialisierungen.
immerhin tut:
1 | #define countof(a) (sizeof(a) / sizeof(*(a)))
|
2 | #define TxBufferSize (countof(TxBuffer) - 1)
|
3 | EXTERN volatile uint8_t TxBuffer[] INIT("\n\rbla bla bla\n\r"); |
aber offensichtlich gibts keine wirkliche Patentlösung
J. V. schrieb: >> PS: Funktioniert dann aber auch nur für "simple" Initialisierungen. > immerhin tut: Ein einfaches Stringliteral ist eine simple Initialisierungen. Was ich mit "nicht simpel" meine, und was mit der Variante garantiert nicht funktioniert, sind Initialisierungen der Form {a,b,c} (die Kommas sind dann das Problem).
> die Kommas sind dann das Problem
Verstanden - Vielleicht gäbs dazu mit Variadic Macros noch einen Hack ?
(Besten Dank auch auf für --save-temp - damit sieht man was man
anrichtet)
> Und wo stecken die eigentlichen Daten?
Im Block auf der höchsten Ebene. Vom Prinzip her so:
1 | int main() |
2 | { |
3 | MODULE_HANDLE handle; // <-- hier |
4 | |
5 | Module_Init(&handle, "parameter", 42); |
6 | |
7 | Module_DoSomething(&handle, "kuckuck"); |
8 | } |
9 | |
10 | //... |
11 | |
12 | void Module_DoSomething(MODULE_HANDLE* arg_Handle, const char* x) |
13 | { |
14 | int x = handle->x; |
15 | AnotherModule_DoSomething(&(handle->anotherHandle), x); |
16 | } |
> Für Controllerprojekte können sie aber durchaus effektiver sein > als das etagenweise Durchreichen von Zeigern. Effizienz und Wartbarkeit steht oft im Widerspruch zueinander. Aber wenn die Frage bereits lautet: > Wie organisiert ihr eure globalen Variablen ab einer > mittleren Projektgröße ? dann würde ich eher dazu tendieren die Zahl der globalen Variablen zu überdenken anstatt diese "besser zu organisieren".
>dann würde ich eher dazu tendieren die Zahl der globalen Variablen zu >überdenken anstatt diese "besser zu organisieren". Es handelt sich um einen STM32 (Cortex-M3) und damit auch mein erstes Projekt auf einem 32 bit Prozessor wo ich bislang immer nur 8 bit Assembler geschrieben habe. Der STM32 hat 64 verschiedene Interrupt Service Funktionen welcher über Nestet Interrupt Vector Controller aufgerufen werden plus ein Dutzend gleichzeitig möglicher DMA Kanäle welchen man weder einen Zeiger über den den Stack zuschieben, noch die angezeigten Inhalte auf Plausibilität überprüfen kann. Bei DMA ohne MMU knallts bei Fehlern eben einfach und daher sind die globalen Variablen zum Datenaustausch das kleinste Übel.
> methode 3) : hab ich nicht so ganz kapiert ...
Sag ich doch. Die Lesbarkeit ist total bescheiden.
>Wie organisiert ihr eure globalen Variablen ab einer mittleren >Projektgröße ? Abschaffen. In C++ sowieso. In C die, die es unbedingt braucht, (Kommunikation mit ISR's, etc.) möglichst mit Zugriffsfunktionen kapseln. Wenn das nicht geht, exportiert das Modul die per header-Datei, zu welchem diese Variable gehört. > methode 3) ist schlicht albern. Oliver
>> Und wo stecken die eigentlichen Daten? > > Im Block auf der höchsten Ebene. Vom Prinzip her so: > > > int main() > { > MODULE_HANDLE handle; // <-- hier Ach so, dann haben wir an einander vorbeigeredet. Unter "Handle" verstand ich jetzt einen Integer, der innerhalb des Moduls dann z.B. als Index in ein Array mit den intern nötigen Verwaltungsinformationen verwendet wird. Ich wollte wissen, wo dieses Array dann stehen soll. Du nimmst aber eine Struktur als Handle, wo diese Daten direkt drin stehen. Geht natürlich auch, hat aber den Nachteil, daß es auf dem Stack liegt, was den RAM-Verbrauch schwerer einschätzbar macht. Der Verbrauch durch globale Variablen wird mir nach dem Compilieren direkt angezeigt. Übrigens sollte man meiner Meinung nach sowas:
1 | int x = handle->x; |
vermeiden und stattdessen das Handle immer nur über Funktionen benutzen. Der Benutzer des Moduls sollte sich dann überhaupt nicht dafür interessieren müssen, welche Elemente ein Handle hat, so wie das z.B. beim FILE* aus C oder beim pthread_t aus den POSIX threads ist.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.