C Zeiger auf Array / Array von Zeigern Disambiguierung

463

Was ist der Unterschied zwischen den folgenden Erklärungen:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

Was ist die allgemeine Regel für das Verständnis komplexerer Erklärungen?

George
quelle
54
Hier ist ein großartiger Artikel über das Lesen komplexer Deklarationen in C: unixwiz.net/techtips/reading-cdecl.html
jesper
@jesper Leider ist die constund volatileQualifikation , die wichtig und heikel ist, wird in diesem Artikel fehlt.
Nicht-Benutzer
@ not-a-user die sind für diese frage nicht relevant. Ihr Kommentar ist nicht relevant. Bitte unterlassen Sie es.
user64742

Antworten:

439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

Der dritte ist der gleiche wie der erste.

Die allgemeine Regel ist die Priorität des Operators . Es kann noch viel komplexer werden, wenn Funktionszeiger ins Bild kommen.

Mehrdad Afshari
quelle
4
Also für 32-Bit-Systeme: int * arr [8]; / * 8x4 Bytes zugewiesen für jeden Zeiger / int (* arr) [8]; / 4 Bytes zugewiesen, nur ein Zeiger * /
George
10
Nee. int * arr [8]: 8x4 Bytes insgesamt zugewiesen , 4 Bytes für jeden Zeiger. int (* arr) [8] ist richtig, 4 Bytes.
Mehrdad Afshari
2
Ich hätte noch einmal lesen sollen, was ich geschrieben habe. Ich meinte 4 für jeden Zeiger. Danke für die Hilfe!
George
4
Der Grund dafür, dass der erste mit dem letzten identisch ist, besteht darin, dass Deklaratoren immer in Klammern gesetzt werden dürfen. P [N] ist ein Array-Deklarator. P (....) ist ein Funktionsdeklarator und * P ist ein Zeigerdeklarator. Im Folgenden ist also alles dasselbe wie ohne Klammern (mit Ausnahme der Funktion '"()": int (((* p))); void ((g (void))); int * (a [1]); nichtig (* (p ())).
Johannes Schaub - litb
2
Gut gemacht in deiner Erklärung. Ausführliche Informationen zu Vorrang und Assoziativität von Operatoren finden Sie auf Seite 53 der Programmiersprache C (ANSI C, zweite Ausgabe) von Brian Kernighan und Dennis Ritchie. Die Operatoren ( ) [ ] assoziieren von links nach rechts und haben eine höhere Priorität als *das, was int* arr[8]als Array der Größe 8 gelesen wird, wobei jedes Element auf ein int zeigt, und int (*arr)[8]als Zeiger auf ein Array der Größe 8, das ganze Zahlen enthält
Mushy
267

Verwenden Sie das von K & R vorgeschlagene Programm cdecl .

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

Es funktioniert auch umgekehrt.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )
Sigjuice
quelle
@ankii Die meisten Linux-Distributionen sollten ein Paket haben. Sie können auch Ihre eigene Binärdatei erstellen.
Sigjuice
ah sorry für nicht erwähnen, macOS hier. wird sehen, ob verfügbar, sonst ist die Website auch in Ordnung. ^^ danke, dass du mich darüber informiert hast .. Fühle dich frei, NLN zu kennzeichnen.
Ankii
2
@ankii Sie können von Homebrew (und vielleicht MacPorts?) installieren. Wenn diese nicht Ihrem Geschmack entsprechen, ist es trivial, Ihre eigenen über den Github-Link oben rechts auf cdecl.org zu erstellen (ich habe sie gerade auf macOS Mojave erstellt). Dann kopieren Sie einfach die cdecl-Binärdatei in Ihren PATH. Ich empfehle $ PATH / bin, da es nicht notwendig ist, root in so etwas Einfaches einzubeziehen.
Sigjuice
Oh, ich hatte den kleinen Absatz über die Installation in Readme nicht gelesen. Nur einige Befehle und Flags zur Behandlung von Abhängigkeiten. Installiert mit Brew. :)
Ankii
1
Als ich das zum ersten Mal las, dachte ich: "Ich werde niemals auf dieses Niveau kommen". Am nächsten Tag habe ich es heruntergeladen.
Ciro Santilli 27 冠状 病 六四 事件 27
126

Ich weiß nicht, ob es einen offiziellen Namen hat, aber ich nenne es das Right-Left Thingy (TM).

Beginnen Sie bei der Variablen, gehen Sie dann nach rechts und links und rechts ... und so weiter.

int* arr1[8];

arr1 ist ein Array von 8 Zeigern auf Ganzzahlen.

int (*arr2)[8];

arr2 ist ein Zeiger (der Klammerblock rechts-links) auf ein Array von 8 Ganzzahlen.

int *(arr3[8]);

arr3 ist ein Array von 8 Zeigern auf Ganzzahlen.

Dies sollte Ihnen bei komplexen Deklarationen helfen.

GManNickG
quelle
19
Ich habe gehört, dass es unter dem Namen "The Spiral Rule" bezeichnet wird, das hier zu finden ist .
Fouric
6
@InkBlend: Die Spiralregel unterscheidet sich von der Rechts-Links-Regel . Der ehemalige versagt in solchen Fällen int *a[][10]während letztere gelingt.
Legends2k
1
@dogeen Ich dachte, dieser Begriff hätte etwas mit Bjarne Stroustrup zu tun :)
Anirudh Ramanathan
1
Wie InkBlend und legends2k sagten, ist dies eine Spiralregel, die komplexer ist und nicht in allen Fällen funktioniert. Es gibt also keinen Grund, sie zu verwenden.
Kotlomoy
Vergessen Sie nicht die Lef nach rechts Assoziativität von ( ) [ ]und von rechts nach links von* &
Mushy
28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 
Sunil bn
quelle
Sollte der dritte nicht sein: a ist ein Array von Zeigern auf ein ganzzahliges Array der Größe 8? Ich meine, jedes der Integer-Arrays hat die Größe 8, oder?
Rushil Paul
2
@Rushil: Nein, der letzte Index ( [5]) repräsentiert die innere Dimension. Dies bedeutet, dass dies (*a[8])die erste Dimension und somit die äußere Darstellung des Arrays ist. Was jedes Element innerhalb a zeigt, ist ein anderes ganzzahliges Array der Größe 5.
Zeboidlund
Danke für den dritten. Ich suche nach, wie man ein Array von Zeigern auf ein Array schreibt.
Deqing
15

Die Antwort für die letzten beiden kann auch von der goldenen Regel in C abgezogen werden:

Erklärung folgt Verwendung.

int (*arr2)[8];

Was passiert, wenn Sie dereferenzieren arr2? Sie erhalten ein Array von 8 Ganzzahlen.

int *(arr3[8]);

Was passiert, wenn Sie ein Element entnehmen arr3? Sie erhalten einen Zeiger auf eine Ganzzahl.

Dies hilft auch beim Umgang mit Zeigern auf Funktionen. Um das Beispiel von Sigjuice zu nehmen:

float *(*x)(void )

Was passiert, wenn Sie dereferenzieren x? Sie erhalten eine Funktion, die Sie ohne Argumente aufrufen können. Was passiert, wenn Sie es nennen? Es wird ein Zeiger auf a zurückgegeben float.

Die Priorität des Bedieners ist jedoch immer schwierig. Die Verwendung von Klammern kann jedoch auch verwirrend sein, da die Deklaration auf die Verwendung folgt. Zumindest arr2sieht es für mich intuitiv aus wie ein Array von 8 Zeigern auf Ints, aber es ist tatsächlich umgekehrt. Es ist nur gewöhnungsbedürftig. Grund genug, diesen Erklärungen immer einen Kommentar hinzuzufügen, wenn Sie mich fragen :)

bearbeiten: Beispiel

Übrigens bin ich gerade auf die folgende Situation gestoßen: Eine Funktion mit einer statischen Matrix, die Zeigerarithmetik verwendet, um festzustellen, ob der Zeilenzeiger außerhalb der Grenzen liegt. Beispiel:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

Ausgabe:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

Beachten Sie, dass sich der Wert von border nie ändert, sodass der Compiler dies optimieren kann. Dies unterscheidet sich von dem, was Sie ursprünglich verwenden möchten const int (*border)[3]:: Das deklariert border als Zeiger auf ein Array von 3 Ganzzahlen, die den Wert nicht ändern, solange die Variable vorhanden ist. Dieser Zeiger kann jedoch jederzeit auf ein anderes solches Array gerichtet sein. Wir wollen stattdessen diese Art von Verhalten für das Argument (weil diese Funktion keine dieser ganzen Zahlen ändert). Erklärung folgt Verwendung.

(ps: zögern Sie nicht, dieses Beispiel zu verbessern!)

Hraban Luyat
quelle
5
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];
Byron Formwalt
quelle
3

Als Faustregel gilt : rechts unäre Operatoren (wie [], ()usw.) haben Vorrang vor links diejenigen. Wäre int *(*ptr)()[];also ein Zeiger, der auf eine Funktion verweist, die ein Array von Zeigern auf int zurückgibt (holen Sie sich die richtigen Operatoren, sobald Sie aus der Klammer herauskommen können).

Luis Colorado
quelle
Das ist wahr, aber es ist auch legitim. Sie können keine Funktion haben, die ein Array zurückgibt. Ich habe versucht und das bekommen: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];unter GCC 8 mit$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito
1
Der Grund, warum der Compiler diesen Fehler ausgibt, besteht darin, dass er die Funktion als Rückgabe eines Arrays interpretiert, wie es die korrekte Interpretation der Prioritätsregel angibt. Es ist als Deklaration legitim, aber die gesetzliche Deklaration int *(*ptr)();ermöglicht die spätere Verwendung eines Ausdrucks wie p()[3](oder (*p)()[3]).
Luis Colorado
Ok, wenn ich es verstehe, sprechen Sie über das Erstellen einer Funktion, die einen Zeiger auf das erste Element eines Arrays zurückgibt (nicht auf ein Array selbst), und verwenden Sie diese Funktion später so, als würde sie ein Array zurückgeben? Interessante Idee. Ich werde es versuchen. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }und nenne es so: foo(arr)[4];was soll enthalten arr[2][4], oder?
Cacahuete Frito
richtig ... aber du hattest auch recht und die Erklärung war ilegal. :)
Luis Colorado
2

Ich denke, wir können die einfache Regel anwenden.

example int * (*ptr)()[];
start from ptr 

" ptrist ein Zeiger auf" nach rechts gehen .. es ")" jetzt nach links gehen ist ein "(" herauskommen nach rechts gehen "()" also "auf eine Funktion, die keine Argumente akzeptiert" nach links gehen "und einen Zeiger zurückgeben" gehen " rechts "zu einem Array" gehe links "von ganzen Zahlen"

simal
quelle
Ich würde das ein wenig verbessern: "ptr ist ein Name, der sich auf" nach rechts gehen ... es ist ), jetzt nach links gehen ... es ist *"ein Zeiger auf" nach rechts gehen ... es ist ), jetzt nach links gehen ... es ist a (komm raus, gehe nach rechts ()so "zu einer Funktion, die keine Argumente akzeptiert" gehe nach rechts ... []"und gebe ein Array von" nach rechts gehen ;, also nach links gehen ... *"Zeiger auf" nach links gehen ... int"Ganzzahlen" zurück.
Cacahuete Frito
2

So interpretiere ich es:

int *something[n];

Hinweis zur Priorität: Der Array-Indexoperator ( []) hat eine höhere Priorität als der Dereferenzierungsoperator ( *).

Also, hier werden wir die Anwendung []vor *, um die Anweisung äquivalent zu machen:

int *(something[i]);

Hinweis darüber , wie eine Erklärung macht Sinn: int numMittel numeinint , int *ptroder int (*ptr)Mittel, (Wert bei ptr) ein int, wodurch ptrauf einen Zeiger int.

Dies kann gelesen werden als (Wert von (Wert am i-ten Index des Etwas)) ist eine ganze Zahl. Also (Wert am i-ten Index von etwas) ist ein (ganzzahliger Zeiger), der das Etwas zu einem Array von ganzzahligen Zeigern macht.

Im zweiten,

int (*something)[n];

Um aus dieser Aussage einen Sinn zu machen, müssen Sie mit dieser Tatsache vertraut sein:

Hinweis zur Zeigerdarstellung des Arrays: somethingElse[i]entspricht*(somethingElse + i)

Also, ersetzt somethingElsemit (*something), bekommen wir *(*something + i), was eine ganze Zahl als pro - Deklaration. Geben Sie (*something)uns also ein Array, das etwas Äquivalentes zu (Zeiger auf ein Array) macht .

nishantbhardwaj2002
quelle
0

Ich denke, die zweite Erklärung ist für viele verwirrend. Hier ist eine einfache Möglichkeit, dies zu verstehen.

Lassen Sie uns ein Array von ganzen Zahlen haben, dh int B[8].

Lassen Sie uns auch eine Variable A haben, die auf B zeigt. Nun ist der Wert bei A B, dh (*A) == B. Daher zeigt A auf ein Array von ganzen Zahlen. In Ihrer Frage ähnelt arr A.

In ähnlicher Weise ist in int* (*C) [8]C ein Zeiger auf ein Array von Zeigern auf eine Ganzzahl.

nishantbhardwaj2002
quelle
0
int *arr1[5]

In dieser Deklaration arr1befindet sich ein Array von 5 Zeigern auf Ganzzahlen. Grund: Eckige Klammern haben eine höhere Priorität als * (Dereferenzierungsoperator). Bei diesem Typ ist die Anzahl der Zeilen festgelegt (hier 5), die Anzahl der Spalten ist jedoch variabel.

int (*arr2)[5]

In dieser Deklaration arr2ist ein Zeiger auf ein ganzzahliges Array von 5 Elementen. Grund: Hier haben () Klammern eine höhere Priorität als []. Bei diesem Typ ist die Anzahl der Zeilen variabel, aber die Anzahl der Spalten ist fest (hier 5).

Krishna Kanth Yenumula
quelle
-7

Wenn der Zeiger im Zeiger auf eine Ganzzahl inkrementiert wird, wird die nächste Ganzzahl verwendet.

Wenn im Zeigerarray der Zeiger inkrementiert ist, springt er zum nächsten Array

Nikhil
quelle
" Im Array des Zeigers springt der Zeiger zum nächsten Array, wenn er inkrementiert wird. " Dies ist einfach falsch.
Alk