¿El nombre de una matriz es un puntero en C? Si no, ¿cuál es la diferencia entre el nombre de una matriz y una variable de puntero?
&array[0]
produce un puntero, no una matriz;)
¿El nombre de una matriz es un puntero en C? Si no, ¿cuál es la diferencia entre el nombre de una matriz y una variable de puntero?
&array[0]
produce un puntero, no una matriz;)
Respuestas:
Una matriz es una matriz y un puntero es un puntero, pero en la mayoría de los casos los nombres de matriz se convierten en punteros. Un término usado a menudo es que se descomponen en punteros.
Aquí hay una matriz:
int a[7];
a
contiene espacio para siete enteros, y puede poner un valor en uno de ellos con una asignación, como esta:
a[3] = 9;
Aquí hay un puntero:
int *p;
p
no contiene espacios para enteros, pero puede apuntar a un espacio para un entero. Podemos, por ejemplo, configurarlo para que apunte a uno de los lugares de la matriz a
, como el primero:
p = &a[0];
Lo que puede ser confuso es que también puedes escribir esto:
p = a;
Esto no copia el contenido de la matriz a
en el puntero p
(lo que sea que eso signifique). En cambio, el nombre de la matriza
se convierte en un puntero a su primer elemento. Entonces esa asignación hace lo mismo que la anterior.
Ahora puede usar p
de manera similar a una matriz:
p[3] = 17;
La razón por la que esto funciona es que el operador de desreferencia de matriz en C [ ]
, se define en términos de punteros. x[y]
significa: comience con el puntero x
, avance y
elementos después de lo que señala el puntero y luego tome lo que esté allí. Usando la sintaxis aritmética del puntero, x[y]
también se puede escribir como*(x+y)
.
Para que esto funcione con una gama normal, como nuestra a
, el nombre a
en a[3]
primer lugar debe ser convertido a un puntero (para el primer elemento dea
). Luego avanzamos 3 elementos hacia adelante y tomamos lo que está allí. En otras palabras: tome el elemento en la posición 3 de la matriz. (Cuál es el cuarto elemento en la matriz, ya que el primero está numerado 0.)
Entonces, en resumen, los nombres de matriz en un programa C se convierten (en la mayoría de los casos) en punteros. Una excepción es cuando usamos el sizeof
operador en una matriz. Si a
se convirtiera en un puntero en este contexto, sizeof a
daría el tamaño de un puntero y no de la matriz real, lo que sería bastante inútil, por lo que en ese caso a
significa la matriz misma.
functionpointer()
y (*functionpointer)()
significan lo mismo, curiosamente.
sizeof()
otro contexto en el que no hay matriz-> la caída del puntero es operador &
: en su ejemplo anterior, &a
será un puntero a una matriz de 7 int
, no un puntero a un solo int
; es decir, su tipo será int(*)[7]
, que no es implícitamente convertible a int*
. De esta forma, las funciones pueden llevar punteros a matrices de tamaño específico y aplicar la restricción a través del sistema de tipos.
Cuando se usa una matriz como valor, su nombre representa la dirección del primer elemento.
Cuando una matriz no se usa como valor, su nombre representa la matriz completa.
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
Si una expresión de tipo de matriz (como el nombre de la matriz) aparece en una expresión más grande y no es el operando de &
osizeof
operadores , entonces el tipo de la expresión de matriz se convierte de "matriz de elementos N de T" a "puntero a T", y el valor de la expresión es la dirección del primer elemento de la matriz.
En resumen, el nombre de la matriz no es un puntero, pero en la mayoría de los contextos se trata como si fuera un puntero.
Editar
Respondiendo la pregunta en el comentario:
Si uso sizeof, ¿cuento solo el tamaño de los elementos de la matriz? Entonces, la "cabeza" de la matriz también ocupa espacio con la información sobre la longitud y un puntero (y esto significa que ocupa más espacio que un puntero normal).
Cuando crea una matriz, el único espacio asignado es el espacio para los elementos mismos; no se materializa el almacenamiento para un puntero separado o cualquier metadato. Dado
char a[10];
lo que obtienes en la memoria es
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
La expresión se a
refiere a toda la matriz, pero no hay ningún objeto a
separado de los elementos de la matriz. Por lo tanto, sizeof a
le da el tamaño (en bytes) de toda la matriz. La expresión &a
le da la dirección de la matriz, que es la misma que la dirección del primer elemento . La diferencia entre &a
y &a[0]
es el tipo del resultado 1 - char (*)[10]
en el primer caso ychar *
en el segundo.
Donde las cosas se ponen raras es cuando quieres acceder a elementos individuales (la expresión a[i]
se define como el resultado de *(a + i)
) dado un valor de dirección a
, i
elementos de desplazamiento ( no bytes) ) de esa dirección y desreferenciar el resultado.
El problema es que a
no es un puntero o una dirección, es todo el objeto de matriz. Por lo tanto, la regla en C dice que cada vez que el compilador ve una expresión de tipo de matriz (como a
, que tiene tipo char [10]
) y esa expresión no es el operando de los operadores sizeof
unarios &
, el tipo de esa expresión se convierte ("decae") a un tipo de puntero ( char *
), y el valor de la expresión es la dirección del primer elemento de la matriz. Por lo tanto, la expresión a
tiene el mismo tipo y valor que la expresión &a[0]
(y, por extensión, la expresión *a
tiene el mismo tipo y valor que la expresióna[0]
).
C se derivó de un lenguaje anterior llamado B, y en B a
había un objeto puntero separado de los elementos de la matriz a[0]
,a[1]
etc. Ritchie quería mantener la semántica de la matriz B, pero que no quería meterse con el almacenamiento de objeto de puntero separada. Entonces se deshizo de él. En cambio, el compilador convertirá expresiones de matriz en expresiones de puntero durante la traducción, según sea necesario.
Recuerde que dije que las matrices no almacenan metadatos sobre su tamaño. Tan pronto como esa expresión de matriz "decae" a un puntero, todo lo que tiene es un puntero a un único elemento. Ese elemento puede ser el primero de una secuencia de elementos, o puede ser un solo objeto. No hay forma de saberlo basado en el puntero mismo.
Cuando pasa una expresión de matriz a una función, todo lo que recibe la función es un puntero al primer elemento; no tiene idea de cuán grande es la matriz (es por eso que la gets
función fue una amenaza y finalmente se eliminó de la biblioteca). Para que la función sepa cuántos elementos tiene la matriz, debe usar un valor centinela (como el terminador 0 en cadenas C) o debe pasar el número de elementos como un parámetro separado.
sizeof
es un operador y evalúa el número de bytes en el operando (ya sea una expresión que denota un objeto o un nombre de tipo entre paréntesis). Entonces, para una matriz, se sizeof
evalúa el número de elementos multiplicado por el número de bytes en un solo elemento. Si an int
tiene 4 bytes de ancho, entonces una matriz de 5 elementos int
ocupa 20 bytes.
[ ]
especial el operador también? Por ejemplo, int a[2][3];
entonces for x = a[1][2];
, aunque se puede reescribir como x = *( *(a+1) + 2 );
, aquí a
no se convierte a un tipo de puntero int*
(aunque si a
es un argumento de una función se debe convertir a int*
).
a
tiene tipo int [2][3]
, que "decae" al escribir int (*)[3]
. La expresión *(a + 1)
tiene tipo int [3]
, que "decae" a int *
. Por lo tanto, *(*(a + 1) + 2)
tendrá tipo int
. a
apunta a la primera matriz de 3 elementos de int
, a + 1
apunta a la segunda matriz de 3 elementos de int
, *(a + 1)
es la segunda matriz de 3 elementos de int
, *(a + 1) + 2
apunta al tercer elemento de la segunda matriz de int
, así *(*(a + 1) + 2)
es el tercer elemento de la segunda matriz de int
. La forma en que eso se asigna al código de la máquina depende completamente del compilador.
Una matriz declarada así
int a[10];
asigna memoria por 10 int
s. No puede modificar a
pero puede hacer aritmética de puntero con a
.
Un puntero como este asigna memoria solo para el puntero p
:
int *p;
No asigna ningún int
s. Puedes modificarlo:
p = a;
y use subíndices de matriz como pueda con un:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
s con duration` almacenamiento automático.
El nombre de la matriz en sí mismo produce una ubicación de memoria, por lo que puede tratar el nombre de la matriz como un puntero:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
Y otras cosas ingeniosas que puede hacer para apuntar (por ejemplo, agregar / restar un desplazamiento), también puede hacerlo en una matriz:
printf("value at memory location %p is %d", a + 1, *(a + 1));
En cuanto al lenguaje, si C no expone la matriz como una especie de "puntero" (pedagógicamente es solo una ubicación de memoria. No puede apuntar a una ubicación arbitraria en la memoria, ni puede ser controlada por el programador). Siempre necesitamos codificar esto:
printf("value at memory location %p is %d", &a[1], a[1]);
Creo que este ejemplo arroja algo de luz sobre el tema:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
Se compila bien (con 2 advertencias) en gcc 4.9.2 e imprime lo siguiente:
a == &a: 1
Uy :-)
Entonces, la conclusión es no, la matriz no es un puntero, no está almacenada en la memoria (ni siquiera de solo lectura) como un puntero, aunque parezca que sí, ya que puede obtener su dirección con el operador & . Pero, vaya, ese operador no funciona :-)), de cualquier manera, te han advertido:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C ++ rechaza dichos intentos con errores en tiempo de compilación.
Editar:
Esto es lo que quise demostrar:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
Aunque c
y a
"apunte" a la misma memoria, puede obtener la dirección del c
puntero, pero no puede obtener la dirección del a
puntero.
-std=c11 -pedantic-errors
, obtendrá un error de compilación al escribir código C no válido. La razón es porque intenta asignar int (*)[3]
a una variable de int**
, que son dos tipos que no tienen absolutamente nada que ver entre sí. Entonces, lo que se supone que demuestra este ejemplo, no tengo idea.
int **
tipo no es el punto allí, uno debería usar mejor void *
para esto.
El nombre de la matriz se comporta como un puntero y apunta al primer elemento de la matriz. Ejemplo:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
Ambas declaraciones de impresión darán exactamente la misma salida para una máquina. En mi sistema dio:
0x7fff6fe40bc0
Una matriz es una colección de elementos secuenciales y contiguos en la memoria. En C, el nombre de una matriz es el índice del primer elemento, y aplicando un desplazamiento puede acceder al resto de elementos. Un "índice al primer elemento" es de hecho un puntero a una dirección de memoria.
La diferencia con las variables de puntero es que no puede cambiar la ubicación a la que apunta el nombre de la matriz, por lo que es similar a un puntero constante (es similar, no es lo mismo. Vea el comentario de Mark). Pero también que no necesita desreferenciar el nombre de la matriz para obtener el valor si usa la aritmética del puntero:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
Entonces la respuesta es un "sí".
El nombre de la matriz es la dirección del primer elemento de una matriz. Entonces sí, el nombre de la matriz es un puntero constante.