Tipo de matriz – Reglas para asignación / uso como parámetro de función

cuando necesito pasar una matriz a una función, parece que funcionarán todas las siguientes declaraciones de la función

void f(int arr[]) void f(int arr[4]) // is this one correct? 

para esto:

 int a[]={1,2,3,4}; f(a); 

Pero cuando asigno una matriz a otra matriz, falla

 int a[]={1,2,3,4}; int b[4] = a; // error: array must be initialized with a brace-enclosed initializer 

Entonces, ¿por qué una matriz aprobada como argumento de una función está bien, pero se usa en el rhs de asignación simple?

Para entender la diferencia, necesitamos entender dos contextos diferentes.

  • En contextos de valor , el nombre de una matriz de tipo T es equivalente a un puntero para escribir T , y es igual a un puntero al primer elemento de la matriz.
  • En contextos de objeto , el nombre de una matriz de tipo T no se reduce a un puntero.

¿Qué es el contexto del objeto?

En a = b; , a está en el contexto del objeto. Cuando tomó la dirección de una variable, se usa en el contexto del objeto. Finalmente, cuando usa el operador sizeof en una variable, se usa en el contexto del objeto. En todos los demás casos, una variable se usa en contexto de valor.

Ahora que tenemos este conocimiento, cuando lo hagamos:

 void f(int arr[4]); 

Es exactamente equivalente a

 void f(int *arr); 

Como descubriste, podemos omitir el tamaño (4 arriba) de la statement de la función. Esto significa que no puede saber el tamaño de la “matriz” pasada a f() . Más tarde, cuando lo haga:

 int a[]={1,2,3,4}; f(a); 

En la llamada de función, el nombre a está en contexto de valor, por lo que se reduce a un puntero a int . Esto es bueno, porque f espera un puntero a un int , por lo que la definición y el uso de la función coinciden. Lo que se pasa a f() es el puntero al primer elemento de a ( &a[0] ).

En el caso de

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

El nombre b se usa en un contexto de objeto y no se reduce a un puntero. (Por cierto, a aquí está en un contexto de valor, y se reduce a un puntero).

Ahora, int b[4]; asigna un valor de almacenamiento de 4 int s y le da el nombre b . a también se le asignó un almacenamiento similar. Entonces, en efecto, la asignación anterior significa, “Quiero que la ubicación de almacenamiento sea la misma que la ubicación anterior”. Esto no tiene sentido.

Si desea copiar el contenido de a en b , puede hacer:

 #include  int b[4]; memcpy(b, a, sizeof b); 

O bien, si desea un puntero b que apunte a:

 int *b = a; 

Aquí, a está en contexto de valor y se reduce a un puntero a int , por lo que podemos asignar a a int * .

Finalmente, al inicializar una matriz , puede asignarle valores explícitos:

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

Aquí, a tiene 4 elementos, inicializados a 1, 2, 3 y 4. También puede hacer:

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

Si hay menos elementos en la lista que el número de elementos en la matriz, entonces el rest de los valores se toman como 0:

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

establece a[2] y a[3] en 0.

 void f(int arr[]); void f(int arr[4]); 

La syntax es engañosa. Ambos son lo mismo que esto:

 void f(int *arr); 

es decir, está pasando un puntero al comienzo de la matriz. No estás copiando la matriz.

C no admite la asignación de matrices. En el caso de una llamada de función, la matriz se desintegra a un puntero. C no admite la asignación de punteros. Esto se pregunta aquí todos los días: ¿qué libro de texto de C leen ustedes que no explica esto?

Prueba memcpy.

 int a[]={1,2,3,4}; int b[4]; memcpy(b, a, sizeof(b)); 

Gracias por señalar eso, Steve, ha pasado un tiempo desde que usé C.

Para obtener su intuición al respecto, debe comprender lo que está sucediendo a nivel de la máquina.

La semántica de inicialización (= {1,2,3,4}) significa “ponerlo en su imagen binaria exactamente de esta manera” para que pueda comstackrse.

La asignación de la matriz sería diferente: el comstackdor tendría que traducirla a un bucle, que en realidad iteraría sobre los elementos. El comstackdor de C (o C ++, para el caso) nunca hace tal cosa. Con razón, espera que lo hagas tú mismo. ¿Por qué? Porque tú puedes. Entonces, debería ser una subrutina, escrita en C (memcpy). Todo esto se trata de la simplicidad y la cercanía de tu arma, que se trata de C y C ++.

Tenga en cuenta que el tipo de a en int a[4] es int [4] .

Pero TypeOf ( &a ) == int (*)[4] ! = int [4] .

También tenga en cuenta que el tipo del valor de a es int * , que es distinto de todo lo anterior.

Aquí hay un progtwig de muestra que puedes probar:

 int main() { // All of these are different, incompatible types! printf("%d\n", sizeof (int[4])); // 16 // These two may be the same size, but are *not* interchangeable! printf("%d\n", sizeof (int (*)[4])); // 4 printf("%d\n", sizeof (int *)); // 4 } 

Quiero aclarar. Hay algunas pistas engañosas en las respuestas … Todas las siguientes funciones pueden tomar matrices de enteros:

 void f(int arr[]) void f(int arr[4]) void f(int *arr) 

Pero los argumentos formales no son lo mismo. Entonces el comstackdor puede manejarlos de manera diferente. En el sentido de administración de memoria interna, todos los argumentos conducen a indicadores.

 void f(int arr[]) 

… f () toma una matriz de cualquier tamaño.

 void f(int arr[4]) 

… El argumento formal indica un tamaño de matriz.

 void f(int *arr) 

… También puede pasar un puntero entero. f () no sabe nada sobre el tamaño.