Web Analytics

See also ebooksgratis.com: no banners, no cookies, totally FREE.

CLASSICISTRANIERI HOME PAGE - YOUTUBE CHANNEL
Privacy Policy Cookie Policy Terms and Conditions
C (język programowania) - Wikipedia, wolna encyklopedia

C (język programowania)

Z Wikipedii

C jest imperatywnym strukturalnym językiem programowania stworzonym na początku lat siedemdziesiątych przez Dennisa Ritchiego do programowania systemów operacyjnych i innych zadań niskiego poziomu.

Spis treści

[edytuj] Historia

Poprzednikiem języka C był interpretowany język B, który Ritchie rozwinął w język C. Pierwszy okres rozwoju języka to lata 1969-1973. W roku 1973 w języku C udało się zaimplementować jądro (ang. kernel) systemu operacyjnego Unix. W 1978 roku Brian Kernighan i Dennis Ritchie opublikowali dokumentację języka p.t. C Programming Language (wydanie polskie: Język ANSI C).

C stał się popularny poza Laboratoriami Bella (gdzie powstał) po 1980 roku i stał się dominującym językiem do programowania systemów operacyjnych i aplikacji. Na bazie języka C w latach osiemdziesiątych Bjarne Stroustrup stworzył język C++, który wprowadza możliwość programowania obiektowego.

[edytuj] Standardy

Standard języka C został zapisany w normie ISO 9899. Pierwsze wydanie tego dokumentu miało miejsce w 1990 roku (ISO 9899:1990) i było modyfikacją standardu ANSI: ANSI X3.159-1989 "Programming Language C". Język zgodny z tą wersją standardu określany jest nieformalnie jako C89. Od tego czasu powstało wiele uaktualnień tej normy. Ostatnia ma oznaczenie ISO 9899:1999 i została opublikowana w 1999 roku, język zgodny z tą normą określany jest nieformalnie jako C99. Standard C99 nie jest kompatybilny z C++.

[edytuj] Podstawowe elementy języka C

[edytuj] Komentarze

Komentarz blokowy umieszcza się między sekwencją znaków "/*" a "*/", a komentarz liniowy rozpoczyna się sekwencją "//" a kończy znakiem końca linii. Komentarz liniowy wprowadzono do obecnego standardu języka C (ISO 9899:1999) z języka C++.

/* To jest komentarz
 * blokowy. Zajmuje on
 * kilka linii */
 
// to jest komentarz liniowy

[edytuj] Słowa kluczowe

Lista słów kluczowych języka C na podstawie normy ISO/IEC 9899-1999 (C99). Istnieją zależne od implementacji rozszerzenia języka o inne słowa kluczowe jak np. asm.

auto enum restrict unsigned
break extern return void
case float short volatile
char for signed while
const goto sizeof _Bool
continue if static _Complex
default inline struct _Imaginary
do int switch
double long typedef
else register union

[edytuj] Typy podstawowe

Typ Typowe wielkości pamięci Uwagi
_Bool 1 bajt tylko w nowych wersjach
char 1 bajt  
unsigned char 1 bajt  
signed char 1 bajt  
int 2 lub 4 bajty  
unsigned int 2 lub 4 bajty  
short int 2 bajty  
unsigned short int 2 bajty  
long int 4 bajty  
unsigned long int 4 bajty  
long long int 8 bajtów tylko w nowych wersjach
unsigned long long int 8 bajtów tylko w nowych wersjach
float 4 bajty  
double 8 bajtów  
long double 8, 10 lub 12 bajtów  
float _Complex 8 bajtów tylko w nowych wersjach
double _Complex 16 bajtów tylko w nowych wersjach
long double _Complex 24 bajty tylko w nowych wersjach
float _Imaginary   tylko w nowych wersjach
double _Imaginary   tylko w nowych wersjach
long double _Imaginary   tylko w nowych wersjach
void    

Zmienne deklaruje się za pomocą prostej konstrukcji:

typ nazwa;

Należy pamiętać, że podane powyżej rozmiary zmiennych są jedynie orientacyjne i mogą się różnić w zależności od środowiska (np. jeśli w systemie zdefiniujemy znaki 16-bitowe, to zmiene typu char zajmują po 16 bitów; w systemach 64-bitowych zmienna long posiada zazwyczaj 64-bity).
Inną ważną sprawą jest szerokość słowa. Nic nie stoi na przeszkodzie, aby słowo miało np. 7 bitów.
Wielu programistów nie zdaje sobie sprawy z powyższych problemów, co może być (i jest) przyczyną wielu błędów oprogramowania, a w rezultacie powstają różne luki w bezpieczeństwie oprogramowania.

[edytuj] Typy pochodne

enum nazwa { jeden, dwa };
struct nazwa {
    typ1 nazwa1;
    typ2 nazwa2;
};
  • Unie
union nazwa {
    typ1 nazwa1;
    typ2 nazwa2;
};
typ [identyfikator] : długość;
typ nazwa[liczba];
typ *nazwa;
typ **nazwa;
typ_zwracany (*nazwa_wsk_do_funkcji)(typ nazwa_parametru1,typ nazwa_parametru2,...);

[edytuj] Instrukcje sterujące

[edytuj] Instrukcja if

Instrukcja if (ang. jeśli) to podstawowa instrukcja warunkowa w C - gdy warunek1 jest spełniony (zwraca wartość niezerową), wykonany zostanie kod zawarty w bloku ograniczonym klamrami. Instrukcje else if i else są opcjonalne, sprawdzane są wyłącznie, gdy podstawowy warunek nie jest spełniony.

if (warunek1) {
    instrukcje;
}
else if(warunek2){
    instrukcje;
}
else {
    instrukcje;
}

[edytuj] Pętla while

Pętla while (ang. dopóki) - instrukcja wykonuje kod zawarty w bloku ograniczonym klamrami tak długo, dopóki jej warunek jest spełniony (ma wartość różną od zera). Instrukcja sprawdza warunek przed wykonaniem ciała pętli. Pętla while może wykonywać się nieskończoną ilość razy, gdy wyrażenie nigdy nie przyjmie wartości 0, może także nie wykonać się nigdy, gdy wartość przed pierwszym przebiegiem będzie zerowa.

while (wyrażenie) {
 instrukcje;
}

Przykład

int x = 10;
while (x > 0)
{
  printf(".");
  --x;
}

Pętla będzie się wykonywać tak długo, jak zmienna x będzie dodatnia - wykona się więc 10 razy, drukując kropkę na standardowe wyjście.

[edytuj] Pętla do...while

Pętla do...while (ang. wykonuj...dopóki) jest podobna do pętli while z tą różnicą, że warunek sprawdzany jest po każdym wykonaniu pętli, a więc instrukcje w pętli zawsze wykonają się co najmniej raz.

do {
instrukcje;
}
while(warunek);

Przykład

int x = 0;
do
{
  printf(".");
}
while(x > 0);

Instrukcje w pętli wykonają się jeden raz i zostanie wydrukowana kropka na standardowe wyjście. Następnie sprawdzony będzie warunek pętli. W podanym przykładzie nie będzie spełniony, pętla zakończy więc działanie po jednym obiegu.

[edytuj] Pętla for

Diagram pętli for
Diagram pętli for

Pętla for (ang. dla) jest rozwinięciem pętli while o instrukcję wykonywaną przed pierwszym obiegiem oraz dodatkową instrukcję wykonywaną po każdym przebiegu - najczęściej służącą jako licznik obiegów. Często zmienną liczącą kolejne wykonania ciała pętli nazywa się iteratorem.

for (wyrażenie1; wyrażenie2; wyrażenie3) {
  instrukcje;
}

Przed pierwszym sprawdzeniem warunku pętli wykonane zostanie wyrażenie1 (na diagramie oznaczone przez literkę A), następnie sprawdzony zostanie warunek umieszczony w wyrażeniu2 (literka B). Dopóki warunek będzie miał niezerową wartość logiczną, wykonywane będzie ciało pętli ograniczone klamrami, oraz - po każdym obiegu - wyrażenie3. Jeśli wyrażenie2 na początku jest fałszywe, ciało pętli nie wykona się wcale. Każde z wyrażeń można opuścić (nie opuszczając jednak towarzyszącego jej średnika) - zamiast nich domyślnie występować będzie wartość niezerowa. Ominięcie wszystkich wyrażeń lub tylko środkowego doprowadzi więc do powstania nieskończonej pętli.

Przykład

int x;
for (x = 10; x > 0; x--)
{
  printf(".");
}

Powyższa pętla jest równoważna przykładowi podanemu przy pętli while. Przed sprawdzeniem warunku zmienna x zainicjalizowana zostanie wartością 10. Następnie sprawdzony będzie warunek, który w tym przypadku zwróci wartość niezerową. Wykonane zostanie ciało pętli - na standardowe wyjście wydrukowana zostanie kropka. Następnie wykonana zostanie trzecia instrukcja - dekrementacja wartości x. Pętla wykona się dziesięciokrotnie, a zmienna x, służąca w tej pętli za iterator, po jej zakończeniu będzie miała wartość 0.

[edytuj] Instrukcja switch

Instrukcją decyzyjną switch (ang. przełącznik) zastąpić można wielokrotne wywoływanie instrukcji warunkowej if np. dla różnych wartości tej samej zmiennej - przykładowo, gdy zmienna może przyjąć 10 różnych wartości, a dla każdej z nich należy podjąć inne działanie.

switch (wyrażenie) {
     case wartość1 :
        instrukcje;
        [break;]
     case wartość2 :
        instrukcje;
        [break;]
     default :
        instrukcje;
        [break;]
}

Wyrażenie najczęściej jest zmienną o określonej wartości. Jeśli tą wartością jest wartość1, wykonywane są instrukcje następujące po odpowiedniej etykiecie case aż do następnej instrukcji przerywającej, z reguły break (instrukcja ta nie musi występować na zakończenie każdego bloku rozpoczętego przez case - wykonany zostanie wtedy kod następnych przypadków). Przypadek default jest opcjonalny, określa instrukcje wykonywane, gdy wartość zmiennej nie jest równa żadnemu z wyszczególnionych przypadków.

Przykład

int x;
scanf("%d", &x);
switch(x)
{
  case 1:
    printf("jeden");
    break;
  case 2:
    printf("dwa");
    break;
  default:
    printf("coś innego");
    break;
}

Powyższa instrukcja switch wczyta liczbę ze standardowego wejścia i wyświetli "jeden", jeśli podana liczba to 1, "dwa" jeśli podano 2 oraz "coś innego", jeśli podano jakąkolwiek inną wartość liczbową. W przypadku, gdyby program nie zawierał instrukcji break, podanie wartości 1 spowodowałoby wyświetlenie zarówno "jeden", jak i "dwa" oraz "coś innego".

[edytuj] Funkcje

Funkcje w C tworzy się za pomocą następującej składni:

[klasa_pamieci] [typ] nazwa([lista_argumentów])
{
  instrukcje;
  [return wartość;]
}

Klasa pamięci, określenie zwracanego typu oraz lista argumentów są opcjonalne. Jeżeli nie podano typu, domyślnie jest to typ liczbowy int, a instrukcję return kończącą funkcję i zwracającą wartość do funkcji nadrzędnej można pominąć. Listę argumentów tworzą wszystkie zmienne (zarówno przekazywane przez wartość jak i wskaźniki) wraz z określeniem ich typu. Dozwolona jest rekurencja, nie ma natomiast możliwości przeciążania funkcji (wprowadzonego m.in. w C++).

Przykład

int kwadrat(int x)
{
  return x*x;
}

Ta prosta funkcja zwraca podaną do niej liczbę podniesioną do kwadratu. Typ przekazanej do niej zmiennej oraz typ zwracany określony jest jako int. Definicja funkcji umieszczona musi być w głównej przestrzeni (poza wszelkimi innymi funkcjami), a wywoływać ją można z każdego miejsca w programie. Przykładowo, aby zmiennej n przypisać wartość kwadratu z 16, wywołać należy: int n = kwadrat(16);.

[edytuj] Przykłady

Hello, world

#include <stdio.h>
 
int main(void)
{
    printf ("Hello, world!\n");
    return 0;
}

W powyższym kodzie:

  • Dyrektywa #include włącza do pliku zawartość odpowiednich plików nagłówkowych - w tym przypadku pliku stdio.h, zawierającego m.in. prototyp funkcji printf.
  • Główna funkcja nazywa się zawsze main. Zwraca ona wartość typu całkowitoliczbowego - int, w tym przypadku 0.
  • Za wyprowadzenie wyniku na standardowe wyjście (zwykle na ekran) odpowiedzialna jest funkcja printf.
  • Łańcuch tekstowy zamyka się w cudzysłowach: "łańcuch".
  • Znak nowej linii zapisuje się jako "\n".

[edytuj] Krytyka języka C

Język C pozwala na wykonywanie niskopoziomowych operacji, przez co wiele prostych błędów programistycznych nie jest wykrywanych przez kompilator, a przy wykonywaniu programu ujawniają się dopiero po jakimś czasie i w przypadkowych miejscach. Twórcy języka chcieli uniknąć sprawdzeń w czasie kompilacji i wykonywania programu, bo były one zbyt kosztowne czasowo, gdy C był implementowany po raz pierwszy. Z czasem powstały zewnętrzne narzędzia do wykonywania części z tych sprawdzeń. Nic nie przeszkadza implementacji języka w dostarczaniu takich sprawdzeń, ale też nie są one wymagane przez oficjalne standaryzacje.

Używanie języka C wymaga od programisty dokładnego zrozumienia pisanego kodu źródłowego, łącznie z mechanizmami kompilacyjnymi, dodatkowo komplikowanymi nieprzenośnością między platformami i kompilatorami, jak również rygorystycznego przestrzegania dobrych praktyk, szczególnie w odniesieniu do funkcji obsługujących wszelkiego rodzaju buforowania. Podobnie brak standaryzacji bibliotek wyższego poziomu jest powodem do uznania C za język niezalecany dla początkujących. Jednakże wiele z tych niedogodności można zniwelować tworząc własne elastyczniejsze rozwiązania. Pod względem zastosowań praktycznych C nie ustępuje innym językom, traci w stosunku do nich, gdy wziąć pod uwagę czas i inne środki niezbędne do implementacji porównywalnych systemów.


[edytuj] Niedostępne właściwości

C był tworzony jako mały i prosty język, co niewątpliwie przyczyniło się do jego popularności, ponieważ nowe kompilatory języka mogły być szybko tworzone na nowe platformy. Relatywnie niskopoziomowa natura języka daje programiście dokładną kontrolę nad tym co robi komputer, jednocześnie pozwalając na specjalne dostosowanie i agresywne optymalizacje na konkretnych platformach. Pozwala to na szybkie działanie kodu nawet na ograniczonym sprzęcie, na przykład w systemach wbudowanych.

C nie zawiera wielu właściwości dostępnych w innych językach programowania:

  • Nie można przypisywać tablic (nie mylić ze wskaźnikami traktowanymi jako tablice) lub stringów - kopiowanie może zostać wykonane za pomocą standardowych funkcji; możliwe jest przypisywanie obiektów o typach struct lub union.
  • Brak odśmiecacza
  • Brak wymagania sprawdzania zakresu tablic
  • Brak operacji na całych tablicach
  • Brak składni dla zasięgów, na przykład notacji A..B używanej w wielu językach, z wyjątkiem zasięgu dla pól bitowych.
  • Brak funkcji zagnieżdżonych
  • Brak domknięć lub przekazywania funkcji jako parametru (tylko wskaźniki do funkcji i zmiennych)
  • Brak generatorów i coroutines; Kontrola przepływu programu w obrębie wątku opiera się tylko na zagnieżdżonych wywołaniach funkcji, nie licząc funkcji bibliotecznych longjmp czy setcontext
  • Brak obsługi wyjątków; funkcje standardowe pokazują błędy za pomocą globalnej zmiennej errno lub specjalnych zwracanych wartości
  • Ograniczona obsługa programowania modułowego.
  • Brak polimorfizmu w czasie kompilacji w formie przeciążania funkcji i operatorow
  • Brak obsługi programowania obiektowego, a w szczególności polimorfizmu, dziedziczenia i ograniczona (tylko w obrębie modułu) obsługa enkapsulacji
  • Brak bezpośredniej obsługi programowania wielowątkowego i sieci
  • Brak standardowych bibliotek graficznych i innych.

Wiele z tych właściwości jest dostępnych w różnych kompilatorach jako dodatkowe rozszerzenia lub może zostać dostarczone przez zewnętrzne biblioteki albo zasymulowane przez odpowiednią dyscyplinę przy programowaniu. Na przykład, w większości języków zorientowanych obiektowo, funkcje-metody mają specjalny wskaźnik "this", który wskazuje na aktualny obiekt. Przekazując ten wskaźnik jako zwykły argument funkcji podobna funkcjonalność może zostać uzyskana w C. Gdy w C++ napisano by:

stack->push(val);

w C można zapisać:

push(stack,val);

Możliwości graficzne można rozszerzyć poprzez:

  • bezpośrednie tworzenie pliku graficznego ( rastrowego czy wektorowego ),
  • korzystania z niestandardowych bibliotek graficznych, np. SDL, Freeglut.
  • bezpośredniego dostępu do GPU za pomocą Cg lub CUDA
  • tworzenie pliku z danymi w C a grafiki za pomocą innego programu, np. Gnuplota.

[edytuj] Niezdefiniowane zachowania

Wiele operacji w C mających niezdefiniowane zachowanie nie jest sprawdzanych w czasie kompilacji. W przypadku C, "niezdefiniowane zachowanie" oznacza że zachowanie nie jest opisane w standardzie i co dokładnie się stanie nie musi być opisane w dokumentacji danej implementacji C. W praktyce czasami poleganie na niezdefiniowanych zachowaniach może prowadzić do trudnych w rozpoznaniu błędów. Zachowania te mogą różnić się między kompilatorami C. Głównym celem pozostawienia niektórych zachowań jako niezdefiniowane jest pozwolenie kompilatorowi na generowanie bardziej wydajnego kodu dla zdefiniowanych zachowań, co jest ważne dla głównej roli języka C jako języka implementacji systemów; unikanie niezdefiniowanych zachowań jest odpowiedzialnością programisty. Przykłady niezdefiniowanych zachowań:

  • Odczyt i zapis poza zasięgiem tablicy.
  • Przepełnienie liczby całkowitej ze znakiem (integer overflow).
  • Dotarcie do końca funkcji zwracającej wartość, bez napotkania na wyrażenie return.
  • Odczytanie zmiennej przed zapisaniem do niej wartości.
  • Kolejność wykonywania wyrażeń przekazanych jako argumenty do funkcji.

Wszystkie te operacje to błędy programistyczne, które mogą się zdarzyć w wielu językach programowania; C przyciąga krytykę ponieważ jego standard wyraźnie wylicza wiele przypadków niezdefiniowanego zachowania, także tam, gdzie mogło by ono zostać dobrze zdefiniowane i nie zawiera żadnego mechanizmu obsługi błędów w czasie wykonywania programu.

[edytuj] Alokacja pamięci

Automatycznie i dynamicznie alokowane obiekty nie są koniecznie zainicjalizowane; początkowo mają niezdefiniowane wartości (zwykle zbiór bitów który akurat był poprzednio w danym miejscu w pamięci, który nawet może nie reprezentować żadnej prawidłowej wartości dla danego typu danych). Gdy program próbuje odczytać taką niezainicjalizowaną wartość, rezultat jest niezdefiniowany. Wiele współczesnych kompilatorów próbuje wykryć i ostrzec przed tym problemem, ale pojawiają się błędy pierwszego i drugiego rodzaju.

Innym częstym problemem jest konieczność ręcznej synchronizacji użycia pamięci na stercie. Na przykład, gdy jedyny wskaźnik na przydzieloną pamięć wyjdzie poza zasięg lub gdy jego wartość się zmieni przed wywołaniem na nim free (), to pamięć nie może zostać już odzyskana do dalszego użycia i jest stracona do końca działania programu. Zjawisko to nazywa się wyciekiem pamięci. Odwrotnie, możliwe jest zwolnienie pamięci zbyt wcześnie i mimo to dalsze odwoływanie się do niej; ponieważ system alokacji pamięci może ją w każdej chwili wykorzystać do innych celów, dochodzi do nieprzewidywalnych zachowań programu, gdy dane miejsce pamięci ma wielu użytkowników jednocześnie uszkadzających sobie nawzajem dane. Zwykle symptomy te pojawiają się w miejscach programu zupełnie oddalonych od faktycznego błędu. Błędy te można ograniczyć przez użycie dodatkowego odśmiecacza lub RAII.

[edytuj] Wskaźniki

Wskaźniki są głównym źródłem zagrożeń w języku C. Ponieważ mogą zwykle wskazywać na dowolny obszar pamięci, prowadzić to może do niepożądanych efektów. Nawet odpowiednio używane wskaźniki wskazujące na bezpieczne miejsca, mogą zostać przypadkiem przeniesione na miejsca niebezpieczne przez użycie nieodpowiedniej arytmetyki wskaźników; pamięć na którą wskazują może być zwolniona i użyta już na coś innego (zwisający wskaźnik); mogą być niezainicjalizowane (dziki wskaźnik), lub mogą mieć bezpośrednio przypisaną wartość poprzez rzutowanie, unię, lub inny uszkodzony wskaźnik. Ogólnie C pozwala na swobodną manipulację i konwersję typów wskaźników, chociaż kompilatory zwykle dostarczają opcje różnego poziomu ich kontroli. Inne języki niwelują problemy ze wskaźnikami poprzez użycie bardziej ograniczoncych typów referencji.

[edytuj] Tablice

Chociaż C wspiera tablice statyczne, nie jest wymagane, aby sprawdzany był zasięg ich indeksów. Na przykład, można zapisać w szóstym elemencie tablicy pięcioelementowej, powodując nadpisanie innej pamięci. Ten rodzaj błędu, przepełnienie bufora, jest źródłem wielu problemów z bezpieczeństwem komputerowym. Z drugiej strony, ponieważ technologia eliminacji sprawdzania zasięgu tablic praktycznie nie istniała w czasie tworzenia języka C, sprawdzanie zasięgu miało duży narzut czasu działania programu, zwłaszcza w obliczeniach numerycznych. Kilka lat później, niektóre kompilatory Fortranu miały przełącznik do włączania lub wyłączania sprawdzania zasiegu tablic. Byłoby to jednak dużo mniej użyteczne w języku C, gdzie argumenty o typie tablicowym są przekazywane przez zwykłe wskaźniki.

Tablice wielowymiarowe są często używane w algorytmach numerycznych (zwłaszcza z algebry liniowej) do zapisu matryc. Struktura tablicy w języku C jest bardzo dobrze przystosowana do tego zadania. Ponieważ zmienne są przekazywane jedynie jako proste wskaźniki, zasięg tablicy musi być znany i stały lub osobno przekazywany do funkcji korzystających z nich i dostęp do tablic dynamicznych nie może być realizowany za pomocą podwójnego indeksu (obejściem jest użycie dodatkowej tablicy "rzędu" wskaźników do kolumn). Problemy te są omówione w książce Numerical Recipes in C, rozdział 1.2, strona 22ff.

C99 wprowadził tablice o zmiennym rozmiarze, które rozwiązują niektóre problemy ze zwykłymi tablicami z C.

[edytuj] Składnia

Chociaż naśladowana przez wiele języków z powodu jej popularności, składnia C jest często uznawana za jeden z jego słabszych punktów. Na przykład, Kernighan i Ritchie mówią w drugiej edycji The C Programming Language "C, tak jak każdy inny język, ma swoje słabe punkty. Niektóre operatory mają zły priorytet; niektóre części składni mogłyby być lepsze." Niektóre konkretne problemy to:

  • Brak sprawdzenia liczby i typów argumentów gdy deklaracja funkcji ma pustą listę parametrów. (To pozwala na kompatybilność wstecz z K&R C, w którym nie było prototypów funkcji.)
  • Wspomniany przez Kerninghan i Ritchie wyżej kwestionowalny wybór niektórych priorytetów operatorów, na przykład == wiażący ściślej niż & i | w wyrażeniu takim jak x & 1 == 0.
  • Użycie operatora =, używanego w matematyce do porównania, jako operatora przypisania, podążając za Fortran, PL/I, Basic, ale w przeciwieństwie do ALGOL i jego pochodnych. Ritchie dokonał tego wyboru świadomie, bazując na tym że przypisanie występuje częściej niż porównanie.
  • Podobieństwo operatorów przypisania i porównania (= i ==), przez co łatwo je pomylić. Słaby system typów języka C pozwala na błędną podmianę ich bez błędu kompilacji (chociaż niektóre kompilatory emitują ostrzeżenia).
  • Brak operatorów infixowych dla złożonych obiektów, zwłaszcza dla operacji na stringach, co czyni programy gęsto wykorzystujące te operacje nieczytelne.
  • Duże oparcie na symbolach nawet tam, gdzie według niektórych jest to mniej czytelne, na przykład "&&" i "||" zamiast odpowiednio "and" i "or". Możliwe do pomylenia są też operatory bitowe ("&" i "|") z operatorami logicznymi ("&&" i "||"), ponieważ te pierwsze mogą być często, ale nie zawsze, użyte w miejsce drugich bez zmiany działania programu.
  • Składnia deklaracji może być nieintuicyjna, zwłaszcza dla wskaźników do funkcji. (Pomysłem Ritchiego była deklaracja identyfikatorów w kontekstach przypominających ich użycie.)

[edytuj] Oszczędność wyrażenia

Jedną z krytyk C jest jego możliwość tworzenia ponad miarę zwięzłych fragmentów kodu. Klasyczny przykład pojawiający się w K&R to poniższa funkcja kopiująca zawartość ciągu znaków wskazywanego przez t do ciągu znaków wskazywanego przez s:

void strcpy(char *s, char *t)
{
    while (*s++ = *t++);
}

W tym przykładzie, s i t to wskaźniki na pierwsze elementy tablic znaków zakończonych wartościami null. Każde przejście pętli wyrażenia while wykonuje poniższe operacje:

  • Kopiowanie litery wskazywanej przez t (oryginalnie ustawionego na pierwszą literę stringa do skopiowania) do odpowiadającej pozycji wskazywanej przez s (oryginalnie ustawionego na pierwszą literę stringa do którego kopiowane są dane).
  • Zwiększenie wartości wskaźników s i t, tak by wskazywały na kolejne znaki. Zauważ, że wartości s i t mogą być bezpiecznie zmieniane ponieważ są to lokalne kopie wskaźników na oryginalne tablice
  • Sprawdza czy skopiowany znak (rezultat operatora przypisania) to null oznaczający koniec stringa. Test mógłby być zapisany jako "((*s++ = *t++) != '\0')" (gdzie '\0' to znak null), ale w C test wartości boolean sprawdza tylko czy zmienna różni się od zera. Stąd test zwraca prawdę tak długo jak tylko znak jest inny od przerywającego string null (0).
  • Do póki znak nie jest null, warunek daje prawdę, powodując powtórzenie pętli while. (W szczególności, ponieważ kopiowanie znaku następuje przed ewaluacją wyrażenia, jest gwarancja że kończąca wartość null jest też skopiowana.
  • Ciągle powtarzające się ciało pętli for jest pustym wyrażeniem, oznaczonym przez pojedynczy średnik (który pomimo wyglądu nie jest częścią składni pętli while). (Puste ciało pętli nie jest rzadkością.)

Powyższy kod może zostać zapisany jako:

void strcpy(char *s, char *t)
{
    char aux;
    do {
        *s = *t;
        aux = *s;
        s++;
        t++;
    } while (aux != '\0');
}

Przy użyciu współczesnego optymalizującego kompilatora powyższe dwie funkcje skompilują się do identycznej sekwencji instrukcji procesora, więc mniejszy kod programu nie koniecznie oznacza mniejszy kod wynikowy. W bardziej rozwlekłych językach programowania takich jak Pascal, podobna iteracja wymagałaby wielu poleceń. Dla programistów C, ekonomia stylu jest idiomatyczna i pozwala na krótsze wyrażenia; dla krytyków możliwość zrobienia zbyt wiele w jednej linii kodu C prowadzi do problemów z czytelnością kodu.

[edytuj] Ciekawostki

Corocznie organizowany jest konkurs IOCCC (International Obfuscated C Code Contest) prezentujący najbardziej "zaciemnione" (trudne do odczytania) programy w języku C.

Osobliwością języka C jest sposób traktowania tablic[1], a szczególności ich indeksowania. W zasięgu deklaracji:

int i,t[10];

dostęp do np. drugiego elementu tablicy t uzyskuje się poprzez zapis:

i = t[1];

Jednakże (w odróżnieniu od większości innych języków programowania) symbol "[]" nie jest tylko elementem składni (jak np. w Pascalu), ale również operatorem, który przez kompilator traktowany jest następująco:

i = *(t + 1);

Ponieważ dodawanie jest przemienne, przemienny jest również operator "[]" (sic!), a to oznacza, że poniższy fragment kodu (mimo dość zaskakującego zapisu) jest poprawny i równoważny przytoczonemu powyżej:

i = 1[t];

Cechy tej nie mają nawet te języki, których składnia wywodzi się z C, jak np. Java, JavaScript czy Perl.

Inną ciekawostką jest istnienie w C tzw. operatora połączenia, zapisywanego jako "," (przecinek). Operator ten powoduje obliczenie najpierw wartości lewego argumentu, potem prawego, a wartością i typem całego wyrażenia jest wartość i typ prawego argumentu. Może to powodować nieoczekiwane skutki, jeśli program kodowany jest przez początkującego i mało uważnego programistę. Poniższy fragment kodu (który mógłby powstać jako skutek pomylenia kropki dziesiętnej z przecinkiem) zostanie przez kompilator potraktowany jako poprawny, a wartością zmiennej x stanie się 5.0 (sic!):

float x;
 
x = (2,5);

Zastawiający jest również fakt wybrania dwuznaku /* jako otwarcia komentarza (było to najprawdopodobniej zapożyczenie z języka PL/I), można sobie bowiem wyobrazić fragment poprawnego kodu, który w sposób absolutnie niezgodny z intencją programisty niespodziewanie otwiera komentarz. Oto przykład:

int i = 5, *p = &i;
 
i = i/*p;

Intencją programisty było tu podzielenie zmiennej i przez wartość wyłuskaną spod wskaźnika p, jednak kompilator znajdzie w kodzie nie dzielenie ("/") i wyłuskanie ("*"), a otwarcie komentarza ("/*"). Problem rozwiązuje wstawienie do wyrażenia jednej spacji:

int i = 5, *p = &i;
 
i = i/ *p;

[edytuj] Zobacz też

Wikibooks
Zobacz podręcznik na Wikibooks:
C

Przypisy

[edytuj] Literatura

[edytuj] Linki zewnętrzne

Static Wikipedia (no images)

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - bcl - be - be_x_old - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - co - cr - crh - cs - csb - cu - cv - cy - da - de - diq - dsb - dv - dz - ee - el - eml - en - eo - es - et - eu - ext - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gan - gd - gl - glk - gn - got - gu - gv - ha - hak - haw - he - hi - hif - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kaa - kab - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mdf - mg - mh - mi - mk - ml - mn - mo - mr - mt - mus - my - myv - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - quality - rm - rmy - rn - ro - roa_rup - roa_tara - ru - rw - sa - sah - sc - scn - sco - sd - se - sg - sh - si - simple - sk - sl - sm - sn - so - sr - srn - ss - st - stq - su - sv - sw - szl - ta - te - tet - tg - th - ti - tk - tl - tlh - tn - to - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu -

Static Wikipedia 2007 (no images)

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - bcl - be - be_x_old - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - co - cr - crh - cs - csb - cu - cv - cy - da - de - diq - dsb - dv - dz - ee - el - eml - en - eo - es - et - eu - ext - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gan - gd - gl - glk - gn - got - gu - gv - ha - hak - haw - he - hi - hif - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kaa - kab - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mdf - mg - mh - mi - mk - ml - mn - mo - mr - mt - mus - my - myv - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - quality - rm - rmy - rn - ro - roa_rup - roa_tara - ru - rw - sa - sah - sc - scn - sco - sd - se - sg - sh - si - simple - sk - sl - sm - sn - so - sr - srn - ss - st - stq - su - sv - sw - szl - ta - te - tet - tg - th - ti - tk - tl - tlh - tn - to - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu -

Static Wikipedia 2006 (no images)

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - bcl - be - be_x_old - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - co - cr - crh - cs - csb - cu - cv - cy - da - de - diq - dsb - dv - dz - ee - el - eml - eo - es - et - eu - ext - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gan - gd - gl - glk - gn - got - gu - gv - ha - hak - haw - he - hi - hif - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kaa - kab - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mdf - mg - mh - mi - mk - ml - mn - mo - mr - mt - mus - my - myv - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - quality - rm - rmy - rn - ro - roa_rup - roa_tara - ru - rw - sa - sah - sc - scn - sco - sd - se - sg - sh - si - simple - sk - sl - sm - sn - so - sr - srn - ss - st - stq - su - sv - sw - szl - ta - te - tet - tg - th - ti - tk - tl - tlh - tn - to - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu

Static Wikipedia February 2008 (no images)

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - bcl - be - be_x_old - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - co - cr - crh - cs - csb - cu - cv - cy - da - de - diq - dsb - dv - dz - ee - el - eml - en - eo - es - et - eu - ext - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gan - gd - gl - glk - gn - got - gu - gv - ha - hak - haw - he - hi - hif - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kaa - kab - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mdf - mg - mh - mi - mk - ml - mn - mo - mr - mt - mus - my - myv - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - quality - rm - rmy - rn - ro - roa_rup - roa_tara - ru - rw - sa - sah - sc - scn - sco - sd - se - sg - sh - si - simple - sk - sl - sm - sn - so - sr - srn - ss - st - stq - su - sv - sw - szl - ta - te - tet - tg - th - ti - tk - tl - tlh - tn - to - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu