it-swarm.com.de

Wie weist der Compiler Speicher zu, ohne die Größe zum Zeitpunkt des Kompilierens zu kennen?

Ich habe ein C-Programm geschrieben, das Integer-Eingaben vom Benutzer akzeptiert, die als Größe eines Integer-Arrays verwendet werden. Mit diesem Wert wird ein Array mit der angegebenen Größe deklariert. Ich bestätige es, indem ich die Größe des Arrays überprüfe.

Code:

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

und überraschenderweise ist es richtig! Das Programm kann das Array mit der erforderlichen Größe erstellen.
.__ Alle statischen Speicherzuweisungen werden jedoch zur Kompilierzeit ausgeführt, und während der Kompilierzeit ist der Wert von n nicht bekannt. Wie kommt es, dass der Compiler Speicher mit der erforderlichen Größe zuordnen kann?

Wenn wir den benötigten Speicher einfach so zuordnen können, was ist dann die dynamische Zuordnung mit malloc() und calloc()?

66
Rahul

Dies ist keine "statische Speicherzuordnung". Ihr Array k ist ein Array mit variabler Länge (Variable Length Array, VLA). Dies bedeutet, dass der Speicher für dieses Array zur Laufzeit zugewiesen wird. Die Größe wird durch den Laufzeitwert von n bestimmt.

Die Sprachspezifikation schreibt keinen bestimmten Zuordnungsmechanismus vor, aber in einer typischen Implementierung wird Ihre Variable k normalerweise ein einfacher int *-Zeiger, wobei der tatsächliche Speicherblock zur Laufzeit auf dem Stack zugewiesen wird. 

Für einen VLA-Operator wird auch sizeof zur Laufzeit ausgewertet, weshalb Sie in Ihrem Experiment den korrekten Wert erhalten. Verwenden Sie einfach %zu (nicht %ld), um Werte des Typs size_t zu drucken.

Der Hauptzweck von malloc (und anderen dynamischen Speicherzuweisungsfunktionen) ist das Überschreiben der Gültigkeitsbereichs-basierten Gültigkeitsregeln, die für lokale Objekte gelten. Das heißt Speicher, der mit malloc zugewiesen wurde, bleibt "für immer" zugewiesen, oder bis Sie ihn explizit mit free freigeben. Mit malloc zugewiesener Speicher wird am Ende des Blocks nicht automatisch freigegeben.

VLA bietet, wie in Ihrem Beispiel, diese Funktionalität nicht. Ihr Array k gehorcht weiterhin regulären Gültigkeitsregeln für den Gültigkeitsbereich: seine Lebensdauer endet am Ende des Blocks. Aus diesem Grund kann VLA im Allgemeinen die Variable malloc und andere dynamische Speicherzuordnungsfunktionen nicht ersetzen.

In bestimmten Fällen, in denen Sie den Bereich nicht "besiegen" müssen und nur malloc verwenden, um ein Array mit Laufzeitgröße zuzuweisen, wird VLA möglicherweise als Ersatz für malloc angesehen. Denken Sie auch daran, dass VLAs normalerweise auf dem Stack zugewiesen werden und dass die Zuweisung großer Speicherblöcke auf dem Stack bis heute eine eher fragwürdige Programmierpraxis bleibt.

72
AnT

In C ist das Mittel, mit dem ein Compiler VLAs (Arrays mit variabler Länge) unterstützt, dem Compiler überlassen - er muss malloc() nicht verwenden und kann (und macht dies oft), was manchmal als "Stack" -Speicher bezeichnet wird, z. Verwenden systemspezifischer Funktionen wie alloca(), die nicht Teil von Standard C sind. Wenn Stack verwendet wird, ist die maximale Größe eines Arrays normalerweise viel geringer als mit malloc() möglich, da moderne Betriebssysteme Programmen eine viel geringere Stapelspeichermenge zulassen.

11
Peter

Der Speicher für Arrays mit variabler Länge kann eindeutig nicht statisch zugewiesen werden. Es kann jedoch auf dem Stapel zugewiesen werden. Im Allgemeinen beinhaltet dies die Verwendung eines "Rahmenzeigers", um die Position des Funktionsstapelrahmens angesichts dynamisch festgelegter Änderungen des Stapelzeigers zu verfolgen.

Wenn ich versuche, Ihr Programm zu kompilieren, scheint es, dass das Array mit variabler Länge tatsächlich optimiert wurde. Also habe ich Ihren Code geändert, um den Compiler zu zwingen, das Array tatsächlich zuzuweisen.

#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 kompiliert für Arm mit gcc 6.3 (Arm verwendet, weil ich Arm ASM lesen kann) kompiliert dies nach https://godbolt.org/g/5ZnHfa . (mein Kommentar)

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
plugwash

Der Speicher für dieses Konstrukt, das als "Array mit variabler Länge" (VLA) bezeichnet wird, wird auf ähnliche Weise wie alloca auf dem Stack zugewiesen. Wie dies genau abläuft, hängt davon ab, welchen Compiler Sie verwenden. Im Wesentlichen wird jedoch die Größe berechnet, wenn sie bekannt ist, und dann die Gesamtgröße [1] vom Stack-Pointer abgezogen.

Sie brauchen malloc und Freunde, da diese Zuordnung "stirbt", wenn Sie die Funktion verlassen. [Und es ist in Standard C++ nicht gültig]

[1] Für typische Prozessoren, die einen Stack verwenden, der "gegen Null wächst".

3
Mats Petersson

Wenn gesagt wird, dass der Compiler Speicherplatz für Variablen zur Compile-Zeit reserviert, bedeutet dies, dass die Platzierung dieser Variablen festgelegt und in den vom Compiler generierten ausführbaren Code eingebettet ist, nicht jedoch, dass der Compiler Platz für sie schafft während der Arbeit verfügbar ist ..__ Die tatsächliche dynamische Speicherzuweisung wird vom generierten Programm ausgeführt, wenn es ausgeführt wird.

0
Linkon