Crea un puntero a una matriz bidimensional

119

Necesito un puntero a una matriz bidimensional estática. ¿Cómo se hace esto?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

Recibo todo tipo de errores como:

  • advertencia: asignación de tipo de puntero incompatible
  • el valor subindicado no es ni una matriz ni un puntero
  • error: uso no válido del miembro de matriz flexible
eneldo
fuente
Lea stackoverflow.com/questions/423823/ ... puede ayudarlo
Johannes Schaub - litb
1
@ JohannesSchaub-litb Eso ya no existe. (¿Cómo lo veo de nuevo ...? Sé que los miembros de baja repetición pueden verlo, pero olvidé cómo ...)
Mateen Ulhaq
@muntoo: Aquí hay una copia: gist.github.com/sharth/ede13c0502d5dd8d45bd
Bill Lynch

Respuestas:

139

Aquí quieres hacer un puntero al primer elemento de la matriz.

uint8_t (*matrix_ptr)[20] = l_matrix;

Con typedef, esto se ve más limpio

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

Entonces podrás volver a disfrutar de la vida :)

matrix_ptr[0][1] = ...;

Tenga cuidado con el mundo de punteros / matrices en C, hay mucha confusión alrededor de esto.


Editar

Revisando algunas de las otras respuestas aquí, porque los campos de comentarios son demasiado cortos para hacerlo allí. Se propusieron múltiples alternativas, pero no se mostró cómo se comportan. Así es como lo hacen

uint8_t (*matrix_ptr)[][20] = l_matrix;

Si corrige el error y agrega el operador de dirección de &como en el siguiente fragmento

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Luego, ese crea un puntero a un tipo de matriz incompleta de elementos de tipo matriz de 20 uint8_t. Debido a que el puntero es a una matriz de matrices, debe acceder a él con

(*matrix_ptr)[0][1] = ...;

Y debido a que es un puntero a una matriz incompleta, no puede hacerlo como un atajo

matrix_ptr[0][0][1] = ...;

Debido a que la indexación requiere que se conozca el tamaño del tipo de elemento (la indexación implica una adición de un número entero al puntero, por lo que no funcionará con tipos incompletos). Tenga en cuenta que esto solo funciona en C, porque T[]y T[N]son tipos compatibles. C ++ no tiene un concepto de tipos compatibles , por lo que rechazará ese código, porque T[]y T[10]son tipos diferentes.


La siguiente alternativa no funciona en absoluto, porque el tipo de elemento de la matriz, cuando lo ve como una matriz unidimensional, no lo es uint8_t, perouint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

La siguiente es una buena alternativa

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Lo accedes con

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

Tiene la ventaja de que conserva el tamaño de la dimensión exterior. Entonces puedes aplicar sizeof en él

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

Hay otra respuesta que hace uso del hecho de que los elementos de una matriz se almacenan contiguamente

uint8_t *matrix_ptr = l_matrix[0];

Ahora, eso formalmente solo le permite acceder a los elementos del primer elemento de la matriz bidimensional. Es decir, se cumple la siguiente condición

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

Notará que probablemente funciona 10*20-1, pero si agrega análisis de alias y otras optimizaciones agresivas, algún compilador podría hacer una suposición que podría romper ese código. Habiendo dicho eso, nunca me he encontrado con un compilador que falle en él (pero, de nuevo, no he usado esa técnica en código real), e incluso el C FAQ contiene esa técnica (con una advertencia sobre su UB'ness ), y si no puede cambiar el tipo de matriz, esta es la última opción para salvarlo :)

Johannes Schaub - litb
fuente
+1 - buena información sobre el desglose int (*) [] [20] - no puedo hacer eso en C ++
Faisal Vali
@litb, lo siento, pero esto es incorrecto ya que su solución no proporciona ninguna asignación de almacenamiento para la matriz.
Rob Wells
2
@Rob, no te entiendo del todo. el almacenamiento en todos estos casos lo proporciona la propia matriz l_matix. Los apuntadores a ellos toman almacenamiento desde donde se declaran y como (pila, segmento de datos estáticos, ...).
Johannes Schaub - litb
Solo curiosidad, ¿por qué necesitamos la dirección "&" de l_matrix?
electro
1
@Sohaib: no, eso crea solo un puntero. Es posible que lo haya confundido con uint8_t *d[20], que crea una matriz de 3 punteros a uint8_t, pero eso no funcionaría en este caso.
Palo
27

Para comprender completamente esto, debe comprender los siguientes conceptos:

¡Las matrices no son punteros!

En primer lugar (y se ha predicado lo suficiente), las matrices no son punteros . En cambio, en la mayoría de los usos, 'decaen' a la dirección de su primer elemento, que puede asignarse a un puntero:

int a[] = {1, 2, 3};

int *p = a; // p now points to a[0]

Supongo que funciona de esta manera para que se pueda acceder al contenido de la matriz sin copiarlos todos. Eso es solo un comportamiento de tipos de matriz y no significa que sean lo mismo.



Matrices multidimensionales

Los arreglos multidimensionales son solo una forma de 'particionar' la memoria de una manera que el compilador / máquina pueda entender y operar.

Por ejemplo, int a[4][3][5]= una matriz que contiene 4 * 3 * 5 (60) 'trozos' de memoria de tamaño entero.

La ventaja sobre el uso int a[4][3][5]frente al simple int b[60]es que ahora están "particionados" (es más fácil trabajar con sus "fragmentos", si es necesario), y el programa ahora puede realizar comprobaciones vinculadas.

De hecho, int a[4][3][5]se almacena exactamente como int b[60]en la memoria: la única diferencia es que el programa ahora lo administra como si fueran entidades separadas de ciertos tamaños (específicamente, cuatro grupos de tres grupos de cinco).

Tenga en cuenta: Ambos int a[4][3][5]y int b[60]son iguales en memoria, y la única diferencia es cómo los maneja la aplicación / compilador

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

A partir de esto, puede ver claramente que cada "partición" es solo una matriz de la que el programa realiza un seguimiento.



Sintaxis

Ahora, las matrices son sintácticamente diferentes de los punteros . Específicamente, esto significa que el compilador / máquina los tratará de manera diferente. Esto puede parecer una obviedad, pero eche un vistazo a esto:

int a[3][3];

printf("%p %p", a, a[0]);

El ejemplo anterior imprime la misma dirección de memoria dos veces, así:

0x7eb5a3b4 0x7eb5a3b4

Sin embargo, solo se puede asignar uno a un puntero de manera tan directa :

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

¿Por qué no se a puede asignar a un puntero pero a[0] sí?

Esto, simplemente, es una consecuencia de las matrices multidimensionales, y explicaré por qué:

En el nivel de " a", todavía vemos que tenemos otra "dimensión" que esperar. a[0]Sin embargo, en el nivel de " ", ya estamos en la dimensión superior, por lo que en lo que respecta al programa, solo estamos viendo una matriz normal.

Puede estar preguntando:

¿Por qué importa si la matriz es multidimensional en lo que respecta a hacer un puntero para ella?

Es mejor pensar de esta manera:

Un 'decaimiento' de una matriz multidimensional no es solo una dirección, sino una dirección con datos de partición (también conocido como aún entiende que sus datos subyacentes están hechos de otras matrices), que consiste en límites establecidos por la matriz más allá de la primera dimensión.

Esta lógica de 'partición' no puede existir dentro de un puntero a menos que la especifiquemos:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

De lo contrario, se pierde el significado de las propiedades de clasificación de la matriz.

También tenga en cuenta el uso de paréntesis alrededor de *p: int (*p)[5][95][8]- Eso es para especificar que estamos haciendo un puntero con estos límites, no una matriz de punteros con estos límites:int *p[5][95][8]



Conclusión

Revisemos:

  • Las matrices decaen a direcciones si no tienen otro propósito en el contexto utilizado
  • Los arreglos multidimensionales son solo arreglos de arreglos - Por lo tanto, la dirección 'decaída' llevará la carga de "Tengo subdimensiones"
  • Los datos de dimensión no pueden existir en un puntero a menos que se los proporcione .

En resumen: los arreglos multidimensionales decaen hacia direcciones que tienen la capacidad de comprender su contenido.

Super gato
fuente
1
La primera parte de la respuesta es excelente, pero la segunda no. Esto no es correcto:, en int *p1 = &(a[0]); // RIGHT !realidad es idéntico aint *p1 = a;
2501
@ 2501 Gracias por detectar ese error, lo he corregido. No puedo decir con certeza por qué el ejemplo que define esta "regla" también la desafió. Vale la pena reiterar que el hecho de que dos entidades puedan interpretarse como punteros y arrojen el mismo valor no significa que tengan el mismo significado.
Super Cat
7

En

int *ptr= l_matrix[0];

puedes acceder como

*p
*(p+1)
*(p+2)

después de que todas las matrices bidimensionales también se almacenan como 1-d.

Sagar
fuente
5

Buenos dias

La declaracion

static uint8_t l_matrix[10][20];

ha reservado almacenamiento para 10 filas de 20 ubicaciones unit8_t, es decir, 200 ubicaciones del tamaño de uint8_t, y cada elemento se encuentra calculando 20 x fila + columna.

Entonces no

uint8_t (*matrix_ptr)[20] = l_matrix;

darle lo que necesita y señalar el elemento de la columna cero de la primera fila de la matriz?

Editar: Pensando en esto un poco más, ¿no es un nombre de matriz, por definición, un puntero? Es decir, el nombre de una matriz es sinónimo de la ubicación del primer elemento, es decir, l_matrix [0] [0]?

Edit2: Como lo mencionaron otros, el espacio de comentarios es demasiado pequeño para una discusión adicional. De todas formas:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

no proporciona ninguna asignación de almacenamiento para la matriz en cuestión.

Como se mencionó anteriormente, y según lo definido por el estándar, la declaración:

static uint8_t l_matrix[10][20];

ha reservado 200 ubicaciones secuenciales de tipo uint8_t.

Refiriéndose a l_matrix usando declaraciones de la forma:

(*l_matrix + (20 * rowno) + colno)

le dará el contenido del elemento colno'th que se encuentra en la fila rowno.

Todas las manipulaciones del puntero tienen en cuenta automáticamente el tamaño del objeto apuntado. - Sección 5.4 de K&R, p.103

Este también es el caso si hay algún cambio de alineación de byte o relleno en el almacenamiento del objeto en cuestión. El compilador se ajustará automáticamente a estos. Por definición del estándar C ANSI.

HTH

salud,

Rob Wells
fuente
1
uint8_t (* matrix_ptr) [] [20] << los primeros corchetes deben dejarse fuera, el correcto es uint8_t (* matrix_ptr) [20]
Aconcagua
5

En C99 (compatible con clang y gcc) hay una sintaxis oscura para pasar matrices multidimensionales a funciones por referencia:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

A diferencia de un puntero simple, esto sugiere el tamaño de la matriz, lo que teóricamente permite que el compilador advierta sobre el paso de una matriz demasiado pequeña y detecte un acceso obvio fuera de los límites.

Lamentablemente, no se corrige sizeof()y los compiladores no parecen usar esa información todavía, por lo que sigue siendo una curiosidad.

Kornel
fuente
1
Esta respuesta es engañosa: eso no hace que el argumento sea una matriz de tamaño fijo, sigue siendo un puntero. static 10es una especie de garantía de que están presentes al menos 10 elementos, lo que nuevamente significa que el tamaño no es fijo.
sonroja el
1
@bluss la pregunta era sobre un puntero, así que no veo cómo responder con un puntero (anotando por referencia ) es engañoso. La matriz tiene un tamaño fijo desde la perspectiva de la función, porque el acceso a los elementos más allá de estos límites no está definido.
Kornel
No creo que el acceso más allá de 10 no esté definido, no veo nada que indique eso.
sonroja el
Esta respuesta parece sugerir que sin la palabra clave static, la matriz no se pasaría por referencia, lo cual no es cierto. Las matrices se pasan por referencia de todos modos. La pregunta original se refería a un caso de uso diferente: acceder a elementos de una matriz 2D utilizando un puntero suplementario dentro de la misma función / espacio de nombres.
Palo
4

Siempre puede evitar jugar con el compilador declarando la matriz como lineal y haciendo el (fila, columna) al cálculo del índice de matriz usted mismo.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

esto es lo que habría hecho el compilador de todos modos.

gnosis
fuente
1
Esto es lo que hace el compilador de C de todos modos. C realmente no tiene ninguna noción real de una "matriz" - la notación [] es simplemente azúcar sintáctica para la aritmética de punteros
Ken Keenan
7
Esta solución tiene la desventaja de no encontrar nunca la forma correcta de hacerlo.
Craig McQueen
2

La sintaxis básica de la inicialización del puntero que apunta a una matriz multidimensional es

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

La sintaxis básica para llamarlo es

(*pointer_name)[1st index][2nd index][...]

He aquí un ejemplo:

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

int main() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

   char (*p)[5][100] = &balance; // Pointer initialization...

   printf("%s\n",(*p)[0]); // Calling...
   printf("%s\n",(*p)[1]); // Calling...

  return 0;
}

La salida es:

Subham
Messi

Funcionó...

Subham Debnath
fuente
1

Puedes hacerlo así:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;
Nick Dandoulakis
fuente
1
¿No ocupa esto 10 * 20 bytes de RAM? (estoy en un microcontrolador)
Dill
Ocupará 4 bytes o cualquier tamaño de un puntero en su caja. Pero recuerde que si tiene este, debe indexar con matrix_ptr [0] [x] [y] o (* matrix_ptr) [x] [y]. Es la interpretación directa y palabra por palabra de "puntero a matriz bidimensional": p
Johannes Schaub - litb
Gracias litb, olvidé mencionar cómo acceder a él. No tiene sentido editar mi respuesta ya que hiciste un gran trabajo con tu respuesta :)
Nick Dandoulakis
Entonces, ¿esto ocupa 10 * 20 bytes de RAM o no?
Danijel
@Danijel, dado que es un puntero a una matriz bidimensional, ocupará solo 4 bytes o el tamaño que tenga un puntero en su caja, es decir, 16 bits, 32 bits, 64 bits, etc.
Nick Dandoulakis
1

Quieres un puntero al primer elemento, entonces;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}
Ken Keenan
fuente
0

También puede agregar un desplazamiento si desea utilizar índices negativos:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

Si su compilador da un error o advertencia, puede usar:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;
ingeniero matemático
fuente
Hola. Esta pregunta está etiquetada con c, por lo que la respuesta debe estar en el mismo idioma. Observe las etiquetas.
2501