Che cos'è il decadimento da array a puntatore?


384

Che cos'è il decadimento da array a puntatore? C'è qualche relazione con i puntatori di array?


73
poco noto: l'operatore unary plus può essere utilizzato come "operatore di decadimento": dato int a[10]; int b(void);, quindi +aè un puntatore int ed +bè un puntatore a funzione. Utile se si desidera passarlo a un modello che accetta un riferimento.
Johannes Schaub - litb,

3
@litb - parens farebbe lo stesso (es., (a) dovrebbe essere un'espressione che valuta un puntatore), giusto ?.
Michael Burr,

21
std::decayda C ++ 14 sarebbe un modo meno oscuro di decomporre un array su unario +.
legends2k

21
@ JohannesSchaub-litb poiché questa domanda è contrassegnata sia in C che in C ++, vorrei chiarire che sebbene +asia +blegale in C ++, è illegale in C (C11 6.5.3.3/1 "L'operando dell'unario +o -dell'operatore deve avere tipo aritmetico ")
MM

5
@lege Right. Ma suppongo che non sia così poco conosciuto come il trucco con unario +. Il motivo per cui l'ho menzionato non è solo perché decade ma perché è un po 'di cose divertenti con cui giocare;)
Johannes Schaub - litb

Risposte:


283

Si dice che le matrici "decadano" in puntatori. Un array C ++ dichiarato come int numbers [5]non può essere reindirizzato, cioè non si può dire numbers = 0x5a5aff23. Ancora più importante, il termine decadimento significa perdita di tipo e dimensione; numbersdecadono int*perdendo le informazioni sulla dimensione (conteggio 5) e il tipo non è int [5]più. Cerca qui i casi in cui il decadimento non si verifica .

Se si passa un array in base al valore, ciò che si sta realmente facendo è copiare un puntatore: un puntatore al primo elemento dell'array viene copiato nel parametro (il cui tipo dovrebbe essere anche un puntatore del tipo dell'elemento dell'array). Questo funziona a causa della natura in decomposizione dell'array; una volta decaduto, sizeofnon fornisce più le dimensioni complete dell'array, perché diventa essenzialmente un puntatore. Questo è il motivo per cui è preferito (tra le altre ragioni) passare per riferimento o puntatore.

Tre modi per passare in un array 1 :

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])

Gli ultimi due forniranno le sizeofinformazioni corrette , mentre il primo no poiché l'argomento array è decaduto per essere assegnato al parametro.

1 La costante U dovrebbe essere nota al momento della compilazione.


8
Come va il primo passaggio per valore?
rlbond

10
by_value sta passando un puntatore al primo elemento dell'array; nel contesto dei parametri di funzione, T a[]è identico a T *a. by_pointer sta passando la stessa cosa, tranne per il fatto che il valore del puntatore è ora qualificato const. Se si desidera passare un puntatore all'array (anziché un puntatore al primo elemento dell'array), la sintassi è T (*array)[U].
John Bode,

4
"con un puntatore esplicito a quell'array" - questo non è corretto. Se aè un array di char, allora aè di tipo char[N]e decadrà in char*; ma &aè di tipo char(*)[N]e non decadrà.
Pavel Minaev,

5
@FredOverflow: Quindi, se le Umodifiche non devono ricordarsi di modificarle in due punti o rischiare bug silenziosi ... Autonomia!
Razze di leggerezza in orbita

4
"Se stai passando un array per valore, quello che stai facendo è copiare un puntatore" Non ha senso, perché gli array non possono essere passati per valore, punto.
juanchopanza,

103

Le matrici sono sostanzialmente le stesse dei puntatori in C / C ++, ma non del tutto. Dopo aver convertito un array:

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

in un puntatore (che funziona senza casting, e quindi può accadere inaspettatamente in alcuni casi):

const int* p = a;

si perde la capacità sizeofdell'operatore di contare gli elementi nell'array:

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

Questa abilità perduta viene definita "decadimento".

Per maggiori dettagli, consulta questo articolo sul decadimento dell'array .


51
Le matrici non sono sostanzialmente le stesse dei puntatori; sono animali completamente diversi. Nella maggior parte dei contesti, un array può essere trattato come se fosse un puntatore e un puntatore può essere trattato come se fosse un array, ma è il più vicino possibile.
John Bode,

20
@Giovanni, ti prego di scusare il mio linguaggio impreciso. Stavo cercando di arrivare alla risposta senza impantanarmi in un lungo retroscena, e "in sostanza ... ma non del tutto" è una spiegazione valida come non sono mai stata al college. Sono sicuro che chiunque sia interessato può ottenere un'immagine più accurata dal tuo commento votato.
sistema PAUSA

"funziona senza casting" significa lo stesso di "succede implicitamente" quando si parla di conversioni di tipo
MM

47

Ecco cosa dice lo standard (C99 6.3.2.1/3 - Altri operandi - Valori, array e designatori di funzioni):

Tranne quando si tratta dell'operando dell'operatore sizeof o dell'operatore unario e, oppure è una stringa letterale utilizzata per inizializzare un array, un'espressione che ha il tipo '' array di tipo '' viene convertita in un'espressione con puntatore di tipo '' in digitare '' che punta all'elemento iniziale dell'oggetto array e non è un valore.

Ciò significa che praticamente ogni volta che il nome dell'array viene utilizzato in un'espressione, viene automaticamente convertito in un puntatore al primo elemento dell'array.

Si noti che i nomi delle funzioni agiscono in modo simile, ma i puntatori di funzione vengono utilizzati molto meno e in un modo molto più specializzato che non causa molta confusione quanto la conversione automatica dei nomi di array in puntatori.

Lo standard C ++ (4.2 conversione da array a puntatore) allenta il requisito di conversione a (enfatizzare il mio):

Un valore o un valore di tipo "matrice di NT" o "matrice di limite sconosciuto di T" può essere convertito in un valore di tipo "puntatore a T."

Quindi la conversione non deve avvenire come quasi sempre in C (questo consente il sovraccarico delle funzioni o la corrispondenza dei modelli sul tipo di array).

Questo è anche il motivo per cui in C dovresti evitare di usare i parametri dell'array nei prototipi / definizioni delle funzioni (secondo me - non sono sicuro che ci sia un accordo generale). Causano confusione e sono comunque una finzione: utilizzare i parametri del puntatore e la confusione potrebbe non scomparire del tutto, ma almeno la dichiarazione dei parametri non sta mentendo.


2
Che cos'è un esempio di riga di codice in cui "un'espressione che ha il tipo 'array di tipo'" è "una stringa letterale utilizzata per inizializzare un array"?
Garrett,

4
@Garrett char x[] = "Hello";. La matrice di 6 elementi "Hello"non decade; invece xottiene dimensione 6e i suoi elementi sono inizializzati dagli elementi di "Hello".
MM

30

"Decadimento" si riferisce alla conversione implicita di un'espressione da un tipo di array a un tipo di puntatore. Nella maggior parte dei contesti, quando il compilatore vede un'espressione di array converte il tipo di espressione da "array di elementi N di T" a "puntatore a T" e imposta il valore dell'espressione sull'indirizzo del primo elemento dell'array . Le eccezioni a questa regola sono quando una matrice è un operando dell'operatore sizeofo &, oppure la matrice è una stringa letterale utilizzata come inizializzatore in una dichiarazione.

Assumi il seguente codice:

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

L'espressione aè di tipo "array di caratteri a 80 elementi" e l'espressione "Questo è un test" è di tipo "array di caratteri a 16 elementi" (in C; in C ++ i valori letterali di stringa sono array di caratteri costanti). Tuttavia, nella chiamata a strcpy(), nessuna delle due espressioni è un operando di sizeofo &, quindi i loro tipi vengono implicitamente convertiti in "puntatore a carattere" e i loro valori sono impostati sull'indirizzo del primo elemento in ciascuno. Ciò che strcpy()riceve non sono matrici, ma puntatori, come si vede nel suo prototipo:

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

Questa non è la stessa cosa di un puntatore ad array. Per esempio:

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

Entrambi ptr_to_first_elemente ptr_to_arrayhanno lo stesso valore ; l'indirizzo di base di a. Tuttavia, sono tipi diversi e vengono trattati in modo diverso, come mostrato di seguito:

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

Ricordare che l'espressione a[i]viene interpretata come *(a+i)(che funziona solo se il tipo di matrice viene convertito in un tipo di puntatore), in modo da entrambi a[i]e ptr_to_first_element[i]di lavoro stesso. L'espressione (*ptr_to_array)[i]viene interpretata come *(*a+i). Le espressioni *ptr_to_array[i]e ptr_to_array[i]possono portare a avvisi o errori del compilatore a seconda del contesto; faranno sicuramente la cosa sbagliata se ti aspetti che valutino a[i].

sizeof a == sizeof *ptr_to_array == 80

Ancora una volta, quando un array è un operando di sizeof, non viene convertito in un tipo di puntatore.

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 è un semplice puntatore a char.


1
Non è "This is a test" is of type "16-element array of char"un "15-element array of char"? (lunghezza 14 + 1 per \ 0)
chux - Ripristina Monica il

16

Le matrici, in C, non hanno valore.

Ovunque sia previsto il valore di un oggetto ma l'oggetto è un array, al suo posto viene utilizzato l'indirizzo del suo primo elemento, con type pointer to (type of array elements).

In una funzione, tutti i parametri vengono passati per valore (le matrici non fanno eccezione). Quando si passa un array in una funzione, "decade in un puntatore" (sic); quando si confronta un array con qualcos'altro, di nuovo "decade in un puntatore" (sic); ...

void foo(int arr[]);

La funzione foo prevede il valore di un array. Ma, in C, le matrici non hanno valore! Quindi fooottiene invece l'indirizzo del primo elemento dell'array.

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

Nel confronto sopra, arrnon ha valore, quindi diventa un puntatore. Diventa un puntatore a int. Quel puntatore può essere confrontato con la variabile ip.

Nella sintassi di indicizzazione dell'array a cui sei abituato a vedere, di nuovo, arr è "decaduto in un puntatore"

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

Le uniche volte in cui un array non si decompone in un puntatore è quando è l'operando dell'operatore sizeof, o l'operatore & (l'operatore "indirizzo"), o come letterale stringa usato per inizializzare un array di caratteri.


5
"Le matrici non hanno valore" - cosa dovrebbe significare? Ovviamente le matrici hanno valore ... sono oggetti, puoi avere puntatori e, in C ++, riferimenti ad essi, ecc.
Pavel Minaev,

2
Credo, rigorosamente, che "Valore" sia definito in C come interpretazione dei bit di un oggetto secondo un tipo. Ho difficoltà a capire un significato utile di ciò con un tipo di array. Invece, puoi dire che ti converti in un puntatore, ma questo non sta interpretando il contenuto dell'array, ma ottiene solo la sua posizione. Quello che ottieni è il valore di un puntatore (ed è un indirizzo), non il valore di un array (questa sarebbe "la sequenza di valori degli elementi contenuti", come usato nella definizione di "stringa"). Detto questo, penso che sia giusto dire "valore dell'array" quando si intende il puntatore che si ottiene.
Johannes Schaub - litb,

comunque, penso che ci sia una leggera ambiguità: valore di un oggetto e valore di un'espressione (come in "rvalue"). Se interpretato in quest'ultimo modo, allora un'espressione di matrice ha sicuramente un valore: è quella risultante dal decadimento in un valore ed è l'espressione del puntatore. Ma se interpretato nel modo precedente, ovviamente non vi è alcun significato utile per un oggetto array.
Johannes Schaub - litb,

1
+1 per la frase con una piccola correzione; per le matrici non è nemmeno una tripletta, solo un distico [posizione, tipo]. Avevi in ​​mente qualcos'altro per la terza posizione nel caso dell'array? Non riesco a pensare a nessuno.
legends2k,

1
@ legends2k: penso di aver usato la terza posizione negli array per evitare di renderli un caso speciale di avere solo un couplet. Forse [location, type, void ] sarebbe stato meglio.
pm

8

È quando l'array marcisce e viene puntato verso ;-)

In realtà, è solo che se vuoi passare un array da qualche parte, ma il puntatore viene invece passato (perché chi diavolo avrebbe passato l'intero array per te), la gente dice che un array povero è decaduto in puntatore.


Ben detto. Quale sarebbe un array piacevole che non decade in un puntatore o in uno a cui è impedito il decadimento? Puoi citare un esempio in C? Grazie.
Unheilig,

@Unheilig, certo, si può sottovuotare un array in struct e passare la struct.
Michael Krelin - hacker

Non sono sicuro di cosa intendi per "lavoro". Non è consentito accedere oltre l'array, sebbene funzioni come previsto se ci si aspetta che cosa accada realmente. Tale comportamento (sebbene, ancora una volta, ufficialmente indefinito) viene preservato.
Michael Krelin - hacker

Il decadimento si verifica anche in molte situazioni che non passano l'array da nessuna parte (come descritto da altre risposte). Ad esempio a + 1,.
MM

3

Il decadimento della matrice significa che, quando una matrice viene passata come parametro a una funzione, viene trattata in modo identico a ("decade a") un puntatore.

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);
}

Esistono due complicazioni o eccezioni a quanto sopra.

Innanzitutto, quando si ha a che fare con array multidimensionali in C e C ++, si perde solo la prima dimensione. Questo perché le matrici sono disposte contigue nella memoria, quindi il compilatore deve conoscere tutto tranne la prima dimensione per poter calcolare gli offset in quel blocco di memoria.

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;
}

In secondo luogo, in C ++, è possibile utilizzare i modelli per dedurre la dimensione degli array. Microsoft lo utilizza per le versioni C ++ delle funzioni Secure CRT come strcpy_s e puoi usare un trucco simile per ottenere in modo affidabile il numero di elementi in un array .


1
il decadimento si verifica in molte altre situazioni, non solo passando un array a una funzione.
MM

0

tl; dr: quando usi un array che hai definito, in realtà utilizzerai un puntatore al suo primo elemento.

Così:

  • Quando scrivi arr[idx]dici davvero *(arr + idx).
  • le funzioni non accettano mai array come parametri, solo puntatori, anche quando si specifica un parametro array.

Sorta di eccezioni a questa regola:

  • È possibile passare matrici di lunghezza fissa alle funzioni all'interno di a struct.
  • sizeof() indica la dimensione occupata dall'array, non la dimensione di un puntatore.

0

Potrei essere così audace da pensare che ci siano quattro (4) modi per passare un array come argomento della funzione. Anche qui c'è il codice breve ma funzionante per la tua lettura.

#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 ) ;
}

Potrei anche pensare che ciò mostri la superiorità di C ++ rispetto a C. Almeno in riferimento (gioco di parole previsto) di passare un array per riferimento.

Naturalmente ci sono progetti estremamente severi senza allocazione di heap, senza eccezioni e senza std :: lib. La gestione degli array nativi in ​​C ++ è una caratteristica del linguaggio mission-critical, si potrebbe dire.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.