C (język programowania)

Transkrypt

C (język programowania)
C (język programowania)
1
C (język programowania)
C
Pojawienie się
1972
Paradygmat
imperatywny (proceduralny)
Typowanie
statyczne (słabe)
Implementacje
Borland Turbo C, GCC, Microsoft Visual C, MinGW, LLVM, Tiny C Compiler
Pochodne
K&R C, ANSI C, C99, C++
Twórca
Dennis Ritchie
Platforma sprzętowa
wieloplatformowy
Platforma systemowa wieloplatformowy
C – imperatywny, strukturalny język programowania wysokiego poziomu stworzony na początku lat
siedemdziesiątych XX w. przez Dennisa Ritchiego do programowania systemów operacyjnych i innych zadań
niskiego poziomu.
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 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 ułatwia znacząco programowanie obiektowe.
Standardy
W 1983 roku ANSI powołało komitet X3J11 w celu ustanowienia standardu języka C. Standard został zatwierdzony
w 1989 roku jako ANSI X3.159-1989 "Programming Language C". Ta wersja języka jest określana nieformalnie
jako ANSI C, standardowe C lub C89. W 1990 roku standard ANSI C został zaadoptowany przez ISO jako norma
ISO/IEC 9899:1990. Ta wersja jest potocznie nazywana C90. Ponieważ normy wydane przez oba ciała
standaryzacyjne są identyczne, wobec tego potoczne określenia C89 oraz C90 dotyczą tej samej wersji języka C. W
1999 roku ISO opublikowało normę ISO/IEC 9899:1999, język zgodny z tą normą jest nieformalnie nazywany C99.
Ostatnia norma została opublikowana w 2011 roku pod nazwą ISO/IEC 9899:2011. Ta wersja języka jest potocznie
nazywana C11 (C1X przed opublikowaniem normy).
C (język programowania)
2
Podstawowe elementy języka C
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
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
Typy podstawowe
Typ
Typowe wielkości pamięci
Uwagi
bool
1 bajt
tylko w wersji C99 (po włączeniu nagłówka <stdbool.h>)
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 lub 8 bajtów
unsigned long int
4 lub 8 bajtów
long long int
8 bajtów
tylko w nowych wersjach
unsigned long long int 8 bajtów
tylko w nowych wersjach
C (język programowania)
3
float
4 bajty
double
8 bajtów
long double
8, 10, 12 lub 16 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 (w systemach 64-bitowych zmienna long posiada zazwyczaj 64-bity).
Inną ważną sprawą jest szerokość bajtu. Język C wymaga tylko, by bajt składał się z co najmniej 8 bitów. Jest to
zwykle najmniejsza porcja danych, która może być adresowana.
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.
Typy pochodne
• Wyliczeniowy typ danych
enum nazwa { jeden, dwa };
• Struktury
struct nazwa {
typ1 nazwa1;
typ2 nazwa2;
};
• Unie
union nazwa {
typ1 nazwa1;
typ2 nazwa2;
};
• Pola bitowe
typ [identyfikator] : długość;
• Tablice
typ nazwa[liczba];
C (język programowania)
• Wskaźniki
typ *nazwa;
typ **nazwa;
typ_zwracany (*nazwa_wsk_do_funkcji)(typ parametru1,typ parametru2,...);
Instrukcje sterujące
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. Fragment else jest opcjonalny.
Problem wiszącego else jest rozwiązany przez przyporządkowanie else do na najbardziej zagnieżdżonego if.
if (warunek1)
instrukcja1
else
instrukcja2
Pętla while
Pętla while (ang. podczas gdy) – wykonuje instrukcję 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)
instrukcja
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 w
każdym obiegu pętli kropkę na standardowe wyjście.
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
instrukcja
while(warunek);
Przykład
int x = 0;
do
{
4
C (język programowania)
5
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.
Pętla 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.
Diagram pętli for
for (wyrażenie1; wyrażenie2; wyrażenie3)
instrukcja
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ść, wykonywane będzie ciało pętli 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--)
{
C (język programowania)
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.
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 opuszczenia 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 0:
case 1:
printf("jeden");
break;
case 2:
printf("dwa");
break;
default:
printf("coś innego");
break;
}
6
C (język programowania)
Powyższa instrukcja switch wczyta liczbę ze standardowego wejścia i wyświetli "jeden", jeśli podana liczba to 0
lub 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 0 lub 1 spowodowałoby wyświetlenie zarówno "jeden",
jak i "dwa" oraz "coś innego".
Funkcje
Funkcje w C tworzy się za pomocą następującej składni:
[klasa_pamieci] [typ] nazwa([lista_parametrów])
{
instrukcje;
[return wartość;]
}
Klasa pamięci, określenie zwracanego typu oraz lista parametró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);.
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".
7
C (język programowania)
• Znak nowej linii zapisuje się jako "\n".
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 jednak w stosunku do nich, gdy wziąć pod uwagę czas i inne środki niezbędne do
implementacji porównywalnych systemów.
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 (ang. garbage collection).
• 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 współprogramów; 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 operatorów.
• 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.
8
C (język programowania)
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),
• tworzenie pliku z danymi w C, a grafiki za pomocą innego programu, np. Gnuplota,
• dostępu do sprzętu, tj. do procesora karty graficznej (ang. GPU), za pomocą:
• języka asemblera,
• języka Cg,
• języka C z wykorzystaniem:
• niestandardowych bibliotek graficznych:
• wieloplatformowych: SDL, Freeglut lub Cairo,
• jednoplatformowych: Windows WGL, Apple QuickDraw, GLX,
• systemu operacyjnego poprzez interfejs programowania aplikacji (ang. Application Programming Interface,
API):
• WinAPI (GDI lub DirectX),
• QuickDraw z Mac OS X,
• Linux Kernel API.
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.
Przekroczenie zakresu liczb całkowitych.
Dotarcie do końca funkcji zwracającej wartość, bez napotkania na wyrażenie return.
Odczytanie zmiennej przed zapisaniem do niej wartości.
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łoby ono zostać dobrze zdefiniowane i nie zawiera żadnego mechanizmu obsługi błędów w
czasie wykonywania programu.
9
C (język programowania)
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.
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 ograniczonych typów referencji.
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
macierzy. 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.
10
C (język programowania)
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 infiksowych 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.)
Oszczędność wyrażenia
Jedną z krytykowanych cech języka C jest możliwość tworzenia zwięzłych ponad miarę 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 znaku wskazywanego przez t (oryginalnie ustawionego na pierwszy znak stringa do skopiowania) do
odpowiadającej pozycji wskazywanej przez s (oryginalnie ustawionego na pierwszy znak 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).
• Dopó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ż
11
C (język programowania)
skopiowana.)
• Ciągle powtarzające się ciało pętli while 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 niekoniecznie 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.
Osobliwości języka 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 „[]”, 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:
float x;
x = (2,5);
12
C (język programowania)
Zastanawiają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;
Przypisy
[1] The Development of the C Language (http:/ / cm. bell-labs. com/ cm/ cs/ who/ dmr/ chist. html)
Bibliografia
• Brian W. Kernighan, Dennis M. Ritchie: Język C, Wydawnictwa Naukowo-Techniczne, Warszawa 1988, ISBN
83-204-1067-3
Linki zewnętrzne
• Krzysztof Diks, Wstęp do programowania w języku C (http://wazniak.mimuw.edu.pl/index.
php?title=Wstęp_do_programowania_w_języku_C) (materiały dydaktyczne MIMUW na studia
informatyczne)
• Mike Banahan, Declan Brady, Mark Doran, The C Book, Second Edition (http://publications.gbdirect.co.uk/
c_book/) (ang.), Addison-Wesley 1991, ISBN 0-201-54433-4, wersja online.
• Kurs programowania w języku C na Wikibooks (http://pl.wikibooks.org/wiki/C)
13
Źródła i autorzy artykułu
Źródła i autorzy artykułu
C (język programowania) Źródło: http://pl.wikipedia.org/w/index.php?oldid=37318569 Autorzy: A., Adam majewski, Adzinok, Alikasundara, Arin, Bartholomaeus, Beau, Bercik, Borkowsk,
BoryslawBobulski, Boud, Derbeth, Diabelb, Dlvoy, Dodek, E2rd, Ejdzej, Enkidu666, Exe, Faxe, Ghazer, Googl, Harry Xin, Harry lp, Jdx, Joee, Julo, Kaczor, Kaszkawal, Kbsc, Kggucwa, Kj,
Kocio, Kpjas, Krzysztofj, Kuszi, Lam, Lampak, Leopold, LukKot, Lukas3, Lzur, MK wars, Maikking, Marcin Robert, Marioosz, Matekm, Matusz, Mciura, Mikron, Mulat, Nefenit, Nominativus,
Nova, Nux, Olaf, PG, PMG, Paweł Ł Zawada, Pawhox, Pchela, Piotr Parda, PracownikFizyczny, Raq0, Robsuper, Rofro, Roo72, Royas, Saper, Sappy, Sebek95, Sidevar, Siedlaro, Skrzeczu,
Smyru, Sonji, Stepa, Stimoroll, Sunridin, Tdc6502, ToSter, Topory, Trosmisiek, Tsca, Turkusowy smok, Vaxquis, Vindicator, Voslak, WarX, Webprog, Wiktoryn, Wojciech mula, Xywek,
Youandme, Zero, Zjem ci chleb, conversion script, Żbiczek, 107 anonimowych edycji
Źródła, licencje i autorzy grafik
Plik:For-loop-diagram.png Źródło: http://pl.wikipedia.org/w/index.php?title=Plik:For-loop-diagram.png Licencja: Creative Commons Attribution-Sharealike 2.5 Autorzy: Bináris,
CountingPine, Faxe, Leyo, Ma-Lik, Mdd, 1 anonimowych edycji
Licencja
Creative Commons Attribution-Share Alike 3.0
//creativecommons.org/licenses/by-sa/3.0/
14

Podobne dokumenty