Diferencia entre el tipo de matriz y la matriz asignada con malloc

Hoy estaba ayudando a un amigo mío con un código C, y he encontrado un comportamiento extraño que no podía explicarle por qué estaba sucediendo. Teníamos un archivo TSV con una lista de enteros, con un int cada línea. La primera línea era la cantidad de líneas que tenía la lista.

También teníamos un archivo ac con un “archivo de lectura” muy simple. La primera línea se leyó a n, el número de líneas, luego hubo una inicialización de:

int list[n] 

y finalmente un bucle for de n con fscanf.

Para pequeñas n (hasta ~ 100.000), todo estaba bien. Sin embargo, hemos encontrado que cuando n era grande (10 ^ 6), se producía un segfault.

Finalmente, cambiamos la inicialización de la lista a

 int *list = malloc(n*sizeof(int)) 

y todo cuando está bien, incluso con n muy grande.

¿Alguien puede explicar por qué ocurrió esto? ¿Qué estaba causando segfault with int list [n], que se detuvo cuando empezamos a usar list = malloc (n * sizeof (int))?

Aquí hay varias piezas diferentes en juego.

El primero es la diferencia entre declarar una matriz como

 int array[n]; 

y

 int* array = malloc(n * sizeof(int)); 

En la primera versión, declaras un objeto con duración de almacenamiento automático. Esto significa que la matriz vive solo mientras exista la función que la llama. En la segunda versión, obtiene memoria con duración de almacenamiento dynamic, lo que significa que existirá hasta que se desasigne explícitamente de forma free .

La razón por la que la segunda versión funciona aquí es un detalle de implementación de cómo C generalmente se comstack. Normalmente, la memoria C se divide en varias regiones, incluida la stack (para llamadas a funciones y variables locales) y el montón (para objetos malloc ). La stack generalmente tiene un tamaño mucho más pequeño que el montón; por lo general, es algo así como 8 MB. Como resultado, si intenta asignar una gran matriz con

 int array[n]; 

Entonces puede exceder el espacio de almacenamiento de la stack, causando la segfault. Por otro lado, el montón generalmente tiene un gran tamaño (por ejemplo, tanto espacio como libre en el sistema), por lo que el malloc un objeto grande no causará un error de falta de memoria.

En general, tenga cuidado con las matrices de longitud variable en C. Pueden exceder fácilmente el tamaño de la stack. Prefiere malloc menos que sepas que el tamaño es pequeño o que realmente solo quieres la matriz por un corto período de tiempo.

¡Espero que esto ayude!

 int list[n] 

Asigna espacio para n enteros en la stack , que generalmente es bastante pequeño. El uso de memoria en la stack es mucho más rápido que la alternativa, pero es bastante pequeño y es fácil desbordar la stack (es decir, asignar demasiada memoria) si hace cosas como asignar matrices enormes o hacer recursiones demasiado profundas. No tiene que desasignar manualmente la memoria asignada de esta manera, sino que lo hace el comstackdor cuando la matriz se sale del scope.

malloc por otro lado, asigna espacio en el montón , que suele ser muy grande en comparación con la stack. Tendrá que asignar una cantidad mucho mayor de memoria en el montón para agotarlo, pero es mucho más lento asignar memoria en el montón que en la stack, y debe desasignarlo manualmente de forma free cuando haya terminado de usar eso.

int list [n] almacena los datos en la stack, mientras que malloc los almacena en el montón.

La stack es limitada y no hay mucho espacio, mientras que el montón es mucho más grande.

int list[n] es un VLA, que asigna en la stack en lugar de en el montón. No tiene que liberarlo (se libera automáticamente al final de la llamada a la función) y se asigna rápidamente, pero el espacio de almacenamiento es muy limitado, como ha descubierto. Debe asignar valores más grandes en el montón.

Esta statement asigna memoria en la stack

  int list[n] 

malloc asigna en el montón.

El tamaño de stack suele ser más pequeño que el montón, por lo que si asigna demasiada memoria a la stack, obtiene un stackoverflow.

Ver también esta respuesta para más información

Suponiendo que tiene una implementación típica en su implementación, lo más probable es que:

 int list[n] 

lista asignada en tu stack, donde como:

 int *list = malloc(n*sizeof(int)) 

memoria asignada en tu montón.

En el caso de una stack, normalmente existe un límite de la capacidad de crecimiento (si es que pueden crecer). En el caso de un montón, todavía hay un límite, pero eso tiende a ser en gran medida y (en general) restringido por su espacio de direcciones RAM + swap + que normalmente es al menos un orden de magnitud mayor, si no más.

Cuando asigna utilizando un malloc , la memoria se asigna desde el montón y no desde la stack, que tiene un tamaño mucho más limitado.

Si está en Linux, puede establecer ulimit -s a un valor mayor y esto también podría funcionar para la asignación de la stack. Cuando asigna memoria en la stack, esa memoria permanece hasta el final de la ejecución de su función. Si asigna memoria en heap (utilizando malloc), puede liberar la memoria cuando lo desee (incluso antes de que finalice la ejecución de su función).

Generalmente, heap debe usarse para asignaciones de memoria grandes.

  int array[n]; 

Es un ejemplo de matriz asignada estáticamente y en el momento de la comstackción se conocerá el tamaño de la matriz. Y la matriz se asignará en la stack.

  int *array(malloc(sizeof(int)*n); 

Es un ejemplo de matriz asignada dinámicamente y el usuario conocerá el tamaño de la matriz en el momento de la ejecución. Y la matriz se asignará en el montón.