Мне было интересно, можно ли объявить extern локально и регистровую переменную. Если можно, то какие ограничения будут наложены?
Можно ли объявить локальные и регистровые переменные extern?
Ответы (5)
В некоторых случаях локальные переменные могут быть объявлены внешними
Давайте прочитаем проект стандарта C99 N1256.
Стандарт называет локальные переменные имеющими блочную область видимости.
6.7.1/5 Спецификаторы класса хранения говорят:
Объявление идентификатора функции с блочной областью действия не должно иметь явного спецификатора класса хранения, отличного от extern.
Тогда о том, что означает добавление extern
к локальной переменной, 6.2.2/4 Связи идентификаторов говорит:
Для идентификатора, объявленного с помощью спецификатора класса хранения extern в области, в которой видно предыдущее объявление этого идентификатора, если предыдущее объявление указывает внутреннюю или внешнюю связь, связь идентификатора в более позднем объявлении такая же, как связь указывается в предварительном заявлении. Если предыдущее объявление не видно или если предыдущее объявление не указывает на связь, то идентификатор имеет внешнюю связь.
Давайте разберем эти случаи.
без предварительного заявления
void f() {
extern int i;
}
такой же как:
extern int i;
void f() {}
за исключением того, что объявление видно только внутри f
.
Это связано с тем, что i
не имеет видимого предыдущего объявления. Таким образом, i
имеет внешнюю связь (такую же связь, что и глобальные переменные).
предыдущее объявление не указывает на связь
int i;
void f() {
extern int i;
}
такой же как:
void f() {
extern int i;
}
потому что предыдущее объявление int i
не указывает на связь, потому что в параграфе 6 говорится:
Следующие идентификаторы не имеют связи: идентификатор, объявленный чем-то иным, чем объект или функция; идентификатор, объявленный как параметр функции; идентификатор области блока для объекта, объявленного без спецификатора класса хранения extern.
предварительное объявление указывает внутреннюю или внешнюю связь
extern int i;
void f() {
extern int i;
}
такой же как:
extern int i;
void f() {}
а также:
static int i;
void f() {
extern int i;
}
такой же как:
static int i;
void f() {}
потому что в обоих случаях у нас есть предыдущие видимые объявления внешней и внутренней (static
) связи соответственно.
Инициализировать локальный внешний
Недействительный С:
void f() {
extern int i = 0;
}
потому что объявление области блока имеет инициализацию.
Действительный С:
extern int i = 0;
void f() {}
но, возможно, плохой стиль, потому что эквивалентен более короткому:
int i = 0;
void f() {}
потому что 6.7.8 Инициализация говорит:
Если объявление идентификатора имеет область действия блока, а идентификатор имеет внешнюю или внутреннюю связь, объявление не должно иметь инициализатора для идентификатора.
void f() { static void g() {} }
недействителен, только extern
можно указать для объявлений функций области видимости блока.
- person blelbach; 25.03.2018
int i; extern int i;
совпадает с extern int i;
. Однако это не так, потому что поведение не определено. C 2018 (и 2011 и ранее) 6.7 3 дает ограничение, согласно которому, если идентификатор не имеет связи, должно быть не более одного его объявления в той же области и пространстве имен, с некоторыми исключениями для имен и тегов typedef. Поскольку i
из int i;
не имеет связи, не должно быть другого его объявления в той же области. Когда это ограничение нарушается, поведение не определено.
- person Eric Postpischil; 10.02.2021
int i
из функции. Это считается другой областью действия? Я не мог легко найти ограничение, о котором вы упоминаете, в черновиках C11/C18 (но я вам верю), не могли бы вы указать абзац / цитату? Ты все еще говоришь как твой отец ;-)
- person Ciro Santilli 新疆再教育营六四事件ۍ 10.02.2021
int i
за пределы функции перемещает его в новую область. (Каждый составной оператор начинает новый блок [блок для составного оператора, который определяет функцию, фактически начинается в начале объявлений параметров], как и операторы выбора и итерации и их подоператоры.) Однако, когда int i;
находится вне какой-либо функции, у него нет никакой связи; он имеет внешнюю связь. Для примера без связи, за которой следует внешняя связь, которая не является нарушением ограничения, у вас может быть void f(void) { int i; { extern int i; … } }
.
- person Eric Postpischil; 10.02.2021
- Можно ли объявить локальные переменные внешними?
Нет. Но глобальную переменную можно объявить extern
локально.
// file1.c
int Count;
// file2.c
void foo(void) {
extern int Count;
Count++;
}
- Можно ли регистровые переменные объявлять extern?
Нет. Переменная не может быть extern
и register
.
C11 dr 6.7.1 Спецификаторы класса хранения
1 спецификатор класса хранилища:typedef
extern
static
_Thread_local
auto
register
Ограничения
2 Максимум один спецификатор класса хранения может быть указан в спецификаторах объявления в объявлении, за исключением того, что_Thread_local
может стоять вместе сstatic
илиextern
)
6.9 Внешние определения состояний C99:
Спецификаторы класса хранения auto и register не должны появляться в спецификаторах объявления во внешнем объявлении.
Вам разрешено только определять глобальную переменную как extern
. Сообщить компилятору (и компоновщику), что он определен в другом месте.
Локальная переменная существует только в локальной области видимости, поскольку она создается в стеке или в регистре. Когда выполнение не входит в область действия (больше), стек разворачивается (поэтому свободное место снова становится доступным) или регистр используется для других целей, а переменная не существует (больше).
Таким образом, определение локального внешнего модуля было бы «странным» и невозможным (из-за использования стека).
extern
, чтобы сообщить компилятору, что она уже создана где-то еще и что компоновщик должен найти ее во время компоновки, чтобы ее можно было использовать (даже если она явно не определена в исходном файле, который ее использует). Я обновил свой ответ, чтобы сделать его более понятным!
- person Veger; 15.01.2013
extern int a;
, из области действия функции, если переменная определена в другом месте в глобальной области. Вам не нужно помещать extern int a;
в глобальную область в вашем файле, если вам нужно получить к нему доступ только из одной функции.
- person Jite; 15.01.2013
extern
с определением, если хотите. И это хорошо, иначе вы не смогли бы включить собственный заголовочный файл библиотеки в исходный файл библиотеки до определения ее глобальных переменных :-) Внешняя связь означает, что если две разные TU используют одинаковые имена, обе имеют внешнюю связь, они относятся к одному и тому же объекту/функции.
- person Steve Jessop; 15.01.2013
Фраза register variable
мне непонятна, поэтому я бы сделал одно смелое предположение о том, что действительно интересует OP, и перефразировал исходный вопрос как: Could local variables be declared with extern specifier?
, проиллюстрированный следующим фрагментом:
int main() {
extern int x; // Is this OK?
return 0;
}
Ответ положительный.
объем (видимость) и хранение - два независимых и связанных понятия. Здесь x
— это одна локальная переменная (область видимости), и она видна только внутри этого блока. extern
диктует хранилище, то есть это всего лишь одно объявление, эта переменная определена где-то еще. Рекомендовал бы стандарт C для определенной ссылки.
Что касается опущенной части register
, я предполагаю, что OP имел в виду одну переменную с спецификатором класса хранения register
, например register int x
. Тогда нельзя одновременно указывать register
и extern
.
int main() {
extern auto int x; // This is wrong.
return 0;
}
At most, one storage-class specifier may be given in the declaration specifiers in a declaration, except that _Thread_local may appear with static or extern.
Симметричным вопросом будет: допустимо ли указывать auto
или register
с глобальными или внешними переменными, и именно об этом ответ Алексея Фрунзе.
auto int x; // This is wrong.
int main() {
return 0;
}