Можно ли объявить локальные и регистровые переменные extern?

Мне было интересно, можно ли объявить extern локально и регистровую переменную. Если можно, то какие ограничения будут наложены?


person Av03    schedule 15.01.2013    source источник


Ответы (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 Инициализация говорит:

Если объявление идентификатора имеет область действия блока, а идентификатор имеет внешнюю или внутреннюю связь, объявление не должно иметь инициализатора для идентификатора.

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 19.06.2015
comment
Некропостинг, но - ISO/IEC 9899:1999 6.7.1/p5 не говорит о переменных области блока, он говорит о функциях области блока. Он говорит, что void f() { static void g() {} } недействителен, только extern можно указать для объявлений функций области видимости блока. - person blelbach; 25.03.2018
comment
@blelbach спасибо за это. Есть аналогичная цитата для переменных или ответ неверный? Если есть аналогичная цитата для переменных, можно ли с ней отредактировать ответ? - person Ciro Santilli 新疆再教育营六四事件ۍ 25.03.2018
comment
В разделе «предыдущая декларация не указывает на привязку» этот ответ говорит, что int i; extern int i; совпадает с extern int i;. Однако это не так, потому что поведение не определено. C 2018 (и 2011 и ранее) 6.7 3 дает ограничение, согласно которому, если идентификатор не имеет связи, должно быть не более одного его объявления в той же области и пространстве имен, с некоторыми исключениями для имен и тегов typedef. Поскольку i из int i; не имеет связи, не должно быть другого его объявления в той же области. Когда это ограничение нарушается, поведение не определено. - person Eric Postpischil; 10.02.2021
comment
@EricPostpischil спасибо за это, я попытался исправить это, переместив int i из функции. Это считается другой областью действия? Я не мог легко найти ограничение, о котором вы упоминаете, в черновиках C11/C18 (но я вам верю), не могли бы вы указать абзац / цитату? Ты все еще говоришь как твой отец ;-) - person Ciro Santilli 新疆再教育营六四事件ۍ 10.02.2021
comment
(теперь также заметил, что GCC фактически предупреждает об ошибке, о которой вы упомянули, когда оба находятся в объявлении extern функции «i» следует за объявлением без привязки, и это здорово) - person Ciro Santilli 新疆再教育营六四事件ۍ 10.02.2021
comment
Параграф 3 пункта 6.7 C 2018 гласит: «Если идентификатор не имеет связи, должно быть не более одного объявления идентификатора (в описателе или спецификаторе типа) с той же областью действия и в одном и том же пространстве имен, за исключением того, что: имя typedef может быть переопределено для обозначения того же типа, что и в настоящее время, при условии, что этот тип не является изменяемым типом; - теги могут быть повторно объявлены, как указано в 6.7.2.3». - person Eric Postpischil; 10.02.2021
comment
@EricPostpischil спасибо, я смотрел 6.7.3, я не видел пробела в предыдущем комментарии. - person Ciro Santilli 新疆再教育营六四事件ۍ 10.02.2021
comment
Да, перемещение int i за пределы функции перемещает его в новую область. (Каждый составной оператор начинает новый блок [блок для составного оператора, который определяет функцию, фактически начинается в начале объявлений параметров], как и операторы выбора и итерации и их подоператоры.) Однако, когда int i; находится вне какой-либо функции, у него нет никакой связи; он имеет внешнюю связь. Для примера без связи, за которой следует внешняя связь, которая не является нарушением ограничения, у вас может быть void f(void) { int i; { extern int i; … } }. - person Eric Postpischil; 10.02.2021

  1. Можно ли объявить локальные переменные внешними?

Нет. Но глобальную переменную можно объявить extern локально.

// file1.c
int Count;

// file2.c
void foo(void) {
  extern int Count;
  Count++;
}
  1. Можно ли регистровые переменные объявлять extern?

Нет. Переменная не может быть extern и register.

C11 dr 6.7.1 Спецификаторы класса хранения
1 спецификатор класса хранилища:
typedef
extern
static
_Thread_local
auto
register
Ограничения
2 Максимум один спецификатор класса хранения может быть указан в спецификаторах объявления в объявлении, за исключением того, что _Thread_local может стоять вместе с static или extern)

person chux - Reinstate Monica    schedule 29.12.2014

6.9 Внешние определения состояний C99:

Спецификаторы класса хранения auto и register не должны появляться в спецификаторах объявления во внешнем объявлении.

person Alexey Frunze    schedule 15.01.2013
comment
Параграф вопроса OP не очень ясен, но заголовок отражает суть; Я считаю, что это не отвечает на то, что он / она спросил. - person Albert Netymk; 29.12.2014

Вам разрешено только определять глобальную переменную как extern. Сообщить компилятору (и компоновщику), что он определен в другом месте.

Локальная переменная существует только в локальной области видимости, поскольку она создается в стеке или в регистре. Когда выполнение не входит в область действия (больше), стек разворачивается (поэтому свободное место снова становится доступным) или регистр используется для других целей, а переменная не существует (больше).

Таким образом, определение локального внешнего модуля было бы «странным» и невозможным (из-за использования стека).

person Veger    schedule 15.01.2013
comment
Ну, технически вы не «делаете это внешним», а больше говорите компилятору, что эта переменная не определена в этом файле? - person Jite; 15.01.2013
comment
Хм... ты прав. Вы определяете переменную extern, чтобы сообщить компилятору, что она уже создана где-то еще и что компоновщик должен найти ее во время компоновки, чтобы ее можно было использовать (даже если она явно не определена в исходном файле, который ее использует). Я обновил свой ответ, чтобы сделать его более понятным! - person Veger; 15.01.2013
comment
Однако следует добавить, что, конечно, можно получить доступ к переменной, такой как extern int a;, из области действия функции, если переменная определена в другом месте в глобальной области. Вам не нужно помещать extern int a; в глобальную область в вашем файле, если вам нужно получить к нему доступ только из одной функции. - person Jite; 15.01.2013
comment
@Jite: (в ответ на ваш первый комментарий) технически я думаю, что язык стандарта таков, что объявление приводит к имени с внешней связью. На самом деле это не означает, что он не определен в этом файле, вам разрешено следовать за объявлением extern с определением, если хотите. И это хорошо, иначе вы не смогли бы включить собственный заголовочный файл библиотеки в исходный файл библиотеки до определения ее глобальных переменных :-) Внешняя связь означает, что если две разные TU используют одинаковые имена, обе имеют внешнюю связь, они относятся к одному и тому же объекту/функции. - person Steve Jessop; 15.01.2013
comment
@SteveJessop: Конечно, спасибо за разъяснение. Мой первый комментарий был не утверждением, а вопросом, так что спасибо за ответ :) - person Jite; 16.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;
}
person Albert Netymk    schedule 29.12.2014