• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Zasięg zmiennej w języku C

Cloud VPS
0 głosów
1,588 wizyt
pytanie zadane 7 marca 2017 w C i C++ przez Evelek Nałogowiec (28,960 p.)
edycja 7 marca 2017 przez Evelek

Mój kod składa się z 2 plików źródłowych .c oraz jednego pliku nagłówkowego .h. Wygląda to tak:

NAGLOWEK.H

int liczba_rzutow = 0;

 

MAIN.C

#include "naglowek.h"	

extern int liczba_rzutow;   //lacznosc zewnetrzna

int main(void)
{
	return 0;
}

 

FUNKCJA.C

#include "naglowek.h"

extern int liczba_rzutow;	//lacznosc zewnetrzna

 

Pytanie brzmi: Dlaczego to się nie kompiluje? Mam błąd "Znaleziono co najmniej jeden wielokrotnie zdefiniowany symbol". Dopiero gdy w pliku naglowek.h napiszę: "int liczba_rzutow;" bez przypisywania do zera, to się kompiluje. To teoretycznie jedno i to samo, bo niezdefiniowana zmienna zewnętrzna ma wartość początkową 0, ale ja bym chciał, aby zmienna liczba_rzutow była zdefiniowana wybraną przeze mnie liczbą.

Przychodzi mi do głowy, że może ta zewnętrzna zmienna liczba_rzutow jest załączana do programu dwukrotnie, raz do pliku main.c oraz raz do funkcja.c i stąd ten błąd. Ale chyba to by trochę zaprzeczało potrzebie używania słowa extern.

Napisałem za to coś podobnego używając trzech plików z rozszerzeniem .c i się kompiluje:

INICJALIZACJA.C

int liczba_rzutow = 0;

 

FUNKCJA.C

extern int liczba_rzutow;

 

MAIN.C

extern int liczba_rzutow;

int main(void)
{
	return 0;
}

W jaki sposób więc można rozwiązać ten problem? Zaznaczę, że zmienna liczba_rzutow ma być zdefiniowana w pliku nagłówkowym .h i widoczna dla obu plików źródłowych z rozszerzeniem .c.

komentarz 7 marca 2017 przez niezalogowany
Nie jestem specjalistą od tego, dlatego jako komentarz, a nie odpowiedź.

 

include powoduje, że plik jest wklejany po prostu przed kompilacją do pliku c. Więc może go to gryzie, że masz 2 razy to samo przypisanie do zera i zadeklarowanie?
komentarz 7 marca 2017 przez Eryk Andrzejewski Mędrzec (164,260 p.)
Tak na szybko (nie wiem czy to pomoże) dodaj np. include guardy do pliku nagłówkowego.
komentarz 7 marca 2017 przez Evelek Nałogowiec (28,960 p.)

Przemysław A Śmiejek: no zgadza się, widzę to teraz, #include oraz extern powodują, że dwukrotnie kompiluję tę samą zmienną.

Eryk: Good idea, pewnie by to przeszło przez kompilator bez błędów, ale na razie chcę jeszcze zrozumieć błędy z tego. To tak jakbym w PHP przy błędach pisał @ i się nie przejmował. laugh

2 odpowiedzi

0 głosów
odpowiedź 7 marca 2017 przez mokrowski Mędrzec (158,960 p.)
wybrane 7 marca 2017 przez Evelek
 
Najlepsza

W pierwotnym programie main.c włącza nagłówek naglowek.h zawierający definicję zmiennej. Po co więc w main.c extern? To samo masz dla funkcja.c. Włącza nagłówek haglowek.h i znowu deklaruje extern. To się nie może powieść. extern ma sens jeśli na etapie konsolidacji (linkowania) masz konieczność "widzenia" zmiennej w innym pliku *.o. Tu poprzez załączone nagłówki masz wielokrotne definiowanie zmiennej.

Tak BTW to stosowanie extern'ów bardzo często świadczy o nie najlepszym projekcie. W większości wypadków można go uniknąć dobrze modularyzując program.

Rozwiązanie (z rozmysłem pozostawiłem komentarze aby widać było zmiany):

naglowek.h

int liczba_rzutow = 0;

funkcja.c

#include "naglowek.h"
 
/* extern int liczba_rzutow;   //lacznosc zewnetrzna */

main.c

/* #include "naglowek.h" */

extern int liczba_rzutow; //lacznosc zewnetrzna

int main(void)
{
    return 0;
}

Rzecz jasna jeśli ma to dotyczyć to Twoich plików. A co do poprawki, zacznij od stworzenia nagłówka dla funkcja.c (czyli funkcja.h) i przemyśl gdzie umieścić zmienną. Jak hermetyzację zrobisz dobrze, to nie będzie ona widoczna dla main.c bo będzie w 1 miejscu czyli funkcja.c Traktuj extern jako ostateczność i mam nadzieję że wiesz do jak funkcjonuje static w plikach *.c :-)

komentarz 7 marca 2017 przez Evelek Nałogowiec (28,960 p.)

W pierwotnym programie main.c włącza nagłówek naglowek.h zawierający definicję zmiennej. Po co więc w main.c extern? To samo masz dla funkcja.c. Włącza nagłówek haglowek.h i znowu deklaruje extern. To się nie może powieść. extern ma sens jeśli na etapie konsolidacji (linkowania) masz konieczność "widzenia" zmiennej w innym pliku *.o. Tu poprzez załączone nagłówki masz wielokrotne definiowanie zmiennej.

Dziękuję ślicznie za wyjaśnienie. smiley Moja wiedza na temat extern wygląda tak: Jeśli zmienna została zadeklarowana w pewnym pliku A, a my chcemy z niej korzystać w innym pliku B, to konieczna jest ponowna jej deklaracja ze słowem extern.

Po przykładzie od Ciebie zrozumiałem już w czym tkwi problem. Gdzieś w następnym rozdziale książki mam jak wygląda kompilacja wielu plików w C, troszkę się różni jednak z tego co widzę od C++.

To w takim razie jeszcze jeden przykład, bo zauważyłem, że inaczej to wszystko działa dla zmiennych a inaczej dla funkcji.

funkcja.c

#include <stdio.h>

int liczba_rzutow = 0;

void funkcja()
{
	printf("%d \n", liczba_rzutow);
}

main.c

extern int liczba_rzutow;
int main(void)
{
	printf("%d \n", liczba_rzutow); 
	funkcja();

	for (;;);
	return 0;
}

Plik main.c ma dodane "extern int liczba_rzutow", ale nie ma nic dla funkcja(). Czy inne są zasady dla zmiennych a inne dla funkcji? Kolejne spostrzeżenie: main.c kompiluje funkcję printf(), w takim razie wniosek stąd, że main.c korzysta z "#include <stdio.h>" z funkcja.c.

I ostatnia rzecz, skoro już wspomniałeś o static. Podam przykład, najwyżej mnie poprawisz jeśli źle to rozumiem.

#include <stdio.h>

int zmienna = 5;
static int liczba = 12;

int main(void)
{
   return 0;
}

Różnica polega na tym, że zmienna o nazwie zmienna może być używana przez funkcje znajdujące się w innych plikach, a ze zmiennej o nazwie liczba mogą korzystać tylko i wyłącznie funkcje znajdujące się w tym samym pliku co ta zmienna. Zarówno jedna jak i druga zmienna istnieją przez cały czas trwania programu.

(Oczywiście mamy jeszcze auto oraz register, ale z tym problemu nie mam (albo tak mi się na razie wydaje smiley )).

komentarz 7 marca 2017 przez mokrowski Mędrzec (158,960 p.)

To o czym piszesz to tzw. łączność zewnętrzna i wewnętrzna. Jeśli dla zmiennej lub funkcji użyjesz słowa kluczowego static, to taka funkcja czy zmienna będzie widoczna w 1 jednostce translacji (czyli w 1 pliku lub w tych które go "includują"... tak można includować *.c ale to ... "durny" pomysł .. albo... dość zaawansowane zagadnienie... na tym etapie poprzestańmy że "durny" :-) ). W ten sposób możesz osiągnąć prywatność funkcji. Inna zdefiniowana w tym samym pliku może z niej skorzystać a ktokolwiek poza jej nie widzi (nie będzie mógł jej wywołać).

Jeśli w pliku *.c użyjesz jakieś nazwy której nie zna kompilator (np. printf() a nie ma nagłówka *.h), to kompilator oprócz ostrzeżenia, generując plik *.o, zapisze informacje dla linkera że należy przyłączyć taką funkcję. Tak się składa że się uda bo.. printf() jest dostępna w libc a ta jest standardowo łączona dla każdego pospolitego programu :-) Podobnie jest dla przypadku użycia funkcji z zdefiniowanej w innym pliku *.o. Linker budując z plików *.o cały program jest w stanie ją odnaleźć. Jeśli na tym bazujesz, to jest to zła praktyka. Powinieneś zawsze włączać nagłówek z deklaracją funkcji aby kompilator na etapie budowania plików *.o zawsze wiedział jakiej funkcji może gdzieś oczekiwać. Ogólnie.... dobrze napisany program nie generuje ostrzeżeń.

extern z kolei obiecuje że ktoś.. gdzieś... taką funkcję lub zmienną posiada i będzie ona dostępna na etapie linkowania. Używasz extern jeśli z jakiś powodów nie możesz włączyć poprawnego nagłówka lub nie chcesz aby był widoczny cały (bo include  działa zawsze w trybie "wszystko albo nic").

Oczywiście stosowanie static i extern są sobie przeciwstawne i stosować w 1 zmiennej lub funkcji ich nie należy :-) 

komentarz 7 marca 2017 przez Evelek Nałogowiec (28,960 p.)

Bardzo bardzo dziękuję! Wytłumaczone lepiej niż w niejednej książce.

Zaskoczyłeś mnie jednak z tym static - sądziłem, że zmienna zadeklarowana w ten sposób jest widoczna tylko w tym pliku, w którym jest zdefiniowana. A tu się okazuje, że także w tych, które ten plik includują.

 

Podobnie jest dla przypadku użycia funkcji z zdefiniowanej w innym pliku *.o. Linker budując z plików *.o cały program jest w stanie ją odnaleźć. Jeśli na tym bazujesz, to jest to zła praktyka. Powinieneś zawsze włączać nagłówek z deklaracją funkcji aby kompilator na etapie budowania plików *.o zawsze wiedział jakiej funkcji może gdzieś oczekiwać. Ogólnie.... dobrze napisany program nie generuje ostrzeżeń.

Bardziej zapytałem z ciekawości o to, bo się skompilowało (a według moich założeń nie powinno). Dobrze wiedzieć, że nie powinno się tak robić - że są to złe praktyki. W przypadku tego programu kompilator MSVC wygenerował 2 ostrzeżenia:

  • niezdefiniowany "printf"; przy założeniu, że extern zwraca int

  • niezdefiniowany "funkcja"; przy założeniu, że extern zwraca int

 Po dopisaniu: #include <stdio.h> oraz extern void funkcja();  kompiluje się bez ostrzeżeń.

Dziękuje raz jeszcze za lekcję. wink

+2 głosów
odpowiedź 7 marca 2017 przez j23 Mędrzec (195,240 p.)

Deklarację z extern daj do pliku nagłówkowego, a definicję zmiennej do pliku źródłowego .c.

Podobne pytania

0 głosów
1 odpowiedź 368 wizyt
0 głosów
1 odpowiedź 165 wizyt
0 głosów
2 odpowiedzi 271 wizyt
pytanie zadane 5 lutego 2020 w C i C++ przez ullortnaci Nowicjusz (220 p.)

93,487 zapytań

142,423 odpowiedzi

322,773 komentarzy

62,909 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Kursy INF.02 i INF.03
...