Zoeken…


Invoering

Een opslagklasse wordt gebruikt om het bereik van een variabele of functie in te stellen. Door de opslagklasse van een variabele te kennen, kunnen we de levensduur van die variabele tijdens de looptijd van het programma bepalen.

Syntaxis

  • [auto | register | statisch | extern] <Gegevenstype> <Variabelenaam> [= <waarde>];

  • [statische _Thread_local | extern _Thread_local | _Thread_local] <Gegevenstype> <Naam variabele> [= <waarde>]; / * sinds> = C11 * /

  • Voorbeelden:

  • typedef int foo ;

  • extern int foo [2];

Opmerkingen

Opslagklasse-aanduidingen zijn de trefwoorden die naast het hoogste type van een aangifte kunnen worden weergegeven. Het gebruik van deze trefwoorden beïnvloedt de opslagduur en koppeling van het gedeclareerde object, afhankelijk van of het is gedeclareerd bij bestandsbereik of bij blokbereik:

keyword Opslagduur verbinding Opmerkingen
static Statisch intern Stelt interne koppeling in voor objecten op bestandsbereik; stelt de statische opslagduur in voor objecten met blokbereik.
extern Statisch extern Impliciet en daarom overbodig voor objecten die zijn gedefinieerd in het bestandsbereik en die ook een initialisatie hebben. Wanneer gebruikt in een aangifte in bestandsbereik zonder initialisatie, geeft dit aan dat de definitie in een andere vertaaleenheid moet worden gevonden en zal worden opgelost tijdens de koppeling.
auto automatisch Irrelevant Impliciet en daarom overbodig voor objecten die in blokbereik worden gedeclareerd.
register automatisch Irrelevant Alleen relevant voor objecten met automatische opslagduur. Geeft een hint dat de variabele moet worden opgeslagen in een register. Een opgelegde beperking is dat men de unaire & "adres van" -operator op een dergelijk object niet kan gebruiken, en daarom kan er geen alias op het object worden toegepast.
typedef Irrelevant Irrelevant In de praktijk geen specificatie voor opslagklasse, maar werkt synoniem vanuit syntactisch oogpunt. Het enige verschil is dat de aangegeven identifier een type is en geen object.
_Thread_local Draad Intern extern Geïntroduceerd in C11 om de opslagduur van de threads weer te geven. Indien gebruikt in blokbereik, zal het ook extern of static omvatten.

Elk object heeft een bijbehorende opslagduur (ongeacht het bereik) en koppeling (alleen relevant voor declaraties bij bestandsbereik), zelfs wanneer deze trefwoorden worden weggelaten.

Het bestellen van opslagklasse-aanduidingen met betrekking tot topniveau-typespecificaties ( int , unsigned , short , enz.) En top-level typekwalificaties ( const , volatile ) wordt niet afgedwongen, dus beide verklaringen zijn geldig:

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

Het wordt echter als een goede gewoonte beschouwd om eerst de opslagklasse-aanduidingen, daarna alle typekwalificaties en vervolgens de typeaanduiding ( void , char , int , signed long unsigned long long , long double unsigned long long , long double ...) in te voeren.

Niet alle opslagklasse-specificaties zijn legaal met een bepaald bereik:

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;

Opslagduur

De opslagduur kan statisch of automatisch zijn. Voor een gedeclareerd object wordt dit bepaald afhankelijk van het bereik en de opslagklasse-specificatie.

Statische opslagduur

Variabelen met statische opslag duur wonen in de hele uitvoering van het programma en kan zowel op file scope (met of zonder worden verklaard static ) en bij blok scope (door de invoering van static expliciet). Ze worden meestal toegewezen en geïnitialiseerd door het besturingssysteem bij het opstarten van het programma en worden teruggewonnen wanneer het proces wordt beëindigd. In de praktijk hebben uitvoerbare indelingen speciale secties voor dergelijke variabelen ( data , bss en rodata ) en deze hele secties uit het bestand worden in bepaalde bereiken in het geheugen toegewezen.

Draadopslagduur

C11

Deze opslagduur werd geïntroduceerd in C11. Dit was niet beschikbaar in eerdere C-normen. Sommige compilers bieden een niet-standaard extensie met vergelijkbare semantiek. Gcc ondersteunt bijvoorbeeld __thread specifier die kan worden gebruikt in eerdere C-standaarden die geen _Thread_local .

Variabelen met threadopslagduur kunnen worden opgegeven voor zowel bestandsbereik als blokbereik. Indien gedeclareerd bij blokbereik, zal het ook een static of extern opslagspecificatie gebruiken. De levensduur is de volledige uitvoering de rode draad waarin het is gemaakt. Dit is de enige opslagspecificatie die naast een andere opslagspecificatie kan worden weergegeven.

Automatische opslagduur

Variabelen met automatische opslagduur kunnen alleen worden gedeclareerd bij blokbereik (direct binnen een functie of binnen een blok in die functie). Ze zijn alleen bruikbaar in de periode tussen het binnengaan en verlaten van de functie of het blok. Als de variabele eenmaal buiten bereik is (door terug te keren van de functie of door het blok te verlaten), wordt de opslag ervan automatisch toegewezen. Verdere verwijzingen naar dezelfde variabele vanuit verwijzingen zijn ongeldig en leiden tot ongedefinieerd gedrag.

In typische implementaties bevinden automatische variabelen zich op bepaalde offsets in het stapelframe van een functie of in registers.

Externe en interne koppeling

Koppeling is alleen relevant voor objecten (functies en variabelen) die bij bestandsbereik worden gedeclareerd en beïnvloedt hun zichtbaarheid in verschillende vertaaleenheden. Objecten met externe koppeling zijn zichtbaar in elke andere vertaaleenheid (op voorwaarde dat de juiste verklaring is opgenomen). Objecten met interne koppeling worden niet blootgesteld aan andere vertaaleenheden en kunnen alleen worden gebruikt in de vertaaleenheid waar ze zijn gedefinieerd.

typedef

Definieert een nieuw type op basis van een bestaand type. De syntaxis weerspiegelt die van een variabele declaratie.

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

Hoewel het technisch gezien geen opslagklasse is, zal een compiler het als één behandelen, omdat geen van de andere opslagklassen is toegestaan als het sleutelwoord typedef wordt gebruikt.

De typedef s zijn belangrijk en mogen niet worden vervangen door #define macro.

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

Echter,

#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

Deze opslagklasse geeft aan dat een ID een automatische opslagduur heeft. Dit betekent dat zodra het bereik waarin de identifier werd gedefinieerd, eindigt, het object dat wordt aangegeven door de identifier niet langer geldig is.

Aangezien alle objecten, die niet in een wereldwijd bereik leven of static worden verklaard, standaard een automatische opslagduur hebben wanneer gedefinieerd, is dit trefwoord meestal van historisch belang en mag het niet worden gebruikt:

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

De static opslagklasse dient verschillende doeleinden, afhankelijk van de locatie van de aangifte in het bestand:

  1. Om de identifier alleen te beperken tot die vertaaleenheid (scope = file).

    /* 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. Gegevens opslaan voor gebruik bij de volgende aanroep van een functie (scope = 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;
     }
    

    Deze code drukt af:

     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 variabelen behouden hun waarde, zelfs wanneer ze vanuit meerdere verschillende threads worden aangeroepen.

C99
  1. Wordt gebruikt in functieparameters om aan te geven dat een array naar verwachting een constant minimum aantal elementen en een niet-nul parameter heeft:

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

    Het vereiste aantal items (of zelfs een niet-lege aanwijzer) wordt niet noodzakelijkerwijs gecontroleerd door de compiler en compilers zijn niet verplicht u op enigerlei wijze op de hoogte te stellen als u onvoldoende elementen hebt. Als een programmeur minder dan 512 elementen of een nulaanwijzer doorgeeft, is ongedefinieerd gedrag het resultaat. Aangezien het onmogelijk is om dit af te dwingen, moet u extra voorzichtig zijn bij het doorgeven van een waarde voor die parameter aan een dergelijke functie.

extern

Wordt gebruikt om een object of functie aan te geven die elders is gedefinieerd (en die externe koppeling heeft ). Over het algemeen wordt het gebruikt om aan te geven dat een object of functie moet worden gebruikt in een module die niet degene is waarin het overeenkomstige object of functie is gedefinieerd:

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

Dingen worden iets interessanter met de introductie van het inline trefwoord 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);

registreren

Hints voor de compiler dat de toegang tot een object zo snel mogelijk moet zijn. Of de compiler de hint daadwerkelijk gebruikt, is door de implementatie bepaald; het kan het eenvoudigweg behandelen als gelijkwaardig aan auto .

De enige eigenschap die definitief verschilt voor alle objecten die met register zijn gedeclareerd, is dat hun adres niet kan worden berekend. Daardoor kan register een goed hulpmiddel zijn om bepaalde optimalisaties te garanderen:

register size_t size = 467;

is een object dat nooit een alias kan gebruiken omdat geen code zijn adres kan doorgeven aan een andere functie waar het onverwacht kan worden gewijzigd.

Deze eigenschap houdt ook in dat een array

register int array[5];

kan niet in een wijzer naar zijn eerste element vervallen (dwz array verandert in &array[0] ). Dit betekent dat de elementen van een dergelijke array niet toegankelijk zijn en dat de array zelf niet aan een functie kan worden doorgegeven.

In feite is de enige legale gebruik van een array gedeclareerd met een register opslag klasse is de sizeof operator; elke andere operator zou het adres van het eerste element van de array nodig hebben. Daarom moeten arrays in het algemeen niet met het register sleutelwoord worden gedeclareerd, omdat het ze nutteloos maakt voor iets anders dan de grootteberekening van de hele array, wat net zo gemakkelijk kan worden gedaan zonder het register sleutelwoord.

De register is geschikter voor variabelen die binnen een blok zijn gedefinieerd en met hoge frequentie worden benaderd. Bijvoorbeeld,

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

De operator _Alignof mag ook worden gebruikt met register .

_Thread_local

C11

Dit was een nieuwe opslagspecificatie geïntroduceerd in C11 samen met multi-threading. Dit is niet beschikbaar in eerdere C-normen.

Geeft de opslagduur van de thread aan . Een variabele gedeclareerd met _Thread_local opslagspecificatie geeft aan dat het object lokaal is voor die thread en dat de levensduur ervan de volledige uitvoering is van de thread waarin het is gemaakt. Het kan ook verschijnen samen met static of 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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow