Aussehen
Einloggen
[c] [meta] [fefe] [erp]

/fefe/ – Fefes Kommentarfunktion


Antwort erstellen

(≤ 4)



[Zurück]

  • [l] PSA angesichts der aktuellen liblzma-Lücke: Es gibt ... Effe ## Mod Tue, 08 Apr 2025 12:19:30 GMT Nr. 153237
    PNG 800×1153 1.1M
    PSA angesichts der aktuellen liblzma-Lücke: Es gibt ein Google-Projekt namens wuffs [0] mit Parsern für einige Dateiformate. Das ganze Projekt ist recht spannend, denn die haben da einen Übersetzer gebaut und eine eingeschränkte Programmiersprache. Der Übersetzer nimmt nur Programme an, bei denen er die Abwesenheit von Speicherfehlern und Integerüberläufen beweisen kann.

    Die haben auch Dekompressionscode für lzw (GIF), inflate (gzip, zlib), bzip2 und lzma (xz, lzip). Die liefern ein Beispielprogramm namens mzcat mit, das ihre Library benutzt, und innerhalb eines Seccomp-Käfigs läuft, der nur read, write und exit erlaubt. Wenn also eine Lücke übrig bleibt und der Angreifer sie erfolgreich ausnutzt, kann er bloß Müll dekomprimieren, was er auch ohne den Angriff gekonnt hätte.

    Die Library hat keine Abhängigkeiten.

    Wenn ihr also mal Code schreiben müsst, der Daten dekomprimiert, und ihr wisst nicht, ob ihr denen trauen könnt oder nicht, dann nehmt wuffs.

    Oh, und: Deren Dekompressoren sind auch noch signifikant schneller als die Standard-Dekompressoren.

    Leider haben sie noch keinen zstandard-Dekompressor, aber der ist geplant.

    [0] https://github.com/google/wuffs

    https://blog.fefe.de/?ts=990bd317
  • [l] Felix Tue, 08 Apr 2025 13:38:10 GMT Nr. 153244
    xz war ja auch von Anfang an lahm am Bein, aber das erklärt Felix jetzt nicht nochmal.

    https://www.nongnu.org/lzip/xz_inadequate.html
  • [l] Felix ☎️ Tue, 08 Apr 2025 15:49:33 GMT Nr. 153255 SÄGE
    PSA: halt dein Maul.
  • [l] Felix Tue, 08 Apr 2025 21:18:09 GMT Nr. 153274
    bloß ein Bisschen Lesen und Schreiben. Ja klar.
  • [l] Felix Wed, 09 Apr 2025 03:03:57 GMT Nr. 153283 SÄGE
    >>153244
    Dieser Quatsch wird nicht sinnvoller, wenn man ihn mehrmals pfostiert, Antonio.
  • [l] Felix Wed, 09 Apr 2025 05:10:49 GMT Nr. 153288
    >>153283
    Der Punkt ist, daß das ein Projekt von Idioten ist.
  • [l] Felix ☎️ Wed, 09 Apr 2025 05:27:18 GMT Nr. 153289 SÄGE
    >>153283
    Fresse missgeburt.
  • [l] Felix Wed, 09 Apr 2025 08:46:51 GMT Nr. 153294 SÄGE
    >>153289
    Oh, die geistige Elite ist auch schon aufgewacht!
  • [l] libeberwurst Felix Wed, 09 Apr 2025 10:44:58 GMT Nr. 153304
    >>153237
    >libbastel
    >libhalbärschel
    >libfrickel
    >Ersatz: libwrang(el|le)
    >>..., but look how fast it is!1!!

    Irgendwann in den 1990-ern müssen wir(tm) falsch abgebogen sein.
    https://www.dstoecker.eu/xpkmaster.html

    Auch:
    >>The "Wuffs the Language" document has more information on how it differs from other languages in the C family.
    >>in the C family
    Endlich ein neuer C-Dialekt! Diesmal wird er alles richtigmachen!1!!
  • [l] Felix Wed, 09 Apr 2025 17:48:12 GMT Nr. 153351
    >>153304
    >-leberwurst
    Nicht schlecht.

    >Endlich ein neuer C-Dialekt! Diesmal wird er alles richtigmachen!1!!
    In dem Punkt (C-Dialektikeit) hält es sich noch in Grenzen:
    https://github.com/google/wuffs/blob/main/std/jpeg/decode_jpeg.wuffs

    >Irgendwann in den 1990-ern müssen wir(tm) falsch abgebogen sein.
    Sag das mal den OOP-Leuten.
  • [l] Felix Wed, 09 Apr 2025 18:03:40 GMT Nr. 153353 SÄGE
    >>153351
    >Hurrdurr OOP kagge
    Haskell-Spast oder sonst ein hässlicher Mongo? Steck dir deine Rekursion, inbes. die END-Rekursion in den ARSCH, du funktionaler Spast. Mongohurnsohn.
  • [l] Felix Wed, 09 Apr 2025 20:03:04 GMT Nr. 153359
    >>153353
    Trollbubi, dein Getrolle ist qualitativ noch zu hochwertig, daran erkennt man dich.
    Und nein, Felix hat mit seinem Pfosten ganz sicherlich nicht funktionale Programmierung loben wollen.
  • [l] Felix Thu, 10 Apr 2025 09:24:15 GMT Nr. 153385
    PNG 464×450 225.4k
    >>153359
    Wo er recht hat, hat er recht. Kann man nichts machen.
  • [l] Felix Thu, 10 Apr 2025 09:41:12 GMT Nr. 153389
    >>153385
    Ja und wo er net Recht hat, hatter net Recht. Bravo und herzlich willkommen im Tautologie Club, du bist gefeuert.
  • [l] xileF Felix Thu, 17 Apr 2025 15:33:15 GMT Nr. 154055
    >>153351
    Schöner Beinahe-Bekomm, übrigens.

    >https://github.com/google/wuffs/blob/main/std/jpeg/decode_jpeg.wuffs
    Interessant. Den Dekompressionsteil davon kann man(tm) leider nicht direkt mit dem von xpkHUFF (oder xpkHFMN) vergleichen:
    https://www.dstoecker.eu/xpklibs.html#HUFF

    Und:
    >https://github.com/google/wuffs/blob/main/std/jpeg/decode_jpeg.wuffs
    Naja, da kann man bspw. dies lesen:
    >
    if args.dst <> nullptr {
        // ...
    }

    Zur Laufzeit Null-Pointer so abfragen zu müssen, soll sicher sein? Für Herrn von Leitner vielleicht, der lebt schließlich davon, anderen solche Frickeleien um die Ohren zu hauen ...

    >>Irgendwann in den 1990-ern müssen wir(tm) falsch abgebogen sein.
    >Sag das mal den OOP-Leuten.
    Hmmm, inwiefern? Wurde OOP zu dieser Zeit signifikant schlechter?
  • [l] Felix Fri, 18 Apr 2025 01:21:26 GMT Nr. 154087
    GIF 400×556 10.2k
    PNG 1475×581 186.9k
    >>154055
    >soll sicher sein?
    Wenn der Kompiler der Sprache darauf prüft, dass es außerhalb dieser Abfrage keine Dereferenzierung des Nullzeigers gibt (und generell Zeiger nur auf Null oder auf gültige Speicherbereiche zeigen können), ja.

    >Hmmm, inwiefern? Wurde OOP zu dieser Zeit signifikant schlechter?
    Nicht OOP wurde signifikant schlechter, sondern es hat sich in den 90ern durch die "OOP-Leute" in der breiten Masse verschoben, was mit "OOP" überhaupt gemeint war. Vorher Nachrichten mit Mailboxen, Identifizierung z.B. via Namen, späte Bindung (so spät, dass Nachrichtentypen/Methoden auch einfach gar nicht existieren können oder zur Laufzeit hinzukommen können), ggf. eine Registratur zum Auffinden der Objekte zu einem Namen. Nachher ... eben das, was man von C++ oder Java kennt.

    Felix wirft noch zwei semi-relatierte Bildchen rein, die Felix nachdenken ließen.
  • [l] Felix Fri, 18 Apr 2025 07:10:19 GMT Nr. 154090
    >>154087
    >No one sane uses inheritance, it was and is a bad idea.
    Was für eine bescheuerte Aussage. Fast so schlimm wie >Hurr Dur Goto böse. Felix kannte in den 2000ern in deutschen Programmierforen sogar Leute, die sämtliche break, continue und return vermieden, weil es zu sehr wie goto war. Waren natürlich auch alles "Clean Code"-Anhänger. Keine Funktion darf länger als 7 Zeilen sein!!!11 Das ist Gottes Gebot.
  • [l] Felix Fri, 18 Apr 2025 07:12:16 GMT Nr. 154091
    "Vererbung" existiert übrigens auch nicht. Es ist bloß ein Struct mit einem Pointer auf ein anderes Struct mit Pointern zu Funktionen, denen man ein Pointer auf das erste Struct übergibt.
  • [l] Felix 🚽 Fri, 18 Apr 2025 07:21:58 GMT Nr. 154093 SÄGE
  • [l] OOP-Ranz-Rant Felix Sat, 19 Apr 2025 15:49:12 GMT Nr. 154152
    WEBM 490×360 0:54 1.1M
    >>154090
    >>No one sane uses inheritance, it was and is a bad idea.
    >Was für eine bescheuerte Aussage.
    Er hat Recht mit dem Kern, dass Vererbung schlecht ist. Das "fast immer" spart sich Felix mal.
    Dass da so echte Schotten wie "no one sane uses..." kommen, ist eher dem Ortsdialekt des dortigen Forums geschuldet (die Einzeiler müssen reinknallen).

    >Clean Code
    Davon ist der Pfostierer dort doch meilenweit entfernt, weil das doch auch nur wieder ein weiterer spukiger >Spook ist. Aber dazu später mehr...

    Felix nimmt im Folgenden mal die C++-Bezeichnungen, damit klarer ist, was gemeint ist. Da muss man mal schauen, was tatsächlich am Ende rauskommt.

    Was Vererbung da eigentlich ist:
    *Man hat ein struct, und will dem struct ein paar Variablen hinzufügen.
    *Man kann der Kotbasis einige Funktionen hinzufügen, die auf dem so entstandenen struct arbeiten können (ein Zeiger aufs struct als impliziten ersten Parameter der Funktion)
    *Man kann dem struct eine Funktionszeiger-Tabelle geben, von Funktionen, die dann ebenso auf dem so entstandenen struct arbeiten können

    Ja leck die Wand an. Wie man den ersten Punkt erfüllt, ist klar: Man fügt dem struct die Sachen hinzu. Von mir aus auch mit einem weiteren struct. Ob man das nun "Komposition" nennt oder manuell macht oder wie auch immer bewerkstelligt, ist dafür egal. Der zweite Punkt ist auch trivial: Man schreibt halt die Funktion als freie Funktion.

    Für den letzten Punkt: Es wird fast nie gebraucht, da man die Elemente nicht (z.B. in ein einziges Array) zusammenwerfen muss. Statt in ein Array alle abgeleitete Klassen Derived1, Derived2, Derived3 reinzuschmeißen, nimmt man halt drei Arrays. Das erledigt bereits die meisten Fälle. Wenn man die Elemente doch zusammenwerfen muss, ist ein enum + switch fast immer die bessere Wahl, welche auch besseren Kot generiert (weil zur Kompilierzeit alle abgeleiteten Varianten bekannt sind, im Gegensatz zu Funktionszeigern, da kommen dann manche mit dem CRTP an).

    Der einzige Fall, wo das nicht klappt, ist, wenn jemand anderes, außerhalb der eigenen Organisationseinheit (→ Conway's Law), auch in Zukunft noch weitere Klassen ableiten kann, und dem obigen Array nun plötzlich noch einige Derived4 hinzugefügt werden. Dann muss man tatsächlich Zeiger auf Elemente speichern (weil man die Größe von Derived4 nicht kennt), dann muss man tatsächlich auch eine Virtuelle-Funktionszeiger-Tabelle benutzten. Denn man weiß zur Kompilierzeit noch nicht, welche abgeleiteten Klassen es noch alles geben wird.

    Und dieser Fall tritt ... extrem selten, praktisch nie auf? Vielleicht, wenn die Anwendung sowieso ein krasses Plugin-System (Programmier-Brummwort der frühen 2000er!) hat, bei dem eine .dll/.so dynamisch geladen wird, und nun tatsächlich ein paar von externen Leuten definierten Elemente in den eigenen Arrays rumschwirren können?

    Übrigens haben die OOP-Apologeten das auch bemerkt: "Erweiterbarkeit" hieß dabei ursprünglich, dass tatsächlich _zur Laufzeit_ weitere abgeleitete Klassen hinzukommen können, von externen Dritten. Die Ummünzung von "Erweiterbarkeit" im Sinne von "Wartbarkeit" und "Clean Code" kam erst später, und hat dann schlussendlich auch die technische Grundlage verloren. Zur Laufzeit kann es weitere Klassen geben? Kann man technisch überprüfen. Der Kot ist "sauber"? Herzlich willkommen in der Laber-Anstalt.

    Übrigens ist es verboten, Arrays mit Vererbung zu benutzen, ohne dass jedes Array-Element ein Zeiger ist. Das hat C mit unions bereits Jahrzehnte vorher gelöst (und andere Sprachen natürlich noch früher). Für den extrem seltenen Fall des externen, erst bei Laufzeit bekannten Kots direkt alle Arrays zu Zeiger-Arrays machen? Mit Kanonen auf Spatzen, danke Vererbung.

    Wohlgemerkt geht das natürlich auch alles ohne Klassen, OOP, C++, sondern so ein obiges Array aus Zeigern und obige Funktionszeiger kann man auch einfach so benutzen. Dass Dinge wie die Implementierung eines Dateisystems nicht nur die Anzahl möglicher Implementierungen dynamisch hält, sondern auch noch die Menge der möglichen _Methoden_ dynamisch hält, zeigt auf, dass OOP selbst bei der Flexibilität vom uralten C überholt wird.

    Was bleibt von OOP noch übrig? Ein paar structs, Funktionen, enums und switches? In 100 Jahren mal ein Funktionszeiger?
    Das ist mit >Spook gemeint, es ist hinterm Vorhang nichts da. Aber wehe du benutzt OOP nicht, dann bist du alt und zerdengelt, und irgendjemand beschwört die spukigen OOP-Geister herauf. Spuk! Spuk! Dein Kot ist schlecht, weil ... weil da zu wenig OOP drin ist! Achso, was ist konkret schlecht? Uh äh, Spuk! Spuk! Büdde nicht bemerken, dass OOP nicht real ist, wenn man den Vorhang hochzieht.
  • [l] Felix ☎️ Sat, 19 Apr 2025 17:03:10 GMT Nr. 154155 SÄGE
    > dann muss man tatsächlich auch eine Virtuelle-Funktionszeiger-Tabelle benutzten.
    Das ist das Problem des Compilerbauers, nicht meines.
  • [l] Felix Sat, 19 Apr 2025 18:50:53 GMT Nr. 154158
    >>154155
    Und? Der Kompilerbauer gibt dir ja die Virtuelle-Funktionszeiger-Tabelle. Problem ist, dass der Fall so extrem selten auftritt, dass man das gerade nicht vom Kompiler braucht.

    Seitennotiz: "Ist Problem des Kompilerbauers" klappt übrigens auch im Allgemeinen nicht, weil Nullkostabstraktionen eine Lüge sind und das damit zurück in den Schoß des Programmierers fällt, aber das führt schon wieder von OOP weg.
  • [l] Felix Sat, 19 Apr 2025 19:27:16 GMT Nr. 154161
    >>154152
    Ziemlich viel Text für ziemlich wenig Substanz. Was soll dein Argument sein? OOP ist immer blöd weil du so viel labern kannst?
  • [l] Felix Sat, 19 Apr 2025 19:38:26 GMT Nr. 154162 SÄGE
    >>154161
    Netter Versuch :3
  • [l] Felix Sat, 19 Apr 2025 21:05:54 GMT Nr. 154163
    >>154152
    Schon klar, Polymorphismus zur Laufzeit braucht man eigentlich gar nicht. Was Arrays mit Vererbung zu tun haben, bleibt auch dein Geheimnis.

    Dauerhafter C++-Gebrauch macht doch echt die Birne weich.
  • [l] Felix Mon, 21 Apr 2025 20:15:13 GMT Nr. 154245
    >>154163
    >Schon klar, Polymorphismus zur Laufzeit braucht man eigentlich gar nicht.
    Tatsächlich braucht man den Polymorphismus nicht in der Ausprägung, der man in C++ begegnet. Wie schon oben geschrieben:
    *Man nimmt mehrere Arrays
    *Man nimmt ein switch
    *Verbleibender, extrem seltener Fall: Man hat externe Programmierer bei Drittanbietern außerhalb der Organisationseinheit, die die aufzurufenden Funktionen schreiben, die zur Kompilierzeit noch gar nicht bekannt sind, Plugin-System-Style. Da braucht man Zeiger-Array und Virtuelle-Funktionszeiger-Tabellen.
    *Verbleibender, noch extrem seltenerer Fall: Man muss auch die Anzahl der Methoden zur Laufzeit dynamisch halten. Geht mit OOP garnicht, hat C bereits bei Dateisystemen vor Äonen gelöst.

    >C++
    Ist tatsächlich für einen Vergleich zwischen OOP und Nicht-OOP sehr brauchbar, da man im Vergleich zu einigen anderen Sprachen die Wahl hat, ob man die OOP-ismen benutzt.

    >Was Arrays mit Vererbung zu tun haben, bleibt auch dein Geheimnis.
    #include <stdio.h>
    
    struct Base
    {
    	int member1;
    };
    
    struct Derived1 : public Base
    {
    	int member2;
    	int member3;
    };
    
    int diff1(Base * arr, size_t n)
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		result += arr[i+1].member1 - arr[i].member1;
    
    	return result;
    }
    
    int diff2(Derived1 * arr, size_t n)
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		result += arr[i+1].member1 - arr[i].member1;
    
    	return result;
    }
    
    int main(int /*argc*/, char ** /*argv*/)
    {
    	Derived1 arr[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		arr[i].member1 = 1;
    		arr[i].member2 = 5;
    		arr[i].member3 = 1337;
    	}
    
    	printf("%d\n", diff1(arr, 8));
    	printf("%d\n", diff2(arr, 8));
    
    	return 0;
    }


    Die Felixe dürfen mal die Ausgabe raten (ja, dort steht member1 minus member1).
    Dort ist noch nicht einmal ein Typ-Cast im Kot drin, Warnungen gibt es auch mit -Wall -Wextra -Wpedantic keine.
    Dahinter steckt aber ein grundlegendes, von C++ vollkommen unabhängiges Problem.
  • [l] Felix Mon, 21 Apr 2025 20:40:30 GMT Nr. 154246
    >>154245
    +/-1 weil das Array im Base nicht populiert wird? Wenig überraschend.
  • [l] Felix Mon, 21 Apr 2025 21:09:16 GMT Nr. 154249 SÄGE
    >>154245
    >*Man nimmt mehrere Arrays
    Vererbung hat immer noch nichts mit Arrays zu tun.

    >*Man nimmt ein switch
    Warum glauben Leute nur, dass sie Vererbung bräuchten? Man kann doch auch einfach bei jeder.png Änderung alle.jpg Verwendungen bei allen.jpg Benutzern anpassen. Es ist so ein Fach!

    >*Verbleibender, extrem seltener Fall: Man hat externe Programmierer bei Drittanbietern außerhalb der Organisationseinheit, die die aufzurufenden Funktionen schreiben, die zur Kompilierzeit noch gar nicht bekannt sind, Plugin-System-Style. Da braucht man Zeiger-Array und Virtuelle-Funktionszeiger-Tabellen.
    Oder man möchte einfach das Verhalten eines Objekts befristet anpassen, ohne dabei jedes Mal durch die gesamte Kotbasis dackeln zu müssen.

    >*Verbleibender, noch extrem seltenerer Fall: Man muss auch die Anzahl der Methoden zur Laufzeit dynamisch halten. Geht mit OOP garnicht, hat C bereits bei Dateisystemen vor Äonen gelöst.
    Wo fängt man bei so viel Verwirrung überhaupt an?

    >Dahinter steckt aber ein grundlegendes, von C++ vollkommen unabhängiges Problem.
    Nein, dahinter steckt das ausschließlich von C++ abhängige Problem, dass C++ Object Slicing vorschreibt.
  • [l] Felix Mon, 21 Apr 2025 21:10:53 GMT Nr. 154250
    >>154245

    Die Größe von Base ist 1, also ist arr[1].member1 das zweite int im Speicher, aber weil Base in Wirklichkeit Derived1 mit Größe 3 ist, ist das zweite int im Speicher member2 vom ersten Arrayelement, nicht member1 vom zweiten Arrayelement.
  • [l] Felix Mon, 21 Apr 2025 22:28:58 GMT Nr. 154253
    >>154245
    Also Felix hatte in 20 Jahren Programmieren irgendwie noch nie dein komisches Problem mit Arrays. Wirkt ziemlich konstruiert. Auch: Wieso denken alle immer, OOP = C++?

    >Dahinter steckt aber ein grundlegendes, von C++ vollkommen unabhängiges Problem.
    Nein, das ist ein reines C++-Problem, weil C++ halt an C drangeflanscht wurde.
  • [l] Felix Tue, 22 Apr 2025 04:03:37 GMT Nr. 154264
    JPG 207×243 7.9k
    Oke also wenn man mit Absicht Scheiße baut dann ist es scheiße.
    Das ist ja unglaublich Bob!

    Kuck mal hier ein Fahrrad mit viereckigen Rädern — also sind alle Fahrräder per se dumm weil ich so schlau bin und ein dummes Beispiel gefunden habe.
  • [l] Felix Tue, 22 Apr 2025 05:14:19 GMT Nr. 154266
    >>154249
    > Vererbung hat immer noch nichts mit Arrays zu tun
    Polymorphismus hat sehr wohl was mit Arrays zu tun.
  • [l] Felix Tue, 22 Apr 2025 06:37:46 GMT Nr. 154267
    >>154266
    >Alles hat mit allem zu tun
    >Alles hängt irgendwie miteinander zusammen
    Bubi glaubt heute wirklich schlau zu sein.
  • [l] Felix Tue, 22 Apr 2025 06:54:57 GMT Nr. 154268
    >>154266
    Ob du behindert bist?
  • [l] Felix Tue, 22 Apr 2025 08:41:49 GMT Nr. 154276
    >>154091
    Vererbung funktioniert doch ganz einfach mit Structs, siehe GObject.

    https://de.wikipedia.org/wiki/GObject
  • [l] Felix Tue, 22 Apr 2025 08:51:31 GMT Nr. 154277
    Das Problem mit dem OOP der 90er wurde mal hier am Beispiel von Java beschrieben:
    http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html

    Nicht ohne Grund haben Rust und Go beide keine Klassen mehr.
  • [l] Felix Tue, 22 Apr 2025 09:04:27 GMT Nr. 154278 SÄGE
    >>154267
    >>154268
    Wie wollt ihr kernbehinderten Mongos denn Typurteile über Inhalte von Arrays Polymorpher Typen mit Vererbung fällen? Ach, geht halt nicht, weil Arrays haben mit Vererbung nix zu tun.

    Ihr saudummen Hurensöhne solltet einfach mal die Fresse halten, anstatt hier dumm rauszulabern. Was für Missgeburten hier rumhängen. Erkennbar noch nie Wohlgetyptheit von irgendwas nachgewiesen, aber hier rumpöbeln. Gesindel.
  • [l] Felix Tue, 22 Apr 2025 09:23:38 GMT Nr. 154279
    >>154278
    Ich bin nicht der Arrayfelix.

    t. GObjectfelix
  • [l] Felix Tue, 22 Apr 2025 09:52:00 GMT Nr. 154283
    >>154152
    Man macht das gar nicht mit Zeigern, man macht das direkt inline.

    Beispiel:
    typedef struct {
      int weight;
      int age;
    } pet;
    
    typedef struct {
      pet p;
      char *name;
    } dog;
    
    int get_age(pet *p) {
      return p->age;
    }
    
    dog *new_dog(int weight, int age, char *name) {
      dog *rex = malloc(sizeof(dog));
      ((pet *)rex)->weight = weight;
      ((pet *)rex)->age = age;
      rex->name = name;
      return rex;
    }
    
    dog *rex = new_dog(40, 5, "Rex");
    int age = get_age(rex)
    


    usw usf.
  • [l] Felix Tue, 22 Apr 2025 09:55:16 GMT Nr. 154284
    >>154245
    Das ist Blödsinn. Der C-Standard gibt nicht vor, wie Arrayelemente im Speicher liegen. Du kannst nicht einfach Structs als Arrays betrachten.
  • [l] Felix Tue, 22 Apr 2025 10:02:32 GMT Nr. 154285
    >>struct Derived1 : public Base
    Das ist C++. Das gibt es in C nicht.
  • [l] HTTP 418 Felix 🫖 Tue, 22 Apr 2025 10:23:25 GMT Nr. 154290
    >>154245
    >Dort ist noch nicht einmal ein Typ-Cast im Kot drin
    Lüge :3

    Abgesehen davon: komisch alte C Sachen (Zeiger, brrrr) mit C++ mischen und sagen das wäre aber ein grundlegendes OOP-Problem ist falsch und dumm.

    Korrekterweise würde man einen Container von Base-Zeigern übergeben würg! und Probleme werden keine gehabt.
  • [l] Felix Sun, 27 Apr 2025 21:22:03 GMT Nr. 154893
    JPG 2252×4000 1.7M
    >>154246
    Nein, damit hat das leider nichts zu tun.

    >>154249
    >Vererbung hat immer noch nichts mit Arrays zu tun.
    Das Gegenteil ist der Fall: Drösel das mit den Subtypen und den Arrays doch mal auf.

    >Warum glauben Leute nur, dass sie Vererbung bräuchten? Man kann doch auch einfach bei jeder.png Änderung alle.jpg Verwendungen bei allen.jpg Benutzern anpassen. Es ist so ein Fach!
    In der Tat ist es so einfach:
    Die Sache der Änderungen ist mit Kapselung gegessen. Wow, jede der 1 Änderungen, wie soll man sich von so viel Arbeit erholen?
    Die Sache mit _neuen_ Klassen ist dank Compiler-Warnung gegessen.

    Wobei Kapselung auch schon wieder spukig ist, und dann die "Experten" kommen, was "echte" Kapselung sei. Man könnte auch ganz unspukig sagen: Man schreibt ein paar Funktionen (wau).

    >Oder man möchte einfach das Verhalten eines Objekts befristet anpassen, ohne dabei jedes Mal durch die gesamte Kotbasis dackeln zu müssen.
    Du passt dann die Funktionalität an der Stelle an, an der die Funktionalität implementiert ist.
    Pro-Tipp: Du benennst einfach den Typ um.
    >Oder
    Oder man will schnell einen Überblick über diese Funktionalität bei allen Klassen haben.
    Oder man möchte eine Funktionalität von mehreren Klassen anpassen, ohne an mehrere Stellen der Kotbasis dackeln zu müssen.
    Oder man möchte den Klassen eine neue, unterschiedliche Funktionalität hinzufügen, ohne dabei jedes Mal zu drölf Kotstellen zu gehen.
    Oder man möchte gleiche Funktionalität erst gar nicht über mehrere Klassen hinweg sprotzeln. Zu welcher Klasse soll das dann gerade noch mal gehören?

    Dass ist alles meilenweit davon entfernt, dass man Vererbung bräuchte, weil, wenn man schaut, was Vererbung in der Implementierung eigentlich ist, Vererbung am Ende als alleiniges Alleinstellungsmerkmal eine einfache Generierung/Aufruf von einer Virtuellen-Funktionszeiger-Tabelle bringt. Alles andere geht trivial ohne Vererbung, insbesondere, wo die Funktionalität liegt. Und insbesondere geht auch alles aus deiner Auflistung ohne Vererbung. Nur hat man dann die Freiheit, es auch anders zu organisieren, oder es gleich zu organisieren. Wegen letzterem ist an dem Punkt argumentativ dann Ende.

    Übrigens führt Vererbung gerade zu weniger Kapselung. Denn Vererbung verbietet es, virtuelle Funktionen nicht als Teil der Klasse zu implementieren. Deswegen ist hier
    >How Non-Member Functions Improve Encapsulation
    https://www.aristeia.com/Papers/CUJ_Feb_2000.pdf
    auch der erste Fall make f a member function of C, was eben dieser Vererbungs-Beschränkung geschuldet ist. Hätte man diese Beschränkung nicht, würde man es lieber, falls möglich, als freie Funktion implementieren, und damit bessere Kapselung haben. Freie Funktionen (falls möglich) sind besser, aber Vererbung verwehrt einem das.

    In Bezug z.B. auf WARMED ist das switch überlegen. Eben auch, weil es einem die Wahl lässt, wohin man die Funktionalität tut.
    Es hat schon einen Grund, warum OOP-Klassen-Kot eine elendige Rumspringerei (mit diffus über die Kotbasis verteilter Funktionalität) ist.

    Übrigens findet es Felix schön, dass du direkt den OOP-Spook heraufbeschwört hast. "Du machst keine Vererbung? Dann _muss_ dein Kot scheiße sein!" Nein, eben nicht.

    >Wo fängt man bei so viel Verwirrung überhaupt an?
    Vermutlich da, wo du ein erstes Argument hervorbringst. Vererbung und OOP (zumindest in den meistgenutzten Implementierungen, wovon Felix idF ausgeht) hat ein Problem damit, wenn die möglichen Methoden zur Kompilierzeit nicht bekannt sind. Neue Typen? Einfach, sogar zur Laufzeit von Drittanbietern. Neue Methoden? Katastrophe, Neukompilation notwendig. Viel Spaß, mit allen Drittanbietern zu sprechen.

    >Nein, dahinter steckt das ausschließlich von C++ abhängige Problem, dass C++ Object Slicing vorschreibt.
    Das ist dabei nicht das Problem. unions sind die Könige des Object Slicings, nur eben anders. unions können auch eine common initial sequence haben, auf die man "runterslicen" (oder "kleinslicen") kann, und natürlich bei Bekanntheit des Typs auch wieder "hochslicen" kann (vgl. Downcasting). Trotzdem haben unions nicht dieses Problem, an Object Slicing liegt diese Sache nicht.

    Hier mal die Killer-Frage: Warum das überhaupt machen, wenn man das, was Vererbung real in der Implementierung tatsächlich als Alleinstellungsmerkmal bringt (Zeiger auf VTable, damit Reinschleusen zur Kompilierzeit unbekannter Typen unbekannter Größe durch Drittanbieter möglich), fast nie braucht?

    >>154253
    Lass mich raten: Du hast statt normalen Arrays dann Zeiger-Arrays (oder Container, die dann Zeiger benutzen) genommen?
    Es ist ein allgemeineres Problem, das über Arrays (und C++) hinausgeht.

    >>154264
    Das "mit Absicht Scheiße bauen" könnte man so OOP vorwerfen, richtig. "Wir machen bei Vererbung normale Arrays karpott" - gute Idee (Sarkasmus).
    Lass mich raten: Du machst immer Zeiger-Arrays?
    >
    Dein Gedankenstrich ist zu lang, im Englischen wird ein Geviertstrich benutzt (aber ohne Leerzeichen drumrum), im Deutschen ein Halbgeviertstrich.
    Hat dein Pfosten eine LLM in Englisch geschrieben und das wurde dann ins Deutsche übersetzt?

    >>154250
    >>154266
    Klug-Felix.

    >>154267
    >>154278
    Aber du bist einer heißen Sache auf der Spur. Pro-Tipp: Man muss bei Arrays noch nicht einmal ein vollständiges Typurteil für die Elemente fällen, wenn VTables benutzt werden. Sondern: Worüber muss man vor VTable-Aufruf ein Urteil fällen? Wie kann man dies allgemein fällen?

    >>154284
    Felix denkt gerade über deine Bezeichnung "Structs als Arrays" nach, aber der Kot von diff2 ist legal und wird mit Array-zu-Zeiger-Zerfall so auch schon in C gemacht.
    Um mal bei deiner Bezeichnung zu bleiben: Man darf schon "Structs als Arrays" betrachten, aber nicht misachten.

    >>154285
    Das ist richtig. Und man darf es nicht unerwähnt lassen: Zum Glück.

    >>154290
    >Lüge :3
    Richtig :3 Felix korrigiert zu: "Expliziter Typ-Cast" :3

    >Korrekterweise würde man einen Container von Base-Zeigern übergeben
    Du bist der heißen Sache ganz heiß auf der Spur.
    >würg!
    Korrekt!
  • [l] Felix Sun, 27 Apr 2025 22:26:10 GMT Nr. 154894
    >>154893
    >Lass mich raten: Du hast statt normalen Arrays dann Zeiger-Arrays (oder Container, die dann Zeiger benutzen) genommen?
    Sowas macht Felix automatisch in solchen Fällen ohne drüber nachzudenken. Felix' erste Programmiersprache war Delphi, dort sind alle Objekte Zeiger und die Daten leben auf dem Heap. In den meisten anderen objektorientierten Programmiersprachen ist das genauso, z.B. in Java (Felix ist kein Lüfter von Java, nur um das klarzustellen).

    Es ist und bleibt ein C++-Problem, weil "Objektorientierung" in C++ eben nur stümperhaft an C drangeflanscht wurde. Ja, es ist schön, dass man in C++ Objekte auf dem Stack anlegen kann oder innerhalb von anderen Strukturen. Aber es hat halt auch manchmal Nachteile wie in deinem Fall. Da muss man eben aufpassen. C++ und C sind bekannt und berüchtigt dafür, dass man sich leicht in den Fuß schießen kann. Das hat überhaupt nichts mit Objektorienterung oder Vererbung zu tun.
  • [l] Felix Sun, 27 Apr 2025 22:30:19 GMT Nr. 154895
    JPG 560×395 201.1k
    >>154894
    >Heap
    Oder löblich: Halde, wie Mr. Tichy, Professor für Zangendeutsch an der Uni Karlsruheam KIT, sagen würde

    >Stack
    Löblich: Keller
  • [l] Felix Sun, 27 Apr 2025 22:36:06 GMT Nr. 154898
    >>154895
    Ich mag seine Raumflugzeitgeschichte sehr.

    https://www.youtube.com/watch?v=A9D_PlfpBH4&list=PLxP-RRl_T7sPM3n-okLQiTjvwMmcGEzUx
  • [l] Felix Sun, 27 Apr 2025 23:08:47 GMT Nr. 154900
    >>154898
    Ziemlich authentischer Slawenakzent, nicht schlecht. Hat man selten.
  • [l] Felix Sun, 27 Apr 2025 23:33:35 GMT Nr. 154901
    >>154893
    Du redest viel, aber es ist viel Dung dabei.
    Felix sieht das wie die anderen Felixe: dein künstliches Problem ist ein C++ Problem und kein allgemeines OOP-Problem.
  • [l] Felix Sun, 27 Apr 2025 23:47:31 GMT Nr. 154908
    >>154901
    kein OOP - kein Problem
  • [l] Felix Sun, 27 Apr 2025 23:57:56 GMT Nr. 154910
    >>154908
    Stell dich dein simples Beispiel mal in einer anderen OOP-Sprache wie Java dar.
    Da das Problem deiner Behauptung nach ein reines OOP-Problem sey und kein C++-Problem sollte das dir nicht schwer fallen.
    Außer du bist nur ein dummer Schwätzer aber dann wäre dein ganzer Trollfaden in Gefahr °_°
  • [l] Felix Sun, 27 Apr 2025 23:58:31 GMT Nr. 154911 SÄGE
    >>154910
    >Stell dich
    <Stell doch
  • [l] Felix Mon, 28 Apr 2025 02:39:51 GMT Nr. 154931
    >>154910
    Felix reist mit C und Golang, von euren dummen OOP-Sachen ist er nicht betroffen. Ein wenig Kapselung, Polyamore bei Signaturen, und Strukturen mit Funktionen sind bei Golang schon okäy.
  • [l] Felix Mon, 28 Apr 2025 07:02:21 GMT Nr. 154966
    >>154931
    Dann ist das Problem also rein aus C++ und kein pures OOP-Problem. Vielen Dank für deine Mitarbeit 💞
  • [l] Felix Mon, 28 Apr 2025 10:34:30 GMT Nr. 154970
    >>154966
    Ach, Binärfelix, lies doch, was du gerade geschrieben hast, noch mal.
    Falls du deinen Fehler nicht erkennst, denk über die Bedeutung von "rein aus" und "kein pures", und beantworte die Frage, in wie fern diese beiden Begriffe in einem direkten Widerspruch zu einander stehen.
  • [l] Felix Mon, 28 Apr 2025 14:26:58 GMT Nr. 155000
    >>154970
    Du willst also deinen C++ Kot welcher ein allgemeines OOP-Problem demonstriert nicht in einer anderen OOP-Sprache wie Java konvertieren? Sollte doch möglich seyn, weil es ein allgemeines OOP-Problem ist und kein spezifisches C++-Problem.
  • [l] Felix Mon, 28 Apr 2025 17:32:42 GMT Nr. 155022
    >>155000
    Ich will gar nichts, hab mit der Diskussion eigentlich gar nichts am Hut, bin nur darüber gestolpert, dass dein Text sich selbst widerspricht. Und du hast es offensichtlich nicht erkannt, womit ich auch all deine anderen Argumente für fragwürdig halte und die Gegenseite eher unterstützen würde.
  • [l] Felix Mon, 28 Apr 2025 20:07:53 GMT Nr. 155035
    >>155022
    Wie auch immer du zu entgleisen versuchst, >>154245 liegt an C++ und nicht an OOP.
  • [l] Felix Mon, 28 Apr 2025 21:55:38 GMT Nr. 155040
    JPG 3000×2000 747.4k
    >>154894
    >Sowas macht Felix automatisch in solchen Fällen ohne drüber nachzudenken.
    Das ist in der Tat richtig, Felix macht es auch in "OOP-Sprachen" automatisch, da es auch der idiomatischen Sprachnutzung entspricht und alle Beispiele so verfasst sind.

    >>154966
    >>155000
    Dieser Felix ist nicht der referenzierte Felix.

    >>154910
    >Stell dich dein simples Beispiel mal in einer anderen OOP-Sprache wie Java dar
    Überhaupt gar kein Problem! Hier die gleiche Sache ...written exclusively in ADA:

    with Ada.Text_IO; use Ada.Text_IO; with Ada.Unchecked_Conversion; use Ada;
    
    procedure Main is
        type Base is tagged record
            member1 : Integer;
        end record;
        
        type Derived1 is new Base with record
            member2 : Integer;
            member3 : Integer;
        end record;
    
        type BaseArray is array (1 .. 8) of Base;
        type Derived1Array is array (1 .. 8) of Derived1;
        function ToBaseArray is new Unchecked_Conversion(Derived1Array, BaseArray);
    
        function diff1(arr : in BaseArray) return Integer is
            result : Integer := 0;
        begin
            for i in 1 .. arr'Last - 1 loop
                result := result + arr(i+1).member1 - arr(i).member1;
            end loop;
            
            return result;
        end diff1;
    
        function diff2(arr : in Derived1Array) return Integer is
            result : Integer := 0;
        begin
            for i in 1 .. arr'Last - 1 loop
                result := result + arr(i+1).member1 - arr(i).member1;
            end loop;
            
            return result;
        end diff2;
    
        arr : Derived1Array;
    begin
        for i in arr'Range loop
            arr(i).member1 := 1;
            arr(i).member2 := 5;
            arr(i).member3 := 1337;
        end loop;
    
        Put_Line(Integer'Image(diff1(ToBaseArray(arr))));
        Put_Line(Integer'Image(diff2(arr)));
    end Main;


    Lass es krachen auf:
    https://onecompiler.com/ada/

    Java: Wie macht man da noch mal ein normales Array von einer Klasse (statt einem Zeiger/Referenzen-Array)? :3

    >>154894
    >Es ist und bleibt ein C++-Problem
    >>154901
    >dein künstliches Problem ist ein C++ Problem und kein allgemeines OOP-Problem.
    >>154910
    >Da das Problem deiner Behauptung nach ein reines OOP-Problem sey und kein C++-Problem sollte das dir nicht schwer fallen.

    Jetzt wird Felix mal die Rahmenbedingungen festzurren, um zu zeigen, dass es doch ein allgemeines OOP-Problem ist, von C++ unabhängig. Rahmenbedinungen:
    - Datenstruktur Array: Kontinuierlich allozierter Speicher, Zugriff in O(1) ohne Indirektionen. Eben das, was man als normales Array bezeichnet (von mir aus "Werte-Array", um es vom idF angesprochenen "Zeiger-Array" abzugrenzen).
    - Vererbung: Es können weitere Klassen als Subtypen mit zur Kompilierzeit unbekannter Größe hinzukommen (oder bei Sprachen ohne Kompilierung: zur Allokationszeit des zuvor genannten Arrays, im Folgenden statt "zur Kompilierzeit" dann durch "zur Allokationszeit" ersetzen). Dies ist die normale Vererbung mit OOP-Erweiterbarkeit, die von OOP-Sprachen so fast ausschließlich umgesetzt wird, und bei der ein Drittanbieter auch zur Laufzeit weitere Typen unbekannter Größe zur Vererbungshierarchie und als Folge auch z.B. zu den eigenen Arrays im Programm hinzufügen kann.

    Tja, und da hat man dann ein Problem, denn beides zusammen geht nicht. Die beiden obigen Punkte sind auch komplett sprachunabhängig, es kann lediglich sein, dass eine Sprache irgendwas vom obigen noch nicht einmal unterstützt, dann ist es aber erst recht nicht möglich.

    Dreh- und Angelpunkt ist, dass die Größen der Typen bekannt sein müssen, und zwar zur Kompilierzeit. Dann kann man den Zugriff des Werte-Arrays tatsächlich in O(1) ohne Indirektionen implementieren: Hätte man eine Hierarchie Base, Derived1 von Base, Derived2 von Base, etc., so kann man alle Typen union-mäßig zusammenpacken, und die Größe eines Elements wäre max(Base, Derived1, Derived2, ...). Aber sobald man Typen unterschiedlicher Größe im Array hat, muss auf eine der obigen Rahmenbedingungen verzichtet werden: Sei es auf die Indirektion (Zeiger-Array, wird so von fast allen OOP-Sprachen gemacht), sei es der O(1)-Zugriff (z.B. zusätzliche Tabelle mit Präfix-Summe für den Lookup), sei es das Hinzufügen neuer Klassen mit unbekannter Größe nach der Kompilierzeit.

    So, und nun fragt man sich, auf was man am ehesten verzichten könnte: Wie oft wurden Werte-Arrays in Programmen vor der OOP-Zeitrechnung benutzt (sehr häufig, man könnte auch sagen, ein Kernfeature)? Wie wichtig ist das Hinzufügen von neuen Klassen zur Vererbungshierarchie nach der Kompilierzeit z.B. durch Drittanbieter (fast nirgends notwendig)? Und nun zum Kern: Felix findet die Entscheidung, Werte-Arrays aufzugeben, um Drittanbietererweiterung zur Laufzeit zu ermöglichen, für falsch.

    Das wäre alles vermeidbar gewesen, wenn man bei Vererbung auf das Hinzufügen neuer Klassen nach der Kompilierzeit verzichtet hätte. Eine Sache, die praktisch niemand braucht, aber welche genau die Benutzung von Werte-Arrays mit Vererbung im OOP-Style inkompatibel macht.

    Vererbung im OOP-Style und Werte-Arrays geht nicht zusammen. Es wäre mit anderen Prioritäten einfach zu vermeiden gewesen.
    Felix nennt es den Geburtsfehler von Vererbung in OOP.
  • [l] Felix Mon, 28 Apr 2025 22:38:30 GMT Nr. 155041
    >>155040
    <5.9.1 Unchecked Conversion - Chapter 5 - Ada 95 QUALITY AND STYLE Guide
    <An unchecked conversion is a bit-for-bit copy without regard to the meanings attached to those bits and bit positions by either the source or the destination

    Also gut:
    >Baue mit Absicht Scheiße
    <Es ist Scheiße
    >Oh mein Gott, warum ist es Scheiße?

    Ach und in Java geht's gar nicht?
  • [l] Felix Mon, 28 Apr 2025 22:40:29 GMT Nr. 155042 SÄGE
    >>155040
    C-Raute hat dein künstliches Problem übrigens gelöst.
  • [l] Felix Tue, 29 Apr 2025 00:22:52 GMT Nr. 155045 SÄGE
    >>155040
    >Unchecked_Conversion()
    Hmm, ich frage mich, ob diese Funktion wohl irgenwdie gefährlich sein könnte?

    Komm, lass es gut sein. Es wird einfach nur immer peinlicher für dich.
  • [l] Felix Tue, 29 Apr 2025 22:48:43 GMT Nr. 155145
    WEBM 1280×720 1:01 3.7M
    >>155041
    >>155042
    >>155045
    Am Kern vorbeigeredet. Es geht nicht darum, dass das Ergebnis fehlerhaft ist. Es geht darum, dass es nicht implementierbar ist - egal in welcher Sprache.
    Um es implementierbar zu machen, muss man seine Prioriät setzen, und eine Sache davon aufgeben.

    Erstmal: Es wurde damit gezeigt, dass es kein reines C++-Problem ist. Keine Ahnung, warum der Punkt überhaupt zur Debatte stand, und wohin das führen sollte, aber egal. Bereits im obigen Code-Beispiel aus >>154245 befindet sich in diff2 bei der Array-Subskribierung arr[i+1] und arr[i] Undefined Behavior. Für die dabei auftretende Zeigeraddition muss der Zeiger dem Array-Elementtyp entsprechen (5.7.7 [expr.add], siehe auch explizite Notiz dort). Dementsprechend ist beim ADA-Code auch kaum etwas anderes los: Es hat "implementierungsabhängige Resultate", d.h. hierbei fehlerhaft. Es geht aber nicht um die Fehlerhaftigkeit - es geht darum, _warum_ man es entweder nur fehlerhaft oder (durch Sprachverbot) garnicht machen kann.

    Wie schon gesagt: Man muss sich zwischen Werte-Arrays und Vererbung im OOP-Stil entscheiden.

    >Java
    >C#
    Und diese haben diese Entscheidung genau so getroffen: Sie erlauben keine Werte-Arrays von Klassen. Es ist gerade eine Bestätigung von dem, was Felix sagt.
    Das Problem von Werte-Arrays und Vererbung wurde also einfach versteckt, indem dem Programmierer verboten wurde, in die Nähe davon zu kommen.

    Die beiden Sprachen unterscheiden dabei explizit zwischen Werten und Referenzen (Java: Primitives und Objects, C#: Value Types und Reference Types). Die üblichen Klassen, insbesondere bei Vererbung, fallen dann zu jeweils letzterer Gruppe (Objects bzw. Reference Types). Damit kriegt man nur Referenzen und kann keine Werte-Arrays davon bauen. Man kann in C# structs bauen, welche Value Types sind, aber Vererbung geht mit structs in C# nicht. Erneute Bestätigung von dem, was Felix sagt.

    Übrigens hat Java für sich erkannt (nachdem die sich so köstlich über C++ mokiert haben, dass C++ doch mit class und struct total doppelt-gemoppelt wäre), dass es doch eigentlich auch total knorke wäre, wenn man neben Klassen noch etwas anderes hätte, etwas, mit dem man (so wie in C# mit structs) neue primitive Typen erschaffen könnte. Und daher haben sie Java JEP 401 aufgefahren.

    Und wenn man reinschaut:
    >
    // implicitly final

    >A concrete value class is implicitly final and may have no subclasses.
    https://openjdk.org/jeps/401

    Also haben sie damit zwar Werte-Arrays erlaubt, aber genau dabei dann Vererbung (bei der tatsächlich irgendwelche Attribute vererbt werden) verboten. Erneute Bestätigung von dem, was Felix sagt.

    Kein Wunder: Oben wurde gezeigt, dass es logische Folge von Arrays und der Vererbung im OOP-Stil ist. Und dass es eine bewusste Entscheidung war - eine Entscheidung, die schlecht war. Merkwürdig, dass darauf keiner eingegangen ist. Vielleicht, weil eine Reaktion nach dem Motto "hahaha, dein Kot funktioniert so nicht, ganz schön doof, ne?" einfacher war, anstatt den zugrundeliegenden Grund zu ergründen, _warum_ das nicht funktionieren kann und _warum_ einige Sprachen das verbieten.

    Felix dreht das jetzt daher einfach mal um: Wie würdet ihr Werte-Arrays + zur Laufzeit erweiterbare Vererbung für eine Sprache umsetzen wollen?

    Da bisher niemand etwas gegen die sprachunabhängige, logische Folge der Inkompatibilität von Werte-Arrays und Vererbung im OOP-Stil gesagt hat: Es bleibt der Geburtsfehler von Vererbung im OOP-Stil. Sie haben lieber Werte-Arrays rausgekickt als Erweiterbarkeit zur Laufzeit. Eine schlechte Entscheidung.
  • [l] Felix Tue, 29 Apr 2025 22:52:36 GMT Nr. 155146
    >>155145
    >Blablabla
    >Bla Bla
    Dann machst du eben kein OOP und hältst einfach den Rand.
  • [l] Felix Wed, 30 Apr 2025 00:58:46 GMT Nr. 155150
    >>155145
    >Übrigens hat Java für sich erkannt (nachdem die sich so köstlich über C++ mokiert haben, dass C++ doch mit class und struct total doppelt-gemoppelt wäre)
    Dafür brauchte es nicht erst Java. Bereits Pascal hatte zwei Typen dafür: Records (= C structs), die keine Vererbung erlauben und auch keine Methoden, und Classes (erst später eingeführt und reine Referenztypen). Mit Pointern rumpanschen kannst du da natürlich auch, wenn du willst, aber dann weißt du auch hoffentlich, was du tust. Wo da das Problem sein soll, hast du immer noch nicht erklärt. Dein Blabla ist übrigens in der Tat ziemlich ermüdend.

    Lass mich raten: Du hast mit C oder C++ als Erstsprache programmieren gelernt? Das scheint Leuten irgendwie die Hirnwindungen zu verknoten und die Schäden sind nicht mehr rückgängig zu machen.
  • [l] Felix ☎️ Wed, 30 Apr 2025 03:38:41 GMT Nr. 155151
    >>155145
    >Sie erlauben keine Werte-Arrays von Klassen.
    Das klingt für diesen Felix, der bisher idF noch nichts geschrieben hat, einigermaßen vernünftig. Entweder willst du Pedal ans Metall, dann willst du keine Dereferenzierungen quer durch den Speicher und demzufolge auch keine Vererbung. Oder du willst die Fähigkeit, polymorphe Rube-Goldberg-Klassen zu bauen, dann sind die Dereferenzierungen verschmerzbar. "Do one thing well" gilt auch für Sprachelemente.
  • [l] Felix Wed, 30 Apr 2025 06:46:35 GMT Nr. 155155 SÄGE
    >>155150
    Simula hatte auch Klassen. Wer an Pascal irgendwas positives sieht, gehört geprügelt, bis er sein behindertes, missgeborenes Maul hält.
  • [l] Felix Wed, 30 Apr 2025 14:36:40 GMT Nr. 155207
    Ich habe auch nicht verstanden, was das Problem ist. Welches praktische Handicap legt es einem wohl auf?
  • [l] Felix Wed, 30 Apr 2025 23:45:20 GMT Nr. 155227
    >>155146
    Oke kühl.

    >>155150
    >Wo da das Problem sein soll, hast du immer noch nicht erklärt. Dein Blabla ist übrigens in der Tat ziemlich ermüdend.
    Naja, eigentlich ging es Felix zuletzt hauptsächlich um das:
    >Vererbung hat immer noch nichts mit Arrays zu tun.
    Felix denkt/hofft, er hat seinen Kram (Inkompatibilität von Werte-Arrays und Vererbung im OOP-Stil) im Faden jetzt erklärt.

    >Du hast mit C oder C++ als Erstsprache programmieren gelernt?
    Tatsächlich nicht, es war zuerst eine Art von Shell-Skript und dann BASIC.

    >>155151
    >Entweder Pedal ans Metall
    >Oder polymorphe Rube-Goldberg-Klassen
    Felix stimmt zu. Felix ist noch einmal in sich gegangen und findet eher merkwürdig, dass _alle_ Sprachen so ab Mitte der 80er/Anfang der 90er lieber die Entscheidung pro "Erweiterbarkeit zur Laufzeit" getroffen haben, vielleicht auch, weil es die hippe Sache damals war. Das ist dann noch einmal Anfang-Mitte der 2000er mit "Plugin-Systemen" aufgewärmt worden.

    >>155207
    Wenn man mit dem Referenze-Wald leben kann, wäre der Hauptgrund dann wohl Performanz (wie sie es im Java JEP auch geschrieben haben), weswegen auch Unity in den letzten Jahren mit ihrem C#-Kompiler auf den DOD-Zug mit ihrem NativeContainer/NativeArray gesprungen ist, und ihr Entity-Component-System (statt Vererbung) damit gebastelt haben:
    https://www.youtube.com/watch?v=EGKmNQL9CcM&t=321s
    https://docs.unity3d.com/6000.0/Documentation/Manual/job-system-native-container.html
    https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Unity.Collections.NativeArray_1.html
    https://www.youtube.com/watch?v=p65Yt20pw0g&t=2603s

    Zum Abschluss will Felix nicht vorenthalten, was er über die Nacht gebastelt hat ... nämlich wie man den Krams mit Werte-Arrays + Vererbung zur Kompilierzeit in einer Sprachimplementierung implementieren könnte ...written exclusively in C:

    #include <stdio.h>
    
    typedef struct
    {
    	int member1;
    } Base;
    
    // Kompiler-generiert
    #define BASE_MEMBERS \
    	int member1;
    
    // Derived1 erbt von Base
    typedef struct
    {
    	BASE_MEMBERS // Kompiler-generiert
    	int member2;
    	int member3;
    } Derived1;
    
    // Kompiler-generiert, ein einziges Mal für die gesamte Hierarchie (da _zur Kompilierzeit_ bekannt)
    typedef union
    {
    	Base Base;
    	Derived1 Derived1;
    } Polymorph_Base_Derived1;
    
    // Kompiler-generiert
    #define CONCAT_IMPL(a, b) a ## b
    #define CONCAT(a, b) CONCAT_IMPL(a, b)
    #define Polymorph_Base Polymorph_Base_Derived1
    #define Polymorph_Derived1 Polymorph_Base_Derived1
    #define Polymorph_Member(c, x) x.c
    #define Polymorph(x) CONCAT(Polymorph, CONCAT(_, x))
    
    // Benutzt nur "Base"
    int diff1(Polymorph(Base) * arr, size_t n) // "Polymorph(Base)" ist Kompiler-generiert
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		// "Polymorph_Member" ist Kompiler-generiert
    		result += Polymorph_Member(Base, arr[i+1]).member1 - Polymorph_Member(Base, arr[i]).member1;
    
    	return result;
    }
    
    // Benutzt nur "Derived1"
    int diff2(Polymorph(Derived1) * arr, size_t n) // "Polymorph(Derived1)" ist Kompiler-generiert
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		// "Polymorph_Member" ist Kompiler-generiert
    		result += Polymorph_Member(Derived1, arr[i+1]).member1 - Polymorph_Member(Derived1, arr[i]).member1;
    
    	return result;
    }
    
    int main(int argc, char ** argv)
    {
    	// "Polymorph(x)" ist Kompiler-generiert für Arrays mit einem Typ, welcher Teil einer Vererbungs-Hierarchie ist
    	// (es sei denn, der Benutzer spezifiziert, dass er das nicht möchte, z.B. für ein Array von Base).
    	// arr ist ein Werte-Array und speichert kontinuierlich: member1, member2, member3, member1, ...
    	Polymorph(Derived1) arr[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		Polymorph_Member(Derived1, arr[i]).member1 = 1;		// "Polymorph_Member" ist Kompiler-generiert
    		Polymorph_Member(Derived1, arr[i]).member2 = 5;		// "Polymorph_Member" ist Kompiler-generiert
    		Polymorph_Member(Derived1, arr[i]).member3 = 1337;	// "Polymorph_Member" ist Kompiler-generiert
    	}
    
    	printf("%d\n", diff1(arr, 8));
    	printf("%d\n", diff2(arr, 8));
    
    	return 0;
    }


    SIEHT HALT SCHEISSE AUS, aber liegt hauptsächlich daran, dass alle "Kompiler-generiert"-Sachen bei einer nativen Sprachimplementierung nicht mehr sichtbar wären.
  • [l] Felix Wed, 30 Apr 2025 23:54:21 GMT Nr. 155228
    >>155227
    >Tatsächlich nicht, es war zuerst eine Art von Shell-Skript und dann BASIC.
    Das ist ja noch schlimmer. Mein Beileid.
  • [l] Felix Thu, 01 May 2025 08:03:15 GMT Nr. 155236
    >>155227
    Ich bin mir nicht ganz sicher, ob ich dein Dings da verstehe. Sieht auf den ersten Blick so aus, als hättest du da im Wesentlichen eine union aus beiden Typen gebildet und damit dafür gesorgt, dass beide dasselbe Speicherlayout haben, also im Grunde hast du den Basistyp einfach nur gepadded. Kann man natürlich machen, aber das Problem ist, dass man dann vorab wissen muss, wie groß der größte abgeleitete Typ ist, und dann alle (Arrays aus) Basistypen die maximale Größe haben, also u.U. ziemlich viel verschwendeter Speicher und nicht gerade Cache-effizient (was dir bestimmt furchtbar wichtig ist).

    Aus meiner Sicht ist das, was du da machst, wenn ich es richtig verstanden habe, einfach nur unnötig kompliziert. Es löst das eigentliche Problem auch nicht, aber wir bewegen uns langsam in die richtige Richtung: Dein Problem ist, dass du einfach nicht unterscheiden kannst zwischen der abstrakten Repräsentation und der tatsächlichen Allokation der Daten im Speicher. Daher auch meine ursprüngliche Vermutung, dass C oder C++ deine Erstsprache waren, denn dieses Defizit habe ich bei betroffenen Personen schon häufiger beobachtet.

    Dein Problem ist weiterhin, dass du bei deinem ursprünglichen Beispiel zwei Dinge vermischt, die einfach nicht zusammengehören:

    0. Low-Level-Konstrukte (C-Arrays)
    1. High-Level-Konstrukte (C++-Klassen mit Vererbung)

    Aus meiner Sicht hast du folgende Wahl:

    0. Entweder du nimmst C-Arrays und verzichtest auf C++-Features. Das wäre dann die Low-Level-C-Lösung.
    1. Oder du gehst voll C++ und löst das mit Iteratoren und allem Pipapo. Das wäre dann die High-Level-C++-Lösung.
    (2. Du bastelst dir selbst etwas, das für deinen speziellen Anwendungsfall passt, siehe unten)

    Beide haben ihre Vor- und Nachteile, aber du kannst nicht beides haben. Beides zu vermischen ist tödlich. Das ist in etwa so, als ob du ein C++-Objekt mit dem C++-Operator new anlegst und anschließend mit der C-Funktion free zerstörst. Es wird dir um die Ohren fliegen.

    Ich wollte auch noch mal hierzu etwas sagen, weil es vielleicht falsch rüberkam:
    >>154290
    >Korrekterweise würde man einen Container von Base-Zeigern übergeben würg! und Probleme werden keine gehabt.
    >>154893
    <würg!
    >>154893
    <Lass mich raten: Du hast statt normalen Arrays dann Zeiger-Arrays (oder Container, die dann Zeiger benutzen) genommen?
    >>154894
    >Sowas macht Felix automatisch in solchen Fällen ohne drüber nachzudenken.

    Bitte stell dir das jetzt nicht so vor, als ob Felix Code voll wäre von Zeiger-Arrays. Eigentlich hat Felix sowas nie in seinem Code. Das kam hier einfach nur daher, dass dein Beispiel ziemlich konstruiert ist. In der Praxis würde Felix das nie so machen.

    Folgende Beobachtungen:
    0. Fast immer wenn man über eine Liste/Array von irgendwas iteriert, macht man eine Sache pro Element. Das heißt, man macht sich eine Funktion, die genau einen Zeiger als Parameter hat. Das Iterieren über die Elemente machte man außerhalb dieser Funktion. Die Funktion braucht gar nicht zu wissen, wie und wo das Element allokiert sind, also ob es z.B. in einem Array allokiert sind oder in einer verketteten Liste, auf dem Heap oder auf dem Stack. Sie braucht auch nicht den genauen Typ zu kennen. Sie bekommt nur den Zeiger.
    1. Falls man doch etwas mit mehreren Elementen machen muss, dann ist die Lösung ganz einfach: Man gibt der Funktion mehrere Argumente.

    Also in deinem Fall wäre die naheliegenste Lösung folgende gewesen:

    #include <stdio.h>
    
    struct Base
    {
    	int member1;
    };
    
    struct Derived1
    {
    	struct Base parent;
    	int member2;
    	int member3;
    };
    
    int diff(struct Base *a, struct Base *b)
    {
    	return b->member1 - a->member1;
    }
    
    int main(int, char **)
    {
    	struct Derived1 arr[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		((struct Base*)&arr[i])->member1 = 1;
    		arr[i].member2 = 5;
    		arr[i].member3 = 1337;
    	}
    
    	int sum = 0;
    	int n = sizeof(arr) / sizeof(arr[0]);
    
    	for(size_t i = 0; i < n - 1; i++)
    		sum += diff((struct Base*)&arr[i], (struct Base*)&arr[i+1]);
    
    	printf("%d\n", sum);
    
    	return 0;
    }
    


    Und ich habe jetzt hier bewusst C benutzt, weil ich C++ eigentlich nie verwende (STAUM! Er verteidigt "OOP" und das obwohl er C benutzt!), und weil man hier außerdem besser sieht, was abgeht. Die Typecasts da sind ein bisschen unschön, bräuchte man in C++ nicht, aber trotzdem ist das ganze viel einfacher als dein komisches Ding da und macht keinerlei Probleme.

    Es ist immer noch ein konstruiertes Beispiel, aber es liegt näher an dem, wie Felix es in der Praxis lösen würde. Und ich höre dich jetzt schon sagen:
    >Aber! Aber! Funktionsaufrufe sind langsam! Muh SIMD!!!11
    Stell dir vor, der Compiler ist schlau genug, das zu inlinen.

    Arrays von Pointern als Argument an eine Funktion zu übergeben ist etwas, das bei Felix in der Praxis eigentlich nie vorkommt. Wenn dann nur, weil er es mit einer absolut beschissenen API zu tun hat.

    Es gäbe aber auch noch andere Möglichkeiten. Z.B. könntest du der Funktion einfach die Größe des Datentyps als weiteren Parameter übergeben:

    int diff(struct Base *arr, size_t n, size_t member_size)
    {
    	int result = 0;
    	for (size_t i = 0; i + 1 < n; ++i) {
    		struct Base *a = (void*)((char*)arr + member_size * i);
    		struct Base *b = (void*)((char*)arr + member_size * (i + 1));
    		result += b->member1 - a->member1;
    	}
    	return result;
    }
    
    int main(int, char **)
    {
    	struct Derived1 arr[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		((struct Base*)&arr[i])->member1 = 1;
    		arr[i].member2 = 5;
    		arr[i].member3 = 1337;
    	}
    
    	int n = sizeof(arr) / sizeof(arr[0]);
    
    	int sum = diff((struct Base*)&arr[0], n, sizeof(arr[0]));
    	printf("%d\n", sum);
    
    	return 0;
    }
    


    Auch das funktioniert. Und das sogar ohne C++-Template-Bloat! Stell dir vor. Natürlich ein bisschen unschön mit den Typecasts, aber du kannst dir das ja in eine Funktion packen.

    Beide Varianten setzen aber immer noch voraus, dass alle Elemente im Array den gleichen Typ haben. Du kannst also nicht, Base und Derived1 (oder andere abgeleitete Typen unterschiedlicher Größe) mischen. Also eigentlich hast du das Polymorphismus-Problem immer noch nicht gelöst. Könntest du aber beispielsweise so lösen:

    #include <stdio.h>
    
    struct Base
    {
    	int member1;
    };
    
    struct Derived1
    {
    	struct Base parent;
    	int member2;
    	int member3;
    };
    
    enum { MAX_ARRAY_SIZE = 1024 };
    
    struct MyAllocator {
    	char buf[MAX_ARRAY_SIZE];
    	size_t offset[MAX_ARRAY_SIZE];
    	size_t n;
    };
    
    void *my_allocator_alloc(struct MyAllocator *allocator, size_t elem_size)
    {
    	// Alignment und bounds-checking ignorieren wir hier mal
    	size_t n = allocator->n;
    	size_t offset = allocator->offset[n];
    	allocator->offset[n + 1] = offset + elem_size;
    	++allocator->n;
    	return (void*)(&allocator->buf[offset]);
    }
    
    void *my_allocator_get(struct MyAllocator *allocator, size_t index)
    {
    	size_t offset = allocator->offset[index];
    	return (void*)(&allocator->buf[offset]);
    }
    
    int diff(struct MyAllocator *allocator)
    {
    	int result = 0;
    	for (size_t i = 0; i + 1 < allocator->n; ++i) {
    		struct Base *a = my_allocator_get(allocator, i);
    		struct Base *b = my_allocator_get(allocator, i+1);
    		result += b->member1 - a->member1;
    	}
    	return result;
    }
    
    int main(int, char **)
    {
    	struct MyAllocator allocator = {0};
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		struct Base *elem = my_allocator_alloc(&allocator, sizeof(*elem));
    		elem->member1 = 1;
    	}
    
    	// Und weil es so schön ist, gleich noch mal, aber diesmal mit Derived1
    	for(size_t i = 0; i < 8; i++)
    	{
    		struct Derived1 *elem = my_allocator_alloc(&allocator, sizeof(*elem));
    		((struct Base*)elem)->member1 = 1;
    		elem->member2 = 5;
    		elem->member3 = 1337;
    	}
    
    	int sum = diff(&allocator);
    	printf("%d\n", sum);
    
    	return 0;
    }
    


    Es gibt natürlich über 9000 weitere Möglichkeiten, dieses "Problem" zu lösen. Wenn deine "Klasse" z.B. eine VTable hat, dann könntest du darin die größe Speichern. Oder du gibst deinem Struct ein zusätzliches Element, das die Größe angibt. Der Fantasie sind keine Grenzen gesetzt. Dein Problem war von Anfang an komplett konstruiert und hausgemacht.

    Dein Problem existiert in der Praxis nicht.
  • [l] Felix Thu, 01 May 2025 08:18:23 GMT Nr. 155238
    Und nicht zuletzt könntest du natürlich auch einfach keinen Fick geben und alles auf den Heap packen. Sollte man auch noch erwähnen. Manchmal ist das völlig in Ordnung. Nicht alles muss immer superhochgeschwindigkeitsoptimiert sein bis auf das allerletzte Prozent. Oft ist es das einfach nicht wert. Optimier die Stellen, wo es sich lohnt. und für den Rest: KISS. Es wird immer noch 1000x schneller sein als jedes Web-Geraffel (oder C#... wer C#, Java oder irgendwas anderes mit Garbage Collection für Spieleprogrammierung benutzt, hat eh nicht alle Tassen im Schrank. Das ist das eigentliche Problem bei Unity. Es ist halt eine Spieleengine für Kackb00ns und man wollte es halt deppensicher machen. Die Deppensicherheit erkauft man sich aber eben mit schlechter Performanz. Kann man nix machen. Man kann nicht alles haben. Das hat auch nichts mit Vererbung zu tun. Felix kann Entity-Component außerdem langsam nicht mehr hören.)
  • [l] Felix Thu, 01 May 2025 13:34:52 GMT Nr. 155267 SÄGE
    >>155227
    >Ich erfinde mal in ein paar Zeilen GObject neu
    hat leider nicht hingehaut, tut mir sorry.
  • [l] Felix ☎️ Thu, 01 May 2025 14:35:12 GMT Nr. 155269
    >>155236
    >Fast immer wenn man über eine Liste/Array von irgendwas iteriert, macht man eine Sache pro Element. Das heißt, man macht sich eine Funktion, die genau einen Zeiger als Parameter hat. Das Iterieren über die Elemente machte man außerhalb dieser Funktion.
    Grundgütiger. So langsam bekommt Felix eine Ahnung, wer für Wirths Gesetz verantwortlich ist.
  • [l] Felix Thu, 01 May 2025 15:13:28 GMT Nr. 155275 SÄGE
    >>155269
    Ja, Deppen wie du, die nicht verstehen, wie ein Compiler funktioniert und nicht mal in der Lage sind, einen Pfosten zu Ende zu lesen. Funktionale Analphabeten sollten nicht programmieren.

    >Pfostiert von meinem Schlaufon™
    Passt mal wieder wie die Faust aufs Auge.
  • [l] Felix Thu, 01 May 2025 15:30:47 GMT Nr. 155279 SÄGE
    >>155269
    Der Compiler liniert die Funktion ein.

    Die richtig schlimmen Probleme kommen auch normalerweise nicht durch irgendwelche überflüssigen Funktionsaufrufe oder durch dereferenzierung von ein paar Zeigern, die werden auf höherer Ebene verbrochen.
  • [l] Felix Thu, 15 May 2025 23:51:03 GMT Nr. 156625
    >>155236
    Felix hat sich wieder an den Faden erinnert.
    Erst mal Danke, sehr guter Pfosten.

    >Sieht auf den ersten Blick so aus, als hättest du da im Wesentlichen eine union aus beiden Typen gebildet
    Das ist korrekt.

    >Kann man natürlich machen, aber das Problem ist, dass man dann vorab wissen muss, wie groß der größte abgeleitete Typ ist
    Genau, d.h. zur Kompilierzeit bekannt.

    >und dann alle (Arrays aus) Basistypen die maximale Größe haben
    Genau das hat doch Felix bedacht und genau da hat Felix geschrieben:
    >
    (es sei denn, der Benutzer spezifiziert, dass er das nicht möchte, z.B. für ein Array von Base).

    Ein nicht-polymorphes Array von Base kann man natürlich trotzdem anlegen, muss diesen Wunsch aber z.B. durch Sprachschlüsselwort spezifizieren. Oder man macht es andersherum, und spezifiert polymorphe Arrays durch Sprachschlüsselwort. Man hat eben die volle Auswahl, und kann sich seine Cache-Line-kompakten Arrays erstellen. Was natürlich nicht geht: Ein polymorphes Array haben, das zur Laufzeit nur mit Base befüllen, und dann erwarten, dass da kein Padding ist (was aber Vorteile für den Schreibzugriff und die Iteration bringt, siehe unten).

    >Dein Problem ist, dass du einfach nicht unterscheiden kannst zwischen der abstrakten Repräsentation und der tatsächlichen Allokation der Daten im Speicher.
    Schauen wir doch mal, was der C- und C++-Standard mit ihrer "abstrakten Maschine" zur Datenstruktur "Array" so schreibt:
    >An array type describes a contiguously allocated nonempty set of objects
    Also so ziemlich das, was Felix in >>155040 mit "kontinuierlich allozierter Speicher" geschrieben hat. Es darf insbesondere kein Padding zwischen den Elementen geben, aber jedes Element darf (auch am Ende) Padding haben. Und natürlich darf man und will man gerade einschränken, wie das dann im Speicher landet (beim C-Standard auf der abstrakten Maschine, bei einer C-Implementierung auf einer konkreten Plattform). Ein Array ist nicht nur Luft&Liebe mit einem Subskript-Operator, und die Daten dürfen irgendwo im Speicher fliegen. Ein "du darfst nicht sagen, wie die Datenstruktur im Speicher liegt" wäre da komplett sinnlos, zumindest die Leitplanken, wie das im Speicher liegt, muss man geben können.
    Und zu structs und unions wird übrigens geschrieben:
    >As discussed in 6.2.5, a structure is a type consisting of a sequence of members, whose storage is allocated in an ordered sequence, and a union is a type consisting of a sequence of members whose storage overlap.

    Natürlich darf man, wenn man eine Datenstruktur definiert, zumindest grundlegende Annahmen über die Plattform machen (und sei es im C-Standard nur für eine abstrakte Maschine), z.B. dass diese "Speicher" hat. Das hat man bereits auf einer Turing-Maschine, wenn man definiert, wie die Ausgabe auf dem Band zu liegen hat. Das ist keine "fehlende Unterscheidung", das ist Teil der Definition, wie die Datenstruktur am Ende aussehen soll, und ja, das schließt die Leitplanken fürs Speicherlayout mit ein. Und wenn man jetzt sagt:
    >Ja, aber andere Sprachen können das anders definieren!
    dann wird darauf verwiesen, dass die Rahmenbedingungen oben hinreichend festgezurrt wurden. Ich denke, daran ist auch nichts irgendwie abweichend von dem, was man sich landläufig als "Array" vorstellt.

    Das allgemeinere Problem ist: Man kann "High-Level"-Vorgaben machen, die dann (teilweise überraschende) Auswirkungen darauf haben, welche "Low-Level"-Implementierungen (wenn man selber der Sprach-/Kompiler-Implementierer ist) überhaupt noch möglich sind.

    Felix geht mal auf eine Tangente: Genau das hatte man bereits bei std::unordered_map. Die Standard-Spezifikation gibt vor, dass die Bucket-Iterierung in O(1) vonstatten geht. Die Bucket-Iterierung ist mal wieder etwas, was man praktisch nie braucht (man will Sachen reinspeichern und Lookups machen und über alle Elemente iterieren, nicht über Buckets iterieren, letzteres ist was ganz anderes).

    So, aber diese "High-Level"-Vorgabe führt dann dazu, dass man die std::unordered_map nicht mit offener Addressierung implementieren kann (z.B. ein großes Array, dessen 3 möglichen Zustände frei, belegt, Grabstein sind). Würde man das mit offener Addressierung implementieren, wäre das Bucket::begin() der Buckets nicht mehr in O(1), weil man das Array durchiterieren muss, bis das erste nicht-freie Array-Element gefunden wäre (nicht mehr O(1)). Und immer einen Zeiger auf das erste nicht-freie Element vorzuhalten bringt auch nichts, weil die Iterierung über Buckets dann natürlich beim nächsten Element das gleiche Problem hätte.

    Darüber hinaus hat erase() das Problem, dass ein Iterator auf das nächste Element zurückgegeben werden muss, was wieder praktisch nie gebraucht wird, aber man keine Performanz von O(1) mehr hinkriegt (was der Standard an der Stelle auch nicht fordert):
    https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2023.pdf

    So, und dementsprechend gibt es jetzt neu in C++23 std::flat_map (und vorher boost::unordered_flat_map), welche diese Vorgaben einfach wegschmeißen, womit man das dann mit offener Addressierung implementieren kann.

    Das einer der Punkte von Felix: "High-Level"-Vorgaben (bei std::unordered_map bestimmte Vorgaben im Interface, oder bei Vererbung, dass es zur Laufzeit erweiterbar sein soll) führen zu Einschränkungen bei der "Low-Level"-Implementierung (bei std::unordered_map keine offene Addressierung, oder bei Vererbung keine Werte-Arrays von solchen Typen).

    Das heißt bzgl.
    >Dein Problem ist, dass du einfach nicht unterscheiden kannst zwischen der abstrakten Repräsentation und der tatsächlichen Allokation der Daten im Speicher.
    ist es tatsächlich so, dass man die "abstrakten Vorgaben" von oben diktiert, und dies dann unten in der Implementierung zu schlechten Auswirkungen führen kann, die man vorher gar nicht so bedacht hat. Dann klappt std::unordered_map nicht mit offener Addressierung, dann klappt erweiterbare Vererbung nicht mit Werte-Arrays.

    >Beides zu vermischen ist tödlich.
    Ja. Felix würde beispielsweise die union-Typen so niemals in einen C++-Container kippen (auch wenn man es könnte).

    Bevor er auf den Kot der drei Code-Beispiele eingeht, will Felix klarmachen, dass es dabei zwei mehr oder weniger unabhängige Sachen gibt:
    1. Die zugrundeliegende Datenstruktur. Aus deinen Beispielen wird klar: Wenn man keinen union-Typ verwendet, d.h. Array-Elemente mit Größe max(Base, Derived1, Derived2, ...), kann man nicht O(1)-Schreibzugriffe machen (mehr dazu unten).
    2. Zähmung der Kot-Explosion. Wenn man tatsächlich Funktionalität für Array von Base, Array von Derived1 und ein polymorphes Array von Base und Derived1 (im gleichen Array) anbieten will, läuft man in die Gefahr der Kot-Explosion. Dann kann man die Funktionalität 3x implementieren, oder einen Größen-Parameter verwenden, oder automatische Funktionsgenerierung im C++-Template-Stil betreiben.

    Die Null-Variante mit auftretender Kot-Explosion sähe so aus:
    int diff_Base(Base * arr, size_t n)
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		result += arr[i+1].member1 - arr[i].member1;
    
    	return result;
    }
    
    int diff_Derived1(Derived1 * arr, size_t n)
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		result += arr[i+1].member1 - arr[i].member1;
    
    	return result;
    }
    
    int diff_Polymorph(Polymorph_Base_Derived1 * arr, size_t n)
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		result += Polymorph_Member(Base, arr[i+1]).member1 - Polymorph_Member(Base, arr[i]).member1;
    
    	return result;
    }
    
    int main(int argc, char ** argv)
    {
    	Polymorph_Base_Derived1 arr[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		Polymorph_Member(Base, arr[i]).member1 = (int)(i);
    		Polymorph_Member(Derived1, arr[i]).member2 = 5;
    		Polymorph_Member(Derived1, arr[i]).member3 = 1337;
    	}
    
    	//printf("%d\n", diff_Base(arr, 8)); // Kompiler-Fehler
    	//printf("%d\n", diff_Derived1(arr, 8)); // Kompiler-Fehler
    	printf("%d\n", diff_Polymorph(arr, 8));
    
    	return 0;
    }


    Die Byte-Array-Variante gegen die Kot-Explosion sähe so aus (vgl. Code-Beispiel 2 von dir):
    #define ArrayGet(dest, arr, i, stride, Type, MemberType, member) memcpy(&(dest), (char *)(arr) + (i) * (stride) + offsetof(Type, member), sizeof(MemberType))
    
    int diff(void * arr, size_t n, size_t stride)
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    	{
    		int a, b;
    		ArrayGet(a, arr, i, stride, Base, int, member1);
    		ArrayGet(b, arr, i+1, stride, Base, int, member1);
    
    		result += b - a;
    	}
    
    	return result;
    }
    
    int main(int argc, char ** argv)
    {
    	Base arr1[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		arr1[i].member1 = (int)(i);
    	}
    
    	printf("%d\n", diff(arr1, 8, sizeof(Base)));
    
    	Derived1 arr2[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		arr2[i].member1 = (int)(i);
    		arr2[i].member2 = 5;
    		arr2[i].member3 = 1337;
    	}
    
    	printf("%d\n", diff(arr2, 8, sizeof(Derived1)));
    
    	Polymorph_Base_Derived1 arr3[8];
    
    	for(size_t i = 0; i < 8; i++)
    	{
    		// Es wird mit Derived1 befüllt
    		Polymorph_Member(Derived1, arr3[i]).member1 = (int)(i);
    		Polymorph_Member(Derived1, arr3[i]).member2 = 5;
    		Polymorph_Member(Derived1, arr3[i]).member3 = 1337;
    	}
    
    	printf("%d\n", diff(arr3, 8, sizeof(Polymorph_Base_Derived1)));
    
    	return 0;
    }

  • [l] Felix Thu, 15 May 2025 23:51:35 GMT Nr. 156626
    (Fortsetzung)

    Die C++-Template-Variante gegen die Kot-Explosion sähe so aus:
    template <typename T>
    int diff(T * arr, size_t n)
    {
    	int result = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		result += arr[i+1].member1 - arr[i].member1;
    
    	return result;
    }

    Probleme beim C++-Template-Gedönse gibts natürlich, wenn z.B. Derived1 und Derived2 jeweils einen Member member4 hätten, mit jeweils unterschiedlichem Typ. Dann kann man mit dem union das auseinanderhalten, muss aber einen auf die Klasse getemplateten Getter für member4 schreiben.

    Das sind, wie gesagt, Varianten gegen die _Kot-Explosion_. Die zugrundliegende Datenstruktur sieht bei allen gleich aus.

    Nun zu den drei Code-Beispielen.

    >[1. Code-Beispiel]
    >
    ((struct Base*)&arr[i])

    Dazu die C-Anmerkung: Ohne union darf man das nicht, denn die "common initial sequence" für Zeiger auf Objekte, die nicht mit den zugrundeliegenden Objekt übereinstimmen, gilt nur für unions. Darüber hinaus verstößt es gegen die strict-aliasing-Rule, da man nun für arr[i] zwei Objekte (eines nur temporär) unterschiedlichen Typs hat, womit man nicht mehr mittels * oder -> dereferenzieren darf, wie im Kot gemacht. Du hast aber gesagt, dass das nur Beispielcode für eine mögliche Umsetzung sein soll, deshalb hackt Felix darauf nicht weiter rum.

    >Also in deinem Fall wäre die naheliegenste Lösung folgende gewesen:
    Das klappt in dem einfachen Beispiel gut. Aber: Sobald die for-Schleife nicht mehr in der gleichen Funktion wie die Array-Definition liegt, läuft man wieder in den Fall wie oben rein. Damit wurde das Problem nur verschoben.

    Und in einen solchen Fall kann man schnell kommen. Kaum macht man um die Schleife drumrum etwas Komplexeres, oder man will an einer anderen Stelle die gleiche Berechnung durchführen, refaktorisiert man die Schleife raus in eine Funktion. Dann muss man sich bereits fragen, welchen Parameter diese Funktion kriegen soll. Base *? Dann kann der Benutzer kein Derived1-Array übergeben. Derived1 *? Was ist, wenn der Benutzer aber ein Derived2-Array hat? Man ist wieder an der gleichen Stelle wie zuvor.

    Der Aufrufer, der auch schon mal viele Ebenen drüber im Callstack stehen kann, muss dann wissen und mitteilen, welcher Typ ganz unten im Callstack, in der innersten Berechnungsschleife, zu verwenden sein soll. Das wurde in deinem zweiten Kot-Beispiel auch erkannt: Die Information, die man mein Array-Iterieren über den Typ braucht, nämlich die Größe, wird mit member_size runtergereicht.

    Das Problem, nämlich das Runterreichen der Information, ist leider auch genau das, was je nach Lösung zur Kot-Explosion führt.

    Wenn man nicht alle Funktionen verdoppeln will, muss man dann wohl in der Praxis zu so etwas wie Typdurchreichungen im C++-Template-Style greifen.

    template <typename T>
    int diff(T * a, T * b)
    {
    	return b->parent.member1 - a->parent.member1;
    }
    
    template <typename T>
    int ProcessArray(T * arr, size_t n)
    {
    	int sum = 0;
    
    	for(size_t i = 0; i < n - 1; i++)
    		sum += diff(&arr[i], &arr[i+1]);
    
    	return sum;
    }
    
    int main(int argc, char ** argv)
    {
    	size_t n = 8;
    	Derived1 * arr = CreateDerived1Array(n);
    	InitializeDerived1Array(arr, n);
    
    	int sum = ProcessArray(arr, n);
    	printf("%d\n", sum);
    
    	DestroyDerived1Array(arr);
    
    	return 0;
    }


    Und da hat man dann das Problem: Wenn ProcessBaseArray und ProcessDerived1Array zu einer Funktion zusammenfassen will, wie es bei Polymorphie der Fall ist, kann man sich nicht in der Parameterliste einfach für Base * entscheiden, weil man dann beim gleichen Problem wie oben ist. D.h. man hat dann wieder das gleiche Problem - nur eben bei ProcessArray, statt bei diff. Man hat damit das Problem nur verschoben.

    >Stell dir vor, der Compiler ist schlau genug, das zu inlinen.
    Stell dir vor, der Compiler macht es nicht, weil er es nicht durchschaut (vgl. Video unten, wo es nicht geinlined wurde).

    >[2. Code-Beispiel]
    >Es gäbe aber auch noch andere Möglichkeiten. Z.B. könntest du der Funktion einfach die Größe des Datentyps als weiteren Parameter übergeben:
    Da würde in C erst mal das gleiche bzgl. der strict-aliasing-Rule gelten, dabei dann sogar explizit mit arr + i, a, arr + (i + 1), b. Wieder hackt Felix nicht weiter darauf rum.

    Die Größe des Datentyps zu übergeben, ist eine sehr gute Lösung, die Felix auch schon dabei in den Sinn gekommen war und Felix so natürlich von einigen APIs kennt (Klassiker: Man übergibt Null-Zeiger und Zeiger auf Größe und kriegt Größe zurück, man übergibt Nicht-Null-Zeiger mit Größe und Array wird befüllt). Felix hatte oben etwas von "bei Sprachen ohne Kompilierung: zur Allokationszeit" geschrieben, aber das kann man so natürlich auch bei kompilierten Sprachen machen. Geht auch zur Vermeidung der Kot-Explosion mit der union-Variante von Felix, siehe oben.

    Zunächst zu dem, was Felix auch schon zum 1. Code-Beispiel geschrieben hat: Der Aufrufer, der auch schon mal viele Ebenen drüber im Callstack stehen kann, muss dann wissen und mitteilen, welcher Typ ganz unten im Callstack, in der innersten Berechnungsschleife, zu verwenden sein soll. Das ist hierbei einfach, weil wegen der Definition von arr im gleichen Scope das sizeof(arr[0]) richtig ist. Das gilt aber nicht, wenn man das Array per Base * übergibt und dann den sizeof nimmt. Man muss von Allokation der Objekte im Array bis zur for-Schleife das member_size durchreichen, so wie man im ersten Beispiel den richtigen Typ durchreichen muss. Falscher Typ führt zu Undefined Behavior, falsches member_size führt zu Undefined Behavior.

    Aber: Auch die Lösung bietet keine O(1)-Schreibzugriff mit anderem Typ - und zwar einem anderem Typ, dessen Größe erst bekannt wird, nachdem man das Array bereits befüllt hat. Das ist jedoch gerade die Erweiterbarkeit bei Vererbung im OOP-Stil: Man hat bereits ein gefülltes Array, und nun kommt noch ein Typ mit einer anderen Größe zur Vererbungshierarchie, und das Element soll mitten ins gefüllte Array geschrieben werden (und natürlich alles O(1) halten).

    Deswegen hatte Felix geschrieben
    >sei es der O(1)-Zugriff
    weil man dann beim _Schreiben_ ins Array keinen O(1)-Schreibzugriff mehr hat, sondern erst das Array umstrukturieren muss. Und wenn dann andere Stellen im Kot keine Array-Indizes von dem Array gespeichert haben, sondern direkt Zeiger aufs Array, müssen alle diese Zeiger umgebogen werden, was ein großer Aufwand und fehleranfällig ist.

    Das mit dem Schreibzugriff war natürlich auch für die Sprach-Implementierer relevant, weil die genau nicht wissen, ob der Sprach-Benutzer dann plötzlich nach der Initialisierung (was bereits ein Schreibzugriff ist) nochmal ins Array schreibt.

    Das gleiche Problem gibt es auch bei Felixens union-Lösung, weshalb die Erweiterbarkeit bei Vererbung im OOP-Stil bei Felix unerfüllt gelassen wurde, und bei deiner Lösung auch.

    Wenn das nicht vorkommt, kann man das genauso machen, wie von dir oder auch mir beschrieben. Wenn man wahlfreie Schreibzugriffe mit einem neuen, größeren Typ hat, hat man ein Problem. So langsam sollte es dämmern, warum eine solche OOP-Erweiterbarkeit in der Praxis gerade fast nie auftaucht.

    Was man stattdessen sehr viel häufiger hat: Man hat eine Bibliothek, und die wird einmal geladen, und mehr Typen kommen nach der Lade-Zeit nicht mehr hinzu. Wenn man einmal die kernel32.dll geladen hat, will man nicht noch eine zweite kernel32.dll laden. Ladezeitpunkt der Bibliothek ist fast immer der Ladezeitpunkt des Programms. Dann holt man sich einmal zu Programmstart die (Maximal-)Größe von der Bibliothek und man kann damit seine Arrays erstellen und mit Objekten der Bibliothek befüllen. Das ist gerade weniger als der OOP-Stil, bei dem jederzeit weitere Bibliotheken hinzukommen können, Plugin-System-Style.

    >[3. Code-Beispiel]
    >Also eigentlich hast du das Polymorphismus-Problem immer noch nicht gelöst. Könntest du aber beispielsweise so lösen:
    Ob das damit "nicht gelöst" ist, ist debattierbar. Aber bzgl. des Kots: Wieder keine O(1)-Schreibzugriffe. Wenn man als Funktionalität nur Hinzufügen zum Ende vom Array/Entfernen vom Ende vom Array braucht, und nicht mitten drin reinspeichern muss, klappt das vollkommen.

    (Felix hat auch mal auf Godbolt bzgl. der Kot-Generierung geschaut, wenig überraschend schafft der Kompiler erst mal das Endergebnis komplett durchzurechnen und auf eine Konstante zu reduzieren. Wenn man die Array-Größe variabel macht, sieht der Kot für die Variante ohne member_size besser aus, weniger Berechnungen in der heißen Schleife. Aber Felix wird darauf nicht rumhacken, weil Beispielkot.)
  • [l] Felix Thu, 15 May 2025 23:52:16 GMT Nr. 156627
    (Fortsetzung)

    >Wenn deine "Klasse" z.B. eine VTable hat, dann könntest du darin die größe Speichern.
    Ja. Bei den Beispielen oben fehlt auch noch der enum, mit dem man den Typ eines Elements im Array (insbesondere als union) unterscheiden kann, als alternative zur VTable.
    >Oder du gibst deinem Struct ein zusätzliches Element, das die Größe angibt.
    Dass man den Pointer durch einen Offset ersetzt hat, der einem anzeigt, wo man das nächste Element finden kann, macht es nicht besser. Ist halt nicht mehr "kontinuierlich", behindert wahlfreien Zugriff. Wäre außerdem in der for-Schleife eine serielle Abhängigkeit und verhindert OoOE.

    >0. Fast immer wenn man über eine Liste/Array von irgendwas iteriert, macht man eine Sache pro Element.
    >1. Falls man doch etwas mit mehreren Elementen machen muss, dann ist die Lösung ganz einfach: Man gibt der Funktion mehrere Argumente.
    Ne, Felix macht viele Sachen mit Grafik (deswegen kennt Felix auch die Lösung, wie bei dir, mit explizit übergebenem Pitch, wobei dieser ggf. auch ein ganzer Zeilen-Pitch ist, das dann aber hauptsächlich fürs Alignment).

    Und da wäre ein Standardbeispiel eine Konvolution, z.B. einen Weichzeichnungsfilter über ein Bild. Wenn diff zwei Parameter bekommt, soll man dann für einen 5x5-Filter 25 Parameter übergeben?

    Wenn es un-mathematischer sein soll: Sortieren, Mustersuche (mit variabel langem Muster/Regex), geordnetes Zusammenfügen zweier Arrays (wie bei Mergesort), Zusammenfügen im Reißverschlussverfahren von zwei Arrays ("zip"), Element entfernen, Duplikate entfernen, bestimmte Elemente duplizieren, Umordnen (z.B. Elemente bestimmten Typs ans Ende verschieben), Shiften eines Arrays um n Stellen mit Wraparound (braucht idR temporären Speicher), Array splitten. Da kommt man mit elementweisen Operationen nicht weit und will auf den ganzen Arrays arbeiten.

    Ganz davon abgesehen (ohne jetzt zum DOD-Evangelisten zu werden), haben die DODler gerade erkannt, dass das "man macht eine for-Schleife und ruft eine Funkton auf, die genau ein Element kriegt" genau das ist, was man nicht machen sollte.

    Siehe dazu genau das hier:
    https://www.youtube.com/watch?v=rX0ItVEVjHc&t=1919s
    Insbesondere sorgt die Übergabe als Array auch dafür, dass man so Flags von Elementen aus der Schleife rausheben kann. Der Kompiler ist halt zu doof.

    >Arrays von Pointern als Argument an eine Funktion zu übergeben ist etwas, das bei Felix in der Praxis eigentlich nie vorkommt. Wenn dann nur, weil er es mit einer absolut beschissenen API zu tun hat.
    Wut? Arrays übergeben zu bekommen ist der absolute Standardfall, und gerade so explizit gewollt. Öffnest du eine Datei und kriegst du beim Lesen ein einzelnes Byte zurück? Nö, du übergibst ein Array, und das wird dann gefüllt.

    Bereits letzteres (also das _Array_) wurde als "Get a byte, get a byte, get a byte, byte, byte" von Dave Cutler kritisiert, aber wenn man nur ein einziges Element zurück bekäme, wäre das Betriebssystem-Interface so karpott, dass man keine performanten Programme mit Datei-Zugriff auf dem Betriebssystem mehr hinkriegen würde.

    Mehr noch, eigentlich sieht das API-Design bei einer Bibliothek eher so aus: Man legt selber ein Array in seinem Kot an. Vielleicht sagt einem die Bibliothek die Größe dafür, falls notwendig. Das Array wird dann z.B. elementweise gefüllt (das kann auch tatsächlich einzelne Funktionsaufrufe haben). Dann aber wird das gesamte Array zur Verarbeitung an die Bibliothek übergeben. Man will gerade nicht einzelne Elemente durch die Bibliothek verarbeiten lassen.

    Der ganze Kram von io_uring basiert auch darauf, dass man das noch weiter getrieben hat: Jetzt wird nicht nur ein Array befüllt, sondern ein Ring-Buffer hält Zeiger auf Arrays, in die die Ergebnisse schrieben werden, und man übergibt damit einen ganzen Buffer an Kommandos, und kriegt damit eine ganze Reihe an Arrays zurück. Nicht nur ein Element, nicht nur ein Array, direkt einen Haufen Arrays.

    Das ist auch einer der Mit-Gründe, warum man in 3D-Grafik-APIs keine einzelnen glVertex3f()-Aufrufe macht, sondern Vertex-Buffer anlegt, und die in einem Schwupps füllt und rüberkopiert. Die logische Weiterentwicklung sieht man dann z.B. bei Vulkan, wo auch explizit um die Vermeidung von CPU-Overhead, besonders bei den Aufrufen Anwendung → Treiber, ging.

    Man _will_ gerade Arrays und keine einzelnen Elemente übergeben.
    Das gilt ganz besonders bei OOP mit Drittanbietern, wo über die Drittanbietergrenze kein Inlining betrieben werden kann.

    >Es gibt natürlich über 9000 weitere Möglichkeiten, dieses "Problem" zu lösen.
    Is des so? Du hast Möglichkeit 1 gegeben, welche bzgl. der Datenstruktur mit Felixens Lösung identisch ist, und das relevante Problem versteckt, indem die for-Schleife direkt in den gleichen Scope wie die Array-Definition gepackt wurde. Möglichkeit 2 ist bzgl. der Datenstruktur auch mit Felixens Lösung identisch, und krankt genau an der gleichen Stelle (Erweiterbarkeit zur Laufzeit, s.o.). Möglichkeit 3 hat generell keinen O(1)-Schreibzugriff. Also im Moment bleibt es beim Problem, Felix ist aber offen für neues.

    Das
    >das hat nichts mit Vererbung und Arrays zu tun
    ist jedenfalls nicht richtig, denn wenn man im in den Beispielen entweder Vererbung oder Werte-Arrays weglässt, löst sich das Problem in Luft auf. Hat also anscheinend doch etwas mit beidem zu tun.

    >Dein Problem existiert in der Praxis nicht.
    Und da sagt Felix gerade: Das Gegenteil ist der Fall.

    Der Grund, warum das nur so aussieht, liegt darin, dass Sprachen wie C# oder Java es von vornherein verbieten, es also aus der Gedankenwelt der Programmierer wegen Unmöglichkeit komplett herausfällt. Und in C++ sagt dann der Programmierer "oh Fugg :D:DDD", und ändert es zu einem Zeiger-Array und freut sich über den funktionierenden Kot. Man baut eben in der Praxis drumrum, fast immer unbewusst, weil Zeiger-Arrays da idiomatisch fest verwurzelt sind.

    Was umgekehrt in der Praxis tatsächlich kaum existiert, ist eine Notwendigkeit für Vererbung im OOP-Stil, d.h. dass nicht nur nicht zur Kompilierzeit, nicht nur nicht zur Lade-Zeit des Programms, sondern mitten in der Programmausführung (konkret: mit bereits gefüllten Arrays) weitere Typen hinzukommen können. Und die kann man dann nicht mehr unter obigen Rahmenbedingungen mitten ins Array schreiben. Jedoch deckt Vererbung im OOP-Stil genau das ab, und erklärt das zum Standardfall, für den man gerüstet sein muss, was in der Regel heißt: Alle Arrays sind Zeiger-Arrays.

    >Und nicht zuletzt könntest du natürlich auch einfach keinen Fick geben und alles auf den Heap packen. Sollte man auch noch erwähnen.
    Dann heißt es mal wieder: ""Clean" Code, Horrible Performance" (das Video ist auch bekannt, nimmt Felix an).
  • [l] Felix Fri, 16 May 2025 01:15:43 GMT Nr. 156631
    >>156626
    >
    ((struct Base*)&arr[i])

    >Dazu die C-Anmerkung: Ohne union darf man das nicht, denn die "common initial sequence" für Zeiger auf Objekte, die nicht mit den zugrundeliegenden Objekt übereinstimmen, gilt nur für unions. Darüber hinaus verstößt es gegen die strict-aliasing-Rule, da man nun für arr[i] zwei Objekte (eines nur temporär) unterschiedlichen Typs hat, womit man nicht mehr mittels * oder -> dereferenzieren darf, wie im Kot gemacht.
    Felix ist sich zu 99% sicher, dass genau für diesen Anwendungsfall im C-Standard für structs eine Ausnahme existiert. Aber frag Felix nicht, wo das steht. Wenn das nicht ginge, dann wäre auch so ziemlich alles von GObject bis Linux-Kernel kaputt.

    >Und da wäre ein Standardbeispiel eine Konvolution, z.B. einen Weichzeichnungsfilter über ein Bild. Wenn diff zwei Parameter bekommt, soll man dann für einen 5x5-Filter 25 Parameter übergeben?
    Und wie oft berechnest du eine Konvolution über ein Bild, in dem jedes Pixel einen anderen polymorphen Typ hat? Das ist völlig an den Haaren herbeigezogen.

    >>Arrays von Pointern als Argument an eine Funktion zu übergeben ist etwas, das bei Felix in der Praxis eigentlich nie vorkommt. Wenn dann nur, weil er es mit einer absolut beschissenen API zu tun hat.
    >Wut? Arrays übergeben zu bekommen ist der absolute Standardfall, und gerade so explizit gewollt. Öffnest du eine Datei und kriegst du beim Lesen ein einzelnes Byte zurück? Nö, du übergibst ein Array, und das wird dann gefüllt.
    Array von Pointern. Nicht Arrays allgemein. Auch nicht Pointer zu Arrays. Arrays von Pointern.

    >>Stell dir vor, der Compiler ist schlau genug, das zu inlinen.
    >Stell dir vor, der Compiler macht es nicht, weil er es nicht durchschaut (vgl. Video unten, wo es nicht geinlined wurde).
    Welches Video? Probier es doch selbst aus. Gib es bei Godbolt ein und kompiliere mit mindestens O1. Ich fresse meinen Besen, wenn der Compiler das nicht inlined.

    Hab ich gesagt, dass der Compiler alles immer inlined? Nein. Das wäre auch gar nicht wünschenswert. Hab ich gesagt, dass du dir eine neun schichten Tiefe Template-Abstraktionsfabrik bauen kannst und dann immer noch alles geinlined wird und es "0 cost ist"? Nein, aber das ist auch ein komplettes Strohmannargument. Das hier wird geinlined.

    >Dann heißt es mal wieder: ""Clean" Code, Horrible Performance" (das Video ist auch bekannt, nimmt Felix an).
    Warum lutscht du Casey Muratori nicht einfach eindlich die Pene, wenn du es so dringend willst, denn darauf laufen all deine Textwände ja letztlich hinaus.

    Felix kann dieses MÖchtegern-1337-Indie-Entwickler-Kreisgewichse eh nicht mehr hören.
    <HURR ich lade eine Million Dreiecke mit EINEM Systemaufruf in den RAM meiner 10k NG teueren GPU und dann rendert die GPU das mit 1000 Frames pro Sekunde!!!
    <Ich bin ja so klever!!! Warum sind nicht alle so genial wie ich? Warum ist andere Software langsam? Dreiecke an die GPU hochladen ist das KOMPLEXESTE Problem der Welt. Achja und Octrees, einen Octree-Fetisch haben sie auch alle. Die komplexeste Datenstruktur der Welt. Nur supergeniale Spieleprogrammierer können die verstehen. Und Voxel. Jeder muss seine eigene scheiß Voxelengine programmieren. Es ist ja so originell, hat noch nie jemand gemacht. Blabla...
  • [l] Felix Sun, 18 May 2025 01:06:02 GMT Nr. 156739
    WEBM 640×360 0:11 1.4M
    WEBM 648×480 0:05 952.7k
    >>156631
    >Felix ist sich zu 99% sicher, dass genau für diesen Anwendungsfall im C-Standard für structs eine Ausnahme existiert.
    Wegen der strict-aliasing-Rule braucht man zumindest schon mal den Umweg über Werte statt Zeiger. D.h. entweder ein memcpy oder union. (Anmerkung: Kompiler implementieren memcpy als Kompiler-Intrinsic, d.h. bei zur Kompilierzeit bekannter Größe, zumindest bis zu einer gewissen Grenze, wird keine memcpy-Funktion aufgerufen.) Die Probleme beginnen, wenn man memcpy über mehrere struct-Elemente hinweg machen will (insbesondere ggf. über Padding hinweg). Von letzterem lässt Felix von vornherein die Finger.

    Beste Beschäftigung damit hat Felix hier gefunden:
    https://groups.google.com/a/isocpp.org/g/std-discussion/c/swgdeSqyTIw

    Es scheint aus dem Standard so zu sein, dass man ein union braucht:
    https://port70.net/~nsz/c/c11/n1570.html#6.5.2.3p6
    https://timsong-cpp.github.io/cppwp/n4659/class.mem#20
    https://timsong-cpp.github.io/cppwp/n4659/class.union#1
    Felix sagt daher erst mal: Nein, ohne memcpy oder union gehts in C und C++ nicht (wenn man nicht Strict Aliasing deaktiviert). Der Standard definiert zwar "layout-compatible classes", aber es wurde vergessen festzulegen, was man mit denen überhaupt machen darf. Im Spezialfall von unions wurde es festgelegt.

    >AFAICT the standard doesn't actually say what can and can't be done with layout-compatible types.
    https://stackoverflow.com/questions/21956354/can-i-legally-reinterpret-cast-between-layout-compatible-standard-layout-types/22017275#22017275

    Felix mag Sprach-Juristerei aber generell nicht.
    Dazu relatiert gibt es auch diesen schönen Torvalds-Rant (Kernel benutzt insbesondere -fno-strict-aliasing):
    https://lkml.org/lkml/2018/6/5/769

    >Und wie oft berechnest du eine Konvolution über ein Bild, in dem jedes Pixel einen anderen polymorphen Typ hat? Das ist völlig an den Haaren herbeigezogen.
    Das ist ein guter Punkt. Felix will aber anführen, dass er im Absatz darüber über seine Grafikgeschichten geschrieben hat und er das in dem Kontext anführen wollte (und nicht im Kontext von Polymorphie), d.h. dass man im Allgemeinen sehr wohl nicht nur elementweise Operationen durchführen will, und daher so etwas wie z.B. std::for_each + Lambda nicht klappt (und einfach Parameterleiste länger machen klappt auch nicht).

    Desweiteren hat Felix auch direkt die Beispiele aus
    >Wenn es un-mathematischer sein soll: Sortieren, Mustersuche (mit variabel langem Muster/Regex), geordnetes Zusammenfügen zweier Arrays (wie bei Mergesort), Zusammenfügen im Reißverschlussverfahren von zwei Arrays ("zip"), Element entfernen, Duplikate entfernen, bestimmte Elemente duplizieren, Umordnen (z.B. Elemente bestimmten Typs ans Ende verschieben), Shiften eines Arrays um n Stellen mit Wraparound (braucht idR temporären Speicher), Array splitten.
    nachgeschoben. Da kommt man mit elementweisen Operationen nicht weit und will auf den ganzen Arrays arbeiten. Da ist es dann mit elementweisen Operationen vorbei. Und wenns um Polymorphie geht: Einige der obigen Operationen brauchen eine Vergleichsfunktion, die dann bei Polymorphie eine virtuelle Funktion aufruft (z.B. Flächeninhalt von Shapes).

    >Array von Pointern. Nicht Arrays allgemein. Auch nicht Pointer zu Arrays. Arrays von Pointern.
    Und was hat das dann mit den Beispielen zu tun? In den Code-Beispielen werden gerade keine Arrays von Pointern übergeben, sondern Werte-Arrays. Wohin sollte das Argument führen?

    >Arrays von Pointern als Argument an eine Funktion zu übergeben ist etwas, das bei Felix in der Praxis eigentlich nie vorkommt.
    Arrays würden zu Pointern decayen, d.h. "Array von Pointern" wäre void f(Typ * x[]) oder void f(Typ ** x) als Funktionssignatur. Das soll nicht vorkommen?

    >
    int execve(const char * pathname, char * const argv[], char * const envp[]);

    >argv is an array of pointers to strings passed to the new program as its command-line arguments.

    Dann gibt es noch den elitären Triple-Pointer in scandir ("T-T-TRIPLE POINTER!!1"):
    >
    int scandir(const char * dirp, struct dirent *** namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **));

    >The scandir() function scans the directory dirp, calling filter() on each directory entry. Entries for which filter() returns nonzero are stored in strings allocated via malloc(3), sorted using qsort(3) with the comparison function compar(), and collected in array namelist which is allocated via malloc(3). If filter is NULL, all entries are selected.

    >
    void glShaderSource(GLuint shader, GLsizei count, const GLchar ** string, const GLint * length);

    >string: Specifies an array of pointers to strings containing the source code to be loaded into the shader.

    Wenn das zu C-ig ist, dann hier mal was für C++:
    >
    inline void setData(T * const * data)

    >Set the data as an array of pointers.
    https://nvidia-omniverse.github.io/PhysX/physx/5.1.3/_build/physx/latest/struct_px_vehicle_array_data.html

    >
    bool addActors(PxActor * const * actors, PxU32 nbActors)

    >Adds actors to this scene.
    >this method is optimized for high performance. (!!!11111111)
    https://nvidia-omniverse.github.io/PhysX/physx/5.1.3/_build/physx/latest/class_px_scene.html

    Letzteres ist ja auch klar: Man will nicht nur "set an element, set an element, set an element, element, element" machen, sondern auch einmal die Elemente in einem Rutsch übergeben. Und wenn die Elemente groß sind, will man nicht Werte, sondern Zeiger auf die Elemente übergeben.

    Auch: Immer wenn in C++ ein std::vector<Base *> & v übergeben wird (und man dann (*i)->Irgendwas() aufrufen kann, Polymorphie!!1), wird schlussendlich damit der Funktion auch ein Array von Pointern gegeben, über das man via Iteratoren oder v.data() iterieren kann und dann die Methoden aus der VTable aufrufen kann. Wie bei jedem Zeiger-Array kann von vornherein das hiesige Problem nicht auftreten (man kann dann noch einen fehlerhaften Downcast machen, aber das ist eine andere Baustelle).

    Vom vorherigen unabhängig, aber in Bezug auf dein vorgebrachtes Inlining: Wenn einem Inlining wichtig ist und es Sachen von Drittanbietern gibt, will man die Elemente gerade in einem Rutsch übergeben, weil es eine Drittanbietergrenze gibt, und da kein Inlining funktioniert. Auch versteht Felix immer noch nicht, warum sich dieser Teil um Zeiger-Arrays dreht, Felix geht es um Werte-Arrays.

    >Welches Video?
    Felix muss korrigieren auf: Der Kompiler bekam das if nicht aus der Schleife gehoistet, was der Schritt nach dem Inlinen wäre.
    https://www.youtube.com/watch?v=rX0ItVEVjHc&t=2588s
    Was Felix in der Meinung bestärkt, ein Kompiler bereits bei simplen Dingen nicht mehr durchblickt.

    >Probier es doch selbst aus.
    >Ich fresse meinen Besen, wenn der Compiler das nicht inlined.
    >Das hier wird geinlined.
    >Nein, aber das ist auch ein komplettes Strohmannargument. Das hier wird geinlined.
    Felix bezweifelt nicht, dass es im Spielzeugbeispiel geinlined wird. Das Strohmannargument hier ist das obige "Und ich höre dich jetzt schon sagen: Aber! Aber! Funktionsaufrufe sind langsam! Muh SIMD!!!11", denn das hat Felix nirgends gesagt. Was Felix daraufhin sehr wohl gesagt hat: Du hast das Problem nur verschoben, und im Spielzeugbeispiel unsichtbar gemacht, und sobald man die Schleife aus der Funktion (mit der Array-Definition) rausrefaktorisiert, steht man wieder vor dem gleichen Problem.

    >Warum lutscht du Casey Muratori nicht einfach eindlich die Pene, wenn du es so dringend willst, denn darauf laufen all deine Textwände ja letztlich hinaus.
    Soll ich jetzt sagen, du schnüffelst am Clean Kot von Onkel Bob? Das bringt doch nichts.
  • [l] Felix Sun, 18 May 2025 01:15:19 GMT Nr. 156740
    >>156739
    Es geht hier aber nicht um "layout-compatible" sondern um den Fall, dass man ein struct auf seinen ersten Member casten darf (beides Pointer natürlich). Und da bin ich mir 99% sicher, dass das explizit erlaubt ist.
  • [l] Felix Sun, 18 May 2025 01:47:49 GMT Nr. 156741 SÄGE
    >>156739
    >Arrays würden zu Pointern decayen, d.h. "Array von Pointern" wäre void f(Typ * x[]) oder void f(Typ ** x) als Funktionssignatur. Das soll nicht vorkommen?
    >execve
    >scandir
    >glShaderSource
    Ich habe nicht gesagt, dass es nie vorkommt, sondern fast nie. Weiß auch nicht, was du hier eigentlich argumentieren willst. Oder willst du einfach nur klugscheißen? Du meintest zuerst, dass Pointer böse sind, und dass Arrays von Pointern gaaanz gaaanz böse und gaaaaaaanz schlechter Programmierstil ist. So als ob ich vorgeschlagen hätte, dass in einer Bitmap jedes Pixel ein Objekt auf dem Heap sein soll und das eigentliche Bild dann aus einem riesen Array aus lauter Pointern bestünde, nur damit man OOP machen kann, und bla bla, oh Gott, die Humanität. Und du bist so schlau, weil du rausgefunden hast, dass das langsam wäre.
  • [l] Felix Sun, 18 May 2025 04:17:53 GMT Nr. 156743 SÄGE
    >>156741
    Was echt, das hat der rausgefunden? Dass ein Array von Objekten schneller ist als ein Array von Zeigern zu Objekten? Nobelpreis einkombend...
    Außerdem hat er herausgefunden, dass wenn diese Objekte polymorph sind, das mit dem Array von Objekten nicht mehr so gut klappt und deswegen sey ganz OOP immer scheiße. Außerdem ist er gar kein Klugscheißer sondern nur ein Dünnpfeifer.
  • [l] Niemand Wed, 21 May 2025 22:31:41 GMT
    Beitrag hat niemals nie nicht existiert
  • [l] Felix Wed, 21 May 2025 23:12:11 GMT Nr. 156883
    >>156740
    >Es geht hier aber nicht um "layout-compatible" sondern um den Fall, dass man ein struct auf seinen ersten Member casten darf (beides Pointer natürlich).
    Damit hätte man zwei Zeiger unterschiedlichen Typs auf das gleiche Objekt, und das riecht für Felix nach einem Verstoß gegen die strict-aliasing-Rule.

    Felix hat von einem GNOME-Entwickler gefunden (aus dem Jahre 2018):
    >There's too much code, when using GObject, that does not follow the strict aliasing rules.
    https://bugzilla.gnome.org/show_bug.cgi?id=769183#c3

    Und dementsprechend gab es "Use -fno-strict-aliasing for everything".

    >>156741
    >Ich habe nicht gesagt, dass es nie vorkommt, sondern fast nie.
    Das "Arrays von Zeigern" im Kot vorkommen, und auch mal ein Zeiger/Referenz darauf rumgereicht wird, kommt Felixens Meinung schon häufiger in OOP-Kot vor. In C++ dann nur eben als std::vector. Letzteres sieht man an API-Grenzen weniger, weil C++/STL für Modul-übergreifende Parameterübergabe denkbar ungeeignet ist, weil die kleinste Änderung an der STL die ABI-Stabilität karpott macht, und man daher dazu konvergiert ist, keine C++-API mit STL und STL-Containern anzubieten, insbesondere auf Windows. Die üblichen Alternativen sind eigene Container in der Bibliothek zu implementieren (hallo QList<T>) oder Header-only-Bilbiotheken, die direkt mitkompiliert werden.

    Wenn man sich normalen C++-OOP-Kot anschaut, kann man das zu Zeiger-Arrays gesagte mal wie folgt transferieren:

    Schritt 1: Ein Zeiger-Array und ein std::vector<Typ *> ist von der Datenstruktur her hier äquivalent (aber bzgl. der Sprache nicht, siehe unten).
    Schritt 2: Ob das nun ein Zeiger oder ein Schlau-Zeiger ist, ist hier äquivalent.
    Schritt 3: Einen std::vector direkt zu übergeben, oder indirekt als Teil eines structs oder einer class zu übergeben, ist hier äquivalent.
    Schritt 4: Methoden kriegen einen this-Zeiger übergeben.

    Ergebnis: Wenn man Kot hat wie

    class MyClass
    {
    	std::vector<std::unique_ptr<Base>> v;
    
    public:
    	void DoSomething()
    	{
    		for(auto & i : v)
    			i->DoSomething();
    	}
    };


    dann kriegt DoSomething ein Zeiger-Array übergeben. Ist auch wenig überraschend, schließlich kann diese Methode über das Zeiger-Array iterieren.
    Das ganze kann man auch umgekehrt machen:

    class MyClass
    {
    	Base * b;
    
    public:
    	void DoSomething()
    	{
    		b->DoSomething();
    	}
    };
    
    void f(std::vector<MyClass> & v)
    {
    	for(auto & i : v)
    		i.DoSomething();
    }


    Es gibt dann in beiden Fällen Zeiger-Arrays, die ggf. über Umwege an Funktionen übergeben werden. Felix hält obige Kot-Abschnitte für durchaus öfters in C++-OOP-Kot anzutreffend.

    Und damit kommt nun Felix zum entscheidenden Unterschied, warum ein Zeiger-Array und std::vector<Typ *> eben nur von der Datenstruktur her äquivalent sind (zumindest in Bezug auf den Array-Teil), aber in einem ganz entscheidenden Punkt in der Sprache nicht: In C++ sind std::vector<Base *> und std::vector<Derived *> vollkommen unabhängige Typen, die nichts miteinander zu tun haben. Man kann vom einen vector-Typ nicht zum anderen vector-Typ konvertieren, weder implizit noch durch simplen Cast.

    Es geht dabei um die Typ-Konfusion über den Typ des Arrays bzw. die Größe des Strides: Ein Derived1 * kann man in Base * konvertieren, und verliert damit das Wissen über den Array-Stride. Bei einem union verliert man kein Wissen über den Stride, weil man das Wissen über den Stride durch die Größe vom Union-Typ hat. Und das ist auch der Grund, warum in den vorherigen Beispielen mit member_size alles funktionierte: Man hat das Wissen über den Array-Stride bei den Funktionsaufrufen explizit weitergepumpt. Irgendwie muss man diese Information schon weiterpumpen. Man muss eben darauf achten, dass kein Informationsverlust darüber auftritt.

    In C++ taucht mit std::vector dieses Problem des (durch impliziten Cast erlaubten) Informationsverlustes nicht auf. Man kann eine Referenz auf std::vector<Derived1 *> nie mit einer Referenz auf std::vector<Base *> konfusionieren, da komplett unabhängige Typen in C++, Kompilerfehler. Die Sprache hat es schlicht verboten und ein großes Kompilerfehler-Schild davor aufgestellt, so dass man gar nicht erst in die Nähe dieses Problems kommt.

    Und dieses Verbot mit großem Kompilerfehler-Schild ist natürlich auch zu weitläufig: In manchen Fällen könnte man durchaus ein std::vector<U> an Stelle eines std::vector<V> für richtig halten, für bestimmte U und V, siehe Liskov-Substitutions-Prinzip und Kovarianz/Kontravarianz. C++ hat es komplett verboten. Erinnert ein wenig daran, wie andere Sprachen wie C# oder Java auch Werte-Arrays von Objekten verboten haben.

    Das Problem mit dem Informationsverlust kommt auch z.B. bei stinknormaler Benutzung der STL vor, und zwar mit stinknormalen Vergleichs-Lambdas:

    #include <stdio.h>
    #include <algorithm>
    
    struct Base
    {
    	int member1;
    };
    
    struct Derived1 : public Base
    {
    	int member2;
    	int member3;
    };
    
    void Sort1(Base * arr, size_t n)
    {
    	std::sort(arr, arr + n, [](Base & a, Base & b)
    	{
    		return a.member1 < b.member1;
    	});
    }
    
    void Sort2(Base * begin, Base * end)
    {
    	std::sort(begin, end, [](Base & a, Base & b)
    	{
    		return a.member1 < b.member1;
    	});
    }
    
    int main(int /*argc*/, char ** /*argv*/)
    {
    	Derived1 arr[8];
    
    	for(int i = 0; i < 8; i++)
    	{
    		arr[i].member1 = i * 2;
    		arr[i].member2 = 5;
    		arr[i].member3 = 1337;
    	}
    
    	Sort1(arr, sizeof(arr) / sizeof(arr[0]));
    
    	printf("%d\n", arr[0].member1);   // 0
    	printf("%d\n", arr[1].member1);   // 5
    	printf("%d\n", arr[2].member1);   // 1337
    	printf("%d\n", arr[3].member1);   // 6
    
    	//Sort2(arr, arr + sizeof(arr) / sizeof(arr[0]));
    
    	//printf("%d\n", arr[0].member1);   // 0
    	//printf("%d\n", arr[1].member1);   // 5
    	//printf("%d\n", arr[2].member1);   // 5
    	//printf("%d\n", arr[3].member1);   // 5
    
    	return 0;
    }


    Wenn man da jetzt sagt, das Problem taucht in der Praxis nicht auf, dann nur, weil der C++-Programmierer hier gesehen hat, dass das Ergebnis total karpott ist, dann brav seinen Kot debuggt hat, und seinen Kot in Zeiger-Arrays umgebaut hat, und siehe da: Das Problem ist weg! Man sieht solche Stellen nicht, weil sie eben entkäfert worden sind, und Leute, die das Problem kennen, das von vornherein mit Zeiger-Arrays umschiffen.

    >Du meintest zuerst, dass Pointer böse sind
    "Pointer böse" wird Felix sicherlich nicht sagen.
    >und dass Arrays von Pointern gaaanz gaaanz böse und gaaaaaaanz schlechter Programmierstil ist.
    Felix hat seinen Punkt bereits mehrfach vorgetragen: So eine Vererbung und Werte-Arrays sind inkompatibel. Man würde annehmen, wenn man ein neues Feature namens Vererbung einer Sprache hinzufügt, ein vorheriges Kernfeature damit nicht inkompatibel sein sollte/bei Benutzung karpott gemacht werden sollte. Zumal das Zeiger-Array nur dann notwendig wird, wenn man Vererbung im OOP-Stil benutzen will, d.h. Lese- und Schreibzugriff auf Arrays mit Typen, die auch erst irgendwann später von Drittanbietern mal zur Laufzeit hinzukommen.

    Die Alternative mit unions hat Felix auch vorgeschlagen, hätte der Sprachdesigner/Kompilerentwickler auch so bauen können. Diese Alternative hätte man damals gehabt, dann wäre nichts karpott gegangen. Mit unions hätten sich alle Probleme aus diesem Faden in Luft aufgelöst. Dann gibts nur halt keine solche Erweiterbarkeit zur Laufzeit, was man aber eh fast nie braucht.

    Vielmehr hat sich im Faden herauskristallisiert: Die Vererbung im OOP-Stil (Drittanbieter, zur Laufzeit wie ein Plugin-System, in polymorphen Typen gehalten) kommt in der Praxis tatsächlich extrem selten vor, allein schon, weil man meist keinen solchen Drittanbieter hat. Stattdessen hat man seine Shape-Arrays mit Rechtecken und Kreisen, die aber nicht mehr um weitere Typen zur Laufzeit erweitert werden. Aber genau auf das zuvor beschriebene, extrem selten vorkommende Ziel hin haben die Sprachdesigner/Compilerbauer ihren Vererbungskrams implementiert. Wie Felix schon sagte: Eine schlechte Entscheidung; den häufigen Fall verschlechtern, um den seltenen Fall zu ermöglichen.

    Statt seine Werte-Arrays mit union-Typen hat Felix von den Sprachdesignern nun gewissermaßen den Zwang zu Zeiger-Arrays vorgesetzt bekommen ("Zwang" im Sinne davon, dass wenn man den bunten Syntaxzucker wie gewünscht benutzt, das auf Zeiger-Arrays hinausläuft). Und man kriegt den ganzen Rattenschwanz, der an dem Vorhalten von Zeigern hängt (Allokation, und sei es mit eigenem Allokator, Deallokation falls notwendig, und Lebenszeiten, und ggf. mal das ein oder andere Aliasing-Problem). Danke, liebe Sprachdesigner. Eure Hirne wurden vom hippen, coolen OOP-Quatsch der 80er/90er infiziert, und ihr habt eure Entscheidung getroffen, ohne zu überprüfen, ob das in der Praxis überhaupt so vorkommt. Man braucht eine so weitreichende Erweiterbarkeit nicht als Standardsprachfeature. Ihr seid in den 90ern falsch abgebogen.

    Das ist keine Kritik am gemeinen C++-Programmierer, dass der total doof sei und totalen Schrott schreibe. Nein, es ist das, wo die Sprache einen ideomatisch hinlenkt.
  • [l] Felix Wed, 21 May 2025 23:33:25 GMT Nr. 156884
    >>156883
    >Damit hätte man zwei Zeiger unterschiedlichen Typs auf das gleiche Objekt, und das riecht für Felix nach einem Verstoß gegen die strict-aliasing-Rule.
    Das ist doch Schwachsinn. Wenn man das weiterdenkt, dann dürftest du auch niemals einen Zeiger auf Element eines Structs nehmen oder eines Arrays.

    struct Parent {
      int field1;
    };
    
    struct Child {
      struct Parent parent;
      int field2;
    }
    
    int main()
    {
      struct Child child;
      //struct Parent *parent = (struct Parent*)&child;
      struct Parent *parent = &child.parent; 
    }
    


    Was soll da der Unterschied sein? Es wäre halt einfach nur nervig, wenn du jedes mal &x.parent schreiben müsstest, vor allem, wenn du mehrere Hierarchiestufen überspringen musst. &x.parent.parent.parent.parent...
  • [l] Felix Wed, 21 May 2025 23:36:02 GMT Nr. 156886 SÄGE
    <Liskov-Substitutions-Prinzip
    Spätestens hier hört Felix auf zu lesen. Jeder, der diesen Ausdruck verwendet, hat sich bisher als absoluter Oberdepp erwiesen.
  • [l] Felix Wed, 21 May 2025 23:48:36 GMT Nr. 156887
    >This basically applies to all code that does things like if I understand correctly.
    >
    >> SomeObject *obj = ...;
    >> ParentObject *parent = PARENT_OBJECT(obj);
    >>
    >> obj->foo = 123;
    >> parent->foo = 456;
    >> some_method(obj) // might see parent->foo == 456 or not

    Der entscheidende Teil hier ist
    <if I understand correctly

    Klingt alles nach FUD. Und die Gnome-Entwickler sind auch nicht allwissend. Felix weiß das, weil er schon mit ein paar davon zu tun hatte. Auf deren Bugtrackern kann man sehr viel Unsinn und Halbwissen lesen manchmal. Das ist ungefähr so wertvoll als Quelle wie ein Kommentar auf /fefe/.

    >https://bugzilla.gnome.org/show_bug.cgi?id=769183#c3
    >Und dementsprechend gab es "Use -fno-strict-aliasing for everything".

    Keine Ahnung, aber
    pkg-config --cflags glib-2.0
    -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-6 -pthread
    

    Sehe dort zumindest kein -fno-strict-aliasing.

    Und bei dem ursprünglichen Link mit Cocoa [0] dort ging es um was völlig anderes. Apfel hat diese Eigenart, dass sie Typen wie CGRect und NSRect in mehreren Headern definieren, jeweils als komplett eigene Definition, aber eigentlich identisch. Das ist auch wieder nicht das, was ich meinte. Da gibt es kein Struct, das von einem anderen "erbt". Das sind Parallelstrukturen, die zufällig die gleiche Struktur haben. Die sind nicht miteinander verwandt. Zwischen denen zu casten ist in der Tat potentiell gefährlich. Nochmal: Das ist was völlig anderes, und da greift die Klausel, an die ich mich zu erinnern meine, aus dem C-Standard auch nicht.

    Der Gnom-Entwickler da hat einfach nur den gleichen Denkfehler gemacht wie du.

    [0] https://www.cocoawithlove.com/2008/04/using-pointers-to-recast-in-c-is-bad.html
  • [l] Felix Thu, 22 May 2025 00:41:32 GMT Nr. 156889
    JPG 1980×1080 564.1k
    >>156883
    >Das "Arrays von Zeigern" im Kot vorkommen, und auch mal ein Zeiger/Referenz darauf rumgereicht wird, kommt Felixens Meinung schon häufiger in OOP-Kot vor. In C++ dann nur eben als std::vector.
    >dann kriegt DoSomething ein Zeiger-Array übergeben. Ist auch wenig überraschend, schließlich kann diese Methode über das Zeiger-Array iterieren.
    >Felix hält obige Kot-Abschnitte für durchaus öfters in C++-OOP-Kot anzutreffend.
    Ich hatte geschrieben, dass das in meinem Code so gut wie nie vorkommt, nicht in irgendwelchem Ranzkot. Natürlich ist Ranzkot voll mit sowas. Erklärt sich von selbst. Deswegen ist es ja auch Ranzkot.
  • [l] Felix Thu, 22 May 2025 23:50:34 GMT Nr. 156937
    >>156884
    >>156887
    >Wenn man das weiterdenkt, dann dürftest du auch niemals einen Zeiger auf Element eines Structs nehmen oder eines Arrays.
    Naja, die Frage ist, ob der "effektive Typ" von einem Objekt vom Typ struct S { int n; } und einem Objekt vom Typ int n; der gleiche ist. Also ob ein struct-Kondom etwas am "effektiven Typ" vom Objekt ändert oder nicht. Das ist zu unterscheiden davon, ob man sich zum Objekt (auch über structs) in Form eines Elements hinhangelt, und dann nur Überlegungen über den "effektiven Type" von dem einem Element int n; macht. Bei Arrayelementen hätte man sowieso keine unterschiedlichen Zeigertypen, Pointer können da immer aliasen.

    Aber Felix hat etwas gefunden.
    In C++ wurde jetzt (im Jahre 2017) explizit klargestellt, dass es funktioniert (nachdem im Jahr 2016 gesagt wurde, dass es funktionieren soll):
    https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0817r0.html
    Es ist also nicht so offensichtlich, wenn es erst klargestellt werden muss.
    Damit ist nun natürlich die Frage, wie es in C ohne Klarstellung aussieht...

    Übrigens wird das obige direkt wieder eingeschränkt mit:
    >An array object and its first element are not pointer-interconvertible, even though they have the same address.

    Jedenfalls hat Felix damit etwas gelernt und sagt: Ja, der Kot oben verstößt (jedenfalls in C++) nicht gegen die strict-aliasing-Rule.

    >
    pkg-config --cflags glib-2.0

    Felix hat es gerade auf seinem Gentoo (eigene Kompiler-Flags gesetzt) getestet, und da fehlt eine gigantische Menge an Kompiler-Flags in der Ausgabe.

    >Und bei dem ursprünglichen Link mit Cocoa [0] dort ging es um was völlig anderes. Apfel hat diese Eigenart, dass sie Typen wie CGRect und NSRect in mehreren Headern definieren, jeweils als komplett eigene Definition, aber eigentlich identisch.
    Ich sehe in der Dokumentation nur:
    >typealias NSRect = CGRect
    https://developer.apple.com/documentation/foundation/nsrect
    Felix wundert sich, warum, nachdem der GNOME-Typ (zumindest beim "Product: GStreamer") "we should enable it" gesagt hat, der Status vom Käfer auf "RESOLVED FIXED" gesetzt wurde. Naja seis drum.

    Da du dich mit der glib auszukennen scheinst, würde Felix mal nachfragen, was es damit auf sich hat:
    /* We need GCC for __extension__, which we need to sort out strict aliasing of @object_ptr */
    #if defined(__GNUC__)
    
    #define g_set_object(object_ptr, new_object) \
      (G_GNUC_EXTENSION ({ \
        G_STATIC_ASSERT (sizeof *(object_ptr) == sizeof (new_object)); \
        /* Only one access, please; work around type aliasing */ \
        union { char *in; GObject **out; } _object_ptr; \
        _object_ptr.in = (char *) (object_ptr); \
        /* Check types match */ \
        (void) (0 ? *(object_ptr) = (new_object), FALSE : FALSE); \
        (g_set_object) (_object_ptr.out, (GObject *) new_object); \
      })) \
      GOBJECT_AVAILABLE_MACRO_IN_2_44
    

    https://gitlab.gnome.org/GNOME/glib/-/blob/main/gobject/gobject.h?ref_type=heads#L759

    >>156886
    >Spätestens hier hört Felix auf zu lesen. Jeder, der diesen Ausdruck verwendet, hat sich bisher als absoluter Oberdepp erwiesen.
    Hervorragend! Denn es sind gerade die OOPler, welche mit dem Begriff rumwedeln, wenn sie ihren SOLIDen OOP-Krams machen.
    Und wie man auch an Java sieht, die ihren ? extends T- und ? super T-Krams für die OOPler nachträglich eingeführt haben.

    Da Felix faul ist und es auch nicht besser erklären kann:
    >Inheritance and behavioral subtyping
    >People often think that if one class inherits from another, it means the subclass "is a" more specific version of the original class. This presumes the program semantics are that objects from the subclass can always replace objects from the original class without problems. This concept is known as behavioral subtyping, more specifically the Liskov substitution principle.
    >However, this is often not true, especially in programming languages that allow mutable objects, objects that change after they are created. In fact, subtype polymorphism as enforced by the type checker in OOP languages cannot guarantee behavioral subtyping in most if not all contexts. For example, the circle-ellipse problem is notoriously difficult to handle using OOP's concept of inheritance. Behavioral subtyping is undecidable in general, so it cannot be easily implemented by a compiler. Because of this, programmers must carefully design class hierarchies to avoid mistakes that the programming language itself cannot catch.
    https://en.wikipedia.org/wiki/Object-oriented_programming

    Es gab schon immer das Lager der abstrakten Sprachtheoretiker, die mit dem Kram herumgewedelt haben, und dem gegenüber das Lager der Praktiker, die sich fragen, was da am Ende eigentlich rumkommt. Wenn man aber den abstrakten Krams von Vererbung, und allem, was daran hängt, jetzt auch noch wegschmeißt, dann sagt Felix ganz ehrlich: Dann bleibt von irgendwelchen abstrakten Prinzipien bei OOP fast nichts mehr übrig.

    Denn Felix ist (als Praktiker) auch voll dafür, das gesamte Gehampele mit Vererbung, Liskov-Substitutions-Prinzip, Polymorphismus, und was nun ein "Objekt" eigentlich sei, wegzuwerfen, und stattdessen darauf zu schauen, was der Kompiler ganz handfest macht. Am Ende bleibt dann übrig: Automatisch ein paar Variablen in structs einzubinden, ein paar Funktionen mit impliziten ersten Parameter zu kriegen und ggf. mal automatisch eine VTable zu generieren. Wau.

    >>156889
    Ohne jetzt darauf rumzuhamplen, dass "Felixens Kot" nicht das gleiche wie "bei Felix" ist (insbesondere, wenn man APIs von anderen benutzt), so ist die Einschränkung auf "in meiner kleinen heilen Welt sieht das aber so nicht aus" aber nicht wirklich zielführend. Mag sein, dass bei dir echtes OOP, wie es sonst noch nie versucht wurde, vorherrscht. Felix bezeichnet mit C++-OOP aber schon das, was man für gewöhnlich antrifft. Felix hat auch einem der modernen C++-Projekte, an dem er gerade etwas Kot umschreibt (Hyprland), durchsucht, und natürlich trifft man es da an.

    Das Paradebeispiel für Polymorphie in C++ ist gerade so etwas wie das Shape-Zeiger-Array mit Kreisen und Rechtecken, von denen man dann den Flächeninhalt bestimmen kann. Genau das ist es, was OOPler als Paradebeispiel proklamieren und man direkt auf den ersten Seiten von jedem OOP-Buch beim Thema Vererbung findet. Ich nehme an, dass der Umstand, dass OOPler das als Paradebeispiel vorführen, auch hier nicht zur Debatte steht. Die müssen dann auch dafür geradestehen.


[Zurück]
[c] [meta] [fefe] [erp]