Im letzten, eher technischen Artikel ging es um den Stack und wie er genutzt wird, um Funktionen, Funktionsaufrufe und Variablendeklarationen abzubilden; bisher können wir primär allerdings nur ganze Zahlen darstellen. Heute wollen wir darauf aufbauend uns wieder der konkreten Programmierung mit C++ zuwenden und besprechen, wie sich auch andere und komplexere Daten darstellen lassen.

Wie wir in einem früheren Artikel besprochen haben, muss bei der Deklaration von Variablen in C++ immer auch ihr Wertebereich – der Datentyp – mit angegeben werden. Das hat 2 wichtige Gründe: erstens kann so der Compiler an Hand des Datentyps abschätzen, wie viel Speicherplatz eine Variable auf dem Stack benötigt und entsprechenden Code generieren (nicht jeder Typ braucht 4 Byte) und zweitens kann der Compiler überprüfen, ob nur sinnvolle Operationen im Programm ausgeführt werden – einer ganzen Zahl eine Zeichenkette zuweisen ist nicht sinnvoll und kann durch den Compiler so ausgeschlossen werden.

Bisher kennen wir lediglich den Datentyp der ganzen Zahlen, bei der Variablendeklaration mit int markiert. Üblicherweise werden für ganze Zahlen 4 Byte, also 32 Bit benutzt; damit lassen sich insgesamt 232, also 4.294.967.296 verschiedene Zahlen darstellen (und bevor entsprechende Kommentare kommen: ja, int ist nicht auf 32 Bit im C++-Standard festgelegt und kann auch andere Größen annehmen; oft sind es jedoch 32 Bit), je zur Hälfte positive und negative, mit −2.147.483.648 als der kleinsten und 2.147.483.647 als der größten Zahl.

Daneben existieren in C++ noch weitere, fest eingebaute Datentypen zur Speicherung von Informationen; die folgende Tabelle gibt einen Überblick über die entsprechenden Schlüsselwörter, die zur Deklaration genutzt werden können, zusammen mit den typischerweise auf aktuellen Systemen für den jeweiligen Datentyp gültigen Wertebereichen (die Tabelle ist nicht erschöpfend, umfasst aber die wichtigsten Typen) und der Anzahl der Bytes, die zur Darstellung der Werte verwendet wird (der Bitbreite, falls der Wert in Bit angegeben wird). Zu beachten ist: die Wertebereiche sind (abgesehen von char, welches immer 1 Byte groß ist) lediglich gängige Werte in der C++-Welt; sie sind nicht durch den C++-Standard festgelegt und können zwischen Betriebssystemen und Compilern variieren – lediglich ihre Reihenfolge ist standardisiert (bei den Zahlen gehe ich davon aus, dass 1 Byte 8 Bit entspricht – auf den meisten Systemen ist das auch so).

Typ Bytes Minimum Maximum
bool 1 0 (falsch) 1 (wahr)
char 1 -128 127
short 2 -32.768 32.767
int 4 -2.147.483.648 2.147.483.647
long 4 -2.147.483.648 2.147.483.647
long long 8 -9.223.372.036.854.775.808 9.223.372.036.854.775.807
float 4 -3,40282 * 1038 3,40282 * 1038
double 8 -1,79769 * 10308 1,79769 * 10308

Der Typ bool dient zur Darstellung von Wahrheitswerten; historisch bedingt unterscheidet C++ allerdings nicht zwischen Zahlen und Wahrheitswerten, weswegen an eine Variable vom Typ bool neben den speziellen (Wahrheits-)Werten true und false auch Zahlen zugewiesen werden können

Neben den hier genannten Typen gibt es von den meisten Typen auch noch eine Variante mit gleicher Bitbreite, allerdings ohne Vorzeichen, sprich, mit einem Wertebereich ausschließlich im Bereich der positiven Zahlen. Diese Datentypen werden einfach mit dem vorangestellten Schlüsselwort unsigned gekennzeichnet und haben üblicherweise die folgenden Wertebereiche (der Minimalwert ist natürlich immer 0):

Typ Bytes Maximum
unsigned char 1 255
unsigned short 2 65.535
unsigned int 4 4.294.967.295
unsigned long 4 4.294.967.295
unsigned long long 8 18.446.744.073.709.551.615

Wollen wir also eine Variable deklarieren, die eine Zahl im Bereich von 0 bis 255 aufnehmen kann, so schreiben wir (der Variablenname kann natürlich frei gewählt werden):

unsigned char c;

Eine natürliche Zahl wird so deklariert:

unsigned int i;

Für eine reelle Zahl einfacher Genauigkeit schreiben wir

float f;

Und für eine reelle Zahl doppelter Genauigkeit entsprechend:

double d;

Der für die einzelnen Variablen auf dem Stack reservierte Speicher hängt dabei von verschiedenen Faktoren ab, unter anderem natürlich von der Bitbreite, aber auch von diversen Einstellungen des Compilers, die hier aber erst einmal nicht näher diskutiert werden sollen.

1 / 2 / 3 / Auf einer Seite lesen

Kommentare (22)

  1. #1 MartinB
    April 30, 2013

    Hmm, bei deiner set-Funktion machst du von call-by-reference Gebrauch, oder? (Denn es wird doch nur ein Pointer statt des Arrays übergeben…) War das in der Serie schon dran?

  2. #2 Marcus Frenkel
    April 30, 2013

    @MartinB
    Pssst. Großes Geheimnis. Das kommt später. 😉
    Aber um die Frage zu beantworten: bei ganz strenger Definition von call-by-reference haben wir in set ein normales call-by-value; als Value wird nämlich der Zeiger auf das Array übergeben. Die Zuweisung wird natürlich trotzdem nach außen weitergegeben, weil eben auf dem Pointer operiert wird.

  3. #3 MartinB
    April 30, 2013

    @Marcus
    Schon klar, ich dachte nur, dass das Leute verwirren könnte, die das noch nicht wissen.

  4. #4 Marcus Frenkel
    April 30, 2013

    @MartinB
    Vermutlich; ich habe den Text etwas angepasst, danke für den Hinweis.

  5. #5 Nicolai
    April 30, 2013

    Vielen Dank für deine Mühe und die guten Erklärungen – findet man selten!
    Für meinen Geschmack ist bisher nur ein bisschen zu viel C dabei, aber das ändert sich bestimmt noch. 🙂

    Wäre std::array nicht eleganter? Das kann man ja per Value oder per Reference übergeben, je nach Anwendung könnte das praktischer sein..

    Oder wissen wir Leser hier offiziell noch nichts von der Standardbibliothek? 😀

  6. #6 Dr. Webbaer
    April 30, 2013

    Ist die dynamische Typisierung böse?

  7. #7 Marcus Frenkel
    April 30, 2013

    @Nicolai

    Für meinen Geschmack ist bisher nur ein bisschen zu viel C dabei, aber das ändert sich bestimmt noch.

    Das ändert sich noch, ja. Objektorientierung kommt, sobald die Grundlagen da sind. 😉

    Wäre std::array nicht eleganter? Das kann man ja per Value oder per Reference übergeben, je nach Anwendung könnte das praktischer sein..

    Das ist schon zu tief in der OO drin, da fehlen noch einige Grundlagen dafür.

    Oder wissen wir Leser hier offiziell noch nichts von der Standardbibliothek?

    Richtig. 😉

  8. #8 rolak
    April 30, 2013

    5. Element des Arrays..
    as[4] = 10;
    Wer jetzt denkt, die 4 wäre ein Tippfehler

    Nee, in dem Fall nicht, jedoch bei den beiden folgenden Beispielen

    int as[4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    int as[4]; as = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    sehe ich das schon als Tippfehler an. Bzw zu stabiles sample/hold bei c/p 😉

    die Gründe hierfür kenne ich allerdings nicht – wem sie bekannt sind

    As simple as can be: Sprachdefinition.

    Die cliffhanger dieser Serie sind nett…

  9. #9 Marcus Frenkel
    April 30, 2013

    Nee, in dem Fall nicht, jedoch bei den beiden folgenden Beispielen

    Whoops…Copy&Paste ist nicht nur beim Programmieren schädlich…

    As simple as can be: Sprachdefinition.

    Ich hoffe bei so etwas immer auf tiefergehende Gründe und nicht einfach “weil es so gemacht wurde”. Wobei Java einen ja eines besseren belehren sollte…

    Die cliffhanger dieser Serie sind nett…

    Was soll man machen. Wenn alles in einem Artikel steht, schreibe ich zu lang daran und es liest auch keiner mehr. 😉

  10. #10 rolak
    April 30, 2013

    tiefergehende Gründe

    Damals™ bei 8080 und so konnte ich mir ja noch durchaus vorstellen, daß irgendwelche Platz-, Komplexitäts- oder Integrationsprobleme eine Orthogonalität des Befehlssatzes verhinderten. Doch bei heutigen Kernen, insbesondere allerdings (und das schon immer) bei Computersprachen sind mir solch krasse Brüche unverständlich.

    Was soll man machen.

    Aber nicht doch, das war durchaus und ernsthaft positiv gemeint: Nicht nur daß unnötige Fragen gebremst werden, locker eingestreut und insbesondere wie hier als Schlußsatz-Brücke hält sowas durch den offenen Spannungsbogen auch die Aufmerksamkeit wach.

    Millionen Heft{roman}-, Radio- und Fernsehserien können nicht irren…

  11. #11 Stefan W.
    https://demystifikation.wordpress.com/2013/04/09/balkentrager/
    April 30, 2013

    Ist die dynamische Typisierung böse?

    Was für eine unschuldige Frage!

  12. #12 Dr. Webbaer
    Mai 3, 2013

    Auf die ganz blöden Diskussionen – Scope von Variablen, globale Variablen böse, untypisierte Datenstrukturen sowieso, lässt sich ja hier keiner ein, lol. – Ein sicheres Merkmal, dass hier bevorzugt Sacharbeit stattfindet.

    MFG
    Wb

  13. #13 Geralt
    Mai 10, 2013

    ###
    #Der folgende Code ist dagegen leider nicht möglich (die #Gründe hierfür kenne ich allerdings nicht – wem sie #bekannt sind, der möge es in den Kommentaren schreiben):
    # int as[10];
    # as = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    ####

    “as” dürfte in dem Zusammenhang nur ein Pointer auf einen long-integer Wert (die Anfangsadresse des Arrays) sein. Evtl. klappt’s mit dem Dereferenzierungsopearator, aber sicher bin ich mir hier auch nicht. Direkt zuweisen geht aber bei Pointern natürlich nicht.

  14. #14 Geralt
    Mai 10, 2013

    sry wegen der Formatierung. Wie ging blogqote noch mal?

    bei erfolgreichem Ausführen der zweiten Zeile würde die Startdresse des Arrays auf einen recht komischen Wert überschrieben werden… zum Glück schnallt das der Compiler;)

  15. #15 Marcus Frenkel
    Mai 10, 2013

    <blockquote>Das Zitat</blockquote>

    😉

  16. #16 Geralt
    Mai 10, 2013

    danke.
    merke gerade ich hab alles ein bisschen undeutlich formuliert. Also:
    “as” ist in dem Zusammenhang ein long-int Pointer, der als Wert die Startadresse des Arrays beinhaltet. Die Zeile

    as = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    bedeutet also, dass der Startadresse des Arrays as ein neuer Wert zugewiesen werden soll, und zwar {1,2,3…}. Das dürfte schon nicht klappen, weil Long-Int nicht mehrere Werte annehmen kann.

    Nutz man zusätzlich den Deferenzierungsoperator, würde man statt der Speicheradresse den Inhalt der Speicheradresse ansprechen. Aber darin dürfte bereits der erste Wert des Arrays befinden, also as[0]. Der erste Wert (wie alle Werte des Arrays as) hat den Typ int. Also versucht man, einen int-Wert auf {1,2,3…} zu setzten, was ebenfalls in die Hose geht sollte.

    Was meint ihr?

  17. #17 Marcus Frenkel
    Mai 10, 2013

    Das erklärt aber nicht ganz, warum die gleiche Syntax bei der gleichzeitigen Deklaration und Zuweisung erlaubt ist.

  18. #18 Geralt
    Mai 10, 2013

    Na ja, dazu müsste man wohl teilweise auch in die Köpfe der Compiler-Entwickler sehen können.

    Aber dass man eine Funktion eingebaut hat um ein Array ohne for-Schleife, oder 10 einzelnen Befehlen (in unserem Fall), zu initialisieren, können wir nachvollziehen. Und dass das in bereits vorhandener Syntax passieren sollte auch.

    Dann stellt sich die Frage wo und wie man die Funktion implementiert. Und warum nicht gleich bei der Deklaration?
    Den (meisten) Compilern ist’s eh “egal”, ob bei der Deklaration von Arrays gleich Werte mit angegeben werden oder nicht, falls ja, werden diese verwendet, falls nicht, wir alles auf 0 gesetzt. In beiden Fällen wird der Inhalt des Arrays bei Deklaration gesetzt.

    Lange Rede kurzer Sinn: ich denke dass die erste Version funktioniert, ist von den Compiler-Entwicklern einfach so gewollt, denn die Funktion an sich macht ja Sinn.

    Daher sehe ich die Frage primär darin, warum es mit der Zwei-Zeilen-Version nicht funktioniert. Technisch ist’s nachvollziehbar (s #16), aber könnte der Compiler nicht trotzdem so agieren wie bei der Deklaration? Dann dürfte die Variable “as” aber nicht nur ein Pointer auf die Startadresse des Arrays sein. Womöglich ist das einfach wieder historischer Speichergeiz…

  19. #19 Marcus Frenkel
    Mai 10, 2013

    Die Compiler-Entwickler haben damit erst einmal direkt nichts zu tun; es steht in der Sprachspezifikation, was erlaubt ist und was nicht. Heißt, hier wurde sich schon bei der Spezifikation überlegt, dass das nicht gestattet sein soll; vielleicht mit Hinblick auf die spätere Implementierung des Compilers, das kann gut sein. Vielleicht hat es aber auch andere Gründe. Ich hatte gehofft, dass die konkreten Gründe jemandem bekannt sind. 😉

  20. #20 michael
    Mai 10, 2013

    > Vielleicht hat es aber auch andere Gründe.

    Etwas dazu findet man hier und in den Verweisen:

    https://stackoverflow.com/questions/3437110/why-does-c-support-memberwise-assignment-of-arrays-within-structs-but-not-gen?rq=1

  21. #21 Dr. Webbaer
    Juli 17, 2013

    Sup!

    Kommt noch was Ergänzendes?

    MFG
    Dr. W

    • #22 Marcus Frenkel
      Juli 17, 2013

      Der nächste Artikel ist in Arbeit und kommt hoffentlich bald.