Adressen in struct
-
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)
-
Ok.
Aber ich hatte das auch wirklich nur für eine althergebrachte struct gedacht, die aus PODs (
wieder was gelernt ) besteht. Wenn eigene Methoden usw. zur Verfügung ständen, würde ich solche Sachen denen "überlassen".
-
Hi
POD = "plain old data" = "reine Datenansammlung"; also nix, was eine Funktion, virtuelle Basisklasse, .... hat.
Dein Design ist ganz einfach "unsauber". Was Du willst, sind 2 Dinge in einem:
- eine "fachliche Schnittstelle", in der i1, i2, ... eine fachliche Bedeutung haben und als solche angesprochen werden können und
- eine "technische Schnittstelle", in der die Byterepräsentation als Message festgezurrt ist.Sowas sollte man sauber trennen; z.B. eine Klasse mit einer serialize()-Funktion; Letztere wirft einen "Bytestring" raus, der Deine Message entspricht.
Auch ist mir nicht ganz klar, was Du mit dem "Ausnullen verschiedener Speicherberiche" eigentlich erreichen willst ... sollen da nicht bestimmte Variablen auf 0 gesetzt werden ? Das würde ich dann auch wirklich so machen - gerade, wenn jede nutzende Klasse immer mal andere Bereiche nimmt.Zudem scheint mir Deine "Message" auf dem besten Wege, zum "eierlegenden Wollmilchmoloch" zu werden, um den herum sich alle anderen Klassen zurechtbiegen müssen ("Welchen Bereich der Message muss ich ausnullen ? Welchen auswerten ? welchen ignorieren ? welchen darf ich ändern ? ...) - da würde ich nochmal das Gesamtdesign der Anwendung checken.
Nochmal zu Deiner Ursprungsfrage:
GV schrieb:
...
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?
Dann lass Dir doch mal die Werte der Adressen ausgeben ! Dann kannst Du selbst nachrechnen, was der Compiler da rausschmeisst.
BTW: Schonmal auf die Idee gekommen, dass der Standard evtl. die Reihenfolge der Member im Speicher gar nicht vorgibt ? Ein besonders gewitzter Compiler könnte also erstmal alle int's hintereinanderlegen und danach alle char[], ....

Gruß,
Simon2.
-
Simon2 schrieb:
# 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.Bei Initialisierung durch ein Stringliteral ist der Standard ein bisschen knapp. Grundsätzlich ist aber nicht einzusehen, warum im Ergebnis ein Unterschied zwischen
char foo[10] = "abcd"; // und char bar[10] = { 'a', 'b', 'c', 'd', '\0' };bestehen sollte. Und im Falle von bar ist ganz eindeutig, dass alle Elemente des Arrays initialisiert werden, solche die nicht in der Initialisierungsliste stehen, werden dabei value-initialisiert (hier: auf Null gesetzt). Das ist sowieso eine gut zu merkende Regel: wenn ein Aggregat (außer union) überhaupt initialisiert wird, dann immer gleich alle Elemente davon.
Simon2 schrieb:
Schonmal auf die Idee gekommen, dass der Standard evtl. die Reihenfolge der Member im Speicher gar nicht vorgibt ? Ein besonders gewitzter Compiler könnte also erstmal alle int's hintereinanderlegen und danach alle char[], ....
Das darf der Compiler nur, wenn zwischen den Membern ein Zugriffsdeklarator (d.h.public,protected,private) auftaucht, andernfalls liegen die einzelnen Memberobjekte (nicht notwendig unmittelbar) hintereinander im Speicher.
-
Dann lass Dir doch mal die Werte der Adressen ausgeben ! Dann kannst Du selbst nachrechnen, was der Compiler da rausschmeisst.
Hab ich doch:
// sizeof(SRTUCT) = 16
// &(test.i2) = EF4
// &test = &(test.i1) = EE8Wer lesen kann ist klar im Vorteil.

Auch ist mir nicht ganz klar, was Du mit dem "Ausnullen verschiedener Speicherberiche" eigentlich erreichen willst ... sollen da nicht bestimmte Variablen auf 0 gesetzt werden ? Das würde ich dann auch wirklich so machen - gerade, wenn jede nutzende Klasse immer mal andere Bereiche nimmt.
Zudem scheint mir Deine "Message" auf dem besten Wege, zum "eierlegenden Wollmilchmoloch" zu werden, um den herum sich alle anderen Klassen zurechtbiegen müssen ("Welchen Bereich der Message muss ich ausnullen ? Welchen auswerten ? welchen ignorieren ? welchen darf ich ändern ? ...) - da würde ich nochmal das Gesamtdesign der Anwendung checken.
Es gibt nur einen Sender und nur einen Empfänger, es braucht sich also niemand zu verbiegen. Wenn ich für jede Msg eine eigene struct machen sollte, würde ich Dutzende brauchen, die sich teilweise überschneiden.
So habe ich einen Platz (die struct) an dem ich alle Msgs hinterlegen kann. Dann sage ich dem Empfänger "Lies die Msg". Anhand von bestimmten Werten weiss er, dass es sich um eine Msg "der Sorte A" handelt und er holt sich dann die Daten da raus die er braucht. Bei deer nächsten Msg "der Sorte B" liest er dann teilweise die gleichen Daten (mit anderen Werten) und auch andere, usw.
Natürlich könnte ich dran gehen und vor jedem "Verschicken" alle Daten neu schreiben (entweder 0 oder einen Wert). Ich finde es aber einfacher, ersteinmal "reinen Tisch zu machen" ( alles, was sich von einem Verschicken zum anderen ändert, auf 0 setzen) und dann nur die Daten zu schreiben, die ungleich 0 sind.
Die Daten die über mehrere "Verschickungsvorgänge" unverändert bleiben, setze ich nicht jedesmal auf 0 und brauche sie auch nicht jedesmal neu zu schreiben.
-
camper schrieb:
...
Grundsätzlich ist aber nicht einzusehen, warum im Ergebnis ein Unterschied zwischenchar foo[10] = "abcd"; // und char bar[10] = { 'a', 'b', 'c', 'd', '\0' };bestehen sollte. Und im Falle von bar ist ganz eindeutig, dass alle Elemente des Arrays initialisiert werden, solche die nicht in der Initialisierungsliste stehen, werden dabei value-initialisiert (hier: auf Null gesetzt). Das ist sowieso eine gut zu merkende Regel: wenn ein Aggregat (außer union) überhaupt initialisiert wird, dann immer gleich alle Elemente davon.
Aha !
Allerdings werden mitchar foo[10] = "1";NICHT alle mit '1' initialisiert, sondern eben nur der erste. Falls "0-Initialisierung" genau das ist, was man braucht, ist ja praktisch - ich hatte nur den Eindruck, dass es hier mehr zufällig passte.
Übrigens: Mein gcc 3.4.4 (sonst recht standardkonform) initialisiert bei
char foo[10] = "";nur das erste Element von foo. ...
Gruß,
Simon2.
-
GV schrieb:
...
Hab ich doch:
...hast Recht, hatte ich nicht gesehen.
GV schrieb:
...Ich finde es aber einfacher,...
Ich nunmal nicht ! Ich habe so ein Design auch mal in ein Programm gebaut (als ich noch jung und unerfahren war
) und habe damals sehr viel Tränen vergossen wegen- des immer unübersichtlicher werdenden Codes,
- einer Datenstruktur mit mehreren "Zuständen" (read, write, ignore, init, ...) und
- exponentiell zunehmender Fingerbrecherei bei verändernder Messagestruktur oder neuen Klassen.
Später haben wir es gemeinsam für teuer Geld vernünftig gemacht und nun fluppt es.
Vielleicht machst Du andere Erfahrungen - kann gut sein; dann Glückwunsch von mir.Aber die Tatsache, dass Dir bislang niemand Dein ursprüngliches Problem erklären oder lösen konnte (und dass es überhaupt schon bei einem für Dich doch so simplen Fall aufgetaucht ist), überzeugt mich nicht davon, dass Deine Herangehensweise wirklich besser ist als meine (jetzige).
Gruß,
Simon2.
-
Simon2 schrieb:
Allerdings werden mit
char foo[10] = "1";NICHT alle mit '1' initialisiert, sondern eben nur der erste.
Wieso sollten sie auch? Default-Initialisierung bei int ist 0. Das wäre äquivalent mit:
char foo[10] = { '1', '\0' };ERGÄNZUNG:
char foo1[10] = { '1' }; char foo2[10] = "1";G++ 4.1.0 initialisiert beide mit { '1', 0, 0, 0, 0, 0, 0, 0, 0, 0 }
-
Aber die Tatsache, dass Dir bislang niemand Dein ursprüngliches Problem erklären oder lösen konnte (und dass es überhaupt schon bei einem für Dich doch so simplen Fall aufgetaucht ist),

Das Problem ist doch gelöst, von MKF ganz am Anfang des Threads. Ich hatte nicht berücksichtigt, dass die Differenz zwischen 2 int* als Einheit sizeof int hat unt nicht Bytes.
Und, dass der Fall so simple ist, liegt daran, dass das Programm nur eine Miniversion ist, in der ich die "Reset"funktion entwickeln und testen wollte.
Ich fang doch keine Experimente in einem richtigen Projekt an.

-
LordJaxom schrieb:
Simon2 schrieb:
Allerdings werden mit
char foo[10] = "1";NICHT alle mit '1' initialisiert, sondern eben nur der erste.
Wieso sollten sie auch?...
Weil der Programmierer das erwarten könnte !

Du hattest meinen Einwand mißverstanden: Ich will nix gegen einen Compiler oder den Standard sagen, sondern lediglich darauf hinweisen, dass die obige Initialisierung wahrscheinlich von vielen mißverstanden wird und
char foo2[10] = "";als "Sonderfall davon" ebenso.
Sagen wir mal so: "foo2 wird nicht deswegen komplett mit '\0' initialisiert, weil man das in seinen Initialisierungs reingeschrieben hätte, sondern weil man überhaupt a) initialisiert, b) zu wenig Werte angegeben hat und c) 0 zufällig dem gewünschten Initialisierungswert entspricht."Auf mehr wollte ich nicht hinweisen.
Ich hatte übrigens mit meinem "gcc-Hinweis" das
="";-Beispiel gemeint und Unrecht (war ein Programmierfehler von mir).Gruß,
Simon2.