Forum: PC-Programmierung Den Befehl extern


von Gernot (Gast)


Lesenswert?

Grüße!

Ich hätte ein Frage zu dem Befehl "extern".

In einigen Header - Dateien hab ich jetzt den Befehl verwendet, meistens 
in Verbindung mit einer if - Abfrage, und würde nun gerne wissen was 
dieser Befehl genau bewirkt.

Beispiel:
1
#ifdef Dateienname
2
#define EXTERN
3
#else
4
#define EXTERN extern
5
#endif
6
7
//später kommt dann das:
8
9
EXTERN void protocol_data_put(char variable);
10
EXTERN void protocol_data_get(char variable);
11
EXTERN unsigned char variable;

Jetzt hab ich zwar eine Vermutung wie der Befehl wirkt / was er macht da 
ich aber nirgends dazu was gefunden habe würde ich euch gerne um Rat 
fragen.

Dank im voraus

von Stefan B. (stefan) Benutzerseite


Lesenswert?

> #ifdef Dateienname
http://www.mikrocontroller.net/articles/Include-Files_%28C%29#Probleml.C3.B6sung_mit_Makros

> EXTERN void protocol_data_get(char variable);
Frage 10.8
http://www.dclc-faq.de/kap10.htm

> EXTERN unsigned char variable;
Frage 10.7
http://www.dclc-faq.de/kap10.htm

von Karl H. (kbuchegg)


Lesenswert?

Hmpf.
Da muss man weiter ausholen. Ich rate dir dringend zu einem
C-Buch. Da sind solche und ähnliche Dinge ausführlichst erklärt.

Aber was solls.

Zunächst mal musst du 2 Begriffe unterscheiden lernen:
  * Definition
  * Deklaration

Was ist was?
Kurz gefasst und etwas vereinfacht gesagt, ist eine Definition alles
was im endgültig Programm Speicherplatz verbraucht. Eine Deklaration
hingegen, teilt dem Compiler lediglich zur geschätzten Kenntnisnahme
mit, dass Etwas irgendwo im Programm existiert und wie dieses Etwas
aussieht.
1
int main()
2
{
3
  int j;
4
}

Das 'int j;' ist eine Definition, da der Compiler hier Speicherplatz
reservieren muss, um darin die Variable j unterzubringen. Aber auch
die komplette Funktion main() ist an dieser Stelle eine Definition,
da der Compiler ja dafür Code generieren muss und dieser Code im
fertigen Programm Speicher verbraucht.

Ein anderes Beispiel:
1
void foo( void );     // <----- A
2
3
int main()            // <----- B
4
{
5
  foo();              // <----- B1
6
}
7
8
void foo()            // <----- C
9
{
10
  // Irgendwas
11
}

An den markierten Zeilen B und C beginnt jeweils eine Definition,
denn der Compiler muss ja Code für die Funktionen main() und foo()
generieren. Aber was ist das an Zeile A?
Das ist eine Deklaration. Diese Zeile (auch 'Prototyp' genannt)
teilt dem Compiler lediglich mit, dass es irgendwo im Programm
eine Funktion namens foo() gibt. Der Compiler nimmt dies zur
Kenntnis und trägt sich in seine internen Tabellen ein:
Es gibt eine Funktion foo. Diese Funktion liefert kein Ergebnis
und will auch keine Argumente haben.

Wozu braucht der Compiler diese Dekleration?

Er braucht sie um in Zeile B1, den Funktionsaufruf überprüfen zu
können. Der Compiler liest das Programm von oben nach unten durch.
Wenn er an die Zeile B1 stösst, dann hat er die Funktionsdefinition
von foo() noch nicht gesehen. Ohne die Deklaration in A, würde der
Compiler also nicht wissen, dass foo überhaupt existiert. Eine
Fehlermeldung wäre die Folge, denn das könnte ja auch ein Tippfehler
sein. Die Deklaration in A behebt dieses Problem: Dadurch weiss der
Compiler Bescheid, dass es sich um keinen Tippfehler handelt, dass
also foo tatsächlich existiert, dass es keinen Wert zurückliefert
(damit wäre automatisch eine Verwendung in der Form   i = foo()
illegal) und das es keine Argumente haben möchte (was jegliche
Verwendung in der Form foo(4); als fehlerhaft qualifiziert).
Das ist alles. Mehr braucht der Compiler nicht um zu überprüfen,
ob der Funktionsaufruf, so wie ihn der Programmierer geschrieben
hat, auch gültig ist.

Soweit so gut. Bei Funktionen kann der Compiler immer zwischen
Definition und Deklaration unterscheiden. Etwas in der Form
1
void bar()
2
{
3
  ...
4
}
ist immer eine Definition.
Etwas in der Form
1
void bar();
ist immer eine Deklaration.

Aber wie ist das mit Variablen?
Nun, da ist das nicht so einfach zu unterscheiden.
1
int j;
könnte beides sein, je nachdem wo und wie es eingesetzt wird.
Um da eine Abhilfe zu schaffen, gibt es das Schlüsselwort 'extern'.
extern wandelt eine Definition zu einer Deklaration.
1
extern int a;  // Das ist eine Deklaration. Dem Compiler wird
2
               // mitgeteilt, dass es irgendwo eine globale
3
               // Variable namens 'a' gibt und dass es sich dabei
4
               // um einen int handelt
5
6
int b;         // Das ist eine Definition. Der Compiler wird
7
               // angewiesen, hier und jetzt, an dieser Stelle
8
               // eine Variable namens b im Code vorzusehen.

Warum ist nun diese Unterscheidung wichtig?
Weil es in C die sog. 'One definition rule' gibt. Sie besagt
schlicht und einfach, dass jedes Teil, egal ob Variable oder
Funktion nur ein einziges mal definiert werden darf. Es darf
aber beliebig viele Deklarationen darauf geben, solange nur
alle Deklarationen mit der Definition in den Datentypen
übereinstimmen.

Man benutzt das ganze zb so:
In einer *.c Datei, wird eine Variable definiert
1
// main.c
2
int a;
3
int b;
4
5
int main()
6
{
7
  ...
8
}

und beliebig viele andere *.c Dateien können sich auf diese
Variable beziehen, indem sie Deklarationen darauf enthalten
1
// foo.c
2
extern int a;
3
extern int b;
4
5
void foo()
6
{
7
  ...
8
}

Auch umgekehrt wird das Verfahren eingesetzt. Damit aus main.c
heraus die Funktion foo in foo.c aufgerufen werden kann, muss es
eine Deklaration dieser Funktion in main.c geben. Es schadet nichts
bei Funktionsprototypen ebenfalls ein extern zu schreiben, auch
wenn es nichts bewirkt, der Compiler kann ja bei Funktionen immer
erkennen, ob das jetzt eine Definition oder eine Deklaration ist.
1
// main.c
2
int a;
3
int b;
4
5
extern void foo( void );  // Das extern ist an dieser Stelle nicht
6
                          // wirklich notwendig. Schadet aber auch
7
                          // nicht.
8
9
int main()
10
{
11
  foo();
12
}

2 Dinge sind nocht (fürs erste) wichtig:
* Eine Definition ist immer auch eine Deklaration. Das ist auch
  logisch, denn durch eine Definition wird dem Compiler ja auch
  mitgeteilt wie Dinge aussehen.
1
void foo()    // Diese Definition ist automatisch auch eine
2
{             // Deklaration ....
3
}
4
5
int main()
6
{
7
  foo();      // .... damit der Compiler hier die Funktionsparameter
8
              // der Funktion foo auch in seinen internen Tabellen
9
              // wiederfindet
10
}

* Besitzt eine Variable eine Initialisierung, dann handelt es sich
  immer um eine Definition. Im Zweifelsfall wird in so einem Fall
  das Schlüsselwort 'extern' ignoriert
1
extern int a = 5;      // Das ist keine Deklaration, sondern eine
2
                       // Definition

Nachtrag zur ODR (One definition rule):
Wird sie verletzt, dann gibt es eine Fehlermeldung.
Das hier:
1
// main.c
2
int a;
3
4
int main()
5
{
6
  ...
7
}
1
// foo.c
2
int a;
3
4
void foo()
5
{
6
  ...
7
}

ist also nicht zulässig, da es 2 Definitionen für a im gesamten
Programm gibt.

genausowenig wäre das hier zulässig (auch wenn es dein Compiler
bzw. Linker nicht prüfen kann):
1
// main.c
2
int a;
3
4
int main()
5
{
6
  ...
7
}
1
// foo.c
2
extern long a;
3
4
void foo()
5
{
6
  ...
7
}

Es gibt zwar nur eine Definition für a, aber die Deklaration in
foo.c stimmt nicht mit der Definition überein.

von Gernot (Gast)


Lesenswert?

Vielen Dank für die ausführlichen Antworten, ich glaub das hab ich jetzt 
verstanden.

Noch mal herzlichen Dank.

von Icke M. (Firma: my-solution) (hendi)


Lesenswert?

Hab den Artikel grad per Google gefunden und ich muss sagen: Danke Karl 
Heinz, sehr geil erklärt. Ich glaub jetzt hab ich das Prinzip auch 
geschnallt. Nach K/R besagt extern, das Speicherplatz an anderer Stelle 
belegt wird, bei Fkt. wird das also implizit gemacht, durch die 
Unterscheidung, dass erklärt noch mal einiges.
Damit kann ich dann wohl endlich den Linkerfehlern begegenen.
Eine Frage hätte ich noch, bewahrt mich eigentlich ein
1
#ifndef HEADER_H
2
# define HEADER_H
3
...
4
#endif
in einer Headerdatei normalerweise wirklich vor doppelten 
Definitionen(den Unterschied habe ich nun auch endlich kapiert...), bei 
Mehrfacheinbindungen, oder sollte man lieber den Weg über extern gehen? 
Also wenn man z.B. statische Arrays mit nem String hat, die in einer 
File genutzt werden, dann werden die Strings, so im Header, ja auch 
wieder neu zugewiesen, bzw die Arrays ohnehin neu definiert. Mit dem 
Schalter würde das theoretisch nicht passieren, oder? Aber was ist mit 
den Deklarationen, sind die dann im zweiten einbindenden *.c trotzdem 
bekannt?
An der Stelle noch die Anmerkung, wer KEIL mit Realview benutzt, da 
funktioniert das mit dem Schalter nicht. Dort wird ein definiertes Label 
für jede neue *.c erstellt. Die Workarounds kann man sich hier 
http://www.realview.com*dot*cn/support/kb.asp?ID=834 anschauen(dot 
durch nen punkt ersetzen ersetzen, wurde komischerweise als Spam 
erkannt, isses aber nich). Die erste Möglichkeit funzt bei mir nicht, 
deswegen mach ich es jetzt ordentlich. Ich wollte es nur anmerken, falls 
sich noch andere wundern.

von Karl H. (kbuchegg)


Lesenswert?

Icke Muster wrote:
> Eine Frage hätte ich noch, bewahrt mich eigentlich ein
>
1
> #ifndef HEADER_H
2
> # define HEADER_H
3
> ...
4
> #endif
5
>
> in einer Headerdatei normalerweise wirklich vor doppelten
> Definitionen(den Unterschied habe ich nun auch endlich kapiert...),

Nein, das bewahrt dich nicht davor.
Diese Include Guards bewahren dich nur vor Problemen, die
bei der Übersetzung eines Source Code Files entstehen.

Wenn dein Programm aber aus mehreren *.c Files besteht, dann
hilft dir der Include Guard nichts, weil ja jedes *.c File
für sich alleine übersetzt wird.

Worum gehts bei den Include Guards?
Wenn Programme größer werden, dann kommt es schon mal vor, dass
ein Header File in ein *.c File mehrfach includiert wird.
Das passiert natürlich nicht so:
1
#include "foo.h"
2
#include "foo.h"
3
#include "foo.h"
4
5
int main()
6
{
7
}

sondern etwas subtiler zb so:
1
// File: main.c
2
#include "test1.h"
3
#include "test2.h"
4
5
int main()
6
{
7
}
1
// File: test1.h
2
#include "globals.h"
3
4
int InputBuffer[MAX_INPUT];
5
// noch andere Dinge
1
// File: test2.h
2
#include "globals.h"
3
4
int OutputBuffer[MAX_OUTPUT];
5
// noch andere Dinge
1
// File: globals.h
2
3
#define MAX_INPUT  10
4
#define MAX_OUTPUT 20

Hier wird, wenn main.c compiliert wird, die Datei globals.h zweimal
includiert. Einmal über den Umweg über File1.h und einmal über File2.h

Einmal den Text in globals.h einbinden reicht aber. Und genau dafür
sorgt der Include Guard. Wenn globals.h das erste mal includiert wird,
dann ist das Makro noch nicht definiert, er #ifndef schlägt nicht fehl
und der weitere Rest des Files wird abgearbeitet. Unter anderem enthält
diese Abarbeitung die Definition genau dieses Makronamens.

Wenn der Präprozessor dann das zweite mal den include durchführt
dann bindet er genauso den Quelltext ein, aber diesmal geht der
#ifndef schief, denn ein Makro mit diesem Namen gibt es ja bereits.
Es wurde erzeugt als dieses Header File das erste mal eingebunden
wurde. Dadurch das der #ifndef aber fehl schlägt, wird dann der
Rest dieser Datei nicht mehr eingebunden.

> Mehrfacheinbindungen, oder sollte man lieber den Weg über extern gehen?

Sollte ist der falsche Ausdruck. Du musst
Es gibt keinen anderen Weg, ausser einer compilerspezifischen
Erweiterung, so dass sich der Linker nicht mehr daran stört.

> Also wenn man z.B. statische Arrays mit nem String hat, die in einer
> File genutzt werden, dann werden die Strings, so im Header, ja auch
> wieder neu zugewiesen, bzw die Arrays ohnehin neu definiert. Mit dem
> Schalter würde das theoretisch nicht passieren, oder? Aber was ist mit
> den Deklarationen, sind die dann im zweiten einbindenden *.c trotzdem
> bekannt?
> An der Stelle noch die Anmerkung, wer KEIL mit Realview benutzt, da
> funktioniert das mit dem Schalter nicht. Dort wird ein definiertes Label
> für jede neue *.c erstellt.

Ich denke du hast eine etwas falsche Vorstellung wie das funktioniert.
Jedes einzelne *.c File wird für sich alleine compiliert.
Wenn du also in deinem Gesammtprogramm ein a.c und ein b.c hast,
dann wird a.c compiliert. Die Include Guards greifen und verhindern,
dass bei der Mehrfachinclusion bei der Compilierung von a.c
irgendetwas abwegiges passiert.
Aber dann wird b.c compiliert. Der Compiler geht aber wieder mit
jungfräulichem Wissen an diese Compilierei. Was immer er auch
durch das compilieren von a.c gelernt wurde, es wird alles verworfen
und wieder bei 0 angefangen. Dass muss auch so sein. Denn eine
der Designprinzipien von C war es, dass jede Übersetzungseinheit
(so nennt man ein *.c) für sich alleine compiliert werden kann
und zwar unabhängig davon, was vorher oder nachher compiliert wird.
Bei 1000 *.c Files möchte ich schliesslich nur die neu compiliert
haben, in denen es auch eine Änderung gab und nicht alle neu,
nur weil der Compiler aus dem compilieren von a.c Wissen erlangt
hat, das er für b.c benötigt.

von Severino R. (severino)


Lesenswert?

Ich will jetzt nicht Verwirrung stiften, aber es gibt offenbar Compiler, 
welche Modul-Übergreifend compilieren. Der Sinn davon ist eine bessere 
Optimierung.
z.B.:
http://www.htsoft.com/products/OCG.php

Aber dies ändern nichts an den sehr guten Ausführungen von Karl heinz.

von Karl H. (kbuchegg)


Lesenswert?

Severino R. wrote:
> Ich will jetzt nicht Verwirrung stiften, aber es gibt offenbar Compiler,
> welche Modul-Übergreifend compilieren. Der Sinn davon ist eine bessere
> Optimierung.
> z.B.:
> http://www.htsoft.com/products/OCG.php

Dagegen ist auch nichts zu sagen. Der Compiler kann machen
was er will, solange er die 'As if' Regel einhält. Das heist
er kann nach Herzenslust herumoptimieren, solange sich das
Programm insgesammt noch immer so verhält, wie es der C-Standard
fordert.

von Icke M. (Firma: my-solution) (hendi)


Lesenswert?

Ok, alles klar, dann war ich wohl mächtig auf dem Holzweg. Mir ist 
eigentlich schon klar, dass alle einzeln übersetzt werden können, wenn 
du es jetzt so sagst, weiß ich auch nich genau, was ich mir dabei 
gedacht hab. Dann verhält sich auch RealView völlig normal. Also machen 
in einer Headerdatei nur Variablen Sinn, die man global benutzen möchte 
und vll noch defines, die zu Konfigurationszwecken dienen. 
Methodenköpfe, der Übersicht wegen, aber mehr eigentlich nicht, oder? Es 
macht ja eigentlich auch keinen Sinn z.B. eine int Variable im Header 
nur zu deklarieren, wenn man sie am Anfang des C-Files eh initialisiert 
und damit implizit deklariert und definiert. Oder seh ich das falsch? 
Sorry für die vermeintlich blöden Fragen, aber ich hab scheinbar an der 
Stelle noch keinen richtigen Zugang.

von Karl H. (kbuchegg)


Lesenswert?

Icke Muster wrote:
> Also machen
> in einer Headerdatei nur Variablen Sinn, die man global benutzen möchte
> und vll noch defines, die zu Konfigurationszwecken dienen.
> Methodenköpfe, der Übersicht wegen, aber mehr eigentlich nicht, oder?

Strukturdefinitionen, Typedefs, Enums.

Ich seh das so:
Jedes *.c File (manchmal auch mehrere davon) bildet ein 'Modul'.
Ein Modul ist eine 'Baugruppe' die ein Problem löst.
Für diese Baugruppe gibt es ein Header File, in dem alles
drinnen steht, was ein anderer Programmierer benötigt um mit
dieser Baugruppe arbeiten zu können.

Dinge, die nur für ein spezifisches *.c interessant sind, gehören
nicht ins Header File, sondern werden direkt im *.c definiert.

Beispiel:
Ein Modul für einen Ringbuffer.
So ein Ringbuffer benötigt ja eine Speicherfläche auf der er
operieren kann. Ins Header File oder ins Source File?

Von aussen soll und darf sich niemand an diesem Buffer vergreifen.
Ganz im Gegenteil, das hat niemanden zu ineterssieren, wie ich diesen
Buffer verwalte. Daher ganz klar ins Source File. Im Header File
stehen nur die Dinge, die jemand wissen muss, der mit diesem
Modul arbeitet. Das sind in erster Linie natürlich die Prototypen für 
die Funktionen, die dieses Modul anbietet:

1
// Ringbuffer.h
2
//
3
#ifndef RINGBUFFER_H_INCLUDED
4
#ifndef RINGBUFFER_H_INCLUDED
5
6
bool EnqueueByte( unsigned char Value );
7
bool EnqueueInt( int Value );
8
bool EnqueueDouble( double Value );
9
bool EnqueueString( const char* Value );
10
bool EnqueueMem( void* Value, size_t ValueSize );
11
12
bool DequeueByte( unsigned char* Value );
13
bool DequeueInt( int* Value );
14
bool DequeueDouble( double* Value );
15
bool DequeueString( const char* Value, size_t MaxStringSize );
16
bool DequeueMem( void* Value, size_t ValueSize );
17
18
bool IsEmpty();
19
20
#endif
1
// Ringbuffer.c
2
//
3
#include "Ringbuffer.h"
4
5
#define STORAGE_SIZE  512
6
7
static unsigned char Buffer[ STORAGE_SIZE ];
8
static size_t Used;
9
10
....

Da das Ringbuffer-Modul keinerlei eigene Datentypen vorhält, über die 
der aufrufende Code etwas wissen müsste und die als Funktionsargument 
irgendwo vorkommen, ist damit das Header File schon fertig.
Ein anderer Programmteil, weilcher den Ringbuffer benutzen möchte, 
includiert einfach nur das Header File
1
#include "Ringbuffer.h"
2
3
int main()
4
{
5
  EnqueueInt( 5 );
6
  EnqueueInt( 7 );
7
  ...
8
}
und kann damit die Funktionen benutzen. Und das (die Funktionen) ist 
alles was er von meinem Ringbuffer Modul wissen muss. Im speziellen muss 
er nicht wissen, dass ich die Zahlen die er mir übergibt in einem Array 
namens 'Buffer' ablege. Muss er nicht wissen und weiß er auch nicht. 
Denn im Header File ist dieses Array ja nicht enthalten.


> macht ja eigentlich auch keinen Sinn z.B. eine int Variable im Header
> nur zu deklarieren, wenn man sie am Anfang des C-Files eh initialisiert
> und damit implizit deklariert und definiert.

Du meinst, wenn diese Variable keine Globale ist?
In dem Fall hat sie in einem Header File nichts verloren.

von Icke M. (Firma: my-solution) (hendi)


Lesenswert?

Gut, dann hab ich das jetzt soweit, glaub ich, verstanden. Hab meine 
Dateien jetzt so weit angepasst.
Danke dir noch mal für die Erklärung!!!

von Simon K. (simon) Benutzerseite


Lesenswert?

Karl heinz Buchegger wrote:
> Ich seh das so:
> Jedes *.c File (manchmal auch mehrere davon) bildet ein 'Modul'.
> Ein Modul ist eine 'Baugruppe' die ein Problem löst.
> Für diese Baugruppe gibt es ein Header File, in dem alles
> drinnen steht, was ein anderer Programmierer benötigt um mit
> dieser Baugruppe arbeiten zu können.
>
> Dinge, die nur für ein spezifisches *.c interessant sind, gehören
> nicht ins Header File, sondern werden direkt im *.c definiert.

Das sehe ich ganz genau so, Karl Heinz. Aber solltest du das #define für 
die Puffergröße nicht lieber in die Header-Datei tun? Sonst kann der 
externe Programmierer ja gar nicht die Speichergröße verändern.

von Karl H. (kbuchegg)


Lesenswert?

Simon K. wrote:
> Das sehe ich ganz genau so, Karl Heinz. Aber solltest du das #define für
> die Puffergröße nicht lieber in die Header-Datei tun? Sonst kann der
> externe Programmierer ja gar nicht die Speichergröße verändern.

Das kann man so machen. Ja.
Ich kann aber auch die andere Auffassung vertreten und sagen
dass diese Speichergröße fix ist. Das wäre zb. angebracht, wenn
dieses Ringbuffer Modul Teil einer Library ist. Denn dann bringt
es ja klarerweise nichts, wenn der Anwendungsprogrammierer zwar
den #define ändert und auch mit einer geänderten Buffergröße
rechnet aber die Library nicht neu gebaut hat.

Zb. sind die diversen Anfragen legendär, die sich damit
beschäftigen, warum der Compiler eine Veränderung von RAND_MAX
nicht korrekt umsetzt :-)

Langer Rede kurzer Sinn: Natürlich gibt es auch Grauzonen. Das
ist überhaupt etwas was man akzeptieren muss: Es gibt keine
100% Regeln in der Programmierung. Es kann, wird und soll
auch immer die Grauzone geben, die man so oder so sehen kann
und die man je nach konkreten Anforderungen dehnen kann.
Ist für mich einer der Gründe, warum Programmierung so schwer
ist. Neulinge habn gerne fixe Regeln: Zuerst machst du dieses,
dann machst du jenes und wenn du dich stur an dieses Kochrezept
hältst, dann hast du am Ende ein fehlerfreies Programm.
Nur leider spielts das in der Praxis nicht.
Was nicht heissen soll, dass es nicht Grundsätze gibt, die sich
bewährt haben und die sinnvoll sind. Man muss aber immer auch
einen gewissen Freiraum zulassen. Programmieren hat (auf diesen
Aspekt reduziert) sehr viel mit Kunst gemeinsam.

von Simon K. (simon) Benutzerseite


Lesenswert?

Karl heinz Buchegger wrote:
> Ich kann aber auch die andere Auffassung vertreten und sagen
> dass diese Speichergröße fix ist. Das wäre zb. angebracht, wenn
> dieses Ringbuffer Modul Teil einer Library ist. Denn dann bringt
> es ja klarerweise nichts, wenn der Anwendungsprogrammierer zwar
> den #define ändert und auch mit einer geänderten Buffergröße
> rechnet aber die Library nicht neu gebaut hat.

Gut, das kann man so sehen. Ich dachte jetzt eher an einen Fall, wo das 
Ringbuffer Modul beispielsweise für das UART in einem AVR benutzt wird.
Bei so einem Projekt wird ja eine veränderte Ringbuffer.h/c direkt neu 
kompiliert. (Im Gegensatz zu einer festen Library)

> Zb. sind die diversen Anfragen legendär, die sich damit
> beschäftigen, warum der Compiler eine Veränderung von RAND_MAX
> nicht korrekt umsetzt :-)

> Langer Rede kurzer Sinn: Natürlich gibt es auch Grauzonen. Das
> ist überhaupt etwas was man akzeptieren muss: Es gibt keine
> 100% Regeln in der Programmierung. Es kann, wird und soll
> auch immer die Grauzone geben, die man so oder so sehen kann
> und die man je nach konkreten Anforderungen dehnen kann.

Absolut.

> Ist für mich einer der Gründe, warum Programmierung so schwer
> ist. Neulinge habn gerne fixe Regeln: Zuerst machst du dieses,
> dann machst du jenes und wenn du dich stur an dieses Kochrezept
> hältst, dann hast du am Ende ein fehlerfreies Programm.

Sehe ich (fast) jeden Tag in der Schule. Fürs Programmieren braucht man 
halt eine bestimmte (erleichternde) Denkweise.

> Was nicht heissen soll, dass es nicht Grundsätze gibt, die sich
> bewährt haben und die sinnvoll sind.

Glücklicherweise gibt es die. Das fängt ja schon damit an Variablen 
geeignet zu prefixieren. Oder Techniken um Algorithmen zu 
implementieren.

> Man muss aber immer auch
> einen gewissen Freiraum zulassen. Programmieren hat (auf diesen
> Aspekt reduziert) sehr viel mit Kunst gemeinsam.

Das sehe ich übrigens ganz genauso, leider ließ sich bis jetzt kaum 
jemand davon überzeugen. Das ist dann direkt Kunst-Blasphemie sowas zu 
behaupten...

von Clemens Eisserer (Gast)


Lesenswert?

@Karl: Danke, bin auch gerade über Google dazugestoßen - auf der suche 
nach verständlichen Erklärungen erklärungen für extern.
Danke für die Hilfte!

lg Clemens

von Martin E (Gast)


Lesenswert?

Ich bin soeben per Google auf diese Seite gestoßen und als nicht Neuling 
im Programmieren habe ich endlich vollends begriffen wie sich 
Definitionen und Deklarationen unterscheiden. Karl Heinz hat das 
wirklich super erklärt und auch bestimmt einige zeit hier gelassen. Ich 
bedanke mich dafür.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

> #ifdef Dateienname
> #define EXTERN
> #else
> #define EXTERN extern
> #endif
>
> //später kommt dann das:
>
> EXTERN void protocol_data_put(char variable);
> EXTERN void protocol_data_get(char variable);
> EXTERN unsigned char variable;

Leider ist diese Coding-Style Sünde immer noch im Tutorial :-(

Bitte reduzieren Sie die Anzahl der Zitatzeilen.

von Siro (Gast)


Lesenswert?

Ich möchte mich auch herzlichst bedanken für die wirklich guten 
Erklärungen.
Bin durch Zufall hier gelandet, weil ich das "Extern" nicht richtig 
verstanden habe.
TOP Erklärungen in diesem Thread von Karl Heinz.
Vielen Dank

von Mino (Gast)


Lesenswert?

@kbuchegg: Die Erklärungen sind wirklich fantastisch!

Selbst in 2018 ist dies immer noch die beste Erklärung, die ich zu dem 
Thema finden konnte.

Vielen Dank für die Mühe, solch ausführliche Texte zu schreiben!

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.