Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Zugriff auf Variante Teile in Record-Typen (https://www.delphipraxis.net/57704-zugriff-auf-variante-teile-record-typen.html)

ma2xx 25. Nov 2005 12:22


Zugriff auf Variante Teile in Record-Typen
 
Beim Zugriff auf die Varianten Teile eines Records sollten diese normalerweise auf der gleichen Adresse liegen, oder?

Delphi-Quellcode:
TTest = record
    case Typ:Boolean of
    false: (T1     :Integer);
    true: (T2     :Double);
  end;
Die Adresse von T2 ist 4 Bytes größer als die Adresse von T1! Wieso?
Da es kein packed record ist gehe ich davon aus, dass der Compiler die varianten Teile des Records gleich ausrichtet (Compilerschalter "Recordfelder ausrichten" ein [Delphi5] bzw. auf 8 [Delphi7 und Delphi2005]).

Ändert man obriges Record wie folgt, dann ergeben sich gleiche Adressen für T1 und T2:

Delphi-Quellcode:
TTest = record
    Dummy: Integer;
    case Typ:Boolean of
    false: (T1     :Integer);
    true: (T2     :Double);
  end;
Hintergrund:
Meine Anwendung übergibt ein (sinnvolles) Record einer DLL die in C++ geschrieben ist. Die DLL definiert das gleiche Record, wobei der Variante Teil als "union" definiert wird. Interessanterweise richtet der C++ Compiler bei varianten Teile des Record gleich aus. Da es Delphi nicht gleich ausrichtete kommt es zu einer Verschiebung um 4 Bytes beim Zugriff auf Teil mit der kleineren Adresse. Das original Record definiert übrigens im varianten Teil weitere Records. Die Beispiele habe ich exemplarisch vereinfacht um das Problem zu verdeutlichen.

Für eine Erklärung wäre ich dankbar.

NicoDE 25. Nov 2005 12:53

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von ma2xx
Delphi-Quellcode:
TTest = record
    case Typ:Boolean of
    false: (T1     :Integer);
    true: (T2     :Double);
  end;

Probier's mit...
Delphi-Quellcode:
type
  TTest = record
    Typ   : Boolean;
    case Boolean of
      False: (T1: Integer);
      True : (T2: Double);
  { end; }
  end;
...ist das gleiche in grün.

ma2xx 25. Nov 2005 13:08

Re: Zugriff auf Variante Teile in Record-Typen
 
Korrekt, ist das selbe:

Debugger meldet
@Test.T1: $12F3E8
@Test.T2: $12F3EC
wobei var Test:TTest;

... immernoch 4 Byte unterschied!

himitsu 25. Nov 2005 13:35

Re: Zugriff auf Variante Teile in Record-Typen
 
Eventuell richtet Delphi den Record andersrum aus, obwohl dieses eigentlich falsch wäre?

Versuch es mal so (Variante Teile mit der selben Größe):
Delphi-Quellcode:
TTest = record
  case Typ: Boolean of
    false: (T1, dummy: Integer);
    true: (T2:       Double);
  end;
oder:
Delphi-Quellcode:
TTest = packed record
  case Typ: Boolean of
    false: (T1, dummy: Integer);
    true: (T2:       Double);
  end;

ma2xx 25. Nov 2005 13:50

Re: Zugriff auf Variante Teile in Record-Typen
 
Variante 1 - Fehler:
@Test.Typ: $12F3E4
@Test.T1: $12F3E8
@Test.T2: $12F3EC

Variante 2 - OK, aber ist ja auch packed record:
@Test.Typ: $12F3EB
@Test.T1: $12F3EC
@Test.T2: $12F3EC

... Ziel ist es ohne Dummy auszukommen UND ohne packed (C++ DLL, die das Record verarbeitet, ist auch nicht packed)

NicoDE 25. Nov 2005 13:58

Re: Zugriff auf Variante Teile in Record-Typen
 
Ich würde mich nicht auf das Alignment verlassen (ist auch der Grund für den Compiler-Bug).
Du solltest die Struktur als 'packed' deklarieren und die Zwischenräume durch die benötigte 'Ausrichtung' mit Variablen füllen...

Ein Workaround könnte so aussehen:
Delphi-Quellcode:
type
  TTest = record
    case Integer of
      0: (Typ    : Boolean); // LongBool? (check C/C++ code)
      1: (Reserved: Double;   // Alignment
  { end; }
    case {Typ: }Boolean of
      False: (T1: Integer);
      True: (T2: Double));
  { end; }
  end;
edit:
Zitat:

Zitat von ma2xx
... Ziel ist es ohne Dummy auszukommen UND ohne packed

Wird wegen des Compiler-Bugs nicht gehen...

ps: muss es denn auf der C/C++-Seite unbedingt eine unbenannte Union sein? (ist ohnehin nicht standard-konform...)

ma2xx 25. Nov 2005 14:17

Re: Zugriff auf Variante Teile in Record-Typen
 
Als Workaround kann ich natürlich alle records auf gerade Adressen trimmen indem ich Dummy-Bytes einführe. Das ist auch die derzeitige Lösung. Leider besteht das original Projekt aus ein paar Duzend Records die miteinander verschachtelt sind. Hier manuell eine Ausrichtung auf die korrketen Adressen durchzuführen ist aufwendig und frustrierend. Ich denke schon, dass das die Aufgabe des Compilers ist. Ich hoffe immernoch auf eine Lösung per Compiler (der lieben Codesicherheit wegen) bzw. einer Erklärung, ob diese krumme Adressausrichtung möglicherweise beabsichtigt ist.

NicoDE 25. Nov 2005 14:30

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von ma2xx
Ich denke schon, dass das die Aufgabe des Compilers ist.

Ist es auch, aber er ist nicht perfekt ;)
Ich habe meinen letzten Beitrag editiert; kanst du bitte die Frage nach den unbenannten Unions beantworten?

ma2xx 25. Nov 2005 14:41

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von NicoDE
ps: muss es denn auf der C/C++-Seite unbedingt eine unbenannte Union sein? (ist ohnehin nicht standard-konform...)

Ich bin nicht so vertraut mit der C++ Seite (gibt es auch andere Unions?), denn die DLL ist nicht von mir. Prinzipell sollten die Records in der DLL aber ausgerichtet verarbeitet werden, denn es ist eine schnelle Laufzeit gewünscht (Bildverarbeitungsfunktionen). Ich muss mich also nach der DLL richten.

himitsu 25. Nov 2005 14:47

Re: Zugriff auf Variante Teile in Record-Typen
 
ahh, es könnte tatsächlich an der Speicherausrichtung liegen ^^

Code:
Boolean/ByteBool:
************************  // Ausrichtung alle 1 Byte

Integer/LongInt:
*---*---*---*---*---*---  // Ausrichtung alle 4 Byte

Double:
*-------*-------*-------  // Ausrichtung alle 8 Byte
       
unpacked:
*   *---          // Boolean + Integer
*       *-------  // Boolean + Double

packed:
**---             // Boolean + Integer
**-------         // Boolean + Double

NicoDE 25. Nov 2005 15:11

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von ma2xx
(gibt es auch andere Unions?)

Jupp:
Code:
union {
    int    T1;
    double T2;
} u1;
Würde übersetzt werden mit:
Delphi-Quellcode:
u1: record
  case Boolean of
    False: (T1: Integer);
    True: (T2: Double);
{ end; }
end;
...unbenannte Unions sind eine Strafe für jeden Entwickler der C/C++ nach Delphi portieren muss...

ma2xx 25. Nov 2005 15:25

Re: Zugriff auf Variante Teile in Record-Typen
 
Es ist offensichtlich, dass das Problem die Speicherausrichtung ist.
Delphi-Quellcode:
TTest = record
    case Typ:Boolean of
    false: (T1     :Integer);
    true: (T2     :Double);
  end;
- TTest wird je nach Compilerschalter gerade ausgerichtet (Startadresse)
- Typ (Boolean) ist 1Byte lang (wäre es ein Aufzählungstyp, dann auch 1Byte, es sei denn man aktiviert {$Z4} (4Bytes))
- Es folgt speicherüberlappend T1 (4Bytes lang) und T2 (8Bytes lang) -> nur warum nicht an der gleichen Anfangsadresse???

@Nico Bendlin
Der Code in C++ sieht wie folgt aus (wenn ich mich nicht irre)
Code:
struct {
  Bool     Typ;
  union {
    int    T1;
    double T2;
  };
} TTest;
Jedenfalls ist hier die Adresse von T1 == Adresse von T2!
Siehe hierzu: MSDN Figure 8.1 Storage of Data in NumericType Union

NicoDE 25. Nov 2005 16:02

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von ma2xx
nur warum nicht an der gleichen Anfangsadresse?

Weil der Delphi-Compiler den varianten Teil der Struktur offensichtlich nicht als Einheit betrachtet und die Ausrichtung jeweils einzeln für die Mitglieder der 'Union' berechnet (dies ist wahrscheinlich nicht das, was die meisten Entwickler erwarten würden - kurz, man könnte es als Bug bezeichnen (ich kenne die Delphi-Dokumentation nicht auswendig :))).

Zitat:

Zitat von ma2xx
Der Code in C++ sieht wie folgt aus (wenn ich mich nicht irre)

Ja, leider...
...in einer Typ-Definition haben anonyme Unions eigentlich nichts zu suchen.
( siehe http://msdn.microsoft.com/library/de...m/class_33.asp )

Es wäre besser gewesen, wenn die Deklaration so ausgesehen hätte:
Code:
typedef struct tag_TTest {
  bool     Typ;
  union {
    int    T1;
    double T2;
  } u1;
} TTest, * PTest;
Wie auch immer, es hält dich niemand davon ab, die Struktur in Delphi anders zu übersetzen (zum Beispiel mit u1 als inline-record)...

himitsu 25. Nov 2005 16:03

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von ma2xx
nur warum nicht an der gleichen Anfangsadresse???

Weil die vermutlich an einer ihrem Typ entsprechenden Stelle ausgerichtet werden. (siehe meinen letzten Beitrag)

ma2xx 25. Nov 2005 16:30

Re: Zugriff auf Variante Teile in Record-Typen
 
Nunja, wir kommen wohl nicht weiter ...

Mein Eindruck ist, dass hier ein Delphi-Bug vorliegt.
Meine Versuche zeigten, dass die Anfangsadressen von T1 und T2 abhängig von der Position des Typ sind, egal ob der Typ vor der case oder in der case steht. Es ist auch egal wie lang die varianten Teile des Record sind.
Es bleibt also als Workaround nur das Einfügen von Bytes bis ein gerade Adresse vorliegt (getestet und OK) oder die Verwendung eines weiteren lokalen record um die case (getestet und OK, aber dadurch lokaler Bezeichner -> umständlich).
Die Deklaration der union in C++ ist davon unabhängig. Hier liegen in allen Varianten gleiche Adressen vor!

Vielleicht werde ich mal bei Borland nachfragen ...
Danke für die Diskussion.

Christian Seehase 25. Nov 2005 22:53

Re: Zugriff auf Variante Teile in Record-Typen
 
Moin ma2xx,

je nach Delphi-Version könntest Du es auch einmal mit $A bzw. $ALIGN probieren den Compiler zum "mitspielen" zu bewegen.

NicoDE 27. Nov 2005 17:11

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von Christian Seehase
je nach Delphi-Version könntest Du es auch einmal mit $A bzw. $ALIGN probieren den Compiler zum "mitspielen" zu bewegen.

Bringt in dem Fall nichts (der Bug entsteht dadurch, dass die Ausrichtung für jedes Mitglied des varianten Teils der Struktur neu berechnet wird...).

himitsu 27. Nov 2005 21:30

Re: Zugriff auf Variante Teile in Record-Typen
 
{$A-}, {$A1}, oder {$ALIGN OFF} sollte wohl helfen?
Zitat:

Zitat von OH
Wenn ein Record-Typ mit dem voreingestellten Status {$A+} deklariert wird und die Deklaration nicht den Modifizierer packed enthält, handelt es sich um einen ungepackten Record-Typ. Die Felder des Records werden so ausgerichtet, dass die CPU möglichst effizient darauf zugreifen kann. Die Ausrichtung hängt von den Typen der einzelnen Felder ab und davon, ob Felder zusammen deklariert werden. Jeder Datentyp besitzt eine implizite Ausrichtungsmaske, die vom Compiler automatisch berechnet wird. Sie kann die Werte 1, 2, 4 oder 8 haben und entspricht dem Byte-Raster, in dem ein Wert dieses Typs für den optimalen Zugriff im Speicher angeordnet werden muss. Die folgende Tabelle enthält die Ausrichtungsmasken für alle Datentypen.
...
Wenn ein Record-Typ mit dem Status {$A–} deklariert wird oder die Deklaration den Modifizierer packed enthält, werden die Felder des Records nicht ausgerichtet, sondern einfach an aufeinander folgenden Offsets abgelegt.

Themen in der OH > "$ALIGN" oder auch "Felder ausrichten (Compiler-Direktive)"
wobei hierfür der Abschitt "Record-Typen" (wird von dem oben angegebenen Abschitt weiterverlinkt) zutreffender ist.

NicoDE 28. Nov 2005 09:00

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von himitsu
{$A-}, {$A1}, oder {$ALIGN OFF} sollte wohl helfen?

Nope, weil die Aufgabe war/ist, ohne Hilfsvariablen die selbe Ausrichtung wie in der Referenzstruktur zu erreichen (und das geht, wie oben bereits mehrmals festgestellt, nicht).

ma2xx 28. Nov 2005 09:28

Re: Zugriff auf Variante Teile in Record-Typen
 
Ich bin für neue Ideen immer dankbar ...
Wenn ich folgenden Code teste ...
Delphi-Quellcode:
TTest = record
    case Typ:Boolean of
{$A-}
    false: (T1     :Integer);
    true: (T2     :Double);
{$A+}
end;
... dann erhalte ich tatsächlich gleiche Adressen für T1 und T2:
@Test.T1: $12F3D5
@Test.T2: $12F3D5
Diese Vorgehensweise macht prinzipiell Sinn, wenn man sich als Programmierer dem Problem unterschiedlicher Adressen für die varianten Teile des Records bewusst ist (zumindest muss ich nicht alle Records manuell nachrechnen). Nur jetzt muss man noch klären, ob C++ für die varianten Teile immer eine gerade (ausgerichtete) Adresse standardmäßig verlangt, denn wie unschwer zu erkennen, sind in meinem Beispiel die Adressen ungerade. Ich werde das auch nochmal klären ...

NicoDE 28. Nov 2005 15:56

Re: Zugriff auf Variante Teile in Record-Typen
 
Zitat:

Zitat von ma2xx
Wenn ich folgenden Code teste ...
Delphi-Quellcode:
TTest = record
    case Typ:Boolean of
{$A-}
    false: (T1     :Integer);
    true: (T2     :Double);
{$A+}
end;
... dann erhalte ich tatsächlich gleiche Adressen für T1 und T2:

Weil es das gleiche ist, als wenn du die Struktur als 'packed' deklariert hättest.

Test-Code:
Delphi-Quellcode:
type
  TTest = record
    case Typ: Boolean of
{$A-}
      False: (T1: Integer);
      True: (T2: Double);
{$A+}
  end;

function FieldOffset(const Struct, Field): Cardinal;
begin
  Result := Cardinal(Addr(Field)) - Cardinal(Addr(Struct));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Test: TTest;
begin
  ShowMessage(
    'T1: ' + IntToStr(FieldOffset(Test, Test.T1)) + #13#10 +
    'T2: ' + IntToStr(FieldOffset(Test, Test.T2))
    );
end;
Ergebnis (Delphi 6):
Code:
T1: 1
T2: 1
Ziel sollte aber sein, dass beide Offsets 8 sind (wie in der C-Referenzstruktur).


Gruß Nico


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:08 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz