O que é matriz decaindo?

O que está decaindo de um array? Existe alguma relação com pointers de array?

   

Dizem que os arrays “decaem” em pointers. Uma matriz C ++ declarada como int numbers [5] não pode ser apontada novamente, isto é, você não pode dizer numbers = 0x5a5aff23 . Mais importante, o termo decadência significa perda de tipo e dimensão; numbers decaem em int* perdendo a informação da dimensão (contagem 5) e o tipo não é mais int [5] . Veja aqui os casos em que a decadência não acontece .

Se você está passando uma matriz por valor, o que você está realmente fazendo é copiar um ponteiro – um ponteiro para o primeiro elemento da matriz é copiado para o parâmetro (cujo tipo também deve ser um ponteiro do tipo do elemento da matriz). Isso funciona devido à natureza decadente da matriz; uma vez decaído, sizeof não fornece mais o tamanho do array completo, porque ele se torna essencialmente um ponteiro. É por isso que é preferível (entre outras razões) passar por referência ou ponteiro.

Três maneiras de passar em uma matriz 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]) 

Os dois últimos darão o sizeof adequado das informações, enquanto o primeiro não fará, já que o argumento da matriz decaiu para ser atribuído ao parâmetro.

1 A constante U deve ser conhecida em tempo de compilation.

Arrays são basicamente os mesmos que pointers em C / C ++, mas não completamente. Depois de converter uma matriz:

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

em um ponteiro (que funciona sem conversão e, portanto, pode acontecer inesperadamente em alguns casos):

 const int* p = a; 

você perde a capacidade do operador sizeof contar elementos no array:

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

Esta habilidade perdida é referida como “decadência”.

Para mais detalhes, confira este artigo sobre o decaimento da matriz .

Aqui está o que a norma diz (C99 6.3.2.1/3 – Outros operandos – Lvalues, arrays e designadores de function):

Exceto quando é o operando do operador sizeof ou o unary & operator, ou é um literal de string usado para inicializar um array, uma expressão que tenha o tipo ” array of type ” é convertida em uma expressão com o tipo ” pointer to tipo ” que aponta para o elemento inicial do object da matriz e não é um lvalue.

Isso significa que praticamente sempre que o nome da matriz é usado em uma expressão, ele é convertido automaticamente em um ponteiro para o primeiro item da matriz.

Note que os nomes das funções agem de maneira semelhante, mas os pointers de function são usados ​​muito menos e de uma maneira muito mais especializada que não causa tanta confusão quanto a conversão automática de nomes de array em pointers.

O padrão C ++ (conversão de conversão de ponteiro para ponteiro 4.2) afrouxa o requisito de conversão para (ênfase minha):

Um lvalue ou rvalue do tipo “array de NT” ou “array de bound desconhecido de T” pode ser convertido em um rvalue do tipo “pointer to T.”

Portanto, a conversão não precisa acontecer como praticamente sempre acontece em C (isso permite que as funções sobrecarregem ou os modelos correspondam ao tipo de matriz).

É também por isso que, em C, você deve evitar usar parâmetros de matriz em protótipos / definições de function (na minha opinião – não tenho certeza se existe algum acordo geral). Eles causam confusão e são uma ficção de qualquer maneira – use parâmetros de ponteiro e a confusão pode não desaparecer completamente, mas pelo menos a declaração de parâmetro não está mentindo.

“Decay” refere-se à conversão implícita de uma expressão de um tipo de matriz para um tipo de ponteiro. Na maioria dos contextos, quando o compilador vê uma expressão de matriz, ele converte o tipo de expressão de “N-element array de T” para “pointer to T” e define o valor da expressão para o endereço do primeiro elemento da matriz. . As exceções a essa regra são quando uma matriz é um operando do sizeof ou & operadores, ou a matriz é um literal de seqüência de caracteres sendo usado como um inicializador em uma declaração.

Assuma o seguinte código:

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

A expressão a é do tipo “array de 80 elementos de char” e a expressão “This is a test” é do tipo “array de 16 elementos de char” (em C; em C ++ string literais são arrays de const char). No entanto, na chamada para strcpy() , nenhuma expressão é um operando de sizeof ou & , portanto, seus tipos são implicitamente convertidos para “ponteiro para caractere” e seus valores são definidos para o endereço do primeiro elemento em cada. O que strcpy() recebe não são arrays, mas pointers, como visto em seu protótipo:

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

Isso não é a mesma coisa que um ponteiro de matriz. Por exemplo:

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

Tanto ptr_to_first_element quanto ptr_to_array possuem o mesmo valor ; o endereço base de um. No entanto, eles são tipos diferentes e são tratados de forma diferente, conforme mostrado abaixo:

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

Lembre-se que a expressão a[i] é interpretada como *(a+i) (o que só funciona se o tipo de array é convertido para um tipo de ponteiro), então ambos a[i] e ptr_to_first_element[i] funcionam da mesma forma. A expressão (*ptr_to_array)[i] é interpretada como *(*a+i) . As expressões *ptr_to_array[i] e ptr_to_array[i] podem levar a avisos ou erros do compilador, dependendo do contexto; eles definitivamente farão a coisa errada se você estiver esperando que eles avaliem a[i] .

 sizeof a == sizeof *ptr_to_array == 80 

Novamente, quando uma matriz é um operando de sizeof , ela não é convertida em um tipo de ponteiro.

 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 é um ponteiro simples para char.

Arrays, em C, não têm valor.

Sempre que o valor de um object é esperado, mas o object é uma matriz, o endereço de seu primeiro elemento é usado, com o tipo pointer to (type of array elements) .

Em uma function, todos os parâmetros são passados ​​por valor (arrays não são exceção). Quando você passa um array em uma function, “decai em um ponteiro” (sic); quando você compara uma matriz com outra coisa, novamente “decai para um ponteiro” (sic); …

 void foo(int arr[]); 

Função foo espera o valor de uma matriz. Mas, em C, matrizes não têm valor! Então foo recebe o endereço do primeiro elemento da matriz.

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

Na comparação acima, arr não tem valor, então se torna um ponteiro. Torna-se um ponteiro para int. Esse ponteiro pode ser comparado com a variável ip .

Na syntax de indexação de array você está acostumado a ver, novamente, o arr é ‘decaído para um ponteiro’

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

As únicas vezes que um array não se decompõe em um ponteiro são quando ele é o operando do operador sizeof, ou o operador & (o operador ‘endereço de’), ou como um literal de string usado para inicializar um array de caracteres.

É quando o array apodrece e está sendo apontado 😉

Na verdade, é só que se você quiser passar uma matriz em algum lugar, mas o ponteiro é passado (porque quem diabos passaria toda a matriz para você), as pessoas dizem que a matriz pobre decaiu para ponteiro.

Array decaying significa que, quando uma matriz é passada como um parâmetro para uma function, ela é tratada de forma idêntica a um ponteiro (“decays to”).

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

Existem duas complicações ou exceções ao acima.

Primeiro, ao lidar com matrizes multidimensionais em C e C ++, apenas a primeira dimensão é perdida. Isso ocorre porque as matrizes são colocadas contiguamente na memory, portanto, o compilador deve saber tudo, mas a primeira dimensão para poder calcular deslocamentos nesse bloco de memory.

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

Em segundo lugar, em C ++, você pode usar modelos para deduzir o tamanho dos arrays. A Microsoft usa isso para as versões C ++ de funções Secure CRT, como strcpy_s , e você pode usar um truque semelhante para obter com segurança o número de elementos em uma matriz .

tl: dr: Quando você usa um array que você define, você estará usando um ponteiro para o primeiro elemento.

Portanto:

  • Quando você escreve arr[idx] você está apenas dizendo *(arr + idx) .
  • funções nunca realmente usam arrays como parâmetros, apenas pointers, mesmo quando você especifica um parâmetro de array.

Excepções de tipo a esta regra:

  • Você pode passar matrizes de comprimento fixo para funções dentro de uma struct .
  • sizeof() fornece o tamanho da matriz, não o tamanho de um ponteiro.