Forum: Compiler & IDEs Handhabung globaler Variablen


von J. V. (janvi)


Lesenswert?

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 ?

von Klaus (Gast)


Lesenswert?

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__

von J. V. (janvi)


Lesenswert?

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 ?

von Klaus (Gast)


Lesenswert?

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? ;)

von P. S. (Gast)


Lesenswert?

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.

von Globuli (Gast)


Lesenswert?

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.

von klaus (Gast)


Lesenswert?

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().

von J. V. (janvi)


Lesenswert?

> 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...

von Rolf Magnus (Gast)


Lesenswert?

> Die Handles selbst sind dann beispielsweise lokale Variablen der
> Funktion main()

Und wo stecken die eigentlichen Daten?

von Klaus F. (kfalser)


Lesenswert?

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.

von J. V. (janvi)


Lesenswert?

>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

von Stefan E. (sternst)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Rene B. (themason) Benutzerseite


Lesenswert?

>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 :-)

von J. V. (janvi)


Lesenswert?

> 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

von Stefan E. (sternst)


Lesenswert?

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).

von J. V. (janvi)


Lesenswert?

> 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)

von klaus (Gast)


Lesenswert?

> 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".

von J. V. (janvi)


Lesenswert?

>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.

von Globuli (Gast)


Lesenswert?

> methode 3) : hab ich nicht so ganz kapiert ...

Sag ich doch. Die Lesbarkeit ist total bescheiden.

von Oliver (Gast)


Lesenswert?

>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

von Rolf Magnus (Gast)


Lesenswert?

>> 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
Noch kein Account? Hier anmelden.