Может ли кто-нибудь подробно объяснить концепцию связи на языке C

У меня есть следующая программа C:

#include<stdio.h>

static void p(void);
static int c;

int main(int argc,char **argv) {
    p();
    printf("%d",c);
    return 0;
}


void p(void) {
    printf("From the Function P\n");
}

int c=232;

И ошибка вывода компилятора gcc: ошибка: нестатическое объявление 'c' следует за статическим объявлением

и когда я посмотрел в стандарте C ISO / IEC 9899: TC2:


6.2.2 Связывание идентификаторов

1 Идентификатор, объявленный в разных областях или в одной области более одного раза, может быть использован для ссылки на один и тот же объект или функцию с помощью процесса, называемого связыванием. 21) Существует три вида привязки: внешняя, внутренняя и никакая.

2 В наборе единиц перевода и библиотек, составляющих всю программу, каждое объявление конкретного идентификатора с внешней связью обозначает один и тот же объект или функцию. В пределах одной единицы перевода каждое объявление идентификатора с внутренней связью обозначает один и тот же объект или функцию. Каждое объявление идентификатора без привязки обозначает уникальный объект.

3 Если объявление идентификатора области файла для объекта или функции содержит статический спецификатор класса хранения, идентификатор имеет внутреннюю связь. 22)

4 Для идентификатора, объявленного с помощью спецификатора класса хранения extern в области, в которой видно предыдущее объявление этого идентификатора, 23) если предыдущее объявление указывает внутренняя или внешняя связь, связь идентификатора в последующем объявлении такая же, как связь, указанная в предыдущем объявлении. Если предыдущая декларация не видна или если предыдущая декларация не указывает связи, то идентификатор имеет внешнюю связь.

5 Если объявление идентификатора функции не имеет спецификатора класса хранения, его связь определяется точно так же, как если бы оно было объявлено со спецификатором класса хранения extern. Если объявление идентификатора объекта имеет область действия файла и не имеет спецификатора класса хранения, его связь является внешней.

6 Следующие идентификаторы не имеют связи: идентификатор, объявленный чем-либо, кроме объекта или функции; идентификатор, объявленный параметром функции; идентификатор области блока для объекта, объявленного без спецификатора класса хранения extern.

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

21) Нет связи между разными идентификаторами. 22) Объявление функции может содержать статический спецификатор класса хранения, только если он находится в области видимости файла; см. 6.7.1.


Q.1 Я не могу понять правила 4 и 5? в чем разница между привязкой определяется точно так же, как если бы она была объявлена ​​с помощью спецификатора класса хранения extern. и привязка является внешней


Q2. почему возникает эта ошибка, поскольку я могу сделать вывод из правила 5, что c имеет static decl. за которым следует extern decl. так что последний decl.shud тоже будет статическим.? Я буду рад, если кто-то изо всех сил объяснит все правила с самого начала ... или предложит мне ссылку, которая четко объясняет все правила *.

Примечание: спасибо за любые предложения по правильному редактированию этого вопроса, потому что он может дублироваться. Я задал этот вопрос, потому что я не мог заранее понять ответы на похожие вопросы, размещенные на этом сайте.


person Duggs    schedule 23.08.2013    source источник


Ответы (4)


Во-первых, имейте в виду, что приведенные здесь разделы стандарта относятся к двум отдельным вещам: 1. объявлениям функций 2. объявлениям объектов (например, переменных). Ваш вопрос, кажется, показывает некоторую путаницу между ними.

Как сказано в правиле 2, функции или объекты, объявленные с ключевым словом static, имеют только внутреннюю связь. То есть они не видны вне текущей единицы перевода. Когда вы впервые объявляете static int c, вы говорите, что c имеет только внутреннюю связь и не может быть виден за пределами этой единицы перевода. Вы объявляете эту переменную вне какой-либо функции, то есть предоставляете ей область видимости файла.

Позже вы повторно объявляете int c = 232. Вы также объявляете эту переменную в области видимости файла - она ​​не находится внутри какой-либо функции. Правило 5 гласит:

If the declaration of an identifier for an object has file scope and no storage-class 
specifier, its linkage is external.

Поскольку вы объявляете его с областью действия файла и без какого-либо спецификатора класса хранения, по умолчанию используется внешняя связь.

Эти два объявления int c противоречат друг другу: первое, с static, определяет внутреннюю связь; второй, без спецификатора класса хранения, по умолчанию extern. Из-за этого противоречия вы получаете ошибку, которую видите в GCC.

Правило 4 гласит:

For an identifier declared with the storage-class specifier extern in a scope in which 
a prior declaration of that identifier is visible, if the prior declaration specifies 
internal or external linkage, the linkage of the identifier at the later declaration 
is the same as the linkage specified at the prior declaration. 

Но это относится конкретно к идентификаторам, повторно объявленным как extern. Это означает, что если вы повторно объявите c следующим образом: extern int c = 232, вы не получите ошибку. (Однако вы можете получить предупреждение.) Когда я использовал ключевое слово extern перед вторым объявлением, я получил следующий результат, как и ожидалось:

From the Function P
232

Почему это работает? Обоснование стандарта C дает объяснение:

The appearance of the keyword extern in a declaration, regardless of whether it is 
used inside or outside of the scope of a function, indicates a pure reference (ref), 
which does not define storage. Somewhere in all of the translation units, at least 
one definition (def) of the object must exist. An external definition is indicated 
by an object declaration in file scope containing no storage class indication. 
                                                                 // § 6.2.2, p. 33

Когда вы сначала объявляете static int c, а затем объявляете int c = 232 во второй раз без ключевого слова extern, компилятор снова пытается выделить память для c - он пытается переопределить c. Но поскольку c уже находится в области видимости, это второе определение завершится ошибкой, и вы получите сообщение об ошибке.

Когда вы явно повторно объявляете его как extern c = 232, ключевое слово extern указывает, что это не новый объект, для которого необходимо выделить память; хранилище находится в другом месте. Поскольку c уже находится в области действия из более раннего объявления static, второе объявление не перезаписывает его; он устанавливает значение этой static c переменной равным 232.

Это полезно, если у вас есть функция в этом файле, которая должна установить значение этой статической переменной:

static int c;

void funA (void) {

  extern int c = 232;    // "extern" specifies that this is not new storage

}

Что касается Правила 5, первое предложение просто говорит, что функции по умолчанию extern, если они не указаны как static; во втором предложении говорится, что глобальные переменные (объявленные в области файла) по умолчанию равны extern. Если я объявляю функцию внутри другой функции, она все еще имеет extern область видимости; однако переменная, объявленная внутри функции, имеет автоматическое сохранение и не входит в область действия вне этой функции. Чтобы уточнить:

int foo;          
extern void funA (void);

void funB (void) {

    int foo;     // hides global int foo, but has scope only within funB()
    int bar;     // has scope only within funB()
    .... 
}

void funC (void) {

    int funD (int, int);   // has extern scope even though is declared within funC()
    extern int foo = 20;   // sets value of global variable foo declared at file level
    ....
}

Надеюсь это поможет!

person verbose    schedule 23.08.2013

static int c; означает, что c имеет область действия файла и внутреннюю связь из-за static, но позже определение c снова как int c без какого-либо спецификатора класса хранения пытается предоставить ему внешнюю связь по умолчанию (правило 5)), что противоречит тому, что было объявлено ранее в том же файле сфера.

person Dayal rai    schedule 23.08.2013
comment
Этот ответ не решает вопрос 1. В частности, это не касается неспособности OP распознать, что предложение, в котором говорится, что объявление без спецификатора хранения, обрабатывается так, как если бы использовалось extern, касается идентификаторов функций, а не идентификаторов объектов. - person Eric Postpischil; 23.08.2013
comment
@EricPostpischil Я попытался объяснить правила 4 и 5 в строке 1 и строке 2 моего ответа соответственно для данного контекста. - person Dayal rai; 23.08.2013

Идентификаторы, имеющие внешнюю связь, можно увидеть в разных единицах перевода, которые связаны вместе в исполняемый файл. Идентификаторы, не имеющие внешней связи, можно увидеть только в (возможно, только в определенной области) единицы перевода, которая их определила. Начиная с C.99 5.1.1.1:

Отдельные единицы трансляции программы сообщаются (например) посредством вызовов функций, идентификаторы которых имеют внешнюю связь, манипулирования объектами, идентификаторы которых имеют внешнюю связь, или манипулирования файлами данных. Единицы перевода могут переводиться отдельно, а затем связываться с ними для создания исполняемой программы.

Для иллюстрации C.99 6.2.2 4 рассмотрим следующее:

static int c;
void foo () { assert(c == 1); }
int main () {
    extern int c;
    c = 1;
    foo();
    return 0;
}

Условие утверждения будет истинным. Это потому, что C.99 6.2.2 4 говорит, что c в extern int c принимает ту же связь, что и ранее объявленный c, если он есть в области видимости, которым в данном случае является static int c. Если бы не было объявления c, тогда extern int c в main() имел бы внешнюю связь.

В C.99 6.2.2 5 первое предложение относится только к именам функций. Это говорит о том, что если объявление функции не содержит какого-либо класса хранения, тогда имя функции имеет внешнюю связь.

Второе предложение в C.99 6.2.2 5 утверждает, что если объект объявлен в области файла без какого-либо класса хранения, то имя этого объекта будет иметь внешнюю связь. Таким образом, в вашем фрагменте:

static int c;
/*...*/
int c = 5;

Возник конфликт, так как c сначала объявляется как имеющий внутреннюю связь, а затем как внешнюю связь.

person jxh    schedule 23.08.2013

Относительно Q1:

В цитируемом вами абзаце 5 первое предложение применяется только к идентификатору для функции. В int c=232; c - это идентификатор объекта, а не функции. Следовательно, первое предложение неприменимо.

Второе предложение относится к идентификатору объекта. В нем говорится, что для объявления в области видимости файла и без спецификатора класса хранения связь является внешней.

Следовательно, в int c=232; в вашем вопросе ссылка внешняя.

Относительно второго квартала:

Как мы теперь видим, первое объявление static int c; дает c внутреннюю связь, а второе объявление int c=232; дает c внешнюю связь. Эти конфликты, поэтому это ошибка.

person Eric Postpischil    schedule 23.08.2013