C: Funktionen und Variablen

  • Entweder hab ich Tomaten auf den Augen oder aber der GCC-Compiler macht nicht was er soll. ?(

    Folgendes Problem:

    Aus dem Buch "C für dummies" (3. Auflage) hab ich folgendes Programm abgetippt:

    Ziel dieses Programms ist es, die Unabhängigkeit der beiden Variablen v in den beiden Funktionen hier() und dort() zu demonstrieren, obwohl sie den gleichen Namen haben. Steht auch so auf der Seite beschrieben.

    Abgetippt und copy-paste:

    Der Compiler beschwert sich pflichtbewußt über die nicht initialisierte Variable v in der Funktion dort(). :)

    Kompiliert und gestartet:

    Hier ist v = 30

    Dort ist v = 35

    Ähm.... Zufall??? ?(

    Neue Runde:

    hier(): v = 3 + 974

    Ergebnis:

    Hier ist v = 977

    Dort ist v = 982

    Also eher kein Zufall!

    Aber es geht noch verrückter:

    hier(): v = 3597 / 109

    dort(): v wird durch q ersetzt.

    Trotz der unterschiedlichen Variablennamen bleibt q abhängig von v:

    :!:Das sollte nicht so sein. Allem Anschein nach macht der Compiler in der Funktion dort() eine Zwangsinitialisierung der Variable.

    :?:Kann man das abstellen?

    Das ganze wurde mit

    Qt Creator 4.12.0

    Based on Qt 5.12.7 (GCC 7.5.0, 64-bit)

    unter openSUSE Leap 15.3

    erstellt.

    Ich hab das Programm jetzt schon zig Mal auf Tippfehler geprüft, konnte aber keine finden.

    Ich werde mich von keinem einzzzigen Prozzzessor trennen.
    Jedoch lockt es mich beinahe, ihn Dir zu überlassen, nur um zu sehen, wie er Dich in den Wahnsinn treibt :evil:

    Meine Begehren

  • Nun, das würde die Variable v in der Funktion dort() dann initialisieren und gleich 5 setzen unabhängig von allem anderen, was vorher geschehen ist!

    In der Tat ist dann die Ausgabe: Dort ist v = 5:

    Wäre aber etwas sinnlos, da das Programm ja die Unabhängigkeit der beiden Variablen v demonstrieren soll:

    Die abgekürzte Schreibweise v += 5 bedeutet ja ausgeschrieben v = v + 5. Es wird also 5 zum vorhanden Wert in v addiert und das Ergebnis erneut in v gespeichert.

    Da v in dort() nicht initialisiert ist, sollte der vorhandene Wert zufällig sein je nach dem, was an der Speicheradresse, die der Compiler dem zweiten v zuweist, eben gerade an Müll noch gespeichert ist.

    Wenn man jetzt aber unter dort() v = 5 setzt, wie soll dann noch die Unabhängigkeit von dem v aus der Funktion hier() gezeigt werden können?

    EDIT: Gilt natürlich auch analog, wenn v in dort() durch q ersetzt wurde.

    Ich werde mich von keinem einzzzigen Prozzzessor trennen.
    Jedoch lockt es mich beinahe, ihn Dir zu überlassen, nur um zu sehen, wie er Dich in den Wahnsinn treibt :evil:

    Meine Begehren

  • Neuer Versuch:

    Die Funktionen werden zweimal aufgerufen und dafür mit einem Zähler x bzw. y versehen:

    Ergebnis:

    Hier v = 30

    Dort v = 35

    Hier v = 35 (konstant!)

    Dort v = 40 (konstant!)

    Obwohl beim zweiten Durchlauf von hier() v nicht neu berechnet wird, ist es plötzlich um 5 größer als nach der Berechnung von ersten Durchlauf.

    Im zweiten Durchlauf von dort() werden nochmals 5 dazu gezählt.

    Noch seltsamer wird es, wenn die Aufrufe für den zweiten Durchlauf nicht von main() kommen, sondern sich die Funktionen gegenseitig aufrufen:

    Hier v = 30

    Dort v = 35

    Hier v = 32618 (zufällig!)

    Dort v = 32769 (zufällig!)

    Die Werte nach dem zweiten Durchlauf sind nicht mehr konstant, sondern ändern sich bei jeder Programmausführung!


    Jetzt seh ich gar keinen Zusammenhang mehr...

    ?(?(?(

    Ich werde mich von keinem einzzzigen Prozzzessor trennen.
    Jedoch lockt es mich beinahe, ihn Dir zu überlassen, nur um zu sehen, wie er Dich in den Wahnsinn treibt :evil:

    Meine Begehren

  • Selbst wenn ich die Variablennamen nicht mische und alles sauber initialisiere, bleiben sie voneinander abhängig:

    Hier (A) v = 30

    Dort (B) q = 5

    Hier (A) v = 5 (sollte 30 sein)

    Hier (A) v = 6 (sollte 31 sein)

    Dort (B) q = 11 (sollte 10 sein)

    Ich werde mich von keinem einzzzigen Prozzzessor trennen.
    Jedoch lockt es mich beinahe, ihn Dir zu überlassen, nur um zu sehen, wie er Dich in den Wahnsinn treibt :evil:

    Meine Begehren

  • Ich bin kein C-Programmierer, aber: Du verwendest lokal uninitialisierte Variablen. Nach meinem (theoretischen) C-Verständnis arbeitest Du damit in einem undefinierten Zustand. C reserviert den Speicherplatz, wenn Du die Variable definierst. Wenn Du die Variable dann ausliest, liest es gnadenlos ungeprüft den Speicher aus, den er reserviert hat. Da steht halt "zufällig" das drin, was vorher in einer anderen lokal definierten Variable drin stand, weil die zufällig in ihrer Laufzeit den gleichen Speicherbereich belegt hatte.

    In dem Beispiel aus dem Buch haben sie ja auch einen Zufallswert. Nur halt aus irgendeiner anderen Speicherstelle. Das kommt dann sicher auf Compiler-Version, -Einstellungen, vielleicht die CPU an, wie er die Speicher-Allozierung genau macht.

    Wird das in dem Buch danach nicht erklärt? Lösung ist ganz einfach: Nie uninitialisierte Variablen nutzen.

    Edit: Ich sehe, in Beitrag 3 hast Du das ähnlich geschrieben. Wenn das Buch das nicht weiter erläutert, dann ist das Buch da einfach Mist.

    Die Unabhängigkeit der lokalen Variablen hätte man zeigen können, ohne sie uninitialisiert zu lassen. Durch das Weglassen der Initialisierung hat man (bei Deinen Compilereinstellungen etc.) ironischerweise genau das Gegenteil erreicht. Aber auch das halt nicht verlässlich (über alle Maschinen und Compilereinstellungen hinweg)

    We are Microsoft of Borg. Assimilation is imminent. Resistance is... Error in Borg.dll. Press OK to abort.

    Einmal editiert, zuletzt von LoB (21. Februar 2022 um 08:10)

  • Es ist immer empfohlen wenn du eine variable hast das du der auch einen Wert zuweist.

    Das initialisiert die Variable. int a = 0;

    Du solltest _immer_ die variablen initialisieren, also einen Wert zuweisen, damit du kein unerwartets Verhalten davon bekommst.

  • Das ist schon klar. In diesem Beispiel sollte ja durch das Nichtinitialisieren gezeigt werden, daß die beiden Variablen unabhängig sind. Hat halt bei mir nicht ganz funktioniert, weswegen ich ein paar Variationen des Programms getestet habe.

    Bleiben wir mal beim Beispiel aus Beitrag #5 mit einer kleinen Änderung. Hier werden ja in Zeile 22 und 40 beide Variablen initialisiert, die jetzt auch unterschiedlich bezeichnet sind. Wenn ich die Reihenfolge der Funktionsaufrufe in den Zeilen 8 - 11 so abändere:

    also erst zweimal Funktion A, dann zweimal Funktion B aufrufe, rechnet er zumindest mal richtig:


    In der ursprünglichen Variante, bei der A und B abwechselnd aufgerufen wurde (Zeilen 8 - 11):

    bekam v aus Funktion A ja beim zweiten Mal plötzlich auf magische Weise den Wert von q aus Funktion B zugewiesen und q beim zweiten Mal plötzlich den Wert vom zweiten v:


    Der Überkreuzaufruf,

    bei dem A das zweite Mal nach dem ersten Durchlauf von Funktion B direkt aus Funktion B heraus aufgerufen wird und B das zweite Mal direkt aus dem zweiten Durchlauf von A

    - Zeile 10 wird gelöscht und nach Zeile 42 eingefügt (jetzt nach Zeile 43, weil die zweite Änderung das ganze noch mal um eine Zeile verschiebt)

    - Zeile 11 wird gelöscht und nach Zeile 30 eingefügt

    liefert dann plötzlich zufällige, nicht nachvollziehbare Werte:


    Was soll mir der ganze Test bringen?

    Ich will wissen, wie der Compiler arbeitet. Es sieht so aus, als bekämen nur globale Variablen (evtl. auch Variablen der Funktion main(), aber das hab ich noch nicht getestet) einen eigenen Speicherplatz im RAM zugewiesen. Lokale Variablen werden dagegen vermutlich einfach nur in den allgemeinen Registern vorgehalten oder auf dem Stack abgelegt. Ich sollte die drei Beispiele mal disassemblieren. :/

    In so einem kleinen Programm ist das ja noch leicht zu durchschauen. Aber nehmen wir mal an, man schreibt etwas größeres und nimmt für jeden Bereich (bei einem Spiel würde man sagen jeden Level) eine eigene Funktion, zwischen denen man beliebig wechseln kann. Dann sollte man halt wissen, daß die Variablen offensichtlich nicht getrennt voneinander gespeichert werden und man bei jedem Wechsel eine Sicherung des Verlaufs in dieser Funktion (Bereich, Level) selbst durchführen muß.

    Ja für Fortgeschrittenen alles ein alter Hut, aber für blutige Anfänger wie mich eben nicht (siehe die zahlreichen Bugs und Endlosschleifen in diversen Spielen ;)).

    Ich werde mich von keinem einzzzigen Prozzzessor trennen.
    Jedoch lockt es mich beinahe, ihn Dir zu überlassen, nur um zu sehen, wie er Dich in den Wahnsinn treibt :evil:

    Meine Begehren

  • Was soll mir der ganze Test bringen?

    Der Test bringt Dir meiner Meinung nach GAR nichts. Das Buch ist an der Stelle Mist. Ich würde den Test abbrechen, er wird Dich nur in die Irre führen.

    In so einem kleinen Programm ist das ja noch leicht zu durchschauen. Aber nehmen wir mal an, man schreibt etwas größeres und nimmt für jeden Bereich (bei einem Spiel würde man sagen jeden Level) eine eigene Funktion, zwischen denen man beliebig wechseln kann. Dann sollte man halt wissen, daß die Variablen offensichtlich nicht getrennt voneinander gespeichert werden und man bei jedem Wechsel eine Sicherung des Verlaufs in dieser Funktion (Bereich, Level) selbst durchführen muß.

    Also: Eine lokale Variable existiert nur in dem Scope, in dem sie definiert wurde: v existiert nur, WÄHREND die Funktion A() läuft. Davor und danach existiert die Variable nicht. Das selbe gilt für x, q und y. Das gilt auch, wenn Du in einem anderen Scope (z.b. in B() oder auch global!) eine gleichnamige Variable definierst. Die Lebenszeit der Variablen endet mit dem Ende des Funktionsaufrufs.

    Der im Buch abgedruckte Quellcode ist nicht in der Lage, das zu zeigen, WEIL er die Initialisierung der Variablen vergisst. Das ist ein Programmierfehler. Was da rauskommt, ist einfach random. Du hast Beispiele, die vermeintlich eine Abhängigkeit beweisen. Das ist Zufall, weil einfach der selbe (durch das Ende des Funktionsaufrufs freigewordene!) Speicherbereich wiederverwendet (und nicht neu initialisiert) wurde.

    Du hast Beispiele, die auf keine Abhängigkeit hindeuten. Auch das ist Zufall, weil einfach ein ANDERER Speicherbereich alloziert (und nicht neu initialisiert) wurde.

    We are Microsoft of Borg. Assimilation is imminent. Resistance is... Error in Borg.dll. Press OK to abort.

    Einmal editiert, zuletzt von LoB (26. Februar 2022 um 09:24)