Der eindimensionale Zugriff auf ein mehrdimensionales array: ist es gut definiert Verhalten?
Ich vorstellen, wir sind uns alle einig, dass es als idiomatischer C, um den Zugriff auf eine multidimensionale Arrays dereferenzieren eines (evtl offset) Zeiger auf erstes element in a one-dimensional Mode, z.B.:
void clearBottomRightElement(int *array, int M, int N)
{
array[M*N-1] = 0; //Pretend the array is one-dimensional
}
int mtx[5][3];
...
clearBottomRightElement(&mtx[0][0], 5, 3);
Jedoch die Sprache-Anwalt mich davon zu überzeugen braucht, dass das ist eigentlich gut definiert C! Insbesondere:
- Gilt die standard-Garantie, dass der compiler nicht gesetzt Polsterung zwischen z.B.
mtx[0][2]
undmtx[1][0]
? - Normalerweise Indizierung über das Ende eines array (anders als ein-auch über das Ende) ist nicht definiert (C99, 6.5.6/8). Also Folgendes ist klar undefined:
struct { int row[3]; //The object in question is an int[3] int other[10]; } foo; int *p = &foo.row[7]; //ERROR: A crude attempt to get &foo.other[4];
Also durch die gleiche Regel würde man erwarten, die im folgenden nicht definiert:
int mtx[5][3]; int (*row)[3] = &mtx[0]; //The object in question is still an int[3] int *p = &(*row)[7]; //Why is this any better?
Warum also sollten diese definiert werden?
int mtx[5][3]; int *p = &(&mtx[0][0])[7];
Also, was Teil der C-standard erlaubt ausdrücklich das? (Angenommen c99 aus Gründen der Diskussion.)
BEARBEITEN
Beachten Sie, dass ich habe keinen Zweifel, dass dies funktioniert gut in allen Compilern. Was ich mich Abfrage ist, ob dies ausdrücklich durch den standard.
[][]
Zugang ist der gleiche wie *(array + x * index + y)
.Ich bin keine Sprache-Anwalt, damit ich nicht eine Antwort, jedoch ist dies genau, wie es funktioniert für raster-imaging. Im Grunde alles, was Sie haben, ist, bytes und wissen Sie, wie viele bytes in einer Zeile. Gehen Sie zur nächsten Zeile müssen Sie den Versatz der originalen Zeiger mit der Anzahl der Zeilen*Breite. Also im Fall von gut definierten Daten, ich würde sagen, das ist völlig in Ordnung, Codierung.
Oh, ich habe keinen Zweifel, dass es in Ordnung ist! Ich verwende dieses Prinzip jeden Tag, und so auch alle anderen. Ich bin rein bitten, von einer Sprache-Anwalt Pedanterie Sicht!
Gut, Juristen sind schrecklich Entwickler. Im Speicher, das array hat keine Polsterung und, daher, die Indizierung mehrdimensionaler arrays als Einzel-dimensional wird immer funktionieren. Ihre Zeiger-Inkrement ergibt sich aus der base-array-pointer, damit arr[10] werden müssen, arr + 10 * sizeof(arr), die in den Spezifikationen bin ich mir sicher. Dies bedeutet, arr[1][5] mit der zweiten dimension immer 5 lange ist:
arr + 1 * 5 * sizeof(arrType) + 5 * sizeof (arrType)
...Ich habe nicht die Zeit, es zu schreiben, aber C99-6.5.2.1 die Absätze 3 und 4 scheinen zu machen, dieses gut definiert
InformationsquelleAutor Oliver Charlesworth | 2011-06-09
Du musst angemeldet sein, um einen Kommentar abzugeben.
Das einzige Hindernis für die Art von Zugang, die Sie tun möchten, ist, dass Objekte vom Typ
int [5][3]
undint [15]
sind nicht erlaubt, um alias den anderen. Wenn also der compiler ist sich bewusst, dass ein Zeiger vom Typint *
Punkte in einer derint [3]
arrays der ehemaligen, es könnte verhängen von array-Grenzen, Einschränkungen, die verhindern würde, dass der Zugriff auf alles, was außerhalb diesesint [3]
array.Könnten Sie in der Lage zu umgehen dieses Problem, indem Sie alles in eine union, die enthält sowohl die
int [5][3]
array und dieint [15]
array, aber ich bin wirklich unklar, ob die union hacks benutzen die Menschen für Typ-Zweideutigkeiten sind eigentlich gut definiert. In diesem Fall vielleicht etwas weniger problematisch, da Sie sonst nicht geben-Zweideutigkeiten einzelne Zellen, nur das array logic, aber ich bin mir noch nicht sicher.Einem speziellen Fall, der sollte noch hingewiesen werden: wenn Ihre Art waren
unsigned char
(oder jedechar
- Typ), der Zugriff auf den multi-dimensionalen array als ein eindimensionales array wäre sehr gut definiert. Dies ist, weil die ein-dimensionales array vonunsigned char
überschneidet, es ist explizit definiert durch die Norm als die "Repräsentation" des Objekts, und ist von Natur aus erlaubt, alias.C99 erlaubt Typ Zweideutigkeiten durch die Gewerkschaften - dies ist ausdrücklich erwähnt in Fußnote 82 (S. 73), die Hinzugefügt wurde, mit TC3
Ich war in der Hoffnung, jemand würde Antworten mit "Nein, Nein! Sie sind falsch! Suchen Sie die standard-ausdrücklich erlaubt ist es hier...", aber anscheinend nicht. Also ich hab akzeptiert diese Antwort, als diese Sätze dem Thema (über-aliasing) am treffendsten.
Beachten Sie, dass Anhang J. 2 in der Norm explizit Listen dieser Art von OOB-multi-dimensionale array-Zugriff als ein Beispiel der UB.
Ich Stimme mit der Antwort, außer der Teil, der behauptet, dass Charakter-Typen, mit ihm Weg erhalten. Speziell, wenn ein Zeichen-pointer wird inkrementiert, um auf ein anderes Objekt, in diesem Fall ein weiteres element des äußeren array. Können Sie näher erläutern, oder geben Sie einen Standard-Zitat. 6.5 macht keine Ausnahmen für char. Danke.
InformationsquelleAutor R..
Alle arrays (einschließlich mehrdimensionale Bilder) sind Polsterung-frei. Auch wenn es nie explizit erwähnt, es kann abgeleitet werden aus
sizeof
Regeln.Nun, array-Abo ist eine spezielle Form der pointer-Arithmetik, und C99 Abschnitt 6.5.6, §8 besagt eindeutig, dass Verhalten ist nur definiert, wenn die pointer Operanden und das daraus resultierende Zeiger liegen in das gleiche array (oder ein element Vergangenheit), die macht bounds-checking-Implementierungen der C-Sprache möglich.
Dies bedeutet, dass Ihr Beispiel ist in der Tat ein Undefiniertes Verhalten. Jedoch, wie die meisten C-Implementierungen nicht überprüfen, Grenzen, es funktioniert wie erwartet - die meisten Compiler Behandlung von undefinierten Zeiger-Ausdrücke wie
identisch mit gut definierten Pendants wie
ist gut definiert, da jedes Objekt (einschließlich der gesamten zwei-dimensionales array) kann immer behandelt werden als ein-dimensinal array vom Typ
char
.Auf weitere meditation auf die Formulierung von Abschnitt 6.5.6, splitting out-of-bounds Zugriff auf vermeintlich gut definierte Teilausdrücke wie
Argumentation, dass
mtx[0] + 3
ist ein Zeiger auf ein element über das Ende desmtx[0]
(die erste Zugabe gut definiert) sowie einen Zeiger auf das erste element dermtx[1]
(das zweite neben gut definierten) ist falsch:Obwohl
mtx[0] + 3
undmtx[1] + 0
sind garantiert zu vergleichen gleich (siehe Abschnitt 6.5.9, §6), Sie sind semantisch anders. Zum Beispiel, die ehemalige kann nicht aufgelöst werden, und so hat nicht zeigen Sie auf ein element dermtx[1]
.(mtx[0] + 3) + 2)
ist gültig, denn alle out-of-bounds-Zeiger Ergänzungen könnte sein rekursiv ausgedrückt als(((p+1)+1)+1)
etc. Und wenn es gut definiert, um auszudrücken, Ihnen auf diesem Weg, was wäre dann der Punkt 6.5.6/8?C-Arithmetik ist nicht assoziativ -
(a+b)+c
ist nicht unbedingt identisch mita+(b+c)
; die Krux an der Sache ist, dass im Falle von multi-dimensionalen arrays ein Zeiger kann 'gehören' zwei arrays gleichzeitig, und der Zeiger-Arithmetik nicht verfolgen die Ursprungs-array, so müssen Sie nur überprüfen Sie bei jedem Teilausdruck; soweit ich sagen kann, es ist tatsächlich möglich, Durchlaufen Sie ein multi-dimensionales array von single-step-SchrittenIch Stimme mit Ihren Standpunkt über die Assoziativität. Ich denke, der einzige Punkt übrig bleibt, ist, ob es gültig ist alias ein Objekt mit einem Zeiger auf die ein-past-the-end-element für das Vorherige Objekt. Zum Beispiel, in meinem struct Beispiel, ist das Verhalten gut definiert für eine Umsetzung, die garantiert keine Polsterung zwischen
row
undother
?nach re-Lektüre, Abschnitt 6.5.6, und einige weitere meditation, habe ich meine Meinung geändert 😉 Zeiger hinter das Letzte element eines array sind 'spezielle' und kann nicht verwendet werden, so, wie ich es ursprünglich beschrieben
Ja, der Zeiger ist darüber hinaus, was potentiell problematisch ist. Beachten Sie, dass es nicht ein problem, wenn der base type ist ein
char
geben, da dann jeder pointer ist auch ein Zeiger in die Darstellung - array, das ein array vom Typunsigned char [sizeof whole_multi_dim_array]
, und damit der Arithmetik gültig ist.InformationsquelleAutor Christoph
Es ist sicher, dass es keine Polsterung zwischen den Elementen eines Arrays.
Gibt es Bestimmung für das tun-Adresse Berechnung in der kleineren Größe, als der Adressraum. Dies könnte zum Beispiel verwendet werden in die große mode des 8086, so dass das segment Teil würde nicht immer aktualisiert werden, wenn der compiler Sie wusste, dass Sie konnte nicht über eine segment Grenze. (Es ist zu lange her für mich, Sie daran zu erinnern, wenn der Compiler verwendet, ich nahm Vorteil, oder nicht).
Mit meinem internen Modell -- ich bin nicht sicher, es ist vollkommen die gleiche wie die standard-ein-und es ist zu schmerzhaft, um zu überprüfen, dass die Informationen überall verteilt --
was Sie tun in der
clearBottomRightElement
gültig ist.int *p = &foo.row[7];
undefinedint i = mtx[0][5];
undefinedint *p = &row[7];
nicht kompilieren (gcc mir Zustimmen)int *p = &(&mtx[0][0])[7];
ist in der grauen zone (Letzte mal ich habe überprüft, in Einzelheiten etwas wie dieses, ich landete durch die Berücksichtigung ungültig C90 und gültigen C99-es könnte hier der Fall sein oder ich könnte etwas verpasst haben).int *p = &row[7]
. Ich Bearbeiten meine Frage.Was ich wirklich Suche, ist ein argument stützt sich auf die Formulierung in der Norm...
InformationsquelleAutor AProgrammer
Mein Verständnis von der C99-standard ist, dass es keine Anforderung, dass mehrdimensionale arrays müssen ausgelegt werden, dass ein zusammenhängender Auftrag in Erinnerung. Folgenden nur die relevanten Informationen, die ich im standard (jede dimension ist garantiert zusammenhängend sein).
Wenn Sie möchten, verwenden Sie die x[COLS*r + c] zugreifen, schlage ich vor, Sie bleiben zu eindimensionalen arrays.
Array subscripting
Aufeinander subskript-Operatoren bezeichnet ein element in einem mehrdimensionalen array-Objekt.
Wenn E ein n-dimensionalen array (n ≥ 2) mit den Dimensionen i × j × . . . × k, dann ist E (als
anderen als einen lvalue) wird umgewandelt in einen Zeiger auf ein (n − 1)-dimensionalen array mit
Abmessungen j × . . . × k. Wenn der unäre * - operator angewendet wird, um diesen Zeiger explizit, oder
implizit als Ergebnis der subscripting, das Ergebnis ist das Spitze-zu (n − 1)-dimensionalen array,
das selbst wird umgewandelt in einen Zeiger, wenn verwendet, als ein lvalue. Es folgt aus dieser
dass arrays gespeichert werden, in row-major order (Letzte index variiert am schnellsten).
Array-Typ
— Ein array-Typ beschreibt ein zusammenhängend zugeteilt nichtleere Menge von Objekten mit einem
insbesondere die Objekt-Typ bezeichnet den Typ des Elements.
36)
Array-Typen sind
zeichnen sich durch Ihre element-Typ und die Anzahl der Elemente im array. Ein
array-Typ ist, sagte zu sein, die sich aus Ihrer element-Typ, und wenn die element-Typ T , der
array-Typ wird manchmal auch als "array von T ". Der Aufbau einer array-Typ aus
ein element-Typ heißt "array-Typ "Ableitung".
Ihre Auslegung der Norm weitgehend entspricht mir. (Das ist beruhigend, denke ich!)
Der erste Satz ist offensichtlich falsch. Das layout im memory ist genau bestimmt durch die Semantik der
sizeof
- und Zeiger-Arithmetik. Es ist nur durch den aliasing-Regeln, dass diese Nutzung nicht definiert ist, und somit nur für nicht-char-Datentypen.Ich Stimme nicht über die Tcl / tk-Abmessungen verlangt wird, die nicht zusammenhängend sein. Ein array mit 3 Elementen in einem array mit 3 Elementen (arr[3][3]) sind zusammenhängend der Beschreibung entspricht sonst der zweite "array" (das eine mit den anderen 3 arrays) würde nicht zugelassen werden, nennen sich selbst ein array, da es das layout wäre nicht zusammenhängend. Die "innere" array (arr[3]) hat array von X in der Erwägung, dass die "äußere" array ist ein array von X[sizeof("inner array")].
Es ist der letztere - ein array von
int[3]
Objekte.InformationsquelleAutor nimrodm