Forum: Compiler & IDEs (Über)lebensraum von Variablen...


von beta-frank (Gast)


Lesenswert?

Hey All,

hab mal 2 grundsätzliche Fragen:

1.
--------------------------
Mein Programm besteht aus mehreren C-Modulen. Jede Datei *.c includiert
dieselbe global.h. In der global.h ist u.a. eine Variable

int foo;

definiert.
Es funktioniert, diese (programmglobal?) von jedem Modul aus ohne

extern int foo;

etc. einfach so zu benutzen. Ist es Toleranz vom Compiler oder
legitim?

2.
---------------------------
Ist es ein Unterschied (Ausführungsgeschwindigkeit), ob Variablen in
einer ISR (static oder "volatile") defininiert werden oder
Modulglobal außerhalb der ISR?

Bsp.
char foo;
SIGNAL (SIG_OUTPUT_COMPARE3A){
   static char bar=0;
   char temp;
   foo = PINA;
   /* ...code... */
}

von Jörg Wunsch (Gast)


Lesenswert?

> In der global.h ist u.a. eine Variable

> int foo;

> definiert.

Definitionen sollten aber nicht in Headerdateien stehen, nur
Deklarationen (also extern int foo;).  Ein Objekt soll exakt einmal
definiert werden.

Dass es mit dem GCC dennoch funktioniert, hat eher historische
Ursachen.  Ganz früher[TM] gab es eine derartige Unterscheidung noch
nicht (vermutlich noch nichtmal ein »extern« Schlüsselwort), und der
Compiler hat sich daher im Rückgriff auf FORTRAN eines sogenannten
COMMON-Blocks bedient, um die dergestalt definierten Variablen
unterzubringen.  Der Linker überlagert dann alle COMMON-Blöcke mit
gleichem Namen, so daß sie im Executable denselben Speicher belegen.

Der GCC macht das heute noch so (by default -- man kann es
abschalten), da es historischen Code gibt, der sich darauf verläßt.
Meiner Erinnerung nach ist das aber seit C89 nicht mehr vom Standard
abgedeckt.  (Als unangenehmen Nebeneffekt kann man die Größe des .bss
nicht durch Aufsummieren der .bss der einzelnen .o-Dateien ermitteln,
sondern erst nach dem Linken am fertigen Executable.)

> Es funktioniert, diese ... einfach so zu benutzen. Ist es Toleranz
> vom Compiler oder legitim?

Fällt in dieselbe Kategorie ,,Historisches''.  Eine nicht
deklarierte
Variable wird als vom Typ `int' angenommen, eine nicht deklarierte
Funktion wird als einen `int' zurückgebend und eine beliebige Anzahl
von Argumenten übernehmend angenommen.

Sollte aber eine Warnung ergeben.  (Im Gegensatz zur mehrfachen
Definition von obendrüber: diese kann der Compiler nicht wirklich
feststellen, sondern erst der Linker, und für den ist es ein normales
Feature, COMMON-Blöcke zu überlagern.)

> Ist es ein Unterschied (Ausführungsgeschwindigkeit), ob Variablen in
> einer ISR (static oder "volatile") defininiert werden oder
> Modulglobal außerhalb der ISR?

Eine ISR ist erstmal eine ganz normale Funktion für den Compiler, die
außer einem speziellen Namen (der mit etwas Magie in gcrt1.S dann zum
Einbinden in die Interruptvektortabelle führt) keine sonstige
Besonderheit hat.

»static oder "volatile"« vergleicht Äpfel mit Geldstücken; beides
hat
überhaupt nichts miteinander zu tun.

Prinzipiell belegen alle globalen Variablen sowie alle innerhalb einer
Funktion als `static' deklarierte Variablen statischen Speicherplatz
(der Standard sagt, sie haben `static storage') und sind damit im
Zugriffsverhalten erstmal gleich.  Automatische Variablen verhalten
sich anders, da sie entweder gleich nur in einem Register geführt
werden oder aber als Offset in den Stack, so dass sich deren
Adressberechnung (und Adressierung) unterscheidet.

Dennoch kann es Unterschiede zwischen beiden Varianten geben: wenn die
Variable static storage innerhalb der Funktion hat, weiß der
Optimierer, dass sie nicht von außerhalb der Funktion zugegriffen
werden kann.  Damit können sich bestimmte Updates wegoptimieren
lassen, die andernfalls nötig wären, weil er bei einer globalen
Variablen den dümmsten Fall annehmen muss, dass sie eine externe
Funktion (die nicht zum aktuellen Übersetzungsmodul gehört) benötigt.

Eine als `volatile' markierte Variable (wobei `volatile' genau wie
`const' und in C99 `restricted' ein `type qualifier' genannt wird,
hat
also gar nichts damit zu tun, wo der Compiler die Variable speichern
lässt) hat generell das schlechteste Laufzeitverhalten, da der
Compiler explizit angewiesen wird, sie nach jeder Modifikation
zurückzuschreiben und sie vor jeder Abfrage von ihrer Speicherstelle
einzulesen.  Wenn man daher `volatile' zur Sicherstellung der
Kommunikation einer ISR mit dem Rest der Applikation braucht, aber die
entsprechende Variable innerhalb der ISR viel manipulieren muss, dann
lohnt es in aller Regel, sie temporär innerhalb der ISR in eine
(automatische, d.h. normalerweise ein Register) zu kopieren und sie
von dort erst vor dem Verlassen der ISR wieder zurückzuschreiben.

von Rufus T. Firefly (Gast)


Lesenswert?

1.
In das Headerfile gehört die Deklaration

extern int foo;

In genau ein Sourcefile gehört die Definition

int foo;

Der von Dir vorgeschlagene Weg würde in jedem Modul (Sourcefile), das
das Headerfile einbindet, eine neue Instanz einer globalen Variablen
namens foo anlegen - das mag der Linker gar nicht.

Bei den von Dir verwendeten Dateinamen etc. empfiehlt es sich, die
globale Variable foo in einem Sourcefile namens global.c zu definieren
- prinzipiell ist es empfehlenswert, zu jedem Sourcefile ein
gleichnamiges Headerfile anzulegen, in dem alle zugehörigen (externen)
Definitionen stehen.

Desweiteren ist es IMHO ratsam, exportierten Symbolen eines Modules den
Modulnamen als Präfix zu verpassen - dann kann man im Sourcecode leicht
sehen, daß hier auf eine Variable zugegriffen wird oder eine Funktion
aufgerufen wird, die in einem anderen Modul steht.
Aus foo würde dann
global_foo

Aber das ist eine stilistische Glaubenssache.


2.
Die Ausführungsgeschwindigkeit wird das nicht zwingend betreffen; wenn
aber auf Variablen aus einer ISR und anderen Stellen des Programmes
zugegriffen wird, empfiehlt es sich dringeng, "volatile" zu
verwenden, da sonst der Compiler nicht daran gehindert wird, für die
Variable innerhalb einer Schleife einen konstanten Wert anzunehmen, wie
beispielsweise hier:

ISR:
   ...
   meinevariable = 1;
   ...

Sonstiges Programm:

   meinevariable = 0;
   ...
   while (meinevariable != 1);
   machwas();
   ...

Das while-Statement würde vom Compiler wegoptimiert, da er nicht weiß,
daß die Variable sich in einem anderen Kontext verändern kann. Dies
zeigt "volatile" an.

Ob die Variable nun static innerhalb eines Modules ist oder aber auf
Linkerebene sichtbar ist, hat auf die Ausführungsgeschwindigkeit
hingegen keinerlei Einfluss.

von Rolf (Gast)


Lesenswert?

Zu 1. gibt es unterschiedlichste Meinungen; ich bevorzuge die Variante
eine exportierte Variable in einer Header-Datei in einem Block zu
definieren und deklarieren:

#ifdef EXAMPLE_C
  signed int i_foo = 4711;
  uint32_t i_bar = 1580;
#else
  extern signed int i_foo;
  extern const uint32_t i_bar;
#endif


Es gibt auch diese Variante:

#ifdef EXAMPLE_C
#   define EXT
#   define CONST
#else
#   define EXT    extern
#   define CONST  const
#endif

EXT int i_foo;
CONST int i_bar;

#undef EXT
#undef CONST

Für beide muß am Anfang von example.c

#define EXAMPLE_C

stehen, so wie man es von der Kapselung von Header-Dateien kennt.
Die zweite Variante hat den Vorteil, dass Definition und Deklaration in
nur einer Zeile sind, aber den Nachteil, dass man dort nicht
Initialisieren kann. Falls man nicht zu initialiseren braucht, ist das
letztere besser.

Diese beiden Varianten haben den Vorteil, dass man eine Änderung,
beispielsweise des Datentyps, in nun einer Datei, in der letzten
Variante sogar nur in einer Zeile, vornehmen muß und zumindest bei der
   zweiten Variante immer Konsistenz hat.
Ansonsten müßte man bei größeren Projekten ja erstmal die Definition
und dann noch dutzende bis hunderte Deklarationen ändern.

von Rolf (Gast)


Lesenswert?

Nachtrag: In beiden Varianten wird die zweite Variable read-only
exportiert.

von Jörg Wunsch (Gast)


Lesenswert?

Naja, so viele unterschiedliche Meinung gibt es darüber nicht.
Es gibt viele stilistische Details darüber, aber Definitionen
von Objekten in Headerdateien unterzubringen ist eher unüblich.

Besonders gefährlich finde ich die Suggestion, `extern' (eine storage
class) und `const' (einen type qualifier) miteinander irgendwie in
Zusammenhang zu bringen: beide haben miteinander nichts, aber auch gar
nichts zu tun, und es gibt bereits genügend Leute, die damit nicht
klar kommen, als dass man sowas noch zur stilistischen Frage erheben
sollte.

`const' sollte in keinem Falle irgendwo wegdefiniert werden (mit
der
einzigen Ausnahme: wenn man Code wirklich noch prähistorisch zum alten
K&R-C rückwärtskompatibel pflegen muss).

extern const int foo;

ist eine durchaus praktikable Deklaration für ein Objekt, dessen
Definition/Initialisierung dann z. B. mit

const int foo = 42;

in genau einer Datei erfolgen kann/muss.  Im Falle von const ist
übrigens die Initialisierung zwingend, da alles andere sinnlos wäre.
OK,

const int zero;

wäre noch sinnvoll. :-)

von nobody0 (Gast)


Lesenswert?

Also durch das ifdef wird die Definition nur in einer einzigen .c-Datei
vorgenommen (nachdem inkludiert wurde); dafür wird ja der Präprozessor
genommen.

Naja, die zweite Variante ist nicht ganz sauber, da hast Du recht!
Die habe ich von einem Quick-and-Dirty-Programmierer in der
Original-Version von Grundig kopiert ohne sie zu checken; vielleicht
ist Grundig ja wegen solcher Sachen pleite gegangen ;-)

Ein richtiger ANSI-C Compiler wie z. B. der gcc akzeptiert die zweite
Variante natürlich nicht, weil damit in einiger Datei

int i_bar;

und in einer anderen Datei

const int i_bar;

steht; kranke Compiler wie die von IAR akzeptieren diesen Unsinn (wobei
interessant wäre zu wissen was denn rauskommt), aber nach ANSI ist das
ein Fehler, der gemeldet werden muß; beispielsweise so:

blah.c:123: conflicting types for `i_foo'
inc/blah.h:105: previous declaration of `i_foo'
make: *** [blah.o] Fehler 1

Die richtige Version ist deshalb diese:

#ifdef EXAMPLE_C
#   define EXT
#   define CONST
#else
#   define EXT    extern
#   define CONST  extern const
#endif

EXT int i_foo;
CONST int i_bar;

#undef EXT
#undef CONST


Mit war das früher nicht aufgefallen, weil ich selber nur die erste
Variante verwendet habe.

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.