Beim Durchlaufen einiger C-Interview-Fragen habe ich eine Frage gefunden, die besagt: "Wie wird die Größe eines Arrays in C ermittelt, ohne den Operator sizeof zu verwenden?", Mit der folgenden Lösung. Es funktioniert, aber ich kann nicht verstehen warum.
#include <stdio.h>
int main() {
int a[] = {100, 200, 300, 400, 500};
int size = 0;
size = *(&a + 1) - a;
printf("%d\n", size);
return 0;
}
Wie erwartet wird 5 zurückgegeben.
edit: Leute haben auf diese Antwort hingewiesen , aber die Syntax unterscheidet sich ein wenig, dh die Indizierungsmethode
size = (&arr)[1] - arr;
Daher glaube ich, dass beide Fragen gültig sind und eine etwas andere Herangehensweise an das Problem haben. Vielen Dank für die immense Hilfe und gründliche Erklärung!
c
arrays
size
language-lawyer
pointer-arithmetic
janojlic
quelle
quelle
&a + 1
wird nicht auf ein gültiges Objekt verwiesen, daher ist es ungültig.*((*(&array + 1)) - 1)
sicher, das letzte Element eines automatischen Arrays abzurufen? . tl; dr*(&a + 1)
ruft Undefined Behvaior(ptr)[x]
ist das gleiche wie*((ptr) + x)
.Antworten:
Wenn Sie einem Zeiger 1 hinzufügen, ist das Ergebnis die Position des nächsten Objekts in einer Folge von Objekten vom Typ, auf die gezeigt wird (dh ein Array). Wenn
p
auf einint
Objekt zeigt,p + 1
zeigt esint
in einer Sequenz auf das nächste . Wennp
auf ein 5-Element-Array vonint
(in diesem Fall den Ausdruck&a
) verweist ,p + 1
wird auf das nächste 5-Element-Array vonint
in einer Sequenz verwiesen.Das Subtrahieren von zwei Zeigern (vorausgesetzt, beide zeigen auf dasselbe Array-Objekt oder einer zeigt auf das letzte Element des Arrays) ergibt die Anzahl der Objekte (Array-Elemente) zwischen diesen beiden Zeigern.
Der Ausdruck
&a
liefert die Adresse vona
und hat den Typint (*)[5]
(Zeiger auf 5-Element-Array vonint
). Der Ausdruck&a + 1
liefert die Adresse des nächsten 5-Element-Arrays derint
folgendena
und hat auch den Typint (*)[5]
. Der Ausdruck*(&a + 1)
dereferenziert das Ergebnis von&a + 1
, so dass er die Adresse des erstenint
nach dem letzten Element von ergibta
und den Typ hatint [5]
, der in diesem Zusammenhang zu einem Ausdruck vom Typ "zerfällt"int *
.In ähnlicher Weise
a
"zerfällt" der Ausdruck in einen Zeiger auf das erste Element des Arrays und hat den Typint *
.Ein Bild kann helfen:
Dies sind zwei Ansichten desselben Speichers - links sehen wir ihn als eine Folge von 5-Element-Arrays von
int
, während wir ihn rechts als eine Folge von anzeigenint
. Ich zeige auch die verschiedenen Ausdrücke und ihre Typen.Beachten Sie, dass der Ausdruck
*(&a + 1)
zu undefiniertem Verhalten führt :C 2011 Online Draft , 6.5.6 / 9
quelle
size = (int*)(&a + 1) - a;
diesen Code schreiben würde, wäre er vollständig gültig? : oDiese Linie ist von größter Bedeutung:
Wie Sie sehen können, nimmt es zuerst die Adresse von
a
und fügt eine hinzu. Dann wird der Zeiger dereferenziert und der ursprüngliche Wert von subtrahierta
.Die Zeigerarithmetik in C bewirkt, dass die Anzahl der Elemente im Array zurückgegeben wird, oder
5
. Hinzufügen eines und&a
ist ein Zeiger auf das nächste Array von 5int
s danacha
. Danach dereferenziert dieser Code den resultierenden Zeiger und subtrahierta
(einen Array-Typ, der zu einem Zeiger zerfallen ist) von diesem, wobei die Anzahl der Elemente im Array angegeben wird.Details zur Funktionsweise der Zeigerarithmetik:
Angenommen, Sie haben einen Zeiger
xyz
, der auf einenint
Typ zeigt und den Wert enthält(int *)160
. Wenn Sie eine Zahl von subtrahierenxyz
, gibt C an, dass der tatsächliche Betrag, von dem subtrahiertxyz
wird, die Zahl multipliziert mit der Größe des Typs ist, auf den er zeigt. Wenn Sie zum Beispiel, subtrahierten5
vonxyz
dem Wert vonxyz
wäre resultierendenxyz - (sizeof(*xyz) * 5)
wenn Pointer - Arithmetik nicht anwendbar.Wie
a
bei einem Array von5
int
Typen lautet der resultierende Wert 5. Dies funktioniert jedoch nicht mit einem Zeiger, sondern nur mit einem Array. Wenn Sie dies mit einem Zeiger versuchen, wird das Ergebnis immer sein1
.Hier ist ein kleines Beispiel, das die Adressen zeigt und wie dies undefiniert ist. Die linke Seite zeigt die Adressen:
Dies bedeutet, dass der Code
a
von&a[5]
(odera+5
) subtrahiert und gibt5
.Beachten Sie, dass dies ein undefiniertes Verhalten ist und unter keinen Umständen verwendet werden sollte. Erwarten Sie nicht, dass das Verhalten auf allen Plattformen konsistent ist, und verwenden Sie es nicht in Produktionsprogrammen.
quelle
Hmm, ich vermute, das ist etwas, das in den frühen Tagen von C nicht funktioniert hätte. Es ist jedoch klug.
Führen Sie die einzelnen Schritte aus:
&a
erhält einen Zeiger auf ein Objekt vom Typ int [5]+1
erhält das nächste derartige Objekt unter der Annahme, dass es ein Array von diesen gibt*
konvertiert diese Adresse effektiv in einen Typzeiger auf int-a
subtrahiert die beiden int-Zeiger und gibt die Anzahl der int-Instanzen zwischen ihnen zurück.Ich bin mir nicht sicher, ob es vollständig legal ist (hier meine ich Sprachanwalt - es wird in der Praxis nicht funktionieren), angesichts einiger der laufenden Operationen. Beispielsweise dürfen Sie nur dann zwei Zeiger "subtrahieren", wenn sie auf Elemente im selben Array zeigen.
*(&a+1)
wurde durch Zugriff auf ein anderes Array, wenn auch ein übergeordnetes Array, synthetisiert, ist also eigentlich kein Zeiger auf dasselbe Array wiea
. Während Sie einen Zeiger nach dem letzten Element eines Arrays synthetisieren dürfen und jedes Objekt als Array mit einem Element behandeln können, ist die Operation dereferencing (*
) für diesen synthetisierten Zeiger nicht "erlaubt", obwohl dies der Fall ist hat in diesem Fall kein Verhalten!Ich vermute, dass in den frühen Tagen von C (K & R-Syntax, irgendjemand?) Ein Array viel schneller in einen Zeiger zerfiel, sodass das
*(&a+1)
möglicherweise nur die Adresse des nächsten Zeigers vom Typ int ** zurückgibt. Die strengeren Definitionen von modernem C ++ ermöglichen definitiv, dass der Zeiger auf den Array-Typ existiert und die Array-Größe kennt, und wahrscheinlich sind die C-Standards gefolgt. Der gesamte C-Funktionscode verwendet nur Zeiger als Argumente, sodass der technisch sichtbare Unterschied minimal ist. Aber ich rate nur hier.Diese Art der detaillierten Legalitätsfrage gilt normalerweise für einen C-Interpreter oder ein Flusentyp-Tool anstelle des kompilierten Codes. Ein Interpreter kann ein 2D-Array als Array von Zeigern auf Arrays implementieren, da eine Laufzeitfunktion weniger implementiert werden muss. In diesem Fall wäre eine Dereferenzierung von +1 fatal, und selbst wenn dies funktioniert, würde dies die falsche Antwort liefern.
Eine weitere mögliche Schwäche kann sein, dass der C-Compiler das äußere Array ausrichtet. Stellen Sie sich vor, dies wäre ein Array mit 5 Zeichen (
char arr[5]
). Wenn das Programm ausgeführt&a+1
wird, ruft es das Verhalten "Array of Array" auf. Der Compiler kann entscheiden, dass ein Array mit einem Array von 5 Zeichen (char arr[][5]
) tatsächlich als Array mit einem Array mit 8 Zeichen (char arr[][8]
) generiert wird , sodass das äußere Array gut ausgerichtet ist. Der Code, den wir diskutieren, würde jetzt die Arraygröße als 8 und nicht als 5 angeben. Ich sage nicht, dass ein bestimmter Compiler dies definitiv tun würde, aber es könnte sein.quelle
sizeof(array)/sizeof(array[0])
die Anzahl der Elemente in einem Array an.&a+1
ist definiert. Wie John Bollinger bemerkt,*(&a+1)
ist dies nicht der Fall, da versucht wird, ein nicht existierendes Objekt zu dereferenzieren.char [][5]
as implementierenchar arr[][8]
. Ein Array besteht nur aus den wiederholten Objekten. Es gibt keine Polsterung. Zusätzlich würde dies das (nicht normative) Beispiel 2 in C 2018 6.5.3.4 7 brechen, das uns sagt, dass wir die Anzahl der Elemente in einem Array mit berechnen könnensizeof array / sizeof array[0]
.