Verständnisfrage: Datentypen in C



  • Hi Leute! .

    Ich bin relativ neu in der Welt der Programmierung, und habe daher jede Menge offener Fragen. Aber hier will ich erstmal über ein paar Unsicherheiten reden, mit denen ich zu tun habe, mir gehen diese Sachen nicht aus dem Kopf. Deswegen schreibe ich sie zumindest mal, ich hoffe das hilft mir, da Klarheit zu bekommen.
    Ich komme ursprünglich von Python3 (seit Februar) und habe jetzt auch mit C angefangen, weil ich mich auch sehr für die technische Seite des Computers interessiere.

    Diese beiden Programmiersprachen sind natürlich völlig verschieden, vor Allem wegen dem, was "unter der Haube" zu geschehen scheint.
    Python realisiert Datentypen als Klassen im Sinne der Objektorientierten Programmierung. Das war für mich ein wunderbar intuitiver Ansatz. Das ist in C anders.

    Ich habe soviel verstanden, dass C Werte als Bitfolgen im Speicher ablegt und diese, je nach Datentyp, verschieden interpretiert.
    Die Fragen, die ich mir jetzt stelle sind "Wenn Binärfolge A an Adresse X abgelegt ist, wo ist dann die Information abgelegt, wie A zu interpretieren ist, also als int, double etc?"
    Dann gibt es noch den Sizeof-Operator, der die Größe eines Datentyps oder eines Werts im Speicher zurückgeben kann. Ich habe gehört, dass diese Größe aber nicht Bytes sind, sondern ominöse "Adressierbare Einheiten".

    Könnt ihr mir vielleicht ein paar Hinweise geben, wie ich mir die Speicherung von Werten zusammen mit ihren Datentypen vorzustellen habe? Ich steh glaub ich ziemlich auf dem Schlauch



  • Deine Vorstellungen sind noch etwas sehr vage...
    C ist nicht unbedingt ganz anders, man kann auch in C objektorientiert programmieren (und tut man auch oft), nur unterstützt die Sprache nicht dabei.

    "Ich habe soviel verstanden, dass C Werte als Bitfolgen im Speicher ablegt und diese, je nach Datentyp, verschieden interpretiert."
    Das ist eine sehr vage Beschreibung und könnte genauso auf Python zutreffen. Ich mein, im Endeffekt geht das nicht anders, das ist die technische Basis moderner Computer. Ok, auch eine sehr vage Beschreibung 😃
    Sie werden von den "Befehlen interpretiert", wenn du so willst. Da musst du evtl. noch einen Schritt weitergehen und dir Assembler anschauen, dann wird das vielleicht klarer. Wenn du in C Code schreibst und Variablen referenzierst, dann wird entsprechener Maschinencode generiert, der die Daten eben so interpretieren will, wie du das geschrieben hast. Ist aber in Python genauso (oder sehr ähnlich, da ist noch der Interpreter dazwischen).
    "Adressierbare Einheiten" ist mir z.B. überhaupt nicht geläufig.



  • @mechanics sagte in Verständnisfrage: Datentypen in C:

    "Adressierbare Einheiten" ist mir z.B. überhaupt nicht geläufig.

    Die kleinste adressierbare Einheit in C ist ein Byte:

    3.6
    1 byte
    addressable unit of data storage large enough to hold any member of the basic character
    set of the execution environment

    2 NOTE 1 It is possible to express the address of each individual byte of an object uniquely.
    3 NOTE 2 A byte is composed of a contiguous sequence of bits, the number of which is implementationdefined. The least significant bit is called the low-order bit; the most significant bit is called the high-order bit.

    bzw ein char:

    6.2.5 Types
    [...]
    3 An object declared as type char is large enough to store any member of the basic
    execution character set.
    [...]

    und sizeof X ergibt die Größe des Ausdrucks/Typs X in Bytes (chars):

    6.5.3.4 The sizeof and _Alignof operators
    [...]
    2 The sizeof operator yields the size (in bytes) of its operand, which may be an
    expression or the parenthesized name of a type. The size is determined from the type of
    the operand. The result is an integer [<<-- NEIN, das ist KEIN int!!]. [...]
    [...]
    4 When sizeof is applied to an operand that has type char, unsigned char, or
    signed char, (or a qualified version thereof) the result is 1. [...]



  • Ja, man kann sich natürlich sofort was drunter vorstellen. Ist mir aber trotzdem überhaupt nicht geläufig, dass jemand von "adressierbaren Einheiten" reden würde.



  • @joshuah sagte in Verständnisfrage: Datentypen in C:

    Dann gibt es noch den Sizeof-Operator, der die Größe eines Datentyps oder eines Werts im Speicher zurückgeben kann.

    Noch etwas dazu: sizeof wird zur Copiletime ausgewertet und kann eben nicht die Größe eines Werts im Speicher ermitteln weil es zu dieser Zeit noch gar keinen Speicher gibt, in dem etwas liegen könnte. sizeof ermittelt bloß den Typ seines Operanden und gibt dessen Größe zurück. Deshalb ist auch soetwas erlaubt:

    int *p = 0;
    sizeof *p;
    

    da

    6.5.3.2 Address and indirection operators
    Constraints
    [...]
    4 The unary * operator denotes indirection. If the operand points to a function, the result is
    a function designator; if it points to an object, the result is an lvalue designating the
    object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’.
    [obwohl:]
    If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.102)
    [...]
    102) Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer [...]

    weil es bei sizeof eben nur um den Typ des Ausdrucks geht und nicht um dessen Ergebnis. Deshalb wird auch keine Indirektion durchgeführt, die zB. für (int*)0 undefiniertes Verhalten verursachen würde.



  • Vielen Dank für die vielen Antworten!

    "Adressierbare Einheiten", das kommt soweit ich mich erinnere hierher:

    https://www.youtube.com/watch?v=naXqxJ95myw&list=PL5eolwFmTdvj6uEwatR3NrPLDtGV3Qln6&index=4

    Bei 53:22 min (Die Bezeichnung kommt aber so 53:50 oder so)

    Ich fasse das was ihr gesagt habt, mal zusammen:
    Es ist also zumindest möglich, im Arbeitsspeicher einzelne Bytes zu adressieren. Ein char ist ein Byte groß. Die Zahl, die der sizeof-Operator zurückgibt, ist die Anzahl der Bytes, die der Operand im Speicher verbraucht.



  • @joshuah sagte in Verständnisfrage: Datentypen in C:

    Die Fragen, die ich mir jetzt stelle sind "Wenn Binärfolge A an Adresse X abgelegt ist, wo ist dann die Information abgelegt, wie A zu interpretieren ist, also als int, double etc?"

    Im Source-Code, beim deklarieren der Variablen.
    C ist eine typisierte Sprache, du kannst den Typ einer Variable nicht nachträglich ändern.

    @joshuah sagte in Verständnisfrage: Datentypen in C:

    Ein char ist ein Byte groß.

    Ein Byte muss (in C) aber nicht 8 Bit groß sein.
    Das können auch 9 Bit oder auch 16 oder 24 Bit (z.B auf DSP) sein



  • Zusätzlich zu dem was DirkB gesagt hat, ich finde, du versteifst dich etwas zu sehr auf Details und deine eigenen Formulierungen sind deswegen auch etwas zu umständlich. Lern doch einfach C, fang an zu programmieren, dann wird das alles einfach klar sein und dann kannst du dir auch die ganzen Details aneignen. Die wären dann auch nicht mehr verwirrend, weil ich kann mir nicht vorstellen, dass es dir im Moment irgendwie weiterhilft, wie groß ein char ist oder sein kann.



  • @dirkb sagte in Verständnisfrage: Datentypen in C:

    @joshuah sagte in Verständnisfrage: Datentypen in C:

    Ein char ist ein Byte groß.

    Ein Byte muss (in C) aber nicht 8 Bit groß sein.
    Das können auch 9 Bit oder auch 16 oder 24 Bit (z.B auf DSP) sein

    Garantiert ist nur, daß die Anzahl an Bits in einem char (CHAR_BIT) größer oder gleich 8 ist.



  • Okay, ich glaube ich weiß jetzt ein wenig mehr, vielen Dank euch allen .

    @Mechanics
    Ich glaube es ist kein Fehler, Dinge die unklar sind, hartnäckig zu hinterfragen. Aber natürlich hast du absolut nicht unrecht wenn du sagst, dass man eine Programmiersprache nur lernen kann, wenn man darin programmiert.



  • @joshuah sagte in Verständnisfrage: Datentypen in C

    @Mechanics
    Ich glaube es ist kein Fehler, Dinge die unklar sind, hartnäckig zu hinterfragen.

    Nein, das sicher nicht, versteh mich nicht falsch. Nur hörte sich deine Zusammenfassung für mich z.B. zu steif an. Nicht so, als ob du das wirklich grundlegend verstanden hättest, eher, als hättest du versucht, etwas auswendig zu lernen. Dabei sind es noch eher triviale Sachverhalte, die würdest du wahrscheinlich wirklich gleich verstehen, wenn du ein bisschen damit arbeiten würdest.



  • Da sprichst du mir aus der Seele @Mechanics ^^ Ob ich das Ganze wirklich verstanden habe - wessen ich mir jetzt gerade relativ sicher bin, aber das ist nur mein subjektives Empfinden - wird die Zukunft zeigen.


  • Mod

    Mir tat beim Lernen die Vorstellung ganz gut, einem Programm wirklich bei der Arbeit in den Speicher zu gucken, und den Speicher dabei als lineare Abfolge von Speicherstellen zu visualisieren (wo jede Speicherstelle ganz neutral ein Byte aufnehmen kann, und keine Ahnung von der höheren logischen Organisation der Daten hat). In einem Programmierlehrbuch für Kinder war da eine Bilderserie mit einer Reihe Schließfächern. In der Schule hatten wir einen Lerndebugger, wo man ein Programm (Assembler-)Schritt für Schritt ausführen konnte mit einer übersichtlichen Darstellung, was zu jedem Zeitpunkt wo im Speicher steht.

    Leider ist das ein bisschen zu groß, für einen Forenbeitrag. Und echte Debugger mit echten Programmen haben im Hintergrund viel zu viel Rauschen (alleiene schon, was es ein Aufwand ist, ein Zeichen auszugeben!) und viel zu viele Verkomplizierungen durch technische Details (Register, ...). Aber vielleicht kennt jemand irgendeine gute Seite mit einer Darstellung dieser Art?



  • @seppj Das hört sich mega interessant an o.O Ich werde mich mal nach tools/Büchern etc. umschauen, die sowas in der Richtung machen.

    Abgesehen davon war ja eine meiner Eingangsfragen, wie ein Programm zur Laufzeit Datentypen memorisiert. Tut es das überhaupt, oder ist das mehr eine Erscheinung, die nur im Quellcode vorkommt, und die auf Assemblerebene keine Rolle mehr spielt? Vielleicht weil der Compiler Operationen nur auf für sie sinnvolle Operanden anwendet, wäre jetzt mein Gedanke dazu.



  • @joshuah sagte in Verständnisfrage: Datentypen in C:

    Tut es das überhaupt, oder ist das mehr eine Erscheinung, die nur im Quellcode vorkommt, und die auf Assemblerebene keine Rolle mehr spielt?

    Hier kannst Du ganz deutlich sehen, daß für verschiedene Datentypen unterschiedlicher Assemblercode erzeugt wird.

    Genau wie @Mechanics schon gesagt hat:

    @mechanics sagte in Verständnisfrage: Datentypen in C:

    Wenn du in C Code schreibst und Variablen referenzierst, dann wird entsprechener Maschinencode generiert, der die Daten eben so interpretieren will, wie du das geschrieben hast.


  • Mod

    Variablen im C-Programm sind streng genommen nur ein abstraktes Hilfsmittel für den Programmierer. Die müssen später kein direktes Äquivalent im Maschinencode haben. Es ist nur garantiert, dass sich das fertige Programm später so verhält, "als ob" da wirklich mit Variablen im Hintergrund gearbeitet wird.

    Eine der Möglichkeiten für ein Maschinenprogramm, sich so zu verhalten, als ob es mit einer echten 1:1 Abbildung von Variablen zu Speicherstellen arbeitet, ist offensichtlicherweise eben genau das zu tun: Jeder Variablen irgendwie eine Stelle im Arbeitsspeicher zuzuordnen. Muss es aber nicht. Es gibt auch viele andere Möglichkeiten und oft sind diese anderen Möglichkeiten viel effizienter. Ein Beispiel wäre:

    for(int i=0; i<5; ++i)
      tu_was();
    

    Eine Möglichkeit für äquivalenten Maschinencode wäre, tatsächlich irgendwo eine Speicherstelle für i anzulegen, ihr den Wert 0 zuzuweisen, diesen wieder und wieder um 1 zu erhöhen, und danach stets die Bedingung zu prüfen. Eine andere Möglichkeit, das gleiche Programm in Maschinencode auszudrücken wäre aber, einfach nur die Funktion tu_was fünf Mal nacheinander aufzurufen. Die beiden fertigen Programme sind dann äquivalent, beides sind gültige Maschinenprogramme zu diesem C-Quellcode. Aber bei letzterem gibt es auf Maschinenebene nichts mehr, was der Variable i auch nur irgendwie ähnlich sieht.

    Häufig werden Compiler, wenn sie angewiesen werden, keine Optimierungen durchzuführen, eher Maschinencode erzeugen, der eher der klassischen Vorstellung mit Variablen entspricht. Hochoptimierter Code ist hingegen oft eher von der Art, dass man eigentlich nicht mehr wirklich erkennen kann, was das mit den Variablen im C-Code zu tun hat. Bloß das letztlich sichtbare Ergebnis wird bei all diesen verschiedenen Möglichkeiten das gleiche sein.

    PS: Wie du siehst, hat Swordfish aus diesem Grund bei seinem Beispiel aus den von mir beschriebenen Gründen extra die Compileroption -O0 gewählt, das heißt er hat alle Optimierungen ausgeschaltet. Wenn du Optimierungen wieder anschaltest, siehst du, dass von den Variablen und Datentypen nichts mehr übrig bleibt:
    https://godbolt.org/z/MWQ3Nj



  • Ha, wunderbar . Tatsächlich, ich konnte zwar den Link nicht öffnen, aber diese Seite zeigt das trotzdem sehr klar, ich hab mal ein bisschen rumprobiert. Vielen Dank euch auch für eure Mühen. Ich glaube, ich kann mir jetzt sehr viel selbst erschließen! .



  • @joshuah sagte in Verständnisfrage: Datentypen in C:

    Abgesehen davon war ja eine meiner Eingangsfragen, wie ein Programm zur Laufzeit Datentypen memorisiert. Tut es das überhaupt, oder ist das mehr eine Erscheinung, die nur im Quellcode vorkommt, und die auf Assemblerebene keine Rolle mehr spielt? Vielleicht weil der Compiler Operationen nur auf für sie sinnvolle Operanden anwendet,

    Du (als Programmierer) teilst dem Compiler mit der Deklaration der Variablen mit, wie er sie behandeln soll.

    Mit double a; weiß der Compiler, dass er entsprechenden Code für Fließkommaoperationen einbauen muss, wenn er diese Variable a behandelt.
    Da diese Information schon zur Compilezeit festgelegt wird, braucht auch nur der Compiler wissen, was a ist.
    Im fertigen Programm ist dieses Verhalten dann fest eingebaut.
    Der Bezeichner a taucht da auch nicht mehr auf. Eher sowas wie "die Adresse Stackpointer+8"


  • Mod

    @dirkb sagte in Verständnisfrage: Datentypen in C:

    Der Bezeichner a taucht da auch nicht mehr auf. Eher sowas wie "die Adresse Stackpointer+8"

    Und ganz wichtig (da ich irgendwie das Gefühl habe, das ist beim TE noch nicht angekommen), es steht auch nirgendwo "an der Adresse liegt ein double". Das Maschinenprogramm hat halt Anweisungen fest eingebaut, die diese Adresse stets in Fließkommaregister schieben und Fließkommabefehle darauf ausführen.



  • Hi(gh)!

    @swordfish sagte in Verständnisfrage: Datentypen in C:

    Hier kannst Du ganz deutlich sehen, daß für verschiedene Datentypen unterschiedlicher Assemblercode erzeugt wird.

    Also, ich bekomme nur eine Fehlerseite mit der Textausgabe "Cannot GET /z/NzJmN2"...

    Bis bald im Khyberspace!

    Yadgar


Anmelden zum Antworten