¿Qué es la descomposición de matriz a puntero?

384

¿Qué es la descomposición de matriz a puntero? ¿Hay alguna relación con los punteros de matriz?

Vamsi
fuente
73
poco conocido: el operador unario más se puede usar como un "operador de desintegración": dado int a[10]; int b(void);, entonces +aes un puntero int y +bes un puntero de función. Útil si desea pasarlo a una plantilla aceptando una referencia.
Johannes Schaub - litb
3
@litb - parens haría lo mismo (por ejemplo, (a) debería ser una expresión que se evalúe como un puntero), ¿no ?.
Michael Burr
21
std::decayde C ++ 14 sería una forma menos oscura de descomponer una matriz sobre unary +.
legends2k
21
@ JohannesSchaub-litb ya que esta cuestión se marca tanto C como C ++, me gustaría aclarar que, si bien +ay +bson legales en C ++, es ilegal en C (C11 6.5.3.3/1 "El operando de la unario +o -del operador quedarán tipo aritmético ")
MM
55
@lege Derecha. Pero supongo que no es tan poco conocido como el truco con unary +. La razón por la que lo mencioné no fue simplemente porque se descompone sino porque es algo divertido para jugar;)
Johannes Schaub - litb

Respuestas:

283

Se dice que las matrices "decaen" en punteros. Una matriz C ++ declarada como int numbers [5]no se puede volver a señalar, es decir, no se puede decir numbers = 0x5a5aff23. Más importante aún, el término decaimiento significa pérdida de tipo y dimensión; numbersdecaer al int*perder la información de la dimensión (cuenta 5) y el tipo ya no int [5]existe. Mira aquí para los casos en que la descomposición no ocurre .

Si pasa una matriz por valor, lo que realmente está haciendo es copiar un puntero: un puntero al primer elemento de la matriz se copia en el parámetro (cuyo tipo también debe ser un puntero del tipo del elemento de matriz). Esto funciona debido a la naturaleza decadente de la matriz; una vez decaído, sizeofya no da el tamaño completo de la matriz, porque esencialmente se convierte en un puntero. Es por eso que se prefiere (entre otras razones) pasar por referencia o puntero.

Tres formas de pasar en una 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])

Los dos últimos proporcionarán la sizeofinformación adecuada , mientras que el primero no lo hará, ya que el argumento de matriz ha decaído para asignarse al parámetro.

1 La constante U debe conocerse en tiempo de compilación.

Febo
fuente
8
¿Cómo es el primer paso por valor?
rlbond
10
by_value pasa un puntero al primer elemento de la matriz; en el contexto de los parámetros de función, T a[]es idéntico a T *a. by_pointer está pasando lo mismo, excepto que el valor del puntero ahora está calificado const. Si desea pasar un puntero a la matriz (en lugar de un puntero al primer elemento de la matriz), la sintaxis es T (*array)[U].
John Bode
44
"con un puntero explícito a esa matriz" - esto es incorrecto. Si aes una matriz de char, entonces aes de tipo char[N]y decaerá a char*; pero &aes de tipo char(*)[N]y no se descompondrá.
Pavel Minaev
55
@FredOverflow: Entonces, si los Ucambios no tienes que recordar cambiarlos en dos lugares, o arriesgarte a errores silenciosos ... ¡Autonomía!
Carreras de ligereza en órbita
44
"Si está pasando una matriz por valor, lo que realmente está haciendo es copiar un puntero" Eso no tiene sentido, porque las matrices no se pueden pasar por valor, punto.
juanchopanza
103

Las matrices son básicamente lo mismo que los punteros en C / C ++, pero no del todo. Una vez que convierte una matriz:

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

en un puntero (que funciona sin conversión y, por lo tanto, puede suceder inesperadamente en algunos casos):

const int* p = a;

pierdes la habilidad del sizeof operador de contar elementos en la matriz:

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

Esta habilidad perdida se conoce como "decadencia".

Para obtener más detalles, consulte este artículo sobre la disminución de la matriz .

PAUSA del sistema
fuente
51
Las matrices no son básicamente lo mismo que los punteros; Son animales completamente diferentes. En la mayoría de los contextos, una matriz puede tratarse como si fuera un puntero, y un puntero puede tratarse como si fuera una matriz, pero eso es lo más cercano posible.
John Bode
20
@ John, por favor, perdona mi lenguaje impreciso. Estaba tratando de llegar a la respuesta sin atascarme en una larga historia de fondo, y "básicamente ... pero no del todo" es una explicación tan buena como la que obtuve en la universidad. Estoy seguro de que cualquiera que esté interesado puede obtener una imagen más precisa de su comentario votado.
PAUSE del sistema
"funciona sin conversión" significa lo mismo que "sucede implícitamente" cuando se habla de conversiones de tipos
MM
47

Esto es lo que dice el estándar (C99 6.3.2.1/3 - Otros operandos - Lvalues, matrices y designadores de funciones):

Excepto cuando es el operando del operador sizeof o el operador unario &, o es un literal de cadena utilizado para inicializar una matriz, una expresión que tiene el tipo '' matriz de tipo '' se convierte en una expresión con tipo '' puntero a escriba '' que apunta al elemento inicial del objeto de matriz y no es un valor l.

Esto significa que casi siempre que el nombre de la matriz se usa en una expresión, se convierte automáticamente en un puntero al primer elemento de la matriz.

Tenga en cuenta que los nombres de función actúan de manera similar, pero los punteros de función se usan mucho menos y de una manera mucho más especializada que no causa tanta confusión como la conversión automática de nombres de matriz a punteros.

El estándar C ++ (4.2 conversión de matriz a puntero) afloja el requisito de conversión a (énfasis mío):

Un valor o valor de tipo "matriz de NT" o "matriz de límite desconocido de T" se puede convertir en un valor de tipo "puntero a T".

Por lo tanto, la conversión no tiene que suceder como siempre ocurre en C (esto permite que las funciones se sobrecarguen o que las plantillas coincidan con el tipo de matriz).

Esta es también la razón por la cual en C debe evitar el uso de parámetros de matriz en prototipos / definiciones de funciones (en mi opinión, no estoy seguro de si hay algún acuerdo general). Causan confusión y de todos modos son una ficción: use parámetros de puntero y la confusión podría no desaparecer por completo, pero al menos la declaración de parámetros no está mintiendo.

Michael Burr
fuente
2
¿Qué es una línea de código de ejemplo donde una "expresión que tiene el tipo 'matriz de tipo'" es "un literal de cadena utilizado para inicializar una matriz"?
Garrett
44
@Garrett char x[] = "Hello";. La matriz de 6 elementos "Hello"no decae; en cambio xobtiene tamaño 6y sus elementos se inicializan a partir de los elementos de "Hello".
MM
30

"Decaimiento" se refiere a la conversión implícita de una expresión de un tipo de matriz a un tipo de puntero. En la mayoría de los contextos, cuando el compilador ve una expresión de matriz, convierte el tipo de expresión de "matriz de elementos N de T" a "puntero a T" y establece el valor de la expresión en la dirección del primer elemento de la matriz . Las excepciones a esta regla son cuando una matriz es un operando de cualquiera de los dossizeof o &los operadores, o la matriz es una cadena literal de ser utilizado como un inicializador en una declaración.

Asuma el siguiente código:

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

La expresión aes del tipo "matriz de char de 80 elementos" y la expresión "Esta es una prueba" es del tipo "matriz de char de 16 elementos" (en C; en C ++ los literales de cadena son matrices de const char). Sin embargo, en la llamada a strcpy(), ninguna expresión es un operando de sizeofo &, por lo que sus tipos se convierten implícitamente en "puntero a char", y sus valores se establecen en la dirección del primer elemento en cada uno. Lo que strcpy()recibe no son matrices, sino punteros, como se ve en su prototipo:

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

Esto no es lo mismo que un puntero de matriz. Por ejemplo:

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

Ambos ptr_to_first_elementy ptr_to_arraytienen el mismo valor ; la dirección base de a. Sin embargo, son diferentes tipos y se tratan de manera diferente, como se muestra a continuación:

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

Recuerde que la expresión a[i]se interpreta como *(a+i)(que sólo funciona si el tipo de matriz se convierte en un tipo de puntero), por lo tanto a[i]y ptr_to_first_element[i]funcionan de la misma. La expresión (*ptr_to_array)[i]se interpreta como *(*a+i). Las expresiones *ptr_to_array[i]y ptr_to_array[i]pueden dar lugar a advertencias o errores del compilador según el contexto; definitivamente harán lo incorrecto si espera que lo evalúen a[i].

sizeof a == sizeof *ptr_to_array == 80

Nuevamente, cuando una matriz es un operando de sizeof, no se convierte en un tipo de puntero.

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 es un simple puntero a char.

John Bode
fuente
1
No es "This is a test" is of type "16-element array of char"un "15-element array of char"? (longitud 14 + 1 para \ 0)
chux - Restablecer Monica
16

Las matrices, en C, no tienen valor.

Donde se espera el valor de un objeto, pero el objeto es una matriz, se utiliza la dirección de su primer elemento, con tipo pointer to (type of array elements).

En una función, todos los parámetros se pasan por valor (las matrices no son una excepción). Cuando pasa una matriz en una función, "se desintegra en un puntero" (sic); cuando compara una matriz con otra cosa, nuevamente "se desintegra en un puntero" (sic); ...

void foo(int arr[]);

La función foo espera el valor de una matriz. ¡Pero, en C, las matrices no tienen valor! Por lo tanto, fooobtiene la dirección del primer elemento de la matriz.

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

En la comparación anterior, arrno tiene valor, por lo que se convierte en un puntero. Se convierte en un puntero a int. Ese puntero se puede comparar con la variableip .

En la sintaxis de indexación de matriz que está acostumbrado a ver, nuevamente, el arr se 'decae a un puntero'

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

Las únicas veces que una matriz no se descompone en un puntero es cuando es el operando del operador sizeof, o el operador & (la 'dirección de' operador), o como un literal de cadena utilizado para inicializar una matriz de caracteres.

pmg
fuente
55
"Las matrices no tienen valor": ¿qué se supone que significa eso? Por supuesto, las matrices tienen valor ... son objetos, puede tener punteros y, en C ++, referencias a ellos, etc.
Pavel Minaev
2
Creo, estrictamente, que "Valor" se define en C como la interpretación de los bits de un objeto de acuerdo con un tipo. Me resulta difícil descubrir un significado útil de eso con un tipo de matriz. En cambio, puede decir que convierte a un puntero, pero eso no interpreta el contenido de la matriz, solo obtiene su ubicación. Lo que obtienes es el valor de un puntero (y es una dirección), no el valor de una matriz (esto sería "la secuencia de valores de los elementos contenidos", como se usa en la definición de "cadena"). Dicho esto, creo que es justo decir "valor de matriz" cuando uno significa el puntero que se obtiene.
Johannes Schaub - litb
de todos modos, creo que hay una ligera ambigüedad: el valor de un objeto y el valor de una expresión (como en "rvalue"). Si se interpreta de esta manera, entonces una expresión de matriz seguramente tiene un valor: es la resultante de la descomposición en un valor r, y es la expresión del puntero. Pero si se interpreta de la manera anterior, entonces, por supuesto, no hay un significado útil para un objeto de matriz.
Johannes Schaub - litb
1
+1 para la frase con una pequeña corrección; para las matrices, ni siquiera es un triplete, solo un pareado [ubicación, tipo]. ¿Tenía algo más en mente para la tercera ubicación en el caso de la matriz? No se me ocurre ninguno.
legends2k
1
@ legends2k: creo que utilicé la tercera ubicación en las matrices para evitar convertirlas en un caso especial de tener solo un pareado. Quizás [ubicación, tipo, vacío ] hubiera sido mejor.
pmg
8

Es cuando la matriz se pudre y se apunta a ;-)

En realidad, es solo que si quieres pasar una matriz a alguna parte, pero el puntero se pasa en su lugar (porque quién demonios pasaría toda la matriz por ti), la gente dice que la matriz deficiente decayó al puntero.

Michael Krelin - hacker
fuente
Bien dicho. ¿Cuál sería una buena matriz que no se descomponga en un puntero o que no se pudra? ¿Puedes citar un ejemplo en C? Gracias.
Unheilig
@Unheilig, seguro, uno puede empaquetar al vacío una matriz en struct y pasar la estructura.
Michael Krelin - hacker
No estoy seguro de qué quieres decir con "trabajo". No está permitido acceder más allá de la matriz, aunque funciona como se espera si espera lo que realmente sucederá. Ese comportamiento (aunque, nuevamente, oficialmente indefinido) se conserva.
Michael Krelin - hacker
La descomposición también ocurre en muchas situaciones que no pasan la matriz a ninguna parte (como se describe en otras respuestas). Por ejemplo, a + 1.
MM
3

La descomposición de la matriz significa que, cuando una matriz se pasa como un parámetro a una función, se trata de manera idéntica a ("decae a") un puntero.

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

Hay dos complicaciones o excepciones a lo anterior.

Primero, cuando se trata de matrices multidimensionales en C y C ++, solo se pierde la primera dimensión. Esto se debe a que las matrices se distribuyen de forma contigua en la memoria, por lo que el compilador debe conocer todo menos la primera dimensión para poder calcular las compensaciones en ese bloque de 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;
}

En segundo lugar, en C ++, puede usar plantillas para deducir el tamaño de las matrices. Microsoft usa esto para las versiones C ++ de las funciones de Secure CRT como strcpy_s , y puede usar un truco similar para obtener de manera confiable la cantidad de elementos en una matriz .

Josh Kelley
fuente
1
La descomposición ocurre en muchas otras situaciones, no solo pasando una matriz a una función.
MM
0

tl; dr: cuando usa una matriz que ha definido, en realidad usará un puntero a su primer elemento.

Así:

  • Cuando escribes arr[idx], en realidad solo estás diciendo *(arr + idx).
  • las funciones realmente nunca toman matrices como parámetros, solo punteros, incluso cuando especifica un parámetro de matriz.

Tipo de excepciones a esta regla:

  • Puede pasar matrices de longitud fija a funciones dentro de a struct.
  • sizeof() da el tamaño que ocupa la matriz, no el tamaño de un puntero.
einpoklum
fuente
0

Podría ser tan audaz para pensar que hay cuatro (4) formas de pasar una matriz como argumento de función. También aquí está el código corto pero funcional para su lectura.

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

También podría pensar que esto muestra la superioridad de C ++ frente a C. Al menos en referencia (juego de palabras) de pasar una matriz por referencia.

Por supuesto, hay proyectos extremadamente estrictos sin asignación de montón, sin excepciones y sin std :: lib. El manejo de la matriz nativa de C ++ es una función de lenguaje de misión crítica, se podría decir.

Chef Gladiador
fuente