Pasar una matriz como argumento a una función en C

86

Escribí una función que contiene una matriz como argumento y la llamo pasando el valor de la matriz de la siguiente manera.

void arraytest(int a[])
{
    // changed the array a
    a[0]=a[0]+a[1];
    a[1]=a[0]-a[1];
    a[0]=a[0]-a[1];
}

void main()
{
    int arr[]={1,2};
    printf("%d \t %d",arr[0],arr[1]);
    arraytest(arr);
    printf("\n After calling fun arr contains: %d\t %d",arr[0],arr[1]);
}

Lo que encontré es que aunque estoy llamando a la arraytest()función pasando valores, int arr[]se cambia la copia original de .

¿Puede explicar por qué?

Mohan Mahajan
fuente
1
Está pasando la matriz por referencia pero está modificando su contenido, por lo que está viendo un cambio en los datos
Shaun Wilde
main()debe regresar int.
underscore_d

Respuestas:

137

Al pasar una matriz como parámetro, esto

void arraytest(int a[])

significa exactamente lo mismo que

void arraytest(int *a)

por lo que está modificando los valores en main.

Por razones históricas, las matrices no son ciudadanos de primera clase y no pueden pasarse por valor.

Bo Persson
fuente
3
¿Qué notación es mejor en qué circunstancias?
Ramon Martinez
20
@Ramon: usaría la segunda opción, ya que parece menos confusa e indica mejor que no obtiene una copia de la matriz.
Bo Persson
2
¿Puede explicar las "razones históricas"? Supongo que pasar por valores necesitaría una copia y, por lo tanto, una pérdida de memoria ... gracias
Jacquelyn.Marquardt
5
@lucapozzobon: originalmente, C no tenía ningún valor de paso, excepto los valores individuales. No fue hasta que structse agregó al lenguaje que esto se cambió. Y luego se consideró demasiado tarde para cambiar las reglas de las matrices. Ya había 10 de usuarios. :-)
Bo Persson
1
... significa exactamente lo mismo que, void arraytest(int a[1000])etc., etc. Respuesta ampliada aquí: stackoverflow.com/a/51527502/4561887 .
Gabriel Staples
9

Si desea pasar una matriz de una sola dimensión como argumento en una función , tendrá que declarar un parámetro formal de una de las siguientes tres formas y los tres métodos de declaración producen resultados similares porque cada uno le dice al compilador que un puntero entero va para ser recibido .

int func(int arr[], ...){
    .
    .
    .
}

int func(int arr[SIZE], ...){
    .
    .
    .
}

int func(int* arr, ...){
    .
    .
    .
}

Entonces, está modificando los valores originales.

Gracias !!!

Monirul Islam Milon
fuente
Estaba buscando su segundo ejemplo, ¿puede explicar cuáles son las ventajas de cada método?
Puck
8

No está pasando la matriz como copia. Es solo un puntero que apunta a la dirección donde el primer elemento de la matriz está en la memoria.

fyr
fuente
7

Estás pasando la dirección del primer elemento de la matriz

ob_dev
fuente
7

Las matrices en C se convierten, en la mayoría de los casos, en un puntero al primer elemento de la propia matriz. Y, más en detalle, las matrices que se pasan a funciones siempre se convierten en punteros.

Aquí una cita de K & R2nd :

Cuando se pasa un nombre de matriz a una función, lo que se pasa es la ubicación del elemento inicial. Dentro de la función llamada, este argumento es una variable local, por lo que un parámetro de nombre de matriz es un puntero, es decir, una variable que contiene una dirección.

Escritura:

void arraytest(int a[])

tiene el mismo significado que escribir:

void arraytest(int *a)

Entonces, a pesar de que no lo está escribiendo explícitamente, es porque está pasando un puntero y, por lo tanto, está modificando los valores en el archivo principal.

Para más, sugiero leer esto .

Además, puede encontrar otras respuestas sobre SO aquí

granmirupa
fuente
6

Está pasando el valor de la ubicación de memoria del primer miembro de la matriz.

Por lo tanto, cuando comienza a modificar la matriz dentro de la función, está modificando la matriz original.

Recuerda que a[1]es *(a+1).

alex
fuente
1
Supongo que faltan () para * a + 1 debería ser * (a + 1)
ShinTakezou
@Shin Gracias, ha pasado un tiempo desde que jugué con C.
alex
6

En C, excepto en algunos casos especiales, una referencia de matriz siempre "decae" a un puntero al primer elemento de la matriz. Por lo tanto, no es posible pasar una matriz "por valor". Una matriz en una llamada de función se pasará a la función como un puntero, que es análogo a pasar la matriz por referencia.

EDITAR: Hay tres casos especiales en los que una matriz no decae a un puntero a su primer elemento:

  1. sizeof ano es lo mismo que sizeof (&a[0]).
  2. &ano es lo mismo que &(&a[0])(y no es exactamente lo mismo que &a[0]).
  3. char b[] = "foo"no es lo mismo que char b[] = &("foo").
Thom Smith
fuente
Si paso una matriz a una función. Digamos, por ejemplo, que hice una matriz int a[10]y asigné un valor aleatorio a cada elemento. Ahora, si paso esta matriz a una función usando int y[]o int y[10]o, y int *yluego, en esa función, uso sizeof(y)Respuesta será el puntero de bytes que se ha asignado. Entonces, en este caso, se descompondrá como un puntero. Sería útil si incluye esto también. Ver este postimg.org/image/prhleuezd
Suraj Jain
Si utilizo sizeofoperar en la función en la matriz que definimos originalmente, entonces decaerá como una matriz, pero si paso otra función, entonces usará el sizeofoperador, decaerá como un puntero.
Suraj Jain
Sé que esto es viejo. Dos preguntas si alguien ve esto :) 1. @ThomSmith escribió que &ano es exactamente lo mismo que &a[0]cuando aes una matriz. ¿Cómo es eso? En mi programa de prueba, ambos muestran ser iguales, tanto en la función donde se declara la matriz como cuando se pasan a una función diferente. 2. El escritor escribe que "char b[] = "foo" no es lo mismo que char b[] = &("foo")". Para mí, este último ni siquiera se compila. ¿Se trata sólo de mí?
Aviv Cohn
6

Pasar una matriz multidimensional como argumento a una función. Pasar una matriz de un dim como argumento es más o menos trivial. Echemos un vistazo a un caso más interesante de pasar una matriz de 2 dim. En C, no puede usar un puntero para puntero construct ( int **) en lugar de 2 dim array. Hagamos un ejemplo:

void assignZeros(int(*arr)[5], const int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 5; j++) {
            *(*(arr + i) + j) = 0;
            // or equivalent assignment
            arr[i][j] = 0;
        }
    }

Aquí he especificado una función que toma como primer argumento un puntero a una matriz de 5 enteros. Puedo pasar como argumento cualquier matriz de 2 dim que tenga 5 columnas:

int arr1[1][5]
int arr1[2][5]
...
int arr1[20][5]
...

Puede tener una idea para definir una función más general que pueda aceptar cualquier matriz de 2 dim y cambiar la firma de la función de la siguiente manera:

void assignZeros(int ** arr, const int rows, const int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            *(*(arr + i) + j) = 0;
        }
    }
}

Este código se compilaría, pero obtendrá un error de tiempo de ejecución al intentar asignar los valores de la misma manera que en la primera función. Entonces, en C, una matriz multidimensional no es lo mismo que punteros a punteros ... a punteros. An int(*arr)[5]es un puntero a una matriz de 5 elementos, an int(*arr)[6] es un puntero a una matriz de 6 elementos, ¡y son punteros a diferentes tipos!

Bueno, ¿cómo definir argumentos de funciones para dimensiones superiores? Simple, ¡simplemente seguimos el patrón! Aquí está la misma función ajustada para tomar una matriz de 3 dimensiones:

void assignZeros2(int(*arr)[4][5], const int dim1, const int dim2, const int dim3) {
    for (int i = 0; i < dim1; i++) {
        for (int j = 0; j < dim2; j++) {
            for (int k = 0; k < dim3; k++) {
                *(*(*(arr + i) + j) + k) = 0;
                // or equivalent assignment
                arr[i][j][k] = 0;
            }
        }
    }
}

Como es de esperar, puede tomar como argumento 3 matrices dim que tengan en la segunda dimensión 4 elementos y en la tercera dimensión 5 elementos. Cualquier cosa como esta estaría bien:

arr[1][4][5]
arr[2][4][5]
...
arr[10][4][5]
...

Pero tenemos que especificar todos los tamaños de dimensiones hasta el primero.

Andrushenko Alexander
fuente
5

Uso de matriz estándar en C con decaimiento de tipo natural de matriz a ptr

@Bo Persson afirma correctamente en su gran respuesta aquí :

Al pasar una matriz como parámetro, esto

void arraytest(int a[])

significa exactamente lo mismo que

void arraytest(int *a)

Sin embargo, permítame agregar también que las dos formas anteriores también:

  1. significa exactamente lo mismo que

     void arraytest(int a[0])
    
  2. que significa exactamente lo mismo que

     void arraytest(int a[1])
    
  3. que significa exactamente lo mismo que

     void arraytest(int a[2])
    
  4. que significa exactamente lo mismo que

     void arraytest(int a[1000])
    
  5. etc.

En cada uno de los ejemplos de arreglos anteriores, el tipo de parámetro de entrada decae a anint * , y se puede llamar sin advertencias ni errores, incluso con las opciones de compilación -Wall -Wextra -Werroractivadas (consulte mi repositorio aquí para obtener detalles sobre estas 3 opciones de compilación), como esta:

int array1[2];
int * array2 = array1;

// works fine because `array1` automatically decays from an array type
// to `int *`
arraytest(array1);
// works fine because `array2` is already an `int *` 
arraytest(array2);

Como cuestión de hecho, el valor "tamaño" ( [0], [1], [2], [1000], etc.) dentro del parámetro de matriz aquí es aparentemente sólo por propósitos / estético auto-documentación, y puede ser cualquier número entero positivo (size_t tipo I creo) que usted quiera!

En la práctica, sin embargo, debe usarlo para especificar el tamaño mínimo de la matriz que espera que reciba la función, de modo que al escribir código sea fácil de rastrear y verificar. El estándar MISRA-C-2012 ( compre / descargue el PDF de la versión 2012 de 236 páginas del estándar por £ 15.00 aquí ) va tan lejos como para afirmar (énfasis agregado):

Regla 17.5 El argumento de la función correspondiente a un parámetro declarado que tiene un tipo de matriz deberá tener un número apropiado de elementos.

...

Si un parámetro se declara como una matriz con un tamaño específico, el argumento correspondiente en cada llamada de función debe apuntar a un objeto que tenga al menos tantos elementos como la matriz.

...

El uso de un declarador de matriz para un parámetro de función especifica la interfaz de la función con más claridad que el uso de un puntero. El número mínimo de elementos esperado por la función se indica explícitamente, mientras que esto no es posible con un puntero.

En otras palabras, recomiendan usar el formato de tamaño explícito, aunque el estándar C técnicamente no lo hace cumplir, al menos ayuda a aclararle a usted como desarrollador y a otros que usan el código qué tamaño de matriz espera la función. que pases.


Forzar la seguridad de tipos en matrices en C

Como señala @Winger Sendon en un comentario debajo de mi respuesta, podemos obligar a C a tratar un tipo de matriz para que sea diferente según el tamaño de la matriz !

Primero, debe reconocer que en mi ejemplo anterior, el uso de int array1[2];este tipo de: arraytest(array1);hace array1que se descomponga automáticamente en un int *. SIN EMBARGO, si toma la dirección de en su array1 lugar y llama arraytest(&array1), ¡obtendrá un comportamiento completamente diferente! ¡Ahora, NO se descompone en un int *! En cambio, el tipo de &array1es int (*)[2], que significa "puntero a una matriz de tamaño 2 de int" , o "puntero a una matriz de tamaño 2 de tipo int" . Por lo tanto, puede FORZAR a C para verificar la seguridad de tipos en una matriz, como esta:

void arraytest(int (*a)[2])
{
    // my function here
}

Esta sintaxis es difícil de leer, pero similar a la de un puntero de función . La herramienta en línea, cdecl , nos dice que eso int (*a)[2]significa: "declarar como puntero a la matriz 2 de int" (puntero a la matriz de 2 ints). NO confunda esto con la versión sin paréntesis:, int * a[2]que significa: "declare a como matriz 2 de puntero a int" (matriz de 2 punteros a int).

Ahora, esta función REQUIERE que la llames con el operador de dirección ( &) así, usando como parámetro de entrada un PUNTERO A UN ARREGLO DEL TAMAÑO CORRECTO !:

int array1[2];

// ok, since the type of `array1` is `int (*)[2]` (ptr to array of 
// 2 ints)
arraytest(&array1); // you must use the & operator here to prevent
                    // `array1` from otherwise automatically decaying
                    // into `int *`, which is the WRONG input type here!

Esto, sin embargo, producirá una advertencia:

int array1[2];

// WARNING! Wrong type since the type of `array1` decays to `int *`:
//      main.c:32:15: warning: passing argument 1 of ‘arraytest’ from 
//      incompatible pointer type [-Wincompatible-pointer-types]                                                            
//      main.c:22:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
arraytest(array1); // (missing & operator)

Puede probar este código aquí .

Para forzar al compilador de C a convertir esta advertencia en un error, de modo que siempre DEBE llamar arraytest(&array1);usando solo una matriz de entrada del tamaño y tipo correctos ( int array1[2];en este caso), agregue -Werrora sus opciones de compilación. Si está ejecutando el código de prueba anterior en onlinegdb.com, haga clic en el icono de engranaje en la parte superior derecha y haga clic en "Banderas extra del compilador" para escribir esta opción. Ahora, esta advertencia:

main.c:34:15: warning: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Wincompatible-pointer-types]                                                            
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’    

se convertirá en este error de compilación:

main.c: In function ‘main’:
main.c:34:15: error: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Werror=incompatible-pointer-types]
     arraytest(array1); // warning!
               ^~~~~~
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
 void arraytest(int (*a)[2])
      ^~~~~~~~~
cc1: all warnings being treated as errors

Tenga en cuenta que también puede crear punteros de "tipo seguro" para matrices de un tamaño determinado, como este:

int array[2];
// "type safe" ptr to array of size 2 of int:
int (*array_p)[2] = &array;

... pero no lo recomiendo necesariamente , ya que me recuerda muchas de las payasadas de C ++ que se usan para forzar la seguridad de los tipos en todas partes, con el costo excepcionalmente alto de la complejidad de la sintaxis del lenguaje, la verbosidad y la dificultad para diseñar el código, y que no me gusta y he despotricado muchas veces antes (por ejemplo, consulte "Mis pensamientos sobre C ++" aquí ).


Para pruebas y experimentación adicionales, consulte también el enlace que se encuentra a continuación.

Referencias

Vea los enlaces de arriba. También:

  1. Mi experimentación de código en línea: https://onlinegdb.com/B1RsrBDFD
Gabriel Staples
fuente
2
void arraytest(int (*a)[1000])es mejor porque entonces el compilador generará un error si el tamaño es incorrecto.
Extremo Sendon
@WingerSendon, sabía que había algunas sutilezas que necesitaba verificar aquí, y que la sintaxis es confusa (como la sintaxis de una función ptr es confusa), así que me tomé mi tiempo y finalmente actualicé mi respuesta con una gran sección nueva titulada Forcing type safety on arrays in C, que cubre su punto.
Gabriel Staples
0

Las matrices siempre se pasan por referencia si usa a[]o *a:

int* printSquares(int a[], int size, int e[]) {   
    for(int i = 0; i < size; i++) {
        e[i] = i * i;
    }
    return e;
}

int* printSquares(int *a, int size, int e[]) {
    for(int i = 0; i < size; i++) {
        e[i] = i * i;
    }
    return e;
}
anil karikatti
fuente
Estoy votando esto. No estoy seguro de por qué se votó en contra.
Gabriel Staples