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

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

Сразу приведем очень полезный пример - инициализация статического указателя на объект.

#include 
#include 
#include "cl1.h"


static Cl1* get_staticPointer()
{
    static Cl1 *pCl1 = NULL;

    if(pCl1 == NULL)
    {
        qDebug()<< "pCl1 == NULL";

        pCl1 = new Cl1();
    }
    else
        qDebug()<< "pCl1 " << pCl1;

    return pCl1;
}


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

    get_staticPointer();
    get_staticPointer();
    get_staticPointer();

    return a.exec();
}

Пример можно скачать внизу.

В консоль выводится: 

pCl1 == NULL
pCl1  0x2538af8
pCl1  0x2538af8

Это означает, что класс Cl1 создается только один раз (в куче). Очень полезно знать для одноразовой инициализации объекта.

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

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

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

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

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

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

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

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

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

Интересное решение одной проблемы. Как известно нельзя об'явить функцию класса (Cl) одновременно виртуальной и статической. А если очень хочется? Можно поступить так: функцию (foo) об'являем виртуальной и в классе создаём ещё статический указатель (pp), который будет указывать на сам класс.

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

Cl::pp->foo();

И это сработает.  Причем foo вызовется последняя в иерархии классов наследников, так как она виртуальная. То есть это может быть и класс Cl2 в иерархии наследования Cl2<-Cl1<-Cl.

Это развязывает руки программисту. Ты можешь везде иметь доступ к виртуальной функции  некоего класса через статический указатель на сам класс.

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

Это только про язык С, не СРР. Это осталось от старых записей.

При программировании микроконтроллеров 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 очищается.