static volatile unsigned char PORTB @ 0x06;
Это строка кода в заголовочном файле микроконтроллера PIC. @
Оператор используется для хранения значения PORTB внутри адрес 0x06
, который является регистром внутри контроллера PIC , который представляет PORTB. До этого момента у меня есть четкая идея.
Эта строка объявлена как глобальная переменная внутри заголовочного файла ( .h
). Итак, из того, что я узнал о языке C, «статическая глобальная переменная» не видна ни в каком другом файле - или просто статические глобальные переменные / функции не могут использоваться вне текущего файла.
Тогда как это ключевое слово PORTB
может быть видимо для моего основного исходного файла и многих других заголовочных файлов, которые я создал вручную?
В моем основном исходном файле я только добавил заголовочный файл. #include pic.h
Это как-то связано с моим вопросом?
static
глобальные переменные видны внутри всего единого блока компиляции и не экспортируются за пределы этого. Они очень похожи наprivate
членов класса в ООП. Т.е. каждая переменная, которая должна быть разделена между различными функциями внутри модуля компиляции, но не должна быть видимой вне того, что в действительности должно быть custatic
. Это также уменьшает «забивание» глобального пространства имен программы.Ответы:
Ключевое слово «статический» в Си имеет два принципиально разных значения.
Ограничивающая область
В этом контексте «static» объединяется с «extern» для управления областью действия переменной или имени функции. Static заставляет имя переменной или функции быть доступным только в пределах одного модуля компиляции и доступно только для существующего кода после объявления / определения в тексте единицы компиляции.
Это ограничение само по себе действительно означает что-то, только если у вас есть более одного модуля компиляции в вашем проекте. Если у вас есть только один модуль компиляции, он все равно работает, но эти эффекты в основном бессмысленны (если вы не любите копаться в объектных файлах, чтобы прочитать то, что сгенерировал компилятор).
Как уже отмечалось, это ключевое слово в этом контексте соединяется с ключевым словом «extern», что делает обратное - делая имя переменной или функции связываемым с тем же именем, что и в других единицах компиляции. Таким образом, вы можете посмотреть на «static» как на требование, чтобы переменная или имя находились в текущей единице компиляции, тогда как «extern» разрешает связывание блоков кросс-компиляции.
Статическая жизнь
Статическое время жизни означает, что переменная существует на протяжении всей программы (как бы долго это ни было.) Когда вы используете «static» для объявления / определения переменной внутри функции, это означает, что переменная создается за некоторое время до ее первого использования ( Это означает, что каждый раз, когда я это испытывал, переменная создается до запуска main () и впоследствии не уничтожается. Даже когда выполнение функции завершено и она возвращается к своему вызывающему. И так же, как статические переменные времени жизни, объявленные вне функций, они инициализируются в один и тот же момент - до запуска main () - в семантический ноль (если инициализация не предусмотрена) или в указанное явное значение, если оно задано.
Это отличается от переменных функции типа 'auto', которые создаются новыми (или, как если бы они были новыми) каждый раз при входе в функцию, а затем уничтожаются (или, как если бы они были уничтожены) при выходе из функции.
В отличие от воздействия применения «static» к определению переменной вне функции, что напрямую влияет на ее область действия, объявление переменной функции (очевидно, в теле функции) как «static» не влияет на ее область действия. Область действия определяется тем, что она была определена в теле функции. Статические переменные времени жизни, определенные в функциях, имеют ту же область видимости, что и другие «автоматические» переменные, определенные в теле функции - область действия функции.
Резюме
Таким образом, ключевое слово «static» имеет разные контексты с тем, что составляет «очень разные значения». Причина, по которой он использовался двумя способами, например, в том, чтобы избегать использования другого ключевого слова. (Об этом было долгое обсуждение.) Чувствовалось, что программисты могут терпеть использование, и значение избегания использования еще одного ключевого слова в языке более важно (чем аргументы в противном случае.)
(Все переменные, объявленные вне функций, имеют статическое время жизни и не нуждаются в ключевом слове «static», чтобы сделать это истинным. Так что этот вид освобождает ключевое слово, которое будет использоваться там, чтобы означать нечто совершенно иное: «видимое только в одной компиляции блок. «Это взломать, вроде.)
Особое примечание
Слово «статический» здесь следует интерпретировать как означающее, что компоновщик не будет пытаться сопоставить несколько вхождений PORTB, которые могут быть найдены в более чем одном модуле компиляции (при условии, что в вашем коде более одного).
Он использует специальный (непереносимый) синтаксис для указания «местоположения» (или числового значения метки, которое обычно является адресом) PORTB. Таким образом, компоновщик получает адрес, и ему не нужно его искать. Если бы у вас было две единицы компиляции, использующие эту линию, они все равно указывали бы на одно и то же место. Так что здесь нет необходимости обозначать его как «внешний».
Если бы они использовали 'extern', это могло бы создать проблему. Затем компоновщик сможет увидеть (и попытается сопоставить) несколько ссылок на PORTB, найденных в нескольких компиляциях. Если все они указывают адрес, подобный этому, и адреса по какой-то причине НЕ совпадают [ошибка?], То что он должен делать? Пожаловаться? Или? (Технически, с «extern» практическим правилом было бы то, что только ОДНА единица компиляции будет указывать значение, а другие не должны.)
Проще пометить его как «статический», избегая того, чтобы компоновщик беспокоился о конфликтах, и просто возложил вину за любые ошибки за несовпадающие адреса на того, кто бы ни изменил адрес, на то, что не должно быть.
В любом случае, переменная рассматривается как имеющая «статическое время жизни». (И «изменчивый».)
Декларация не является определение , но все определения деклараций
В C определение создает объект. Это также заявляет об этом. Но объявление обычно (см. Примечание к пуле ниже) не создает объект.
Ниже приведены определения и декларации:
Ниже приведены не определения, а только объявления:
Обратите внимание, что объявления не создают фактический объект. Они только объявляют подробности об этом, которые затем может использовать компилятор, чтобы помочь сгенерировать правильный код и, при необходимости, предоставить предупреждения и сообщения об ошибках.
Выше я говорю «обычно», советуем. В некоторых случаях объявление может создать объект и, следовательно, повышается до определения компоновщиком (а не компилятором). Таким образом, даже в этом редком случае компилятор C все еще думает, что объявление является только объявлением. Это фаза компоновщика, которая делает любые необходимые продвижения некоторой декларации. Имейте это в виду.
В приведенных выше примерах, если окажется, что есть только объявления для "extern int b;" во всех связанных модулях компиляции компоновщик отвечает за создание определения. Имейте в виду, что это событие времени соединения. Во время компиляции компилятор полностью не осведомлен. Это может быть определено только во время соединения, если объявление этого типа наиболее продвигается.
Компилятор знает, что "static int a;" не может быть продвинут компоновщиком во время компоновки, так что это фактически определение во время компиляции .
источник
extern
, и это был бы более правильный способ сделать это на С: объявить переменнуюextern
в файле заголовка для многократного добавления в программу и определить ее в некотором файле без заголовка для компиляции и связаны ровно один раз. В конце концов,PORTB
это должно быть ровно один экземпляр переменной , в которой различные куб может ссылаться. Таким образом, использованиеstatic
здесь является своего рода ярлыком, который они использовали, чтобы избежать необходимости использования другого файла .c в дополнение к файлу заголовка.static
s не видны за пределами текущей единицы компиляции или "единицы перевода". Это не то же самое, что и тот же файл .Обратите внимание, что вы включаете файл заголовка в любой исходный файл, где вам могут понадобиться переменные, объявленные в заголовке. Это включение делает заголовочный файл частью текущей единицы перевода и (экземпляром) переменной, видимой внутри него.
источник
Files included by using the #include preprocessor directive become part of the compilation unit.
Когда вы включаете файл заголовка (.h) в файл .c, думайте о нем как о вставке содержимого заголовка в исходный файл, и теперь это ваш модуль компиляции. Если вы объявите эту статическую переменную или функцию в файле .c, вы можете использовать их только в том же файле, который в конце станет еще одним модулем компиляции.Я постараюсь обобщить комментарии и ответ @ JimmyB с пояснительным примером:
Предположим, этот набор файлов:
static_test.c:
static.h:
no_static.h:
static_src.c:
Вы можете скомпилировать и запустить код, используя
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. Когда вы используете статическую версию header (
-DUSE_STATIC=1
), версияvar
иsay_hello
будет доступна для каждого модуля компиляции, то есть оба модуля могут использовать их, но убедитесь, что даже еслиvar_add_one()
функция увеличивает своюvar
переменную, когда основная функция печатает своюvar
переменную , это еще 64Теперь, если вы попытаетесь скомпилировать и запустить код, используя нестатическую версию (
-DUSE_STATIC=0
), он выдаст ошибку связывания из-за дублированного определения переменной:Надеюсь, это поможет вам прояснить этот вопрос.
источник
#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 не является нормальной переменной . Это хак, который говорит компилятору использовать специальные инструкции по сборке для регистрации ввода-вывода. Объявление PORTBstatic
помогает заставить компилятор делать правильные вещи.При использовании в области файла
static
ограничивает область действия переменной или функции этим файлом. «Файл» означает файл C и все, что копируется в него препроцессором. Когда вы используете #include, вы копируете код в ваш C-файл. Вот почему использованиеstatic
в заголовке не имеет смысла - вместо одной глобальной переменной каждый файл, содержащий #include заголовка, получит отдельную копию переменной.Вопреки распространенному мнению,
static
всегда означает одно и то же: статическое распределение с ограниченной областью действия. Вот что происходит с переменными до и после объявленияstatic
:Что сбивает с толку, так это то, что поведение переменных по умолчанию зависит от того, где они определены.
источник
Причина, по которой основной файл может видеть «статическое» определение порта, заключается в директиве #include. Эта директива эквивалентна вставке всего файла заголовка в исходный код в той же строке, что и сама директива.
Компилятор микрочипа XC8 обрабатывает файлы .c и .h одинаково, поэтому вы можете поместить определения переменных в любое из них.
Обычно заголовочный файл содержит «внешнюю» ссылку на переменные, которые определены в другом месте (обычно это файл .c).
Переменные порта должны быть указаны по конкретным адресам памяти, которые соответствуют фактическому оборудованию. Таким образом, фактическое (не внешнее) определение должно существовать где-то.
Я могу только догадываться, почему корпорация Microchip решила поместить фактические определения в файл .h. Вероятное предположение состоит в том, что они просто хотели один файл (.h) вместо 2 (.h и .c) (чтобы было проще для пользователя).
Но если вы поместите фактические определения переменных в файл заголовка, а затем включите этот заголовок в несколько исходных файлов, то компоновщик будет жаловаться, что переменные определены несколько раз.
Решение состоит в том, чтобы объявить переменные как статические, тогда каждое определение рассматривается как локальное для этого объектного файла, и компоновщик не будет жаловаться.
источник