Registriert: Do Sep 02, 2004 19:42 Beiträge: 4158
Programmiersprache: FreePascal, C++
Umgewandelt, aber ohne das letzte Boolean-Feld. So wie ich das sehe kann man das in Pascal nicht realisieren, da man nach dem Case keine weiteren Variablen deklarieren kann.
Code:
type
_test =record
a:char;
b:longint;
caseBooleanof
True:(x:longint);
False:(z:char);
end;
Gruß Lord Horazont
_________________ If you find any deadlinks, please send me a notification – Wenn du tote Links findest, sende mir eine Benachrichtigung. current projects: ManiacLab; aioxmpp zombofant network • my photostream „Writing code is like writing poetry“ - source unknown
„Give a man a fish, and you feed him for a day. Teach a man to fish and you feed him for a lifetime. “ ~ A Chinese Proverb
Registriert: Do Sep 25, 2003 15:56 Beiträge: 7810 Wohnort: Sachsen - ERZ / C
Programmiersprache: Java (, Pascal)
Ich hab schon lang nix mehr in Delphi gemacht, deshalb entschuldigt meine dumme frage. Aber wie verwende ich so ein Record? Man kann ja nie wirklich wissen, was drinnen liegt? Was ist der Vorteil gegenüber einer Basisklasse mit 2 Kindern?
_________________ Blog: kevin-fleischer.de und fbaingermany.com
Registriert: Di Okt 03, 2006 14:07 Beiträge: 1277 Wohnort: Wien
Die Konstruktion spart Speicherplatz. Der Record kann entweder das eine haben oder das andere, aber nicht beide. Es wird nur der Speicherplatz für die größere der beiden Datentypen bereitgestellt.
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
Nicht ganz. Du kannst auf alle Elemete im Case zugreifen. Auch "gleichzeitig". Es ist nur die Frage ob es Sinn macht. Aber jedes Case beginnt an der gleichen Stelle und die Größe ist die des maximalen Case Unterabschnittes. Aber ja es sparrt Speicher. Denn bei Klassen hast du immer mehr overhead. Sowohl Speicher als auch Geschwindigkeit.
TRect zum Beispiel sieht so aus. Entweder kann man auch alle Elemente seperat zugreifen oder auf die Ecken ObenLinks oder UntenRechts. Und das bei nur 4*4 Bytes und ohne Konvertieraufwand. Denn es liegt ja bereits so im Speicher.
a:char;//oder byte oder shortint je nachdem was du willst
b:integer;
caseintegerof//bescheuerte syntax, ich weiß, der typ zwischen case und of belegt hier keinen speicher
0:(x:integer;g:boolean);
1:(z:char);
end;
Aufpassen musst du noch wegen alignment. Momentan sind nach a 3 bytes ungenutzt, da ein 4byte integer auf eine 4 byte grenze ausgerichted wird. Wenn du keine Lücken willst, must du "packed record" verwenden. Ich weiß auch nicht wie groß bool in c ist. boolean ist in Delphi 1 byte groß. Für größere booleans musst du WordBool(2byte) oder LongBool(4byte,typedef zu BOOL vorhanden) verwenden.
Das Ganze ist jedoch nur in wenigen Fällen sinnvoll. Insbesondere aus Gründen der Typsicherheit. TRect ist einer der wenigen guten Anwendungsfälle. Ansonsten würde ich das nur in wenigen lowlevel Fällen verwenden. Außerdem ist es die Stelle an der die Syntax in Pascal/Delphi am schlechtesten ist.
Registriert: Do Sep 25, 2003 15:56 Beiträge: 7810 Wohnort: Sachsen - ERZ / C
Programmiersprache: Java (, Pascal)
Und wie findest du raus was jetzt wirklich drin ist im Record? Ich versteh ja, dass du damit reinspeichern kannst was du willst. Aber beim lesen vertraut man dann wohl auf Gott (= den programmierer)?
_________________ Blog: kevin-fleischer.de und fbaingermany.com
Registriert: Di Okt 03, 2006 14:07 Beiträge: 1277 Wohnort: Wien
Ich habe so etwas schon ab und zu mal verwendet, aber dann habe ich vor dem varianten Teil explizit einen Hinweis auf die Struktur der nachfolgenden Daten angebracht (ein modifiziertes Beispiel aus dem Delphi-Handbch):
Code:
TShape =(Rectangle, Triangle, Circle, Other);
TFigure =Record
Shape: TShape;
Case TShape Of
Rectangle:(Height, Width:Real);
Triangle:(Side1, Side2, Angle:Real);
Circle:(Radius:Real);
Other:();
End;
End;
Aber Delphi selber geht noch weiter: Auszug aus dem Handbuch Delphi5, Kapitel Datentypen,Variablen und Konstanten (5-25)
Zitat:
Wie bereits erwähnt, erfüllen variante Teile noch eine zweite Aufgabe. Sie können dieselben Daten so behandeln, als würden sie zu unterschiedlichen Typen gehören. Dies gilt auch in den Fällen, in denen der Compiler eine Typumwandlung nicht zuläßt. Wenn beispielsweise das erste Feld einer Variante einen 64-Bit-Real-Typ und das erste Feld einer anderen Variante einen 32-Bit-Integer-Wert enthält, können Sie dem Real-Feld einen Wert zuweisen und anschließend die ersten 32 Bits als Integer-Wert verwenden (indem Sie sie beispielsweise an eine Funktion übergeben, die einen Integer- Parameter erwartet).
Da kann man dem Armen, der diesen Code weiter pflegen soll, nur viel Spaß wünschen
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
Genau. Um ein Recordfeld welches eine Unterschiedung ermöglicht wirst du nicht drumherum kommen. Bei dem TRect braucht man so etwas aber nicht, da es egal ist ob du auf die einzelnen Integer oder die TPoints zugreifst. Es ist nur eine andere Darstellungsform der Daten. Das Beispiel von The-Winner spiegelt aber etwas anderes wieder. Nämlich was passiert, wenn die Einzelbereich des Case unterschiedlich groß sind. Was aber genau genommen ein generelles Problem von Records ist und evtl jetzt in diesem Kontext eher verwirrt als aufklärt.
Im SDL Header wird von dieser Technik im übrigen häufiger Gebrauch gemacht. Hier mal ein Beispiel mit TSDL_RWops. type_ gibt an welches Feld im case wichtig ist.
Code:
TSDL_RWops =record
seek: TSeek;
read: TRead;
write: TWrite;
close: TClose;
// a keyword as name is not allowed
type_: UInt32;
// be warned! structure alignment may arise at this point
caseIntegerof
0:(stdio: TStdio);
1:(mem: TMem);
2:(unknown: TUnknown);
end;
Das TSDL_event macht auch so etwas. Nur komplexer. Wobei ich das in dem Fall des Events sogar etwas ungünstig gelöst finde.
Registriert: Di Mai 18, 2004 16:45 Beiträge: 2623 Wohnort: Berlin
Programmiersprache: Go, C/C++
Zitat:
// be warned! structure alignment may arise at this point
Die warnung hat weder Hand noch Fuß in Objekt Pascal aber in c/c++ schon.
Objekt Pascal Kapselt eine Klasse als pointer also class=pointer, denn class zeigt auf die vtable.
In c/cpp zeig class auch auf eine vtable aber die steht direkt an der stelle womit class MyClass; typedef MyClass* DelphiClass; xD.
Als designerregel sollte man auch beachten, union/case immer vermeiden, wenn es möglich ist.
Es kann verwirrend sein, wenn man vor einer Struktur steht die damit arbeitet, ist oft eine Speicherverschwendung und es birgt eine Fehlerquelle.
Code:
typedef struct _test {
char a;
int b;
union {
int x;
char z;
}
bool g;
} test_t;
test_t t;
if (g)//man muss immer vorher eine prüfung machen
t.x=300;//überschreibt auch den wert in z
else
t.z='c';//wurde beim setzen des flags g nicht z geändert dann steht hier müll drin
Wenn man wirklich nicht um so eine Struktur rum kommt, dann bin ich eher ein Fan von folgenden.
Code:
struct test{
char a;
bool g;
int b;
char reserved[4];
};
Die Variante ist das gleich in Blau aber wesentlich Sprachunabhängiger, da man auf das union/case sprachelement verzichtet und auch den maximalen Speicher als bytes reserviert. Einzig der typecast kommt im normalen code noch hinzu aber die if/else oder switch/case anweisung hat man ja so oder so.
Aber wie ich schon sagte, kann man solche Konstrukte oft umgehen und dass sollte man auch versuchen.
_________________ "Wer die Freiheit aufgibt um Sicherheit zu gewinnen, der wird am Ende beides verlieren" Benjamin Franklin
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
Die Warnung ist ein Zitat aus der sdl.pas.
Ich musst aber gerade stark überlegen was du mit deinen Ausführungen sagen wolltest. Da die letzendliche Anwendung gefehlt hat. Allerdings hat diese Technik in meinen Augen ganz entscheidende Nachteile. Denn einfach so Casten da wird in Delphi nicht gehen. Delphi ist was solche Typen angeht wesentlich stränger als C++. Und was ist wenn du 2-3 Variablen in dem Union hättest? Willst du dann selber die Offsets verwalten? Ist nicht gerade das was ich mir unter Fehlerunabhängig oder gut strukturiertem Code vorstelle. Zu mal die Thematik trotzdem gleich bleiben würde nur das mit Union/Case der Compiler für die nötigen Rechnungen einspringt.
Nichts desto trotz stimmt es aber. Wenn man die Möglichkeit hat es anders zu machen sollte man es anders machen. Und wenn man es doch so macht sollte man sich bewusst sein was dort passiert und entsprechende Vorsicht walten lassen.
Registriert: Di Okt 03, 2006 14:07 Beiträge: 1277 Wohnort: Wien
Was das Casten oder anders machen betrifft: wir haben keine Wahl, oder? Es handelt sich um eine Übersetzung von C++ nach Delphi. Die C++-Variante liegt nun einmal auf dem Tisch, auch wenn man sie nicht gut findet. Und wenn das stimmt, dass in Delphi nach varianten Record-Teilen keine Variablen mehr kommen dürfen (das weiß ich aber nicht und eigentlich frage ich mich auch warum), dann kann man es ohnehin nicht in einen varianten Delphi-Record übersetzen.
Problem: es ist eine Datenstruktur, die
a) entweder einen Integer (4Bytes) oder
b) ein Char(von der Größe her gleich einem Byte) enthalten kann,
daher muss dafür Speicherplatz von zumindest 4 Bytes zur Verfügung stehen.
Wie wärs mit Folgendem (bissl umständlich, aber es geht):
Code:
Type
TTestRec =PackedRecord
a :Char;
b :Integer;
XZ:Integer;// XZ kann ein Integer (4Bytes) oder ein Char (1Byte) sein
g :Boolean;
End;
T4CharRec =PackedRecord
Char1,Char2,Char3,Char4:Char;
End;
Var
MyTest: TTestRec;
My4CharRec: T4CharRec;
MyChar:Char;
Begin
// Befüllen des XZ als Char
My4CharRec.Char1:= MyChar;
MyTest.XZ:=Integer(My4CharRec);
// Auslesen des XZ als Char
My4CharRec:= T4CharRec(MyTest.XZ);
MyChar:= My4CharRec.Char1;
....
NOTA BENE: Wenn ich so etwas mache, bemeckert das der Compiler als "unsichere Typumwandlung", aber er compiliert es und führt es auch richtig aus. Die Unsicherheit betrifft hier möglicherweise das Alignment des Char. Wo das Char sich befindet, unterliegt in diesem Beispiel der Kontrolle des Programmierers. Wenn die Daten aus einer Datei ausgelesen werden, müsste erst festgestellt werden, wo genau in dem 4-Byte-Sack sich das Char befindet.
In c/cpp zeig class auch auf eine vtable aber die steht direkt an der stelle womit class MyClass; typedef MyClass* DelphiClass; xD.
Hm also in C zeigt garnix auf eine vtable, esseidenn du hast es per Hand implementiert.
In C++ würde obige Struct auch keine vtable besitzen. Nur wenn min. eine virtuelle Methode vorhanden ist, wird u. U. eine vtable angelegt. Es gibt aber auch andere Techniken. Und ob die vtable am Anfang oder am Ende oder sonstwo in der Klasse sitzt, hängt vom Compilerhersteller ab. Allgemein gültige Aussagen würde ich da nicht machen.
Der Vorschlag mit dem Casten dürfte bei Little Endian klappen^^
_________________ __________
"C++ is the best language for garbage collection principally because it creates less garbage." Bjarne Stroustrup
Registriert: Di Mai 18, 2004 16:45 Beiträge: 2623 Wohnort: Berlin
Programmiersprache: Go, C/C++
Zitat:
Hm also in C zeigt garnix auf eine vtable, esseidenn du hast es per Hand implementiert.
Ich hatte extra von Klasse geredet und Klassen sind C++(teilwahrheit).
Zitat:
In C++ würde obige Struct auch keine vtable besitzen. Nur wenn min. eine virtuelle Methode vorhanden ist, wird u. U. eine vtable angelegt.
Korrekt, denn wenn eine Methode hinzu kommt, dann ist es eine Klasse und hinter dem Wort "class TBlupp{};" versteckt sich natürlich ein gepimptes struct. (KnowHow am Rande:In Freepascal ist/war class übrigens von object und object von record erweitert worden.) Der Datenteil(vtable) liegt dann an der Stelle, wo die Variable liegt. Die Methoden gibt es als Code nur einmal und liegen wo ganz anders.
Code:
Und ob die vtable am Anfang oder am Ende oder sonstwo in der Klasse sitzt, hängt vom Compilerhersteller ab.
Stimme ich natürlich zu, denn die RTTI Daten werden von jeden Compiler unterschiedlich realisiert und dies hat natürlich einfluss auf die realisierung von vtable.
Zitat:
In c/cpp zeig class auch auf eine vtable aber die steht direkt an der stelle womit class MyClass; typedef MyClass* DelphiClass; xD.
wohl wirklich blöd ausgedrückt, also mal die ausführlichere Version. Bei Delphi ist eine Klasse ein Zeiger auf die vtable, in c/cpp ist eine Klasse die vtable also wenn man TMyClass cls; hat dann steht an der addresse von cls die vtable und somit kann man z.B. mit pointerfoo einfach die adresse von cls nehmen und 8 addieren und wäre bei class TMyClass { public: int a,b,c; void getsum(); }; beim integer c gelandet. Dies nutzen Serialization z.B. aus und man kann ganz einfach über ein stream cls in eine binäre datei speichern, beim laden genug speicher holen( TMyClass cls=new TMyClass; fs.read(cls,sizeof(cls));) und die daten wieder laden. Das geht bei sowas einfachen ohne probleme und ist im netzt mehrfach zu finden.
Als ich von der Warning im Code gesprochen habe, bin ich davon ausgegangen, dass TStdio,TMem,TUnknown Klassen sind und damit ist die größe bei Delphi maximal 4Byte für den Block sind(bei 64bit entsprechend 8byte).
Ich hoffe damit ist die Problematik abgehakt und wir können auf das ursprüngliche problem zurück kommen ^^.
Code:
TKar_Property=packed record
name:array [0..31] of char;
case typ:Byte of
KAR_PROPERTYBOOLEAN:(bool:boolean);
KAR_PROPERTYSTRING:(str:array [0..31] of char);
...
Sowas hab ich z.B. in meinen alten SceneGraph Format gehabt. Ziemlich ekelig, da man dann beim zugriff auf die Werte viel code für braucht aber ab und zu einfach nicht anders lösbar. In dem Fall ist die Case Variante 32Byte groß, da mein char array die größte variable ist.
Ich weiß nicht wie gut der Delphi interpreter mitlerweile ist aber die früheren haben bei deinem TTestRec=packed record mehr speicher gebraucht(16byte), da a:char 4 byte genommen hat,b,xz jeweils 4byte,g nochmal 4byte(weil er nichts packen konnte). Wenn das immer noch so ist,dann einfach mal g:boolean; hinter a:char; packen, dann ist g und a zu einem 4byte align zusammen gepackt und b und xz jeweils 4byte(4byte weniger speicherverbrauch).
Wenn du auf die Variante, die du vorgeführt hast bestehen willst, kannst du über XZ:array [0..3] of char; den Code runter kürzen.
Speziel in diesem Fall würde ich eher zu was anderem Raten.
Code:
TTestRec=Packed Record
a:Char;
g:Boolean;
z:Char;
b:integer;
X:integer;
end;
Du kannst dann ohne Typecast immer noch auswählen und hast kein weiteren Speicher verbraucht aber dies ist natürlich einer der wenigen möglichkeiten.
Mich ärgert bei sowas auch selten der Speicherverbrauch, als die vielen Prüfungen, um fest zu stellen welche Variable man nun zugreifen muss, da dies CPU last erzeugt.
_________________ "Wer die Freiheit aufgibt um Sicherheit zu gewinnen, der wird am Ende beides verlieren" Benjamin Franklin
Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast
Du darfst keine neuen Themen in diesem Forum erstellen. Du darfst keine Antworten zu Themen in diesem Forum erstellen. Du darfst deine Beiträge in diesem Forum nicht ändern. Du darfst deine Beiträge in diesem Forum nicht löschen. Du darfst keine Dateianhänge in diesem Forum erstellen.