Може ли някой да обясни подробно концепцията за свързване в 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.


В.1 Не мога да разбера правило 4 и 5? между другото каква е разликата връзката се определя точно така, както ако беше декларирана със спецификатора на класа за съхранение extern. и връзката е външна


Q2. защо идва тази грешка, тъй като мога да заключа от правило 5, че c има static decl. последвано от extern decl. така че последното декламиране също трябва да бъде статично.? Ще съм благодарен, ако някой си положи труда да обясни всички правила от самото начало..или ми предложи връзка, която обяснява ясно всички правила.*

Забележка: благодаря за всякакви предложения за правилно редактиране на този въпрос, защото може да се дублира, зададох този въпрос, тъй като не можах да разбера отговорите на подобни въпроси, публикувани на този сайт, благодаря предварително.


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
Този отговор не разрешава Q1 във въпроса. По-конкретно, той не се отнася до неуспеха на 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; във вашия въпрос връзката е външна.

Относно Q2:

Както виждаме сега, първата декларация, static int c;, дава c вътрешна връзка, а втората декларация, int c=232;, дава c външна връзка. Тези конфликти, така че това е грешка.

person Eric Postpischil    schedule 23.08.2013