Const Dyn. Array of Record erstellen

    Const Dyn. Array of Record erstellen

    Hallo, ich hab mal wieder ein Problem.

    Ich habe ein const Array of Record. Und das wird initialisiert mit:

    Delphi-Code

    1. const
    2. Items: array[0..ANZAHL_ITEMS - 1] of TMeinItem =
    3. (
    4. (Foo: 1; Bar: 2),
    5. (Foo: 3; Bar: 4)
    6. ...
    7. )


    Soweit so gut. Nun will ich das umbaun in ein dynamisches Array.
    Das krieg ich hin mit Array of T und auch mit Array of Array of T.
    Aber nicht mit Array of TMeinItem (= Record). Ich wird noch wahnsinnig.
    Mit andren Typen klappt diese Syntax super, nur mit Records nicht.

    Delphi-Code

    1. const
    2. Items: TArray<TMeinItem> = [
    3. (Foo: 1; Bar: 2), // findet Foo nicht
    4. (Foo: 3; Bar: 4)
    5. ];
    Wieder etwas gelernt... Ich hätte gedacht, dass du dynamische Arrays überhaupt nicht vorinitialisieren kannst. Schließlich "können die ja nicht einfach im Datensegment vorallokiert werden", sondern der Compiler müsste dann irgendwie in eine globale Initialization-Sektion eine Heap-Allokation schreiben, das am Ende wieder freigeben... Was eine Menge Aufwand für ein ziemliches Randfeature ist. Zudem es bei solchen Deklarationen noch einfacher ist, den Inhalt der "Konstanten" zu verändern.
    Aber das Feature wurde wohl in XE7 hinzugefügt ein Beitrag dazu. Und tatsächlich macht der Compiler alles im Datensegment, ohne Heap-Funktionen.
    Zunächst mal zur Delphi-Syntax: Einfach ist das nicht möglich, weswegen auch immer. Du kannst sogar verschachtelte dynamische Array-Konstanten definieren, nicht aber Records. Ich habe versucht, das zu umgehen, indem ich zuerst ein Record als Konstante definiert habe und dann ein Array, welches diese Record-Konstante enthält - hier wird behauptet, dass die Record-Konstante kein Konstantenausdruck sei (10.1 Berlin; natürlich mit {$J-}).
    Was immer geht, ist das zu tun, von dem ich dachte, dass es der Compiler intern eh so realisiert: Anstatt einer Konstanten definierst du eine globale Variable, die du in der Initialization-Sektion entsprechend initialisiert (das könntest du dann z.B. mit einer Hilfsfunktion machen, so wie sie in dem verlinkten Beitrag zu sehen ist). Wenn du nicht willst, dass die Länge dieser Variablen geändert wird, kannst du auch ein bisschen tricksen:

    Delphi-Code

    1. type
    2. TMeinItem = record
    3. Foo, Bar: Integer;
    4. procedure Init(Const AFoo, ABar: Integer); Inline;
    5. end;
    6. TMyArray = TArray<TMeinItem>;
    7. PMyArray = ^TMyArray;
    8. const
    9. Items: TMyArray = []; // als leeres Array initialisieren
    10. // ...
    11. procedure TMeinItem.Init(Const AFoo, ABar: Integer);
    12. begin
    13. Foo := AFoo;
    14. Bar := ABar;
    15. end;
    16. initialization
    17. // Länge der Konstante ändern - hier muss die Konstante selbst geändert werden, daher ist dieser Trick nötig
    18. SetLength(PMyArray(@Items)^, 2);
    19. Items[0].Init(1, 2);
    20. Items[0].Init(3, 4);
    21. finalization
    22. Finalize(PMyArray(@Items)^);
    23. end.


    Das ist der "klassische" Weg, der allerdings mehr kostet, weil das Initialiseren mit Heap-Funktionen und Programmanweisungen realisiert wird, nicht über den Program Loader.
    Es gibt noch den "anderen" Weg, indem du dir das zusammenbaust. Ein dynamisches Array ist letztlich einfach nur ein Pointer auf das erste Element (oder NIL, falls Länge Null ist). Vor dem ersten Element befindet sich ein Integer mit der Größe und wiederum davor ein Integer mit dem Referenzzähler (genauso wie bei String). Mit diesem Wissen kann man sich ein dynamisches Array selbst bauen, genau so, wie es der Compiler tatsächlich macht:

    Delphi-Code

    1. type
    2. TMeinItem = record
    3. Foo, Bar: Integer;
    4. end;
    5. TDynamicArray<StaticType> = packed record
    6. Ref: Cardinal;
    7. Len: NativeUInt; // in x64 ist die Länge ein UInt64
    8. Data: StaticType
    9. end;
    10. const
    11. ItemHolder: TDynamicArray<Array[0..1] Of TMeinItem> = ( // hier das darunterliegende statische Array übergeben - Start- und Zielindex sind egal...
    12. Ref: Cardinal(-1); // Konstanten haben Referenzzähler -1
    13. Len: 2; // ...so lange hier die Länge zu Start- und Zielindex passt
    14. Data: ((Foo: 1; Bar: 2), (Foo: 3; Bar: 4))
    15. );
    16. Items: TArray<TMeinItem> = @ItemHolder.Data;


    Du definierst also ein statisches Array, was deine Daten enthält und machst dann eine dynamische Konstante, indem du auf diese Daten zeigst. Hier wird nirgends etwas allokiert oder freigegeben, weil das alles statisch auf dem Datensegment liegt. Also kein Problem mit Memory-Leaks.
    Edit: Doch eine Möglichkeit gefunden, mit generischen Längen zu arbeiten...
    Master of the EDH ;)

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „projekter“ ()

    Wow, ich bin grad sprachlos.
    ​Ich werds zwar nicht umsetzen, weils für den Nutzen zu komplex ist, aber zumindest wird ichs so lange durchlesen, bis ichs verstanden hab, versprochen.

    Zum Nutzen, also warum ichs umbaun wollte:
    ​Diese Konstante wird regelmäßig geändert, da kommen Einträge hinzu oder werden gelöscht. Und wenn man ein Sourcecode-Verwaltungssystem benutzt ist die Konstante ANZAHL_ITEMS die, wos regelmäßig Konflikte gibt.
    ​Der eine löscht 2, der andere fügt 5 hinzu und schon stimmt die Anzahl wieder nicht.
    ​Das ist nicht so sonderlich tragisch, weil einem der Compiler ja weiterhilft, aber mit dynamischem Array würde sich das Problem von selbst lösen.
    Ach so. Dafür ist es überhaupt nicht geeignet. Du musst ja immer noch die Länge des Arrays angeben, nur eben diesmal für den ItemHolder (und das sogar zweimal, für die Indices und das Len-Feld).
    Das ist nun mal im Delphi-Syntax (wie in allen anderen mir bekannten Programmiersprachen, die eine derartige Funktion zulassen) so, dass du die im Vorfeld angeben musst. Wenn die Länge des Arrays und der Inhalt selbst unmittelbar untereinander im Code deklariert sind, dann sollte es auch keine Probleme geben. Wenn jemand etwas entfernt und der andere etwas hinzufügt (und beide von derselben lokalen Kopie gestartet sind), musst man beim Commit/Merge sich eh überlegen, wie man das zusammenfügen will. Und dabei muss man sich halt auch die Länge überlegen, anders geht es nicht.
    Ich würde davon abraten (selbst wenn es geht, also kein Array Of Record da verwendet wird), für einen solchen Fall dynamische Arrays zu verwenden. Der Vorteil von statischen Arrays ist, dass sie (minimal) schneller sind: Delphi weiß schon zur Compilierzeit, wo das erste und letzte Element liegt und kann damit die Adresse des Elements, auf das zugegriffen wird, direkt berechnen. Beim dynamischen Array muss der Offset des Pointers selbst noch draufaddiert werden (das ist sozusagen eine überflüssige MOV-Anweisung, wenn du die Features dynamischer Arrays nicht brauchst, zu vernachlässigen). Abgesehen davon muss aber der Compiler noch Referenzzählung betreiben - im Fall des konstanten dynamischen Arrays ist das zumindest eine Abfrage des Referenzzählers und ein Feststellen, dass hierfür keine Referenzzählung gemacht werden soll. Und wenn Bereichsprüfung aktiviert ist, muss bei statischen Arrays die Länge auch nicht nachgeschlagen werden. Das sind alles praktische Argumente gegen konstante dynamische Arrays (wenn auch zugegeben ziemlich schwache, der Overhead ist minimal).
    Die andere Frage ist die, wieso Delphi das überhaupt unterstützt: Der Sinn von dynamischen Arrays ist gerade, dass sie variable Länge haben können. Das macht es relativ unsinnig, sie als Konstanten zu definieren. Vielleicht war das nur dafür gedacht, um den Fall abzudecken, dass dynamische Arrays als Felder von anderen Datenstrukturen auftauchen, bei denen es Sinn macht, dass sie konstant sind.
    Weil ich mir schon einen einzigen Anwendungsfall für ein konstantes dynamisches Array vorstellen kann (Übergabe eines Arrays unbekannter Länge an eine Funktion), hatte ich den Code oben gepostet, der ein dynamisches Array imitiert. Wenn du Fragen dazu hast, dann frag ruhig, um das zu verstehen ist nicht viel nötig, lediglich die Kenntnis darüber, wie Delphi intern dynamische Arrays im Speicher hält.
    Master of the EDH ;)