Suche…


Einführung

Eine Speicherklasse wird verwendet, um den Gültigkeitsbereich einer Variablen oder Funktion festzulegen. Indem wir die Speicherklasse einer Variablen kennen, können wir die Lebensdauer dieser Variablen während der Laufzeit des Programms bestimmen.

Syntax

  • [auto | register | static | extern] <Datentyp> <Variablenname> [= <Wert>];

  • [static _Thread_local | extern _Thread_local | _Thread_local] <Datentyp> <Variablenname> [= <Wert>]; / * seit> = C11 * /

  • Beispiele:

  • typedef int foo ;

  • extern int foo [2];

Bemerkungen

Speicherklassenspezifizierer sind die Schlüsselwörter, die neben dem Typ einer Deklaration auf oberster Ebene angezeigt werden können. Die Verwendung dieser Schlüsselwörter wirkt sich auf die Speicherdauer und die Verknüpfung des deklarierten Objekts aus, je nachdem, ob es im Dateibereich oder im Blockbereich deklariert ist:

Stichwort Lagerdauer Verknüpfung Bemerkungen
static Statisch Intern Legt die interne Verknüpfung für Objekte im Dateibereich fest. Legt die statische Speicherdauer für Objekte im Blockbereich fest.
extern Statisch Externe Impliziert und daher redundant für Objekte, die im Dateibereich definiert sind und auch über einen Initialisierer verfügen. Bei Verwendung in einer Deklaration im Dateibereich ohne Initialisierer weist dies darauf hin, dass die Definition in einer anderen Übersetzungseinheit zu finden ist und zum Zeitpunkt der Verknüpfung aufgelöst wird.
auto Automatik Irrelevant Impliziert und daher für Objekte, die im Sperrbereich deklariert sind, redundant.
register Automatik Irrelevant Nur relevant für Objekte mit automatischer Speicherdauer. Bietet einen Hinweis, dass die Variable in einem Register gespeichert werden soll. Eine auferlegte Einschränkung ist, dass der unäre & "address of" -Operator für ein solches Objekt nicht verwendet werden kann und das Objekt daher nicht als Alias ​​bezeichnet werden kann.
typedef Irrelevant Irrelevant In der Praxis kein Speicherklassenspezifizierer, er arbeitet jedoch aus syntaktischer Sicht wie einer. Der einzige Unterschied besteht darin, dass es sich bei dem deklarierten Bezeichner nicht um ein Objekt, sondern um einen Typ handelt.
_Thread_local Faden Intern extern In C11 eingeführt, um die Dauer des Thread-Speichers darzustellen. Bei Verwendung im Blockumfang muss es auch extern oder static .

Jedes Objekt hat eine zugeordnete Speicherdauer (unabhängig vom Gültigkeitsbereich) und eine Verknüpfung (nur für Deklarationen im Dateibereich relevant), auch wenn diese Schlüsselwörter nicht angegeben werden.

Die Reihenfolge von Speicherklassenspezifizierern in Bezug auf Typspezifizierer auf oberster Ebene ( int , unsigned , short usw.) und const oberster Ebene ( const , volatile ) wird nicht erzwungen. Daher sind diese beiden Deklarationen gültig:

int static const unsigned a = 5; /* bad practice */
static const unsigned int b = 5; /* good practice */

Es wird jedoch als bewährte Methode angesehen, zuerst Speicherklassenspezifizierer, dann alle Typqualifizierer und dann den Typbezeichner ( void , char , int , signed long , unsigned long long , long double ...) anzugeben.

Nicht alle Speicherklassenspezifizierer sind in einem bestimmten Umfang zulässig:

register int x; /* legal at block scope, illegal at file scope */
auto int y; /* same */

static int z; /* legal at both file and block scope */
extern int a; /* same */

extern int b = 5; /* legal and redundant at file scope, illegal at block scope */

/* legal because typedef is treated like a storage class specifier syntactically */
int typedef new_type_name;

Lagerdauer

Die Lagerdauer kann entweder statisch oder automatisch sein. Für ein deklariertes Objekt wird es abhängig von seinem Gültigkeitsbereich und den Speicherklassenspezifizierern festgelegt.

Statische Speicherdauer

Variablen mit statischer Speicherdauer leben während der gesamten Ausführung des Programms und können sowohl im Dateibereich (mit oder ohne static ) als auch im Blockbereich (durch explizites Setzen von static ) deklariert werden. Sie werden normalerweise vom Betriebssystem beim Programmstart zugewiesen und initialisiert und nach Beendigung des Prozesses wieder freigegeben. In der Praxis haben ausführbare Formate dedizierte Abschnitte für solche Variablen ( data , bss und rodata ), und diese gesamten Abschnitte aus der Datei werden in bestimmten Bereichen in den Speicher abgebildet.

Thread-Speicherdauer

C11

Diese Lagerdauer wurde in C11 eingeführt. Dies war in früheren C-Standards nicht verfügbar. Einige Compiler bieten eine nicht standardmäßige Erweiterung mit ähnlicher Semantik. Zum Beispiel unterstützt gcc den __thread Bezeichner, der in früheren C-Standards verwendet werden kann, die kein _Thread_local .

Variablen mit Thread-Speicherdauer können sowohl im Dateibereich als auch im Blockbereich deklariert werden. Wenn für den Blockbereich deklariert, muss er auch einen static oder extern Speicherbezeichner verwenden. Ihre Lebensdauer ist die gesamte Ausführung des Threads, in dem er erstellt wurde. Dies ist der einzige Speicherbezeichner, der neben einem anderen Speicherbezeichner angezeigt werden kann.

Automatische Speicherdauer

Variablen mit automatischer Speicherdauer können nur im Blockbereich deklariert werden (direkt innerhalb einer Funktion oder innerhalb eines Blocks in dieser Funktion). Sie sind nur in der Zeit zwischen dem Betreten und Verlassen der Funktion oder des Blocks verwendbar. Sobald die Variable den Gültigkeitsbereich verlässt (entweder durch Rückkehr von der Funktion oder durch Verlassen des Blocks), wird der Speicher automatisch freigegeben. Alle weiteren Verweise auf dieselbe Variable aus Zeigern sind ungültig und führen zu undefiniertem Verhalten.

In typischen Implementierungen befinden sich automatische Variablen bei bestimmten Offsets im Stapelrahmen einer Funktion oder in Registern.

Externe und interne Verknüpfung

Die Verknüpfung ist nur für Objekte (Funktionen und Variablen) relevant, die im Dateibereich deklariert sind, und beeinflusst deren Sichtbarkeit in verschiedenen Übersetzungseinheiten. Objekte mit externer Verknüpfung sind in jeder anderen Übersetzungseinheit sichtbar (vorausgesetzt, die entsprechende Deklaration ist enthalten). Objekte mit interner Verknüpfung sind nicht für andere Übersetzungseinheiten verfügbar und können nur in der Übersetzungseinheit verwendet werden, in der sie definiert sind.

Typedef

Definiert einen neuen Typ basierend auf einem vorhandenen Typ. Ihre Syntax spiegelt die einer Variablendeklaration wider.

/* Byte can be used wherever `unsigned char` is needed */
typedef unsigned char Byte;

/* Integer is the type used to declare an array consisting of a single int */
typedef int Integer[1];

/* NodeRef is a type used for pointers to a structure type with the tag "node" */
typedef struct node *NodeRef;

/* SigHandler is the function pointer type that gets passed to the signal function. */
typedef void (*SigHandler)(int);

Ein Compiler wird zwar technisch keine Speicherklasse sein, aber als eine Klasse behandeln, da keine der anderen Speicherklassen zulässig ist, wenn das Schlüsselwort typedef verwendet wird.

Die typedef sind wichtig und sollten nicht durch das Makro #define .

typedef int newType; 
newType *ptr;        // ptr is pointer to variable of type 'newType' aka int

Jedoch,

#define int newType
newType *ptr;        // Even though macros are exact replacements to words, this doesn't result to a pointer to variable of type 'newType' aka int

Auto

Diese Speicherklasse gibt an, dass eine Kennung eine automatische Speicherdauer hat. Das heißt, sobald der Gültigkeitsbereich, in dem der Bezeichner definiert wurde, endet, ist das durch den Bezeichner gekennzeichnete Objekt nicht mehr gültig.

Da alle Objekte, die nicht im globalen Geltungsbereich leben oder als static deklariert werden, standardmäßig eine automatische Speicherdauer aufweisen, ist dieses Schlüsselwort meist von historischem Interesse und sollte nicht verwendet werden:

int foo(void)
{
    /* An integer with automatic storage duration. */
    auto int i = 3;

    /* Same */
    int j = 5;

    return 0;
} /* The values of i and j are no longer able to be used. */

statisch

Die static Speicherklasse erfüllt unterschiedliche Zwecke, abhängig vom Speicherort der Deklaration in der Datei:

  1. Um die Kennung nur auf diese Übersetzungseinheit zu beschränken (Bereich = Datei).

    /* No other translation unit can use this variable. */
    static int i;
    
    /* Same; static is attached to the function type of f, not the return type int. */
    static int f(int n);
    
  2. So speichern Sie Daten zur Verwendung beim nächsten Aufruf einer Funktion (Bereich = Block):

     void foo()
     {
         static int a = 0; /* has static storage duration and its lifetime is the
                            * entire execution of the program; initialized to 0 on 
                            * first function call */ 
         int b = 0; /* b has block scope and has automatic storage duration and 
                     * only "exists" within function */
         
         a += 10;
         b += 10; 
    
         printf("static int a = %d, int b = %d\n", a, b);
     }
    
     int main(void)
     {
         int i;
         for (i = 0; i < 5; i++)
         {
             foo();
         }
    
         return 0;
     }
    

    Dieser Code druckt:

     static int a = 10, int b = 10
     static int a = 20, int b = 10
     static int a = 30, int b = 10
     static int a = 40, int b = 10
     static int a = 50, int b = 10
    

Statische Variablen behalten ihren Wert, auch wenn sie von mehreren verschiedenen Threads aufgerufen werden.

C99
  1. Wird in Funktionsparametern verwendet, um ein Array zu kennzeichnen, wird erwartet, dass es eine konstante Mindestanzahl von Elementen und einen Nicht-Null-Parameter aufweist:

    /* a is expected to have at least 512 elements. */
    void printInts(int a[static 512])
    {
        size_t i;
        for (i = 0; i < 512; ++i)
            printf("%d\n", a[i]);
    }
    

    Die erforderliche Anzahl von Elementen (oder sogar ein Zeiger, der nicht Null ist) wird vom Compiler nicht unbedingt geprüft. Compiler müssen Sie nicht benachrichtigen, wenn Sie nicht genügend Elemente haben. Wenn ein Programmierer weniger als 512 Elemente oder einen Nullzeiger übergibt, ist das Ergebnis ein undefiniertes Verhalten. Da dies nicht erzwungen werden kann, muss besondere Sorgfalt angewandt werden, wenn ein Wert für diesen Parameter an eine solche Funktion übergeben wird.

extern

Wird verwendet , um ein Objekt oder eine Funktion zu deklarieren , die an anderer Stelle definiert ist (und die über eine externe Verknüpfung verfügt ). Im Allgemeinen wird ein Objekt oder eine Funktion für die Verwendung in einem Modul deklariert, in dem das entsprechende Objekt oder die entsprechende Funktion nicht definiert ist:

/* file1.c */
int foo = 2;  /* Has external linkage since it is declared at file scope. */
/* file2.c */
#include <stdio.h>
int main(void)
{
    /* `extern` keyword refers to external definition of `foo`. */
    extern int foo;
    printf("%d\n", foo);
    return 0;
}
C99

Etwas interessanter wird es mit der Einführung des inline Keywords in C99:

/* Should usually be place in a header file such that all users see the definition */
/* Hints to the compiler that the function `bar` might be inlined */
/* and suppresses the generation of an external symbol, unless stated otherwise. */
inline void bar(int drink)
{
    printf("You ordered drink no.%d\n", drink);
}

/* To be found in just one .c file.
   Creates an external function definition of `bar` for use by other files.
   The compiler is allowed to choose between the inline version and the external
   definition when `bar` is called. Without this line, `bar` would only be an inline
   function, and other files would not be able to call it. */
extern void bar(int);

registrieren

Hinweise an den Compiler, dass der Zugriff auf ein Objekt so schnell wie möglich sein sollte. Ob der Compiler den Hinweis tatsächlich verwendet, ist durch die Implementierung definiert. es kann es einfach als äquivalent zu auto .

Die einzige Eigenschaft, die sich definitiv für alle mit register deklarierten Objekte unterscheidet, besteht darin, dass ihre Adresse nicht berechnet werden kann. Dabei kann register ein gutes Werkzeug sein, um bestimmte Optimierungen sicherzustellen:

register size_t size = 467;

ist ein Objekt, das niemals als Alias ​​bezeichnet werden kann, da kein Code seine Adresse an eine andere Funktion übergeben kann, wo sie möglicherweise unerwartet geändert wird.

Diese Eigenschaft impliziert auch ein Array

register int array[5];

kann nicht in einen Zeiger auf sein erstes Element zerfallen (dh aus array in &array[0] ). Dies bedeutet, dass auf die Elemente eines solchen Arrays nicht zugegriffen werden kann und das Array selbst nicht an eine Funktion übergeben werden kann.

Tatsächlich ist die einzige rechtliche Verwendung eines Arrays, das mit einer register deklariert wurde, der Operator sizeof . Jeder andere Operator würde die Adresse des ersten Elements des Arrays benötigen. Aus diesem Grund sollten Arrays im Allgemeinen nicht mit dem register Schlüsselwort deklariert werden, da sie für andere Zwecke als die Größenberechnung des gesamten Arrays unbrauchbar sind. Dies ist genauso einfach ohne das register Schlüsselwort.

Die register eignet sich besser für Variablen, die innerhalb eines Blocks definiert sind und auf die mit hoher Häufigkeit zugegriffen wird. Zum Beispiel,

/* prints the sum of the first 5 integers*/
/* code assumed to be part of a function body*/ 
{ 
    register int k, sum;
    for(k = 1, sum = 0; k < 6; sum += k, k++);
        printf("\t%d\n",sum);
}
C11

Der _Alignof Operator darf auch mit register Arrays verwendet werden.

_Thread_local

C11

Dies war ein neuer Speicherspezifizierer, der in C11 zusammen mit Multithreading eingeführt wurde. Dies ist in früheren C-Standards nicht verfügbar.

Gibt die Thread-Speicherdauer an . Eine mit _Thread_local Speicherkennzeichner deklarierte Variable gibt an, dass das Objekt lokal für diesen Thread ist und seine Lebensdauer die gesamte Ausführung des Threads ist, in dem es erstellt wurde. Es kann auch zusammen mit static oder extern .

#include <threads.h>
#include <stdio.h>
#define SIZE 5

int thread_func(void *id)
{
    /* thread local variable i. */
    static _Thread_local int i;

    /* Prints the ID passed from main() and the address of the i.
     * Running this program will print different addresses for i, showing
     * that they are all distinct objects. */
    printf("From thread:[%d], Address of i (thread local): %p\n", *(int*)id, (void*)&i);

    return 0;
}

int main(void)
{
    thrd_t id[SIZE];
    int arr[SIZE] = {1, 2, 3, 4, 5};

    /* create 5 threads. */
    for(int i = 0; i < SIZE; i++) {
        thrd_create(&id[i], thread_func, &arr[i]);
    }

    /* wait for threads to complete. */
    for(int i = 0; i < SIZE; i++) {
        thrd_join(id[i], NULL);
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow