Поиск…


Вступление

Класс хранения используется для задания области действия переменной или функции. Зная класс хранения переменной, мы можем определить время жизни этой переменной в течение времени выполнения программы.

Синтаксис

  • [auto | register | static | extern] <Тип данных> <Имя переменной> [= <Значение>];

  • [static _Thread_local | extern _Thread_local | _Thread_local] <Тип данных> <Имя переменной> [= <Значение>]; / *, так как> = C11 * /

  • Примеры:

  • typedef int foo ;

  • extern int foo [2];

замечания

Спецификаторы класса хранения - это ключевые слова, которые могут отображаться рядом с типом объявления верхнего уровня. Использование этих ключевых слов влияет на продолжительность хранения и привязку объявленного объекта в зависимости от того, объявлено ли оно в области файла или в области блока:

Ключевое слово Продолжительность хранения связь замечания
static статический внутренний Устанавливает внутреннюю привязку для объектов в области файлов; устанавливает статическую длительность хранения для объектов в области блока.
extern статический внешний Подразумевается и поэтому избыточно для объектов, определенных в области файлов, которые также имеют инициализатор. При использовании в объявлении в области файла без инициализатора намеки на то, что определение должно быть найдено в другой единицы перевода и будет разрешено во время ссылки.
auto автоматическая Ненужные Подразумевается и поэтому избыточно для объектов, объявленных в области блока.
register автоматическая Ненужные Релевантно относится только к объектам с автоматическим временем хранения. Предоставляет подсказку о том, что переменная должна храниться в регистре. Наложенное ограничение состоит в том, что нельзя использовать унарный & «адрес» оператора на таком объекте, и поэтому объект нельзя сгладить.
typedef Ненужные Ненужные Не спецификатор класса хранения на практике, но работает как один с синтаксической точки зрения. Единственное отличие состоит в том, что объявленный идентификатор является типом, а не объектом.
_Thread_local Нить Внутренний / внешний Представлен в C11 для представления продолжительности хранения потоков . Если используется в области блока, он также должен включать в себя extern или static .

Каждый объект имеет связанную длительность хранения (независимо от области действия) и привязку (относится только к объявлениям только в области файлов), даже если эти ключевые слова опущены.

Упорядочение спецификаторов класса хранения по отношению к спецификаторам типа верхнего уровня ( int , unsigned , short и т. Д.) И квалификаторам верхнего уровня ( const , volatile ) не применяется, поэтому оба этих объявления действительны:

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

Однако считается хорошей практикой сначала поставить спецификаторы класса хранения, затем квалификаторы типов, а затем спецификатор типа ( void , char , int , signed long , unsigned long long , long double ...).

Не все спецификаторы класса хранения являются законными в определенной области:

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;

Продолжительность хранения

Длительность хранения может быть как статической, так и автоматической. Для объявленного объекта он определяется в зависимости от его области действия и спецификаторов класса хранения.

Статическая продолжительность хранения

Переменные со статическим хранением сохраняются в течение всего выполнения программы и могут быть объявлены как в области файлов (с или без static ), так и в области блока (путем static ввода). Они обычно выделяются и инициализируются операционной системой при запуске программы и возвращаются, когда процесс завершается. На практике исполняемые форматы имеют выделенные разделы для таких переменных ( data , bss и rodata ), и все эти разделы из файла отображаются в памяти в определенных диапазонах.

Продолжительность хранения резьбы

C11

Эта продолжительность хранения была введена в C11. Это было недоступно в более ранних стандартах C. Некоторые компиляторы предоставляют нестандартное расширение с аналогичной семантикой. Например, gcc поддерживает __thread который может использоваться в более ранних стандартах C, у которых не было _Thread_local .

Переменные с продолжительностью хранения потоков могут быть объявлены как в области файлов, так и в области блоков. Если объявлено в области блока, оно также должно использовать static или extern хранитель. Его время жизни - это полное выполнение потока, в котором он создан. Это единственный спецификатор хранилища, который может появляться рядом с другим спецификатором хранилища.

Автоматическая продолжительность хранения

Переменные с автоматической продолжительностью хранения могут быть объявлены только в области блока (непосредственно внутри функции или внутри блока в этой функции). Они могут использоваться только в период между входом и выходом из функции или блока. Как только переменная выходит из области видимости (либо путем возврата из функции, либо путем выхода из блока), ее память автоматически освобождается. Любые дополнительные ссылки на одну и ту же переменную из указателей являются недопустимыми и приводят к неопределенному поведению.

В типичных реализациях автоматические переменные располагаются при определенных смещениях в кадре стека функции или в регистрах.

Внешняя и внутренняя связь

Связывание относится только к объектам (функциям и переменным), объявленным в области файлов, и влияет на их видимость в разных единицах перевода. Объекты с внешней связью видны во всех других единицах перевода (при условии, что включена соответствующая декларация). Объекты с внутренней связью не подвергаются воздействию других единиц перевода и могут использоваться только в блоке перевода, где они определены.

ЬурейеЕ

Определяет новый тип на основе существующего типа. Его синтаксис является зеркальным отображением объявления переменной.

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

Хотя технически не класс хранения, компилятор будет рассматривать его как один, поскольку ни один из других классов хранения не разрешен, если используется ключевое слово typedef .

typedef s важны и не должны заменяться макросом #define .

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

Тем не мение,

#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

авто

Этот класс хранения означает, что идентификатор имеет автоматическую продолжительность хранения. Это означает, что когда область, в которой был определен идентификатор, заканчивается, объект, обозначенный идентификатором, более недействителен.

Поскольку все объекты, не проживающие в глобальном масштабе или объявленные static , имеют автоматическую продолжительность хранения по умолчанию при определении, это ключевое слово в основном представляет собой исторический интерес и не должно использоваться:

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

статический

static класс хранения используется в разных целях, в зависимости от местоположения объявления в файле:

  1. Ограничить идентификатор только этой единицей перевода (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. Чтобы сохранить данные для использования со следующим вызовом функции (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;
     }
    

    Этот код печатает:

     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
    

Статические переменные сохраняют свое значение даже при вызове из нескольких разных потоков.

C99
  1. Ожидается, что в функциональных параметрах для обозначения массива будет иметься постоянное минимальное количество элементов и ненулевой параметр:

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

    Требуемое количество элементов (или даже ненулевой указатель) не обязательно проверяется компилятором, и компиляторы не обязаны уведомлять вас каким-либо образом, если у вас недостаточно элементов. Если программист передает менее 512 элементов или нулевой указатель, результатом является неопределенное поведение. Так как это невозможно обеспечить, необходимо использовать особую осторожность при передаче значения для этого параметра такой функции.

внешний

Используется для объявления объекта или функции, которая определена в другом месте (и имеет внешнюю связь ). В общем случае он используется для объявления объекта или функции, которые будут использоваться в модуле, который не является тем, в котором определен соответствующий объект или функция:

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

Вещи немного интереснее с введением ключевого слова inline в 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);

регистр

Подсказки к компилятору, что доступ к объекту должен быть как можно быстрее. Независимо от того, использует ли данный компилятор подсказку, определяется ли реализация; он может просто рассматривать его как эквивалент auto .

Единственное свойство, которое окончательно отличается для всех объектов, объявленных с помощью register состоит в том, что они не могут вычислить свой адрес. Таким образом, register может быть хорошим инструментом для обеспечения определенных оптимизаций:

register size_t size = 467;

это объект, который никогда не может быть псевдонимом, потому что ни один код не может передать свой адрес другой функции, где он может быть неожиданно изменен.

Это свойство также означает, что массив

register int array[5];

не может распадаться в указатель на его первый элемент (т.е. array превращается в &array[0] ). Это означает, что элементы такого массива не могут быть доступны, и сам массив не может быть передан функции.

Фактически единственным законным использованием массива, объявленного с классом хранения register является оператор sizeof ; любой другой оператор потребует адрес первого элемента массива. По этой причине массивы обычно не должны быть объявлены с ключевым словом register поскольку они делают их бесполезными для чего-либо, кроме вычисления размера всего массива, что можно сделать так же легко без ключевого слова register .

Класс хранения register более подходит для переменных, которые определены внутри блока, и к ним обращаются с высокой частотой. Например,

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

Оператору _Alignof также разрешено использовать с register массивами.

_Thread_local

C11

Это был новый спецификатор хранилища, введенный в C11, а также многопоточность. Это не доступно в более ранних стандартах С.

Обозначает продолжительность хранения потоков . Переменная, объявленная с помощью _Thread_local хранилища _Thread_local означает, что объект является локальным для этого потока, а его время жизни - это полное выполнение потока, в котором он создан. Он также может появляться вместе со static или 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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow