Външно свързване на const в C

Играех си с extern ключова дума в C, когато срещнах това странно поведение. Имам два файла:

file1.c

#include<stdio.h>
int main()
{
    extern int a;
    a=10;
    printf("%d",a);
    return 0;
}

file2.c

const int a=100;

Когато компилирам тези файлове заедно, няма грешка или предупреждение и когато ги стартирам, резултатът става 10. Очаквах, че компилаторът трябва да докладва грешка на ред a=10;.

Освен това, ако променя съдържанието на file2.c на

const int a;

тоест, ако премахна инициализацията на глобалната константна променлива a и след това компилирам файловете, все още няма грешка или предупреждение, но когато ги стартирам, се появява Segmentation Fault.

Защо се случва това явление? Класифицирано ли е като недефинирано поведение? Това зависи ли от компилатора или машината?

PS: Виждал съм много въпроси, свързани с този, но или са за C++, или обсъждат само extern.


person skrtbhtngr    schedule 29.10.2016    source източник
comment
Вижте това: stackoverflow.com/a/28734780/4085019   -  person PseudoAj    schedule 29.10.2016
comment
Това е много хубав отговор, но не отговаря на моя въпрос.   -  person skrtbhtngr    schedule 29.10.2016


Отговори (4)


Компилирането и свързването са две отделни фази. По време на компилация отделните файлове се компилират в обектни файлове. Компилаторът ще открие, че file1.c и file2.c са вътрешно последователни. По време на фазата на свързване, линкерът просто ще посочи всички появявания на променливата a към едно и също място в паметта. Това е причината да не виждате грешка при компилиране или свързване.

За да избегнете точно проблема, който споменахте, се препоръчва да поставите extern в заглавен файл и след това да включите този заглавен файл в различен C файл. По този начин компилаторът може да улови всяко несъответствие между заглавката и C файла

Следният stackoverflow също говори за линкер, който не може да извършва проверка на типа за външни променливи.

Има ли някаква проверка на типа в C или C++ линкерите?

По същия начин, типовете глобални променливи (и статичните членове на класовете и т.н.) не се проверяват от линкера, така че ако декларирате extern int test; в една транслационна единица и дефиниране на плаващ тест; в друг, ще получите лоши резултати.

person Jay Rajput    schedule 29.10.2016

Това е недефинирано поведение, но компилаторът няма да ви предупреди. Как би могло? Той няма представа как декларирате променлива в друг файл.

Опитът за промяна на променлива, декларирана като const, е недефинирано поведение. Възможно е (но не е необходимо) променливата да се съхранява в памет само за четене.

person rici    schedule 29.10.2016

Това е известно поведение на C компилаторите. Това е една от разликите между C и C++, където се прилага силна проверка на типа по време на компилиране. Грешката на сегментиране възниква, когато се опитвате да присвоите стойност на const, тъй като линкерът поставя стойностите на const в elf сегмент само за четене и записът на този адрес на паметта е грешка по време на изпълнение (сегментиране). но по време на компилиране, компилаторът не проверява никакви "externs", а C линкерът не тества типове. следователно преминава компилация/свързване.

person Yanir    schedule 29.10.2016
comment
Янир, имаш изречение „Това е една от разликите между C и C++, където се налага силна проверка на типа по време на компилиране“. Искате ли да кажете, че C++ компилаторът щеше да улови този проблем? Тъй като файловете са различни, те могат да бъдат компилирани отделно в обектни файлове. - person Jay Rajput; 29.10.2016
comment
@JayRajput C++ компилаторът не би уловил това, но линкерът щеше да го направи. - person user4581301; 29.10.2016
comment
@user4581301, изглежда има объркваща информация онлайн. Например: тази статия за stackoverflow предполага, че линкерът няма да извършва проверка на типа за външни променливи. stackoverflow.com/questions/28090854/. Имате ли някакъв източник, който доказва, че линкерът прави проверка на типа за външни променливи? Бих искал да разбера повече за това. - person Jay Rajput; 29.10.2016
comment
@JayRajput Ще трябва да направя малко стандартно четене, за да намеря точната формулировка. Въпросът, който свързахте, се опитва да отговори както за C, така и за C++ и те са много, много различни зверове, когато става въпрос за проверка на типа. Вижте коментара на Yakk и отговора на Josh Kelly. - person user4581301; 29.10.2016
comment
@JayRajput Изглежда, че работи малко по-различно, отколкото си мислех. Според Basic.Link типът const има вътрешна връзка и не може да се види извън текущия файл, освен ако изрично не е деклариран като extern. Така че const int x = 0; не може да бъде externed, но extern const int x = 0; може да бъде externed. Не мога да намеря никаква информация за това дали липсващият const е уловен от линкера. GCC 4.8 и 6.1 изглежда не прихващат, така че за момента предполагам, че не е необходимо. - person user4581301; 29.10.2016
comment
И сега, когато знам какви ключови думи да търся, Защо прави „extern const int n;“ не работи според очакванията? Добър образователен ден. Сега обратно към работата по почистване на къщата. - person user4581301; 29.10.2016

Вашата програма причинява недефинирано поведение без необходимост от диагностика (независимо дали const int a има или не инициализатор). Съответният текст в C11 е 6.2.7/2:

Всички декларации, които се отнасят до един и същи обект или функция, трябва да имат съвместим тип; в противен случай поведението е недефинирано.

Също 6.2.2/2:

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

В C const int a = 100; означава, че a има външна връзка. Така че обозначава същия обект като extern int a;. Тези две декларации обаче имат несъвместим тип (int не е съвместим с const int, вижте 6.7.2 за дефиницията на "съвместим тип").

person M.M    schedule 30.10.2016