Wie bestimmt dieser Codeabschnitt die Arraygröße, ohne sizeof( ) zu verwenden?

Lesezeit: 9 Minuten

Benutzeravatar von janojlic
Janojlic

Beim Durchgehen einiger C-Interviewfragen habe ich eine Frage gefunden, die besagt: “Wie finde ich die Größe eines Arrays in C, ohne den sizeof-Operator 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.

Bearbeiten: Die 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 einen etwas anderen Ansatz für das Problem haben. Vielen Dank an alle für die immense Hilfe und die gründliche Erklärung!

  • Nun, ich kann es nicht finden, aber sieht streng genommen so aus, als wäre es so. Anhang J.2 erklärt ausdrücklich: Der Operand des unären Operators * hat einen ungültigen Wert ist ein undefiniertes Verhalten. Hier &a + 1 zeigt auf kein gültiges Objekt, also ist es ungültig.

    – Eugen Sch.

    15. Mai 2019 um 18:39 Uhr


  • Verwandte: Ist *((*(&array + 1)) - 1) sicher zu verwenden, um das letzte Element eines automatischen Arrays zu erhalten?. tl;dr *(&a + 1) ruft undefiniertes Verhalten auf

    – Spikatrix

    16. Mai 2019 um 6:30 Uhr


  • Mögliches Duplikat von Find size of array without using sizeof in C

    – Alma tun

    17. Mai 2019 um 8:55 Uhr

  • @AlmaDo Nun, die Syntax unterscheidet sich ein wenig, dh der Indexierungsteil, daher glaube ich, dass diese Frage für sich genommen immer noch gültig ist, aber ich könnte mich irren. Vielen Dank für den Hinweis!

    – Janojlic

    17. Mai 2019 um 20:06 Uhr

  • @janojlicz Sie sind im Wesentlichen gleich, weil (ptr)[x] ist das gleiche wie *((ptr) + x).

    – SS Anne

    7. Juni 2019 um 19:39 Uhr

Benutzeravatar von John Bode
Johannes Bode

Wenn Sie 1 zu einem Zeiger hinzufügen, ist das Ergebnis die Position des nächsten Objekts in einer Folge von Objekten des Typs, auf den gezeigt wird (dh ein Array). Wenn p weist auf ein int Objekt, dann p + 1 wird auf das nächste hinweisen int in einer Folge. Wenn p zeigt auf ein 5-Element-Array von int (in diesem Fall der Ausdruck &a), dann p + 1 wird auf das nächste hinweisen 5-Element-Array von int in einer Folge.

Das Subtrahieren von zwei Zeigern (vorausgesetzt, sie zeigen beide auf dasselbe Array-Objekt oder einer zeigt um einen nach dem letzten Element des Arrays) ergibt die Anzahl der Objekte (Array-Elemente) zwischen diesen beiden Zeigern.

Der Ausdruck &a ergibt die Adresse von aund hat den Typ int (*)[5] (Zeiger auf 5-Element-Array von int). Der Ausdruck &a + 1 liefert die Adresse des nächsten 5-Element-Arrays von int folgende aund hat auch den Typ int (*)[5]. Der Ausdruck *(&a + 1) dereferenziert das Ergebnis von &a + 1so dass es die Adresse des ersten ergibt int nach dem letzten Element von aund hat Typ int [5]die in diesem Zusammenhang zu einem Typus-Ausdruck “zerfällt”. int *.

Ebenso der Ausdruck a “zerfällt” in einen Zeiger auf das erste Element des Arrays und hat einen Typ int *.

Ein Bild kann helfen:

int [5]  int (*)[5]     int      int *

+---+                   +---+
|   | <- &a             |   | <- a
| - |                   +---+
|   |                   |   | <- a + 1
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+
|   | <- &a + 1         |   | <- *(&a + 1)
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+

Dies sind zwei Ansichten desselben Speichers – auf der linken Seite sehen wir ihn als eine Folge von Arrays mit 5 Elementen intwährend wir es auf der rechten Seite als eine Folge von betrachten int. Ich zeige auch die verschiedenen Ausdrücke und ihre Typen.

Achtung, der Ausdruck *(&a + 1) ergibt sich undefiniertes Verhalten:


Wenn das Ergebnis um eins nach dem letzten Element des Array-Objekts zeigt, darf es nicht als Operand eines ausgewerteten unären *-Operators verwendet werden.

C 2011 Online-Entwurf6.5.6/9

  • Dieser Text „darf nicht verwendet werden“ ist offiziell: C 2018 6.5.6 8.

    – Eric Postpischil

    15. Mai 2019 um 20:25 Uhr

  • @EricPostpischil: Hast du einen Link zum Pre-Pub Draft 2018 (ähnlich N1570.pdf)?

    – Johannes Bode

    15. Mai 2019 um 20:36 Uhr

  • @JohnBode: Diese Antwort hat ein Link zur Wayback Machine. Ich habe den offiziellen Standard in meinem gekauften Exemplar überprüft.

    – Eric Postpischil

    15. Mai 2019 um 20:46 Uhr

  • Also wenn man schrieb size = (int*)(&a + 1) - a; wäre dieser Code vollständig gültig? :Ö

    – Gizmo

    16. Mai 2019 um 11:18 Uhr

  • @Gizmo sie haben das wahrscheinlich ursprünglich nicht geschrieben, weil Sie auf diese Weise den Elementtyp angeben müssen; Das Original wurde wahrscheinlich als Makro für die typgenerische Verwendung auf verschiedenen Elementtypen definiert geschrieben.

    – Leuschenko

    17. Mai 2019 um 10:08 Uhr

Benutzeravatar von SS Anne
SS Anne

Diese Zeile ist am wichtigsten:

size = *(&a + 1) - a;

Wie Sie sehen können, nimmt es zuerst die Adresse von a und fügt eins hinzu. Dann wird dieser Zeiger dereferenziert und der ursprüngliche Wert von subtrahiert a davon.

Die Zeigerarithmetik in C bewirkt, dass dies die Anzahl der Elemente im Array oder zurückgibt 5. Hinzufügen von eins und &a ist ein Zeiger auf das nächste Array von 5 intist nach a. Danach dereferenziert dieser Code den resultierenden Zeiger und subtrahiert a (ein Array-Typ, der zu einem Zeiger zerfallen ist) daraus und gibt die Anzahl der Elemente im Array an.

Details zur Funktionsweise der Zeigerarithmetik:

Angenommen, Sie haben einen Zeiger xyz das deutet auf ein int Typ und enthält den Wert (int *)160. Wenn Sie eine beliebige Zahl von subtrahieren xyzC gibt an, dass der tatsächliche Betrag abgezogen wird xyz ist diese Zahl mal die Größe des Typs, auf den es zeigt. Zum Beispiel, wenn Sie subtrahiert haben 5 aus xyzder Wert von xyz Ergebnis wäre xyz - (sizeof(*xyz) * 5) wenn die Zeigerarithmetik nicht angewendet wurde.

Wie a ist ein Array von 5 int -Typen ist 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 sein 1.

Hier ist ein kleines Beispiel, das die Adressen zeigt und wie diese undefiniert sind. Die linke Seite zeigt die Adressen:

a + 0 | [a[0]] | &a points to this
a + 1 | [a[1]]
a + 2 | [a[2]]
a + 3 | [a[3]]
a + 4 | [a[4]] | end of array
a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced

Dies bedeutet, dass der Code subtrahiert a aus &a[5] (oder a+5), geben 5.

Beachten Sie, dass dies ein undefiniertes Verhalten ist und unter keinen Umständen verwendet werden sollte. Erwarten Sie nicht, dass dieses Verhalten auf allen Plattformen konsistent ist, und verwenden Sie es nicht in Produktionsprogrammen.

Benutzeravatar von Gem Taylor
Juwel Taylor

Hmm, ich vermute, das ist etwas, das in den frühen Tagen von C nicht funktioniert hätte. Es ist aber clever.

Gehen Sie die Schritte einzeln durch:

  • &a Ruft einen Zeiger auf ein Objekt vom Typ int ab[5]
  • +1 ruft das nächste derartige Objekt ab, vorausgesetzt, es gibt ein Array davon
  • * wandelt diese Adresse effektiv in einen Typzeiger auf int um
  • -a subtrahiert die beiden int-Zeiger und gibt die Anzahl der int-Instanzen zwischen ihnen zurück.

Ich bin mir nicht sicher, ob es völlig legal ist (damit meine ich das Recht eines Sprachanwalts – in der Praxis wird es nicht funktionieren), wenn man bedenkt, dass einige der Art von Operationen stattfinden. Zum Beispiel dürfen Sie nur zwei Zeiger subtrahieren, wenn sie auf Elemente im selben Array zeigen. *(&a+1) wurde durch Zugriff auf ein anderes Array synthetisiert, obwohl es sich um ein übergeordnetes Array handelt, und ist daher kein Zeiger auf dasselbe Array wie a. Auch wenn es Ihnen erlaubt ist, einen Zeiger über das letzte Element eines Arrays hinaus zu synthetisieren, und Sie jedes Objekt als ein Array mit 1 Element behandeln können, ist die Operation der Dereferenzierung (*) ist für diesen synthetisierten Zeiger nicht “erlaubt”, obwohl er in diesem Fall kein Verhalten hat!

Ich vermute, dass in den frühen Tagen von C (K&R-Syntax, irgendjemand?) ein Array viel schneller in einen Zeiger zerfiel, also die *(&a+1) gibt möglicherweise nur die Adresse des nächsten Zeigers vom Typ int** zurück. Die strengeren Definitionen von modernem C++ lassen definitiv zu, dass der Zeiger auf den Array-Typ existiert und die Array-Größe kennt, und wahrscheinlich sind die C-Standards diesem Beispiel gefolgt. Der gesamte C-Funktionscode akzeptiert nur Zeiger als Argumente, sodass der technisch sichtbare Unterschied minimal ist. Aber ich vermute hier nur.

Diese Art von detaillierter Legalitätsfrage gilt normalerweise eher für einen C-Interpreter oder ein Lint-Tool als für den kompilierten Code. Ein Interpreter könnte ein 2D-Array als ein Array von Zeigern auf Arrays implementieren, da eine Laufzeitfunktion weniger implementiert werden muss. In diesem Fall wäre die Dereferenzierung von +1 fatal, und selbst wenn es funktionieren würde, würde es die falsche Antwort geben.

Eine weitere mögliche Schwachstelle könnte sein, dass der C-Compiler das äußere Array ausrichten könnte. Stellen Sie sich vor, dies wäre ein Array von 5 Zeichen (char arr[5]), wenn das Programm ausgeführt wird &a+1 es ruft das “Array of Array”-Verhalten auf. Der Compiler könnte entscheiden, dass ein Array von Arrays aus 5 Zeichen (char arr[][5]) wird tatsächlich als Array von Arrays mit 8 Zeichen (char arr[][8]), sodass das äußere Array gut ausgerichtet ist. Der Code, den wir besprechen, würde jetzt die Array-Größe als 8 und nicht als 5 melden. Ich sage nicht, dass ein bestimmter Compiler dies definitiv tun würde, aber es könnte sein.

  • Fair genug. Aus schwer zu erklärenden Gründen verwendet jedoch jeder sizeof()/sizeof() ?

    – Juwel Taylor

    15. Mai 2019 um 17:26 Uhr

  • Die meisten Leute tun es. Zum Beispiel, sizeof(array)/sizeof(array[0]) gibt die Anzahl der Elemente in einem Array an.

    – SS Anne

    15. Mai 2019 um 17:28 Uhr

  • Der C-Compiler darf das Array ausrichten, aber ich bin nicht überzeugt, dass es erlaubt ist, den Typ des Arrays danach zu ändern. Die Ausrichtung würde realistischer durch das Einfügen von Füllbytes implementiert werden.

    – Kevin

    15. Mai 2019 um 18:15 Uhr


  • Das Subtrahieren von Zeigern ist nicht auf nur zwei Zeiger in dasselbe Array beschränkt – die Zeiger dürfen auch einen nach dem Ende des Arrays sein. &a+1 ist definiert. Wie John Bollinger feststellt, *(&a+1) nicht, da es versucht, ein nicht vorhandenes Objekt zu dereferenzieren.

    – Eric Postpischil

    15. Mai 2019 um 20:10 Uhr

  • Ein Compiler kann a nicht implementieren char [][5] wie char arr[][8]. Ein Array besteht nur aus den sich wiederholenden Objekten darin; es gibt keine polsterung. Außerdem 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 berechnen können sizeof array / sizeof array[0].

    – Eric Postpischil

    15. Mai 2019 um 20:15 Uhr

1423560cookie-checkWie bestimmt dieser Codeabschnitt die Arraygröße, ohne sizeof( ) zu verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy