problemas para entender qué elementos se pasan al pasar matrices multidimensionales a funciones

Leí en alguna parte que las siguientes matrices se pueden pasar a las siguientes funciones de tal manera, como se muestra a continuación, sin embargo, no entiendo qué elementos dentro de la matriz se pasan exactamente a la función.
Estas son las matrices

int array[NROWS][NCOLUMNS]; int **array1; int **array2; int *array3; int (*array4)[NCOLUMNS]; 

y estas son las funciones:

 f1(int a[][NCOLUMNS], int m, int n); f2(int *aryp, int nrows, int ncolumns); f3(int **pp, int m, int n); 

El sitio web que leí menciona que podemos pasar las siguientes matrices a las siguientes funciones de tal manera:

 f2(&array[0][0], NROWS, NCOLUMNS); f2(*array2, nrows, ncolumns); f2(array3, nrows, ncolumns); f2(*array4, nrows, NCOLUMNS); f3(array1, nrows, ncolumns); f3(array2, nrows, ncolumns); 

¿No es array1 y array2 una matriz de punteros? Entonces, cuando los pasas a la f3 , todos los indicadores pasan? Y sobre array2 cuando pasa a f2 , f2 tiene un puntero normal en argumentos formales, pero array2 es una matriz de punteros, entonces, ¿cómo accedería a las filas y columnas individuales al pasar una matriz de punteros a f2 ? ¿Y cómo accedería a filas y columnas individuales al pasar array4 que es un puntero a una serie de matrices 1D para la función f2 ?

Lo que puedes percibir como arreglos en C no siempre son arreglos en términos de conceptos de lenguaje.

C es un lenguaje de los años 70. Está muy cerca de la implementación de la máquina en comparación con sus descendientes más abstractos. Por lo tanto, debe tener en cuenta la implementación si desea comprender qué hay más allá de elementos sintácticos confusamente similares.

Se puede acceder a ambos punteros y matrices a través de la notación de corchetes (cuadrados) .

La notación de paréntesis es indudablemente útil, pero es la raíz de todo mal en lo que respecta a las confusiones entre punteros y matrices.

f[i] también “funcionará” para punteros y matrices, aunque los mecanismos subyacentes serán diferentes, como veremos.

Relación entre punteros y matrices

Comencemos con declaraciones de variables.

Punteros

float * f simplemente le dice al comstackdor que el símbolo f hará referencia algún día a un número desconocido de flotantes.

f no está inicializado. Depende de usted decidir dónde estarán los datos reales y establecer f para señalarlos.

Aritmética de punteros y notación de corchetes

Tenga en cuenta que cuando agrega / resta un valor a un puntero, la unidad tiene el tamaño del tipo de punta.

 float * f; float * f3 = f+3; // internal value: f + 3 * sizeof (float) // these two statements are identical *f3 = 1; *(f+3) = 1; 

Dado que escribir *(f+i) es incómodo cuando desea referenciar datos contiguos desde un puntero, se puede usar la notación de corchetes :

 f[3] = 1; // equivalent to *(f+3) = 1; 

Independientemente de la notación utilizada, la dirección de f [3] se calcula así:

@f[ 3 ] = f + 3 * sizeof (float)

Podría considerar f como una matriz (dinámica), pero a medida que C lo ve, sigue siendo un puntero , al que se hace referencia mediante una syntax que lo hace parecer una matriz.

Arrays

float f[10] todavía le dice al comstackdor que f hará referencia a algunos flotantes, pero también

  • asigna el número solicitado de flotadores en el lugar apropiado
    • en la stack si f es una variable local automática
    • en los datos estáticos (también conocido como BSS) si f es una variable global o estática
  • considera el símbolo f como un puntero constante al primero de estos valores de flotación

Aunque la syntax de creación de matriz puede ser confusa, una matriz siempre tendrá un tamaño fijo conocido en tiempo de comstackción.

Por ejemplo, float f[] = {2,4,8} declara una matriz de longitud 3, equivalente a float f[ 3 ] = {2,4,8} . La dimensión se puede omitir por conveniencia: la longitud refleja el número de inicializadores sin forzar al progtwigdor a repetirlo explícitamente.

Desafortunadamente, la notación [] también puede referirse a punteros en algunas otras circunstancias (más sobre esto más adelante).

Notación y matrices de corchetes

La notación de paréntesis es la forma más natural de acceder a los contenidos de la matriz.

Cuando hace referencia a una matriz, el comstackdor sabe que es una matriz. A continuación, puede acceder a los datos basados ​​en el primer elemento de la matriz, así:

@f[ 3 ] = f + 3 * sizeof (float)

En el caso de arreglos unidimensionales (¡pero solo en ese caso!), Puede ver que el cálculo de la dirección es exactamente el mismo que para un puntero.

Arrays como punteros

Dado que una matriz también se considera un puntero (constante), puede usar una matriz para inicializar un puntero, aunque lo contrario es obviamente falso (ya que una matriz es un puntero constante y, como tal, su valor no se puede cambiar).

Ilustración

 void test (void) { float* f1; float f2[10]; float f3[]; // <-- compiler error : dimension not known float f4[] = {5,7}; // creates float f4[2] with f4[0]=5 and f4[1]=7 f1[3] = 1234; // <--- write to a random memory location. You're in trouble f2[3] = 5678; // write into the space reserved by the compiler // obtain 10 floats from the heap and set f1 to point to them f1 = (float *) calloc (10, sizeof(float)); f1[3] = 1234; // write into the space reserved by you // make f1 an alias of f2 (f1 will point to the same data as f2) f1 = f2; // f2 is a constant pointer to the array data printf ("%g", f1[3]); // will print "5678", as set through f2 // f2 cannot be changed f2 = f1; // <-- compiler error : incompatible types 'float[10]' / 'float *' } 

Going multidimensional

Extendamos nuestro ejemplo al caso bidimensional:

 float f2[3][10]; // 2d array of floats float ** f1; // pointer to pointer to float f1 = f2; // <-- the compiler should not allow that, but it does! f2[2][5] = 1234; // set some array value printf ("%g\n", f2[2][5]); // no problem accessing it printf ("%g\n",f1[2][5]); // bang you're dead 

veamos que pasó aquí

cuando declaras float f2[3][10] , el comstackdor asigna los 30 flotantes requeridos como un bloque contiguo. Los primeros 10 flotantes representan f [0], los siguientes diez f [1], etc.

Cuando escribe f2[ 2 ][ 5 ] , el comstackdor todavía sabe que f es una matriz , por lo que puede calcular la dirección efectiva de la flotación requerida de esta manera:

@f2[ 2 ][ 5 ] = f + ( 2 * 10 + 5 ) * sizeof (float)

También puede acceder a punteros a través de corchetes múltiples, siempre que el puntero tenga la cantidad adecuada de niveles de referencia:

Al hacer referencia al puntero, el comstackdor simplemente aplica aritmética de puntero en sucesión:

 float h = f1[2][5]; 

es equivalente a:

 float * g = f1[2]; // equivalent to g = *(f1+2) float h = g[5]; // equivalent to h = *(g +5) 

f1[ 2 ][ 5 ] es manejado por el comstackdor como *(*(f1+ 2 )+ 5 ) . La dirección final se computará así:

@f1[ 2 ][ 5 ] = *(f + 2 * sizeof (float *)) + 5 * sizeof (float)

Lo has pedido, lo tienes

Más allá de la misma notación de corchete se encuentran dos implementaciones muy diferentes.

Claramente, cuando se intenta acceder a los datos f2 través de f1 , los resultados serán catastróficos.

El comstackdor obtendrá el 3er float de f2[2] , lo considera como un puntero, agrega 20 a eso e intenta hacer referencia a la dirección resultante.

Si escribe algún valor a través de este tipo de puntero inicializado incorrectamente , considérese afortunado si obtiene una infracción de acceso en lugar de corromper silenciosamente algunos cuatro bytes aleatorios de memoria.

Desafortunadamente, a pesar de que la estructura de datos subyacente no se puede acceder correctamente a menos que el comstackdor sepa que f2 es una matriz, f2 todavía se considera un puntero float** constante.

En un mundo ideal no debería, pero en C (¡ay!), Lo es.

Significa que puede asignar un puntero a una matriz sin que el comstackdor se queje , aunque el resultado no tenga sentido.

Llamadas de función

Tanto las matrices como los punteros se pueden pasar como parámetros a las funciones.

Sin embargo, para evitar tales interpretaciones erróneas catastróficas como en el ejemplo anterior, debe dejar que el comstackdor sepa si lo que está pasando a la función es una matriz o un puntero.

Aquí nuevamente, debido a que el comstackdor considera una matriz como un puntero constante, se le permitirá hacer cosas tontas como declarar una matriz y pasarla a una función como un puntero.

Cuadrados vacíos

Para empeorar las cosas, la syntax de la statement de parámetros de funciones permite usar corchetes de una manera que hace aún más probable la confusión entre matrices y punteros.

 void f (float f1[]); 

se maneja exactamente como

 void f (float * f1); 

aunque la statement variable

 float f1[]; 

producirá un error en lugar de considerarlo como una forma alternativa de declarar float * f .

Se podría decir que la notación [] permite designar punteros , pero solo en los parámetros de función .

Por qué no se permite, ya que una statement de variable podría estar abierta al debate (entre otras cosas, sería ambigua con la syntax de statement de matriz inicializada float f[] = { ... } ), pero el resultado neto es ese parámetro de función decalaration introduce una notación que agrega otra capa de confusión.

Por ejemplo, el famoso parámetro argv puede declararse de cualquier forma extraña:

 int main (int argc, char ** argv) int main (int argc, char * argv[]) int main (int argc, char argv[][]) 

Por otro lado, siempre que tenga plena conciencia de la diferencia entre punteros y matrices, los corchetes vacíos son algo más convenientes que la notación del puntero, especialmente en ese caso:

 void fun (float f[][10]); // pointer to arrays of 10 floats 

la syntax del puntero equivalente te obliga a usar corchetes:

 void fun (float (* f)[10]); 

que no puedes evitar al declarar dicha variable:

 float (* f)[10]; // pointer to array of 10 floats float f[][10]; // <-- compiler error : array dimension not known 

Conclusión

En lo que respecta a los prototipos de función, puede elegir entre variantes sintácticas, pero si la variable que pasa a la función no coincide con el prototipo, todo terminará en lágrimas.

 float ** var1; // pointer to pointer to float float * var2[10]; // array of pointers to float float (* var3)[10]; // pointer to array of floats (mind the brackets!) float var4[10][10]; // array of arrays of floats (2d array of floats) // using empty brackets notation void fun1 (float f[ ][ ]); void fun2 (float f[10][ ]); void fun3 (float f[ ][10]); void fun4 (float f[10][10]); // using same syntax as for variables declaration void fun1 (float ** f); void fun2 (float * f[10]); void fun3 (float (* f)[10]); // <-- [] notation (arguably) easier to read void fun4 (float f[10][10]); // must always use square brackets in that case // even more choice for multiple level pointers void fun1 (float * f[]); // any funI (varJ) call with I != J will end up in tears 

Una última palabra de consejo

Por supuesto, es una cuestión de gusto personal, pero recomendaría el uso de typedef como una forma de obtener un poco más de abstracción y limitar el uso de rarezas sintácticas C a un mínimo.

 // type definition typedef float (* tWeirdElement)[10]; typedef tWeirdElement (* tWeirdo)[10]; // pointer to arrays of 10 pointers // to arrays of 10 floats // variable declaration tWeirdo weirdo; // parameter declaration void do_some_weird_things (tWeirdo weirdo); 

Primero aclare su confusión sobre matrices y punteros. Recuerde siempre que las matrices no son punteros .
Entre todas sus declaraciones, solo dos de ellas

 int array[NROWS][NCOLUMNS]; int (*array4)[NCOLUMNS]; 

son matrices. El rest de ellos son punteros, no matrices.

¿No es array1 y array2 una matriz de punteros?

No nunca. array1 y array2 son de tipo int ** , es decir, son de tipo puntero a puntero a entero .

Entonces, cuando los pasas a la f3 , todos los indicadores pasan?

No. Lo expliqué anteriormente que array1 y array2 no son una matriz de punteros.

Y sobre array2 cuando pasa a f2 , f2 tiene un puntero normal en argumentos formales, pero array2 es una matriz de punteros, entonces, ¿cómo accedería a las filas y columnas individuales al pasar una matriz de punteros a f2 ?

f2 espera un puntero a int como primer argumento. *array2 es un puntero a int . Por lo tanto, en la llamada

 f2(*array2, nrows, ncolumns); 

un puntero a int se pasa a f2 como su primer argumento, no a una matriz de punteros.

¿Y cómo accedería a filas y columnas individuales al pasar array4 que es un puntero a una serie de matrices 1D para la función f2 ?

Como está pasando un puntero al argumento de tipo int a f2 , puede acceder solo a la fila.