Was ist der Zerfall von Array zu Zeiger?

384

Was ist der Zerfall von Array zu Zeiger? Gibt es eine Beziehung zu Array-Zeigern?

Vamsi
quelle
73
wenig bekannt: Der unäre Plus-Operator kann als "Zerfallsoperator" verwendet werden: Gegeben int a[10]; int b(void);, dann +aist ein int-Zeiger und +bein Funktionszeiger. Nützlich, wenn Sie es an eine Vorlage übergeben möchten, die eine Referenz akzeptiert.
Johannes Schaub - Litb
3
@litb - parens würde dasselbe tun (z. B. (a) sollte ein Ausdruck sein, der als Zeiger ausgewertet wird), oder?.
Michael Burr
21
std::decayab C ++ 14 wäre eine weniger obskure Möglichkeit, ein Array über unary + zu zerlegen.
Legends2k
21
@ JohannesSchaub-litb da diese Frage sowohl C und C ++ markiert ist, würde ich das klarstellen , obwohl +aund +bin C ++ legal ist, ist es in C (C11 6.5.3.3/1 „Der Operand des unären illegal ist +oder -Betreiber hat hat arithmetischer Typ ")
MM
5
@lege Richtig. Aber ich nehme an, das ist nicht so wenig bekannt wie der Trick mit unary +. Der Grund, warum ich es erwähnte, war nicht nur, weil es verfällt, sondern weil es Spaß macht, damit zu spielen;)
Johannes Schaub - litb

Antworten:

283

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

Wenn Sie ein Array als 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 auf den Typ des Array-Elements sein sollte). Dies funktioniert aufgrund der verfallenden Natur des Arrays. Einmal verfallen, sizeofgibt es nicht mehr die Größe des gesamten Arrays an, 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 1 zu übergeben :

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 sizeofInformationen, die erste nicht, da das Array-Argument verfallen ist, um dem Parameter zugewiesen zu werden.

1 Die Konstante U sollte zur Kompilierungszeit bekannt sein.

Phoebus
quelle
8
Wie geht der erste Wert vorbei?
Rlbond
10
by_value übergibt einen Zeiger auf das erste Element des Arrays. im Kontext von Funktionsparametern T a[]ist identisch mit T *a. by_pointer übergibt dasselbe, außer dass der Zeigerwert jetzt qualifiziert ist const. Wenn Sie einen Zeiger auf das Array übergeben möchten (im Gegensatz zu einem Zeiger auf das erste Element des Arrays), lautet die Syntax T (*array)[U].
John Bode
4
"mit einem expliziten Zeiger auf dieses Array" - das ist falsch. Wenn aes sich um ein Array von handelt char, aist es vom Typ char[N]und zerfällt in char*; aber &aist vom Typ char(*)[N]und wird nicht verfallen.
Pavel Minaev
5
@FredOverflow: Wenn USie also Änderungen vornehmen müssen, müssen Sie nicht daran denken, sie an zwei Stellen zu ändern, oder riskieren Sie stille Fehler ... Autonomie!
Leichtigkeitsrennen im Orbit
4
"Wenn Sie ein Array nach Wert übergeben, kopieren Sie wirklich einen Zeiger." Das macht keinen Sinn, da Arrays nicht nach Wert übergeben werden können, Punkt.
Juanchopanza
103

Arrays sind im Grunde die gleichen wie Zeiger in C / C ++, aber nicht ganz. Sobald Sie ein Array konvertiert haben:

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

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

const int* p = a;

Sie verlieren die Fähigkeit des sizeofOperators, 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 zum Array-Zerfall .

System PAUSE
quelle
51
Arrays sind im Grunde nicht dasselbe wie Zeiger. Sie sind ganz andere Tiere. In den meisten Kontexten kann ein Array so behandelt werden, als wäre es ein Zeiger, und ein Zeiger kann so behandelt werden, als wäre es ein Array, aber das ist so nah wie möglich.
John Bode
20
@ John, bitte verzeihen Sie meine ungenaue Sprache. Ich habe versucht, die Antwort zu finden, ohne mich in einer langen Hintergrundgeschichte festzumachen, und "im Grunde ... aber nicht ganz" ist eine so gute Erklärung wie nie zuvor auf dem College. Ich bin sicher, dass jeder, der interessiert ist, ein genaueres Bild von Ihrem positiv bewerteten Kommentar erhalten kann.
System PAUSE
"funktioniert ohne Casting" bedeutet dasselbe wie "implizit passieren", wenn es um Typkonvertierungen geht
MM
47

Der Standard sagt Folgendes (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 Operators unary & handelt oder um ein Zeichenfolgenliteral, das zum Initialisieren eines Arrays verwendet wird, wird ein Ausdruck mit dem Typ '' Array vom Typ '' in einen Ausdruck mit dem Zeiger vom Typ '' konvertiert Geben Sie '' ein, das auf das Anfangselement des Array-Objekts zeigt und kein l-Wert ist.

Dies bedeutet, dass der Array-Name so gut wie immer, wenn er in einem Ausdruck verwendet wird, automatisch in einen Zeiger auf das erste Element im Array konvertiert wird.

Beachten Sie, dass Funktionsnamen auf ähnliche Weise funktionieren, Funktionszeiger jedoch weitaus weniger und viel spezieller verwendet werden, da dies nicht annähernd so verwirrend ist wie die automatische Konvertierung von Array-Namen in Zeiger.

Der C ++ - Standard (4.2 Array-zu-Zeiger-Konvertierung) lockert die Konvertierungsanforderung auf (Hervorhebung von mir):

Ein l-Wert oder r-Wert vom Typ "Array von NT" oder "Array von unbekannter Grenze von T" kann in einen r-Wert vom Typ "Zeiger auf T" konvertiert werden.

Die Konvertierung also nicht haben passieren , wie es so ziemlich immer in C tut ( auf diese Weise können Funktionen Überlastung oder Vorlagen auf dem Array - Typ übereinstimmen).

Dies ist auch der Grund, warum Sie in C die Verwendung von Array-Parametern in Funktionsprototypen / -definitionen vermeiden sollten (meiner Meinung nach - ich bin mir nicht sicher, ob es eine allgemeine Übereinstimmung gibt). Sie verursachen Verwirrung und sind sowieso eine Fiktion - verwenden Sie Zeigerparameter und die Verwirrung verschwindet möglicherweise nicht vollständig, aber zumindest lügt die Parameterdeklaration nicht.

Michael Burr
quelle
2
Was ist eine Beispielcodezeile, in der ein "Ausdruck mit dem Typ 'Array vom Typ'" "ein Zeichenfolgenliteral ist, das zum Initialisieren eines Arrays verwendet wird"?
Garrett
4
@ Garrett char x[] = "Hello";. Das Array von 6 Elementen "Hello"zerfällt nicht; stattdessen xerhält Größe 6und seine Elemente werden aus den Elementen von initialisiert "Hello".
MM
30

"Zerfall" bezieht sich auf die implizite Konvertierung eines Ausdrucks von einem Array-Typ in einen Zeigertyp. In den meisten Kontexten konvertiert der Compiler, wenn er einen Array-Ausdruck sieht, 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 . Ausnahmen von dieser Regel sind, wenn ein Array ein Operand des sizeofoder ist& Operatoren oder das Array ein Stringliteral als Initialisierer in einer Erklärung verwendet wird.

Nehmen Sie den folgenden Code an:

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

Der Ausdruck aist vom Typ "80-Element-Array von char" und der Ausdruck "Dies ist ein Test" vom Typ "16-Element-Array von char" (in C; in C ++ sind String-Literale Arrays von const char). Beim Aufruf von strcpy()ist jedoch keiner der Ausdrücke ein Operand von sizeofoder &, sodass ihre Typen implizit in "Zeiger auf Zeichen" konvertiert werden und ihre Werte jeweils auf die Adresse des ersten Elements gesetzt werden. Was strcpy()empfängt, sind keine Arrays, sondern Zeiger, wie im Prototyp zu sehen:

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

Dies ist nicht dasselbe wie ein Array-Zeiger. Zum Beispiel:

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

Beide ptr_to_first_elementund ptr_to_arrayhaben den gleichen Wert ; die Basisadresse von a. Es handelt sich jedoch um unterschiedliche Typen, die wie unten gezeigt 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 wie a[i]folgt interpretiert wird *(a+i)(was nur funktioniert, wenn der Array-Typ in einen Zeigertyp konvertiert wird), also beide a[i]und ptr_to_first_element[i]dasselbe. Der Ausdruck (*ptr_to_array)[i]wird interpretiert als *(*a+i). 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 es bewerten a[i].

sizeof a == sizeof *ptr_to_array == 80

Wenn ein Array ein Operand von ist sizeof, 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.

John Bode
quelle
1
Ist nicht "This is a test" is of type "16-element array of char"ein "15-element array of char"? (Länge 14 + 1 für \ 0)
chux - Monica
16

Arrays in C haben keinen Wert.

Überall dort, wo der Wert eines Objekts erwartet wird, das Objekt jedoch ein Array ist, wird stattdessen die Adresse seines ersten Elements mit dem Typ verwendet pointer to (type of array elements) .

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. In C haben Arrays jedoch keinen Wert! So foowird stattdessen die Adresse des ersten Elements des Arrays.

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

Hat im obigen Vergleich arrkeinen Wert, so dass es ein Zeiger wird. Es wird ein Zeiger auf int. Dieser Zeiger kann mit der Variablen verglichen werden ip.

In der Array-Indizierungssyntax, die Sie gewohnt sind, wird der arr wieder in einen Zeiger zerfallen.

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

Ein Array zerfällt nur dann nicht in einen Zeiger, wenn es der Operand des Operators sizeof oder der Operator & (der Operator 'address of') oder ein Zeichenfolgenliteral ist, das zum Initialisieren eines Zeichenarrays verwendet wird.

pmg
quelle
5
"Arrays haben keinen Wert" - was soll das heißen? Natürlich haben Arrays einen Wert ... sie sind Objekte, Sie können Zeiger haben und in C ++ Verweise auf sie usw.
Pavel Minaev
2
Ich glaube streng genommen, dass "Wert" in C als die Interpretation der Bits eines Objekts gemäß einem Typ definiert ist. Es fällt mir schwer, mit einem Array-Typ eine nützliche Bedeutung herauszufinden. Stattdessen können Sie sagen, dass Sie in einen Zeiger konvertieren, dies interpretiert jedoch nicht den Inhalt des Arrays, sondern nur dessen Position. Was Sie erhalten, ist der Wert eines Zeigers (und es ist eine Adresse), nicht der Wert eines Arrays (dies wäre "die Folge von Werten der enthaltenen Elemente", wie sie in der Definition von "Zeichenfolge" verwendet werden). Das heißt, ich denke, es ist fair, "Wert des Arrays" zu sagen, wenn man den Zeiger meint, den man bekommt.
Johannes Schaub - Litb
Ich denke jedenfalls, dass es eine leichte Mehrdeutigkeit gibt: Wert eines Objekts und Wert eines Ausdrucks (wie in "rvalue"). Wenn ein Array-Ausdruck auf die letztere Weise interpretiert wird, hat er sicherlich einen Wert: Er ist derjenige, der sich aus dem Zerlegen in einen r-Wert ergibt, und ist der Zeigerausdruck. Aber wenn es auf die frühere Weise interpretiert wird, gibt es natürlich keine nützliche Bedeutung für ein Array-Objekt.
Johannes Schaub - Litb
1
+1 für die Phrase mit einem kleinen Fix; Für Arrays ist es nicht einmal ein Triplett, sondern nur ein Couplet [Ort, Typ]. Hatten Sie noch etwas anderes für den dritten Ort im Fall von Array im Sinn? Mir fällt nichts ein.
Legends2k
1
@ legends2k: Ich glaube, ich habe die dritte Position in Arrays verwendet, um zu vermeiden, dass sie zu einem Sonderfall werden, bei dem nur ein Couplet vorhanden ist. Vielleicht wäre [Ort, Typ, Leere ] besser gewesen.
pmg
8

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

Eigentlich ist es nur so, dass, wenn Sie irgendwo ein Array übergeben möchten, aber stattdessen der Zeiger übergeben wird (denn wer zum Teufel würde das gesamte Array für Sie übergeben), die Leute sagen, dass ein schlechtes Array in einen Zeiger zerfallen ist.

Michael Krelin - Hacker
quelle
Schön gesagt. Was wäre ein schönes Array, das nicht in einen Zeiger zerfällt oder dessen Zerfall verhindert wird? Können Sie ein Beispiel in C anführen? Vielen Dank.
Unheilig
@Unheilig, klar, man kann ein Array in struct vakuumieren und die struct übergeben.
Michael Krelin - Hacker
Ich bin mir nicht sicher, was du mit "Arbeit" meinst. Es ist nicht erlaubt, über das Array hinaus zuzugreifen, obwohl es wie erwartet funktioniert, wenn Sie erwarten, was wirklich passieren wird. Dieses Verhalten (obwohl wiederum offiziell undefiniert) bleibt erhalten.
Michael Krelin - Hacker
Zerfall tritt auch in vielen Situationen auf, in denen das Array nirgendwo passiert wird (wie in anderen Antworten beschrieben). Zum Beispiel a + 1.
MM
3

Array-Zerfall bedeutet, dass ein Array, wenn es als Parameter an eine Funktion übergeben wird, identisch mit einem Zeiger behandelt wird ("zerfällt zu").

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 zu den oben genannten.

Erstens geht beim Umgang mit mehrdimensionalen Arrays in C und C ++ nur die erste Dimension verloren. Dies liegt daran, dass Arrays zusammenhängend im Speicher angeordnet sind, sodass der Compiler alle bis auf die erste Dimension kennen muss, 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 die Anzahl der Elemente in einem Array zuverlässig abzurufen .

Josh Kelley
quelle
1
Zerfall tritt in vielen anderen Situationen auf, nicht nur beim Übergeben eines Arrays an eine Funktion.
MM
0

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

Somit:

  • Wenn du schreibst arr[idx], sagst du wirklich nur*(arr + idx) .
  • Funktionen nehmen Arrays nie wirklich als Parameter, sondern nur als Zeiger, selbst wenn Sie einen Array-Parameter angeben.

Art von Ausnahmen von dieser Regel:

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

Ich könnte so mutig sein zu glauben, dass es vier (4) Möglichkeiten gibt, ein Array als Funktionsargument zu übergeben. Auch hier ist der kurze, aber funktionierende Code für Ihre Durchsicht.

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

using namespace std;

// test data
// notice native array init with no copy aka "="
// not possible in C
 const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };

// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) { 
    // a pointer
    assert(array != nullptr); 
} ;

// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) { 
    // decayed to a pointer
    assert( array != nullptr ); 
}

// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
   // dealing with native pointer 
    assert( array != nullptr ); 
}

// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
    // array is not a pointer here
    // it is (almost) a container
    // most of the std:: lib algorithms 
    // do work on array reference, for example
    // range for requires std::begin() and std::end()
    // on the type passed as range to iterate over
    for (auto && elem : array )
    {
        cout << endl << elem ;
    }
}

int main()
{
     // ONE
     as_pointer(specimen);
     // TWO
     by_value_no_size(specimen);
     // THREE
     pointer_to_array(&specimen);
     // FOUR
     reference_to_array( specimen ) ;
}

Ich könnte auch denken, dass dies die Überlegenheit von C ++ gegenüber C zeigt. Zumindest in Bezug auf (Wortspiel beabsichtigt), ein Array als Referenz zu übergeben.

Natürlich gibt es extrem strenge Projekte ohne Heap-Zuordnung, ohne Ausnahmen und ohne std :: lib. Man könnte sagen, dass die native C ++ - Array-Behandlung eine geschäftskritische Sprachfunktion ist.

Chef Gladiator
quelle