2016-09-12 7 views
3

Abhängig von der Version des C-Compilers und Compiler-Flags ist es möglich, Variablen an beliebigen Stellen in Ihren Funktionen zu initialisieren (soweit mir bekannt ist).Speicherzuordnung von Funktionen und Variablen in C

Ich bin es gewohnt, alle Variablen an der Spitze der Funktion zu setzen, aber die Diskussion über die Speicherbenutzung der Variablen begann, wenn an irgendeiner anderen Stelle in der Funktion definiert.

Im Folgenden habe ich 2 kurze Beispiele geschrieben, und ich fragte mich, ob jemand mir erklären könnte (oder verifizieren), wie der Speicher zugewiesen wird.

Beispiel 1: Die Variable y ist nach einer möglichen return-Anweisung definiert, es besteht die Möglichkeit, dass diese Variable aus diesem Grund nicht verwendet wird, soweit mir bekannt ist und der Code (Speicherbelegung)) wäre dasselbe, wenn die Variable an der Spitze der Funktion platziert wäre. Ist das richtig?

Beispiel 2: Die Variable x wird in einer Schleife initialisiert, was bedeutet, dass der Gültigkeitsbereich dieser Variablen nur innerhalb dieser Schleife liegt, aber was ist mit der Speicherbenutzung dieser Variablen? Wäre es anders, wenn es oben auf den Funktionen platziert würde? Oder einfach auf dem Stack beim Funktionsaufruf initialisiert?

Edit: Zum Abschluss einer Hauptfrage: Hat das Reduzieren des Gültigkeitsbereichs der Variablen oder das Ändern des Speicherorts der ersten Verwendung (also anderswo statt oben) irgendwelche Auswirkungen auf den Speicherverbrauch?

Codebeispiel 1

static void Function(void){ 
uint8_t x = 0; 

//code changing x 
if(x == 2) 
{ 
    return; 
} 

uint8_t y = 0;  
//more code changing y 
} 

Code-Beispiel 2

static void LoopFunction(void){ 
uint8_t i = 0; 

for(i =0; i < 100; i ++) 
{ 
    uint8_t x = i; 
    // do some calculations 
    uartTxLine("%d", x); 
} 

//more code 
} 

Antwort

5

Ich benutze es, alle Variablen am Anfang der Funktion

Diese verwendet zu setzen in den älteren Versionen von C erforderlich sein, aber moderne Compiler haben diese Anforderung fallen gelassen. Solange sie den Typ der Variablen zum Zeitpunkt ihrer ersten Verwendung kennen, haben die Compiler alle Informationen, die sie benötigen.

Ich fragte mich, ob jemand mir erklären könnte, wie der Speicher zugewiesen wird.

Der Compiler entscheidet, wie im automatischen Speicherbereich Speicher zugewiesen wird. Implementierungen sind nicht auf den Ansatz beschränkt, der jede Variable, die Sie als separate Position deklarieren, angibt. Sie dürfen Orte von Variablen, die außerhalb des Geltungsbereichs liegen, sowie Variablen, die nach einem bestimmten Punkt nicht mehr verwendet werden, wiederverwenden.

In Ihrem ersten Beispiel variable y, um den Raum früher besetzt durch variable x, weil der erste Punkt der Verwendung von y ist nach dem letzten Punkt der Verwendung von x zu nutzen.

In Ihrem zweiten Beispiel der Raum für x innerhalb der Schleife verwendet wird, kann für andere Variablen wiederverwendet werden, die Sie im // more code Bereich erklären kann.

+0

In Ordnung, so bedeutet dies, es tatsächlich effizienter ist es auf diese Weise zu codieren, anstatt alle Variablen an der Spitze der Putten, wo es nicht klar sein kann, wenn es aus ist Umfang oder wann wird es verwendet werden? Oder ist das noch egal? – koldewb

+6

@koldewb Es ist nicht wichtig für den Compiler, weil es den Bereich der aktiven Verwendung Ihrer Variablen herausfinden und Speicher entsprechend zuordnen kann. Es ist jedoch sehr wichtig für Leute, die Ihren Code lesen, also ist es besser, Variablen an Orten zu platzieren, an denen sie verwendet werden, als alles oben zu deklarieren. – dasblinkenlight

1

Grundsätzlich läuft die Geschichte so. Wenn Sie eine Funktion im rohen Assembler aufrufen, ist es üblich, alles, was von der Funktion beim Eingeben der Funktion auf dem Stapel verwendet wird, zu speichern und beim Verlassen aufzuräumen. Bestimmte CPUs und ABIs können eine Aufrufkonvention aufweisen, die ein automatisches Stapeln von Parametern beinhaltet.

Wahrscheinlich C und viele andere alte Sprachen hatte die Voraussetzung, dass alle Variablen an der Spitze der Funktion (oder oben auf dem Bereich) deklariert werden müssen, so dass die { } Push/Pop auf dem Stapel widerspiegeln .

Irgendwo in den 80er/90er Jahren begannen Compiler, solchen Code effizient zu optimieren, da sie nur Platz für eine lokale Variable an dem Punkt, an dem sie zuerst verwendet wurde, zuweisen und sie wieder freigeben, wenn sie nicht mehr verwendet wurde dafür. Unabhängig davon, wo diese Variable deklariert wurde - für den optimierenden Compiler war das egal.

Ungefähr zur gleichen Zeit hob C++ die Variablendeklarationseinschränkungen auf, die C hatte, und ließ Variablen überall deklarieren. Allerdings hat C dies vor dem Jahr 1999 mit dem aktualisierten C99-Standard nicht behoben. Im modernen C können Sie Variablen überall deklarieren.

Es gibt also absolut keinen Leistungsunterschied zwischen Ihren beiden Beispielen, es sei denn, Sie verwenden einen unglaublich alten Compiler. Es wird jedoch als gute Programmierpraxis angesehen, den Umfang einer Variablen so weit wie möglich einzuschränken - obwohl dies nicht auf Kosten der Lesbarkeit erfolgen sollte.

Obwohl es nur eine Frage des Stils ist, würde ich persönlich vorziehen, Ihre Funktion wie folgt zu schreiben:

(beachten Sie, dass Sie die falsche printf Formatbezeichner für uint8_t verwenden)

#include <inttypes.h> 

static void LoopFunction (void) 
{ 
    for(uint8_t i=0; i < 100; i++) 
    { 
    uint8_t x = i; 
    // do some calculations 
    uartTxLine("%" PRIu8, x); 
    } 

//more code 
} 
0

Alt C darf nur Variablen an der Spitze eines Blocks deklarieren (und initialisieren). Sie, wo ein neuer Block (ein Paar { und } Zeichen) überall innerhalb eines Blocks init erlaubt, so musste man dann die Möglichkeit, Variablen zu deklarieren neben dem Code mit ihnen:

... /* inside a block */ 
{ int x = 3; 
    /* use x */ 
} /* x is not adressabel past this point */ 

Und du, wo zulässig dies geschieht in switch Aussagen, if Aussagen und while und do Aussagen (überall dort, wo Sie einen neuen Block init können)

Nun sind Sie erlauben eine Variable zu deklarieren überall dort, wo eine Anweisung erlaubt ist, und der Umfang dieser variablen geht von der Deklaration bis zum Ende des Inneren ted Block, in den Sie es deklariert haben.

Compiler entscheiden, wenn sie Speicher für lokale Variablen zuweisen, so dass Sie alle zugeordnet werden können, wenn Sie einen Stapelrahmen erstellen (dies ist die gcc Weise, da lokale Variablen nur einmal zugeordnet) oder wenn Sie in den Block eingeben definition (Beispiel: Microsoft C funktioniert so) Wenn Sie Speicherplatz zur Laufzeit zuweisen, müssen Sie den Stack-Pointer zur Laufzeit vorschieben. Wenn Sie also nur einmal pro Stack-Frame vorgehen, sparen Sie CPU-Zyklen (aber verschwenden Speicherplätze). Wichtig ist hier, dass Sie nicht auf einen Variablenstandort außerhalb seiner Bereichsdefinition verweisen dürfen. Wenn Sie dies also versuchen, erhalten Sie undefiniertes Verhalten. Ich habe über lange Zeit einen alten Fehler entdeckt, der über das Internet läuft, weil sich niemand die Zeit nimmt, das Programm mit dem Microsoft-C-Compiler (der in einem Core-Dump fehlgeschlagen ist) zu kompilieren, anstatt es mit GCC zu kompilieren. Der Code verwendete eine lokale Variable in einem inneren Bereich (der then Teil einer if-Anweisung) durch Verweis in einem anderen Teil des Codes (wie alles auf main Funktion war, war der Stapelrahmen die ganze Zeit vorhanden) Microsoft-C hat den Platz beim Verlassen der if Anweisung nur neu zugewiesen, aber GCC hat darauf gewartet, bis main fertig ist.Der Fehler wurde behoben, indem einfach ein Modifikator static zu der Variablendeklaration hinzugefügt wurde (was es global machte) und kein Refactoring mehr nötig war.

int main() 
{ 
    struct bla_bla *pointer_to_x; 
    ... 
    if (something) { 
     struct bla_bla x; 
     ... 
     pointer_to_x = &x; 
    } 
    /* x does not exist (but it did in gcc) */ 
    do_something_to_bla_bla(pointer_to_x); /* wrong, x doesn't exist */ 
} /* main */ 

wenn geändert:

int main() 
{ 
    struct bla_bla *pointer_to_x; 
    ... 
    if (something) { 
     static struct bla_bla x; /* now global ---even if scoped */ 
     ... 
     pointer_to_x = &x; 
    } 
    /* x is not visible, but exists, so pointer_to_x continues to be valid */ 
    do_something_to_bla_bla(pointer_to_x); /* correct now */ 
} /* main */