Co to jest idiomatyczne użycie dowolnych bloków w C?

15

Blok to lista instrukcji do wykonania. Przykłady bloków pojawiających się w C znajdują się po instrukcji while i w instrukcjach if

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C pozwala również na zagnieżdżanie bloku w bloku. Mogę użyć tego do ponownego użycia nazw zmiennych, przypuśćmy, że naprawdę lubię „x”

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

wypisze cyfrę 5 dziesięć razy. Chyba widziałem sytuacje, w których pożądane jest utrzymywanie niskiej liczby nazw zmiennych. Być może w makropoleceniach. Nie widzę jednak żadnego trudnego powodu, aby potrzebować tej funkcji. Czy ktoś może mi pomóc w zrozumieniu zastosowań tej funkcji, podając niektóre idiomy tam, gdzie jest ona używana.

Jonathan Gallagher
źródło
1
Szczerze mówiąc, staram się zrozumieć składnię C i jestem ciekawy.
Jonathan Gallagher,
Duch C polega na zaufaniu programistowi. Programista ma moc, aby zrobić coś wielkiego ... lub zrobić coś strasznego. Osobiście nie lubię przeciążonych nazw zmiennych, ale inny programista może. Na koniec dnia, jeśli osiągamy to, co powinniśmy, przy minimalnych błędach ... dlaczego mielibyśmy się kłócić?
Fiddling Bits,
1
To właśnie odczuwam od C. Jestem zwolennikiem składni, która obsługuje różne style kodowania (o ile semantyka języka jest w porządku). Po prostu ... Widziałem to i moją natychmiastową odpowiedzią było to, że mogłem zastosować transformację źródło-źródło, zmieniając nazwy wszystkich zmiennych w bloku o świeżych nazwach i całkowicie spłaszczyć blok. Za każdym razem, gdy myślę, że mógłbym się czegoś pozbyć, zakładam, że coś mi umknęło.
Jonathan Gallagher,
Zabawne wyczucie czasu, ponieważ jako programista inny niż C natknąłem się dziś na tę składnię w kodzie C i byłem ciekaw, do czego to służy. Cieszę się, że zapytałeś.
Brandon

Odpowiedzi:

8

Pomysł nie polega na utrzymywaniu niskiej liczby nazw zmiennych lub zachęcaniu do ich ponownego użycia, ale raczej na ograniczaniu zakresu zmiennych. Jeśli masz:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

wówczas zakres yjest ograniczony do bloku, co oznacza, że ​​możesz o nim zapomnieć przed lub po bloku. Widzisz to najczęściej używane w połączeniu z pętlami i warunkami warunkowymi. Widuje się go również częściej w językach podobnych do C, takich jak C ++, gdzie poza zakresem zmienna powoduje jej zniszczenie.

Caleb
źródło
Myślę, że blok występuje naturalnie w warunkach, pętlach i ciałach funkcyjnych. Ciekawe, że w C mogę umieścić blok w dowolnym miejscu. Twój drugi komentarz na temat C ++ jest interesujący - opuszczenie zakresu powoduje zniszczenie. Czy odnosi się to tylko do wyrzucania elementów bezużytecznych, czy może jest to kolejne zastosowanie: czy mogę wziąć ciało funkcji, które ma wyraźne „sekcje” i użyć bloków do kontrolowania śladu pamięci, powodując zniszczenie zmiennej przestrzeni?
Jonathan Gallagher,
1
O ile mi wiadomo, C wstępnie przydzieli całą pamięć dla wszystkich zmiennych w funkcji. Ich wyjście z zakresu w części przez funkcję nie przyniesie żadnych korzyści w zakresie wydajności. Podobnie, gdy zmienne wprowadzą zakres „tylko czasami”, nie zmieni śladu pamięci podczas jakiegokolwiek wywołania funkcji
Gankro
@Gankro Może to mieć wpływ, jeśli istnieje wiele wyłącznych zakresów zagnieżdżonych. Kompilator może ponownie wykorzystać unikalny wstępnie przydzielony fragment pamięci dla zmiennych w każdym z tych zakresów. Oczywiście powodem, dla którego nie przychodzi mu na myśl, jest to, że jeśli pojedynczy arbitralny zakres jest już znakiem, prawdopodobnie prawdopodobnie musisz wyodrębnić do funkcji, dwa lub więcej dowolnych zakresów jest zdecydowanie dobrym wskazaniem, że musisz zmienić fakturę. Mimo to od czasu do czasu pojawia się jako rozsądne rozwiązanie w takich sprawach, jak bloki skrzynek rozdzielczych.
tne
16

Ponieważ w dawnych czasach C nowe zmienne mogły być deklarowane tylko w nowym bloku.

W ten sposób programiści mogą wprowadzać nowe zmienne w środku funkcji bez wycieku i minimalizacji zużycia stosu.

W dzisiejszych optymalizatorach jest to bezużyteczne i znak, że należy rozważyć wyodrębnienie bloku we własnej funkcji.

W instrukcji switch użyteczne jest zamknięcie skrzynek we własnych blokach, aby uniknąć podwójnej deklaracji.

W C ++ jest to bardzo przydatne na przykład dla blokad blokujących RAII i zapewniania, że ​​destruktory uruchamiają blokadę zwolnienia, gdy wykonanie wykracza poza zakres i nadal wykonuje inne czynności poza sekcją krytyczną.

maniak zapadkowy
źródło
2
+1 dla strażników zamków RAII. To powiedziawszy, czy ta sama koncepcja nie może być również przydatna w przypadku dezalokacji bufora dużych stosów w ramach części procedury? Nigdy tego nie zrobiłem, ale brzmi jak coś, co na pewno mogłoby się zdarzyć w jakimś osadzonym kodzie ...
J Trana
2

Nie patrzyłbym na to jak na „arbitralne” bloki. Nie jest to funkcja przeznaczona specjalnie dla programistów, ale sposób, w jaki C używa bloków, pozwala na stosowanie tej samej konstrukcji bloku w wielu miejscach o tej samej semantyce. Blok (w C) to nowy zakres, a zmienne, które go opuszczają, są eliminowane. Jest to jednolite bez względu na sposób użycia bloku.

W innych językach tak nie jest. Ma to tę zaletę, że pozwala ograniczyć liczbę nadużyć, jak pokazano, ale wadą jest to, że bloki zachowują się inaczej w zależności od kontekstu, w jakim się znajdują.

Rzadko widziałem samodzielne bloki używane w C lub C ++ - często, gdy istnieje duża struktura lub obiekt reprezentujący połączenie lub coś, co chcesz wymusić zniszczenie. Zwykle jest to wskazówka, że ​​twoja funkcja robi zbyt wiele rzeczy i / lub jest za długa.

Telastyn
źródło
1
Niestety regularnie widzę samodzielne bloki - w prawie wszystkich przypadkach, ponieważ funkcja robi za dużo i / lub jest za długa.
mattnz
Regularnie również widzę i piszę samodzielne bloki w C ++, ale wyłącznie w celu wymuszenia zniszczenia opakowań wokół blokad i innych wspólnych zasobów.
J Trana
2

Musisz zdać sobie sprawę, że zasady programowania, które wydają się teraz oczywiste, nie zawsze były takie. C najlepsze praktyki w dużym stopniu zależą od wieku twoich przykładów. Kiedy po raz pierwszy wprowadzono C, rozbicie kodu na małe funkcje było uważane za zbyt nieefektywne. Dennis Ritchie po prostu skłamał i powiedział, że wywołania funkcji były naprawdę wydajne w C (nie były w tym czasie), co sprawiło, że ludzie zaczęli ich częściej używać, chociaż programiści C jakoś nigdy tak naprawdę nie przeszli kultury przedwczesnej optymalizacji.

Jest to dobra praktyka programowania nawet dzisiaj, aby ograniczyć zakres zmiennych jak najmniejsza. W dzisiejszych czasach zwykle robimy to, tworząc nową funkcję, ale jeśli funkcje są uważane za drogie, wprowadzenie nowego bloku jest logicznym sposobem ograniczenia zakresu bez narzutu wywołania funkcji.

Jednak zacząłem programować w C ponad 20 lat temu, kiedy musiałeś zadeklarować wszystkie swoje zmienne na początku zakresu i nie przypominam sobie, żeby cieniowanie zmiennych było takie, jak zawsze uważane za dobry styl. Ponowne zgłaszanie w dwóch blokach jeden po drugim, tak jak w instrukcji switch, tak, ale nie w cieniu. Może jeśli zmienna została już używany i nazwa specyficzna była bardzo idiomatyczne dla API ty dzwonisz, jak desti srcw strcpy, na przykład.

Karl Bielefeldt
źródło
1

Arbitralne bloki są przydatne do wprowadzania zmiennych pośrednich, które są używane tylko w szczególnych przypadkach obliczeń.

Jest to powszechny wzorzec w obliczeniach naukowych, w których procedury numeryczne zwykle:

  1. polegać na wielu parametrach lub ilościach pośrednich;
  2. mam do czynienia z wieloma specjalnymi przypadkami.

Ze względu na drugi punkt przydatne jest wprowadzenie zmiennych tymczasowych o ograniczonym zasięgu, które osiąga się dodatkowo, stosując dowolny blok lub wprowadzając funkcję pomocniczą.

Chociaż wprowadzenie funkcji pomocniczej może wydawać się bezmyślnością lub najlepszą praktyką, którą należy ślepo stosować, w rzeczywistości w tej konkretnej sytuacji jest niewiele korzyści .

Ponieważ istnieje wiele parametrów i wielkości pośrednich, chcemy wprowadzić strukturę, która przekaże je do funkcji pomocniczej.

Ponieważ jednak chcemy konsekwentnie stosować się do naszych praktyk, nie wprowadzimy tylko jednej funkcji pomocniczej, ale kilka. Tak więc, najpierw wprowadzamy struktury ad-hoc przenoszące parametry dla każdej funkcji, które wprowadzają wiele narzutu kodu do przenoszenia parametrów tam iz powrotem, lub wprowadzamy jeden, który będzie rządził wszystkimi strukturami arkusza, który zawiera wszystkie nasze zmienne, ale wygląda jak pakiet bitów bez konsekwencji, w którym tylko połowa parametrów ma interesujące znaczenie.

Dlatego te struktury pomocnicze są zwykle uciążliwe, a ich użycie oznacza wybranie między rozdęciem kodu lub wprowadzeniem abstrakcji, której zakres jest zbyt szeroki i osłabia znaczenie programu, zamiast go rozszerzać .

Wprowadzenie funkcji pomocniczych mogłoby ułatwić testowanie jednostkowe programu poprzez wprowadzenie drobniejszego testu szczegółowości, ale łączenie testów jednostkowych nie dla procedur niskopoziomowych i testów regresyjnych w formie porównań (z cyfrą liczbową) śladów numerycznych procedur wykonuje równie dobrą robotę .

użytkownik40989
źródło
0

Ogólnie, ponowne użycie nazw zmiennych w ten sposób powoduje zbyt duże zamieszanie wśród przyszłych czytelników twojego kodu. Lepiej po prostu nazwać zmienną wewnętrzną czymś innym. W rzeczywistości język C # nie zezwala na używanie zmiennych w ten sposób.

Widziałem, w jaki sposób użycie zagnieżdżonych bloków w rozwinięciach makr byłoby przydatne w zapobieganiu kolizjom nazw zmiennych.

Robert Harvey
źródło
0

Służy do określania zakresu rzeczy, które zwykle nie mają zasięgu na tym poziomie. Jest to niezwykle rzadkie i ogólnie refaktoryzacja jest lepszym wyborem, ale natknąłem się na to raz lub dwa razy w przeszłości w instrukcjach switch:

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

Jeśli rozważasz konserwację, refaktoryzację należy zrównoważyć, mając wszystkie implementacje w rzędzie, o ile każda ma tylko kilka linii. Łatwiej jest mieć je wszystkie razem niż listę nazw funkcji.

W moim przypadku, jeśli dobrze pamiętam, większość przypadków była niewielkimi zmianami tego samego kodu - z wyjątkiem tego, że jeden z nich był identyczny z drugim, tylko z dodatkowym przetwarzaniem wstępnym. Przełącznik okazał się najprostszą formą dla kodu, a dodatkowy zakres pozwolił na użycie dodatkowych zmiennych lokalnych, o które on i inne przypadki nie musieli się martwić (takie jak nazwy zmiennych przypadkowo pokrywające się z nazwami zdefiniowanymi przed przełącznikiem, ale używane później).

Zauważ, że instrukcja switch to po prostu użycie jej, z którą się spotkałem. Jestem pewien, że może mieć zastosowanie również w innych miejscach, ale nie przypominam sobie, aby były skutecznie wykorzystywane w ten sposób.

Izkata
źródło