C Language
Opslagklassen
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
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:
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);
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.
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;
}
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);
}
De operator _Alignof
mag ook worden gebruikt met register
.
_Thread_local
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);
}
}