¿Cómo asigna el compilador la memoria sin saber el tamaño en tiempo de compilación?


Escribí un programa en C que acepta la entrada de enteros del usuario, que se usa como el tamaño de una matriz entera, y usando ese valor declara una matriz de un tamaño dado, y lo estoy confirmando comprobando el tamaño de la matriz.

Código:

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%ld",sizeof(k));
    return 0;
}

Y, sorprendentemente, es correcto! El programa es capaz de crear la matriz del tamaño requerido.
Pero toda la asignación de memoria estática se realiza en tiempo de compilación, y durante el tiempo de compilación el valor de n no se conoce, así que ¿cómo compilador es capaz de asignar la memoria del tamaño requerido?

Si podemos asignar la memoria requerida así como así, entonces ¿cuál es el uso de la asignación dinámica usando malloc() y calloc()?

Author: Banex, 2017-09-24

5 answers

Esto no es una "asignación de memoria estática". Su matriz k es una Matriz de Longitud Variable (VLA), lo que significa que la memoria para esta matriz se asigna en tiempo de ejecución. El tamaño será determinado por el valor de tiempo de ejecución de n.

La especificación del lenguaje no dicta ningún mecanismo de asignación específico, pero en una implementación típica, su k generalmente terminará siendo un simple puntero int * con el bloque de memoria real asignado en la pila en tiempo de ejecución.

Para un operador VLA sizeof también se evalúa en tiempo de ejecución, por lo que obtiene el valor correcto en su experimento. Simplemente use %zu (no %ld) para imprimir valores de tipo size_t.

El propósito principal de malloc (y otras funciones de asignación de memoria dinámica) es anular las reglas de vida basadas en el ámbito, que se aplican a los objetos locales. Es decir, la memoria asignada con malloc permanece asignada "para siempre", o hasta que la desasigne explícitamente con free. Memoria asignada con malloc does no se desasignará automáticamente al final del bloque.

El VLA, como en su ejemplo, no proporciona esta funcionalidad de "derrota de alcance". Su array k sigue obedeciendo reglas de vida regulares basadas en el ámbito: su vida termina al final del bloque. Por esta razón, en general, VLA no puede reemplazar malloc y otras funciones de asignación de memoria dinámica.

Pero en casos específicos cuando no necesita "derrotar el alcance" y solo use malloc para asignar un tamaño de tiempo de ejecución array, VLA podría de hecho ser visto como un reemplazo para malloc. Solo tenga en cuenta, una vez más, que los VLA se asignan típicamente en la pila y la asignación de grandes trozos de memoria en la pila hasta el día de hoy sigue siendo una práctica de programación bastante cuestionable.

 72
Author: AnT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-24 20:24:42

En C, el medio por el cual un compilador soporta VLAs (arrays de longitud variable) depende del compilador - no tiene que usar malloc(), y puede (y a menudo lo hace) usar lo que a veces se llama memoria "stack" - por ejemplo, usando funciones específicas del sistema como alloca() que no son parte del estándar C. Si usa stack, el tamaño máximo de un array es típicamente mucho más pequeño que el posible usando malloc(), porque los sistemas operativos modernos permiten a los programas una cuota mucho menor de memoria de pila.

 11
Author: Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-24 06:21:37

La memoria para matrices de longitud variable claramente no se puede asignar estáticamente. Sin embargo, se puede asignar en la pila. Generalmente esto implica el uso de un" puntero de marco " para realizar un seguimiento de la ubicación del marco de la pila de funciones frente a los cambios dinámicamente determinados en el puntero de la pila.

Cuando intento compilar su programa parece que lo que realmente sucede es que la matriz de longitud variable se optimizó. Así que modifiqué tu código para forzar al compilador a asignar la matriz.

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%s %ld",k,sizeof(k));
    return 0;
}

Godbolt compilando para arm usando gcc 6.3 (usando arm porque puedo leer arm ASM) compila esto a https://godbolt.org/g/5ZnHfa . (comentarios míos)

main:
        push    {fp, lr}      ; Save fp and lr on the stack
        add     fp, sp, #4    ; Create a "frame pointer" so we know where
                              ; our stack frame is even after applying a 
                              ; dynamic offset to the stack pointer.
        sub     sp, sp, #8    ; allocate 8 bytes on the stack (8 rather
                              ; than 4 due to ABI alignment
                              ; requirements)
        sub     r1, fp, #8    ; load r1 with a pointer to n
        ldr     r0, .L3       ; load pointer to format string for scanf
                              ; into r0
        bl      scanf         ; call scanf (arguments in r0 and r1)
        ldr     r2, [fp, #-8] ; load r2 with value of n
        ldr     r0, .L3+4     ; load pointer to format string for printf
                              ; into r0
        lsl     r2, r2, #2    ; multiply n by 4
        add     r3, r2, #10   ; add 10 to n*4 (not sure why it used 10,
                              ; 7 would seem sufficient)
        bic     r3, r3, #7    ; and clear the low bits so it is a
                              ; multiple of 8 (stack alignment again) 
        sub     sp, sp, r3    ; actually allocate the dynamic array on
                              ; the stack
        mov     r1, sp        ; store a pointer to the dynamic size array
                              ; in r1
        bl      printf        ; call printf (arguments in r0, r1 and r2)
        mov     r0, #0        ; set r0 to 0
        sub     sp, fp, #4    ; use the frame pointer to restore the
                              ; stack pointer
        pop     {fp, lr}      ; restore fp and lr
        bx      lr            ; return to the caller (return value in r0)
.L3:
        .word   .LC0
        .word   .LC1
.LC0:
        .ascii  "%d\000"
.LC1:
        .ascii  "%s %ld\000"
 10
Author: plugwash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-25 13:16:47

La memoria para esta construcción, que se llama "variable length array", VLA, se asigna en la pila, de manera similar a alloca. Exactamente cómo sucede esto depende exactamente de qué compilador está utilizando, pero esencialmente es un caso de calcular el tamaño cuando se conoce, y luego restar [1] el tamaño total del puntero de pila.

Necesitas malloc y amigos porque esta asignación "muere" cuando dejas la función. [Y no es válido en C++estándar]

[1] Para procesadores típicos que usan una pila que "crece hacia cero".

 3
Author: Mats Petersson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-24 06:10:20

Cuando se dice que el compilador asigna memoria para variables en tiempo de compilación, significa que la colocación de esas variables se decide y se incrusta en el código ejecutable que genera el compilador, no que el compilador esté haciendo espacio para ellas disponible mientras funciona. La asignación de memoria dinámica real se lleva a cabo por el programa generado cuando se ejecuta.

 0
Author: Linkon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-26 13:11:36