Wir lernen in der Schule das programmieren mit C++, unsere Lehrkraft
meinte, das verwenden von globalen Variablen ist schlecht, weil der
reservierte Speicherbereich nach Programm-Ausführung nicht wieder
freigegeben wird.
Ist dies korrekt? Ich bin bisher davon ausgegangen, dass das
Betriebsystem (Windows) den reservierten Speicherbereich wieder frei
gibt, wenn die Programm-Ausführung beendet wurde.
Milo schrieb:> Ich bin bisher davon ausgegangen, dass das> Betriebsystem (Windows) den reservierten Speicherbereich wieder frei> gibt, wenn die Programm-Ausführung beendet wurde.
Natürlich.
Wenn ein Programm beendet wird, wird der Speicher für das nävhste
verwendet.
Probiere es doch einfach aus.
Schreibe ein Programm, mit einer sehr großen globalen Variable und führe
es mehrfach aus. Wenn der PC abstürzt, hat dein Lehrer wohl recht.
Milo schrieb:> das verwenden von globalen Variablen ist schlecht
Abgesehen davon:
Das ist in vielen Fällen sogar richtig. Viele Fälle von globalen
Variablen sind tatsächlich schlecht.
Aber die Begründung mit dem verbrauchten Speicher ist absolut falsch.
Milo schrieb:> Wir lernen in der Schule das programmieren mit C++, unsere Lehrkraft> meinte, das verwenden von globalen Variablen ist schlecht, weil der> reservierte Speicherbereich nach Programm-Ausführung nicht wieder> freigegeben wird.
Das ist schlecht erklärt oder falsch verstanden. Globale Variablen
werden statisch allokiert und verbrauchen also ihren Speicherplatz die
ganze Zeit die ein Programm läuft. Lokale Variablen werden dynamisch
allokiert und ihr Speicherplatz wird nach Beendigung der Prozedur oder
Funktion, in der sie definiert sind, wieder freigegeben.
LG, Sebastian
Klaus W. schrieb:> Aber die Begründung mit dem verbrauchten Speicher ist absolut falsch.
Für die üblichen Betriebssysteme haste recht, das normal mit
new/malloc-allokierte Zeug sollte dann weg sein.
Aber auch bei Windows muss nicht unbedingt jeder Speicher nach
Prozessende freigegeben sein, Stichwort IPC/SharedMemory. Kann man aber
gerne als Spezialfall bzw. bewusst getroffene Entwurfsentscheidung
sehen, sowas zu nutzen.
Milo schrieb:> ... weil der reservierte Speicherbereich nach Programm-Ausführung> nicht wieder freigegeben wird.
Ersetze "Programm-Ausführung" durch "Unterprogrammausführung". Dann
passt es.
tut nix zur Sache schrieb:> Aber auch bei Windows muss nicht unbedingt jeder Speicher nach> Prozessende freigegeben sein, Stichwort IPC/SharedMemory.
Ja klar.
Aber nachdem der letzte Prozeß, der einen shared memory-Bereich nutzt,
beendet ist, wird der auch freigegeben.
Sogar unter Windows.
Die Frage war aber nach globalen Variablen.
Sebastian W. schrieb:> Das ist schlecht erklärt oder falsch verstanden.
Dann hat die Lehrkraft das selbst falsch verstanden, ich habe meinen
Kenntnisstand ebenfalls im Unterricht geäußert und mir wurde die Antwort
gegeben, dass es nicht der Fall wäre, dass das Betriebssystem den
Speicher freiräumt, sondern der Speicherplatz der globalen Variablen bis
zum Neustart des Rechners nicht mehr für andere Anwendungen zur
Verfügung steht.
Milo schrieb:> und mir wurde die Antwort gegeben, dass es nicht der Fall wäre, dass das> Betriebssystem den Speicher freiräumt,
Das ist völliger Quatsch. Jedes Programm hat globale Variablen, denn die
C- bzw. C++-Standard Library definiert auch welche (z.B. die
errno-Variable). Da man globale Variablen nicht freigeben kann, würden
die ja permanent Speicher belegen. Es wäre auch eine ziemliche
Sicherheitslücke, wenn Programme den Speicher so vollmüllen könnten.
String-Literale und sonstige Konstanten werden genau wie globale
Variablen im Speicher hinterlegt, typischerweise aber nochmal getrennt
(schreibgeschützt). Würden die wie globale Variablen auch im Speicher
bleiben, wäre das ziemlich mies!
Globale Variablen haben zwar gewisse Probleme, aber wenn sie "const"
sind und idealerweise auch "static", sind sie okay. Größere Blöcke an
konstanten Daten kann man auch nur so sinnvoll hinterlegen (z.B.
Grafiken, Kennlinien). Windows hat da zwar noch einen anderen
Mechanismus für, aber andere Systeme nicht.
Aus Sicht des Betriebssystems sind die unterschiedlichen Speichertypen
des Programms alles das gleiche, einfach nur Teile eines linearen
Adressraums. Einzelne Variablen kann das Betriebssystem sowieso nicht
"sehen". Der Speicher eines Programms wird am Stück oder zerteilt in den
RAM gelegt. Das Betriebssystem gibt diesen gesamten Bereich wieder frei
nach Beenden des Programms, völlig egal ob da jetzt Stack, dynamisch
allokierte Variablen oder globale Variablen drin sind. Dem
Betriebssystem wäre es sogar egal wenn das Programm eine völlig andere
Art der Speicherverwaltung nutzt.
Klaus W. schrieb:> Dann eher Leerkraft, würde ich sagen
Leider gar nicht so selten. Ich hatte einen Professor, der in einer
Vorlesung über Linux-Kernel-Entwicklung behauptet hat, es gäbe keinen
virtuellen Speicher und alle Programme würden sich den selben Bereich
teilen. Nicht der gleiche Unsinn, aber artverwandt. Insbesondere
Leerpersonal scheint in solchen Bereichen besonders gut Bescheid zu
wissen...
wie du siehst, "scheinst" du recht zu haben. Die Frage wäre also:
a) woher hat dein Lehrer seine Meinung und
b) was bräuchte es, ihn von etwas anderem zu überzugen?
k.A. schrieb:> wie du siehst, "scheinst" du recht zu haben. Die Frage wäre also:> a) woher hat dein Lehrer seine Meinung und> b) was bräuchte es, ihn von etwas anderem zu überzugen?
Die Lehrkraft ist ehemals Software-Entwickler gewesen, unterrichtet aber
den Großteil des Berufslebens Informationstechnik, ich habe es nicht
notwendig die Lehrkraft von etwas zu überzeugen, davon wird die Note 1
auch nicht besser.
Es ging mir ums Verständnis, ich habe in einer Prüfung Punktabzug
bekommen, weil ich meine Variablen vor main() deklariert habe.
Lehrkräfte sind auch nicht unfehlbar, hätte es eine Note ausgemacht,
wäre ich natürlich eher dahinter, die Lehrkraft vom Gegenteil zu
überzeugen, so geht es mir nur darum zu lernen und zu verstehen, statt
es einfach ohne zu akzeptieren, ohne es Verstanden zu haben.
Softwerker schrieb:> Jedes Programm hat globale Variablen, denn die> C- bzw. C++-Standard Library definiert auch welche (z.B. die> errno-Variable).
Das sollte eigentlich längst Geschichte sein (taucht aber immer wieder
mal in einzelnen Funktionen als Problem auf -> *1). Anfänglich hat das
zu heftigsten Problemen geführt als es üblich wurde
Multi-Thread-Anwendungen zu erstellen und viele Funktionen solche
Problem hatten. Wenn da nicht jede einzelne Instanz sauber ihren eigene
Speicherbereich hat knallt es da ganz schnell. Static in Funktionen ist
auch eine gute Methode hier fahler zu fabrizieren, wenn diese plötzlich
von mehreren Threads benutzt wird. Gibt beides sehr lustige Effekte die
ewig lange suchen kann, weil da immer nur gelegentlich mal ein paar
Daten verfälscht werden.
Die "errno" ist ein Sonderfall. Diese ist aus Anwendungssicht ja read
only und das OS sorgt dafür das da immer nur der letzte Fehler
drinsteht. Bei mehreren Threads muss das also garnicht mal mehr der
Fehler sein der aufgetreten ist, sondern kann schon wieder eine anderer
sein der wo anderes in der Zwischenzeit aufgetreten ist. Ist eher ein
Relikt aus längst vergangen Tagen das meiner meinung nach mal bereinigt
gehört.
*1) Einge schöne Beispiele:
"The function localtime may not be thread-safe.":
- https://en.cppreference.com/w/c/chrono/localtime
- https://en.cppreference.com/w/cpp/chrono/c/localtime
"This function returns a pointer to static data and is not thread-safe"
- https://en.cppreference.com/w/c/chrono/ctime
- https://en.cppreference.com/w/cpp/chrono/c/ctime
Milo schrieb:> k.A. schrieb:> ich habe es nicht notwendig die Lehrkraft von etwas zu überzeugen,> davon wird die Note 1 auch nicht besser.> Lehrkräfte sind auch nicht unfehlbar, hätte es eine Note ausgemacht,> wäre ich natürlich eher dahinter, die Lehrkraft vom Gegenteil zu> überzeugen, so geht es mir nur darum zu lernen und zu verstehen, statt> es einfach ohne zu akzeptieren, ohne es Verstanden zu haben.
Pragmatisch und intelligent. Hier nachzubohren hätte für dich keinen
Vorteil (eher im Gegenteil).
Klaus W. schrieb:> Ja klar.> Aber nachdem der letzte Prozeß, der einen shared memory-Bereich nutzt,> beendet ist, wird der auch freigegeben.> Sogar unter Windows.>> Die Frage war aber nach globalen Variablen.
Wollte bloß darauf hinaus, dass halt der Begriff '(Shared) global' bei
eingefleischten Windows-Betriebssystem-Fans mitunter auch für
Synchronisationsprimitive oder IPC genutzt werden kann. Und da kann's
halt eben durchaus sein, dass die Exe aus dem Taskmgr weg ist, der
Speicher aber noch von nem anderen Prozess in Verwendung (-> halt im
Sinne von global).
Egal.
Für globale Variablen in einem Programm ist die Sache klar, das OS muss
alles, was beim Start eines Prozesses angeschubst wird, auch wieder
aufräumen, wenn der Prozess terminiert.
Milo schrieb:> Lehrkräfte sind auch nicht unfehlbar, hätte es eine Note ausgemacht,> wäre ich natürlich eher dahinter, die Lehrkraft vom Gegenteil zu> überzeugen
Sehr pragmatisch, aber so ganz gutheißen kann ich das nicht; am Besten
lernt man doch in einer direkten Diskussion und ggf. gemeinsamen
Recherchieren/Ausprobieren. Es ist schade wenn Schüler/Studenten
falsches einfach abnicken, aber natürlich auch bedenklich, wenn Lehrer
unbeirrbar auf ihrer falschen Position beharren. Das habe ich auch oft
genug erlebt. Aber es ist für niemanden eine Neuigkeit, dass es im
Bildungssystem schon lange nicht mehr um das Lernen an sich geht...
Milo schrieb:> Punktabzug bekommen, weil ich meine Variablen vor main() deklariert> habe.
Die Formulierung 'vor main' in diesem Zusammenhang macht mich stutzig.
A. S. schrieb:> Milo schrieb:>> Punktabzug bekommen, weil ich meine Variablen vor main() deklariert>> habe.>> Die Formulierung 'vor main' in diesem Zusammenhang macht mich stutzig.
Die Formulierung ‚vor main’im Zusammenhang mit ‚global‘ macht für
Variable eigentlich sehr viel Sinn.
Oliver
Klaus W. schrieb:> Ja klar.> Aber nachdem der letzte Prozeß, der einen shared memory-Bereich nutzt,> beendet ist, wird der auch freigegeben.
Schön wär's, System V Shared Memory ist da anderer Meinung.
Oliver S. schrieb:> Die Formulierung ‚vor main’im Zusammenhang mit ‚global‘ macht für> Variable eigentlich sehr viel Sinn.>> Oliver
Eigentlich nicht. Wenn man nur mit einer C-Datei arbeitet, dann ist main
meist ganz unten und die Funktionen darüber.
Wenn da eine variable "vor main", also zwischen den Funktionen und main
steht und nicht vorher declariert (oder es nur main gibt) dann ist diese
Variable nur in main bekannt.
Zwar wäre die Begründung dann immer noch falsch (was nicht passieren
darf), der Punktabzug jedoch gerechtfertigt. Wie gesagt, ich wurde nur
bei der Formulierung stutzig, es gibt ja keinen Kontext (welches Niveau
z.B.). Üblicherweise sagt man "außerhalb von main" oder "extern". Wenn
A. S. schrieb:> Eigentlich nicht. Wenn man nur mit einer C-Datei arbeitet, dann ist main> meist ganz unten und die Funktionen darüber.
Du überschätzt unser Niveau, der komplette Programmablauf findet in
main() statt, vor main() waren nur die includes und eine
Präprozessoranweisung und bei mir eben die Variablendeklarationen.
Milo schrieb:> Du überschätzt unser Niveau, der komplette Programmablauf findet in> main() statt
Wenn es nur eine Funktion main() gibt und keine weitere Funktionen, dann
sind globale Variablen tatsächlich überflüssig.
Frank M. schrieb:> Wenn es nur eine Funktion main() gibt und keine weitere Funktionen, dann> sind globale Variablen tatsächlich überflüssig.
Dies bestreite ich auch nicht, ich habe nur die Begründung der Lehrkraft
hinterfragt, weil es für mich unlogisch klang.
Das kannst Du recht einfach wiederlegen:
1. Kontrolliere den freien Systemspeicher und merke Dir die Größe.
2. Lege in einer Testsequenz eine sehr große Variable an.
3. Nutze sie auch indem Du irgendwas hineinschreibst. Wichtig!
4. Beende das Programm.
5. Kontrolliere den freien Systemspeicher erneut.
Mit "große Variable" meine ich etwas, das nicht in der
"Anzeige/Darstellung" verschütt gehen kann. Also richtig viele Megabyte
oder sogar ein paar Gigadinger. Allerdings kleiner als 4G, da es da, je
nach System (32 Bit), einige Eigenheiten geben kann.
Ich habe mal, in grauer Vorzeit, gelernt, dass das System (Windoof,
Linux usw.), den vom Programm reservierten "normalen" Speicher, beim
Beenden des Programmes, wieder freigibt. Ist ja auch logisch, da oft
viele Programme gestartet werden. Egal ob ein Programm vielfach
gestartet wird oder viele verschiedene Programme nacheinander.
Ist auch unabhängig von der Programmiersprache, da es ja vom System
gemacht wird.
Was aber wichtig ist: Das Programm muss regulär beendet werden denn erst
dann wird "aufgeräumt". Die hellseherischen Fähigkeiten der meisten
Betriebssysteme sind nämlich stark eingeschränkt. Eine Programmleiche
behält meist, bis zu einem Neustart des Systems, ihre Ressourcen.
Klaus W. schrieb:> Natürlich.> Wenn ein Programm beendet wird, wird der Speicher für das nävhste> verwendet.
Klar das machen unsichere Systeme so, damit man auch nach Programmende
noch an dessen (geheime) Daten kommt. Klassisches Beispiel: image
grabber beim Amiga.
Wenn es nur eine Methode in einem Programm gibt, ist egal, ob Variablen
global oder lokal sind, weil die Programmlaufzeit gleich der Lebenszeit
aller globalen und lokalen Variablen und Konstanten ist. Es könnte sein,
dass der Compiler in beiden Fällen den gleichen Assembly-Code erzeugt.
Globale Variablen sind nur suboptimal, wenn es mindestens eine weitere
Funktion oder Methode in dem Programm gibt.
Das wird die Lehrkraft zwar nicht überzeugen, weil Lehrkräfte die
Weisheit mit dem Löffel gegessen haben und unfehlbar sind :) - Und zwar
direkt nach ihrem Studienabschluss. (Ich hatte mal einen Lehrer, der hat
tatsächlich argumentiert, dass er Recht habe, weil er das studiert hätte
:) )
Der Speicher wird - wie bereits mehrfach beschrieben - vom Programm aus
dem virtuellen Speicher alloziiert. Dazu wird der System-Call Malloc
benutzt um vom Betriebssystemkernel den Speicher zugewiesen zu bekommen.
Wird der Prozess beendet, wird sein Speicherbereich vom
Betriebssystemkernel dealloziiert und steht anderen Prozessen zur
Verfügung.
Ausnahmsweise kann der Speicher von einem weiteren Prozess alloziiert
sein (bei Inter-Prozess-Kommunikation). Dann wird der Kernel den
natürlich erst freigeben, wenn beide Prozesse beendet sind.
Almdahl schrieb:> Dazu wird der System-Call Malloc> benutzt um vom Betriebssystemkernel den Speicher zugewiesen zu bekommen.
malloc() ist kein Syscall, der dynamische Speicher wird von der
C-Library verwaltet. Bei Bedarf(!) ruft sie den sbrk-Syscall auf, um
mehr Speicher-Pages anzufordern.
Milo schrieb:> Frank M. schrieb:>>> Wenn es nur eine Funktion main() gibt und keine weitere Funktionen, dann>> sind globale Variablen tatsächlich überflüssig.>> Dies bestreite ich auch nicht, ich habe nur die Begründung der Lehrkraft> hinterfragt, weil es für mich unlogisch klang.
Die Begründung ist natürlich Unsinn, das wurde Dir ja auch schon
mehrfach bestätigt.
Sebastian S. schrieb:> Was aber wichtig ist: Das Programm muss regulär beendet werden denn erst> dann wird "aufgeräumt". Die hellseherischen Fähigkeiten der meisten> Betriebssysteme sind nämlich stark eingeschränkt. Eine Programmleiche> behält meist, bis zu einem Neustart des Systems, ihre Ressourcen.
Wie das Programm beendet wird, spielt keine Rolle. Wichtig ist nur,
dass es wirklich beendet wird, also der Prozess weg ist. Das
Betriebssystem braucht auch keine hellseherischen Fähigkeiten, denn alle
Ressourcen, die das Programm hat, hat es vorher vom Betriebssystem
angefordert, daher weiß dieses sehr genau, was aufgeräumt werden muss.
Irgend W. schrieb:> Anfänglich hat das zu heftigsten Problemen geführt als es üblich wurde> Multi-Thread-Anwendungen zu erstellen und viele Funktionen solche> Problem hatten. Wenn da nicht jede einzelne Instanz sauber ihren eigene> Speicherbereich hat knallt es da ganz schnell.
Deshalb wurde thread-local storage erfunden. Damit kann man Variablen
erstellen, die für jeden Thread separat sind, ohne dass man sich da
weiter drum kümmern muss.
> Die "errno" ist ein Sonderfall. Diese ist aus Anwendungssicht ja read> only
Nein. Der C-Standard schreibt explizit vor, dass errno ein "modifiable
lvalue" sein muss. Den kannst auch du jederzeit beschreiben, wenn du
willst.
> Bei mehreren Threads muss das also garnicht mal mehr der> Fehler sein der aufgetreten ist, sondern kann schon wieder eine anderer> sein der wo anderes in der Zwischenzeit aufgetreten ist. Ist eher ein> Relikt aus längst vergangen Tagen das meiner meinung nach mal bereinigt> gehört.
Damit würdest du extrem viel bestehenden Code ungültig machen. Es gibt
hier auch mehrere Lösungen mit dem bestehenden errno. Die eine ist die
erwähnte thread-local storage. Eine andere ist, errno nicht als Variable
zu implementieren, denn es ist Absicht, dass C nicht sagt, dass es eine
Variable sein muss, sondern nur ein "modifiable lvalue". Das heißt, es
ist auch so etwas erlaubt wie:
1
#define errno *some_function()
Und tatsächlich steht genau sowas auf meinem Linux-System in
/usr/include/errno.h drin:
1
/* The error code set by various library functions. */
Rülpsende Bytes schrieb:> Klaus W. schrieb:>>> Natürlich.>> Wenn ein Programm beendet wird, wird der Speicher für das nävhste>> verwendet.>> Klar das machen unsichere Systeme so, damit man auch nach Programmende> noch an dessen (geheime) Daten kommt. Klassisches Beispiel: image> grabber beim Amiga.
Auch sichere Systeme machen das. Sie nullen nur einfach den Speicher,
bevor sie ihn dem anderen Programm zur Verfügung stellen.
Almdahl schrieb:> Globale Variablen sind nur suboptimal, wenn es mindestens eine weitere> Funktion oder Methode in dem Programm gibt.
Allerdings haben sie auch nur dann überhaupt einen Sinn.
Rolf M. schrieb:> Auch sichere Systeme machen das. Sie nullen nur einfach den Speicher,> bevor sie ihn dem anderen Programm zur Verfügung stellen.>> Almdahl schrieb:>> Globale Variablen sind nur suboptimal, wenn es mindestens eine weitere>> Funktion oder Methode in dem Programm gibt.>> Allerdings haben sie auch nur dann überhaupt einen Sinn.
Aber nur solange wie die localen Objecte nicht die Stackgöße
überschreiten.
Ja, das Verwenden globaler Variablen ist 'ne schlechte Idee, nein der
Speicher bleibt nicht "kleben".
Was stimmt, und Dein Lehrer vermutlich eigentlich ausdrücken wollte
(will ihm ja nicht unterstellen dass er nicht weiß was er unterrichtet),
ist, dass globale Variablen während der gesamten Laufzeit des Programmes
allokiert bleiben (müssen). Wenn Du da 'ne Megabyte große Struktur
rumkugeln hast, von der ein paar Tausend Instanzen (in einem Array
beispielsweise) global durch die Gegend geworfen werden müssen, trägt
das schon ein bissi auf im Arbeitsspeicher.
Allerdings auch das nur so lang wie das Programm läuft.
Globale Variablen sind aber aus einem anderen Grund schlecht, der Dir
wahrscheinlich erst bewusst wird, wenn Du mit Unterprogrammen und
Klassen zu tun bekommst. Sie sind global. Klingt auf den ersten Blick
jetzt praktisch, weil dann kann man ja von überall drauf zugreifen. Ist,
vom Sicherheitsaspekt mal ganz abgesehen, allerdings unhandlich wenn Du
in diversen Unterprogrammen dann plötzlich anfangen müsstest, die
Variablen umständlich zu benennen weil die "einfachen" Namen irgendwann
ausgehen. Ausserdem... war jetzt counter1 die Zählvariable die ich
verwenden kann, oder steht da in der anderen Routine was drin das ich
noch brauch, war das in der Routine die die hier aufgerufen hat, oder
war das ganz woanders, oder...
Da werden die lokalen Variablen dann plötzlich aber SOWAS von praktisch!
> gegeben, dass es nicht der Fall wäre, dass das Betriebssystem den> Speicher freiräumt, sondern der Speicherplatz der globalen Variablen bis> zum Neustart des Rechners nicht mehr für andere Anwendungen zur> Verfügung steht.
Von welchen Versionen von Windows war da die Rede?
Für gewisse Werte von (alten) Windows' traf sowas tatsächlich und
systematisch zu...
(damals waren wir noch klein und die PCs gross, gelle?)
SCNR
Von
https://docs.microsoft.com/en-us/archive/msdn-magazine/2000/july/under-the-hood-happy-10th-anniversary-windows:
If a Win16 application failed to clean up and exit properly, it wasn't
uncommon to leave orphaned DLLs and global heap blocks. Even worse were
unfreed USER and GDI resources, which came from two 64KB heaps. These
small heaps gave rise to the free system resources problems, where their
available space kept dropping until the only recourse was to reboot.