Ist C++ noch zu retten?



  • @john-0 sagte in Ist C++ noch zu retten?:

    Genau darum geht es. C++ ist eine stark typisierte Sprache und eben keine schwachtypisierte Sprache

    Mir geht es aber darum, dass ich nicht an allen Stellen meines Codes die starken Typen brauche. Manchmal sind sie sinnvoll, dann nutzt man dort halt kein auto oder nur sporadisch auto. Manchmal aber machen starke Typen einem das Leben schwerer, ohne Mehrwert zu bieten - mehr noch, wenn man später man einen Typen in der Implementierung austauscht, muss man gleich überall Typen anpassen. Und wenn du jetzt argumentierst, dass Details nicht nach draußen leaken sollen, dann hast du generell zwar recht, aber oft lässt sich das nicht vermeiden oder es bietet Optimierungspotential oder die Implementierung wäre ungleich teurer (in Programmiererstunden).

    Für ein vollständiges Programm können in unterschiedlichen Stellen unterschiedliche Paradigmen sinnvoll sein.



  • Kleiner Hinweis: auto hat mit der Stärke des Typsystems nichts zu tun. Es ist ja sogar so, dass stark typisierte Sprachen oft mit sehr wenig expliziten Typangaben auskommen und der Rest inferiert wird.



  • @john-0 sagte in Ist C++ noch zu retten?:

    Wo bitte ist denn der qualitative Unterschied zum status quo?
    for (auto i = 0; i != 10'000'000'000; ++i) {

    Ja schreib ich jeden Tag sowas 🤣
    Nimm mal weniger drogen dude.



  • @5cript sagte in Ist C++ noch zu retten?:

    @john-0 sagte in Ist C++ noch zu retten?:

    Wo bitte ist denn der qualitative Unterschied zum status quo?
    for (auto i = 0; i != 10'000'000'000; ++i) {

    Ja schreib ich jeden Tag sowas 🤣

    Ich fand diesen Einwand von @john-0 durchaus berechtigt. Was ich schon sehr oft in Code gesehen habe, ist natürlich nicht die Loop von 1..10'000'000'000, sondern stattdessen:

    for (int i = 0; i != some_container.size(); ++i) { ... }
    
    // Oder dem folgenden, um die signed-Warnung zu ignorieren
    
    for (unsigned int i = 0; i != some_container.size(); ++i) { ... }
    

    Hältst du das für so abwegig?

    Nimm mal weniger drogen dude.

    Abstraktionsvermögen weg? Selber auf Droge? (sorry, wenn man anderen das unterstellt, unterstelle ich einfach mal zurück, auch wenn ich nicht angesprochen war)



  • Nein ist er nicht und zwar deswegen:
    warning: comparison of integer expressions of different signedness: 'int' and 'std::vector<char>::size_type' {aka 'long long unsigned int'} [-Wsign-compare]|
    und wenn man einen 2 gigabyte vector (char, das kleinste was geht) auf x86_32 hat oder einen vector größerer größe auf 64 bit dann ist man sich dessen bewusst. Das kommt nicht einfach so "ups, der ist aber groß"

    wer auf microcontrollern einen vector hat, dem gratulier ich.

    (oder andere datenstruktur)

    EDIT: Oder noch breiter: KEIN MENSCH iteriert ohne sich das bewusst zu sein über milliarden an elementen.



  • Mir ist klar, das der Thread eher theoretischer Natur ist, aber wie schafft man in x86 einen vector mit 10'000'000'000 Elementen? Ich bekomme bei der Erstellung einen bad_alloc Fehler. Unter x64 kompiliert klappt das mit der Anzahl. Und dann kann man auch mit auto iterieren.

    int main()
    {
    	std::vector<char> vec(10'000'000'000);
    	std::cout << vec.size();
    
    	for (auto i = 0; i != vec.size(); ++i) {}
    	std::cout << "\nfertig";
    }
    


  • Gar nicht.
    Du kriegst das nie im leben als kontinuierlichen Block, außer vllt auf einem speziellen System?
    Es kommt drauf an wie viel RAM du hast und wie fragmentiert der ist.
    Und wegen addressing ist es sogar unmöglich in 32 bit.



  • Aber das ist dann doch kein Problem von auto, sondern wie man kompiliert?

    Ich versuche gerade

    for (auto i = 0; i != 10'000'000'000; ++i) {}
    

    aber da rechnet er schon seit über 10 Minuten.



  • Das muss ja kein Container sein, das könnte z.B. sowas wie ein Generator sein. Darum ging es jetzt glaub ich nicht primär.
    Aber so oder so, ich halte diese Diskussion für nicht relevant. auto für Zahlen finde ich auch völlig überflüssig, ändert aber nichts daran, dass auto ein sehr großer Gewinn für die Sprache war.



  • @zeropage sagte in Ist C++ noch zu retten?:

    aber da rechnet er schon seit über 10 Minuten.

    Ja genau. Mein reden.



  • Dieses auto- Beispiel war das einzige, was ich ohne größere Überlegungen nachtippen konnte. Und wenn das aber geht, dann verstehe ich eine auto-Problematik erst recht nicht.
    Wenn es mit autonicht geht, würde es mit jedem anderen Standard-Typen auch nicht gehen. Das hat doch dann nichts mit autozu tun?



  • Er hat gemeint es wäre besser wenn auto statt int bei auto i = 0 lieber den kleinst möglichen typ vom Wert ermitteln sollte, also hier unsigned char (oder char?, char wär falsch).

    wohingegen
    for (auto i = 0; i != 365; ++i) noch ein realistisches beispiel ist was man, oder ein anfänger schnell mal reinzimmert und sich dann wundert warum das ne endlosschleife ist, dachte er er könnte countern und sagen:
    "wieso? das hier geht doch jetzt auch schon nicht?"
    for (auto i = 0; i != SOMETHING_LUDICROUS; ++i)
    Dadurch enstand das erst.



  • Ok, danke 😉



  • @zeropage sagte in Ist C++ noch zu retten?:

    Unter x64 kompiliert klappt das mit der Anzahl. Und dann kann man auch mit auto iterieren.

    Kann man nicht, jedenfalls nicht so wie du. Denn deine Schleifenvariable ist ein int, kann also die Größe des Vektors gar nicht ausdrücken, jedenfalls nicht auf x64. Du bekommst irgendwann einen Überlauf. Das ist sogar undefiniertes Verhalten. Im günstigsten Fall ist es ein Überlauf, dein i kippt also bei 2,x Milliarden nach -2,x Milliarden über und wird niemals gleich 10 Milliarden sein, also hast du eine Endlosschleife.

    Du hast damit das Problem mit auto vorzüglich illustriert. Sogar ein Upvote dafür bekommen!



  • Oho, danke. Wenn auch unverdient. Mit size_t geht es dann aber. Wenn ich dort stattdessen auto einsetze, kommt keine entsprechende Ausgabe, wird dann nicht soweit gekommen sein. Habe ich mich von der "fertig"-Meldung blenden lassen.

    int main()
    {
    	std::vector<char> vec(10'000'000'000);
    	std::cout << vec.size();
    
    	std::size_t n = 0;
    	for (std::size_t i = 0; i != vec.size(); ++i)
    	{
    		++n;
    		if (n == 3'000'000'000)
    			std::cout << "\n3 Milliarden: " << n;
    	}
    	std::cout << "\nfertig";
    }
    


  • @Bashar sagte in Ist C++ noch zu retten?:

    Kann man nicht, jedenfalls nicht so wie du. Denn deine Schleifenvariable ist ein int, kann also die Größe des Vektors gar nicht ausdrücken, jedenfalls nicht auf x64. Du bekommst irgendwann einen Überlauf. Das ist sogar undefiniertes Verhalten. Im günstigsten Fall ist es ein Überlauf, dein i kippt also bei 2,x Milliarden nach -2,x Milliarden über und wird niemals gleich 10 Milliarden sein, also hast du eine Endlosschleife.

    Korrekt, es ist UB, aber gerade weil es UB ist kann es sein dass es trotzdem funktioniert. Grund: weil signed Integer Overflow UB ist, kann der Compiler davon ausgehen dass es keinen Overflow gibt. Er kann also das 64 Bit Register mit 0 initialisieren und dann einfach hochzählen. Und sich darauf verlassen dass das Bits 63 ... 31 immer 0 bleiben.

    Beispiel: https://godbolt.org/z/5Eh4er

    #include <stddef.h>
    
    void foo(unsigned char* data, size_t size) {
        for (int i = 0; i < size; i++)
            data[i] = i;
    }
    

    Compiliert GCC 10 zu

    foo(unsigned char*, unsigned long):
            test    rsi, rsi
            je      .L1
            xor     eax, eax
    .L3:
            mov     BYTE PTR [rdi+rax], al
            add     rax, 1
            cmp     rax, rsi
            jne     .L3
    .L1:
            ret
    

    Wie man sieht wird für den Schleifenzähler ein 64 Bit Register verwendet und dieses wird direkt mit der 64 Bit size Variable verglichen.

    Ändert man dagegen den Typ von i zu unsigned int, bekommt man

    foo(unsigned char*, unsigned long):
            test    rsi, rsi
            je      .L1
            xor     eax, eax
            xor     edx, edx
    .L3:
            mov     BYTE PTR [rdi+rdx], al
            lea     edx, [rax+1]
            mov     rax, rdx
            cmp     rdx, rsi
            jb      .L3
    .L1:
            ret
    

    Hier stellt GCC also sicher dass der Wert in rdx niemals >= 2^32 wird.

    Der Aufruf der Funktion mit size >= 2^32 ist nebenbei erwähnt trotzdem UB, da es auch eine Regel gibt die besagt dass ein Programm kontinuierlich beobachtbare Effekte zu produzieren hat. Bzw. anders gesagt: es darf nicht endlos in einem Loop festhängen der keine beobachtbaren Effekte hat. Und das Lesen oder Schreiben von Speicher zählt ohne volatile oder atomic Operations nicht als beobachtbarer Effekt.

    D.h. GCC dürfte sich genaugenommen selbst hier darauf verlassen dass es keinen Overflow gibt. Tut er in diesem Fall bloss nicht.

    Und zu guter letzt: Eine Schleife wie for (auto i = 0; i != vec.size(); ++i) {} kann der Compiler natürlich sowieso gleich ganz wegoptimieren - die tut ja schliesslich nichts.



  • @5cript sagte in Ist C++ noch zu retten?:

    Gar nicht.
    Du kriegst das nie im leben als kontinuierlichen Block, außer vllt auf einem speziellen System?
    Es kommt drauf an wie viel RAM du hast und wie fragmentiert der ist.

    Mein aktuelles System hat 32 GB RAM. Da bekomme ich locker einen zusammenhängenden Block mit 10 GB. Fragmentierung spielt da auch keine Rolle, weil 64 Bit Adressraum und virtuelle Adressen. D.h. das OS mappt einfach die einzelnen Pages so in den 64 Bit Adressraum des Prozesses dass sich dort ein zusammenhängender Block ergibt. Wo die Pages physikalisch liegen spielt dabei überhaupt keine Rolle.

    Und wegen addressing ist es sogar unmöglich in 32 bit.

    Ja, klar. Doh 🙂



  • @5cript sagte in Ist C++ noch zu retten?:

    EDIT: Oder noch breiter: KEIN MENSCH iteriert ohne sich das bewusst zu sein über milliarden an elementen.

    Da bin ich anderer Meinung. Es gibt genug Leute die Code schreiben der allgemein verwendbar sein sollte, und sich über sowas keine Gedanken machen. Und es gibt leider auch genug Leute denen nicht klar ist, dass man Code der für ein bestimmtes Projekt entwickelt wurde, nicht einfach so da rausnehmen und als allgemeingültig erklären sollte, ohne ihn davor nochmal komplett zu reviewen und dann entsprechend zu überarbeiten.

    Also grundsätzlich finde ich das Beispiel schon valide. Ich bin aber nicht der Meinung dass man auto deswegn als Fehldesign oder sonstwie kaputt ansehen sollte. Denn wie schon gesagt: wer Dinge verwendet die er nicht versteht, wird früher oder später mit allem Mist bauen. Und es ist ja nicht so dass die Regeln für auto besonders kompliziert wären. Ganz im Gegenteil: jeder Versuch auto irgendwie sicherer zu machen würde die Regeln wohl deutlich verkomplizieren.



  • @hustbaer sagte in Ist C++ noch zu retten?:

    Mein aktuelles System hat 32 GB RAM. Da bekomme ich locker einen zusammenhängenden Block mit 10 GB. Fragmentierung spielt da auch keine Rolle, weil 64 Bit Adressraum und virtuelle Adressen. D.h. das OS mappt einfach die einzelnen Pages so in den 64 Bit Adressraum des Prozesses dass sich dort ein zusammenhängender Block ergibt. Wo die Pages physikalisch liegen spielt dabei überhaupt keine Rolle.

    hmmm. Ich erinnere mich explizit damit schon ein problem gehabt zu haben. Aber das ist so lange her, da vertrau ich mir selbst nicht (oder den damaligen gegebenheiten).
    Würde solche Mengen trotzdem nicht versuchen zu alloziieren oder zu verwenden, außer ich habe guten Grund.
    Weil ich unterstelle fast immer erstmal dass ich prinzipiell keine Ahnung habe auf welchem System mein Code läuft (linux, windows? gibt es swap? ist es eine VM?).
    Ich habe starke Neurosen Systemspeichrressourcen auszureizen.
    Entweder wird das system unresponsive oder bad_alloc. Letzteres kriege ich fast nicht hin. Eher friert alles ein.
    Hab nen RAM zerstörendes Meta Programm in meinem GIST. Danach darf man erstmal neustarten. Der compiler stürzt aber nicht ab. (vermutlich weil windows alles auf die festplatte geschoben hat)

    @hustbaer sagte in Ist C++ noch zu retten?:

    Da bin ich anderer Meinung. Es gibt genug Leute die Code schreiben der allgemein verwendbar sein sollte, und sich über sowas keine Gedanken machen. Und es gibt leider auch genug Leute denen nicht klar ist, dass man Code der für ein bestimmtes Projekt entwickelt wurde, nicht einfach so da rausnehmen und als allgemeingültig erklären sollte, ohne ihn davor nochmal komplett zu reviewen und dann entsprechend zu überarbeiten.

    Dagegen kann man nicht argumentieren weil es automatisch wahr ist.
    Vielleicht ist es persönlich, aber wenn ich Sachen probiere, die ich als extrem erachte, dann überleg ich erstmal was da schief gehen könnte und was die performance implikationen sind.
    Meine Aufmerksamkeit skaliert direkt mit der Aufgabe.
    EDIT: Ich hab nochmal über deine Aussage rübergelesen - Man hat keine Kontrolle drüber wenn man die Dependency eines anderen Projektes ist. Sprich wenn jemand die eigene library verwendet (oder ein nachfolger den Unternehmenscode missbraucht) für größenordnung die man vorher nicht antizipiert hat, kann sowas vielleicht passieren. Aber dafür gibt es automatische tests und constraints. Außerdem kann die Person dann aber gerne den debugger anschmeißen oder ein git issue öffnen etc. Hohe Anforderungen erfordern auch die Fähigkeit damit umzugehn. Und sollten auch die Notwendige Vorsicht und Testbereitschaft voraussetzen.
    jeder macht Fehler. egal welche Sprache. aber man macht sie einmal, vllt zweimal aber irgendwann macht man sie nicht mehr, oder man erwartet sie und handelt entsprechend. (design, tests,...). das hat nichts mit der Programmiersprache zu tun.

    @hustbaer sagte in Ist C++ noch zu retten?:

    Ich bin aber nicht der Meinung dass man auto deswegn als Fehldesign oder sonstwie kaputt ansehen sollte. Denn wie schon gesagt: wer Dinge verwendet die er nicht versteht, wird früher oder später mit allem Mist bauen. Und es ist ja nicht so dass die Regeln für auto besonders kompliziert wären. Ganz im Gegenteil: jeder Versuch auto irgendwie sicherer zu machen würde die Regeln wohl deutlich verkomplizieren.

    In dem Punkt sind wir einer Meinung.

    For the record: Ich nutze in schleifen mit indizes kein auto. Weil alle std container std::size_t verwenden und das ist unsigned. Ich eliminiere regelrecht religiös alle Warnungen. Erwachsen aus Erfahrung. Der Gedanke "wieso ist doch egal" zu einer Warnung hat schon in dutzenden Fällen zu bugs geführt.
    Die Konsequenz daraus ist, dass ich die Typen da automatisch abstimme, ohne darüber nachzudenken.

    --

    Ich habe auch schon mal vergessen, dass auf meinem 8bit uC die ints nicht 32 bit breit sind (sondern 16). Die Konsequenz daraus waren so ähnlich. Hatte dann aber nichts mit Sprachfeatures zu tun, sondern PEBCAC. Wie meiner Ansicht auch hier.



  • @wob sagte in Ist C++ noch zu retten?:

    Mir geht es aber darum, dass ich nicht an allen Stellen meines Codes die starken Typen brauche. Manchmal sind sie sinnvoll, dann nutzt man dort halt kein auto oder nur sporadisch auto. Manchmal aber machen starke Typen einem das Leben schwerer, ohne Mehrwert zu bieten - mehr noch, wenn man später man einen Typen in der Implementierung austauscht, muss man gleich überall Typen anpassen.

    Es fehlt in C++ ein brauchbares Sprachkonstrukt, um das zum Ausdruck zu bringen. auto ist so etwas wie das void* zum Übersetzungszeitpunkt.

    Nehmen wir mal folgenden Code an.

    #include <iostream>
    #include <cstddef>
    #include "goo.h"
    
    void foo () {
        auto& v = goo();
    
        v[0] = 10;
        std::cout << "size of v is " << v.size() << std::endl;
    
        for (auto i: v) {
            std::cout << i << std::endl;
        }
    }
    
    int main () {
        foo();
    
        return EXIT_SUCCESS;
    }
    

    Dann sieht man am Code, dass v keineswegs ein beliebiger Typ sein kann, weil foo sonst nicht mehr funktioniert. Effektiv ist foo gegen das Kontrakt einer Containerklasse mit operator[] entwickelt worden. D.h. nur std::vector oder std::array wären aus der Standard Library in der Lage das Kontrakt zu erfüllen, oder eben jede beliebige kompatible Containerklasse.

    Was nun fehlt ist ein Sprachkonstrukt, dass genau dies zum Ausdruck bringt, momentan bleibt einem nur der Weg über ein Template das ist aber für eine generische Funktion, die exakt mit einem Typen aufgerufen wird, Overkill. Das Nachfolgende in geschwätzigen Pseudocode

    void foo () 
        requires template C<T> with typename T
        C is Container_Class_with_RandomAccess
    {
        C& v = goo();
    
        v[0] = 10;
        std::cout << "size of v is " << v.size() << std::endl;
    
        for (T i: v) {
            std::cout << i << std::endl;
        }
    }
    

    soll das dokumentieren. Dann wäre klar, dass foo nicht mit irgend etwas anderem funktionieren kann als einer bestimmten Klasse C mit Eigenschaften … .


Anmelden zum Antworten