Struct mit variabler Anzahl an nested structs
-
Man sieht derartige Konstrukte gelegentlich, vor allem im Zusammenhang mit Datenpaketen variabler Länge. Etwa beschreibt POSIX als Datenargument für msgsnd ein Struct der Form
struct mymsg { long mtype; /* Message type. */ char mtext[1]; /* Message text. */ }
Zu benutzen etwa in der Form:
struct mymsg *buf = malloc(sizeof(long) + msgsz); buf->mtype = msgtype; memcpy(buf->mtext, msgsource, msgsz); msgsnd(queue_id, buf, msgsz, 0); free(buf);
Gedacht ist das also so, dass mtext sich hinter das Ende des structs erstreckt.
ABER: Im C-Standard ist dieses Verhalten nicht definiert, die Portabilität ist also jenseits von Plattformen, die es explizit erlauben, zweifelhaft, und man kann derartige Konstrukte auch nicht wie jeden anderen Datentypen benutzen. Beispielsweise wäre es völliger Wahnsinn,
struct mymsg message_array[100]; /* KAWUMM! */
zu schreiben, und natürlich ist auch nur ein solcher Datenbereich pro Struct möglich (es hat ja nur ein Ende).
Also: So etwas ist möglich, wird auch gelegentlich gemacht, ist aber mit Vorsicht zu genießen. Du bist gewarnt worden.
-
Danke für die Antwort. Leider habe ich keine Möglichkeit, an den typedefs zu schrauben.
Der Name des Arrays "firstElement" (vom Typ "myNestedStruct") ist ja letzten Endes nur ein Pointer auf das Erste Element. Meine Idee war eben, Speicher für ein Array aus "myNestedStruct"'s zu allokieren und dann den Pointer "firstElement" da hin zu verbiegen.
Aber das geht wahrscheinlich grundsätzlich nicht, oder?
-
Äh...das ist kein Zeiger, sondern ein Array der Länge 1.
-
Da hat sich die Antwort wohl überschnitten
Vielen Dank auf jeden Fall nochmal, hatte schon befürchtet dass das nicht so einfach geht. Das ganze Konstrukt ist meiner Meinung nach Mist. Ich sehe nur Vorteile, wenn ich die Variante von Wutz vorliegen hätte.
Gibt es denn davon, wie ich es dargestellt habe auch einen Vorteil bzw. Vorteile, die ich jetzt eben übersehe?
-
@seldon, korrigier mich wenn ich falsch liege aber in C ist doch der Name eines Arrays nichts anderes als ein Pointer auf das erste (bzw. das Nullte wie manche gerne sagen) Element. Oder?
-
Naja, du musst sich seltener an den Heap wenden. Mit C99, unter BSD (alloca) oder wenn die benötigte Länge zur Compilezeit feststeht, kannst du es sogar auf dem Stack machen; das spart Rechenzeit und kann in extrem nebenläufigen Szenarien heap contention vermeiden. Wenn du die Daten zwischen verschiedenen Prozessen hin- und herreichen musst, kann es einfacher zu benutzen sein.
Es gibt schon Gründe, ein solches Design vorzuziehen, aber man muss damit halt ein bisschen vorsichtig sein.
Und was das Array/Zeiger-Zeug angeht, da muss ich dich korrigieren. Ein Zeiger ist eine Speicheradresse, ein Array ist ein Speicherbereich. Einfaches Beispiel zur Verdeutlichung:
#include <stdio.h> int main(void) { int *p; int arr[30]; /* Hier eigentlich besser %zu (size_t), aber nicht alle gängigen Compiler * beherrschen C99 bereits. */ printf("%lu != %lu\n", sizeof(p), sizeof(arr)); return 0; } /* Ausgabe bei mir: * * 8 != 120 */
Die Ausgabe ist natürlich plattformabhängig, aber es sollte den Unterschied verdeutlichen. Ein Array kann in einen Zeiger umgewandelt werden; das ist dann ein Zeiger auf sein erstes Element, aber das Array selbst ist kein Zeiger.
-
Gast9483 schrieb:
Danke für die Antwort. Leider habe ich keine Möglichkeit, an den typedefs zu schrauben.
Das hättest du auch gleich sagen können!
*(myNestedStruct**)mySurroundingStruct->firstElement = myInnerStruct;
sollte erstmal funktionieren, arraymäßig zugreifen (was du wohl erreichen wolltest) geht dann mit
myInnerStruct[0].myLen=123; myInnerStruct[1].myLen=456; myInnerStruct[2].myLen=789; oder mit dem zweifellos ziemlich gekünstelten Ausdruck: (*(myNestedStruct**)mySurroundingStruct->firstElement)[0].myLen=123; (*(myNestedStruct**)mySurroundingStruct->firstElement)[1].myLen=456; (*(myNestedStruct**)mySurroundingStruct->firstElement)[2].myLen=789;
Ein Zeiger ist eine Referenz auf den Beginn eines Speicherbereiches der Größe sizeof(size_t), dessen Inhalt als Referenz auf den Beginn eines ANDEREN Speicherbereiches interpretiert wird.
Ein Array ist eine Referenz auf den Beginn eines Speicherbereiches, dessen Inhalt als Datenfolge des Arraytyps DIREKT interpretiert wird.
-
Hallo,
versuch's mal damit (hab ich jetzt nicht übersetzt, daher tippfehler selber korrigieren):
typedef struct { blabla } innerStruct; typedef struct { size_t numBuffer; innerStruct data[1] } outerStruct; outerStruct * data = (outerStruct *)malloc( sizeof( outerStruct ) + (n-1)*sizeof( innerStruct ) ); data->numBuffer = n; for( size_t i=0; i<n; i++ ) data->data[i].blabla = blabla;
mfg Martin
-
seldon schrieb:
ABER: Im C-Standard ist dieses Verhalten nicht definiert, die Portabilität ist also jenseits von Plattformen, die es explizit erlauben, zweifelhaft,
Hallo,
das ist meiner Meinung nach falsch. ANSI-C hat sehr wohl definiert, wie das Speicherlayout von structs auszusehen hat. (Zumindest gilt das für die erste ANSI-C-Version). Die einzelnen Elemente müssen daher genau in der Reihenfolge im Speicher abgelegt sein, wie sie in der Deklaration der Struktur enthalten sind.
Ich weiß das ziemlich genau, weil ich zur Zeit des ersten ANSI-C Standards bei einem Compilerhersteller gearbeitet habe und ich mich damals sehr intensiv mit diesem Standard ausseinandersetzen musste.
mfg Martin
-
Echt? Das wäre natürlich ziemlich praktisch; ich habe nur nichts derartiges im Standard gesehen, und generell hält sich der Standard ziemlich agnostisch, was den Aufbau der Hardware angeht. Das muss aber natürlich nicht heißen, dass ich mich da nicht geirrt habe.
Weißt du vielleicht noch die Stelle im Standard, an der das definiert wird? Das interessiert mich jetzt.
-
seldon schrieb:
Weißt du vielleicht noch die Stelle im Standard, an der das definiert wird? Das interessiert mich jetzt.
The members of a struct are stored in the order in which they are declared.
ANSI C A Lexical Guide, Prentice Hall, 1988. S. 466
mfg Martin
-
Ich hatte eigentlich auf eine Standardreferenz gehofft statt auf ein Buch, das noch vor Abschluss der Standardisierung veröffentlicht wurde...
Wie dem auch sei, ich hab's inzwischen selbst gefunden, an etwas unintuitiver Stelle (6.5.8 Relational operators):
ISO/IEC 9899:1999 6.5.8 (5) schrieb:
When two pointers are compared, the result depends on the relative locations in the address space of the objects pointed to. If two pointers to object or incomplete types both point to the same object, or both point one past the last element of the same array object, they compare equal. If the objects pointed to are members of the same aggregate object, pointers to structure members declared later compare greater than pointers to members declared earlier in the structure, and pointers to array elements with larger subscript values compare greater than pointers to elements of the same array with lower subscript values. All pointers to members of the same union object compare equal. If the expression P points to an element of an array object and the expression Q points to the last element of the same array object, the pointer expression Q+1 compares greater than P. In all other cases, the behavior is undefined.
(Hervorhebung von mir). So wie ich das lese, schreibt der Standard also vor, dass sich das ganze so zu verhalten habe, als lägen die Member in der angegebenen hintereinander im Speicher, und die sinnvollste Methode, das auf derzeit üblichen Computern zu implementieren, wird sein, sie in der angegebenen Reihenfolge hintereinander in den Speicher zu legen.
Wieder was gelernt.
-
seldon schrieb:
Ich hatte eigentlich auf eine Standardreferenz gehofft statt auf ein Buch, das noch vor Abschluss der Standardisierung veröffentlicht wurde...
Es tut mir leid, als wir damals die ANSI-C-IDE gemacht hatten, hatten wir nichts anderes. Irgendwanneinmal bekam ich auch eine Kopie des fertigen Standards. Die habe ich aber leider nicht mehr.
mfg Martin