2016-07-19 12 views
1

Ist der folgende korrekte Code?Verwenden eines zugewiesenen Speicherplatzes zum Speichern mehrerer Arrays

Angenommen, es ist bekannt, dass alle Objektzeigertypen gleiche Größe und Ausrichtung haben, mit einer Größe von nicht mehr als 8.

// allocate some space to A, and set *A and **A to different regions of that space 
char*** A = malloc(92); 
*A = (char**)((char*)A + 2*sizeof(char**)); 
**A = (char*)*A + 4*sizeof(char*); 

// initialize the second char** object 
A[1] = *A + 2; 

// write four strings further out in the space 
strcpy(A[0][0],"string-0-0"); 
A[0][1] = A[0][0] + strlen(A[0][0]) + 1; 
strcpy(A[0][1],"string-0-1"); 
A[1][0] = A[0][1] + strlen(A[0][1]) + 1; 
strcpy(A[1][0],"string-1-0"); 
A[1][1] = A[1][0] + strlen(A[1][0]) + 1; 
strcpy(A[1][1],"string-1-1"); 

mir Sachen wie diese nützlich in Situationen, wo es nicht einfach sein kann wie ausplanen das Objekt. Angenommen, A [1] [1] kann der Adresse eines Zeichenfolgenliterals neu zugewiesen werden oder nicht. In beiden Fällen geben Sie einfach A frei. Außerdem wird die Anzahl der Aufrufe von malloc minimiert.

Mein Anliegen, dass dies möglicherweise nicht korrekt Code ist basiert auf dem folgenden. Mit einem Entwurf des Standards, die ich habe:

7.22.3 Speichermanagementfunktionen

  1. ... Der Zeiger zurückgegeben, wenn die Zuweisung geeigneter Weise ausgerichtet ist, gelingt es so, dass es eine zugeordnet werden kann Zeiger auf jede Art des Gegenstandes mit einer Grundvoraussetzung Ausrichtung und verwendet dann ein solches Objekt zuzugreifen oder eine Anordnung von solchen Objekten in dem Raum zugeordnet (bis der Raum explizit freigegeben wird), ...

So I bin garantiert, dass Sie den Platz als Array eines einzelnen Typs verwenden können. Ich kann keine Garantie finden, dass ich es als ein Array von zwei verschiedenen Typen (char * und char **) verwenden kann. Beachten Sie, dass die Verwendung einiger Leerzeichen als char-Arrays einzigartig ist, da auf jedes Objekt als zeichenartiges Array zugegriffen werden kann.

Die Regeln für den effektiven Typ stimmen mit diesem Ansatz überein, da niemals ein einzelnes Byte als Teil zweier verschiedener Typen verwendet wird.

Während die oben gibt es scheint keine explizite Verletzung der Norm zu sein, wird der Standard nicht explizit das Verhalten entweder erlauben, und wir haben Absatz 2 Kapitel 4 (Hervorhebung hinzugefügt):

Wenn ein Wird eine Anforderung außerhalb einer Integritätsbedingung oder Laufzeitbedingung verletzt, ist das Verhalten nicht definiert. Undefiniertes Verhalten wird in dieser Internationalen Norm durch die Wörter "undefined behaviour" oder durch Weglassen einer expliziten Definition des Verhaltens angegeben. Es gibt keinen Unterschied in der Betonung zwischen diesen drei; Sie alle beschreiben "Verhalten, das nicht definiert ist".

Das ist ein wenig vage, wenn das Speichermodell selbst so vage ist. Die Verwendung von Speicherplatz, der von malloc() zurückgegeben wird, um ein Array eines beliebigen Typs zu speichern, benötigt offensichtlich eine explizite Erlaubnis, die ich oben zitiert habe. Man könnte also argumentieren, dass die Verwendung dieses Raumes für disjunkte Arrays unterschiedlicher Typen ebenfalls eine explizite Berücksichtigung erfordert und ohne dieses als undefiniertes Verhalten gemäß Kapitel 4 zurückbleibt.

Also, um genau zu sein, wenn das Codebeispiel korrekt ist Was stimmt nicht mit dem Argument, dass es nicht explizit definiert und daher undefiniert ist, je nachdem, welcher Teil von Kapitel 4 aus dem oben zitierten Standard stammt?

+0

'char *** A = malloc (90);' ist falsch mit einer Zeigergröße von 8 – 4386427

+0

@ 4386427, danke, ich war ein wenig voreilig auf meine Arithmetik. – Kyle

+0

@ 4386427, bekomme ich 6 Zeiger der Größe 8 haben die Größe 6 * 8 = 48 und 4 Stränge der Länge 10 machen 44, 48 + 44 = 92. – Kyle

Antwort

1

Vorausgesetzt, dass der Versatz eines Objekts vom Anfang der zugewiesenen Region ein Vielfaches der Ausrichtung ist, und vorausgesetzt, dass kein Teil des Speichers innerhalb der Lebensdauer der Zuweisung als mehr als ein Typ verwendet wird kein Problem.

Eine unangenehme Sache ist, dass es einige Algorithmen gibt (zB für Hashtabellen), die sehr gut mit einer Tabelle funktionieren, die anfänglich mit beliebigen Werten gefüllt ist (bei einem Wert, der richtig oder falsch sein kann) in der Lage zu bestimmen, ob der Wert viel schneller ist - O (1) vs O (N) - als es den richtigen Wert ohne eine anfängliche Schätzung finden könnte), Speicher als einen Typ schreiben und lesen als einen anderen Nicht-Zeichen-Typ ergibt Undefined Behavior auch dann, wenn der Zieltyp keine Trap-Repräsentationen hätte und selbst wenn der Code für irgendeinen Wert korrekt funktionieren würde, den der Read in Fällen hätte ergeben können, in denen die Daten noch nicht als der neue Typ geschrieben wurden.

Gegeben:

float *fp; 
uint32_t *ip; 

*fp = 1.0f; 
*ip = 23; 

Verhalten definiert werden würde, wenn fp und ip denselben Speicher identifizieren und die Lagerung halten 23 danach erforderlich wäre. Auf der anderen Seite gegeben:

float *fp; 
uint32_t *ip; 

*fp = 1.0f; 
uint32_t temp = *ip; 
*ip = 23; 

ein Compiler Vorteil des undefinierten Verhaltens nehmen könnte die Operationen neu zu ordnen, um die Schreib zu * fp nach dem Schreiben auf * ip aufgetreten. Wenn die Daten für so etwas wie eine Hash-Tabelle wiederverwendet werden, ist es unwahrscheinlich, dass ein Compiler eine solche Optimierung sinnvoll anwenden könnte, aber ein "intelligenter" Compiler könnte die Schreibvorgänge nutzloser Daten nach dem Schreiben nützlicher Daten neu ordnen. Solche "Optimierungen" sind nicht sehr wahrscheinlich, um Dinge zu brechen, aber es gibt keinen Standard-definierbaren Weg, sie zu verhindern, außer durch Freigeben und Neuzuweisen des Speichers (wenn man ein gehostetes System verwendet und den Leistungseinbruch und mögliche Fragmentierung tolerieren kann). oder andernfalls Löschen des Speichers vor der Wiederverwendung (wodurch O (1) Operationen in O (N) umgewandelt werden können).