En C, ¿son punteros de matrices o se usan como punteros?

10 minutos de lectura

Entendí que las matrices eran simplemente punteros constantes a una secuencia de valores, y cuando declarabas una matriz en C, declarabas un puntero y asignabas espacio para la secuencia a la que apunta.

Pero esto me confunde: el siguiente código:

char y[20];
char *z = y;

printf("y size is %lu\n", sizeof(y));
printf("y is %p\n", y);
printf("z size is %lu\n", sizeof(z));
printf("z is %p\n", z);

cuando se compila con Apple GCC da el siguiente resultado:

y size is 20
y is 0x7fff5fbff930
z size is 8
z is 0x7fff5fbff930

(mi máquina es de 64 bits, los punteros tienen una longitud de 8 bytes).

Si ‘y’ es un puntero constante, ¿por qué tiene un tamaño de 20, como la secuencia de valores a la que apunta? ¿Se reemplaza el nombre de la variable ‘y’ por una dirección de memoria durante el tiempo de compilación cuando sea apropiado? ¿Son las matrices, entonces, algún tipo de azúcar sintáctico en C que simplemente se traduce a cosas de puntero cuando se compila?

  • posible duplicado de una pregunta de las preguntas frecuentes de C++ del sitio: ¿El nombre de la matriz es un puntero en C?

    –Steve Jessop

    5 de enero de 2011 a las 18:17


Aquí está el idioma exacto del estándar C (n1256):

6.3.2.1 Valores L, matrices y designadores de función


3 Excepto cuando es el operando del sizeof operador o el unario & operador, o es un literal de cadena que se usa para inicializar una matriz, una expresión que tiene el tipo ”matriz de tipo” se convierte en una expresión de tipo ” puntero a tipo” que apunta al elemento inicial del objeto de matriz y no es un valor l. Si el objeto de la matriz tiene una clase de almacenamiento de registro, el comportamiento no está definido.

Lo importante a recordar aquí es que hay una diferencia entre un objeto (en términos de C, que significa algo que ocupa memoria) y el expresión se utiliza para referirse a ese objeto.

Cuando declaras una matriz como

int a[10];

la objeto designado por el expresión a es una matriz (es decir, un bloque contiguo de memoria lo suficientemente grande como para contener 10 int valores) y el tipo de expresión a es una “matriz de 10 elementos de int“, o int [10]. Si el expresión a aparece en un contexto que no sea como el operando de la sizeof o & operadores, entonces su tipo se convierte implícitamente a int *y su valor es la dirección del primer elemento.

en el caso de la sizeof operador, si el operando es una expresión de tipo T [N]entonces el resultado es el número de bytes en el objeto de matriz, no en un puntero a ese objeto: N * sizeof T.

en el caso de la & operador, el valor es la dirección de la matriz, que es la misma que la dirección del primer elemento de la matriz, pero el tipo de la expresión es diferente: dada la declaración T a[N];el tipo de expresión &a es T (*)[N]o puntero a la matriz de N elementos de T. El valor es lo mismo que a o &a[0] (la dirección de la matriz es la misma que la dirección del primer elemento de la matriz), pero la diferencia en los tipos es importante. Por ejemplo, dado el código

int a[10];
int *p = a;
int (*ap)[10] = &a;

printf("p = %p, ap = %p\n", (void *) p, (void *) ap);
p++;
ap++;
printf("p = %p, ap = %p\n", (void *) p, (void *) ap);

verá la salida en el orden de

p = 0xbff11e58, ap = 0xbff11e58
p = 0xbff11e5c, ap = 0xbff11e80

OIA, avanzando p agrega sizeof int (4) al valor original, mientras que avanzar ap agrega 10 * sizeof int (40).

Más lenguaje estándar:

6.5.2.1 Subíndice de matriz

Restricciones

1 Una de las expresiones debe tener el tipo ”puntero a objeto tipo”, la otra expresión será de tipo entero, y el resultado será de tipo ”tipo”.

Semántica

2 Una expresión de sufijo seguida de una expresión entre corchetes [] es una designación con subíndice de un elemento de un objeto de matriz. La definición del operador subíndice [] es eso E1[E2] es idéntico a (*((E1)+(E2))). Debido a las reglas de conversión que se aplican al binario + operador, si E1 es un objeto de matriz (equivalentemente, un puntero al elemento inicial de un objeto de matriz) y E2 es un entero, E1[E2] designa el E2-ésimo elemento de E1 (contando desde cero).

Por lo tanto, cuando subíndice una expresión de matriz, lo que sucede bajo el capó es que se calcula el desplazamiento de la dirección del primer elemento de la matriz y se elimina la referencia al resultado. La expresion

a[i] = 10;

es equivalente a

*((a)+(i)) = 10;

que es equivalente a

*((i)+(a)) = 10;

que es equivalente a

 i[a] = 10;

Sí, el subíndice de matriz en C es conmutativo; por el amor de Dios, nunca hagas esto en código de producción.

Dado que los subíndices de matriz se definen en términos de operaciones de puntero, puede aplicar el operador de subíndice a expresiones de tipo de puntero, así como de tipo de matriz:

int *p = malloc(sizeof *p * 10);
int i;
for (i = 0; i < 10; i++)
  p[i] = some_initial_value(); 

Aquí hay una tabla útil para recordar algunos de estos conceptos:

Declaration: T a[N];

Expression    Type    Converts to     Value
----------    ----    ------------    -----
         a    T [N]   T *             Address of the first element in a;
                                        identical to writing &a[0]
        &a    T (*)[N]                Address of the array; value is the same
                                        as above, but the type is different
  sizeof a    size_t                  Number of bytes contained in the array
                                        object (N * sizeof T)
        *a    T                       Value at a[0]
      a[i]    T                       Value at a[i]
     &a[i]    T *                     Address of a[i] 

Declaration: T a[N][M];

Expression     Type        Converts to     Value
----------     ----        ------------    -----
          a    T [N][M]    T (*)[M]        Address of the first subarray (&a[0])
         &a    T (*)[N][M]                 Address of the array (same value as
                                             above, but different type)
   sizeof a    size_t                      Number of bytes contained in the
                                             array object (N * M * sizeof T)
         *a    T [M]      T *              Value of a[0], which is the address
                                             of the first element of the first subarray
                                             (same as &a[0][0])
       a[i]    T [M]      T *              Value of a[i], which is the address
                                             of the first element of the i'th subarray
      &a[i]    T (*)[M]                    Address of the i-th subarray; same value as
                                             above, but different type
sizeof a[i]    size_t                      Number of bytes contained in the i'th subarray
                                             object (M * sizeof T)
      *a[i]    T                           Value of the first element of the i'th 
                                             subarray (a[i][0])
    a[i][j]    T                           Value at a[i][j]
   &a[i][j]    T *                         Address of a[i][j]

Declaration: T a[N][M][O];

Expression        Type             Converts to
----------        ----             -----------
         a        T [N][M][O]      T (*)[M][O]
        &a        T (*)[N][M][O]
        *a        T [M][O]         T (*)[O]
      a[i]        T [M][O]         T (*)[O]
     &a[i]        T (*)[M][O]
     *a[i]        T [O]            T *
   a[i][j]        T [O]            T *
  &a[i][j]        T (*)[O]
  *a[i][j]        T 
a[i][j][k]        T

A partir de aquí, el patrón para arreglos de mayor dimensión debería ser claro.

Entonces, en resumen: las matrices no son punteros. En la mayoría de los contextos, matriz expresiones se convierten en tipos de puntero.

avatar de usuario
miguel rebabas

Las matrices no son punteros, aunque en la mayoría de las expresiones un nombre de matriz se evalúa como un puntero al primer elemento de la matriz. Por lo tanto, es muy, muy fácil usar un nombre de matriz como puntero. A menudo verá que se usa el término ‘decaimiento’ para describir esto, como en “la matriz se decayó en un puntero”.

Una excepción es como el operando del sizeof operador, donde el resultado es el tamaño de la matriz (en bytes, no en elementos).

Un par de problemas adicionales relacionados con esto:

Un parámetro de matriz para una función es una ficción: el compilador realmente pasa un puntero simple (esto no se aplica a los parámetros de referencia a matriz en C ++), por lo que no puede determinar el tamaño real de una matriz pasada a una función – usted debe pasar esa información de alguna otra manera (tal vez usando un parámetro adicional explícito, o usando un elemento centinela, como lo hacen las cadenas C)

Además, un modismo común para obtener la cantidad de elementos en una matriz es usar una macro como:

#define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0]))

Esto tiene el problema de aceptar un nombre de matriz, donde funcionará, o un puntero, donde dará un resultado sin sentido sin previo aviso del compilador. Existen versiones más seguras de la macro (particularmente para C++) que generarán una advertencia o un error cuando se usa con un puntero en lugar de una matriz. Consulte los siguientes elementos SO:

  • versión C++
  • una versión C mejor (aunque todavía no perfectamente segura)

Nota: Es posible que los VLA C99 (matrices de longitud variable) no sigan todas estas reglas (en particular, se pueden pasar como parámetros con el tamaño de matriz conocido por la función llamada). Tengo poca experiencia con los VLA y, que yo sepa, no se usan mucho. Sin embargo, quiero señalar que la discusión anterior podría aplicarse de manera diferente a los VLA.

  • así que la excepción sizeof se hizo porque era útil… ¡no sabía que había una manera de saber el tamaño de una matriz! (aunque todavía no es tan útil porque solo encuentra el tamaño de las matrices con tamaño fijo, pero supongo que es mejor que definir muchas constantes para el mismo propósito)

    – salvador p

    6 de enero de 2011 a las 18:17

avatar de usuario
Péter Török

sizeof se evalúa en tiempo de compilación y el compilador sabe si el operando es una matriz o un puntero. Para arreglos da el número de bytes ocupados por el arreglo. Su matriz es una char[] (y sizeof(char) es 1), por lo tanto sizeof pasa a darle el número de elementos. Para obtener el número de elementos en el caso general, un modismo común es (aquí para int):

int y[20];
printf("number of elements in y is %lu\n", sizeof(y) / sizeof(int));

para punteros sizeof da el número de bytes ocupados por el tipo de puntero sin procesar.

En

char hello[] = "hello there"
int i;

y

char* hello = "hello there";
int i;

En primera instancia (descontando la alineación), se almacenarán 12 bytes para hello con el espacio asignado inicializado en Hola mientras que en el segundo Hola se almacena en otro lugar (posiblemente espacio estático) y hello se inicializa para apuntar a la cadena dada.

hello[2] así como *(hello + 2) devolverá ‘e’ en ambos casos sin embargo.

Además de lo que dijeron los demás, quizás este artículo ayude: http://en.wikipedia.org/wiki/C_%28programming_language%29#Array-pointer_interchangeability

avatar de usuario
andres trineo

Si ‘y’ es un puntero constante, ¿por qué tiene un tamaño de 20, como la secuencia de valores a la que apunta?

Porque z es la dirección de la variable y siempre devolverá 8 para su máquina. Debe usar el puntero de desreferencia (&) para obtener el contenido de una variable.

EDITAR: Una buena distinción entre los dos: http://www.cs.cf.ac.uk/Dave/C/node10.html

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad