Adressen in struct
-
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.
-
GV schrieb:
...Das Problem ist doch gelöst, von MKF ganz am Anfang des Threads....
Dann ist ja alles gut !
Alles Gute noch,Simon2.
-
Hallo,
ich hab gerade mit einem Kollegen über diese Sache und auch über die Antworten hier gesprochen. Dabei haben wir uns ausgemalt, was passieren könnte, wenn wir nur "konformen" Code schreiben würden.
Wir müssten dann unserem Kunden sagen:
"Tut uns leid, wir können die Vorgaben bezüglich möglichst geringem Speicherbedarf und möglichst grosser Geschwindigkeit in diesem Bereich nicht erfüllen. Es gäbe da zwar eine Möglichkeit (sprich: EINE struct, die mit MEMSET zurückgesetzt wird). Die funktioniert auch in Ihrem speziellen Falle, für dessen Entwicklung Sie viel Geld gelassen haben, aber das könnte Probleme geben, wenn wir dann später (Gott bewahre, nätürlich NICHT für Ihre Konkurenz) die gleiche Lösung wieder verwenden wollen.
Es könnte sein, dass dann ein anderer Kompiler benutzt wird oder eine andere Hardware vorliegt, und dass dann diese "wilde Pointerschiesserei" nicht mehr zu dem gewünschten Ergebnis führt. Sorry."Wir haben ernsthaft überlegt, ob wir das mal machen. Nur so, um die Gesichter des Kunden und unseres Chefs zu sehen.

Aber vielleicht lassen wir das doch lieber.
Was ich damit sagen will, ist folgendes:
Bei Hello-World-Programmen und der obligatorischen CD-Verwaltung mag ja die Priorität bei Allgemeingültigkeit und Kompatibilität des Programms liegen, aber bei richtigen Projekten liegt sie beim Ergebnis, d.h. sie Erfüllung der Vorgaben für dieses Projekt hat höchste Priorität.
Ich sage nicht, dass man alles andere komplett vergessen darf, aber die Verwendung von Lösungen, die dabei nur in diesem speziellen Fall funktionieren, ist meiner Meinung nach absolut zulässig und ok.Man sollte sich dann allerdings besondere Mühe mit der Dokumentation geben. - Nicht nur, damit das Projekt beim Konkurenten des Kunden nicht gefährdet ist.

-
GV schrieb:
...
Wir müssten dann unserem Kunden sagen:
"Tut uns leid, wir können die Vorgaben bezüglich möglichst geringem Speicherbedarf und möglichst grosser Geschwindigkeit in diesem Bereich nicht erfüllen. ...Sorry, aber das halte ich für groben Unfug !
(mal abgesehen davon, dass jede Performance immer ihre "möglichst beste" ist)3 Kurzstatements:
- Du glaubst gar nicht, was der Compiler und eine sinnvolle (sprich: Am konkreten Verhalten orientierte) Optimierungsphase rausholen. Das bekommt man mit "Gefühlter Designperformance" gar nicht hin. Gerade bzgl. Speicherkopiererei sind Maschinen sehr gut.
- Der Kunde braucht nicht "möglichst grosse Geschwindigkeit", sondern ein Produkt, das seine Anforderungen entspricht; wenn er die nicht exakt formulieren kann, solltet Ihr ihm dabei helfen.
- Wenn der Kunde dafür für jede Erweiterung 3mal soviel zahlen darf wie bei Eurer Konkurrenz und doppelt soviele Fehler in seinem Produkt hat, bekommt Ihr den Job auch nicht. Ein echtes Programm besteht nicht nur aus Stoppuhrläufen, sondern aus einem "Gesamtzyklus" (Entw-Kosten, Fehleranfälligkeit, ....).GV schrieb:
...
Bei Hello-World-Programmen und der obligatorischen CD-Verwaltung mag ja die Priorität bei Allgemeingültigkeit und Kompatibilität des Programms liegen, aber bei richtigen Projekten liegt sie beim Ergebnis, d.h. sie Erfüllung der Vorgaben für dieses Projekt hat höchste Priorität....Gerade bei großen Projekten (beziehe mich auf unsere letzten beiden 2MioEuro-Projekte) zahlt sich klar strukturierte Programmierung viel eher aus als bei Spielprogrammen.
Im ersten hatten wir so einen "Frickelfreund" - es war seeeeehr teuer, im Nachhinein sein vermeintlich "optimales Design" gegen ein vernünftiges auszutauschen.Aber lass Dich von den Erfahrungen Anderer nicht davon abhalten, selbst welche zu machen !
Gruß,
Simon2.