it-swarm.com.de

Was ist ein Array?

Was ist ein Zerfall eines Arrays? Gibt es eine Beziehung zu Array-Zeigern?

318
Vamsi

Es wird gesagt, dass Arrays in Zeiger zerfallen. Ein als int numbers [5] deklariertes C++ - Array kann nicht erneut angezeigt werden, d. H. Sie können numbers = 0x5a5aff23 nicht sagen. Noch wichtiger ist, dass der Begriff Zerfall den Verlust von Typ und Dimension bedeutet. numbers zerfällt in int* durch Verlust der Dimensionsinformationen (Anzahl 5) und der Typ ist nicht mehr int [5]. Suchen Sie hier nach Fällen, in denen der Zerfall nicht auftritt .

Wenn Sie ein Array nach Wert übergeben, kopieren Sie tatsächlich einen Zeiger. Ein Zeiger auf das erste Element des Arrays wird in den Parameter kopiert (dessen Typ auch ein Zeiger sein soll, der Typ des Array-Elements). Dies funktioniert aufgrund der zerfallenden Natur des Arrays. Nach dem Abklingen gibt sizeof nicht mehr die Größe des gesamten Arrays wieder, da es im Wesentlichen zu einem Zeiger wird. Aus diesem Grund wird es (unter anderem) bevorzugt, als Referenz oder Zeiger zu übergeben.

Drei Möglichkeiten, ein Array zu übergeben1:

void by_value(const T* array)   // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])

Die letzten beiden geben die richtigen sizeof-Informationen, während der erste nicht mehr vorhanden ist, da das Array-Argument dem Parameter nicht mehr zugeordnet wurde.

1 Die Konstante U sollte zur Kompilierzeit bekannt sein.

234
phoebus

Arrays entsprechen im Wesentlichen den Zeigern in C/C++, jedoch nicht ganz. Sobald Sie ein Array konvertieren:

const int a[] = { 2, 3, 5, 7, 11 };

in einen Zeiger (der ohne Casting funktioniert und daher in einigen Fällen unerwartet vorkommen kann):

const int* p = a;

sie verlieren die Möglichkeit des Operators sizeof, Elemente im Array zu zählen:

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

Diese verlorene Fähigkeit wird als "Zerfall" bezeichnet.

Weitere Informationen finden Sie in diesem Artikel über Array Decay .

85
system PAUSE

Das sagt der Standard (C99 6.3.2.1/3 - Andere Operanden - L-Werte, Arrays und Funktionsbezeichner):

Außer, wenn es sich um den Operanden des Operators sizeof oder des unären Operators & oder um einen .__ handelt. String-Literal, das zum Initialisieren eines Arrays verwendet wird. Ein Ausdruck, der den Typ "Array des Typs" hat, ist konvertiert in einen Ausdruck mit dem Typ "Zeiger auf Typ", der auf das Ausgangselement von .__ verweist. das Array-Objekt und ist kein Wert.

Dies bedeutet, dass der Arrayname fast immer dann verwendet wird, wenn der Arrayname in einem Ausdruck verwendet wird, und er wird automatisch in einen Zeiger auf das erste Element des Arrays konvertiert.

Beachten Sie, dass Funktionsnamen auf ähnliche Weise funktionieren, Funktionszeiger jedoch weitaus weniger verwendet werden und auf eine viel speziellere Art und Weise, die nicht annähernd so viel Verwirrung verursacht wie die automatische Konvertierung von Arraynamen in Zeiger.

Der C++ - Standard (4.2 Array-to-Pointer-Konvertierung) löst die Konvertierungsanforderung auf (Hervorhebungsmine):

Ein lvalue oder rvalue vom Typ "Array von N T" oder "Array mit unbekannter Grenze von T" can kann in einen rvalue. vom Typ "Zeiger auf T."

Die Konvertierung führt also nicht zu have, wie es in C eigentlich immer der Fall ist (dadurch können Funktionen überladen oder Vorlagen mit dem Array-Typ übereinstimmen).

Aus diesem Grund sollten Sie in C auf die Verwendung von Array-Parametern in Funktionsprototypen/-definitionen verzichten (meiner Meinung nach - ich bin mir nicht sicher, ob es eine generelle Vereinbarung gibt). Sie verursachen Verwirrung und sind ohnehin eine Fiktion - verwenden Sie Zeigerparameter und die Verwirrung wird möglicherweise nicht vollständig beseitigt, aber zumindest die Parameterdeklaration lügt nicht.

43
Michael Burr

"Decay" bezieht sich auf die implizite Konvertierung eines Ausdrucks von einem Array-Typ in einen Zeigertyp. Wenn der Compiler einen Array-Ausdruck sieht, konvertiert er in den meisten Kontexten den Typ des Ausdrucks von "N-Element-Array von T" in "Zeiger auf T" und setzt den Wert des Ausdrucks auf die Adresse des ersten Elements des Arrays . Die Ausnahmen zu dieser Regel sind, wenn ein Array ein Operand der Operatoren sizeof oder & ist oder das Array ein String-Literal ist, das als Initialisierer in einer Deklaration verwendet wird. 

Nehmen Sie den folgenden Code an:

char a[80];
strcpy(a, "This is a test");

Der Ausdruck a ist vom Typ "80-Elemente-Array von Char" und der Ausdruck "Dies ist ein Test" ist vom Typ "16-Element-Array von Char" (in C; in C++ - String-Literalen sind Arrays von const-Zeichen). Im Aufruf von strcpy() ist jedoch kein Ausdruck ein Operand von sizeof oder &, daher werden deren Typen implizit in "Zeiger auf Char" konvertiert, und ihre Werte werden jeweils auf die Adresse des ersten Elements gesetzt. Was strcpy() empfängt, sind keine Arrays, sondern Zeiger, wie sie in ihrem Prototyp zu sehen sind:

char *strcpy(char *dest, const char *src);

Dies ist nicht dasselbe wie ein Arrayzeiger. Zum Beispiel:

char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;

Sowohl ptr_to_first_element als auch ptr_to_array haben den gleichen Wert ; die Basisadresse von a. Es handelt sich jedoch um unterschiedliche Typen, die wie unten dargestellt unterschiedlich behandelt werden:

a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]

Denken Sie daran, dass der Ausdruck a[i] als *(a+i) interpretiert wird (was nur funktioniert, wenn der Array-Typ in einen Zeigertyp konvertiert wird), sodass a[i] und ptr_to_first_element[i] gleich funktionieren. Der Ausdruck (*ptr_to_array)[i] wird als *(*a+i) interpretiert. Die Ausdrücke *ptr_to_array[i] und ptr_to_array[i] können je nach Kontext zu Compiler-Warnungen oder Fehlern führen. Sie werden definitiv das Falsche tun, wenn Sie erwarten, dass sie a[i] bewerten.

sizeof a == sizeof *ptr_to_array == 80

Wenn ein Array ein Operand von sizeof ist, wird es nicht in einen Zeigertyp konvertiert. 

sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
                                                  is on your platform

ptr_to_first_element ist ein einfacher Zeiger auf char. 

25
John Bode

Arrays in C haben keinen Wert.

Wenn der Wert eines Objekts erwartet wird, das Objekt jedoch ein Array ist, wird stattdessen die Adresse des ersten Elements vom Typ pointer to (type of array elements) verwendet.

In einer Funktion werden alle Parameter als Wert übergeben (Arrays sind keine Ausnahme). Wenn Sie ein Array in einer Funktion übergeben, "zerfällt es in einen Zeiger" (sic); Wenn Sie ein Array mit etwas anderem vergleichen, zerfällt es wieder in einen Zeiger (sic). ...

void foo(int arr[]);

Die Funktion foo erwartet den Wert eines Arrays. Aber in C haben Arrays keinen Wert! foo erhält stattdessen die Adresse des ersten Elements des Arrays.

int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }

Im obigen Vergleich hat arr keinen Wert und wird somit zu einem Zeiger. Es wird ein Zeiger auf int. Dieser Zeiger kann mit der Variablen ip verglichen werden.

In der Array-Indizierungssyntax sind Sie es gewohnt zu sehen, dass der Arr 'zu einem Zeiger zerfallen ist.

arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */

Ein Array zerfällt nur dann nicht in einen Zeiger, wenn es sich um den Operanden des Operators sizeof oder des Operators & (Operator "address of") oder als Stringliteral handelt, das zum Initialisieren eines Zeichenarrays verwendet wird.

12
pmg

Es ist, wenn Array verrottet und darauf gerichtet wird ;-)

Eigentlich ist es nur so, wenn Sie ein Array irgendwo übergeben wollen, aber stattdessen der Zeiger übergeben wird (denn wer zum Teufel würde das ganze Array für Sie übergeben), sagen die Leute, dass ein schlechtes Array zum Zeiger zerfällt.

Array Decaying bedeutet, dass ein Array, das als Parameter an eine Funktion übergeben wird, identisch zu einem Zeiger ("decays to") behandelt wird.

void do_something(int *array) {
  // We don't know how big array is here, because it's decayed to a pointer.
  printf("%i\n", sizeof(array));  // always prints 4 on a 32-bit machine
}

int main (int argc, char **argv) {
    int a[10];
    int b[20];
    int *c;
    printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
    printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
    printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
    do_something(a);
    do_something(b);
    do_something(c);
}

Es gibt zwei Komplikationen oder Ausnahmen von den oben genannten.

Beim Umgang mit mehrdimensionalen Arrays in C und C++ geht zunächst nur die erste Dimension verloren. Dies liegt daran, dass Arrays im Speicher zusammenhängend angeordnet sind. Der Compiler muss also alle außer der ersten Dimension kennen, um Offsets in diesen Speicherblock berechnen zu können.

void do_something(int array[][10])
{
    // We don't know how big the first dimension is.
}

int main(int argc, char *argv[]) {
    int a[5][10];
    int b[20][10];
    do_something(a);
    do_something(b);
    return 0;
}

Zweitens können Sie in C++ Vorlagen verwenden, um die Größe von Arrays abzuleiten. Microsoft verwendet dies für die C++ - Versionen von Secure CRT-Funktionen wie strcpy_s , und Sie können einen ähnlichen Trick verwenden, um zuverlässig die Anzahl der Elemente in einem Array abzurufen.

2
Josh Kelley

tl; dr: Wenn Sie ein von Ihnen definiertes Array verwenden, verwenden Sie tatsächlich einen Zeiger auf das erste Element.

Somit:

  • Wenn Sie arr[idx] schreiben, sagen Sie wirklich nur *(arr + idx).
  • funktionen nehmen Arrays niemals wirklich als Parameter, sondern nur als Zeiger auf, selbst wenn Sie einen Array-Parameter angeben.

Ausnahmen von dieser Regel:

  • Sie können Arrays mit fester Länge an Funktionen innerhalb einer struct übergeben.
  • sizeof() gibt die Größe des Arrays an, nicht die Größe eines Zeigers.
0
einpoklum