pointer - ¿Son punteros idénticos a, & a,*a, a[0], & a[0] y & a[0][0]?
multidimensional array c++ (8)
Esto también significa que en C las matrices no tienen sobrecarga. En algunos otros idiomas, la estructura de las matrices es
&a --> overhead
more overhead
&a[0] --> element 0
element 1
element 2
...
y &a != &a[0]
Tengo el siguiente programa C:
#include <stdio.h>
int main(){
int a[2][2] = {1, 2, 3, 4};
printf("a:%p, &a:%p, *a:%p /n", a, &a, *a);
printf("a[0]:%p, &a[0]:%p /n", a[0], &a[0]);
printf("&a[0][0]:%p /n", &a[0][0]);
return 0;
}
Da el siguiente resultado:
a:0028FEAC, &a:0028FEAC, *a:0028FEAC
a[0]:0028FEAC, &a[0]:0028FEAC
&a[0][0]:0028FEAC
No puedo entender por qué son &a
, a
, *a
- todos idénticos. Lo mismo para a[0]
, &a[0]
y &a[0][0]
.
EDITAR:
Gracias a las respuestas, he entendido la razón por la cual estos valores están saliendo a ser iguales. Esta línea del libro de Kernighan & Ritchie resultó ser la clave de mi pregunta:
the name of an array is a synonym for the location of the initial element.
Entonces, con esto, obtenemos
a
= &a[0]
, y
a[0]
= &a[0][0]
(considerando a
como una matriz de matrices)
Intuitivamente, ahora la razón está clara detrás de la salida. Pero, teniendo en cuenta cómo se implementan los indicadores en C, no puedo entender cómo a
y &a
son iguales. Supongo que hay una variable a
en la memoria que apunta a la matriz (y la dirección de inicio de esta matriz-bloque de memoria sería el valor de esta variable a
).
Pero, cuando lo hacemos &a
, ¿no significa eso tomar la dirección de la ubicación de la memoria donde se almacenó la variable a
? ¿Por qué son estos valores iguales entonces?
Porque todas las expresiones apuntan al comienzo de la matriz:
a = {{a00},{a01},{a10},{a11}}
a
apunta a la matriz, solo porque es una matriz, entonces a == &a[0]
y &a[0][0]
está posicionado en la primera celda de la matriz 2D.
+------------------------------+
| a[0][0] <-- a[0] <-- a | // <--&a, a,*a, &a[0],&a[0][0]
|_a[0][1]_ |
| a[1][0] <-- a[1] |
| a[1][1] |
+------------------------------+
Sabes que a
es la dirección del primer elemento de tu matriz y de acuerdo con el estándar C, a[X]
es igual a *(a + X)
.
Asi que:
&a[0] == a
porque &a[0]
es lo mismo que &(*(a + 0))
= &(*a)
= a
.
&a[0][0] == a
porque &a[0][0]
es lo mismo que &(*(*(a + 0) + 0)))
= &(*a)
= a
Está imprimiendo los mismos valores porque todos apuntan a la misma ubicación.
Una vez dicho esto,
&a[i][i]
es del tipo int *
que es un puntero a un entero.
a
y &a[0]
tienen el tipo int(*)[2]
que indica un puntero a una matriz de 2
ints.
&a
tiene el tipo de int(*)[2][2]
que indica un puntero a una 2-D array
o un puntero a una matriz de dos elementos en la que cada elemento es una matriz de 2-ints.
Entonces, todos ellos son de diferente tipo y se comportan de manera diferente si comienzas a hacer aritmética de puntero sobre ellos.
(&a[0][1] + 1)
apunta al siguiente elemento entero en la matriz 2-D, es decir, a a[0][1]
&a[0] + 1
puntos a la siguiente matriz de enteros, es decir, a a[1][0]
&a + 1
puntos a la siguiente matriz 2-D que no existe en este caso, pero sería a[2][0]
si está presente.
Intuitivamente, ahora la razón está clara detrás de la salida. Pero, teniendo en cuenta cómo se implementan los indicadores en C, no puedo entender cómo a y a a son iguales. Supongo que hay una variable a en la memoria que apunta a la matriz (y la dirección de inicio de esta matriz-bloque de memoria sería el valor de esta variable a).
Bueno no. No existe una dirección almacenada en ningún lugar de la memoria. Solo hay memoria asignada para los datos brutos, y eso es todo. Lo que sucede es que cuando usa una a desnuda, inmediatamente se desintegra en un puntero al primer elemento, dando la impresión de que el ''valor'' de a
era la dirección, pero el único valor de a
es el almacenamiento en matriz sin procesar.
De hecho, a
y &a
son diferentes, pero solo en tipo, no en valor. Vamos a hacerlo un poco más fácil mediante el uso de matrices 1D para aclarar este punto:
bool foo(int (*a)[2]) { //a function expecting a pointer to an array of two elements
return (*a)[0] == (*a)[1]; //a pointer to an array needs to be dereferenced to access its elements
}
bool bar(int (*a)[3]); //a function expecting a pointer to an array of three elements
bool baz(int *a) { //a function expecting a pointer to an integer, which is typically used to access arrays.
return a[0] == a[1]; //this uses pointer arithmetic to access the elements
}
int z[2];
assert((size_t)z == (size_t)&z); //the value of both is the address of the first element.
foo(&z); //This works, we pass a pointer to an array of two elements.
//bar(&z); //Error, bar expects a pointer to an array of three elements.
//baz(&z); //Error, baz expects a pointer to an int
//foo(z); //Error, foo expects a pointer to an array
//bar(z); //Error, bar expects a pointer to an array
baz(z); //Ok, the name of an array easily decays into a pointer to its first element.
Como puede ver, a
yy &a
comportan de manera muy diferente, aunque comparten el mismo valor.
Una matriz 2D en C se trata como una matriz 1D cuyos elementos son matrices 1D (las filas).
Por ejemplo, una matriz de 4x3 de T
(donde "T" es un tipo de datos) puede ser declarada por: T a[4][3]
y descrita por el siguiente esquema:
+-----+-----+-----+
a == a[0] ---> | a00 | a01 | a02 |
+-----+-----+-----+
+-----+-----+-----+
a[1] ---> | a10 | a11 | a12 |
+-----+-----+-----+
+-----+-----+-----+
a[2] ---> | a20 | a21 | a22 |
+-----+-----+-----+
+-----+-----+-----+
a[3] ---> | a30 | a31 | a32 |
+-----+-----+-----+
Además, los elementos de la matriz se almacenan en la memoria fila tras fila.
Añadiendo la T
y agregando el [3]
a a
tenemos una matriz de 3
elementos de tipo T
Pero, el nombre a[4]
es en sí mismo una matriz que indica que hay 4
elementos, cada uno de los cuales es una matriz de 3
elementos. Por lo tanto, tenemos una matriz de 4
matrices de 3
elementos cada una.
Ahora está claro que a
apunta al primer elemento ( a[0]
) de a[4]
. Por otro lado, &a[0]
dará la dirección del primer elemento ( a[0]
) de a[4]
y &a[0][0]
dará la dirección de la 0th
fila ( a00 | a01 | a02)
de matriz a[4][3]
. &a
dará la dirección de la matriz 2D a[3][4]
. *a
decae a punteros a a[0][0]
.
Tenga en cuenta que a
no es un puntero a a[0][0]
; en cambio, es un puntero a a[0]
.
Por lo tanto
- G1:
a
y&a[0]
son equivalentes.- G2:
*a
,a[0]
y&a[0][0]
son equivalentes.- G3:
&a
(proporciona la dirección de la matriz 2Da[3][4]
).
Pero los grupos G1 , G2 y G3 no son idénticos aunque dan el mismo resultado (y expliqué anteriormente por qué da el mismo resultado).
No son punteros idénticos . Son punteros de distintos tipos que apuntan a la misma ubicación de memoria. Mismo valor (tipo de), diferentes tipos.
Una matriz bidimensional en C no es más que una matriz de matrices.
El objeto a
es de tipo int[2][2]
, o matriz de 2 elementos de matriz de 2 elementos de int
.
Cualquier expresión del tipo de matriz es, en la mayoría de los contextos, pero no en todos, implícitamente convertida en ("desintegra") un puntero al primer elemento del objeto de la matriz. Entonces la expresión a
, a menos que sea el operando de unary &
sizeof
, es de tipo int(*)[2]
, y es equivalente a &a[0]
(o &(a[0])
si eso es más claro). Se convierte en un puntero a la fila 0 de la matriz bidimensional. Es importante recordar que este es un valor de puntero (o equivalente a una dirección ), no un objeto de puntero; no hay ningún objeto puntero aquí a menos que usted cree explícitamente uno.
Entonces, observa las diversas expresiones sobre las que preguntaste:
-
&a
es la dirección de todo el objeto de la matriz; es una expresión de puntero de tipoint(*)[2][2]
. -
a
es el nombre de la matriz. Como se discutió anteriormente, se "desintegra" a un puntero al primer elemento (fila) del objeto de la matriz. Es una expresión de puntero de tipoint(*)[2]
. -
*a
desreferencia la expresión del punteroa
. Comoa
(después de que se descompone) es un puntero a una matriz de 2int
s,*a
es una matriz de 2int
s. Como se trata de un tipo de matriz, se descompone (en la mayoría, pero no en todos los contextos) en un puntero al primer elemento del objeto de la matriz. Entonces es de tipoint*
.*a
es equivalente a&a[0][0]
. -
&a[0]
es la dirección de la primera (0ª) fila del objeto de la matriz. Es de tipoint(*)[2]
.a[0]
es un objeto de matriz; no se descompone en un puntero porque es el operando directo de unary&
. -
&a[0][0]
es la dirección del elemento 0 de la fila 0 del objeto de la matriz. Es de tipoint*
.
Todas estas expresiones de puntero se refieren a la misma ubicación en la memoria. Esa ubicación es el comienzo del objeto de matriz a
; también es el comienzo del objeto de matriz a[0]
y del objeto int
a[0][0]
.
La forma correcta de imprimir un valor de puntero es usar el formato "%p"
y convertir el valor del puntero a void*
:
printf("&a = %p/n", (void*)&a);
printf("a = %p/n", (void*)a);
printf("*a = %p/n", (void*)*a);
/* and so forth */
Esta conversión a void*
produce una dirección "en bruto" que especifica solo una ubicación en la memoria, no el tipo de objeto en esa ubicación. Por lo tanto, si tiene varios punteros de diferentes tipos que apuntan a objetos que comienzan en la misma ubicación de memoria, al convertirlos a void*
obtiene el mismo valor.
(He pasado por alto el funcionamiento interno del operador de indexación []
. La expresión x[y]
es por definición equivalente a *(x+y)
, donde x
es un puntero (posiblemente el resultado de la conversión implícita de una matriz) ) y y
es un número entero. O viceversa, pero eso es feo: arr[0]
y 0[arr]
son equivalentes, pero eso es útil solo si estás escribiendo un código deliberadamente ofuscado. Si contamos con esa equivalencia, se necesita una un párrafo más o menos para describir lo que significa a[0][0]
, y esta respuesta probablemente ya sea demasiado larga.)
En aras de la exhaustividad, los tres contextos en los que una expresión del tipo de matriz no se convierte implícitamente en un puntero al primer elemento de la matriz son:
- Cuando es el operando de unary
&
, entonces&arr
arroja la dirección de todo el objeto de la matriz; - Cuando es el operando de
sizeof
,sizeof arr
produce el tamaño en bytes del objeto de matriz, no el tamaño de un puntero; y - Cuando se trata de un literal de cadena en un inicializador utilizado para inicializar una matriz (sub-) objeto, entonces
char s[6] = "hello";
copia el valor de la matriz ens
en lugar de inicializar sin sentido un objeto de matriz con un valor de puntero. Esta última excepción no se aplica al código que está preguntando.
(El borrador N1570 del estándar ISO C 2011 indica incorrectamente que _Alignof
es una cuarta excepción; esto es incorrecto, ya que _Alignof
solo se puede aplicar a un nombre de tipo entre paréntesis, no a una expresión. El error se corrige en el estándar C11 final. )
Lectura recomendada: Sección 6 de las preguntas frecuentes comp.lang.c.