Загадочное слово static

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

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

Для лучшего понимания static смотрим сначала здесь как происходит сборка сpp  файлов.

Другими словами static гарантирует однократное размещение в оперативной памяти переменной, функции, указателя.

Когда это становится выгодно? На самом деле это очень выгодно, если ваши данные однократно определяются и более не изменяются в процессе работы программы.

Ну например структура какой-то базы данных.

Применительно к классам, static переменные необходимо инициализировать особым образом вне класса (это про файл cpp).

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

Главная идея собрать все неизменяюмые однократно определенные данные в static переменные/функции, обернуть это в класс (или в структуру) и пользоваться этими статическими данными из любого места программы.

Надо только подключить заголовочный файл этого класса и вуаля.

Отвлечения на тему контроллеров

Это только про язык С, не СРР.

При программировании микроконтроллеров static говорит компилятору/линковщику разместить данный объект в НЕ в стеке, а в сегменте BSS (Block Started by Symbol) , это просто оперативная память выделенная для данных вашей программе, размер которых заранее известен после компиляции/сборки.

BSS это сегмент памяти RAM , который фиксируется сразу при компиляции/линковке вашей программы.

фотка 1

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

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

Все эти варианты надо рассматривать на практических примерах. Начнем с простейшего :

глобальная переменная вне тела функции

Тут речь про static функцию не как метод класса, а просто обычную функцию.

static int COUNT=0;

void func1()
{
    printf("");
    //int COUNT=0;
    printf("%d",COUNT  );
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    printf("start main\n");

    for (int i = 0; i < 10;   i)
    {
        func1();
    }

    printf("\nend main\n");

    return a.exec();
}

Когда используем static для глобальной переменной COUNT , то COUNT попадет в .bss сегмент.
Примечание: теперь из другого файла *.c до COUNT не достучитесь теперь никак (за исключением класс::имя_статик_переменной).

Когда COUNT без static, то COUNT попадет в кучу (heap).

Примечание: Теперь из другого файла *.с можно достучаться до COUNT через директиву extern.

локальная переменная в теле функции


#include 

void func1()
{
    printf("");
    static int COUNT=0; // примечательно , что присваивание 0  происходит только один раз 
    //при инициализации программы , то есть в начале загрузки программы в память (.bss сегмент), еще до main
    printf("%d",COUNT  );
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    printf("start main\n");

    for (int i = 0; i < 10;   i)
    {
        func1();
    }

    printf("\nend main\n");

    return a.exec();
}

Вывод:

start main
0123456789
end main

Если static не использовать в данном примере вывод будет таким:

start main
0000000000
end main

В данном варианте без static переменная COUNT создается на стеке для каждого вызова функции func1 заново (и ей присваивается 0 каждый раз заново).

Маленькая ржачка : никогда не называйте свои проекты в Qt словом static, добавьте еще хотя бы один символ.

статическая функция

#include 

static void func1() // 
{
    static int COUNT123=0;
    printf("%d",COUNT123  );
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    printf("start main\n");

    for (int i = 0; i < 10;   i)
    {
        func1();
    }

    printf("\nend main\n");

    return a.exec();
}

На первый взгляд кроме того , что функцию func1 нельзя теперь вызвать из других cpp файлов вроде различий нет (что func1 со static , что без нее).

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

Но на самом деле в коде асемблерный код немного меняется. Без static так :

void func1()
{
       0:	6b 58 01 00          	imul   $0x0,0x1(%eax),%ebx
       4:	02 00                	add    (%eax),%al
    static int COUNT123=0;
    printf("%d",COUNT123  );
       6:	00 00                	add    %al,(%eax)
			6: secrel32	.debug_abbrev
       8:	00 00                	add    %al,(%eax)
       a:	04 01                	add    $0x1,%al
       c:	47                   	inc    %edi
       d:	4e                   	dec    %esi
       e:	55                   	push   %ebp
       f:	20 43 2b             	and    %al,0x2b(%ebx)
      12:	2b 20                	sub    (%eax),%esp
      14:	34 2e                	xor    $0x2e,%al
      16:	34 2e                	xor    $0x2e,%al
      18:	30 00                	xor    %al,(%eax)
      1a:	04 2e                	add    $0x2e,%al
      1c:	2e                   	cs
      1d:	5c                   	pop    %esp
      1e:	73 74                	jae    94 <.debug_info 0x94>
      20:	61                   	popa   
      21:	74 69                	je     8c <.debug_info 0x8c>
      23:	63 5f 5c             	arpl   %bx,0x5c(%edi)
}

А когда static func1 так:

static void func1()
{
       0:	60                   	pusha    / / !!!!
       1:	58                   	pop    %eax  / / !!!!
       2:	01 00                	add    %eax,(%eax) // и далее все тоже самое
       4:	02 00                	add    (%eax),%al
    static int COUNT123=0;
    printf("%d",COUNT123  );
       6:	00 00                	add    %al,(%eax)
			6: secrel32	.debug_abbrev
       8:	00 00                	add    %al,(%eax)
       a:	04 01                	add    $0x1,%al
       c:	47                   	inc    %edi
       d:	4e                   	dec    %esi
       e:	55                   	push   %ebp
       f:	20 43 2b             	and    %al,0x2b(%ebx)
      12:	2b 20                	sub    (%eax),%esp
      14:	34 2e                	xor    $0x2e,%al
      16:	34 2e                	xor    $0x2e,%al
      18:	30 00                	xor    %al,(%eax)
      1a:	04 2e                	add    $0x2e,%al
      1c:	2e                   	cs
      1d:	5c                   	pop    %esp
      1e:	73 74                	jae    94 <.debug_info 0x94>
      20:	61                   	popa   
      21:	74 69                	je     8c <.debug_info 0x8c>
      23:	63 5f 5c             	arpl   %bx,0x5c(%edi)
}

Статик переменная в классах

С классами все намного интереснее. И это уже CPP

Допустим в классе есть статик переменная :

class class1{
public:
static int COUNT

Это объявление в файле class1.h .
Но этого не достаточно, в файле class1.c (или в любом другом файле *.с проекта) НАДО инициализировать статик переменную (глобально, над всеми функциями):

static int class1::COUNT=0

Если этого не будет , то проект может даже скомпилируется нормально, но когда дело дойдет непосредственно до работы с COUNT, то возникнут проблемы на этапе линковки.

В результате к переменной теперь COUNT можно обращаться из любого файла например таким образом:

class1::COUNT =2

И надо конечно понимать что переменная COUNT теперь всегда будет хранится в одном экземпляре , по одному адресу в оперативной памяти, не смотря на то , что самих классов class1 может быть создано несколько (но они создаются уже на стеке).

Когда статик переменные класса становятся реально необходимыми?

Есть такая функция например как
qInstallhandler

С помощью ее можно поменять обработчик событий.

В качестве параметра входит имя новой функции, которая у вас имеется (допустим назовем ее func1).

Но вот только func1 должна быть глобальной и лучше размещать ее в BSS.

А вам очень хочется , чтобы func1 была частью класса. Ведь класс удобен тем, что это кусок функционала , который удобно переносить из проекта в проект.

Класс созданный через new class1 в функции допустим func2 создается на стеке. При выходе из функции func2 класс class1 очищается.