static volatile unsigned char PORTB @ 0x06;
Это строка кода в заголовочном файле микроконтроллера PIC. Оператор @
используется для хранения значения PORTB внутри адреса 0x06
, который является регистром внутри PIC-контроллера, представляющего PORTB. На данный момент у меня есть четкое представление.
Эта строка объявлена как глобальная переменная внутри файла заголовка ( .h
). Итак, из того, что я узнал о языке C, "статическая глобальная переменная" не видна никакому другому файлу - или, просто, статические глобальные переменные/функции не могут использоваться вне текущего файла.
Тогда как это ключевое слово может PORTB
быть видимым для моего основного исходного файла и многих других файлов заголовков, которые я создал вручную?
В моем основном исходном файле я добавил только заголовочный файл. #include pic.h
Это как-то связано с моим вопросом?
Ключевое слово «статический» в C имеет два принципиально разных значения.
В этом контексте «статический» сочетается с «внешним» для управления областью действия имени переменной или функции. Static приводит к тому, что имя переменной или функции доступно только в пределах одной единицы компиляции и доступно только для кода, который существует после объявления/определения в тексте единицы компиляции.
Само это ограничение на самом деле что-то значит только тогда и только тогда, когда в вашем проекте более одной единицы компиляции. Если у вас есть только одна единица компиляции, то она все равно делает что-то, но эти эффекты в основном бессмысленны (если только вы не любите копаться в объектных файлах, чтобы прочитать, что сгенерировал компилятор).
Как уже отмечалось, это ключевое слово в этом контексте сочетается с ключевым словом «extern», которое делает обратное — делает имя переменной или функции связываемым с тем же именем, что и в других единицах компиляции. Таким образом, вы можете рассматривать «статический» как требующий, чтобы переменная или имя были найдены в текущей единице компиляции, в то время как «внешний» разрешает связывание единиц кросс-компиляции.
Статическое время жизни означает, что переменная существует на протяжении всей программы (какой бы продолжительной она ни была). Когда вы используете 'static' для объявления/определения переменной внутри функции, это означает, что переменная создается за некоторое время до ее первого использования ( что означает, что каждый раз, когда я сталкивался с этим, переменная создается до запуска main()) и не уничтожается впоследствии. Даже когда выполнение функции завершено и она возвращается к вызывающей стороне. И точно так же, как статические переменные времени жизни, объявленные вне функций, они инициализируются в тот же момент — перед запуском main() — семантическим нулем (если инициализация не предусмотрена) или заданным явным значением, если оно задано.
Это отличается от функциональных переменных типа «авто», которые создаются новыми (или как будто новые) каждый раз при входе в функцию, а затем уничтожаются (или как если бы они были уничтожены) при выходе из функции.
В отличие от влияния применения «статического» на определение переменной вне функции, которое напрямую влияет на ее область действия, объявление функциональной переменной (очевидно, внутри тела функции) как «статической» не влияет на ее область . Область действия определяется тем фактом, что она была определена в теле функции. Статические переменные времени жизни, определенные в функциях, имеют ту же область видимости, что и другие «автоматические» переменные, определенные в телах функций — область действия функции.
Таким образом, ключевое слово «статическое» имеет разные контексты, что равнозначно «совершенно разным значениям». Причина, по которой его использовали двояко, заключалась в том, чтобы избежать использования другого ключевого слова. (Об этом велась долгая дискуссия.) Было сочтено, что программисты могут терпеть такое использование, и ценность отказа от еще одного ключевого слова в языке была более важной (чем аргументы в противном случае).
(Все переменные, объявленные вне функций, имеют статическое время жизни и не нуждаются в ключевом слове «статический», чтобы сделать это верным. Таким образом, это освободило ключевое слово, которое будет использоваться там, чтобы означать что-то совершенно другое: «видимый только в одной компиляции единица.» Это своего рода хак.)
статический изменчивый беззнаковый символ 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;" компоновщик не может продвигать его во время компоновки, так что это фактически определение во время компиляции .
extern
, и это был бы более правильный способ C: объявить переменную extern
в заголовочном файле для многократного включения в программу и определить ее в каком-то не-заголовочном файле для компиляции и подключен ровно один раз. В конце концов, PORTB
предполагается , что это ровно один экземпляр переменной, на которую могут ссылаться разные cu. Таким образом, использование static
здесь является своего рода ярлыком, который они использовали, чтобы избежать необходимости в другом файле .c в дополнение к файлу заголовка.static
работает только потому, что он опирается на (непереносимое) расширение ( @ 0x06;
), которое позволяет размещать несколько (статических) экземпляров переменной в одной памяти. расположение. Использование extern
этого не будет проблемой.static
для глобальных переменных в отличие от локальных переменных функции.@ 0x06
является частью определения переменной, а не ее объявления. В программе может быть только одно определение (нестатической) переменной, все остальное приводит к ошибке во время компоновки (или раньше). Вы можете объявить (нестатическую) переменную несколько раз через extern
, но может быть только одно определение.static
. Я просто хочу сказать, что в этом случае используется статическое объявление (и определение) в заголовочном файле, т.е. ссылка на одну и ту же переменную/ячейку памяти из нескольких файлов .c, работает только из-за (непереносимости , не- стандартный) @ 0x06;
спецификатор адреса, в то время как "правильный" способ C будет использовать extern
в объявлении(ях) и отдельном единственном определении переменной. Без @ 0x06
спецификатора адреса каждыйextern
.extern
не создало бы проблем, потому что это гарантирует наличие только одного определения переменной.static
s не видны за пределами текущей единицы компиляции или «единицы перевода». Это не то же самое, что и тот же файл .
Обратите внимание, что вы включаете файл заголовка в любой исходный файл, где вам могут понадобиться переменные, объявленные в заголовке. Это включение делает заголовочный файл частью текущей единицы перевода и (экземпляром) видимой внутри нее переменной.
Files included by using the #include preprocessor directive become part of the compilation unit.
когда вы включаете файл заголовка (.h) в файл .c, подумайте об этом как о вставке содержимого заголовка в исходный файл, и теперь это ваша единица компиляции. Если вы объявите эту статическую переменную или функцию в файле .c, вы сможете использовать их только в том же файле, который в конце будет другой единицей компиляции.#include
известен как единица перевода предварительной обработки . После предварительной обработки единица перевода предварительной обработки называется единицей перевода ».Я попытаюсь обобщить комментарии и ответ @JimmyB с пояснительным примером:
Предположим, этот набор файлов:
static_test.c:
#include <stdio.h>
#if USE_STATIC == 1
#include "static.h"
#else
#include "no_static.h"
#endif
void var_add_one();
void main(){
say_hello();
printf("var is %d\n", var);
var_add_one();
printf("now var is %d\n", var);
}
статический.ч:
static int var=64;
static void say_hello(){
printf("Hello!!!\n");
};
no_static.h:
int var=64;
void say_hello(){
printf("Hello!!!\n");
};
static_src.c:
#include <stdio.h>
#if USE_STATIC == 1
#include "static.h"
#else
#include "no_static.h"
#endif
void var_add_one(){
var = var + 1;
printf("Added 1 to var: %d\n", var);
say_hello();
}
Вы можете скомпилировать и запустить код, используя gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test
статический заголовок или gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test
нестатический заголовок.
Обратите внимание, что здесь присутствуют две единицы компиляции: static_src и static_test. Когда вы используете статическую версию заголовка ( -DUSE_STATIC=1
), версия var
и say_hello
будет доступна для каждой единицы компиляции, то есть обе единицы могут их использовать, но проверьте, что даже если var_add_one()
функция увеличивает свою var
переменную, когда основная функция печатает свою var
переменную , это все еще 64:
$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test 14:33:12⌚
Hello!!!
var is 64
Added 1 to var: 65
Hello!!!
now var is 64
Теперь, если вы попытаетесь скомпилировать и запустить код, используя нестатическую версию ( -DUSE_STATIC=0
), он выдаст ошибку компоновки из-за дублированного определения переменной:
$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test 14:35:30⌚
/tmp/ccLBy1s7.o:(.data+0x0): multiple definition of `var'
/tmp/ccV6izKJ.o:(.data+0x0): first defined here
/tmp/ccLBy1s7.o: In function `say_hello':
static_test.c:(.text+0x0): multiple definition of `say_hello'
/tmp/ccV6izKJ.o:static_src.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
zsh: no such file or directory: ./static_test
Надеюсь, это поможет вам прояснить этот вопрос.
#include pic.h
примерно означает «скопировать содержимое pic.h в текущий файл». В результате каждый файл, включающий в себя, pic.h
получает собственное локальное определение PORTB
.
Возможно, вам интересно, почему не существует единого глобального определения PORTB
. Причина довольно проста: вы можете определить глобальную переменную только в одном файле C , поэтому, если вы хотите использовать PORTB
в нескольких файлах в своем проекте, вам потребуется pic.h
с объявлениемPORTB
и pic.c
с его определением . Разрешение каждому файлу C определять свою собственную копию PORTB
упрощает создание кода, поскольку вам не нужно включать в свой проект файлы, которые вы не писали.
Дополнительным преимуществом статических переменных по сравнению с глобальными является то, что вы получаете меньше конфликтов имен. AC-файл, который не использует какие-либо аппаратные функции MCU (и, следовательно, не включает pic.h
), может использовать имя PORTB
для своих целей. Не то чтобы это было хорошей идеей делать это специально, но когда вы разрабатываете, например, независимую от MCU математическую библиотеку, вы будете удивлены, насколько легко случайно повторно использовать имя, которое используется одним из MCU.
Уже есть несколько хороших ответов, но я думаю, что причину путаницы нужно решать просто и прямо:
Объявление PORTB не является стандартным C. Это расширение языка программирования C, которое работает только с компилятором PIC. Расширение необходимо, поскольку PIC не предназначены для поддержки C.
Использование ключевого static
слова здесь сбивает с толку, потому что вы никогда не использовали бы его static
таким образом в обычном коде. Для глобальной переменной вы должны использовать extern
в заголовке, а не static
. Но PORTB не является обычной переменной . Это хак, который говорит компилятору использовать специальные инструкции по сборке для регистрового ввода-вывода. Объявление PORTB static
помогает заставить компилятор действовать правильно.
При использовании в области файла static
ограничивает область действия переменной или функции этим файлом. «Файл» означает файл C и все, что в него скопировано препроцессором. Когда вы используете #include, вы копируете код в свой файл C. Вот почему использование static
в заголовке не имеет смысла — вместо одной глобальной переменной каждый файл, который включает заголовок, получит отдельную копию переменной.
Вопреки распространенному мнению, static
всегда означает одно и то же: статическое размещение с ограниченной областью действия. Вот что происходит с переменными до и после объявления static
:
+------------------------+-------------------+--------------------+
| Variable type/location | Allocation | Scope |
+------------------------+-------------------+--------------------+
| Normal in file | static | global |
| Normal in function | automatic (stack) | limited (function) |
| Static in file | static | limited (file) |
| Static in function | static | limited (function) |
+------------------------+-------------------+--------------------+
Что сбивает с толку, так это то, что поведение переменных по умолчанию зависит от того, где они определены.
Причина, по которой основной файл может видеть «статическое» определение порта, заключается в директиве #include. Эта директива эквивалентна вставке всего заголовочного файла в ваш исходный код в той же строке, что и сама директива.
Компилятор microchip XC8 одинаково обрабатывает файлы .c и .h, поэтому вы можете поместить свои определения переменных в любой из них.
Обычно заголовочный файл содержит "внешнюю" ссылку на переменные, которые определены в другом месте (обычно файл .c).
Переменные порта должны быть указаны по конкретным адресам памяти, которые соответствуют фактическому оборудованию. Таким образом, фактическое (не внешнее) определение должно где-то существовать.
Я могу только догадываться, почему корпорация Microchip решила поместить фактические определения в файл .h. Скорее всего, им просто нужен был один файл (.h) вместо двух (.h и .c) (чтобы упростить задачу пользователю).
Но если вы поместите фактические определения переменных в файл заголовка, а затем включите этот заголовок в несколько исходных файлов, компоновщик будет жаловаться, что переменные определены несколько раз.
Решение состоит в том, чтобы объявить переменные как статические, тогда каждое определение будет рассматриваться как локальное для этого объектного файла, и компоновщик не будет жаловаться.
гоммер
Финбарр
ДжиммиБи
static
глобальные объекты видны внутри всей отдельной единицы компиляции и не экспортируются за ее пределы. Они очень похожиprivate
на членов класса в ООП. Т.е. каждая переменная, которая должна быть разделена между различными функциями внутри модуля компиляции, но не должна быть видимой вне этого cu, должна бытьstatic
. Это также уменьшает «затирание» глобального пространства имен программы.Питер Мортенсен