2017-11-30 377 views
6

Skompilowałem następujący program używając polecenia gcc prog.c -Wall -Wextra -std=gnu11 -pedantic na kompilatorze GCC. Zastanawiałem się, że działa bez żadnych ostrzeżeń i błędów.Ponownie deklarowana zmienna wewnątrz pętli for w C

#include <stdio.h> 

int main(void) 
{ 
    for (int i = 0; i == 0; i++) 
    {   
     printf("%d\n", i); 
     long int i = 1; // Why doesn't redeclaration error? 
     printf("%ld\n", i); 
    } 
} 

Dlaczego kompilator nie generuje błędu redeclaration i błędu?

+0

Czy nie otrzymujesz również ostrzeżenia, że ​​'int main' nie zwraca wartości? – MFisherKDX

+0

@MFisherKDX Cppreference mówi: "Ciało funkcji głównej nie musi zawierać instrukcji return: jeśli sterowanie osiągnie koniec głównego bez napotkania instrukcji return, efektem jest wykonanie return 0;" – rsp

+2

Zgodnie z 5.1.2.2.3 z n1570 (szkic C11): "dojście do} kończącego główną funkcję zwraca wartość 0" (o ile typ powrotu jest zgodny z 'int'.) – lockcmpxchg8b

Odpowiedz

6

W języku C zakres statement jest zagnieżdżony w zakresie pętli for init-statement.

Według Cppreference:

Podczas C++, zakres startowe rachunku i zakresu rachunku są jednym i tym samym, w C zakres statement zagnieżdżonym ramach z init-statement.

Według stmt:

instrukcji for

for (for-init-statement conditionopt ; expressionopt) statement 

jest równoważna

{ 
    for-init-statement 
    while (condition) { 
      statement 
      expression ; 
     } 
} 

wyjątkiem tego, że nazwy deklarowane w for-init-oświadczenia znajdują się w ten sam region deklaratywny, co deklarowany w warunku, i oprócz tego, że instrukcja kontynuacji (nieujęta w innej instrukcji iteracji ) wykona wyrażenie przed ponowną oceną stanu .

+1

powinieneś przestać odwoływać się do c ** pp ** odniesienia podczas odpowiadania na pytania języka C-prawnika. Wiadomo, że zawiera błędy nawet w C++. –

+1

@AnttiHaapala, zgodził się, że nie zawsze jest to najlepszy wybór jako źródło. Ale tutaj jest to słuszne i stanowi ważną kwestię, a mianowicie, że C i C++ mają różne reguły. Został nawet zgłoszony defekt dla C (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2148.htm#dr_466) i okazało się, że ta różnica była niezamierzona , ale było już za późno, aby to zmienić. –

+0

@JensGustedt rzeczywiste standardy są znacznie lepsze odniesienia. I że cppreference DR –

2

Musisz ustawić opcję -Wshadow, aby otrzymywać ostrzeżenia o zmiennych zacienionych. Zmienne zaciemnienie jest dozwolone w C.

Ale jest to przypadek skrajny. Zmienna deklarowana w nagłówku konstrukcji for nie znajduje się poza nawiasami, ponieważ nie ma żadnego zakresu po konstrukcji.

To nie równoważne:

int i; 
for(i = 0; …) 
{ … } 
// is is still in scope but wouldn't if declared in the head of for 

Ale to nie jest w nawiasie, too.

for(i = 0; …) 
{ 
    int i; // this would be strange, because i is used before it is declared. 
    … 
} 

Najlepszym przybliżony wymiana kodu jest taka:

{ 
    int i; 
    for(i = 0; …) 
    { 
    … 
    } 
} // i loses scope 

Nie jest więc ponowna, ale deklaracja shadowing wewnątrz ciała pętli za.

+0

Nie sądzę, że o to właśnie chodzi, ale dlaczego deklaracja 'for' i deklaracja wewnątrz' {} 'nie znajdują się w tym samym zakresie deklaracji. –

+0

Identyfikator zadeklarowany w tn 'for' znajduje się poza nawiasami używanymi w treści. Jego zakres obejmuje wyrażenia w 'for'. Zakres identyfikatora w nawiasach nie jest. –

+0

@EricPostpischil Rzeczywiście. Dlaczego tak komentujesz? –

6

od standardowego §6.8.5.5 (N1570)

instrukcji iteracji jest blok, którego zakres jest ściśle określony podzbiór zakres jego bloku okalającego.Korpus pętli jest także blokiem, którego zakres jest ścisłym podzestawem zakresu instrukcji iteracji.

Podkreślenie

0

Why compiler doesn't generate redeclaration variable i error?

Od C Standards#6.2.1p4 Scopes of identifiers

Każdy inny identyfikator został zakres określony przez umieszczenie deklaracji (w deklaratorze lub specyfikatorze typu). Jeśli deklarator lub specyfikator typu deklarujący identyfikator pojawia się poza jakimkolwiek blokiem lub listą parametrów, identyfikator ma zasięg pliku, który kończy się na końcu jednostki tłumaczeniowej. Jeśli deklarator lub specyfikator typu, który deklaruje identyfikator pojawia się wewnątrz bloku lub na liście deklaracji parametrów w definicji funkcji, identyfikator ma zasięg bloku, który kończy się na końcu skojarzonego bloku. Jeśli deklarator lub specyfikator typu deklarujący identyfikator pojawia się na liście deklaracji parametrów w prototypie funkcji (nie jest częścią definicji funkcji), identyfikator ma zakres prototypu funkcji, który kończy się na końcu deklaratora funkcji. Jeśli identyfikator oznacza dwie różne jednostki w tej samej przestrzeni nazw, zakresy mogą zachodzić na siebie. Jeśli tak, zakres jednego podmiotu (zakres wewnętrzny) zakończy się ściśle przed zakresem innego podmiotu (zakres zewnętrzny). W zakresie wewnętrznym identyfikator oznacza jednostkę zadeklarowaną w zakresie wewnętrznym; jednostka zadeklarowana w zewnętrznym zakresie jest ukryta (i niewidoczna) w zakresie wewnętrznym.

Od C standards#6.8.5p5 Iteration statements

oświadczenie iteracja jest blok, którego zakres jest ściśle podzbiorem zakresu jej otaczającego bloku. Ciało pętli jest także blokiem, którego zakres jest ściśle określonym podzestawem zakresu instrukcji iteracji.

Więc w ten kod:

for (int i = 0; i == 0; i++) 
{ 
    printf("%d\n", i); 
    long int i = 1; // Why doesn't redeclaration error? 
    printf("%ld\n", i); 
} 

zakres identyfikator z nazwą i jest nakładanie i ta nazwa przestrzeni, i zadeklarowane w for (int i = 0; i == 0; i++) ma zewnętrzną zakresu i zgłoszonemu wewnątrz pętli body long int i = 1; ma wewnętrzny zakres.

wewnątrz ciała pętli, po tym stwierdzeniem

long int i = 1; 

w i zadeklarowanym w zakresie zewnętrznej jest niewidoczny i printf() drukowania wartość i widzialnym zakresem wewnętrznej, która jest 1.

To zachowanie jest również znane jako Zmienne cienie, które występuje, gdy zmienna zadeklarowana w pewnym zakresie ma taką samą nazwę jak zmienna zadeklarowana w zewnętrznym zasięgu.

Język C umożliwia zmianę cieniowania i dlatego kompilator nie generuje żadnego błędu dla tego. Jednak w kompilatorze gcc, jeśli użyjesz opcji -Wshadow, otrzymasz komunikat ostrzegawczy - declaration shadows a local variable.

0

W celu dalszej weryfikacji sprawdziłem ten kod w visual studio 2008 dla pliku prog.c. Stwierdziłem, że kompilator podaje błąd w wierszu dla (int i = 0; i == 0; i ++). Kompilator oczekuje, że deklaracja i będzie na początku samego programu. To zachowanie jest poprawne dla pliku C. Jeśli deklaracja zostanie przeniesiona na początek programu, nie wystąpią błędy zgodnie z oczekiwaniami. Wszystkie problemy związane z zakresem są rozwiązywane.

Jeśli spróbuję tego kodu jako pliku prog.cpp, kompilator zgłosi błąd podczas redeclaracji. Jest to również oczekiwane zachowanie.

Podsumowując, ma to związek z kompilatorem gcc, czy jakakolwiek flaga/parametry używane do kompilowania/budowania exe powodują takie zachowanie dla kompilatora gcc.

Czy rsp może podać szczegóły pliku make w celu dalszej weryfikacji?