Классы памяти в Си. Какие классы памяти существуют в языке си?
Обычная переменная, объявленная внутри блока без указания для неё иного класса, имеет класс памяти auto по умолчанию.
Такая переменная видна внутри блока.
Переменные с классом памяти auto размещаются в стеке.
Итак по умолчанию локальная переменная имеет класс памяти auto.
Класс памяти static
Переменные, объявленные как static, т.е. статические, сохраняют своё значение между вызовами. Пример для переменных static:
Получаем: 
При втором вызове значение переменной с классом памяти auto, т.е. autoVar в этом примере, потерялось, а значение переменной static, т.е. staticVar в этом примере, сохранилось.
Ещё одна тонкость. static переменная инициализируется только один раз. Если бы это было иначе, то значение статической staticVar обнулилось бы при повторном вызове.
По умолчанию все глобальные переменные являются статическими. И видны они только в своём файле.
Статические функции в Си
Если функция объявлена как static, т.е. статическая, то она видна только в своём файле. Из другого файла к static функции обратиться нельзя. Таким образом, ключевое слово static применительно к функциям в C означает их закрытость для обращений из других файлов программы. Пример. Объявим в отдельном модуле две функции: одну статическую, а другую обычную:
static функция staticFunc будет доступна только в данном модуле. Простая функция simpleFunc доступна для других модулей программы, т.е. её класс памяти extern. Память extern устанавливается для функций по умолчанию. Код основного модуля:
Строка, закрытая комментарием, ошибочна, ведь статическая функция staticFunc объявлена в другом модуле, а значит недоступна в основном модуле.
Класс памяти extern в C
Класс памяти extern в C используем в двух случаях:
- если переменная объявляется в программе ниже, чем ссылка на неё;
- если переменная объявлена в другом модуле.
Пример основного модуля:
и другого модуля программы:
Получаем: 
Класс памяти register в C
Применение модификатора register есть рекомендация компилятору хранить данную переменную в регистре, а не в оперативной памяти. Не факт, что так и будет. Если программа часто обращается к переменной, то есть смысл объявить её с модификатором register. Пример:
Применять register можно только к near указателям и целому типу. Использовать register можно и при указании формальных параметров функций. Примеры:
Пока смерть не разлучит нас или всё о static в C++

Всем привет. На одном из код-ревью я столкнулся с мыслью, что многие, а чего скрывать и я сам, не то чтобы хорошо понимаем когда нужно использовать ключевое слова static. В данной статье я хотел бы поделиться своими знаниями и информацией по поводу ключевого слова static. Статья будет полезна как начинающим программистам, так и людям, работающим с языком С++. Для понимания статьи у вас должны быть знания о процессе сборки проектов и владение языком С/С++ на базовом уровне. Кстати, static используется не только в С++, но и в С. В этой статье я буду говорить о С++, но имейте в виду, что всё то, что не связано с объектами и классами, в основном применимо и к языку С.
Что такое static?
Static — это ключевое слово в C++, используемое для придания элементу особых характеристик. Для статических элементов выделение памяти происходит только один раз и существуют эти элементы до завершения программы. Хранятся все эти элементы не в heap и не на stack, а в специальных сегментах памяти, которые называются .data и .bss (зависит от того инициализированы статические данные или нет). На картинке ниже показан типичный макет программной памяти.

Где используется?
Ниже приведена схема, как и где используется static в программе.

А теперь я постараюсь детально описать все то, что изображено на схеме. Поехали!
Статические переменные внутри функции
Статические переменные при использовании внутри функции инициализируются только один раз, а затем они сохраняют свое значение. Эти статические переменные хранятся в статической области памяти (.data или .bss), а не в стеке, что позволяет хранить и использовать значение переменной на протяжении всей жизни программы. Давайте рассмотрим две почти одинаковые программы и их поведение. Отличие в них только в том, что одна использует статическую переменную, а вторая нет.
Если не использовать static в строке 4, выделение памяти и инициализация переменной count происходит при каждом вызове функции counter(), и уничтожается каждый раз, когда функция завершается. Но если мы сделаем переменную статической, после инициализации (при первом вызове функции counter()) область видимости count будет до конца функции main(), и переменная будет хранить свое значение между вызовами функции counter().
Статические объекты класса
Статический объект класса имеет такие же свойства как и обычная статическая переменная, описанная выше, т.е. хранится в .data или .bss сегменте памяти, создается на старте и уничтожается при завершении программы, и инициализируется только один раз. Инициализация объекта происходит, как и обычно — через конструктор класса. Рассмотрим пример со статическим объектом класса.
В строке 3 мы создаем класс Base с конструктором (строка 5) и деструктором (строка 8). При вызове конструктора либо деструктора мы выводим название метода класса в консоль. В строке 14 мы создаем статический объект obj класса Base. Создание этого статического объекта будет происходить только при первом вызове функции foo() в строке 18.
Из-за того, что объект статический, деструктор вызывается не при выходе из функции foo() в строке 15, а только при завершении программы, т.к. статический объект разрушается при завершении программы. Ниже приведен пример той же программы, за исключением того, что наш объект нестатический.
Если мы уберем static при создании переменной в функции foo(), то разрушение объекта будет происходить в строке 15 при каждом вызове функции. В таком случае вывод программы будет вполне ожидаемый для локальной переменной с выделенной памятью на стеке:
Статические члены класса
В сравнении с предыдущими вариантами использования, статические члены класса немного сложнее для понимания. Давайте разберемся, почему. Предположим, у нас есть следующая программа:
В нашем примере мы создали класс А (строка 3) и класс В (строка 9) со статическими членами класса (строка 15). Мы предполагаем, что при создании объекта b в строке 19 будет создан объект a в строке 15. Так бы и произошло, если бы мы использовали нестатические члены класса. Но вывод программы будет следующим:
Причиной такого поведения является то, что статические члены класса не инициализируются с помощью конструктора, поскольку они не зависят от инициализации объекта. Т.е. в строке 15 мы только объявляем объект, а не определяем его, так как определение должно происходить вне класса с помощью оператора разрешения области видимости (::). Давайте определим члены класса B.
Теперь, после того как мы определили наш статический член класса в строке 18, мы можем увидеть следующий результат программы:
Нужно помнить, что член класса будет один для всех экземпляров класса B, т.е. если мы создали три объекта класса B, то конструктор статического члена класса будет вызван только один раз. Вот пример того, о чем я говорю:
Статические функции
Статические функции пришли в С++ из С. По умолчанию все функции в С глобальные и, если вы захотите создать две функции с одинаковым именем в двух разных .c(.cpp) файлах одного проекта, то получите ошибку о том, что данная функция уже определена (fatal error LNK1169: one or more multiply defined symbols found). Ниже приведен листинг трех файлов одной программы.
Для того чтобы исправить данную проблему, одну из функций мы объявим статической. Например эту:
В этом случае вы говорите компилятору, что доступ к статическим функциям ограничен файлом, в котором они объявлены. И он имеет доступ только к функции sum() из math.cpp файла. Таким образом, используя static для функции, мы можем ограничить область видимости этой функции, и данная функция не будет видна в других файлах, если, конечно, это не заголовочный файл (.h).
Как известно, мы не можем определить функцию в заголовочном файле не сделав ее inline или static, потому что при повторном включении этого заголовочного файла мы получим такую же ошибку, как и при использовании двух функций с одинаковым именем. При определении статической функции в заголовочном файле мы даем возможность каждому файлу (.cpp), который сделает #include нашего заголовочного файла, иметь свое собственное определение этой функции. Это решает проблему, но влечет за собой увеличение размера выполняемого файла, т.к. директива include просто копирует содержимое заголовочного файла в .cpp файл.
Статические функции-члены класса (методы)
Статическую функцию-член вы можете использовать без создания объекта класса. Доступ к статическим функциям осуществляется с использованием имени класса и оператора разрешения области видимости (::). При использовании статической функции-члена есть ограничения, такие как:
- Внутри функции обращаться можно только к статическим членам данных, другим статическим функциям-членам и любым другим функциям извне класса.
- Статические функции-члены имеют область видимости класса, в котором они находятся.
- Вы не имеете доступа к указателю this класса, потому что мы не создаем никакого объекта для вызова этой функции.
В классе A в строке 8 у нас есть статическая функция-член foo(). В строке 14, мы вызываем функцию используя имя класса и оператор разрешения области видимости и получаем следующий результат программы:
Из вывода видно, что никакого создания объекта нет и конструктор/деструктор не вызывается.
Если бы метод foo() был бы нестатическим, то компилятор выдал бы ошибку на выражение в строке 14, т.к. нужно создать объект для того, чтобы получить доступ к его нестатическим методам.
Заключение
В одной статье в интернете я нашел совет от автора – «Используйте static везде, где только можно». Я хотел бы написать, почему так делать не стоит, а стоит использовать только в случае необходимости.
Static функции в C
Не могу до конца понять смысл static функций. Я понимаю, что оно ограничивает видимость, но ведь она и так изначально ограничена. И я не могу вызвать функцию из другой единицы, если не объявил ее изначально в текущей единице трансляции. При этом, static функцию я все равно не могу указывать в *.h файле, и ее приходится объявлять в *.c файлах.
Получается, что и так и так, объявление локальных функций приходится делать в *.c и вызвать ее оттуда, без явного намерения (объявить в *.h или объявить extern ) не получится. Зачем тогда static ? Чтобы перестраховываться от этого?
![]()
static не имеет никакого отношения к видимости вообще. Видимость — свойство идентификатора, на которое static не оказывает никакого влияния.
Объявление функции как static придает ей внутреннее связывание, то есть ограничивает ее линкуемость из других единиц трансляции. Все ссылки на static -сущности обычно разрешаются на стадии компиляции и в генерируемом объектном файле не упоминаются вообще, т.е. линкер даже и не знает об их существовании. В частности, нет никакой необходимости беспокоиться о глобальных конфликтах имен при объявлении статических функций.
Ваши рассуждения на тему «могу объявить» или «не могу объявить» не совсем ясны. К функции с внешним связыванием вы всегда можете прилинковаться из другой единицы трансляции — достаточно просто выполнить ее объявление. К функции с внутренним связыванием (т.е. static -функции) вы прилинковаться из другой единицы трансляции никак не сможете. Разница налицо.
Также объявление функций как static дает компилятору исчерпывающее знание о всех контекстах вызова этой функции, что существенно повышает его возможности по оптимизации кода. (Известный интереснейший пример, в том числе существенно опирающийся на свойства static : Why undefined behavior may call a never-called function)
Static: Многоцелевое ключевое слово
Большинство ключевых слов C++ позволяют сделать одну вещь. Вы используете int для объявления целочисленной переменной, или тогда, когда функция возвращает целое значение, или принимает целое число в качестве аргумента. Вы используете оператор new для выделения памяти, а оператор delete — для ее освобождения. Вы можете использовать const для указания, что значение переменной не может быть изменено. По иронии судьбы, ключевое слово static , хотя и означает «неизменный», имеет несколько (и, видимо, не связанных между собой) способов использования. Ключевое слово static может быть использовано в трех основных контекстах:
- внутри функции;
- внутри определения класса;
- перед глобальной переменной внутри файла, составляющего многофайловую программу.
Использование static внутри функции является самым простым. Это просто означает, что после того, как переменная была инициализирована, она остается в памяти до конца программы. Вы можете думать об этом, как о переменной, которая хранит свое значение до полного завершения программы. Например, вы можете использовать статическую переменную для записи количества раз, когда функция была вызвана, просто добавив строки static int count = 0; и count++; в функцию. Так как count является статической переменной, строка static int count = 0; будет выполняться только один раз. Всякий раз, когда функция вызывается, count будет иметь последнее значение, данное ему.
Вы также можете использовать static таким образом, чтобы предотвратить переинициализацию переменной внутри цикла. Например, в следующем коде переменная number_of_times будет равна 100, несмотря на то что строка static int number_of_times = 0; находится внутри цикла, где она, по-видимому, должна исполнятся каждый раз, когда программа доходит до цикла. Хитрость заключается в том, что ключевое слово static препятствует повторной инициализации переменной. Одной из особенностей использования ключевого слова static является то, что оно автоматически устанавливает переменную в ноль для вас — но не полагайтесь на это (это делает ваши намерения неясными).
Вы можете использовать статические переменные для сохранения информации о последнем значении возвращенной функции, например, если вы хотите сохранить максимальное значение, рассчитанное по функции. Если вы делаете разбор строки, вы можете также хранить последний знак, возвращенный функцией, для того, чтобы иметь возможность вызвать ее с аргументом, означающим, что она должна вернуть последний знак.
Второе использование static — внутри определения класса. Хотя большинство переменных, объявленных внутри класса могут иметь разное значение в каждом экземпляре класса, статические поля класса будут иметь то же значение для всех экземпляров данного класса и даже не обязательно создавать экземпляр этого класса. Полезно представить себе, что статические переменные класса содержат информацию, необходимую для создания новых объектов (например в фабрике классов). Например, если вы хотите пронумеровать экземпляры класса, можно использовать статическую переменную для отслеживания последнего используемого номера. Важно отметить, что хорошим тоном при использовании статических переменных класса является использование class_name::х; , а не instance_of_class.x; . Это помогает напомнить программисту, что статические переменные не принадлежат к одному экземпляру класса, и что вам не обязательно создавать экземпляр этого класса. Как вы уже, наверное, заметили, для доступа к static можно использовать оператор области видимости, :: , когда вы обращаетесь к нему через имя класса.
Важно иметь в виду, при отладке или реализации программы с использованием static , что вы не можете инициализировать его внутри класса. В самом деле, если вы решите написать весь код класса в файл заголовка, вы даже не сможете инициализировать статическую переменную внутри файла заголовка; сделайте это в файле .cpp . Кроме того, вам необходимо инициализировать статические члены класса, или их не будет в области видимости. (Синтаксис немного странный: type class_name::static_variable = value .)
У вас также могут быть статические функции класса. Статические функции — это функции, которые не требуют экземпляра класса и вызываются так же, по аналогии со статическими переменным, с именем класса, а не с именем объекта. Например, a_class::static_function(); , а не an_instance.function(); . Статические функции могут работать только со статическими членами класса, так как они не относятся к конкретным экземплярам класса. Статические функции могут быть использованы для изменения статических переменных, отслеживать их значения — например, вы можете использовать статическую функцию, если вы решили использовать счетчик, чтобы дать каждому экземпляру класса уникальный идентификатор.
Например, вы можете использовать следующий код:
Обратите внимание, что вы должны включать тип статической переменной, когда вы устанавливаете его!
установит идентификатор на следующий идентификационный номер, не используемый любым другим объектом user . Обратите внимание, что это хороший стиль объявления идентификатора как константы.
Последнее использование static — глобальная переменная в файле кода. В этом случае использование static указывает, что исходный код в других файлах, которые являются частью проекта, не может получить доступ к переменной. Только код внутри того же файла может увидеть переменную (её область видимости ограничена файлом). Эта техника может быть использована для моделирования объектно-ориентированного кода, потому что она ограничивает видимость переменных и таким образом помогает избежать конфликта имен. Этот способ использования static является пережитком Cи.