¿Qué es la descomposición de matriz a puntero? ¿Hay alguna relación con los punteros de matriz?
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; numbers
decaer 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, sizeof
ya 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 sizeof
informació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.
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]
.
a
es una matriz de char
, entonces a
es de tipo char[N]
y decaerá a char*
; pero &a
es de tipo char(*)[N]
y no se descompondrá.
U
cambios no tienes que recordar cambiarlos en dos lugares, o arriesgarte a errores silenciosos ... ¡Autonomía!
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 .
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.
char x[] = "Hello";
. La matriz de 6 elementos "Hello"
no decae; en cambio x
obtiene tamaño 6
y sus elementos se inicializan a partir de los elementos de "Hello"
.
"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 a
es 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 sizeof
o &
, 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_element
y ptr_to_array
tienen 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.
"This is a test" is of type "16-element array of char"
un "15-element array of char"
? (longitud 14 + 1 para \ 0)
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, foo
obtiene 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, arr
no 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.
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.
a + 1
.
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 .
Así:
arr[idx]
, en realidad solo estás diciendo *(arr + idx)
.Tipo de excepciones a esta regla:
struct
.sizeof()
da el tamaño que ocupa la matriz, no el tamaño de un puntero.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.
int a[10]; int b(void);
, entonces+a
es un puntero int y+b
es un puntero de función. Útil si desea pasarlo a una plantilla aceptando una referencia.std::decay
de C ++ 14 sería una forma menos oscura de descomponer una matriz sobre unary +.+a
y+b
son 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 ")