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... */ }
> 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.
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.
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.
Nachtrag: In beiden Varianten wird die zweite Variable read-only exportiert.
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. :-)
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.