Skocz do zawartości
GAndaLF

[Programowanie] Pułapki języka C

Pomocna odpowiedź

W poprzednim artykule postawiłem tezę, że dobry programista powinien wiedzieć jakie aspekty języka są niebezpieczne i umiejętnie sobie z nimi radzić. Najlepszym rozwiązaniem jest jasne wyrażanie swoich intencji i unikanie podejrzanych konstrukcji. Teraz postaram się przybliżyć kilka niebezpiecznych sytuacji. Niektóre będą oczywiste, albo wręcz śmieszne, inne z kolei mogą dotyczyć aspektów z których wiele osób nie zdaje sobie spawy. Mam nadzieję, że pozwoli to czytelnikowi wyrobić sobie odpowiedni instynkt pozwalający wykrywać potencjalnie niebezpieczne miejsca na bieżąco w trakcie pracy nad kodem.

UWAGA, to tylko wstęp! Dalsza część artykułu dostępna jest na blogu.

Przeczytaj całość »

Poniżej znajdują się komentarze powiązane z tym wpisem.

bug.thumb.jpg.6968f2f74a683f9f58cb499eef75324f.jpg

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Dorzucę, że styl

if ()
{
...
}

ma ten minus, że przy operacjach kopiuj/wklej można sobie coś wstrzyknąć pomiędzy if a {. Dlatego o wiele bezpieczniejszym i spójnym jest formatowanie typu:

if () {
...
}

Polecam również zawsze dodawać {} nawet jeśli ma to być prosty kod w stylu:

if (a==b) {
a++;
}

Dzięki temu przy modyfikacji kodu albo wstawianiu instrukcji debugujących nie ma ryzyka takiego błędu:

if (a==b)
log(a);
a++;

Tu wcześniejsze ciało instrukcji if wypada nam poza warunek.

Wyjątkiem mogą być jednolinijkowce:

if (a==b) a++;

ale ich też się zazwyczaj unika.

Udostępnij ten post


Link to post
Share on other sites
ma ten minus, że przy operacjach kopiuj/wklej można sobie coś wstrzyknąć pomiędzy if a {. Dlatego o wiele bezpieczniejszym i spójnym jest formatowanie typu:

Kod programu: Zaznacz całyif () {

...

}

Zmniejsza to jednak przejrzystość i czytelność kodu. A mimo wszystko ciężko przy kopiuj-wklej coś popsuć jeśli się kopiuje cały blok kodu.

Czasami jest też tak, że pewne czynności obniżają czytelność kodu, przykładowo co oznacza taka kontrukcja:

if(a<--b)
{
...
}

Jak to pierwszy raz zobaczyłem, to zacząłem szukać informacji co robi operator strzałki 😃

Udostępnij ten post


Link to post
Share on other sites

Marooned: Specjalnie unikałem jasnego deklarowania że jeden styl nawiasów {} jest poprawny a inny nie. W poprzednim temacie OldSkull udzielił kompletnie odwrotnej porady. Obie koncepcje mają sporo zwolenników i dla każdego z nich wybranie drugiej jest kompletnie niezrozumiałe. Dlatego nie chciałem rozpisywać się na ten temat żeby uniknąć sporu, którego nie da się rozwiązać.

Osobiście jestem zdania, że styl K&R czyli nawias początkowy w tej samej linii co wcześniejsze wyrażenie, jest zupełnie nieczytelny. Znalezienie nawiasu początkowego w bardziej rozbudowanym kodzie jest dla mnie niesamowicie trudne. Dlatego przychylam się do zdania OldSkulla.

Zasadą dotyczącą stylu nawiasów, której bezwzględnie należy przestrzegać jest dostosowanie się do stylu projektu. Jeżeli dołączamy do jakiegoś projektu w późniejszym czasie albo nasz styl zostanie przegłosowany przez innych musimy zacisnąć zęby i się dostosować.

Udostępnij ten post


Link to post
Share on other sites

Co do stylu i czytelności (więc może lekko offtopując) powiem tak:

Jak dla mnie mniejszą różnicę w czytelności robi to czy klamra jest w tej samej czy w osobnej linii niż rozmiar tabulacji. Tab na 4 spacje (taki standardowy) nie umywa się do takiego na 6 czy 8 (takich używam) spacji.

Przy monitorze 19" i szerokości 1400px takie wcięcie zbytnio pola nie ogranicza, a jest widoczne...

Udostępnij ten post


Link to post
Share on other sites
Zmniejsza to jednak przejrzystość i czytelność kodu.
To ciekawe, bo dla mnie zwiększa 🙂

Tak, to jest dyskusja o wyższości świąt bożego narodzenia nad świętami wielkiej nocy.

A co do tego, że ciężej odnaleźć nawias otwierający... sęk w tym, że nie ma potrzeby jego odszukania jeśli mamy prawidłowe wcięcia. Poza tym, nawet najprostsze notatniki podświetlają pasujące nawiasy, więc tu też problem niejako znika nawet przy braku jakiegokolwiek formatowania.

Podsumowując. Nie pisałem (choć tak uważam), że ten format jest lepszy. Zaznaczyłem tylko, że eliminuje on pewne ryzyko zniszczenia kodu (i więcej kodu widać na ekranie - szczególnie, że nowe monitory mają coraz gorsze proporcje i mało px w pionie). I to nawet nie moja opinia, ale oficjalny plus tej notacji - kiedyś czytałem dość spory artykuł na temat różnych typów formatowań i teraz dzielę się tym z Wami 🙂

Udostępnij ten post


Link to post
Share on other sites

Możliwe, że sie czepiam ale widzę ten temat któryś tam już raz i za każdym razem rośnie mi ciśnienie 😉 Dlaczego? Po pierwsze tytuł jak z onetu mający na celu moim zdaniem nabicie kliknięć w link, a zawartość... jest kontynuacją tytułu. O co mi chodzi? Mianowicie o przedstawienie elementów języka jako czegoś co zostało wprowadzone nie po to aby służyć programiście ale na złość jemu.

To jest tylko kilka przykładowych tricków. Możliwe jest tworzenie w ten sposób naprawdę ciekawych konstrukcji. Oczywiście pod żadnym pozorem nie można wykorzystywać ich w poważnych programach.

Na przykład taki cytat (zdań w tym sensie jest wiele w tym wpisie). Autor traktuje czytelnika jak przygłupa który ma w każdym kolejnym podpunkcie wpajane, że język C powstał po to by utrudnić mu życie... tylko mam takie jedno pytanie, dlaczego coś takiego miało by nie być stosowane ? Dopuki programista świadomie korzysta z języka moim zdaniem nie ma się czego obawiać. Nie ma w tym wpisie prawdopodobnie żadnej ukrytej funkcjonalności języka nie opisanej przez standard, jeśli więc standard coś przewiduje to jaki sens ma pisać o pułapkach ? Pułapka to coś nieznanego, co może faktycznie wymagać sprecyzowania 🙂

A coś takiego:

Może się wydawać, że zmienne a i b powinny przyjąć taką samą wartość. W końcu struktura składa się z 3 bajtów, więc wyrażenia są jednoznaczne. W wielu procesorach np. ARM lub x86 struktury są jednak wyrównywane do jakiejś wartości np. 32bity albo 64bity. W takim wypadku rozmiar struktury będzie większy niż suma rozmiarów wszystkich jej elementów.

To już na prawdę zagrywka na poziomie onetu 🙂 To nie struktury są złe. Po pierwsze można w kompilatorze wyłączyć wyrównywanie, po drugie jest to jak najbardziej porządana funkcjonalność.

Udostępnij ten post


Link to post
Share on other sites

Jeżeli tworzysz kod wykorzystujący nieoczywiste funkcjonalności języka, każesz czytelnikowi domyślać się intencji. Oczywiście zakładasz, że ludzie, z którymi współpracujesz znają język, w którym piszecie. Po jakimś czasie pewnie nawet przyzwyczajają się do cudzych nawyków. Jednak za każdym razem, kiedy czytają taką linijkę kodu muszą tracić czas na przetworzenie informacji. Czasem mogą patrzeć na szybko i coś źle zrozumieć.

Bardzo często programiści głowią się, jak upchnąć kod w jak najmniejszej liczbie linii, najlepiej jeszcze stosując różne opisane przeze mnie zabiegi, żeby pokazać jak dobrze znają język. Celem artykułu było pokazanie, że takie postępowanie jest krótkowzroczne. Jeżeli uważasz, że kod który piszesz jest przydatny i istnieje szansa, że będziesz go wykorzystywał później, udostępniał innym osobom itd nie możesz postępować w ten sposób. Czas, który tracisz podczas debugowania, dodawania nowych funkcjonalności, jakichkolwiek innych zmian, czy nawet tłumaczenia innym o co ci chodziło jest dużo większy niż ta chwila, którą poświęcisz na przemyślenie, przejrzystą implementację i dodanie komentarzy.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Każda funkcjonalność jest nieoczywista jeżeli nie posiadamy wystarczającej wiedzy na jej temat. Ludzie natomiast nie muszą przyzwyczajać się do czyichś nawyków, ponieważ głupotą było by takie podejście w firmach w których ciągle występują zmiany w zespołach i tak naprawdę nigdy nie doszło by do poznania nawyków wszystkich pracowników, dlatego istnieje coś takiego jak zasady kodowania (tutaj odnośnie tego samego podrozdziału w artykule) które obowiązują w firmie i których trzymać się mają pracownicy.

Co do reszty artykułu to tak jak powiedziałem, jeśli ktoś pisze świadomie w swoim języku, stara się rozumieć co gada do niego kompilator, korzysta z narzędzi kontrolujących jakość kodu, wycieki pamięci etc. wtedy artykuł tego typu można wsadzić między bajki. Oczywiście rozumiem, że chciałeś podzielić się wiedzą i jest to słuszne ale szczerze radzę abyś w przyszłości pisał artykuły w innym tonie bo takie jak ten są podwaliną forumowych mitów, a nie to pewnie było Twoją intencją. C jak każdy język ma swoje specyficzne funkcjonalności, nie jest też najprostszy i nie jest idioto odporny ale żaden nie jest. Może natomiast być piekielnie efektywny, a ceną za to są jak to nazwałeś 'pułapki', zdefiniowane przez standard z resztą 🙂

Udostępnij ten post


Link to post
Share on other sites

osmial, to, że można zroić

#define true rand()%2

nie znaczy, że się powinno. W zdecydowanej większości zastosowań wystarczy, że kod jest zwyczajnie efektywny, piekielnie już być nie musi. A skoro tak, to i różnych diabelskich sztuczek nie trzeba stosować.

To, że jesteś w stanie napisać świetnie działający program, który będziesz doskonale rozumiał, nie znaczy, że inni nie będą mieli z tym problemu. Nie uważam się za złego programistę, ale perspektywa przerabiania czyjegoś kodu zawsze wywołuje we mnie nieprzyjemne dreszcze.

Udostępnij ten post


Link to post
Share on other sites

Jasne, tylko pisanie w artykule np.:

while (*s != 0);
{
   s++;
}

i zaznaczanie, że jest to pułapka języka jest IMO złym nastawieniem autora, bo to najzwyklejszy błąd w kodzie, a to czy kompilator zwrócu warning czy nie to kwestia kompilatora i ewnetualnie nad tym można się rozwodzić.

Chodzi mi o to, że język sam w sobie nie powinien być rozpatrywany w kontekście tego czy ma pułapki czy nie jeśli te zachowania przewiduje dokumentacja analogicznie w innym języku można stworzyć analogiczną konstrukcję do ww. i czy ona tez będzie pułapką języka? Nie, będzie to błąd programisty.

Udostępnij ten post


Link to post
Share on other sites

BTW: zrobienie while (*s != 0); wcale nie musi być błędem, powiem wręcz, że tego się używa w mikrokontrolerach wręcz na okrągło, tylko *s jest jakimś makrem. Zwykłe czekanie aż zmieni się stan rejestru procesora (np. pojawi się jakaś flaga) to jest właśnie taki zapis! Tutaj nam podpowiada, że jest to błąd to, że {} jest w kolejnej linii mimo iż nei jest potrzebne.

Udostępnij ten post


Link to post
Share on other sites

OldSkull, pisząc, że jest to błąd miałem na myśli ten konkretny przykład gdzie ten średnik zmienia logikę dzialania programu. Zastosowanie while(warunek); jest jak piszesz stosowane nagminnie, szczególnie w pracy z mikrokontrolerami. Nie zgodzę się jednak, że otwarcie nowego bloku jest błędem. W mojej wersji gcc: gcc version 4.4.7 20120313 (Red Hat 4.4.7-3) (GCC) taki kod:

while(1);
  {
    int i = 0;
  }

kompilowany z flagą -Wall nie daje nawet warningów.

Tutaj: http://devpytania.pl/questions/17297/po-co-cc-pozwala-na-stawianie-nawiasow-klamrowych-do-ktorych-nie-jest-zastosowana-instrukcja-sterujaca troszeczke więcej na ten temat. Jest też zaproponowane kilka ciekawych zastosowań tej opcji:)

Udostępnij ten post


Link to post
Share on other sites

osmial pomyśl teraz ile zaoszczędzisz sobie a przede wszystkim innym trudu, jeśli po while(); dodasz jeszczelinijkę przerwy przed otwarciem bloku, żeby nikt nie miał wątpliwości jaka była twoja intencja.

Udostępnij ten post


Link to post
Share on other sites
osmial pomyśl teraz ile zaoszczędzisz sobie a przede wszystkim innym trudu, jeśli po while(); dodasz jeszczelinijkę przerwy przed otwarciem bloku, żeby nikt nie miał wątpliwości jaka była twoja intencja.

dzięki za radę, jest na prawdę przednia:) Teraz tylko wskaż mi miejsce gdzie napisałem, że taki kod jest czytelny. Widzę, że mylisz pojęcia, bo to co uważasz za pułapkę języka jest tak naprawdę złym (w sensie prowadzącym do nieczytelności) formatowaniem kodu.

Udostępnij ten post


Link to post
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »

×