O que é matriz para decaimento do ponteiro?

384

O que é matriz para decaimento do ponteiro? Existe alguma relação com ponteiros de matriz?

Vamsi
fonte
73
pouco conhecido: O operador unary plus pode ser usado como um "operador de decaimento": Dado int a[10]; int b(void);, então +aé um ponteiro int e +bé um ponteiro de função. Útil se você deseja passá-lo para um modelo que aceita uma referência.
Johannes Schaub - litb 22/09/09
3
@litb - parens faria o mesmo (por exemplo, (a) deve ser uma expressão avaliada como um ponteiro), certo?
Michael Burr
21
std::decaydo C ++ 14 seria uma maneira menos obscura de deteriorar uma matriz sobre o + unário.
legends2k
21
@ JohannesSchaub-litb uma vez que esta questão é marcado C e C ++, eu gostaria de esclarecer que, embora +ae +bsão legais em C ++, é ilegal em C (C11 6.5.3.3/1 "O operando da unário +ou -operador deve ter tipo aritmético ")
MM
5
@lege Right. Mas suponho que isso não seja tão pouco conhecido quanto o truque do + unário. A razão pela qual eu mencionei não foi apenas porque deteriora, mas porque é algo divertido de se brincar;)
Johannes Schaub - litb

Respostas:

283

Dizem que matrizes "decaem" em ponteiros. Uma matriz C ++ declarada como int numbers [5]não pode ser apontada novamente, ou seja, você não pode dizer numbers = 0x5a5aff23. Mais importante ainda, o termo decadência significa perda de tipo e dimensão; numbersdecaia int*perdendo as informações da dimensão (contagem 5) e o tipo não existe int [5]mais. Veja aqui os casos em que a deterioração não acontece .

Se você estiver passando uma matriz por valor, o que realmente está 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 para o tipo de elemento da matriz). Isso funciona devido à natureza em decomposição da matriz; uma vez deteriorado, sizeofnão fornece mais o tamanho completo da matriz, porque se torna essencialmente um ponteiro. É por isso que é preferível (entre outros motivos) 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 fornecerão sizeofinformações apropriadas , enquanto o primeiro não fornecerá , desde que o argumento do array tenha se deteriorado para ser atribuído ao parâmetro.

1 A constante U deve ser conhecida em tempo de compilação.

phoebus
fonte
8
Como é a primeira passagem por valor?
rlbond 22/09/09
10
by_value está passando um ponteiro para o primeiro elemento da matriz; no contexto dos parâmetros de função, T a[]é idêntico a T *a. by_pointer está passando a mesma coisa, exceto que o valor do ponteiro agora está qualificado const. Se você deseja passar um ponteiro para a matriz (em oposição a um ponteiro para o primeiro elemento da matriz), a sintaxe é T (*array)[U].
John Bode
4
"com um ponteiro explícito para essa matriz" - isso está incorreto. Se aé uma matriz de char, então aé do tipo char[N]e decairá para char*; mas &aé do tipo char(*)[N]e não se deteriora.
Pavel Minaev 22/09/09
5
@FredOverflow: Então, se as Ualterações não forem necessárias, lembre-se de alterá-las em dois lugares ou corra o risco de erros silenciosos ... Autonomia!
Corridas de leveza em órbita
4
"Se você está passando uma matriz por valor, o que realmente está fazendo é copiar um ponteiro" Isso não faz sentido, porque as matrizes não podem ser passadas por valor, ponto final.
Juanchopanza # 20/15
103

Matrizes são basicamente os mesmos que ponteiros em C / C ++, mas não exatamente. Depois de converter uma matriz:

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

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

const int* p = a;

você perde a capacidade do sizeofoperador de contar elementos na matriz:

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

Essa habilidade perdida é chamada de "deterioração".

Para mais detalhes, consulte este artigo sobre decaimento de matriz .

PAUSE do sistema
fonte
51
Matrizes não são basicamente iguais a ponteiros; eles são animais completamente diferentes. Na maioria dos contextos, uma matriz pode ser tratada como se fosse um ponteiro, e um ponteiro pode ser tratado como se fosse uma matriz, mas isso é o mais próximo possível.
John Bode
20
@ John, por favor, perdoe minha linguagem imprecisa. Eu estava tentando chegar à resposta sem ficar atolado em uma longa história de fundo, e "basicamente ... mas não exatamente" é uma explicação tão boa quanto eu já tive na faculdade. Tenho certeza de que qualquer pessoa interessada pode obter uma imagem mais precisa do seu comentário votado.
system PAUSE
"obras sem vazamento" significa o mesmo que "acontecem implicitamente" ao falar sobre conversões de tipo
MM
47

Aqui está o que o padrão diz (C99 6.3.2.1/3 - Outros operandos - Lvalues, matrizes e designadores de função):

Exceto quando é o operando do operador sizeof ou o operador unary &, ou é uma cadeia de caracteres literal usada para inicializar uma matriz, uma expressão que tem o tipo '' matriz do tipo '' é convertida em uma expressão com o tipo '' ponteiro para digite '' que aponta para o elemento inicial do objeto da matriz e não é um lvalue.

Isso significa que praticamente sempre que o nome do array é usado em uma expressão, ele é automaticamente convertido em um ponteiro para o 1º item do array.

Observe que os nomes das funções agem de maneira semelhante, mas os ponteiros de função 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 matriz em ponteiros.

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

Um lvalue ou rvalue do tipo "array of NT" ou "array de limite desconhecido de T" pode ser convertido em um rvalue do tipo "apontador para T."

Portanto, a conversão não precisa acontecer como acontece quase sempre em C (isso permite sobrecarga de funções ou modelos correspondentes no tipo de matriz).

É também por isso que em C você deve evitar o uso de parâmetros de matriz em protótipos / definições de funções (na minha opinião - não tenho certeza se há 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.

Michael Burr
fonte
2
O que é um exemplo de linha de código em que uma "expressão que tem o tipo 'matriz do tipo'" é "uma literal de seqüência de caracteres usada para inicializar uma matriz"?
Garrett
4
@Garrett char x[] = "Hello";. A matriz de 6 elementos "Hello"não decai; em vez disso, xobtém tamanho 6e seus elementos são inicializados a partir dos elementos de "Hello".
MM
30

"Decaimento" 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 da expressão de "matriz do elemento N de T" em "ponteiro para T" e define o valor da expressão para o endereço do primeiro elemento da matriz . As excepções a esta regra são quando uma matriz é um operando de ambos os sizeofou &operadores, ou a matriz é uma string literal sendo usado como um inicializador em uma declaração.

Suponha o seguinte código:

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

A expressão aé do tipo "matriz de 80 elementos do caractere" e a expressão "Este é um teste" é do tipo "matriz de 16 elementos do caractere" (em C; na string C ++, literais são matrizes de const char). No entanto, na chamada para strcpy(), nenhuma expressão é um operando de sizeofou &, portanto, seus tipos são implicitamente convertidos em "ponteiro para char" e seus valores são definidos no endereço do primeiro elemento em cada um. O que strcpy()recebe não são matrizes, mas ponteiros, 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;

Ambos ptr_to_first_elemente ptr_to_arraytêm o mesmo valor ; o endereço base de a. No entanto, são tipos diferentes e são tratados de maneira diferente, conforme mostrado abaixo:

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

Recordar que a expressão a[i]é interpretada como *(a+i)(que só funciona se o tipo de matriz é convertido para um tipo de ponteiro), de modo que ambos a[i]e ptr_to_first_element[i]trabalho a mesma. 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ê espera que eles avaliem a[i].

sizeof a == sizeof *ptr_to_array == 80

Novamente, quando uma matriz é um operando de sizeof, 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.

John Bode
fonte
11
Não é "This is a test" is of type "16-element array of char"um "15-element array of char"? (comprimento 14 + 1 para \ 0)
chux - Reinstala Monica 7/13
16

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

Onde quer que o valor de um objeto seja esperado, mas o objeto seja uma matriz, o endereço do seu primeiro elemento será usado com type pointer to (type of array elements).

Em uma função, todos os parâmetros são passados ​​por valor (matrizes não são exceção). Quando você passa um array em uma função, "decai em um ponteiro" (sic); quando você compara uma matriz com outra coisa, ela "decai em um ponteiro" (sic); ...

void foo(int arr[]);

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

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

Na comparação acima, arrnã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 sintaxe de indexação de array que você está acostumado a ver, novamente, o arr é 'deteriorado para um ponteiro'

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

As únicas vezes em que uma matriz não se decompõe em um ponteiro é quando é o operando do operador sizeof, ou o operador & (o operador 'address of'), ou como uma string literal usada para inicializar uma matriz de caracteres.

pmg
fonte
5
"Matrizes não têm valor" - o que isso significa? De matrizes curso tem valor ... eles são objetos, você pode ter ponteiros, e, em C ++, as referências a eles, etc.
Pavel Minaev
2
Creio que, estritamente, "Valor" é definido em C como a interpretação dos bits de um objeto de acordo com um tipo. Eu tenho dificuldade em descobrir um significado útil disso com um tipo de matriz. Em vez disso, você pode dizer que converte em um ponteiro, mas isso não está interpretando o conteúdo da matriz, apenas obtém sua localização. O que você obtém é o valor de um ponteiro (e é um endereço), não o valor de uma matriz (isso seria "a sequência de valores dos itens contidos", conforme usado na definição de "string"). Dito isto, acho justo dizer "valor da matriz" quando se quer dizer o ponteiro que se recebe.
Johannes Schaub - litb 22/09/09
de qualquer forma, acho que há uma pequena ambiguidade: valor de um objeto e valor de uma expressão (como em "rvalue"). Se interpretada da última maneira, uma expressão de matriz certamente tem um valor: é a resultante da decomposição em um rvalor e é a expressão do ponteiro. Mas se interpretado da maneira anterior, é claro que não há significado útil para um objeto de matriz.
Johannes Schaub - litb 22/09/09
11
+1 para a frase com uma pequena correção; para matrizes, nem sequer é um trigêmeo, apenas um dístico [local, tipo]. Você tinha outra coisa em mente para o terceiro local no caso da matriz? Não consigo pensar em nada.
legends2k
11
@ legends2k: Acho que usei o terceiro local em matrizes para evitar torná-los um caso especial de ter apenas um dístico. Talvez [localização, tipo, vazio ] tivesse sido melhor.
pmg
8

É quando o array apodrece e está sendo apontado para ;-)

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

Michael Krelin - hacker
fonte
Bem dito. O que seria uma boa matriz que não decaia para um ponteiro ou uma que é impedida de decair? Você pode citar um exemplo em C? Obrigado.
Unheilig
@ Unheilig, com certeza, pode-se compactar uma matriz no struct e passar a estrutura.
Michael Krelin - hacker de
Não tenho certeza do que você quer dizer com "trabalho". Não é permitido acessar além da matriz, embora funcione conforme o esperado, se você espera o que realmente deve acontecer. Esse comportamento (embora, novamente, oficialmente indefinido) é preservado.
Michael Krelin - hacker de
A deterioração também ocorre em muitas situações que não passam a matriz em nenhum lugar (conforme descrito por outras respostas). Por exemplo a + 1,.
MM
3

A deterioração da matriz significa que, quando uma matriz é passada como parâmetro para uma função, ela é tratada de forma idêntica a ("decai para") um ponteiro.

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 dispostas de forma contígua na memória; portanto, o compilador deve conhecer tudo, exceto a primeira dimensão, para poder calcular as compensações nesse bloco de memória.

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

Segundo, em C ++, você pode usar modelos para deduzir o tamanho das matrizes. A Microsoft usa isso para as versões C ++ das 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 .

Josh Kelley
fonte
11
decaimento acontece em muitas outras situações, não apenas passando uma matriz para uma função.
MM
0

tl; dr: Quando você usa uma matriz que você define, na verdade você usa um ponteiro para o primeiro elemento.

Portanto:

  • Quando você escreve, arr[idx]está realmente apenas dizendo *(arr + idx).
  • As funções nunca recebem matrizes como parâmetros, apenas ponteiros, mesmo quando você especifica um parâmetro de matriz.

Exceções de tipo a esta regra:

  • Você pode passar matrizes de comprimento fixo para funções dentro de a struct.
  • sizeof() fornece o tamanho ocupado pela matriz, não o tamanho de um ponteiro.
einpoklum
fonte
0

Eu posso ser tão ousado ao pensar que existem quatro (4) maneiras de passar uma matriz como argumento de função. Também aqui está o código curto, mas funcional, para sua leitura.

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

Eu também acho que isso mostra a superioridade de C ++ vs C. Pelo menos em referência (trocadilhos) de passar uma matriz por referência.

É claro que existem projetos extremamente rígidos, sem alocação de heap, sem exceções e sem std :: lib. O tratamento de array nativo em C ++ é um recurso de linguagem de missão crítica, pode-se dizer.

Chef Gladiador
fonte