Szukaj…


Wprowadzenie

Klasa pamięci służy do ustawiania zakresu zmiennej lub funkcji. Znając klasę pamięci zmiennej, możemy określić czas życia tej zmiennej w czasie wykonywania programu.

Składnia

  • [auto | register | static | extern] <Typ danych> <Nazwa zmiennej> [= <Wartość>];

  • [static _Thread_local | extern _Thread_local | _Thread_local] <Typ danych> <Nazwa zmiennej> [= <Wartość>]; / * od> = C11 * /

  • Przykłady:

  • typedef int foo ;

  • extern int foo [2];

Uwagi

Specyfikatory klas pamięci to słowa kluczowe, które mogą pojawiać się obok deklaracji najwyższego poziomu. Zastosowanie tych słów kluczowych wpływa na czas przechowywania i powiązanie zadeklarowanego obiektu, w zależności od tego, czy jest zadeklarowany w zakresie pliku czy w zakresie bloku:

Słowo kluczowe Czas przechowywania Połączenie Uwagi
static Statyczny Wewnętrzny Ustawia wewnętrzne powiązanie dla obiektów w zakresie pliku; ustawia statyczny czas przechowywania obiektów w zakresie bloków.
extern Statyczny Zewnętrzny Implikowane, a zatem nadmiarowe dla obiektów zdefiniowanych w zakresie plików, które również mają inicjalizator. W przypadku użycia w deklaracji w zakresie plików bez inicjatora, wskazuje, że definicja znajduje się w innej jednostce tłumaczeniowej i zostanie rozwiązana w czasie łączenia.
auto Automatyczny Nieistotny Domniemane, a zatem nadmiarowe dla obiektów zadeklarowanych w zakresie bloku.
register Automatyczny Nieistotny Dotyczy tylko obiektów z automatycznym czasem przechowywania. Wskazuje, że zmienna powinna być przechowywana w rejestrze. Narzuconym ograniczeniem jest to, że na takim obiekcie nie można użyć jednoargumentowego & „adresu” operatora, a zatem obiekt nie może być aliasowany.
typedef Nieistotny Nieistotny W praktyce nie jest specyfikatorem klasy pamięci, ale działa jak jeden z syntaktycznego punktu widzenia. Jedyną różnicą jest to, że deklarowany identyfikator jest typem, a nie obiektem.
_Thread_local Wątek Wewnętrzny / zewnętrzny Wprowadzono w C11, aby reprezentować czas przechowywania wątku . Jeśli jest stosowany w zakresie blokowym, powinien obejmować również extern lub static .

Każdy obiekt ma powiązany czas przechowywania (niezależnie od zakresu) i powiązania (dotyczy tylko deklaracji w zakresie plików), nawet jeśli te słowa kluczowe zostaną pominięte.

Kolejność specyfikatorów klas pamięci w odniesieniu do specyfikatorów najwyższego poziomu ( int , unsigned , short itp.) I kwalifikatorów najwyższego poziomu ( const , volatile ) nie jest wymuszona, więc obie deklaracje są ważne:

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

Uważa się jednak, że dobrą praktyką jest stawianie na pierwszym miejscu specyfikatorów klasy pamięci, a następnie dowolnego kwalifikatora typu, a następnie specyfikatora typu ( void , char , int , signed long , unsigned long long , long double ...).

Nie wszystkie specyfikatory klas pamięci są legalne w pewnym zakresie:

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;

Czas przechowywania

Czas przechowywania może być statyczny lub automatyczny. W przypadku zadeklarowanego obiektu jest on określany w zależności od jego zakresu i specyfikatorów klasy pamięci.

Statyczny czas przechowywania

Zmienne o czasie przechowywania statycznym trwają przez cały czas wykonywania programu i mogą być deklarowane zarówno w zakresie pliku (ze static lub bez), jak i w zakresie blokowym (poprzez jawne umieszczenie static ). Są one zazwyczaj przydzielane i inicjowane przez system operacyjny podczas uruchamiania programu i odzyskiwane po zakończeniu procesu. W praktyce formaty wykonywalne mają dedykowane sekcje dla takich zmiennych ( data , bss i rodata ), a te całe sekcje z pliku są mapowane do pamięci w określonych zakresach.

Czas przechowywania wątku

C11

Ten czas przechowywania wprowadzono w C11. Nie było to dostępne we wcześniejszych standardach C. Niektóre kompilatory zapewniają niestandardowe rozszerzenie o podobnej semantyce. Na przykład gcc obsługuje specyfikator __thread który może być używany we wcześniejszych standardach C, które nie miały _Thread_local .

Zmienne z czasem przechowywania wątków mogą być deklarowane zarówno w zakresie pliku, jak i bloku. Jeśli zadeklarowano w zakresie bloków, powinien również używać static lub extern specyfikatora pamięci. Jego żywotność to całe wykonanie wątku, w którym zostało utworzone. Jest to jedyny specyfikator pamięci, który może pojawiać się obok innego specyfikatora pamięci.

Automatyczny czas przechowywania

Zmienne z automatycznym czasem przechowywania mogą być deklarowane tylko w zakresie bloku (bezpośrednio w funkcji lub w bloku w tej funkcji). Są użyteczne tylko w okresie pomiędzy wejściem i wyjściem z funkcji lub bloku. Gdy zmienna wykracza poza zakres (albo przez powrót z funkcji, albo przez opuszczenie bloku), jej pamięć jest automatycznie zwalniana. Wszelkie dalsze odniesienia do tej samej zmiennej z wskaźników są nieprawidłowe i prowadzą do nieokreślonego zachowania.

W typowych implementacjach zmienne automatyczne znajdują się w pewnych przesunięciach w ramce stosu funkcji lub w rejestrach.

Połączenie zewnętrzne i wewnętrzne

Powiązanie dotyczy tylko obiektów (funkcji i zmiennych) zadeklarowanych w zakresie pliku i wpływa na ich widoczność w różnych jednostkach tłumaczeniowych. Obiekty z zewnętrznym powiązaniem są widoczne w każdej innej jednostce tłumaczeniowej (pod warunkiem, że uwzględniono odpowiednią deklarację). Obiekty z wewnętrznym powiązaniem nie są narażone na inne jednostki tłumaczeniowe i mogą być używane tylko w tej jednostce tłumaczeniowej, w której są zdefiniowane.

typedef

Definiuje nowy typ na podstawie istniejącego typu. Jego składnia odzwierciedla deklarację zmiennej.

/* 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);

Chociaż technicznie nie jest to klasa pamięci, kompilator potraktuje ją jako jedną, ponieważ żadna z pozostałych klas pamięci nie jest dozwolona, jeśli zostanie użyte słowo kluczowe typedef .

typedef s są ważne i nie należy ich zastępować makrem #define .

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

Jednak,

#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

automatyczny

Ta klasa pamięci oznacza, że identyfikator ma automatyczny czas przechowywania. Oznacza to, że po zakończeniu zakresu, w którym zdefiniowano identyfikator, obiekt oznaczony przez identyfikator przestaje być ważny.

Ponieważ wszystkie obiekty, które nie żyją w zakresie globalnym lub nie są zadeklarowane jako static , mają domyślnie automatycznie ustawiony czas przechowywania, gdy są zdefiniowane, dlatego to słowo kluczowe ma głównie znaczenie historyczne i nie powinno być używane:

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. */

statyczny

static klasa pamięci służy do różnych celów, w zależności od lokalizacji deklaracji w pliku:

  1. Ograniczenie identyfikatora tylko do tej jednostki tłumaczeniowej (zakres = plik).

    /* 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. Aby zapisać dane do użycia przy następnym wywołaniu funkcji (zakres = blok):

     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;
     }
    

    Ten kod drukuje:

     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
    

Zmienne statyczne zachowują swoją wartość, nawet gdy są wywoływane z wielu różnych wątków.

C99
  1. Oczekuje się, że w parametrach funkcji oznaczających tablicę ma stałą minimalną liczbę elementów i parametr inny niż null:

    /* 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]);
    }
    

    Wymagana liczba elementów (lub nawet niepusty wskaźnik) niekoniecznie jest sprawdzana przez kompilator, a kompilatory nie są zobowiązane do powiadamiania Cię w żaden sposób, jeśli nie masz wystarczającej liczby elementów. Jeśli programista przekaże mniej niż 512 elementów lub wskaźnik zerowy, wynikiem jest niezdefiniowane zachowanie. Ponieważ nie można tego wymusić, należy zachować szczególną ostrożność przy przekazywaniu wartości tego parametru do takiej funkcji.

zewnętrzny

Służy do deklarowania obiektu lub funkcji zdefiniowanej gdzie indziej (i która ma powiązanie zewnętrzne ). Zasadniczo służy do deklarowania obiektu lub funkcji do użycia w module, który nie jest tym, w którym zdefiniowano odpowiedni obiekt lub funkcję:

/* 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

Sprawy stają się nieco ciekawsze dzięki wprowadzeniu słowa kluczowego inline w 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);

zarejestrować

Wskazówki dla kompilatora, że dostęp do obiektu powinien być jak najszybszy. To, czy kompilator rzeczywiście korzysta z podpowiedzi, jest zdefiniowane w implementacji; może po prostu traktować to jako odpowiednik auto .

Jedyną właściwością, która jest zdecydowanie inna dla wszystkich obiektów zadeklarowanych za pomocą register jest to, że nie można obliczyć adresu. W ten sposób register może być dobrym narzędziem do zapewnienia pewnych optymalizacji:

register size_t size = 467;

jest obiektem, który nigdy nie może uzyskać aliasu, ponieważ żaden kod nie może przekazać swojego adresu do innej funkcji, w której można go nieoczekiwanie zmienić.

Ta właściwość sugeruje również, że tablica

register int array[5];

nie może rozpaść się na wskaźnik do pierwszego elementu (tj. array zamienia się w &array[0] ). Oznacza to, że do elementów takiej tablicy nie można uzyskać dostępu, a sama tablica nie może zostać przekazana do funkcji.

W rzeczywistości jedynym dozwolonym użyciem tablicy zadeklarowanej z klasą pamięci register jest operator sizeof ; każdy inny operator wymagałby adresu pierwszego elementu tablicy. Z tego powodu tablice zasadniczo nie powinny być deklarowane słowem kluczowym register ponieważ czyni je bezużytecznym do jakichkolwiek innych czynności niż obliczanie wielkości całej tablicy, co można zrobić równie łatwo bez słowa kluczowego register .

Klasa pamięci register jest bardziej odpowiednia dla zmiennych, które są zdefiniowane w bloku i są dostępne z wysoką częstotliwością. Na przykład,

/* 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

Operator _Alignof może być również używany z tablicami register .

_Thread_local

C11

To był nowy specyfikator pamięci wprowadzony w C11 wraz z wielowątkowością. Nie jest to dostępne we wcześniejszych standardach C.

Oznacza czas przechowywania wątku . Zmienna zadeklarowana za pomocą _Thread_local magazynu oznacza, że obiekt jest lokalny dla tego wątku, a jego żywotność jest całym wykonaniem wątku, w którym został utworzony. Może również pojawiać się wraz ze static lub 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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow