static local variables



В этом давнишнем посте кратко резюмировали все стороны "употребления" ключевого слова static. Сегодня поговорим про статические локальные переменные.



Это довольно интересная сущность, которая сочетает в себе поведение локального объекта функции и глобальной переменной.



От локального объекта она берет область видимости. То есть к этой переменной по имени никак нельзя обратиться вне ее функции. Можно, например, вернуть из функции ссылку на эту переменную и иметь возможность ее читать и модифицировать. Но по имени к ней можно обратиться только внутри функции. Соответственно, у static local variable нет никакого собственного типа линковки, это бессмысленно.



От глобальной переменной она берет статическое время жизни. То есть, начиная с момента своей инициализации, она продолжает существовать, пока не вызовется std::exit aka завершение программы.



Разберем немного цикл жизни такой переменной.



1) Она инициализируется при первом достижении исполнения ее объявления. Стандарт нам говорит:

such a variable is initialized the first 

time control passes through its declaration; [...]

If control enters the declaration concurrently

while the variable is being initialized,

the concurrent execution shall wait for

completion of the initialization.





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



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



2) При выходе из скоупа функции для статической локальной переменной не вызывается деструктор. Она продолжает жить не тужить и сохраняет свое значение до следующего вызова функции.



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



4) После завершения функции main переменная разрушается. Press F умершим.



Пример:



std::string BytesToHex(const void* bytes, size_t size)

{

if (size) {

static const char kHexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7',

'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

std::string output;

output.reserve(size * 2);

auto c = static_cast<const uint8_t*>(bytes);

for (size_t i = 0; i < size; ++i) {

uint8_t value = *(c + i);

output.push_back(kHexDigits[value >> 4]);

output.push_back(kHexDigits[value & 0xf]);

}

return output;

}

else {

return "";

}

}



int main()

{

std::cout << BytesToHex("", 0) << std::endl;

std::cout << BytesToHex("123", 3) << std::endl;

std::cout << BytesToHex("abc", 3) << std::endl;

}





Функция BytesToHex переводит любое количество байт от заданного указателя в их hex представление. Раз мы знаем, что hex представление содержит только 16 символов и больше нигде эти символы не нужны, то очень удобно поместить массив этих символов в саму функцию в качестве локальной статической переменной. Так мы инкапсулируем данные и сохраним возможность 1 раз создать переменную и пользоваться именно этим инстансом во всех вызовах функции.



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



Combine your best sides. Stay cool.



#cpp11 #multitasking #cppcore