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.
    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 … .



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

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

    Das Beispiel stammt ursprünglich von Dir.

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

    Der Rest gelöscht, weil bereits besprochen.



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

    Würde solche Mengen trotzdem nicht versuchen zu alloziieren oder zu verwenden, außer ich habe guten Grund.

    Ja, klar, wer 10 GB RAM anfordert wo er nichtmal die Hälfte braucht, der macht grob was falsch. Ich meine nur, dass eine 10 GB Allocation im Falles des Falles funktioniert, dafür braucht man heutzutage kein besonderes System mehr. Das sollte auf jedem halbwegs aktuellen Gamer-PC funktionieren (aktueller Richtwert sind glaub ich grad 16 GB RAM).

    Und daher überlegt ich mir auch nicht erst noch 10x wie ich eine 10 GB Allocation vermeiden könnte, wenn es die schnellste und einfachste Möglichkeit ist das zu machen was ich machen will. Ausser geringer RAM Verbrauch ist wichtiger als schnelle Entwicklungszeit und gute Performance. Aber wenn das so ist, dann weiss man es normalerweise auch.

    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.

    Genau.

    Aber dafür gibt es automatische tests und constraints.

    Also ne sorry, das sehe ich anders. Wenn ich eine Funktion habe, die z.B. nen std::vector nimmt und da über alle Elemente drüberiteriert, aber dafür keinen size_t sondern etwas kleineres verwendet, dann nenne ich diese Funktion fundamental kaputt. Und doppelt wenn es eine öffentliche Funktion in einer Library ist. Was der Test testet und was in der Doku steht ist mir da egal.

    Außerdem kann die Person dann aber gerne den debugger anschmeißen oder ein git issue öffnen etc.

    Ja, klar. Alles was kaputt ist kann man irgendwie reparieren. Aber kaputt ist es trotzdem, und gut ist es trotzdem nicht. Jeder Fehler der erst nach Auslieferung erkannt wird kostet ein vielfaches davon was Fehlern kosten die man vor der Auslieferung erkennt.

    Wie gesagt: das Beispiel finde ich valide, aber daraus zu folgern dass auto kaputt ist eben nicht. Kaputt ist in dem Fall der Entwickler und der Prozess.



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

    Also ne sorry, das sehe ich anders. Wenn ich eine Funktion habe, die z.B. nen std::vector nimmt und da über alle Elemente drüberiteriert, aber dafür keinen size_t sondern etwas kleineres verwendet, dann nenne ich diese Funktion fundamental kaputt. Und doppelt wenn es eine öffentliche Funktion in einer Library ist. Was der Test testet und was in der Doku steht ist mir da egal.

    Das klingt als würdest du denken ich befürworte absichtlich lazy und buggy zu programmieren, einfach weil 'auto' geil und weil es automatic tests gibt.

    Ich weiß gar nicht warum ich mit dir diskutier, ich bin eigentlich deiner Meinung in fast allem in diesem Thread 🤔

    Mein Beispiel war ungeeignet dafür zu argumentieren, weil es (wenn auch in anderen Größenordnungen) das gleiche Problem hat in echtem C++, wie ich vorgeworfen hatte in seinem Fall.
    Bleibt immernoch, dass auto plötzlich von "typ auf rechter seite" zu "typ durch wert" sich ändern würde durch sein vorschlag und das ist eine grausame idee, dazu steh ich weiter.



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

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

    @john-0

    Ich möchte mich hierfür entschuldigen. Die Reaktion war unangebracht und der Einwand berechtigt.



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

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

    Also ne sorry, das sehe ich anders. Wenn ich eine Funktion habe, die z.B. nen std::vector nimmt und da über alle Elemente drüberiteriert, aber dafür keinen size_t sondern etwas kleineres verwendet, dann nenne ich diese Funktion fundamental kaputt. Und doppelt wenn es eine öffentliche Funktion in einer Library ist. Was der Test testet und was in der Doku steht ist mir da egal.

    Das klingt als würdest du denken ich befürworte absichtlich lazy und buggy zu programmieren, einfach weil 'auto' geil und weil es automatic tests gibt.

    Ich weiß gar nicht warum ich mit dir diskutier, ich bin eigentlich deiner Meinung in fast allem in diesem Thread 🤔

    (...)

    Normalerweise denke ich das nicht. Was du hier geschrieben hast hat für mich aber nur Sinn gemacht unter der Annahme dass so ein Fehler OK ist solange er "works as specified and tested" ist.

    Bleibt immernoch, dass auto plötzlich von "typ auf rechter seite" zu "typ durch wert" sich ändern würde durch sein vorschlag und das ist eine grausame idee, dazu steh ich weiter.

    Das wäre eine grausame Idee, ja. Was Sinn machen könnte wäre die Verwendung von auto (zumindest ohne const) mit Literalen zu verbieten. Dann könnte man keine auto i = 0 Fehler mehr machen weil es gar nicht mehr kompilieren würde. Ist aber auch nichts was ich für wirklich nötig befinden würde. Und vermutlich gibt es auch Fälle wo etwas dadurch nicht mehr funktioniert von dem man eigentlich möchte dass es funktioniert.



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

    Also ne sorry, das sehe ich anders. Wenn ich eine Funktion habe, die z.B. nen std::vector nimmt und da über alle Elemente drüberiteriert, aber dafür keinen size_t sondern etwas kleineres verwendet, dann nenne ich diese Funktion fundamental kaputt. Und doppelt wenn es eine öffentliche Funktion in einer Library ist. Was der Test testet und was in der Doku steht ist mir da egal.

    Die Standard Library erlaubt, dass die Allokatoren kleinere Datentypen als size_t z.B. für std::vector<T>::size_type definieren. Dann ist auch sichergestellt, dass das funktioniert, weil die Allokatoren nur so kleine Allokationen durchführen, dass diese kleineren Typen auch wirklich erlauben alle Elemente der Container zu erreichen. Allerdings sieht der Standard Allokator dafür size_t vor, und das wird auch benutzt, wenn man die type_traits mit einem Custon Allocator benutzt, falls man size_type nicht explizit selbst sinnvoll definiert.

    Wie gesagt: das Beispiel finde ich valide, aber daraus zu folgern dass auto kaputt ist eben nicht. Kaputt ist in dem Fall der Entwickler und der Prozess.

    Das Beispiel zeigt, dass an dieser Stelle auto sinnfrei ist, d.h. dieses Konstrukt kann man nicht als Argument pro auto anführen.

    auto krankt daran, dass es implizite Kontrakte einführt und das in einer stark typisierten Sprache nichts zu suchen hat. Bei Templates hatte man das Problem auch jahrzehntelang und löst es nun damit, dass man nun endlich Concepts einführt. Dabei waren Concepts schon kurz nach C++1998 angekündigt worden, und es gab diverse Anläufe das Problem zu lösen. Jetzt riss man ohne Not eine weitere Bresche in das Typsystem von C++, weil man wieder nicht gründlich etwas durchdacht hat. Mir scheint man wollte unbedingt Lambdas, und hat dann auto übers Knie gebrochen, weil sonst Lambdas nicht praktikabel gewesen wären.

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

    Ich möchte mich hierfür entschuldigen. Die Reaktion war unangebracht und der Einwand berechtigt.

    Akzeptiert, ich hoffe das ist eine Lehre dafür, sich verbal passender auszudrücken.


Log in to reply