Adressen in struct
-
Hallo!
struct STRUCT { int i1; char c1[5]; int i2; }; TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { STRUCT test = {0} ; char C[80] = ""; // Inh. von C: 80 Bytes 0 memcpy ( C, &test, 80) ; // Inh. von C: Bytes 0 - 15 = 0, dann Müll // also meiner Meinung nach korrekt // Jetzt die Bytes von i2 auf 1 setzen memset( &(test.i2) , 1 , sizeof( STRUCT) - ( &(test.i2) - &(test.i1)) ); // sizeof(SRTUCT) = 16 // &(test.i2) = EF4 // &test = &(test.i1) = EE8 memcpy ( C, &test, 80) ; // Inh. von C: Bytes 0 - 11 = 0, Bytes 12 - 24 = 1 dann erst Müll // wieso werden die Bytes 16 - 24 auf 1 gesetzt? }Bei mir ergibt die Rechnung
sizeof( STRUCT) - ( &(test.i2) - &(test.i1))
ein Ergebnis von 4. Also müssten die Bytes 12 - 15 auf 1 gesetzt werden.
Warum werden 13 Bytes (12-24) auf 1 gesetzt? Wo ist der Kurzschluss im Code/Hirn?
-
Hi,
Stichwort: Padding / Alignment.
Ein Compiler muss ein struct nicht "dichtestgepackt" in den Speicher legen - und die allermeisten tun das auch nicht. Deswegen beginnt die 2. Komponente des structs im Speicher oftmals nicht direkt hinter der ersten.
Und deswegen ergibt ein sizeof auf ein struct meistens einen größeren Wert als die Summe der sizeofs auf die einzelnen Komponenten.Das stört aber auch alles nicht, weil Du diese ganze "Hampelei" gar nicht zu machen brauchst.
GV schrieb:
.... TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { STRUCT test = {0} ; char C[80] = ""; // Inh. von C: 80 Bytes 0 memcpy ( C, &test, 80) ; // Inh. von C: Bytes 0 - 15 = 0, dann Müll // also meiner Meinung nach korrekt // Jetzt die Bytes von i2 auf 1 setzen memset( &(test.i2) , 1 , sizeof( STRUCT) - ( &(test.i2) - &(test.i1)) ); // sizeof(SRTUCT) = 16 // &(test.i2) = EF4 // &test = &(test.i1) = EE8 memcpy ( C, &test, 80) ; // Inh. von C: Bytes 0 - 11 = 0, Bytes 12 - 24 = 1 dann erst Müll // wieso werden die Bytes 16 - 24 auf 1 gesetzt? }Geht doch viel einfacher:
// STRUCT test = {0} ; => finde ich "ungünstig"; soll test.i1 auf 0 gesetzt werden, dann tue das doch einfach: STRUCT test; test.i1 = 0; // char C[80] = ""; // Inh. von C: 80 Bytes 0 => Simon2: Stimmt nicht ! nur das erste Byte ist 0 !! // richtig: char C[80]; memset(C, 0, sizeof(C)); // 80 braucht man sich nicht zu merken; sizeof macht das immer richtig // memcpy ( C, &test, 80) ; Simon2: Hier liest Du über den erlaubten Bereich hinaus, // tu das nicht, weil es zu undefiniertem Verhalten führt. // besser: memcpy ( C, &test, sizeof(test)) ; // Jetzt die Bytes von i2 auf 1 setzen => Simon2: Dann tu das doch einfach: test.i2 = 1; // memset( &(test.i2) , 1 , sizeof( STRUCT) - ( &(test.i2) - &(test.i1))); // Das hier macht sowieso was Anderes; da ein int aus mehreren bytes besteht und Du JEDES von ihnen auf 1 setzt, // hat i2 hinterher nicht den Wert 1, sondern sowas wie 0x1 + 0x100 + ... (= 1+256+...) ...Langer Rede kurzer Sinn: Mit sizeof und memset im struct rumhüpfen ist schwer unsicher/undefiniert und eigentlich immer unnötig.
Gruß,
Simon2.
-
Das hat mit Padding gar nichts zu tun. GV hat übersehen, dass beim Ermitteln der Differenz der Adressen von i2 und i1 Zeigerarithmetik angewendet wird, das Ergebnis ist in der "Einheit" sizeof(int). In diesem Fall ist das vermutlich 3, und damit setzt er eben 16 - 3 = 13 Bytes auf 1.
-
Gestern habe ich ein ähnliches Problem gehabt. Hier die Lösung:
/* Alten Zustand auf den Stack */ /* und von align 4 nach align 2 */ /* Doppelwortgrenze nach Wortgrenze */ #pragma pack(push, 2) /* sizeof(DLGTEMPLATEEX) = 26 */ typedef struct { short signature; ... short cy; } DLGTEMPLATEEX; #pragma pack(pop) /* wieder align 4 */ /* Doppelwortgrenze */
-
Hast recht! Mit
sizeof( STRUCT) - ( reinterpret_cast <char*>(&(test.i2)) - reinterpret_cast <char*>(&(test.i1)))
ist alles ok.
Danke.

-
Simon2 schrieb:
// STRUCT test = {0} ; => finde ich "ungünstig"; soll test.i1 auf 0 gesetzt werden, dann tue das doch einfach: STRUCT test; test.i1 = 0;Nur am Rande - 'struct test = {0};' und 'struct test;test.i1=0;' machen nicht das selbe. Die erste Variante initialisiert ALLE Member der Struct (was nicht explizit in der Initialisierungsliste genannt wurde, wird mit 0 gefüllt), die zweite belegt nur das Element i1 und lässt den Rest der struct in undefiniertem Zustand.
-
Simon2 schrieb:
Hi,
Stichwort: Padding / Alignment.
Ein Compiler muss ein struct nicht "dichtestgepackt" in den Speicher legen - und die allermeisten tun das auch nicht. Deswegen beginnt die 2. Komponente des structs im Speicher oftmals nicht direkt hinter der ersten.
Und deswegen ergibt ein sizeof auf ein struct meistens einen größeren Wert als die Summe der sizeofs auf die einzelnen Komponenten.Weiss ich, darum habe ich auch char[5] gewählt, um genau das zu berücksichtigen/demonstrieren.
[cpp]
// STRUCT test = {0} ; => finde ich "ungünstig"; soll test.i1 auf 0 gesetzt werden, dann tue das doch einfach:
STRUCT test;
test.i1 = 0;Ich setze den ganzen STRUCT Inhlat auf 0.
// char C[80] = "";
// Inh. von C: 80 Bytes 0 => Simon2: Stimmt nicht ! nur das erste Byte ist 0 !!
// richtig:Bei mir/meinem Compiler ergibt das 80 mal '\0' .
// memcpy ( C, &test, 80) ; Simon2: Hier liest Du über den erlaubten Bereich hinaus,
// tu das nicht, weil es zu undefiniertem Verhalten führt.
// besser:
memcpy ( C, &test, sizeof(test)) ;LESEN für zu überhaupt keinem undefiniertem Verhalten, solange ich nicht über den Berich von C hinausSCREIBE.
// Jetzt die Bytes von i2 auf 1 setzen => Simon2: Dann tu das doch einfach:
test.i2 = 1;
// memset( &(test.i2) , 1 , sizeof( STRUCT) - ( &(test.i2) - &(test.i1)));
// Das hier macht sowieso was Anderes; da ein int aus mehreren bytes besteht und Du JEDES von ihnen auf 1 setzt,
// hat i2 hinterher nicht den Wert 1, sondern sowas wie 0x1 + 0x100 + ... (= 1+256+...)Ich will nicht i2 auf 1 setzen, sonder jeden der 4 Bytes.
GV schrieb:
// Jetzt die Bytes von i2 auf 1 setzen
Langer Rede kurzer Sinn: Mit sizeof und memset im struct rumhüpfen ist schwer unsicher/undefiniert und eigentlich immer unnötig.
Ich finde das ist ein effektives Tool.
Wenn man es anwenden kann.

Ich will einen Teil des structs einmal mit Werten belegen und den Rest immer wieder neu schreiben. Also habe ich die "statischen" Elemente nach vorn gepackt und überschreibe den hinteren Teil mit 0, um ihn dann wieder teilweise zu füllen.
-
GV schrieb:
// memcpy ( C, &test, 80) ; Simon2: Hier liest Du über den erlaubten Bereich hinaus,
// tu das nicht, weil es zu undefiniertem Verhalten führt.
// besser:
memcpy ( C, &test, sizeof(test)) ;LESEN für zu überhaupt keinem undefiniertem Verhalten, solange ich nicht über den Berich von C hinausSCREIBE.
Wie kommst du darauf? Du weißt nicht, was direkt hinter test auf dem Stack steht - und wenn du Pech hast, kommst du damit bereits in Bereichen, in denen du nichts zu suchen hast.
Ich will einen Teil des structs einmal mit Werten belegen und den Rest immer wieder neu schreiben. Also habe ich die "statischen" Elemente nach vorn gepackt und überschreibe den hinteren Teil mit 0, um ihn dann wieder teilweise zu füllen.
Randfrage: Wozu soll das gut sein?
-
GV schrieb:
Bei mir/meinem Compiler ergibt das 80 mal '\0' .
"Beweis durch Beispiel", oder wie? Darauf solltest du dich aber nicht verlassen.
LESEN für zu überhaupt keinem undefiniertem Verhalten, solange ich nicht über den Berich von C hinausSCREIBE.
Doch, tut es. Dass es bei dir so schon mal funktioniert hat, ist kein Beweis dafür, dass es so richtig ist. Das ist das Problem bei undefiniertem Verhalten.
-
GV schrieb:
...
Ich finde das ist ein effektives Tool.
...OK, wenn Du damit zufrieden bist, bleibe dabei !
Aber ich verstehe dann nicht, wieso Du diesen Thread aufgemacht hast.
Genau die Probleme, von denen Du hier sprichst, handelst Du Dir mit dieser Vorgehensweise nämlich ein: Du begibst Dich in eine extreme Abhängigkeit von der Vorgehensweise Deines Compilers/Betriebssystems sowie deren aktuellen Einstellungen. Anders gesagt: Was jetzt hier laufen mag, wird morgen woanders höchstwahrscheinlich nicht mehr laufen.
Nur mal Beispiele, die mir spontan einfallen:
- Man kann durchaus sein System so konfigurieren, dass es schon Bereichsüberschreitungen beim Lesen mit einem Segfault bestraft. ... und es gibt auch Systemzustände, bei dem ihm kaum etwas anderes übrigbleibt. Aber auch der SegFault ist nicht das Schlimmste, was passieren kann, sondern der falsche Anschein der Korrektheit. Außerdem: Warum nicht gleich sauber machen ?
- Ich bin mir ziemlich sicher, dass Deine Initialisierung
char C[80] = "";lt. Standard nur das 0 im ersten Byte setzt (mein Compiler macht das auch so). Vermutlich hast Du entweder eine spezielle Einstellung oder einen speziellen Compiler, der Speicher sowieso mit 0 vorinitialisiert. Versuche mal stattdessen mit '1' zu initialisieren... - dasselbe gilt auch für
STRUCT test = {0};.. hier wird nur das erste Element auf 0 gesetzt. - Wenn Du schreibst: "...darum habe ich auch char[5] gewählt, um genau das zu berücksichtigen/demonstrieren...." muss ich fragen: und wie läuft das auf meinem Compiler ? Oder wenn Du die Paddingeigenschaften Deines Compilers anders setzt ?
Ich würde das nicht machen, zumal es auch noch deutlich unverständlicheren Code produziert....
Ich würde IMMER versuchen, mich im Bereich "definiertes Verhalten" zu bewegen und nicht auf "bei mir funktioniert's aber gerade" verlassen.Gruß,
Simon2.
-
CStoll schrieb:
GV schrieb:
// memcpy ( C, &test, 80) ; Simon2: Hier liest Du über den erlaubten Bereich hinaus,
// tu das nicht, weil es zu undefiniertem Verhalten führt.
// besser:
memcpy ( C, &test, sizeof(test)) ;LESEN für zu überhaupt keinem undefiniertem Verhalten, solange ich nicht über den Berich von C hinausSCREIBE.
Wie kommst du darauf? Du weißt nicht, was direkt hinter test auf dem Stack steht - und wenn du Pech hast, kommst du damit bereits in Bereichen, in denen du nichts zu suchen hast.
Was kann passieren, wenn ich das in den von mir belegten Speicherbereich kopiere?
Natürlich darf ich nicht versuchen diese Daten für irgendwas zu benutzen, wenn ich nicht weiss was sie beinhalten.Ich will einen Teil des structs einmal mit Werten belegen und den Rest immer wieder neu schreiben. Also habe ich die "statischen" Elemente nach vorn gepackt und überschreibe den hinteren Teil mit 0, um ihn dann wieder teilweise zu füllen.
Randfrage: Wozu soll das gut sein?
Ich benutze eine Struct mit ca. 40 Elementen als Message zwischen zwei Objekten. Ein Teil der Elemente bleibt während mehrerer "Verschickungen" konstant, die anderen ändern sich jedesmal und es werden auch nicht jedesmal alle gebraucht. Welche gebraucht werden, ist auch jedesmal verschieden. Die nicht gebrauchten müssen auf 0 gesetzt werden. Also setze ich vor jedem neuen Auffüllen alle veränderlichen Elemente auf 0 und überschreibe dann die erforderlichen Elemente mit den entsprechenden Werten.
-
GV schrieb:
...Ich benutze eine Struct mit ca. 40 Elementen als Message zwischen zwei Objekten. Ein Teil der Elemente bleibt während mehrerer "Verschickungen" konstant, die anderen ändern sich jedesmal und es werden auch nicht jedesmal alle gebraucht. Welche gebraucht werden, ist auch jedesmal verschieden. Die nicht gebrauchten müssen auf 0 gesetzt werden. Also setze ich vor jedem neuen Auffüllen alle veränderlichen Elemente auf 0 und überschreibe dann die erforderlichen Elemente mit den entsprechenden Werten.
Ist ja auch erlaubt, aber warum greifst Du mit wilder Pointerarithmetik zu und nicht mit Attributnamen ?
Du kannst es sogar mit Memberpointern sehr weit flexibilisieren ohne Einbußen an Sicherheit und Allgemeingültigkeit. Gerade bei 40 Elementen sehe ich den Vorteil in der Übersichtlichkeit als überwältigend gegenüber ein bischen Einsparungen bei der Tipparbeit....Gruß,
Simon2.
-
Simon2 schrieb:
Wenn Du schreibst: "...darum habe ich auch char[5] gewählt, um genau das zu berücksichtigen/demonstrieren...." muss ich fragen: und wie läuft das auf meinem Compiler ? Oder wenn Du die Paddingeigenschaften Deines Compilers anders setzt ?
Ich habe gerade char[5] genommen, um meine "Untersuchung" unabhängig vom Padding zu machen.
Wenn ich z.B. char[4] genommen hätte, hätte das genau einem int entsprochen und der Fehler in meinem Code wäre mir nie aufgefallen.
-
GV schrieb:
...
Ich habe gerade char[5] genommen, um meine "Untersuchung" unabhängig vom Padding zu machen....Und was hat die "Untersuchung" ergeben ? Das Dein Vorgehen NICHT unabhängig vom Padding ist !!
Jetzt solltest Du die Konsequenzen aus dieser Erkenntnis ziehen und entweder versuchen, diese Sack voller "Paddingflöhe" zu bändigen oder eine Technik wählen, mit der Du nicht vom Padding&Co abhängig bist.
Bei Ersterem wird Dir allerdings kaum jemand hier helfen können und ich wette mit Dir, dass es Dir selbst bald zu mühsam sein wird (stell Dir mal vor, es kommt ein Attribut hinzu oder irgendwelche tauschen die Reihenfolge !).Nochmal: Wenn Deine Technik so genial ist - warum hast Du dann Probleme damit ?

Gruß,
Simon2.
-
GV schrieb:
CStoll schrieb:
GV schrieb:
// memcpy ( C, &test, 80) ; Simon2: Hier liest Du über den erlaubten Bereich hinaus,
// tu das nicht, weil es zu undefiniertem Verhalten führt.
// besser:
memcpy ( C, &test, sizeof(test)) ;LESEN für zu überhaupt keinem undefiniertem Verhalten, solange ich nicht über den Berich von C hinausSCREIBE.
Wie kommst du darauf? Du weißt nicht, was direkt hinter test auf dem Stack steht - und wenn du Pech hast, kommst du damit bereits in Bereichen, in denen du nichts zu suchen hast.
Was kann passieren, wenn ich das in den von mir belegten Speicherbereich kopiere?
Natürlich darf ich nicht versuchen diese Daten für irgendwas zu benutzen, wenn ich nicht weiss was sie beinhalten.Das Schreiben ist in deinem Fall nicht das Problem, aber niemand garantiert dir, daß du den fremden Speicher LESEN darfst.
Wenn du mir nicht glaubst, versuch' mal, das folgende auszuführen:
char* str=0; printf("Test %c",*str);Ich will einen Teil des structs einmal mit Werten belegen und den Rest immer wieder neu schreiben. Also habe ich die "statischen" Elemente nach vorn gepackt und überschreibe den hinteren Teil mit 0, um ihn dann wieder teilweise zu füllen.
Randfrage: Wozu soll das gut sein?
Ich benutze eine Struct mit ca. 40 Elementen als Message zwischen zwei Objekten. Ein Teil der Elemente bleibt während mehrerer "Verschickungen" konstant, die anderen ändern sich jedesmal und es werden auch nicht jedesmal alle gebraucht. Welche gebraucht werden, ist auch jedesmal verschieden. Die nicht gebrauchten müssen auf 0 gesetzt werden. Also setze ich vor jedem neuen Auffüllen alle veränderlichen Elemente auf 0 und überschreibe dann die erforderlichen Elemente mit den entsprechenden Werten.[/quote]Da schließe ich mich mal Simon an - wenn du einen Member auf 0 setzen willst, dann solltest du genau das machen. Da wild mit Pointern rumzuschießen bringt nicht sehr viel.
-
Das Schreiben ist in deinem Fall nicht das Problem, aber niemand garantiert dir, daß du den fremden Speicher LESEN darfst.
Ahja, das hab ich noch nicht gewusst. Ich dachte immer, lesen darf man alles, nur natürlich nicht ausserhalb des eigenen Speichers schreiben.
Wieder was gelernt.
Gut, dass ich solche Sachen normalerweise nicht verwende. In diesem Falle wollte ich allerdings sehen, was "hinter" dem struct passiert, und das erschien mir eine einfache Möglichkeit.
-
Hallo
Hab mir Eure Beiträge durch den Kopf gehen lassen, bin aber der Meinung, dass meine Vorgehensweise mit "wilder Pointerschiesserei" oder sowas nichts zu tun hat. Eigentlich ist das nur ein bisschen logische "Streckenberechnung".
Hab das hier noch mal anders dargestellt. Bitte guckt doch noch mal, ob Ihr da einen Gedankenfehler findet.
Ich gehe davon aus, dass eine struct auf jeden Fall einen zusammenhängenden Speicherbereich belegt, ob nun mit Paddings in der Mitte oder ohne.Die Belegung der Bytes mit 1 soll ab "Punkt B" bis zum Ende erfolgen.
// #pragma pack(push, 1) struct STRUCT // alig. 1 alig. 4 { int i1; // Punkt A 4 Bytes 4 char c1[5]; // 5 8 ( 3 Padding ) int i2; // Punkt B 4 4 char c2[2]; // 2 4 ( 2 Padding ) }; // Punkt C --------------------- // 15 20 //#pragma pack(pop) TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { int AnzBytes_A_C = 0 ; // Anzahl der Bytes zwischen Punkt A und C int AnzBytes_A_B = 0 ; int AnzBytes_B_C = 0 ; STRUCT test = {0} ; AnzBytes_A_C = sizeof( STRUCT ); AnzBytes_A_B = reinterpret_cast <char*> (&(test.i2)) - reinterpret_cast <char*> (&test) ; AnzBytes_B_C = AnzBytes_A_C - AnzBytes_A_B ; // Bereich B - C auf 1 setzen memset( &(test.i2) , 1 , AnzBytes_B_C ) ;Das sollte funktionieren, auch wenn Alignment geändert wird oder struct-elemente geändert, hinzugefügt oder entfernt werden.
Das einzige, was als Fixpunkt bleiben muss ist, dass i2 der Punkt ist, ab dem 1 gesetzt werden soll. D.h. nur in dem Fall, dass i2 seinen Namen ändert oder ein anderes Element den Fixpunkt bilden soll, muss ich diese "Reset"-Funktion ändern.
Wenn ich die Elemente alle einzeln setzte, müsste ich sie jedesmal ändern, wenn sich Elemente im Bereich B - C irgendwie ändern.
-
Auch wenn du es nicht glaubst, das bleibt "wilde Pointerschießerei". Und wirklich funktionieren wird sowas nur mit POD-Typen.
Ansonsten: Wenn du sowas unbedingt machen willst, solltest du dir mal offsetof() ansehen:
Anzahl_A_C = sizeof(STRUCT); Anzahl_A_B = offsetof(STRUCT,i2);
-
offsetof werde ich ausprobieren. Klingt gut.
Was sind POD_Typen?
CStoll schrieb:
Auch wenn du es nicht glaubst, das bleibt "wilde Pointerschießerei".
Einfach eine frühere Aussage zu wiederholen finde ich nicht sehr konstruktiv. Mach ich einen Gedankenfehler in meinem Code?
-
GV schrieb:
Was sind POD_Typen?
POD (= "Plain Old Data") sind C-artige Datentypen (das bedeutet: Keine Konstruktoren, Destruktoren, überladenen Operatoren, Vererbungen, privaten Member etc.)
CStoll schrieb:
Auch wenn du es nicht glaubst, das bleibt "wilde Pointerschießerei".
Einfach eine frühere Aussage zu wiederholen finde ich nicht sehr konstruktiv. Mach ich einen Gedankenfehler in meinem Code?
OK, dann anders:
Objekte in C++ haben normalerweise eine innere Struktur (und gelegentlich auch Invarianten, die von ihren Methoden als gegeben vorausgesetzt werden). Solange du nur über die Klassenmethoden auf die Interna des Objekts zugreifst, hat es die volle Kontrolle über das, was passiert (z.B. die Einhaltung der Klasseninvarianten).
Von deinen Spielereien mit memset() und Kollegen bekommt das Objekt nichts mit - damit passen die Erwartungen der Methoden nicht mehr zur Realität. (und das kann schneller ins Auge gehen als dir lieb ist)