Umieszczenie zmiennej deklaracji w C

Długo myślałem, że w C wszystkie zmienne muszą być zadeklarowane na początku funkcji. Wiem, że w C99 zasady są takie same jak w C++, ale jakie są zasady umieszczania deklaracji zmiennych dla C89 / ANSI C?

Poniższy kod kompiluje się pomyślnie z gcc -std=c89 i gcc -ansi:

#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 10; i++) {
        char c = (i % 95) + 32;
        printf("%i: %c\n", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

Czy deklaracje c i s nie powinny powodować błędu w trybie C89 / ANSI?

Author: Nayuki, 2008-11-14

7 answers

Kompiluje się pomyślnie, ponieważ GCC pozwala na to jako rozszerzenie GNU, mimo że nie jest częścią standardu C89 ani ANSI. Jeśli chcesz ściśle przestrzegać tych standardów, musisz przekazać flagę -pedantic.

 127
Author: mipadi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-13 21:47:29

Dla C89, musisz zadeklarować wszystkie swoje zmienne na początku bloku zakresu .

Zatem twoja deklaracja char c jest ważna, ponieważ znajduje się na górze bloku pętli for. Ale deklaracja char *s powinna być błędem.

 65
Author: Kiley Hykawy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-01 17:44:15

Grupowanie deklaracji zmiennych na górze bloku jest dziedzictwem prawdopodobnie ze względu na ograniczenia starych, prymitywnych kompilatorów C. Wszystkie współczesne języki zalecają, a czasami nawet wymuszają deklarację zmiennych lokalnych w ostatnim punkcie: w którym są inicjowane po raz pierwszy. Ponieważ eliminuje to ryzyko użycia losowej wartości przez pomyłkę. Oddzielenie deklaracji i inicjalizacji zapobiega również używaniu "const" (lub" final"), gdy można.

C++ niestety utrzymuje akceptacja starej, topowej deklaracji dla wstecznej kompatybilności z C (jedna zgodność C przeciąga się z wielu innych...) Ale C++ próbuje się od niego odsunąć:

  • konstrukcja odniesień do C++ nie pozwala nawet na takie grupowanie bloków.
  • jeśli oddzielasz deklarację i inicjalizację lokalnego obiektu C++ , to za nic płacisz koszt dodatkowego konstruktora. Jeśli konstruktor no-arg nie istnieje, to ponownie nie możesz nawet oddzielić jedno i drugie!

C99 zaczyna poruszać C w tym samym kierunku.

Jeśli obawiasz się, że nie znajdziesz gdzie zadeklarowane są zmienne lokalne, oznacza to, że masz znacznie większy problem: blok zamykający jest zbyt długi i powinien zostać podzielony.

Https://www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration

 24
Author: MarcH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-05 11:11:33

Z punktu widzenia utrzymania, a nie składni, istnieją co najmniej trzy ciągi myśli:

  1. Zadeklaruj wszystkie zmienne na początku funkcji, aby były one w jednym miejscu i będziesz mógł zobaczyć obszerną listę na pierwszy rzut oka.

  2. Zadeklaruj wszystkie zmienne tak blisko miejsca, w którym są używane po raz pierwszy, więc będziesz wiedział dlaczego każda jest potrzebna.

  3. Zadeklarować wszystkie zmienne na początku najskrytszego scope block, więc wyjdą z zakresu tak szybko, jak to możliwe i pozwolą kompilatorowi zoptymalizować pamięć i powiedzieć, jeśli przypadkowo użyjesz ich tam, gdzie nie zamierzałeś.

Generalnie wolę pierwszą opcję, ponieważ inne często zmuszają mnie do szukania kodu dla deklaracji. Zdefiniowanie wszystkich zmiennych z góry ułatwia również inicjalizację i oglądanie ich z poziomu debuggera.

Czasami deklaruję zmienne w mniejszym bloku zakresu, ale tylko dla Dobry powód, z którego mam bardzo mało. Przykładem może być fork() deklaracja zmiennych potrzebnych tylko procesowi potomnemu. Dla mnie ten wizualny wskaźnik jest pomocnym przypomnieniem ich celu.

 23
Author: Adam Liss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-13 22:05:49

Jak zauważyli inni, GCC jest permisywne pod tym względem (i być może inne Kompilatory, w zależności od argumentów, z którymi są wywoływane) nawet w trybie 'C89', chyba że użyjesz sprawdzania 'pedantic'. Aby być szczerym, nie ma wielu dobrych powodów, aby nie mieć pedantycznego; jakość nowoczesny kod powinien zawsze kompilować bez ostrzeżeń (lub bardzo niewiele, jeśli wiesz, że robisz coś konkretnego, co jest podejrzane dla kompilatora jako ewentualny błąd), więc jeśli nie możesz zrobić kompilacji kodu z pedantyczna konfiguracja prawdopodobnie wymaga uwagi.

C89 wymaga, aby zmienne były deklarowane przed jakimikolwiek innymi instrukcjami w ramach każdego zakresu, późniejsze standardy zezwalają na deklarację bliższą użyciu (która może być zarówno bardziej intuicyjna, jak i bardziej efektywna), zwłaszcza jednoczesną deklaracją i inicjalizacją zmiennej kontrolnej pętli w pętli 'for'.

 6
Author: Gaidheal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-16 11:10:15

Jak zauważono, istnieją dwie szkoły myślenia na ten temat.

1) Zadeklaruj wszystko na szczycie funkcji, ponieważ rok jest 1987.

2) zadeklarować najbliżej pierwszego użycia i w możliwie najmniejszym zakresie.

Moja odpowiedź brzmi: zrób jedno i drugie! Pozwól mi wyjaśnić:

Dla długich funkcji, 1) sprawia, że refaktoryzacja jest bardzo trudna. Jeśli pracujesz w bazie kodowej, w której deweloperzy są przeciwni idei podprogramów, to będziesz miał 50 deklaracji zmiennych na początku funkcja i niektóre z nich mogą być tylko "i" dla pętli for, która jest na samym dole funkcji.

Dlatego opracowałem declaration-at-the-top-PTSD z tego i starałem się zrobić opcję 2).

Wróciłem do opcji pierwszej z powodu jednej rzeczy: krótkich funkcji. Jeśli twoje funkcje są wystarczająco krótkie, będziesz mieć kilka zmiennych lokalnych, a ponieważ funkcja jest krótka, jeśli umieścisz je na górze funkcji, nadal będą blisko pierwszej użyj.

Również, anty-wzorzec "declare and set to NULL", gdy chcesz zadeklarować na górze, ale nie wykonałeś pewnych obliczeń niezbędnych do inicjalizacji, jest rozwiązany, ponieważ rzeczy, które musisz zainicjować, prawdopodobnie zostaną odebrane jako argumenty.

Więc teraz myślę, że powinieneś zadeklarować na górze funkcji i jak najbliżej do pierwszego użycia. Więc jedno i drugie! A sposób na to jest z dobrze podzielonymi podprogramami.

Ale jeśli pracujesz nad długim funkcji, a następnie umieścić rzeczy najbliżej pierwszego użycia, ponieważ w ten sposób będzie łatwiej wyodrębnić metody.

Mój przepis jest taki. Dla wszystkich zmiennych lokalnych, weź zmienną i przenieś jej deklarację na dół, skompiluj, a następnie przenieś deklarację na tuż przed błędem kompilacji. To pierwsze użycie. Zrób to dla wszystkich zmiennych lokalnych.
int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

Teraz zdefiniuj blok zakresu, który rozpoczyna się przed deklaracją i przesuń koniec do momentu kompilacji programu

{
    int foo = 0;
    <code that uses foo>
}

int bar = 1;
<code that uses bar>

>>> First compilation error here
<code that uses foo>

To nie kompiluje się, ponieważ jest więcej kodu, który używa foo. Możemy zauważyć, że kompilator był w stanie przejść przez kod, który używa bar, ponieważ nie używa foo. W tym momencie są dwa wyjścia. Mechaniczna polega na przesunięciu"} " w dół aż do kompilacji, a drugą opcją jest Sprawdzenie kodu i ustalenie, czy kolejność może zostać zmieniona na:

{
    int foo = 0;
    <code that uses foo>
}

<code that uses foo>

int bar = 1;
<code that uses bar>

Jeśli Zamówienie można zmienić, to prawdopodobnie tego chcesz, ponieważ skraca żywotność tymczasowego wartości.

Kolejna rzecz do odnotowania, czy wartość foo musi być zachowana pomiędzy blokami kodu, które go używają, czy może to być po prostu inny foo w obu. Na przykład

int i;

for(i = 0; i < 8; ++i){
    ...
}

<some stuff>

for(i = 3; i < 32; ++i){
    ...
}
Te sytuacje wymagają czegoś więcej niż tylko mojej procedury. Programista będzie musiał przeanalizować kod, aby określić, co zrobić.

Ale pierwszym krokiem jest znalezienie pierwszego użycia. Można to zrobić wizualnie, ale czasami po prostu łatwiej jest usunąć deklarację, spróbować skompilować i po prostu umieścić ją z powrotem powyżej pierwsze użycie. Jeśli to pierwsze użycie znajduje się wewnątrz instrukcji if, umieść je tam i sprawdź, czy kompiluje. Kompilator następnie zidentyfikuje inne zastosowania. Spróbuj utworzyć blok zakresu, który obejmuje oba zastosowania.

Po wykonaniu tej części mechanicznej łatwiej jest przeanalizować, gdzie znajdują się dane. Jeśli zmienna jest używana w bloku dużego zakresu, przeanalizuj sytuację i sprawdź, czy używasz tej samej zmiennej tylko dla dwóch różnych rzeczy (np. "i", które jest używane dla dwóch pętli for). Jeśli zastosowania są niepowiązane, utwórz nowe zmienne dla każdego z tych niepowiązanych zastosowań.

 0
Author: Philippe Carphin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-12 17:16:03

Przytoczę kilka stwierdzeń z podręcznika dla gcc w wersji 4.7.0 dla jasnego wyjaśnienia.

"kompilator może akceptować kilka standardów bazowych, takich jak' c90 ' lub 'C++98', oraz dialekty GNU tych standardów, takie jak 'gnu90' lub 'gnu++98'. Poprzez podanie standardu bazowego kompilator zaakceptuje wszystkie programy zgodne z tym standardem oraz te, które używają rozszerzeń GNU, które mu nie zaprzeczają. Na przykład '- std = c90 ' wyłącza pewne funkcje GCC, które są niezgodne z ISO C90, np. ASM i typeof keywords, ale nie inne rozszerzenia GNU, które nie mają znaczenia w ISO C90, np. pomijając średni termin a ?: expression."

Myślę, że kluczowym punktem Twojego pytania jest to, dlaczego gcc nie jest zgodny z C89, nawet jeśli używana jest opcja "- std=c89". Nie znam wersji twojego gcc, ale myślę, że nie będzie dużej różnicy. Programista gcc powiedział nam, że opcja "- std=c89 " oznacza tylko rozszerzenia, które są sprzeczne z C89 są zwracane wyłącz. Nie ma to więc nic wspólnego z niektórymi rozszerzeniami, które nie mają znaczenia w C89. Rozszerzenie, które nie ogranicza umieszczania deklaracji zmiennej, należy do rozszerzeń, które nie są sprzeczne z C89.

Aby być szczerym, wszyscy pomyślą, że powinno być zgodne z C89 całkowicie na pierwszy rzut oka opcji "- std=c89". Ale tak nie jest. Jeśli chodzi o problem, który deklaruje wszystkie zmienne na początku jest lepszy lub gorszy, to tylko kwestia przyzwyczajenia.

 -1
Author: junwanghe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-12 06:51:23