Ключевое слово «статический» в Си имеет два принципиально разных значения.
Ограничивающая область
В этом контексте «static» объединяется с «extern» для управления областью действия переменной или имени функции. Static заставляет имя переменной или функции быть доступным только в пределах одного модуля компиляции и доступно только для существующего кода после объявления / определения в тексте единицы компиляции.
Это ограничение само по себе действительно означает что-то, только если у вас есть более одного модуля компиляции в вашем проекте. Если у вас есть только один модуль компиляции, он все равно работает, но эти эффекты в основном бессмысленны (если вы не любите копаться в объектных файлах, чтобы прочитать то, что сгенерировал компилятор).
Как уже отмечалось, это ключевое слово в этом контексте соединяется с ключевым словом «extern», что делает обратное - делая имя переменной или функции связываемым с тем же именем, что и в других единицах компиляции. Таким образом, вы можете посмотреть на «static» как на требование, чтобы переменная или имя находились в текущей единице компиляции, тогда как «extern» разрешает связывание блоков кросс-компиляции.
Статическая жизнь
Статическое время жизни означает, что переменная существует на протяжении всей программы (как бы долго это ни было.) Когда вы используете «static» для объявления / определения переменной внутри функции, это означает, что переменная создается за некоторое время до ее первого использования ( Это означает, что каждый раз, когда я это испытывал, переменная создается до запуска main () и впоследствии не уничтожается. Даже когда выполнение функции завершено и она возвращается к своему вызывающему. И так же, как статические переменные времени жизни, объявленные вне функций, они инициализируются в один и тот же момент - до запуска main () - в семантический ноль (если инициализация не предусмотрена) или в указанное явное значение, если оно задано.
Это отличается от переменных функции типа 'auto', которые создаются новыми (или, как если бы они были новыми) каждый раз при входе в функцию, а затем уничтожаются (или, как если бы они были уничтожены) при выходе из функции.
В отличие от воздействия применения «static» к определению переменной вне функции, что напрямую влияет на ее область действия, объявление переменной функции (очевидно, в теле функции) как «static» не влияет на ее область действия. Область действия определяется тем, что она была определена в теле функции. Статические переменные времени жизни, определенные в функциях, имеют ту же область видимости, что и другие «автоматические» переменные, определенные в теле функции - область действия функции.
Резюме
Таким образом, ключевое слово «static» имеет разные контексты с тем, что составляет «очень разные значения». Причина, по которой он использовался двумя способами, например, в том, чтобы избегать использования другого ключевого слова. (Об этом было долгое обсуждение.) Чувствовалось, что программисты могут терпеть использование, и значение избегания использования еще одного ключевого слова в языке более важно (чем аргументы в противном случае.)
(Все переменные, объявленные вне функций, имеют статическое время жизни и не нуждаются в ключевом слове «static», чтобы сделать это истинным. Так что этот вид освобождает ключевое слово, которое будет использоваться там, чтобы означать нечто совершенно иное: «видимое только в одной компиляции блок. «Это взломать, вроде.)
Особое примечание
статический изменчивый тип unsigned char PORTB @ 0x06;
Слово «статический» здесь следует интерпретировать как означающее, что компоновщик не будет пытаться сопоставить несколько вхождений PORTB, которые могут быть найдены в более чем одном модуле компиляции (при условии, что в вашем коде более одного).
Он использует специальный (непереносимый) синтаксис для указания «местоположения» (или числового значения метки, которое обычно является адресом) PORTB. Таким образом, компоновщик получает адрес, и ему не нужно его искать. Если бы у вас было две единицы компиляции, использующие эту линию, они все равно указывали бы на одно и то же место. Так что здесь нет необходимости обозначать его как «внешний».
Если бы они использовали 'extern', это могло бы создать проблему. Затем компоновщик сможет увидеть (и попытается сопоставить) несколько ссылок на PORTB, найденных в нескольких компиляциях. Если все они указывают адрес, подобный этому, и адреса по какой-то причине НЕ совпадают [ошибка?], То что он должен делать? Пожаловаться? Или? (Технически, с «extern» практическим правилом было бы то, что только ОДНА единица компиляции будет указывать значение, а другие не должны.)
Проще пометить его как «статический», избегая того, чтобы компоновщик беспокоился о конфликтах, и просто возложил вину за любые ошибки за несовпадающие адреса на того, кто бы ни изменил адрес, на то, что не должно быть.
В любом случае, переменная рассматривается как имеющая «статическое время жизни». (И «изменчивый».)
Декларация не является определение , но все определения деклараций
В C определение создает объект. Это также заявляет об этом. Но объявление обычно (см. Примечание к пуле ниже) не создает объект.
Ниже приведены определения и декларации:
static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }
Ниже приведены не определения, а только объявления:
extern int b;
extern int f();
Обратите внимание, что объявления не создают фактический объект. Они только объявляют подробности об этом, которые затем может использовать компилятор, чтобы помочь сгенерировать правильный код и, при необходимости, предоставить предупреждения и сообщения об ошибках.
Выше я говорю «обычно», советуем. В некоторых случаях объявление может создать объект и, следовательно, повышается до определения компоновщиком (а не компилятором). Таким образом, даже в этом редком случае компилятор C все еще думает, что объявление является только объявлением. Это фаза компоновщика, которая делает любые необходимые продвижения некоторой декларации. Имейте это в виду.
В приведенных выше примерах, если окажется, что есть только объявления для "extern int b;" во всех связанных модулях компиляции компоновщик отвечает за создание определения. Имейте в виду, что это событие времени соединения. Во время компиляции компилятор полностью не осведомлен. Это может быть определено только во время соединения, если объявление этого типа наиболее продвигается.
Компилятор знает, что "static int a;" не может быть продвинут компоновщиком во время компоновки, так что это фактически определение во время компиляции .