C++ und C, Unterschied



  • Bitte ein Bit schrieb:

    VLA hat einen massiven Nachteil:
    Es liegt auf dem Stack.

    SeppJ schrieb:

    Der Vorteil von VLAs ist, dass sie auf dem Stack liegen.

    VLA sind ein einziger Nachteil.
    Der Standard kennt überhaupt keinen Stack, demzufolge schreibt er sowas wie auch alle anderen gern in diesem Zusammenhang genannten Weisheiten bzgl. Implementierungen nicht vor.
    Ein Compilerhersteller kann VLA auch im Heap (wird auch vom Standard nicht gekannt) anlegen und wäre immer noch standardkonform.



  • Des einen Stack ist des anderen automatic storage duration.


  • Mod

    Tyrdal schrieb:

    SeppJ schrieb:

    Das man nicht ungeprüft riesige Arrays auf den Stack legen sollte ist Aufgabe des Programmierers.

    Und wie prüfe ich, ob genug Platz auf dem Stack ist?

    Auf die gleiche Art und Weise, wie du weißt, ob deine normalen Variablen auf den Stack passen: Du legst einfach keine riesengroßen Objekte auf den Stack und hoffst, das mit dieser Faustregel schon alles passen wird! Oder prüfst du vor jedem Funktionsaufruf, ob dieser nicht den Stack überlaufen lassen könnte?

    Besser ist natürlich zu überlegen, ob du ein überhaupt ein VLA möchtest. Schließlich läuft obige Prüfung darauf hinaus, ob das Array größer oder kleiner wäre als das größte Array mit statischer Größe, das du noch guten Gewissens auf den Stack legen würdest. Warum dann nicht ein statisches Array dieser Größe nehmen?



  • #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct
    {
      char Name[256];
      char Vorname[256];
      int Alter;
    } tPerson;
    
    typedef struct
    {
      tPerson Personen[32];
      int Anzahl;
    } tHaus;
    
    typedef struct
    {
      tHaus Haus[50];
      int Anzahl;
    } tStrasse;
    
    int Test2(void)
    {
      tStrasse S2;
    
      S2.Anzahl = 0;
      return 0;
    }
    
    int Test(int Size)
    {
      tStrasse List[Size];
    
      //return Test2();
      return 0;
    }
    
    int main(int argc, char** argv)
    {
      Test(1);
      return 0;
    }
    

    Mit Test(1) funktioniert es noch. Bei Test(2) kommt schon die Stack Overflow Fehlermeldung. Und dies ist kein untypisches Array!

    Der Fehler ist sporadisch, da er abhängig von der aktuellen Stack Belegung ist und der Größe des Funktionsparameters Size!

    Wie soll man solch ein Problem lösen?

    Ein Compilerhersteller kann VLA auch im Heap (wird auch vom Standard nicht gekannt) anlegen und wäre immer noch standardkonform.

    Wer macht das schon? Das Ganze endet doch dann in nicht portablen Code.


  • Mod

    Bitte ein Bit schrieb:

    Und dies ist kein untypisches Array!

    Ähh, doch? Selbst ein Einzelobjekt mit einer sizeof wie tStrasse würdest du niemals in den automatischen Speicher stecken, geschweige denn ein Array davon anlegen.

    P.S.: Für alle, die nicht rechnen möchten: sizeof(tStrasse) == 825804, falls sizeof(int)==4.



  • Bitte ein Bit schrieb:

    C++ ist für meinen Geschmack eine Sprache auf einem anderen Abstraktionsniveau. In C ist das Abstraktionsniveau niedrig.

    Das war auch nicht anders beabsichtigt.
    C ist von Praktikern für die Praxis entwickelt worden und dabei schon mal sehr portabel ausgelegt worden.
    Im Gegensatz zu Theoretikern wie Stroustrup haben K&R ihr Ergebnis auch selbst eingesetzt, mehr noch, sie haben schon bei der Entwicklung der Sprache die konkreten Einsatzfälle im Auge gehabt, und eben nicht irgendwelche Fallstricke, über die 40 Jahre später immer noch viele Leute stolpern (könnten).
    Stroustrup war ja noch nicht mal in der Lage, eine konkrete Referenzimplementierung eines C++ Compilers anzubieten, d.h. natürlich auch, dass er die Anforderungen an seine Sprache von der Praxis aus gesehen überhaupt nicht kennen geschweige denn testen konnte.

    Bitte ein Bit schrieb:

    In C++ kümmert man sich darum meist wenig, denn mit ein wenig Sorgfalt lassen sich in C++ viele typische C Fehler vermeiden.

    Genau richtig. Deshalb sollte ein C++ler auch niemals mit ureigenen C-Mitteln rumhantieren, davon hat er eh keine Ahnung; muss er ja auch nicht haben wenn er die C++ Intention verstanden hat, die eben gerade vom Lowlevel abhebt;
    denn trotz des geringen Sprachumfanges stecken in C wie man auch sehr oft hier im Forum sieht, viele Hürden und Aberglauben.



  • Bitte ein Bit schrieb:

    Der Fehler ist sporadisch, da er abhängig von der aktuellen Stack Belegung ist

    Richtig erkannt; und nur Anfänger/Fachbuchautoren/Hochschuldozenten verwenden nicht zwingend notwendige Abhängigkeiten.



  • Bitte ein Bit schrieb:

    Wie soll man solch ein Problem lösen?

    Du hast auch nicht sehr viel Ahnung.
    Für sowas gibt es alloca, zumindest bei BSD und gcc vorhanden, wenn auch kein ISO Standard. Das hätten die in C11 mal aufnehmen sollen und nicht diesen vielfach praxisirrelevanten Müll.



  • Selbst ein Einzelobjekt mit einer sizeof wie tStrasse würdest du niemals in den automatischen Speicher stecken, geschweige denn ein Array davon anlegen.

    Ja vielleicht.

    Aber die Gefahr ist doch dass man sowas unbeabsichtigt tut. Da ändert man hier eine Größe, mal da und blub kommt es zu einem Stack Overflow. In meinem Beispiel führt jedes weitere Zeichen in tPerson.Name dazu das 32*50 Bytes mehr benötigt werden.

    Nicht gut...

    Wollte man aber sicher sein, dürfte man bei VLA keine Structs nehmen.

    Du hast auch nicht sehr viel Ahnung.

    Liegt wohl daran dass das eine Fangfrage war. Ich hätte ein Antwort nach dem Motto "Dann erhöhen wird doch einfach den Stack Speicher." erwartet.

    Für sowas gibt es alloca, zumindest bei BSD und gcc vorhanden, wenn auch kein ISO Standard.

    In der Unix Welt kenne ich mich nicht aus. Würde aber natürlich für VLA die Sache ein wenig besser machen. Auch wenn das Kernproblem bleiben würde.


  • Mod

    Bitte ein Bit schrieb:

    Selbst ein Einzelobjekt mit einer sizeof wie tStrasse würdest du niemals in den automatischen Speicher stecken, geschweige denn ein Array davon anlegen.

    Ja vielleicht.

    Aber die Gefahr ist doch dass man sowas unbeabsichtigt tut. Da ändert man hier eine Größe, mal da und blub kommt es zu einem Stack Overflow. In meinem Beispiel führt jedes weitere Zeichen in tPerson.Name dazu das 32*50 Bytes mehr benötigt werden.

    Nicht gut...

    Das gilt für jeden Datentyp und unabhängig von VLAs.

    Das ist hier auch ein Designproblem deines Datentyps TStrasse. Das ist eine typische Art von Datensatz, die man überhaupt nicht in ein struct auf Anwendungsebene stopft, sondern wo man bloß ein Handle anbietet. Und wenn man schon so weit ist, dass man ohnehin ein Handle anbietet, dann kann man es auch gleich dynamisch machen, anstatt sinnlos 800kB zu reservieren, von denen man 799 wahrscheinlich sowieso nicht braucht, dafür aber Probleme hat, wenn der Name mal mehr als 255 Zeichen hat.

    Frei nach Wutz: Problem an dem Beispiel ist schlechte Benutzung von C, nicht C. Und (hier) auch nicht das spezielle Sprachfeature VLA.

    Wollte man aber sicher sein, dürfte man bei VLA keine Structs nehmen.

    Wieso? Wenn ich sage, dass du dein VLA dagegen absichern musst, dass es nicht größer werden kann als gut wäre, dann beinhaltet das natürlich auch, die Größe der Elemente zu beachten, nicht bloß die Anzahl.



  • Bitte ein Bit schrieb:

    In meinem Beispiel führt jedes weitere Zeichen in tPerson.Name dazu das 32*50 Bytes mehr benötigt werden.

    Nicht gut...

    Schon wieder richtig erkannt, aber aufgrund deiner fehlenden Erfahrung falsch geschlussfolgert.
    Wie SeppJ schon andeutete:

    Es ist designmäßig naiv (inperformant,fehleranfällig,...) eigentliche Nutzdaten im Stack zu halten. Wenn dann sind allenfalls "Hilfsdaten" im Stack denkbar, die dann auch wirklich nur innerhalb der Funktion Verwendung finden.
    Es ist deswegen Unsinn, weil du bei Übergabe der Daten an/Übernahme der Daten von der Funktion sie jedesmal kopieren müsstest.
    Deswegen übergibt man explizit nur Verweise, im C++ Jargon auch Referenz genannt.
    Und genau dieses (einfache) Fallmuster hat Ritchie schon vor 40 Jahren erkannt und diese Quasireferenz für Arrays als Funktionsparameter mit in die Sprache eingebaut, indem bei Übergabe bei Arrays immer nur der Verweis übergeben wird und nicht etwa der gesamte Arrayinhalt (durch call by value auf den Stack kopiert wird).

    Du willst diese Daten als VLA innerhalb der Funktion benutzen, d.h. du musst sie erstmal da rein kopieren, sie verarbeiten und anschließen wieder zurückkopieren:

    struct Bla {....};
    struct Bla meineDaten[1000000000];
    bla(meineDaten,1000000000);
    
    void bla(struct Bla *ptr, int x)
    {
      int i;
      struct Bla d[x];
      memcpy(d,ptr,sizeof d);
      ...
      hier irgendwas mit d anfangen
      for(i=0;i<x;++i)
        d[i] = ...
      ...
      memcpy(ptr,d,sizeof d);
    }
    

    Ich hoffe du erkennst die Schizophrenie des VLA-"Designs" für Nutzdaten, dass du ohne VLA viel schneller und sicherer arbeiten kannst:

    void bla(struct Bla *ptr, int x)
    {
      int i;
      ...
      hier irgendwas DIREKT mit ptr anfangen
      for(i=0;i<x;++i)
        ptr[i] = ...
      ...
    }
    

    Einzig zu beachten für Arrays hierbei ist, dass du die Längeninformation des Arrays, wenn sie dynamisch ist und zur Compilezeit nicht bekannt ist, separat übergeben musst.



  • Wutz schrieb:

    Du willst diese Daten als VLA innerhalb der Funktion benutzen

    Wozu sollte ich Daten, die nicht als VLA vorliegen, als VLA behandeln wollen?



  • Schon wieder richtig erkannt, aber aufgrund deiner fehlenden Erfahrung falsch geschlussfolgert.

    Ähh, ja ich glaube da unterschätzt du mich ein wenig. Ich bin kein C Profi, aber auch kein Noob mehr.

    Deswegen übergibt man explizit nur Verweise, im C++ Jargon auch Referenz genannt.

    Ich spreche da lieber von Zeigern auch wenn man diese derefernziert. 😉

    Und ist ja klar. Ich möchte mir nicht den Wolf kopieren, weil ich ständig call-by-value mache. Aber in meinem Beispiel war es ein wenig anders. Ich kopierte nicht die Daten in das lokale Array, sondern ich führte eine Berechnung auf dieser lokalen Datenstruktur durch. VLA nutzte ich nicht, weil es der Compiler nicht kannte.

    Natürlich ist die Datenstruktur ein wenig gekünstelt, auch wenn dies bei mir schon in der Praxis vorkam. Die Datenstruktur war anfangs rank und schlank und wuchs weiter und weiter bis ich ein Redesign durchführte.

    In der Summe: Ich sehe nicht so den Vorteil von VLA.



  • Bitte ein Bit schrieb:

    VLA nutzte ich nicht, weil es der Compiler nicht kannte.

    int Test(int Size)
    {
      tStrasse List[Size];
       
      //return Test2();
      return 0;
    }
    

    Das ist dein Code und das ist VLA. Und ich nehme an, dass du in der Funktion Test auch irgendwas mit deiner Liste anfangen willst.



  • Bitte ein Bit schrieb:

    Ich hätte ein Antwort nach dem Motto "Dann erhöhen wird doch einfach den Stack Speicher." erwartet.

    Du verstehst nicht ganz. VLAs reduzieren die Stackspeichernutzung.

    int size = ... // maximal 1024, kann aber auch kleiner sein.
    char line[size]; // minimale Größe
    

    VLAs an Stellen nutzen, wo die Größe absolut unbekannt ist, ist nicht gut. Wenn performancemäßig nötig, mache ich dann ein if (i_can_has_vla)/else malloc.

    Im Gegenteil: Alle Arrays so zu wählen, dass die Größe sicher ausreicht, führt zu Stacküberläufen und Cachemisses.



  • Ich hätte gerne VLAs für Threading mit OpenMP. häufig brauche ich nur ein paar Byte pro Thread und müsste mir, weil die Anzahl der Threads dynamisch ist, teuer speicher vom Heap holen. Für microthreads echt unangenehm. Dann lieber einmal kurz etwas mehr Speicher vom Stack holen.



  • otze schrieb:

    Ich hätte gerne VLAs für Threading mit OpenMP. häufig brauche ich nur ein paar Byte pro Thread und müsste mir, weil die Anzahl der Threads dynamisch ist, teuer speicher vom Heap holen. Für microthreads echt unangenehm. Dann lieber einmal kurz etwas mehr Speicher vom Stack holen.

    Wenn du's wirklich brauchst: Praktisch jeder Compiler unterstützt sowas wie alloca()... 😉



  • Wenn performancemäßig nötig, mache ich dann ein if (i_can_has_vla)/else malloc.

    Und da wird es meines Erachtens wichtig, eine saubere Programm-Struktur zu haben, damit man sich kein Speicherloch einfängt.

    Ich hätte gerne VLAs für Threading mit OpenMP. häufig brauche ich nur ein paar Byte pro Thread und müsste mir, weil die Anzahl der Threads dynamisch ist, teuer speicher vom Heap holen. Für microthreads echt unangenehm. Dann lieber einmal kurz etwas mehr Speicher vom Stack holen.

    Für welche Dinge benötigt man solche Mikro-Optimierungen?

    Ist es da nicht besser an der Komplexitätsklasse rumzuschrauben?


  • Mod

    Bitte ein Bit schrieb:

    Ist es da nicht besser an der Komplexitätsklasse rumzuschrauben?

    Ich lege dir mal alle meine mikrooptimierten Programme vor, du erklärst mir dann, wie ich die Komplexitätsklasse hätte verbessern können?



  • Ich lege dir mal alle meine mikrooptimierten Programme vor, du erklärst mir dann, wie ich die Komplexitätsklasse hätte verbessern können?

    Was soll das? 😡

    Ja gerne. Ich würde mal gerne ein Programm sehen. Was für Programme sind das, für die sich solche Optimierungen lohnen? High-End Simulationen? Interrupt Funktionen?

    Hier wird von OpenMP und Threads gesprochen. Aber gleichzeitig auch von teueren malloc Aufrufen, Microthreads und Cachemisses. Und den Sinn dahinter verstehe ich nicht. Ich verstehe es nicht wie man OpenMP nutzen kann, und dann bei Optimierungsproblemen wie Microthreads und Cachemisses hängen bleibt.

    Annahme: Ich führe eine Berechnung mit N Threads durch. Durch einen Fehler sind N-1 Threads schnell fertig, wodurch aber der letzte Threads umso länger benötigt... Und da der Hauptthread auf das Ende aller Threads wartet, ist kein Zeitgewinn durch die Threads zu erwarten.

    Lohnen sich Microthreads denn überhaupt, da der Kontextwechsel einen nicht unerheblichen Beitrag zur Thread Laufzeit leistet?

    Wenn Cache Misses ein Problem sind, sollte ich mir überlegen einen Non-Paged Memory Pool zu allokieren oder gleich einen entsprechenden Treiber zu schreiben um direkten Zugriff auf die Hardware zu haben und um das Betriebssystem zu umgehen.

    Und was macht der Optimierer überhaupt? Nutzt dieser den kompletten Befehlsatz der CPU? Kämpfen Mikrooptimierungen nicht gegen den Optimierer? Oder sollen Mikrooptimierungen den mangelhaften Optimierer/Compiler ausgleichen?

    Ich habe es schon erlebt dass man effizient programieren wollte, man sich aber viele Nebenbedingungen einhandelte, das Design darunter Not litt und am Ende sogar langsamer war. Und darum bin ich vorsichtig in Sache Mikro-Optimierungen.


Anmelden zum Antworten