Puntero C a matriz / matriz de desambiguación de punteros

463

¿Cuál es la diferencia entre las siguientes declaraciones:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

¿Cuál es la regla general para entender declaraciones más complejas?

Jorge
fuente
54
Aquí hay un gran artículo sobre la lectura de declaraciones complejas en C: unixwiz.net/techtips/reading-cdecl.html
jesper el
@jesper Desafortunadamente, los calificadores consty volatile, que son importantes y difíciles, faltan en ese artículo.
no usuario
@ no-un-usuario esos no son relevantes para esta pregunta. Tu comentario no es relevante Por favor abstenerse.
user64742

Respuestas:

439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

El tercero es el mismo que el primero.

La regla general es la precedencia del operador . Puede volverse aún más complejo a medida que los punteros de función entran en escena.

Mehrdad Afshari
fuente
44
Entonces, para sistemas de 32 bits: int * arr [8]; / * 8x4 bytes asignados, para cada puntero / int (* arr) [8]; / 4 bytes asignados, solo un puntero * /
George
10
No int * arr [8]: total asignado de 8x4 bytes , 4 bytes para cada puntero. int (* arr) [8] es correcto, 4 bytes.
Mehrdad Afshari
2
Debería haber releído lo que escribí. Quise decir 4 por cada puntero. ¡Gracias por la ayuda!
George
44
La razón por la que el primero es el mismo que el último es que siempre se permite colocar paréntesis alrededor de los declaradores. P [N] es un declarador de matriz. P (....) es un declarador de funciones y * P es un declarador de puntero. Entonces, todo lo siguiente es igual sin paréntesis (excepto la función '' () ": int (((* p))); void ((g (void))); int * (a [1]); nulo (* (p ())).
Johannes Schaub - litb
2
Bien hecho en tu explicación. Para obtener una referencia detallada sobre la precedencia y la asociatividad de los operadores, consulte la página 53 del lenguaje de programación C (ANSI C segunda edición) de Brian Kernighan y Dennis Ritchie. Los operadores ( ) [ ] asocian de izquierda a derecha y tienen una mayor precedencia que *por lo lee int* arr[8]como una matriz de tamaño 8 donde cada elemento apunta a un int y int (*arr)[8]como un puntero a una matriz de tamaño 8 que contiene números enteros
fofo
267

Use el programa cdecl , como lo sugiere K&R.

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

También trabaja de la otra manera.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )
sigjuice
fuente
@ankii La mayoría de las distribuciones de Linux deberían tener un paquete. También puedes construir tu propio binario.
sigjuice
ah lo siento por no mencionar, macOS aquí. verá si está disponible, de lo contrario, el sitio web también está bien. ^^ gracias por dejarme saber sobre esto .. No dude en marcar NLN.
ankii
2
@ankii Puede instalar desde Homebrew (¿y quizás MacPorts?). Si no son de su agrado, es trivial construir uno propio desde el enlace de Github en la parte superior derecha de cdecl.org (lo acabo de construir en macOS Mojave). Luego simplemente copie el binario cdecl a su RUTA. Recomiendo $ PATH / bin, porque no hay necesidad de involucrar a root en algo tan simple como esto.
sigjuice
Oh, no había leído el pequeño párrafo sobre la instalación en el archivo Léame. solo algunos comandos e indicadores para manejar dependencias. Instalados usando brew. :)
ankii
1
Cuando leí esto por primera vez pensé: "Nunca bajaré a este nivel". Al día siguiente, lo descargué.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
126

No sé si tiene un nombre oficial, pero lo llamo el Right-Left Thingy (TM).

Comience en la variable, luego vaya a la derecha, a la izquierda, a la derecha ... y así sucesivamente.

int* arr1[8];

arr1 es una matriz de 8 punteros a enteros.

int (*arr2)[8];

arr2 es un puntero (el paréntesis bloquea la derecha-izquierda) a una matriz de 8 enteros.

int *(arr3[8]);

arr3 es una matriz de 8 punteros a enteros.

Esto debería ayudarte con declaraciones complejas.

GManNickG
fuente
19
Lo he escuchado referirse con el nombre de "La regla espiral", que se puede encontrar aquí .
Fouric
66
@InkBlend: la regla espiral es diferente de la regla derecha-izquierda . El primero falla en casos como int *a[][10]mientras que el segundo tiene éxito.
legends2k
1
@dogeen Pensé que ese término tenía algo que ver con Bjarne Stroustrup :)
Anirudh Ramanathan
1
Como InkBlend y legends2k dijeron, esta es la regla espiral que es más compleja y no funciona en todos los casos, por lo que no hay razón para usarla.
kotlomoy
No olvide la asociatividad de ( ) [ ]izquierda a derecha y de derecha a izquierda de* &
Mushy
28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 
Sunil bn
fuente
¿No debería ser el tercero: a es una matriz de punteros a una matriz entera de tamaño 8? Quiero decir que cada una de las matrices de enteros será de tamaño 8, ¿verdad?
Rushil Paul
2
@Rushil: no, el último subíndice ( [5]) representa la dimensión interna. Esto significa que (*a[8])es la primera dimensión y, por lo tanto, es la representación externa de la matriz. A lo que a apunta cada elemento es a una matriz entera diferente de tamaño 5.
zeboidlund el
Gracias por el tercero. Estoy buscando cómo escribir una matriz de punteros en la matriz.
Deqing
15

La respuesta para los dos últimos también se puede deducir de la regla de oro en C:

La declaración sigue al uso.

int (*arr2)[8];

¿Qué pasa si desreferencias arr2? Obtiene una matriz de 8 enteros.

int *(arr3[8]);

¿Qué sucede si tomas un elemento de arr3? Obtienes un puntero a un entero.

Esto también ayuda cuando se trata de punteros a funciones. Para tomar el ejemplo de sigjuice:

float *(*x)(void )

¿Qué pasa cuando desreferencias x? Obtiene una función que puede llamar sin argumentos. ¿Qué pasa cuando lo llamas? Devolverá un puntero a a float.

Sin embargo, la precedencia del operador siempre es complicada. Sin embargo, el uso de paréntesis también puede ser confuso porque la declaración sigue al uso. Al menos, para mí, intuitivamente se arr2ve como una matriz de 8 punteros a ints, pero en realidad es al revés. Solo toma un tiempo acostumbrarse. Razón suficiente para agregar siempre un comentario a estas declaraciones, si me preguntas :)

editar: ejemplo

Por cierto, me topé con la siguiente situación: una función que tiene una matriz estática y que usa la aritmética del puntero para ver si el puntero de la fila está fuera de los límites. Ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

Salida:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

Tenga en cuenta que el valor del borde nunca cambia, por lo que el compilador puede optimizarlo. Esto es diferente de lo que podría querer usar inicialmente const int (*border)[3]:: que declara el borde como un puntero a una matriz de 3 enteros que no cambiará el valor mientras exista la variable. Sin embargo, ese puntero puede apuntar a cualquier otra matriz de este tipo en cualquier momento. En cambio, queremos ese tipo de comportamiento para el argumento (porque esta función no cambia ninguno de esos enteros). La declaración sigue al uso.

(pd: ¡siéntase libre de mejorar esta muestra!)

Hraban Luyat
fuente
5
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];
Byron Formwalt
fuente
3

Como regla general, los operadores unitarios adecuados (como [], (), etc.) tienen preferencia sobre los de izquierda. Entonces,int *(*ptr)()[]; lo sería un puntero que apunta a una función que devuelve una matriz de punteros a int (obtenga los operadores correctos tan pronto como pueda a medida que sale del paréntesis)

Luis Colorado
fuente
Eso es cierto, pero también es ilegal. No puede tener una función que devuelva una matriz. Intenté y obtuve esto: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];bajo GCC 8 con$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito
1
La razón del compilador para dar ese error es que está interpretando que la función devuelve una matriz, como indica la interpretación correcta de la regla de precedencia. Es ilegal como una declaración, pero la declaración legal int *(*ptr)();permite que una expresión como p()[3](o (*p)()[3]) se use más adelante.
Luis Colorado
Ok, si lo entiendo, ¿estás hablando de crear una función que devuelva un puntero al primer elemento de una matriz (no una matriz en sí), y luego usar esa función como si devolviera una matriz? Idea interesante. Lo intentaré. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }y llámalo así: ¿ foo(arr)[4];cuál debería contener arr[2][4], verdad?
Cacahuete Frito
bien ... pero también tenías razón, y la declaración era ilegal. :)
Luis Colorado
2

Creo que podemos usar la regla simple ...

example int * (*ptr)()[];
start from ptr 

" ptres un puntero para" ir hacia la derecha ... es ")" ahora ir a la izquierda es un "(" salir a la derecha "()" entonces "a una función que no toma argumentos" ir a la izquierda "y devuelve un puntero" ir derecha "a una matriz" ir a la izquierda "de enteros"

simal
fuente
Lo mejoraría un poco: "ptr es un nombre que se refiere a" ir a la derecha ... es ), ahora ir a la izquierda ... es *"un puntero a" ir a la derecha ... es ), ahora ir a la izquierda ... es un (salir, ir a la derecha ()por lo que "a una función que no tiene argumentos" anda bien ... []"y devuelve una matriz de" ir a la derecha ;final, así que ir a la izquierda ... *"punteros" a ir a la izquierda ... int"enteros"
Cacahuete Frito
2

Así es como lo interpreto:

int *something[n];

Nota sobre la precedencia: el operador de subíndice de matriz ( []) tiene mayor prioridad que el operador de desreferencia ( *).

Entonces, aquí aplicaremos el []antes *, haciendo que la declaración sea equivalente a:

int *(something[i]);

Observe cómo una declaración tiene sentido: int numsignifica numes unint , int *ptro int (*ptr)significa, (valor en ptr) es un int, lo que hace ptrun puntero a int.

Esto se puede leer como, (el valor del (valor en el índice i-ésimo de algo)) es un número entero. Entonces, (el valor en el i-ésimo índice de algo) es un (puntero entero), lo que hace que algo sea una matriz de punteros enteros.

En el segundo

int (*something)[n];

Para que esta afirmación tenga sentido, debe estar familiarizado con este hecho:

Nota sobre la representación del puntero de la matriz: somethingElse[i]es equivalente a*(somethingElse + i)

Entonces, reemplazando somethingElsecon (*something), obtenemos *(*something + i), que es un número entero según la declaración. Entonces,(*something) nos dio una matriz, que hace algo equivalente a (puntero a una matriz) .

nishantbhardwaj2002
fuente
0

Supongo que la segunda declaración es confusa para muchos. Aquí hay una manera fácil de entenderlo.

Tengamos una variedad de enteros, es decir int B[8] .

También tengamos una variable A que apunta a B. Ahora, el valor en A es B, es decir (*A) == B . Por lo tanto, A apunta a una matriz de enteros. En su pregunta, arr es similar a A.

Del mismo modo, en int* (*C) [8], C es un puntero a una matriz de punteros a entero.

nishantbhardwaj2002
fuente
0
int *arr1[5]

En esta declaración, arr1hay una matriz de 5 punteros a enteros. Motivo: Los corchetes tienen mayor prioridad sobre * (operador de desreferencia). Y en este tipo, el número de filas es fijo (5 aquí), pero el número de columnas es variable.

int (*arr2)[5]

En esta declaración, arr2es un puntero a una matriz entera de 5 elementos. Motivo: Aquí, los corchetes () tienen mayor prioridad que []. Y en este tipo, el número de filas es variable, pero el número de columnas es fijo (5 aquí).

Krishna Kanth Yenumula
fuente
-7

En puntero a un entero si el puntero se incrementa, entonces pasa al siguiente entero.

en la matriz del puntero si se incrementa el puntero, salta a la siguiente matriz

Nikhil
fuente
" en la matriz del puntero si el puntero se incrementa, salta a la siguiente matriz " esto es simplemente incorrecto.
alk