ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU...

216
Programowanie w C Stworzone na Wikibooks, bibliotece wolny podręczników.

Transcript of ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU...

Page 1: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ

Programowanie w C

Stworzone na Wikibooksbibliotece wolny podręcznikoacutew

Wersja z dnia listopada Copyrightcopy - użytkownicy Wikibooks

Udziela się zezwolenia do kopiowania rozpowszechniania lub modyfikacji tego dokumentuzgodnie z zasadami Licencji Creative Commons Uznanie autorstwa-Na ty samy warunka Unported lub dowolnej poacuteźniejszej wersji licencji opublikowanej przez Creative Com-mons ktoacutera zawiera te same elementy co niniejsza licencja

Tekst licencji można znaleźć na stronie httpcreativecommonsorglicensesby-sa30deedpl

Wikibooks nie udziela żadnych gwarancji zapewnień ani obietnic dotyczących poprawnościpublikowanych treści Nie udziela też żadnych innych gwarancji zaroacutewno jednoznacznychjak i dorozumianych

Spis tresci

O podręczniku O czym moacutewi ten podręcznik 11 Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika 11 Konwencje przyjęte w tym podręczniku 11 Czy mogę pomoacutec 12 Autorzy 12 Źroacutedła 12

O języku C Historia C 13 Zastosowania języka C 15 Przyszłość C 15

Czego potrzebujesz Czego potrzebujesz 17 Zintegrowane Środowiska Programistyczne 18 Dodatkowe narzędzia 18

Używanie kompilatora GCC 19 Borland 20 Czytanie komunikatoacutew o błędach 20

Pierwszy program Twoacutej pierwszy program 23 Rozwiązywanie problemoacutew 24

Podstawy Kompilacja Jak działa C 27 Co może C 27 Struktura blokowa 28 Zasięg 28 Funkcje 29 Biblioteki standardowe 29 Komentarze i styl 30 Preprocesor 32 Nazwy zmiennych stałych i funkcji 32

3

Zmienne Czym są zmienne 33 Typy zmiennych 36 Specyfikatory 39 Modyfikatory 40 Uwagi 41

Operatory Przypisanie 43 Rzutowanie 44 Operatory arytmetyczne 45 Operacje bitowe 46 Poroacutewnanie 48 Operatory logiczne 50 Operator wyrażenia warunkowego 51 Operator przecinek 51 Operator sizeof 51 Inne operatory 52 Priorytety i kolejność obliczeń 52 Kolejność wyliczania argumentoacutew operatora 53 Uwagi 54 Zobacz też 55

Instrukcje sterujące Instrukcje warunkowe 57 Pętle 60 Instrukcja goto 65 Natychmiastowe kończenie programu mdash funkcja exit 65 Uwagi 66

Podstawowe procedury wejścia i wyjścia Wejściewyjście 67 Funkcje wyjścia 68 Funkcja puts 69 Funkcja fputs 70 Funkcje wejścia 71

Funkcje Tworzenie funkcji 78 Wywoływanie 79 Zwracanie wartości 80 Zwracana wartość 81 Funkcja main() 81 Dalsze informacje 83 Zobacz też 87

Preprocesor Wstęp 89 Dyrektywy preprocesora 89 Predefiniowane makra 95

4

Biblioteka standardowa Czym jest biblioteka 97 Po co nam biblioteka standardowa 97 Gdzie są funkcje z biblioteki standardowej 97 Opis funkcji biblioteki standardowej 98 Uwagi 98

Czytanie i pisanie do plikoacutew Pojęcie pliku 99 Identyfikacja pliku 99 Podstawowa obsługa plikoacutew 99 Rozmiar pliku 102 Przykład mdash pliki graficzny 103 Co z katalogami 104

Ćwiczenia dla początkujący Ćwiczenia 105

Tablice Wstęp 107 Odczytzapis wartości do tablicy 109 Tablice znakoacutew 109 Tablice wielowymiarowe 110 Ograniczenia tablic 110 Ciekawostki 111

Wskaźniki Co to jest wskaźnik 113 Operowanie na wskaźnikach 114 Arytmetyka wskaźnikoacutew 117 Tablice a wskaźniki 118 Gdy argument jest wskaźnikiem 119 Pułapki wskaźnikoacutew 120 Na co wskazuje 120 Stałe wskaźniki 121 Dynamiczna alokacja pamięci 122 Wskaźniki na funkcje 125 Możliwe deklaracje wskaźnikoacutew 127 Popularne błędy 127 Ciekawostki 128

Napisy Łańcuchy znakoacutew w języku C 129 Operacje na łańcuchach 132 Bezpieczeństwo kodu a łańcuchy 134 Konwersje 137 Operacje na znakach 137 Częste błędy 137 Unicode 138

5

Typy złożone typedef 141 Typ wyliczeniowy 141 Struktury 142 Unie 142 Inicjalizacja struktur i unii 144 Wspoacutelne własności typoacutew wyliczeniowych unii i struktur 144 Studium przypadku mdash implementacja listy wskaźnikowej 146

Biblioteki Czym jest biblioteka 151 Jak zbudowana jest biblioteka 151

Więcej o kompilowaniu Ciekawe opcje kompilatora 155 Program make 155 Optymalizacje 157 Kompilacja krzyżowa 159 Inne narzędzia 159

Zaawansowane operacje matematyczne Biblioteka matematyczna 161 Prezentacja liczb rzeczywistych w pamięci komputera 162 Liczby zespolone 163

Powszene praktyki Konstruktory i destruktory 165 Zerowanie zwolnionych wskaźnikoacutew 166 Konwencje pisania makr 166 Jak dostać się do konkretnego bitu 167 Skroacutety notacji 168

Przenośność programoacutew Niezdefiniowane zachowanie i zachowanie zależne od implementacji 171 Rozmiar zmiennych 172 Porządek bajtoacutew i bitoacutew 172 Biblioteczne problemy 175 Kompilacja warunkowa 175

Łączenie z innymi językami Język C i Asembler 177 C++ 180

A Indeks alfabetyczny

B Indeks tematyczny B asserth 183B ctypeh 183B errnoh 183B floath 183B limitsh 183

6

B localeh 184B mathh 184B setjmph 185B signalh 185B stdargh 185B stddefh 185B stdioh 185B stdlibh 186B stringh 186B timeh 186

C Wybrane funkcje biblioteki standardowej C assert 189C atoi 190C isalnum 191C malloc 193C printf 195C scanf 199

D Składnia D Symbole i słowa kluczowe 203D Polskie znaki 205D Operatory 205D Typy danych 207

E Przykłady z komentarzem

F Informacje o pliku F Historia 213F Informacje o pliku i historia 213F Autorzy 213F Grafiki 213

Skorowidz

7

8

Spis tablic

Priorytety operatoroacutew 53

D Symbole i słowa kluczowe 204D Typy danych według roacuteżnych specyfikacji języka C 207

9

Rozdział 1

O podręczniku

11 O czym moacutewi ten podręcznikNiniejszy podręcznik stanowi przewodnik dla początkujących programistoacutew po języku pro-gramowania C

12 Co trzeba wiedzieć żeby skorzystać z niniejszego pod-ręcznika

Ten podręcznik ma nauczyć programowania w C od podstaw do poziomu zaawansowanegoDo zrozumienia rozdziału dla początkujących wymagana jest jedynie znajomość podstawo-wych pojęć z zakresu algebry oraz terminoacutew komputerowych Doświadczenie w programo-waniu w innych językach bardzo pomaga ale nie jest konieczne

13 Konwencje przyjęte w tym podręcznikuInformacje ważne oznaczamy w następujący sposoacuteb

Ważna informacja

Dodatkowe informacje ktoacutere odrobinę wykraczają poza zakres podręcznika a także wy-jaśniają kwestie niezwiązane bezpośrednio z językiem C oznaczamy tak

Wyjaśnienie

Ponadto kod w języku C będzie prezentowany w następujący sposoacuteb

include ltstdiohgt

int main (int argc char argv[])

return 0

11

12 ROZDZIAŁ 1 O PODRĘCZNIKU

Innego rodzaju przykłady dialog użytkownika z konsolą i programem wejście wyjścieprogramu informacje teoretyczne będą wyglądały tak

typ zmienna = wartość

14 Czy mogę pomoacutecOczywiście że możesz Mało tego będziemy zadowoleni z każdej pomocy ndash możesz pisaćrozdziały lub tłumaczyć je z angielskiej wersji tego podręcznika Nie musisz pytać się nikogoo zgodę mdash jeśli chcesz możesz zacząć już teraz Prosimy jedynie o zapoznanie się ze stylempodręcznika użytymi w nim szablonami i zachowanie układu rozdziałoacutew Propozycje zmianyspisu treści należy zgłaszać na stronie dyskusji

Jeśli znalazłeś jakiś błąd a nie umiesz go poprawić koniecznie powiadom o tym fakcieautoroacutew tego podręcznika za pomocą strony dyskusji danego modułu książki Dzięki temuprzyczyniasz się do rozwoju tego podręcznika

15 AutorzyIstotny wkład w powstanie podręcznika mają

CzarnyZajaczek

Derbeth

Kj

mina

Dodatkowo w rozwoju podręcznika pomagali między innymi

Lrds

Noisy

16 Źroacutedła podręcznik C Programming na anglojęzycznej wersji Wikibooks licencja GFDL

Brian W Kernighan Dennis M Ritchie Język ANSI C

ISO C Commiee Dra stycznia

Bruce Eckel inking in C++ Rozdział Język C w programie C++

Rozdział 2

O języku C

Zobacz w Wikipedii C (ję-zyk programowania)C jest językiem programowania wysokiego poziomu Jego nazwę interpretuje się jako na-

stępną literę po B (nazwa jego poprzednika) lub drugą literę języka BCPL (poprzednik językaB)

21 Historia CW roku trzej naukowcy z Bell Telephone Laboratories mdashWilliam Shockley Walter Brat-tain i John Bardeen mdash stworzyli pierwszy tranzystor w roku w MIT skonstruowanopierwszy komputer oparty wyłącznie na tranzystorach TX-O w roku Jack Kilby z Te-xas Instruments skonstruował układ scalony Ale zanim powstał pierwszy układ scalonypierwszy język wysokiego poziomu został już napisany

W powstał Fortran (Formula Translator) ktoacutery zapoczątkował napisanie języka For-tran I () Poacuteźniej powstały kolejno

Algol mdash Algorithmic Language w r

Algol ()

CPL mdash Combined Programming Language ()

BCPL mdash Basic CPL ()

B ()

i C w oparciu o BB został stworzony przez Kena ompsona z Bell Labs był to język interpretowany uży-

wany we wczesnych wewnętrznych wersjach systemu operacyjnego UNIX Inni pracownicyBell Labs ompson i Dennis Richie rozwinęli B nazywając go NB dalszy rozwoacutej NB dał Cmdash język kompilowany Większa część UNIX-a została ponownie napisana w NB a następniew C co dało w efekcie bardziej przenośny system operacyjny W roku wydana zostałaksiążka pt ldquoe C Programming Languagerdquo ktoacutera stała się pierwszym podręcznikiem donauki języka C

Możliwość uruchamiania UNIX-a na roacuteżnych komputerach była głoacutewną przyczyną po-czątkowej popularności zaroacutewno UNIX-a jak i C zamiast tworzyć nowy system operacyjnyprogramiści mogli po prostu napisać tylko te części systemu ktoacuterych wymagał inny sprzętoraz napisać kompilator C dla nowego systemu Odkąd większa część narzędzi systemowychbyła napisana w C logiczne było pisanie kolejnych w tym samym języku

13

14 ROZDZIAŁ 2 O JĘZYKU C

Kilka z obecnie powszechnie stosowanych systemoacutew operacyjnych takich jak Linux Mi-croso Windows zostały napisane w języku C

211 Standaryzacje

W roku Ritchie i Kerninghan opublikowali pierwszą książkę nt języka C mdash ldquoe CProgramming Languagerdquo Owa książka przez wiele lat była swoistym ldquowyznacznikiemrdquo jakprogramować w języku C Była więc to niejako pierwsza standaryzacja nazywana od na-zwisk twoacutercoacutew ldquoKampRrdquo Oto nowości wprowadzone przez nią do języka C w stosunku dojego pierwszych wersji (pochodzących z początku lat )

możliwość tworzenia struktur (słowo struct)

dłuższe typy danych (modyfikator long)

liczby całkowite bez znaku (modyfikator unsigned)

zmieniono operator ldquo=+rdquo na ldquo+=rdquo

Ponadto producenci kompilatoroacutew (zwłaszcza ATampT) wprowadzali swoje zmiany nieob-jęte standardem

funkcje nie zwracające wartości (void) oraz typ void

funkcje zwracające struktury i unie

przypisywanie wartości strukturom

wprowadzenie słowa kluczowego const

utworzenie biblioteki standardowej

wprowadzenie słowa kluczowego enum

Owe nieoficjalne rozszerzenia zagroziły spoacutejności języka dlatego też powstał standardregulujący wprowadzone nowinki Od roku trwały prace standaryzacyjne aby w roku wydać standard C (poprawna nazwa to ANSI X-) Niektoacutere zmiany wpro-wadzono z języka C++ jednak rewolucję miał dopiero przynieść standard C ktoacutery wpro-wadził min

funkcje inline

nowe typy danych (np long long int)

nowy sposoacuteb komentowania zapożyczony od C++ ()

przechowywanie liczb zmiennoprzecinkowych zostało zaadaptowane do norm IEEE

utworzono kilka nowych plikoacutew nagłoacutewkowych (stdboolh inypesh)

Na dzień dzisiejszy normą obowiązującą jest norma C

22 ZASTOSOWANIA JĘZYKA C 15

22 Zastosowania języka CJęzyk C został opracowany jako strukturalny język programowania do celoacutew ogoacutelnych Przezcałą swą historię (czyli ponad lat) służył do tworzenia przeroacuteżnych programoacutewmdash od syste-moacutew operacyjnych po programy nadzorujące pracę urządzeń przemysłowych C jako językdużo szybszy od językoacutew interpretowanych (Perl Python) oraz uruchamianych w maszy-nach wirtualnych (np C Java) może bez problemu wykonywać złożone operacje nawetwtedy gdy nałożone są dość duże limity czasu wykonywania pewnych operacji Jest on przytym bardzo przenośny mdash może działać praktycznie na każdej architekturze sprzętowej podwarunkiem opracowania odpowiedniego kompilatora Często wykorzystywany jest takżedo oprogramowywania mikrokontroleroacutew i systemoacutew wbudowanych Jednak w niektoacuterychsytuacjach język C okazuje się być mało przydatny zwłaszcza chodzi tu o obliczenia mate-matyczne wymagające dużej precyzji (w tej dziedzinie znakomicie spisuje się Fortran) lubteż dużej optymalizacji dla danego sprzętu (wtedy niezastąpiony jest język asemblera)

Kolejną zaletą C jest jego dostępność mdash właściwie każdy system typu UNIX posiada kom-pilator C w C pisane są funkcje systemowe

Problemem w przypadku C jest zarządzanie pamięcią ktoacutere nie wybacza programiściebłędoacutew niewygodne operowanie napisami i niestety pewna liczba ldquokruczkoacutewrdquo ktoacutere mogązaskakiwać nowicjuszy Na tle młodszych językoacutew programowania C jest językiem dosyćniskiego poziomu więc wiele rzeczy trzeba w nim robić ręcznie jednak zarazem umożliwia torobienie rzeczy nieprzewidzianych w samym języku (np implementację liczb bitowych)a także łatwe łączenie C z Asemblerem

23 Przyszłość CPomimo sędziwego już wieku (C ma ponad lat) nadal jest on jednym z najczęściej stosowa-nych językoacutew programowania Doczekał się już swoich następcoacutew z ktoacuterymi w niektoacuterychdziedzinach nadal udaje mu się wygrywać Widać zatem że pomimo pozornej prostoty iniewielkich możliwości język C nadal spełnia stawiane przed nim wymagania Warto zatemuczyć się języka C gdyż nadal jest on wykorzystywany (i nic nie wskazuje na to by miałosię to zmienić) a wiedza ktoacuterą zdobędziesz ucząc się C na pewno się nie zmarnuje Skład-nia języka C pomimo że przez wielu uważana za nieczytelną stała się podstawą dla takichjęzykoacutew jak C++ C czy też Java

16 ROZDZIAŁ 2 O JĘZYKU C

Rozdział 3

Czego potrzebujesz

31 Czego potrzebujeszWbrew powszechnej opinii nauczenie się ktoacuteregoś z językoacutew programowania (w tym językaC) nie jest takie trudne Do nauki wystarczą Ci

komputer z dowolnym systemem operacyjnym takim jak FreeBSD Linux Windows

Język C jest bardzo przenośny więc będzie działał właściwie na każdej platformiesprzętowej i w każdym nowoczesnym systemie operacyjnym

kompilator języka C

Kompilator języka C jest programem ktoacutery tłumaczy kod źroacutedłowy napisany przeznas do języka asembler a następnie do postaci zrozumiałej dla komputera (maszynycyfrowej) czyli do postaci ciągu zer i jedynek ktoacutere sterują pracą poszczegoacutelnych ele-mentoacutew komputera Kompilator języka C można dostać za darmo Przykładem sągcc pod systemy uniksowe DJGPP pod systemy DOS MinGW oraz lcc pod systemytypu Windows Jako kompilator C może dobrze służyć kompilator języka C++ (roacuteżnicemiędzy tymi językami przy pisaniu prostych programoacutew są nieistotne) Spokojnie mo-żesz więc użyć na przykład Microso Visual C++reg lub kompilatoroacutew firmy BorlandJeśli lubisz eksperymentować wyproacutebuj Tiny C Compiler bardzo szybki kompilatoro ciekawych funkcjach Możesz ponadto wyproacutebować interpreter języka C Więcejinformacji na Wikipedii

Linker (często jest razem z kompilatorem)

Linker jest to program ktoacutery uruchamiany jest po etapie kompilacji jednego lub kilkuplikoacutew źroacutedłowych (pliki z rozszerzeniem c cpp lub innym) skompilowanych do-wolnym kompilatorem Taki program łączy wszystkie nasze skompilowane pliki źroacute-dłowe i inne funkcje (np printf scan) ktoacutere były użyte (dołączone do naszego pro-gramu poprzez użycie dyrektywy include) w naszym programie a nie były zdefinio-wane(napisane przez nas) w naszych plikach źroacutedłowych lub nagłoacutewkowych Linkerjest to czasami jeden program połączony z kompilatorem Wywoływany jest on naogoacuteł automatycznie przez kompilator w wyniku czego dostajemy gotowy program douruchomienia

Debuger (opcjonalnie według potrzeb)

17

18 ROZDZIAŁ 3 CZEGO POTRZEBUJESZ

Debugger jest to program ktoacutery umożliwia prześledzenie(określenie wartości poszcze-goacutelnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijcewykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu Używasię go w celu określenia czemu nasz program nie działa po naszej myśli lub czemu pro-gram niespodziewanie kończy działanie bez powodu Aby użyć debuggera kompilatormusi dołączyć kod źroacutedłowy do gotowego skompilowanego programu Przykładowymidebuggerami są gdb pod Linuksem lub debugger firmy Borland pod Windowsa

edytora tekstowego

Systemy uniksowe oferują wiele edytoroacutew przydatnych dla programisty jak choćbyvim i Emacs w trybie tekstowym Kate w KDE czy gedit w GNOME Windows maedytor całkowicie wystarczający do pisania programoacutew w C mdash nieśmiertelny Notatnikmdash ale z łatwością znajdziesz w Internecie wiele wygodniejszych narzędzi takich jak npNotepad++ Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE

dużo chęci i dobrej motywacji

32 Zintegrowane Środowiska ProgramistyczneZamiast osobnego kompilatora i edytora możesz wybrać Zintegrowane Środowisko Progra-mistyczne (Integrated Development Environment IDE) IDE jest zestawem wszystkich pro-gramoacutew ktoacutere potrzebuje programista najczęściej z interfejsem graficznym IDE zawierakompilator linker i edytor z reguły roacutewnież debugger

Bardzo popularny IDE to płatny (istnieje także jego darmowa wersja) Microso VisualC++ (MS VC++) popularne darmowe IDE to np

CodeBlocks dla Windows jak i Linux dostępny na stronie wwwcodeblocksorg

KDevelop (Linux) dla KDE

NetBeans multiplatformowy darmowy do ściągnięcia na stronie wwwnetbeansorg

Eclipse z wtyczką CDT (wspoacutełpracuje z MinGW i GCC)

Borland C++ Builder dostępny za darmo do użytku prywatnego

Xcode dlaMac OS X i nowszy kompatybilny z procesorami PowerPC i Intel (moż-liwość stworzenia Universal Binary)

Geany dla systemoacutewWindows i Linux wspoacutełpracuje zMinGW iGCCwwwgeanyorg

Pelles C wwwsmorgasbordetcom

Dev-C++ dla Windows dostępny na stronie wwwbloodshednet

33 Dodatkowe narzędziaWśroacuted narzędzi ktoacutere nie są niezbędne ale zasługują na uwagę można wymienić Valgrindandash specjalnego rodzaju debugger Valgrind kontroluje wykonanie programu i wykrywa nie-prawidłowe operacje w pamięci oraz wycieki pamięci Użycie Valgrinda jest proste mdash kom-pilujemy program jak do debugowania następnie podajemy jako argument Valgrindowi

Rozdział 4

Używanie kompilatora

Język C jest językiem kompilowanym co oznacza że potrzebuje specjalnego programu mdashkompilatora mdash ktoacutery tłumaczy kod źroacutedłowy pisany przez człowieka na język rozkazoacutew da-nego komputera W skroacutecie działanie kompilatora sprowadza się do czytania tekstowegopliku z kodem programu raportowania ewentualnych błędoacutew i produkowania pliku wyniko-wego

Kompilator uruchamiamy ze Zintegrowanego Środowiska Programistycznego lub z kon-soli (linii poleceń) Przejść do konsoli można dla systemoacutew typu UNIX w trybie graficz-nym użyć programoacutew gnome-terminal konsole albo xterm w Windows ldquoWiersz poleceniardquo(można go znaleźćwmenuAkcesoria albo uruchomićwpisującw Start -gtUruchom ldquocmdrdquo)

41 GCCZobacz w Wikipedii GCC

GCC jest to darmowy zestaw kompilatoroacutew min języka C rozwijany w ramach projektuGNU Dostępny jest on na dużą ilość platform sprzętowych obsługiwanych przez takie sys-temy operacyjne jak AIX BSD Linux Mac OS X SunOS Windows Na niektoacuterych sys-temach (np Windows) nie jest on jednak dostępny automatycznie Należy zainstalowaćodpowiednie narzędza (poprzedni rozdział)

Aby skompilować kod języka C za pomocą kompilatora GCC napisany wcześniej w do-wolnym edytorze tekstu należy uruchomić program z odpowiednimi parametrami Podsta-wowym parametrem ktoacutery jest wymagany jest nazwa pliku zawierającego kod programuktoacutery chcemy skompilować

gcc kodc

Rezultatem kompilacji będzie plik wykonywalny z domyślną nazwą (w systemach Unixjest to ldquoaoutrdquo) Jest to metoda niezalecana ponieważ jeżeli skompilujemy w tym samymkatalogu kilka plikoacutew z kodem kolejne pliki wykonywalne zostaną nadpisane i w rezultacieotrzymamy tylko jeden (ten ostatni) skompilowany kod Aby wymusić na GCC nazwę plikuwykonywalnego musimy skorzystać z parametru ldquo-o ltnazwagtrdquo

gcc -o program kodc

W rezultacie otrzymujemy plik wykonywalny o nazwie programPracując nad złożonym programem składającym się z kilku plikoacutew źroacutedłowych (c) mo-

żemy skompilować je niezależnie od siebie tworząc tak zwane pliki typu obiekt z rozszerze-niem o (ang Object File) Następnie możemy stworzyć z nich jednolity program w procesie

19

20 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

konsolidacji (linkowaniu) Jest to bardzo wygodne i praktyczne rozwiązanie ze względu nato iż nie jesteśmy zmuszeni kompilować wszystkich plikoacutew tworzących program za każdymrazem na nowo a jedynie te w ktoacuterych wprowadziliśmy zmiany Aby skompilować plik bezlinkowania używamy parametru ldquo-c ltplikgtrdquo

gcc -o program1o -c kod1cgcc -o program2o -c kod2c

Otrzymujemy w ten sposoacuteb pliki typu obiekt programo i programo A następnie two-rzymy z nich program głoacutewny

gcc -o program program1o program2o

Możemy użyć roacutewnież flag min aby włączyć dokładne rygorystyczne sprawdzanie na-pisanego kodu (co może być przydatne jeśli chcemy dążyć do perfekcji) używamy przełącz-nikoacutew

gcc kodc -o program -Werror -Wall -W -pedantic -ansi

Więcej informacji na temat parametroacutew i działania kompilatora GCC można znaleźć na

Strona domowa projektu GNU GCC

Kroacutetki przekrojowy opis GCC po polsku

Strona podręcznika systemu UNIX (man)

42 BorlandZobacz podręcznik Borland C++ Compiler

43 Czytanie komunikatoacutew o błędaJedną z najbardziej podstawowych umiejętności ktoacutere musi posiąść początkujący progra-mista jest umiejętność rozumienia komunikatoacutew o roacuteżnego rodzaju błędach ktoacutere sygnali-zuje kompilator Wszystkie te informacje pomogą Ci szybko wychwycić ewentualne błędy(ktoacuterych na początku zawsze jest bardzo dużo) Nie martw się że na początku dość częstobędziesz oglądał wydruki błędoacutew zasygnalizowanych przez kompilator mdash nawet zaawanso-wanym programistom się to zdarza Kompilator ma za zadanie pomoacutec Ci w szybkiej popra-wie ewentualnych błędoacutew dlatego też umiejętność analizy komunikatoacutew o błędach jest takważna

431 GCC

Kompilator jest w stanie wychwycić błędy składniowe ktoacutere z pewnością będziesz popełniałKompilator GCC wyświetla je w następującej formie

nazwa_plikucnumer_linijkiopis błędu

Kompilator dość często podaje także nazwę funkcji w ktoacuterej wystąpił błąd Przykładowobłąd deklaracji zmiennej w pliku testc

43 CZYTANIE KOMUNIKATOacuteW O BŁĘDACH 21

include ltstdiohgt

int main ()

intr rprintf (dn r)

Spowoduje wygenerowanie następującego komunikatu o błędzie

testc In function lsquomainrsquotestc5 error lsquointrrsquo undeclared (first use in this function)testc5 error (Each undeclared identifier is reported only oncetestc5 error for each function it appears in)testc5 error syntax error before lsquorrsquotestc6 error lsquorrsquo undeclared (first use in this function)

Co widzimy w raporcie o błędach W linii użyliśmy nieznanego (undeclared) identy-fikatora intr mdash kompilator moacutewi że nie zna tego identyfikatora jest to pierwsze użycie wdanej funkcji i że więcej nie ostrzeże o użyciu tego identyfykatora w tej funkcji Ponieważintr nie został rozpoznany jako żaden znany typ linijka intr r nie została rozpoznana jakodeklaracja zmiennej i kompilator zgłasza błąd składniowy (syntax error) W konsekwencji rnie zostało rozpoznane jako zmienna i kompilator zgłosi to jeszcze w następnej linijce gdzieużywamy r

22 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

Rozdział 5

Pierwszy program

51 Twoacutej pierwszy program

Przyjęło się że pierwszy program napisany w dowolnym języku programowania powinienwyświetlić tekst ldquoHello Worldrdquo (Witaj Świecie) Zauważ że sam język C nie ma żadnychmechanizmoacutew przeznaczonych do wprowadzania i wypisywania danych mdash musimy zatemskorzystać z odpowiadających za to funkcji mdash w tym przypadku printf zawartej w standar-dowej bibliotece C (ang C Standard Library) (podobnie jak w Pascalu używa się do tegoprocedur Pascalowskim odpowiednikiem funkcji printf są procedury writewriteln)

W języku C deklaracje funkcji zawarte są w plika nagłoacutewkowy posiadających naj-częściej rozszerzenie h choć można także spotkać rozszerzenie hpp przy czym to drugiezwykło się stosować w języku C++ (rozszerzenie nie ma swych ldquotechnicznychrdquo korzeni mdash jestto tylko pewna konwencja) W celu umieszczenia w swoim kodzie pewnego pliku nagłoacutewko-wego używamy dyrektywy kompilacyjnej include Przed procesem kompilacji w miejscetej dyrektywy wstawiana jest treść podanego pliku nagłoacutewkowego dostarczając deklaracjifunkcji

Poniższy przykład obrazuje jak przy użyciu dyrektywy include umieścimyw kodzie plikstandardowej biblioteki C stdioh (Standard InputOutputHeaderfile) zawierającą definicjęfunkcji printf

include ltstdiohgt

W nawiasach troacutejkątnych lt gt umieszcza się nazwy standardowych plikoacutew nagłoacutewko-wych1 Żeby włączyć inny plik nagłoacutewkowy (np własny) znajdujący się w katalogu z kodemprogramu trzeba go wpisać w cudzysłoacutew

include moacutej_plik_nagłoacutewkowyh

Mamy więc funkcję printf jak i wiele innych do wprowadzania i wypisywania danychczas na pisanie programu

W programie definujemy głoacutewną funkcję main uruchamianą przy starcie programu za-wierającą właściwy kod Definicja funkcji zawiera oproacutecz nazwy i kodu także typ wartościzwracanej i argumentoacutew pobieranych Konstrukcja funkcji main

1Domyślne pliki nagłoacutewkowe znajdują się w katalogu z plikami nagłoacutewkowymi kompilatora W systemach zrodziny Unix będzie to katalog usrinclude natomiast w systemie Windows oacutew katalog będzie umieszczony wkatalogu z kompilatorem

23

24 ROZDZIAŁ 5 PIERWSZY PROGRAM

int main (void)

Typem zwracany przez funkcję jest int (Integer) czyli liczba całkowita (w przypadkumainbędzie to kod wyjściowy programu) W nawiasach umieszczane są argumenty funkcji tutajzapis void oznacza ich pominięcie Funkcja main jako argumenty może pobierać parametrylinii poleceń z jakimi program został uruchomiony i pełną ścieżkę do katalogu z programem

Kod funkcji umieszcza się w nawiasach klamrowych i Wewnątrz funkcji możemy wpisać poniższy kod

printf(Hello World)return 0

Wszystkie polecenia kończymy średnikiem return określa wartość jaką zwroacuteci funkcja(program) Liczba zero zwracana przez funkcję main() oznacza że program zakończył siębez błędoacutew błędne zakończenie często (choć nie zawsze) określane jest przez liczbę jeden2Funkcję main kończymy nawiasem klamrowym zamykającym

Twoacutej kod powinien wyglądać jak poniżej

include ltstdiohgtint main (void)

printf (Hello World)return 0

Teraz wystarczy go tylko skompilować i uruchomić

52 Rozwiązywanie problemoacutewJeśli nie możesz skompilować powyższego programu to najprawdopodobniej popełniłeś li-teroacutewkę przy ręcznym przepisywaniu go Zobacz też instrukcje na temat używania kompi-latora

Może też się zdarzyć że program skompiluje się uruchomi ale jego efektu działania niebędzie widać Dzieje się tak ponieważ nasz pierwszy program po prostu wypisuje komunikati od razu kończy działanie nie czekając na reakcję użytkownika Nie jest to problememgdy program jest uruchamiany z konsoli tekstowej ale w innych przypadkach nie widzimyefektoacutew jego działania

Jeśli korzystasz ze Zintegrowanego Środowiska Programistycznego (ang IDE) możeszzaznaczyć by nie zamykało ono programu po zakończeniu jego działania Innym sposobemjest dodanie instrukcji ktoacutere wstrzymywałyby zakończenie programu Można to zrobić do-dając przed linią z return funkcję pobierającą znak z wejścia

getchar()

2Jeżeli chcesz mieć pewność że twoacutej program będzie działał poprawnie roacutewnież na platformach gdzie 1 oznaczapoprawne zakończenie (lub nie oznacza nic) możesz skorzystać z makr EXIT SUCCESS i EXIT FAILURE zdefiniowanychw pliku nagłoacutewkowym stdlibh

52 ROZWIĄZYWANIE PROBLEMOacuteW 25

Jest też prostszy (choć nieprzenośny) sposoacuteb mianowicie wywołanie polecenia systemo-wego W zależności od używanego systemu operacyjnego mamy do dyspozycji roacuteżne po-lecenia powodujące roacuteżne efekty Do tego celu skorzystamy z funkcji system() ktoacutera jakoparametr przyjmuje polecenie systemowe ktoacutere chcemy wykonać np

Rodzina systemoacutew UnixLinux

system(sleep 10) oczekiwanie 10 s system(read discard) oczekiwanie na wpisanie tekstu

Rodzina systemoacutew oraz MS Windows

system(pause) oczekiwanie na wciśnięcie dowolnego klawisza

Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnychWindows w ktoacuterychto z reguły pracuje się w trybie okienkowym a z konsoli korzystamy tylko podczas urucha-mianiu programu Z kolei w systemach UnixLinux jest ona praktycznie w ogoacutele nie używanaw tym celu ze względu na uruchamianie programu bezpośrednio z konsoli

26 ROZDZIAŁ 5 PIERWSZY PROGRAM

Rozdział 6

Podstawy

Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogoacutel-nych informacji

61 Kompilacja Jak działa C

Jak każdy język programowania C sam w sobie jest niezrozumiały dla procesora Został onstworzony w celu umożliwienia ludziom łatwego pisania kodu ktoacutery może zostać przetwo-rzony na kod maszynowy Program ktoacutery zamienia kod C na wykonywalny kod binarnyto kompilator Jeśli pracujesz nad projektem ktoacutery wymaga kilku plikoacutew kodu źroacutedłowego(np pliki nagłoacutewkowe) wtedy jest uruchamiany kolejny program mdash linker Linker służy dopołączenia roacuteżnych plikoacutew i stworzenia jednej aplikacji lub biblioteki (library) Bibliotekajest zestawem procedur ktoacutery sam w sobie nie jest wykonywalny ale może być używanaprzez inne programy Kompilacja i łączenie plikoacutew są ze sobą bardzo ściśle powiązane stądsą przez wielu traktowane jako jeden proces Jedną rzecz warto sobie uświadomić mdash kompila-cja jest jednokierunkowa przekształcenie kodu źroacutedłowego C w kod maszynowy jest bardzoproste natomiast odwrotnie mdash nie Dekompilatory co prawda istnieją ale rzadko tworząużyteczny kod C

Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collec-tion dostępny na stronie gccgnuorg

62 Co może C

Pewnie zaskoczy Cię to że tak naprawdę ldquoczystyrdquo język C nie może zbyt wiele Język Cw grupie językoacutew programowania wysokiego poziomu jest stosunkowo nisko Dzięki temukod napisany w języku C można dość łatwo przetłumaczyć na kod asemblera Bardzo łatwojest też łączyć ze sobą kod napisany w języku asemblera z kodem napisanym w C Dla bar-dzo wielu ludzi przeszkodą jest także dość duża liczba i częsta dwuznaczność operatoroacutewPoczątkujący programista czytający kod programu w C może odnieść bardzo nieprzyjemnewrażenie ktoacutere można opisać cytatem ldquoja nigdy tego nie opanujęrdquo Wszystkie te elementyjęzyka C ktoacutere wydają Ci się dziwne i nielogiczne wmiarę jak będziesz nabierał doświadcze-nia nagle okażą się całkiem przemyślanie dobrane i takie a nie inne konstrukcje przypadnąCi do gustu Dalsza lektura tego podręcznika oraz zaznajamianie się z funkcjami z roacuteżnychbibliotek ukażą Ci całą gamę możliwości ktoacutere daje język C doświadczonemu programiście

27

28 ROZDZIAŁ 6 PODSTAWY

63 Struktura blokowaTeraz omoacutewimy podstawową strukturę programu napisanego w C Jeśli miałeś styczność zjęzykiem Pascal to pewnie słyszałeś o nim że jest to język programowania strukturalny WC nie ma tak ścisłej struktury blokowej mimo to jest bardzo ważne zrozumienie co oznaczastruktura blokowa Blok jest grupą instrukcji połączonych w ten sposoacuteb że są traktowanejak jedna całość W C blok zawiera się pomiędzy nawiasami klamrowymi Blok możetakże zawierać kolejne bloki

Zawartość bloku Generalnie blok zawiera ciąg kolejno wykonywanych poleceń Polece-nia zawsze (z nielicznymi wyjątkami) kończą się średnikiem () W jednej linii może znajdo-wać się wiele poleceń choć dla zwiększenia czytelności kodu najczęściej pisze się pojedyncząinstrukcję w każdej linii Jest kilka rodzajoacutew poleceń np instrukcje przypisania warunkoweczy pętli W dużej części tego podręcznika będziemy zajmować się właśnie instrukcjami

Pomiędzy poleceniami są roacutewnież odstępymdash spacje tabulacje oraz przejścia do następnejlinii przy czym dla kompilatora te trzy rodzaje odstępoacutew mają takie samo znaczenie Dlaprzykładu poniższe trzy fragmenty kodu źroacutedłowego dla kompilatora są takie same

printf(Hello world) return 0

printf(Hello world)return 0

printf(Hello world)

return 0

W tej regule istnieje jednak jeden wyjątek Dotyczy on stałych tekstowych W powyż-szych przykładach stałą tekstową jest ldquoHello worldrdquo Gdy jednak rozbijemy ten napis kom-pilator zasygnalizuje błąd

printf(Helloworld)return 0

Należy tylko zapamiętać że stałe tekstowe powinny zaczynać się i kończyć w tej samejlini (można ominąć to ograniczenie mdash więcej w rozdziale Napisy) Oproacutecz tego jednego przy-padku dla kompilatora ma znaczenie samo istnienie odstępu a nie jego wielkość czy rodzajJednak stosowanie odstępoacutew jest bardzo ważne dla zwiększenia czytelności kodu mdash dziękiczemu możemy zaoszczędzić sporo czasu i nerwoacutew ponieważ znalezienie błędu (ktoacutere sięzdarzają każdemu) w nieczytelnym kodzie może być bardzo trudne

64 ZasięgPojęcie to dotyczy zmiennych (ktoacutere przechowują dane przetwarzane przez program) Wkaż-dym programie (oproacutecz tych najprostszych) są zaroacutewno zmienne wykorzystywane przez całyczas działania programu oraz takie ktoacutere są używane przez pojedynczy blok programu (npfunkcję) Na przykład w pewnym programie w pewnym momencie jest wykonywane skom-plikowane obliczenie ktoacutere wymaga zadeklarowania wielu zmiennych do przechowywaniapośrednich wynikoacutew Ale przez większą część tego działania te zmienne są niepotrzebne

65 FUNKCJE 29

i zajmują tylko miejsce w pamięci mdash najlepiej gdyby to miejsce zostało zarezerwowane tużprzed wykonaniemwspomnianych obliczeń a zaraz po ich wykonaniu zwolnione Dlatego wC istnieją zmienne globalne oraz lokalne Zmienne globalne mogą być używane w każdymmiejscu programu natomiast lokalne mdash tylko w określonym bloku czy funkcji (oraz blokachw nim zawartych) Generalnie mdash zmienna zadeklarowanaw danym bloku jest dostępna tylkowewnątrz niego

65 Funkcje

Funkcje są ściśle związane ze strukturą blokową mdash funkcją jest po prostu blok instrukcjiktoacutery jest potem wywoływany w programie za pomocą pojedynczego polecenia Zazwyczajfunkcja wykonuje pewne określone zadanie np we wspomnianym programie wykonują-cym pewne skomplikowane obliczenie Każda funkcja ma swoją nazwę za pomocą ktoacuterejjest potem wywoływana w programie oraz blok wykonywanych poleceń Wiele funkcji po-biera pewne dane czyli argumenty funkcji wiele funkcji także zwraca pewną wartość pozakończeniu wykonywania Dobrym nawykiem jest dzielenie dużego programu na zestawmniejszych funkcji mdash dzięki temu będziesz moacutegł łatwiej odnaleźć błąd w programie

Jeśli chcesz użyć jakiejś funkcji to powinieneś wiedzieć

jakie zadanie wykonuje dana funkcja

rodzaj wczytywanych argumentoacutew i do czego są one potrzebne tej funkcji

rodzaj zwroacuteconych danych i co one oznaczają

W programach w języku C jedna funkcja ma szczegoacutelne znaczenie mdash jest tomain() Funk-cję tę zwaną funkcją głoacutewną musi zawierać każdy program W niej zawiera się głoacutewny kodprogramu przekazywane są do niej argumenty z ktoacuterymi wywoływany jest program (jakoparametry argc i argv) Więcej o funkcji main() dowiesz się poacuteźniej w rozdziale Funkcje

66 Biblioteki standardowe

Język C w przeciwieństwie do innych językoacutew programowania (np Fortranu czy Pascala)nie posiada absolutnie żadny słoacutew kluczowych ktoacutere odpowiedzialne by były za obsługęwejścia i wyjścia Może się to wydawać dziwne mdash język ktoacutery sam w sobie nie posiadapodstawowych funkcji musi być językiem o ograniczonym zastosowaniu Jednak brak pod-stawowych funkcji wejścia-wyjścia jest jedną z największych zalet tego języka Jego składniaopracowana jest tak by można było bardzo łatwo przełożyć ją na kod maszynowy To wła-śnie dzięki temu programy napisane w języku C są takie szybkie Pozostaje jednak pytaniemdash jak umożliwić programom komunikację z użytkownikiem

W roku kiedy zapoczątkowano prace nad standaryzacją C zdecydowano że po-winien być zestaw instrukcji identycznych w każdej implementacji C Nazwano je BibliotekąStandardową (czasemnazywaną ldquolibcrdquo) Zawiera ona podstawowe funkcje ktoacutere umożliwiająwykonywanie takich zadań jak wczytywanie i zwracanie danych modyfikowanie zmiennychłańcuchowych działania matematyczne operacje na plikach i wiele innych jednak nie za-wiera żadnych funkcji ktoacutere mogą być zależne od systemu operacyjnego czy sprzętu jakgrafika dźwięk czy obsługa sieci W programie ldquoHello Worldrdquo użyto funkcji z biblioteki stan-dardowej mdash printf ktoacutera wyświetla na ekranie sformatowany tekst

30 ROZDZIAŁ 6 PODSTAWY

67 Komentarze i stylKomentarze mdash to tekst włączony do kodu źroacutedłowego ktoacutery jest pomijany przez kompilatori służy jedynie dokumentacji W języku C komentarze zaczynają się od

a kończą

Dobre komentowanie ma duże znaczenie dla rozwijania oprogramowania nie tylko dlategoże inni będą kiedyś potrzebowali przeczytać napisany przez ciebie kod źroacutedłowy ale takżemożesz chcieć po dłuższym czasie powroacutecić do swojego programu i możesz zapomnieć doczego służy dany blok kodu albo dlaczego akurat użyłeś tego polecenia a nie innego Wchwili pisania programu to może być dla ciebie oczywiste ale po dłuższym czasie możeszmieć problemy ze zrozumieniem własnego kodu Jednak nie należy też wstawiać zbyt dużokomentarzy ponieważ wtedy kod może stać się jeszcze mniej czytelny mdash najlepiej komen-tować fragmenty ktoacutere nie są oczywiste dla programisty oraz te o szczegoacutelnym znaczeniuAle tego nauczysz się już w praktyce

Dobry styl pisania kodu jest o tyle ważny że powinien on być czytelny i zrozumiały po tow końcu wymyślono języki programowania wysokiego poziomu (w tym C) aby kod było ła-two zrozumieć ) I tak mdash należy stosować wcięcia dla odroacuteżnienia blokoacutew kolejnego poziomu(zawartych w innym bloku podrzędnych) nawiasy klamrowe otwierające i zamykające blokpowinny mieć takie same wcięcia staraj się aby nazwy funkcji i zmiennych kojarzyły się zzadaniem jakie dana funkcja czy zmienna pełni w programie W dalszej części podręcznikamożesz napotkać więcej zaleceń dotyczących stylu pisania kodu Staraj się stosować do tychzaleceń mdash dzięki temu kod pisanych przez ciebie programoacutew będzie łatwiejszy do czytania izrozumienia

Jeśli masz doświadczenia z językiem C++ pamiętaj że w C nie powinno się stosowaćkomentarzy zaczynających się od dwoacutech znakoacutew slash tak nie komentujemy w CJest to niezgodne ze standardem ANSI C i niektoacutere kompilatory mogą nie skompilować koduz komentarzami w stylu C++ (choć standard ISO C dopuszcza komentarze w stylu C++)

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentoacutew kodu Jeśli częśćprogramu źle działa i chcemy ją chwilowo wyłączyć albo fragment kodu jest nam już nie-potrzebny ale mamy wątpliwości czy w przyszłości nie będziemy chcieli go użyć mdash umiesz-czamy go po prostu wewnątrz komentarza

Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uważać najedną subtelność Otoacuteż komentarze w języku C nie mogą być zagnieżdżone Trzebana to uważać gdy chcemy objąć komentarzem obszar w ktoacuterym już istnieje komentarz (na-leży wtedy usunąć wewnętrzny komentarz) W nowszym standardzie C dopuszcza się abykomentarz typu zawierał w sobie komentarz

671 Po polsku czy angielsku

Jak jużwcześniej byłowspomniane zmiennym i funkcjom powinno się nadawać nazwy ktoacutereodpowiadają ich znaczeniu Zdecydowanie łatwiej jest czytać kod gdy średnią liczb przecho-wuje zmienna srednia niż a a znajdowaniemmaksimumw ciągu liczb zajmuje się funkcja maxalbo znajdz max niż nazwana f Często nazwy funkcji to właśnie czasowniki

67 KOMENTARZE I STYL 31

Powstaje pytanie w jakim języku należy pisać nazwy Jeśli chcemy by nasz kod mogłyczytać osoby nieznające polskiego mdash warto użyć języka angielskiego Jeśli nie mdash można bezproblemu użyć polskiego Bardzo istotne jest jednak by nie mieszać językoacutew Jeśli zdecy-dowaliśmy się używać polskiego używajmy go od początku do końca przeplatanie ze sobądwoacutech językoacutew robi złe wrażenie

Warto roacutewnież zdecydować się na sposoacuteb zapisywania nazw składających się z więcej niżjednego słowa Istnieje kilka możliwości najważniejsze z nich

oddzielanie podkreśleniem int to str

ldquokonwencja pascalowskardquo każde słowo dużą literą IntToStr

ldquokonwencja wielbłądziardquo pierwsze słowo małą kolejne dużą literą intToStr

Ponownie najlepiej stosować konsekwentnie jedną z konwencji i nie mieszać ze sobąkilku

672 Notacja węgierska

Czasem programista może zapomnieć jakiego typu była dana zmienna Wtedy musi znaleźćodpowiednią deklarację (co nie zawsze jest łatwe) Dlatego więc wymyślono sposoacuteb by temuzaradzić Pomyślano by w nazwie zmiennej (bądź wskaźnika na zmienną) napisać jakiegojest ona typu np

a liczba (liczba typu int)

w ll dlugaLiczba (wskaźnik na zmienną typu long long)

t5x5 ch tabliczka (tablica x elementoacutew typu char)

func i silnia (funkcja zwracająca int)

Jest to bardzo wygodne przy bardzo zagmatwanych zmiennych

w t4 w t2x2 s pomieszaniec (wskaźnik na tablicę czterech wskaźnikoacutew na tablice dwu-wymiarowe zmiennych typu short)

Lub gdy nie pamiętamy wymiaroacutew tablicy

t4x5x6 f powalonaKostkaRubika (od razu wiemy żet4x5x6 f powalonaKostkaRubika[5][4][6] jest niewłaściwe)

Taki zapis ma też swoje wady Gdy zdecydujemy się zmienić typ zmiennej zamiast poprostu przemienić w deklaracji int na long musimy zmieniać nazwy w całym programieCzęsto takie nazwy są po prostu długie i nie chce nam się ich pisać (no coacuteż programista teżczłowiek) więc wolimy wprowadzić pomieszaniec zamiast w t4 w t2x2 s pomieszaniec Naj-ważniejsze to jednak trzymać się rozwiązania ktoacutere wybraliśmy na początku bo mieszaniejest przerażające

32 ROZDZIAŁ 6 PODSTAWY

68 PreprocesorNie cały napisany przez ciebie kod będzie przekształcany przez kompilator bezpośrednio nakodwykonywalny programu Wwielu przypadkach będziesz używać poleceń ldquoskierowanychdo kompilatorardquo tzw dyrektyw kompilacyjnych Na początku procesu kompilacji specjalnypodprogram tzw preprocesor wyszukuje wszystkie dyrektywy kompilacyjne i wykonujeodpowiednie akcje mdash ktoacutere polegają notabene na edycji kodu źroacutedłowego (np wstawieniudeklaracji funkcji zamianie jednego ciągu znakoacutew na inny) Właściwy kompilator zamie-niający kod C na kod wykonywalny nie napotka już dyrektyw kompilacyjnych ponieważzostały one przez preprocesor usunięte po wykonaniu odpowiednich akcji

W C dyrektywy kompilacyjne zaczynają się od znaku hash () Przykładem najczęściejużywanej dyrektywy jest include ktoacutera jest użyta nawet w tak prostym programie jakldquoHello Worldrdquo include nakazuje preprocesorowi włączyć (ang include) w tym miejscuzawartość podanego pliku tzw pliku nagłoacutewkowego najczęściej to będzie plik zawierającyfunkcje z ktoacuterejś biblioteki standardowej (stdioh mdash STandard Input-Output rozszerzenie hoznacza plik nagłoacutewkowy C) Dzięki temu zamiast wklejać do kodu swojego programu dekla-racje kilkunastu a nawet kilkudziesięciu funkcji wystarczy wpisać jedną magiczną linijkę

69 Nazwy zmienny stały i funkcjiIdentyfikatory czyli nazwy zmiennych stałych i funkcji mogą składać się z liter (bez polskichznakoacutew) cyfr i znaku podkreślenia z tym że nazwa taka nie może zaczynać się od cyfry Niemożna używać nazw zarezerwowanych (patrz Składnia)

Przykłady błędnych nazw

2liczba (nie można zaczynać nazwy od cyfry)moja funkcja (nie można używać spacji)$i (nie można używać znaku $)if (if to słowo kluczowe)

Aby kod był bardziej czytelny przestrzegajmy poniższych (umownych) reguł

nazwy zmiennych piszemy małymi literami i file

nazwy stałych (zadeklarowanych przy pomocy define) piszemy wielkimi literamiSIZE

nazwy funkcji piszemy małymi literami print

wyrazy w nazwach oddzielamy znakiem podkreślenia open file close all files

Są to tylko konwencje mdash żaden kompilator nie zgłosi błędu jeśli wprowadzimy swoacutej wła-sny system nazewnictwa Jednak warto pamiętać że być może nad naszym kodem będą pra-cowali także inni programiści ktoacuterzy mogą mieć trudności z analizą kodu niespełniającegopewnych zasad

Rozdział 7

Zmienne

Procesor komputera stworzony jest tak aby przetwarzał dane znajdujące się w pamięci kom-putera Z punktu widzenia programu napisanego w języku C (ktoacutery jak wiadomo jest języ-kiem wysokiego poziomu) dane umieszczane są w postaci tzw zmienny Zmienne uła-twiają programiście pisanie programu Dzięki nim programista nie musi się przejmowaćgdzie w pamięci owe zmienne się znajdują tzn nie operuje fizycznymi adresami pamięcijak np 0x14613467 tylko prostą do zapamiętania nazwą zmiennej

71 Czym są zmienneZmienna jest to pewien fragment pamięci o ustalonym rozmiarze ktoacutery posiada własny iden-tyfikator (nazwę) oraz może przechowywać pewną wartość zależną od typu zmiennej

711 Deklaracja zmienny

Aby moacutec skorzystać ze zmiennej należy ją przed użyciem zadeklarować to znaczy poinfor-mować kompilator jak zmienna będzie się nazywać i jaki typ ma mieć Zmienne deklarujesię w sposoacuteb następujący

typ nazwa_zmiennej

Oto deklaracja zmiennej o nazwie ldquowiekrdquo typu ldquointrdquo czyli liczby całkowitej

int wiek

Zmiennej w momencie zadeklarowania można od razu przypisać wartość

int wiek = 17

W języku C zmienne deklaruje się na samym początku bloku (czyli przed pierwszą in-strukcją)

int wiek = 17printf(dn wiek)int kopia_wieku tu stary kompilator C zgłosi błąd

deklaracja występuje po instrukcji (printf) kopia_wieku = wiek

33

34 ROZDZIAŁ 7 ZMIENNE

Według nowszych standardoacutewmożliwe jest deklarowanie zmiennej w dowolnymmiejscuprogramu ale wtedy musimy pamiętać aby zadeklarować zmienną przed jej użyciem Toznaczy że taki kod jest niepoprawny

printf (Mam d latn wiek)int wiek = 17

Należy go zapisać tak

int wiek = 17printf (Mam d latn wiek)

Język C nie inicjalizuje zmiennych lokalnych Oznacza to że w nowo zadeklarowanejzmiennej znajdują się śmieci - to co wcześniej zawierał przydzielony zmiennej fragmentpamięci Aby uniknąć ciężkich do wykrycia błędoacutew dobrze jest inicjalizować (przypisywaćwartość) wszystkie zmienne w momencie zadeklarowania

712 Zasięg zmiennej

Zmienne mogą być dostępne dla wszystkich funkcji programu mdash nazywamy je wtedy zmien-nymi globalnymi Deklaruje się je przed wszystkimi funkcjami programu

include ltstdiohgt

int ab nasze zmienne globalne

void func1 ()

instrukcje a=3 dalsze instrukcje

int main ()

b=3a=2return 0

Zmienne globalne jeśli programista nie przypisze im innej wartości podczas definiowa-nia są inicjalizowane wartością

Zmienne ktoacutere funkcja deklaruje do ldquowłasnych potrzebrdquo nazywamy zmiennymi lokal-nymi Nasuwa się pytanie ldquoczy będzie błędem nazwanie tą samą nazwą zmiennej globalneji lokalnejrdquo Otoacuteż odpowiedź może być zaskakująca nie Natomiast w danej funkcji da sięużywać tylko jej zmiennej lokalnej Tej konstrukcji należy z wiadomych względoacutew unikać

int a=1 zmienna globalna

int main()

71 CZYM SĄ ZMIENNE 35

int a=2 to już zmienna lokalna printf(d a) wypisze 2

713 Czas życia

Czas życia to czas od momentu przydzielenia dla zmiennej miejsca w pamięci (stworzenieobiektu) do momentu zwolnienia miejsca w pamięci (likwidacja obiektu)

Zakres ważności to część programu w ktoacuterej nazwa znana jest kompilatorowi

main()

int a = 10 otwarcie lokalnego bloku

int b = 10printf(d d a b)

zamknięcie lokalnego bloku zmienna b jest usuwana

printf(d d a b) BŁĄD b juz nie istnieje tu usuwana jest zmienna a

Zdefiniowaliśmy dwie zmienne typu int Zaroacutewno a i b istnieją przez cały program (czasżycia) Nazwa zmiennej a jest znana kompilatorowi przez cały program Nazwa zmiennej bjest znana tylko w lokalnym bloku dlatego nastąpi błąd w ostatniej instrukcji

Niektoacutere kompilatory (prawdopodobniemożna tu zaliczyćMicrosoVisual C++ dowersji) uznają powyższy kod za poprawny W dodatku można ustawić w opcjach niektoacuterychkompilatoroacutew zachowanie w takiej sytuacji włącznie z zachowaniami niezgodnymi ze stan-dardem języka

Możemy świadomie ograniczyć ważność zmiennej do kilku linijek programu (tak jak ro-biliśmy wyżej) tworząc blok Nazwa zmiennej jest znana tylko w tym bloku

714 Stałe

Stała roacuteżni się od zmiennej tylko tym że nie można jej przypisać innej wartości w trak-cie działania programu Wartość stałej ustala się w kodzie programu i nigdy ona nie ulegazmianie Stałą deklaruje się z użyciem słowa kluczowego const w sposoacuteb następujący

const typ nazwa_stałej=wartość

Dobrze jest używać stałych w programie ponieważ unikniemy wtedy przypadkowychpomyłek a kompilator może często zoptymalizować ich użycie (np od razu podstawiając ichwartość do kodu)

36 ROZDZIAŁ 7 ZMIENNE

const int WARTOSC_POCZATKOWA=5int i=WARTOSC_POCZATKOWAWARTOSC_POCZATKOWA=4 tu kompilator zaprotestuje int j=WARTOSC_POCZATKOWA

Przykład pokazuje dobry zwyczaj programistyczny jakim jest zastępowanie umieszczo-nych na stałe w kodzie liczb stałymi W ten sposoacuteb będziemy mieli większą kontrolę nadkodem mdash stałe umieszczone w jednym miejscu można łatwo modyfikować zamiast szukaćpo całym kodzie liczb ktoacutere chcemy zmienić

Nie mamy jednak pełnej gwarancji że stała będzie miała tę samą wartość przez cały czaswykonania programu możliwe jest bowiem dostanie się do wartości stałej (miejsca jej prze-chowywania w pamięci) pośrednio mdash za pomocą wskaźnikoacutew Można zatem dojść do wnio-sku że słowo kluczowe const służy tylko do poinformowania kompilatora aby ten nie zezwa-lał na jawną zmianę wartości stałej Z drugiej strony zgodnie ze standardem proacuteba mody-fikacji wartości stałej ma niezdefiniowane działanie (tzw undefined behaviour) i w związkuz tym może się powieść lub nie ale może też spowodować jakieś subtelne zmiany ktoacutere wefekcie spowodują że program będzie źle działał

Podobnie do zdefiniowania stałej możemy użyć dyrektywy preprocesora define (opi-sanej w dalszej części podręcznika) Tak zdefiniowaną stałą nazywamy stałą symbolicznąW przeciwieństwie do stałej zadeklarowanej z użyciem słowa const stała zdefiniowana przyużyciu define jest zastępowana daną wartością w każdym miejscu gdzie występuje dlategoteż może być używana w miejscach gdzie ldquonormalnardquo stała nie mogłaby dobrze spełnić swejroli

W przeciwieństwie do języka C++ w C stała to cały czas zmienna ktoacuterej kompilatorpilnuje by nie zmieniła się

72 Typy zmienny

Każdy program w C operuje na zmiennych mdash wydzielonych w pamięci komputera obsza-rach ktoacutere mogą reprezentować obiekty nam znane takie jak liczby znaki czy też bardziejzłożone obiekty Jednak dla komputera każdy obszar w pamięci jest taki sam mdash to ciąg zeri jedynek w takiej postaci zupełnie nieprzydatny dla programisty i użytkownika Podczaspisania programu musimy wskazać w jaki sposoacuteb ten ciąg ma być interpretowany

Typ zmiennej wskazuje właśnie sposoacuteb w jaki pamięć w ktoacuterej znajduje się zmiennabędzie wykorzystywana Określając go przekazuje się kompilatorowi informację ile pamięcitrzeba zarezerwować dla zmiennej a także w jaki sposoacuteb wykonywać na nim operacje

Każda zmienna musi mieć określony swoacutej typ w miejscu deklaracji i tego typu nie możejuż zmienić Lecz co jeśli mamy zmienną jednego typu ale potrzebujemy w pewnymmiejscuprogramu innego typu danych W takimwypadku stosujemy konwersję (rzutowanie) jednejzmiennej na inną zmienną Rzutowanie zostanie opisane poacuteźniej w rozdziale Operatory

Istnieją wbudowane i zdefiniowane przez użytkownika typy danych Wbudowane typydanych to te ktoacutere zna kompilator są one w nim bezpośrednio ldquozaszyterdquo Można też tworzyćwłasne typy danych ale należy je kompilatorowi opisać Więcej informacji znajduje się wrozdziale Typy złożone

W języku C wyroacuteżniamy podstawowe typy zmiennych Są to

char mdash jednobajtowe liczby całkowite służy do przechowywania znakoacutew

int mdash typ całkowity o długości domyślnej dla danej architektury komputera

72 TYPY ZMIENNYCH 37

float mdash typ zmiennopozycyjny (zwany roacutewnież zmiennoprzecinkowym) reprezentującyliczby rzeczywiste ( bajty)

double mdash typ zmiennopozycyjny podwoacutejnej precyzji ( bajtoacutew)

Typy zmiennoprzecinkowe zostały dokładnie opisane w IEEE

W języku C nie istnieje specjalny typ zmiennych przeznaczony na zmienne typu logicz-nego (albo ldquoprawda albo ldquofałszrdquo) Jest to inne podejście niż na przykład w językach Pascalalbo Java - definiujących osobny typ ldquobooleanrdquo ktoacuterego nie można ldquomieszaćz innymi typamizmiennych W C do przechowywania wartości logicznych zazwyczaj używa się typu ldquointrdquoWięcej na temat tego jak język C rozumie prawdę i fałsz znajduje się w rozdziale Operatory

721 int

Ten typ przeznaczony jest do liczb całkowitych Liczby temożemy zapisać na kilka sposoboacutew

System dziesiętny

12 13 45 35 itd

System oacutesemkowy (oktalny)

010 czyli 8016 czyli 8 + 6 = 14018 BŁĄD

System ten operuje na cyfrach od do Tak wiec jest niedozwolona Jeżeli chcemyużyć takiego zapisu musimy zacząć liczbę od

System szesnastkowy (heksadecymalny)

0x10 czyli 116 + 0 = 160x12 czyli 116 + 2 = 180xff czyli 1516 + 15 = 255

W tym systemie możliwe cyfry to hellip i dodatkowo a b c d e f ktoacutere oznaczają Aby użyć takiego systemu musimy poprzedzić liczbę ciągiem 0x Wielkośćznakoacutew w takich literałach nie ma znaczenia

Ponadto w niektoacuterych kompilatorach przeznaczonych głoacutewnie domikrokontroleroacutew spo-tyka się jeszcze użycie systemu binarnego Zazwyczaj dodaje się przedrostek 0b przed liczbą(analogicznie do zapisu spotykanego w języku Python) W tym systemie możemy oczywiścieużywać tylko i wyłącznie cyfr i Tego typu rozszerzenie bardzo ułatwia programowanieniskopoziomowe układoacutew Należy jednak pamiętać że jest to tylko i wyłącznie rozszerzenie

38 ROZDZIAŁ 7 ZMIENNE

722 float

Ten typ oznacza liczby zmiennoprzecinkowe czyli ułamki Istnieją dwa sposoby zapisu

System dziesiętny

314 45644 2354 321 itd

System ldquonaukowyrdquo mdash wykładniczy

6e2 czyli 6 102 czyli 60015e3 czyli 15 103 czyli 150034e-3 czyli 34 10minus3 czyli 00034

Należy wziąć pod uwagę że reprezentacja liczb rzeczywistych w komputerze jest niedo-skonała i możemy otrzymywać wyniki o zauważalnej niedokładności

723 double

Doublemdash czyli ldquopodwoacutejnyrdquomdash oznacza liczby zmiennoprzecinkowe podwoacutejnej precyzji Ozna-cza to że liczba taka zajmuje zazwyczaj w pamięci dwa razy więcej miejsca niż float (np bity wobec dla float) ale ma też dwa razy lepszą dokładność

Domyślnie ułamki wpisane w kodzie są typu double Możemy to zmienić dodając nakońcu literę ldquordquo

15f (float)15 (double)

724 ar

Jest to typ znakowy umożliwiający zapis znakoacutew ASCII Może też być traktowany jako liczbaz zakresu Znaki zapisujemywpojedynczych cudzysłowach (czasami nazywanymi apo-strofami) by odroacuteżnić je od łańcuchoacutew tekstowych (pisanych w podwoacutejnych cudzysłowach)

a 7 $

Pojedynczy cudzysłoacutew rsquo zapisujemy tak a null (czyli zero ktoacutere między innymikończy napisy) tak 0 Więcej znakoacutew specjalnych

Warto zauważyć że typ char to zwykły typ liczbowy i można go używać tak samo jaktypu int (zazwyczaj ma jednak mniejszy zakres) Co więcej literały znakowe (np rsquoarsquo) sątraktowane jako liczby i w języku C są typu int (w języku C++ są typu char)

725 void

Słowa kluczowego void można w określonych sytuacjach użyć tam gdzie oczekiwana jestnazwa typu void nie jest właściwym typem bo nie można utworzyć zmiennej takiego typujest to ldquopustyrdquo typ (ang void znaczy ldquopustyrdquo) Typ void przydaje się do zaznaczania żefunkcja nie zwraca żadnej wartości lub że nie przyjmuje żadnych parametroacutew (więcej o tymw rozdziale Funkcje) Można też tworzyć zmienne będące typu ldquowskaźnik na voidrdquo

73 SPECYFIKATORY 39

73 Specyfikatory

Specyfikatory to słowa kluczowe ktoacutere postawione przy typie danych zmieniają jego zna-czenie

731 signed i unsigned

Na początku zastanoacutewmy się jak komputer może przechować liczbę ujemną Otoacuteż w przy-padku przechowywania liczb ujemnych musimyw zmiennej przechować jeszcze jej znak Jakwiadomo zmienna składa się z szeregu bitoacutew W przypadku użycia zmiennej pierwszy bit zlewej strony (nazywany także bitem najbardziej znaczącym) przechowuje znak liczby Efek-tem tego jest spadek ldquopojemnościrdquo zmiennej czyli zmniejszenie największej wartości ktoacuterąmożemy przechować w zmiennej

Signed oznacza liczbę ze znakiem unsigned mdash bez znaku (nieujemną) Mogą być zasto-sowane do typoacutew char i int i łączone ze specyfikatorami short i long (gdy ma to sens)

Jeśli przy signed lub unsigned nie napiszemy o jaki typ nam chodzi kompilator przyjmiewartość domyślną czyli int

Przykładowo dla zmiennej char(zajmującej bitoacutew zapisanej w formacie uzupełnień dodwoacutech) wygląda to tak

signed char a zmienna a przyjmuje wartości od -128 do 127 unsigned char b zmienna b przyjmuje wartości od 0 do 255 unsigned short cunsigned long int d

Jeżeli nie podamy żadnego ze specyfikatora wtedy liczba jest domyślnie przyjmowanajako signed (nie dotyczy to typu char dla ktoacuterego jest to zależne od kompilatora)

signed int i = 0 jest roacutewnoznaczne zint i = 0

Liczby bez znaku pozwalają nam zapisać większe liczby przy tej samej wielkości zmiennejmdash ale trzeba uważać by nie zejść z nimi poniżej zera mdash wtedy ldquoprzewijająrdquo się na sam konieczakresu co może powodować trudne do wykrycia błędy w programach

732 short i long

Short i long są wskazoacutewkami dla kompilatora by zarezerwował dla danego typu mniej (od-powiednio mdash więcej) pamięci Mogą być zastosowane do dwoacutech typoacutew int i double (tylkolong) mając roacuteżne znaczenie

Jeśli przy short lub long nie napiszemy o jaki typ nam chodzi kompilator przyjmie war-tość domyślną czyli int

Należy pamiętać że to jedynie życzenie wobec kompilatora mdash w wielu kompilatorachtypy int i long int mają ten sam rozmiar Standard języka C nakłada jedynie na kompilatorynastępujące ograniczenia int mdash nie może być kroacutetszy niż bitoacutew int mdash musi byćdłuższy lub roacutewny short a nie może być dłuższy niż long short int mdash nie może byćkroacutetszy niż bitoacutew long int mdash nie może być kroacutetszy niż bity

Zazwyczaj typ int jest typem danych o długości odpowiadającej wielkości rejestroacutew pro-cesora czyli na procesorze szesnastobitowym ma bitoacutew na trzydziestodwubitowym mdash

40 ROZDZIAŁ 7 ZMIENNE

itd1 Z tego powodu jeśli to tylko możliwe do reprezentacji liczb całkowitych preferowanejest użycie typu int bez żadnych specyfikatoroacutew rozmiaru

74 Modyfikatory

741 volatile

volatile znaczy ulotny Oznacza to że kompilator wyłączy dla takiej zmiennej optymaliza-cje typu zastąpienia przez stałą lub zawartość rejestru za to wygeneruje kod ktoacutery będzieodwoływał się zawsze do komoacuterek pamięci danego obiektu Zapobiegnie to błędowi gdyobiekt zostaje zmieniony przez część programu ktoacutera nie ma zauważalnego dla kompilatorazwiązku z danym fragmentem kodu lub nawet przez zupełnie inny proces

volatile float liczba1float liczba2

printf (fnfn liczba1 liczba2) instrukcje nie związane ze zmiennymi printf (fnf liczba1 liczba2)

Jeżeli zmienne liczba i liczba zmienią się niezauważalnie dla kompilatora to odczytując

liczba mdash nastąpi odwołanie do komoacuterek pamięci Kompilator pobierze nową wartośćzmiennej

liczba mdash kompilator może wypisać poprzednią wartość ktoacuterą przechowywał w reje-strze

Modyfikator volatile jest rzadko stosowany i przydaje się w wąskich zastosowaniachjak wspoacutełbieżność i wspoacutełdzielenie zasoboacutew oraz przerwania systemowe

742 register

Jeżeli utworzymy zmienną ktoacuterej będziemy używać w swoim programie bardzo często mo-żemy wykorzystać modyfikator register Kompilator może wtedy umieścić zmienną w re-jestrze do ktoacuterego ma szybki dostęp co przyśpieszy odwołania do tej zmiennej

register int liczba

W nowoczesnych kompilatorach ten modyfikator praktycznie nie ma wpływu na pro-gram Optymalizator sam decyduje czy i co należy umieścić w rejestrze Nie mamy żadnejgwarancji że zmienna tak zadeklarowana rzeczywiście się tam znajdzie chociaż dostęp doniej może zostać przyspieszony w inny sposoacuteb Raczej powinno się unikać tego typu kon-strukcji w programie

1Wiąże się to z pewnymi uwarunkowaniami historycznymi Podręcznik do języka C duetu KampR zakładał żetyp int miał się odnosić do typowej dla danego procesora długości liczby całkowitej Natomiast jeśli procesor moacutegłobsługiwać typy dłuższe lub kroacutetsze stosownego znaczenia nabierałymodyfikatory short i long Dobrymprzykłademmoże być architektura i386 ktoacutera umożliwia obliczenia na liczbach 16-bitowych Dlatego też modyfikator shortpowoduje skroacutecenie zmiennej do 16 bitoacutew

75 UWAGI 41

743 static

Pozwala na zdefiniowanie zmiennej statycznej ldquoStatycznośćrdquo polega na zachowaniu warto-ści pomiędzy kolejnymi definicjami tej samej zmiennej Jest to przede wszystkim przydatnew funkcjach Gdy zdefiniujemy zmienną w ciele funkcji to zmienna ta będzie od nowa defi-niowana wraz z domyślną wartością (jeżeli taką podano) W wypadku zmiennej określonejjako statyczna jej wartość się nie zmieni przy ponownym wywołaniu funkcji Na przykład

void dodaj(int liczba)

int zmienna = 0 bez staticzmienna = zmienna + liczbaprintf (Wartosc zmiennej dn zmienna)

Gdy wywołamy tę funkcję np razy w ten sposoacuteb

dodaj(3)dodaj(5)dodaj(4)

to ujrzymy na ekranie

Wartosc zmiennej 3Wartosc zmiennej 5Wartosc zmiennej 4

jeżeli jednak deklarację zmiennej zmienimy na static int zmienna = 0 to wartość zmiennejzostanie zachowana i po podobnym wykonaniu funkcji powinnyśmy ujrzeć

Wartosc zmiennej 3Wartosc zmiennej 8Wartosc zmiennej 12

Zupełnie co innego oznacza static zastosowane dla zmiennej globalnej Jest ona wtedywidoczna tylko w jednym pliku Zobacz też rozdział Biblioteki

744 extern

Przez extern oznacza się zmienne globalne zadeklarowane w innych plikach mdash informujemyw ten sposoacuteb kompilator żeby nie szukał jej w aktualnym pliku Zobacz też rozdział Biblio-teki

745 auto

Zupełnym archaizmem jest modyfikator auto ktoacutery oznacza tyle że zmienna jest lokalnaPonieważ zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna modyfikator tennie ma obecnie żadnego zastosowania praktycznego auto jest spadkiem po wcześniejszychjęzykach programowania na ktoacuterych oparty jest C (np B)

75 Uwagi Język C++ pozwala na mieszanie deklaracji zmiennych z kodem Więcej informacji w

C++Zmienne

42 ROZDZIAŁ 7 ZMIENNE

Rozdział 8

Operatory

81 Przypisanie

Operator przypisania (=rdquo) jak sama nazwa wskazuje przypisuje wartość prawego argu-mentu lewemu np

int a = 5 bb = aprintf(dn b) wypisze 5

Operator ten ma łączność prawostronną tzn obliczanie przypisań następuje z prawa nalewo i zwraca on przypisaną wartość dzięki czemu może być użyty kaskadowo

int a b ca = b = c = 3printf(d d dn a b c) wypisze 3 3 3

811 Skroacutecony zapis

C umożliwia też skroacutecony zapis postaci a = b gdzie jest jednym z operatoroacutew + - amp | ˆ ltlt lub gtgt (opisanych niżej) Ogoacutelnie rzecz ujmując zapis a = b jest roacutewnoważnyzapisowi a = a (b) np

int a = 1a += 5 to samo co a = a + 5 a = a + 2 to samo co a = a (a + 2) a = 2 to samo co a = a 2

Początkowo skroacutecona notacja miała następującą składnię a = b co często prowadziło doniejasności np i =- (i = - czy też i = i-) Dlatego też zdecydowano się zmienić kolejnośćoperatoroacutew

43

44 ROZDZIAŁ 8 OPERATORY

82 Rzutowanie

Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu Konwer-sja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podanaexplicite przez programistę) Oto kilka przykładoacutew konwersji niejawnej

int i = 427 konwersja z double do int float f = i konwersja z int do float double d = f konwersja z float do double unsigned u = i konwersja z int do unsigned int f = 42 konwersja z double do float i = d konwersja z double do int char str = foo konwersja z const char do char [1] const char cstr = str konwersja z char do const char void ptr = str konwersja z char do void

Podczas konwersji zmiennych zawierających większe ilości danych do typoacutew prostszych(np double do int) musimy liczyć się z utratą informacji jak to miało miejsce w pierwszejlinijce mdash zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta iw rezultacie zmiennej została przypisana wartość

Zaskakująca może się wydać linijka oznaczona przez 1 Niejawna konwersja z typu constchar do typu char nie jest dopuszczana przez standard C Jednak literały napisowe (ktoacutere sątypu const char) stanowią tutaj wyjątek Wynika on z faktu że były one używane na długoprzed wprowadzeniem słoacutewka const do języka i brak wspomnianegowyjątku spowodowałbyże duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod

Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania np

double d = 314int pi = (int)d 1 pi = (unsigned)pi gtgt 4 2

W pierwszym przypadku operator został użyty by zwroacutecić uwagę na utratę precyzji Wdrugim dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej

Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne kon-wersje (tj konwersja z double do int oraz z int do unsigned int) jednak niektoacutere konwersjesą błędne np

const char cstr = foochar str = cstr

W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję

const char cstr = foochar str = (char)cstr

Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompi-lator Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzieon robił i czy nie ma innego sposobu wykonania danej operacji ktoacutery nie wymagałby podej-mowania tak drastycznych krokoacutew

83 OPERATORY ARYTMETYCZNE 45

83 Operatory arytmetyczne

W arytmetyce komputerowej nie działa prawo łączności oraz rozdzielności Wynika toz ograniczonego rozmiaru zmiennych ktoacutere przechowują wartości Przykład dla zmiennycho długości bitoacutew (bez znaku) Maksymalna wartość ktoacuterą może przechowywać typ to216minus1 = 65535 Zatem operacja typu 65530+10minus20 zapisana jako (65530+10)minus20 możezaowocować czymś zupełnie innym niż 65530+(10minus20) W pierwszym przypadku zapewnedojdzie do tzw przepełnienia - procesor nie będzie miał miejsca aby zapisać dodatkowybit Zachowanie programu będzie w takim przypadku zależało od architektury procesoraAnalogiczny przykład możemy podać dla rozdzielności mnożenia względem dodawania

Język C definiuje następujące dwuargumentowe operatory arytmetyczne

dodawanie (+rdquo)

odejmowanie (-rdquo)

mnożenie (rdquo)

dzielenie (rdquo)

reszta z dzielenia (rdquo) określona tylko dla liczb całkowitych (tzw dzielenie modulo)

int a=7 b=2 cc = a bprintf (dnc) wypisze 1

Należy pamiętać że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak naj-większy z argumentoacutew Oznacza to że operacja wykonana na dwoacutech liczbach całkowitychnadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej Dla przy-kładu poniższy kod

float a = 7 2printf(fn a)

wypisze (wbrew oczekiwaniu początkujących programistoacutew) 30 a nie 35 Odnosi sięto nie tylko do dzielenia ale także mnożenia np

float a = 1000 1000 1000 1000 1000 1000printf(fn a)

prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali Aby wymusićobliczenia rzeczywiste należy zmienić typ jednego z argumentoacutew na liczbę rzeczywistą poprostu zmieniając literał lub korzystając z rzutowania np

float a = 70 2float b = (float)1000 1000 1000 1000 1000 1000printf(fn a)printf(fn b)

Operatory dodawania i odejmowania są określone roacutewnież gdy jednym z argumentoacutewjest wskaźnik a drugim liczba całkowita Ten drugi jest także określony gdy oba argumentysą wskaźnikami O takim użyciu tych operatoroacutew dowiesz się więcej CWskaźniki|w dalszejczęści książki

46 ROZDZIAŁ 8 OPERATORY

831 Inkrementacja i dekrementacja

Aby skroacutecić zapis wprowadzono dodatkowe operatory inkrementacji (++rdquo) i dekrementa-cji (ndashrdquo) ktoacutere dodatkowo mogą być pre- lub postfiksowe W rezultacie mamy więc czteryoperatory

pre-inkrementacja (++irdquo)

post-inkrementacja (i++rdquo)

pre-dekrementacja (ndashirdquo) i

post-dekrementacja (indashrdquo)

Operatory inkrementacji zwiększa a dekrementacji zmniejsza argument o jeden Ponadtooperatory pre- zwracają nową wartość argumentu natomiast post- starą wartość argumentu

int a b ca = 3b = a-- po operacji b=3 a=2 c = --b po operacji b=2 c=2

Czasami (szczegoacutelnie w C++) użycie operatoroacutew stawianych za argumentem jest niecomniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tym-czasową)

Bardzo ważne jest abyśmy poprawnie stosowali operatory dekrementacji i inkrementa-cji Chodzi o to aby w jednej instrukcji nie umieszczać kilku operatoroacutew ktoacutere modyfikująten sam obiekt (zmienną) Jeżeli taka sytuacja zaistnieje to efekt działania instrukcji jestnieokreślony Prostym przykładem mogą być następujące instrukcje

int a = 1a = a++a = ++aa = a++ + ++aprintf(d dn ++a ++a)printf(d dn a++ a++)

Kompilator potrafi ostrzegać przed takimi błędami - aby to czynił należy podać mujako argument opcję -Wsequence-point

84 Operacje bitoweOproacutecz operacji znanych z lekcji matematyki w podstawoacutewce język C został wyposażonytakże w operatory bitowe zdefiniowane dla liczb całkowitych Są to

negacja bitowa (˜rdquo)

koniunkcja bitowa (amprdquo)

alternatywa bitowa (|rdquo) i

84 OPERACJE BITOWE 47

alternatywa rozłączna () (ˆrdquo)

Działają one na poszczegoacutelnych bitach przez co mogą być szybsze od innych operacjiDziałanie tych operatoroacutew można zdefiniować za pomocą poniższych tabel

~ | 0 1 amp | 0 1 | | 0 1 ^ | 0 1-----+----- -----+----- -----+----- -----+-----

| 1 0 0 | 0 0 0 | 0 1 0 | 0 11 | 0 1 1 | 1 1 1 | 1 0

a | 0101 = 5b | 0011 = 3

-------+------~a | 1010 = 10~b | 1100 = 12

a amp b | 0001 = 1a | b | 0111 = 7a ^ b | 0110 = 6

Lub bardziej opisowo

negacja bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych argument miał bity roacutewne zero

koniunkcja bitowa daje wwyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych oba argumenty miały bity roacutewne jeden (mnemonik gdy wszystkie)

alternatywa bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden na wszystkichtych pozycjach na ktoacuterych jeden z argumentoacutew miał bit roacutewny jeden (mnemonik jeśli jest )

alternatywa rozłączna daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tychpozycjach na ktoacuterych tylko jeden z argumentoacutew miał bit roacutewny jeden (mnemonik gdy roacuteżne)

Przy okazji warto zauważyć że aˆbˆb to po prostu a Właściwość ta została wykorzystanaw roacuteżnych algorytmach szyfrowania oraz funkcjach haszujących Alternatywę wyłączną sto-suje się np do szyfrowania kodu wirusoacutew polimorficznych

841 Przesunięcie bitowe

Dodatkowo język C wyposażony jest w operatory przesunięcia bitowego w lewo (ltltrdquo) iprawo (gtgtrdquo) Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycjipodaną jako prawy argument Brzmi to może strasznie ale wcale takie nie jest Rozważmy-bitowe liczby bez znaku (taki hipotetyczny unsigned int) woacutewczas

a | altlt1 | altlt2 | agtgt1 | agtgt2------+------+------+------+------0001 | 0010 | 0100 | 0000 | 00000011 | 0110 | 1100 | 0001 | 00000101 | 1010 | 0100 | 0010 | 0001

48 ROZDZIAŁ 8 OPERATORY

1000 | 0000 | 0000 | 0100 | 00101111 | 1110 | 1100 | 0111 | 00111001 | 0010 | 0100 | 0100 | 0010

Nie jest to zatem takie straszne na jakie wygląda Widać że bity będące na skraju sątracone a w pusterdquo miejsca wpisywane są zera

Inaczej rzecz się ma jeżeli lewy argument jest liczbą ze znakiem Dla przesunięcia bito-wego w lewo a ltlt b jeżeli a jest nieujemna i wartość a middot 2b mieści się w zakresie liczby tojest to wynikiem operacji W przeciwnym wypadku działanie jest niezdefiniowane1

Dla przesunięcia bitowego w lewo jeżeli lewy argument jest nieujemny to operacja za-chowuje się tak jak w przypadku liczb bez znaku Jeżeli jest on ujemny to zachowanie jestzależne od implementacji

Zazwyczaj operacja przesunięcia w lewo zachowuje się tak samo jak dla liczb bez znakunatomiast przy przesuwaniu w prawo bit znaku nie zmienia się2

a | agtgt1 | agtgt2------+------+------0001 | 0000 | 00000011 | 0001 | 00000101 | 0010 | 00011000 | 1100 | 11101111 | 1111 | 11111001 | 1100 | 1110

Przesunięcie bitowe w lewo odpowiada pomnożeniu natomiast przesunięcie bitowe wprawo podzieleniu liczby przez dwa do potęgi jaką wyznacza prawy argument Jeżeli prawyargument jest ujemny lub większy lub roacutewny liczbie bitoacutew w typie działanie jest niezdefi-niowane

include ltstdiohgt

int main ()

int a = 6printf (6 ltlt 2 = dn altlt2) wypisze 24 printf (6 gtgt 2 = dn agtgt2) wypisze 1 return 0

85 PoroacutewnanieW języku C występują następujące operatory poroacutewnania

roacutewne (==rdquo)

roacuteżne (=rdquo)

mniejsze (ltrdquo)

1Niezdefiniowane w takim samym sensie jak niezdefiniowane jest zachowanie programu gdy proacutebujemy odwo-łać się do wartości wskazywanej przez wartość czy do zmiennych poza tablicą

2ale jeżeli zależy Ci na przenośności kodu nie możesz na tym polegać

85 POROacuteWNANIE 49

większe (gtrdquo)

mniejsze lub roacutewne (lt=rdquo) i

większe lub roacutewne (gt=rdquo)

Wykonują one odpowiednie poroacutewnanie swoich argumentoacutew i zwracają jedynkę jeżeliwarunek jest spełniony lub zero jeżeli nie jest

851 Częste błędy

Osoby ktoacutere poprzednio uczyły się innych językoacutew programowania często mają nawykużywania w instrukcjach logicznych zamiast operatora poroacutewnania == operatora przypi-sania = Ma to często zgubne efekty gdyż przypisanie zwraca wartość przypisaną lewemuargumentowi

Poroacutewnajmy ze sobą dwa warunki

(a = 1)(a == 1)

Pierwszy z nich zawsze będzie prawdziwy niezależnie od wartości zmiennej a Dziejesię tak ponieważ zostaje wykonane przypisanie do a wartości a następnie jako wartość jestzwracane to co zostało przypisane mdash czyli jeden Drugi natomiast będzie prawdziwy tylkogdy a jest roacutewne

W celu uniknięcia takich błędoacutew niektoacuterzy programiści zamiast pisać a == 1 piszą 1 == adzięki czemu pomyłka spowoduje że kompilator zgłosi błąd

Warto zauważyć że kompilator potrafi w pewnych sytuacjach wychwycić taki błądAby zaczął to robić należy podać mu argument -Wparentheses

Innym błędem jest użycie zwykłych operatoroacutew poroacutewnania do sprawdzania relacji po-między liczbami rzeczywistymi Ponieważ operacje zmiennoprzecinkowe wykonywane są zpewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie roacutewne Dlaprzykładu

include ltstdiohgtint main ()

float a b ca = 1e10 tj 10 do potęgi 10 b = 1e-10 tj 10 do potęgi -10 c = b c = b c = c + a c = b + a (teoretycznie) c = c - a c = b + a - a = b (teoretycznie) printf(dn c == b) wypisze 0

Obejściem jest poroacutewnywanie modułu roacuteżnicy liczb Roacutewnież i takie błędy kompilator potrafi wykrywać mdash aby to robił należy podać mu argument -Wfloat-equal

50 ROZDZIAŁ 8 OPERATORY

86 Operatory logiczneAnalogicznie do części operatoroacutew bitowych w C definiuje się operatory logiczne miano-wicie

negację (zaprzeczenie)

koniunkcję (ldquoirdquo) ampamp

alternatywę (ldquolubrdquo) ||

Działają one bardzo podobnie do operatoroacutew bitowych jednak zamiast operować na po-szczegoacutelnych bitach biorą pod uwagę wartość logiczną argumentoacutew

861 ldquoPrawdardquo i ldquofałszrdquo w języku C

Język C nie przewiduje specjalnego typu danych do operacji logicznych mdash operatory logicznemożna stosować do liczb (np typu int) tak samo jak operatory bitowe albo arytmetyczne

Wyrażenie ma wartość logiczną wtedy i tylko wtedy gdy jest roacutewne (jest ldquofałszywerdquo)W przeciwnym wypadku ma wartość (jest ldquoprawdziwerdquo) Operatory logiczne w wynikudają zawsze albo albo

Żeby w pełni uzmysłowić sobie co to to oznacza spoacutejrzmy na wynik wykonania poniż-szych trzech linijek

printf(koniunkcja dn 18 ampamp 19)printf(alternatywa dn a || b)printf(negacja dn 20)

koniunkcja 1alternatywa 1negacja 0

Liczba nie jest roacutewna więc ma wartość logiczną Podobnie ma wartość logiczną Dlatego ich koniunkcja jest roacutewna Znaki a i b zostaną w wyrażeniu logicznympotraktowane jako liczby o wartości odpowiadającej kodowi znaku mdash czyli oba będąmiały wartość logiczną

862 Skroacutecone obliczanie wyrażeń logiczny

Język C wykonuje skroacutecone obliczanie wyrażeń logicznych mdash to znaczy oblicza wyrażenietylko tak długo jak nie wie jaka będzie jego ostateczna wartość To znaczy idzie od lewejdo prawej obliczając kolejne wyrażenia (dodatkowo na kolejność wpływ mają nawiasy) i gdybędzie miał na tyle informacji by obliczyć wartość całości nie liczy reszty Może to wydawaćsię niejasne ale przyjrzyjmy się wyrażeniom logicznym

A ampamp BA || B

Jeśli A jest fałszywe to nie trzeba liczyć B w pierwszym wyrażeniu bo fałsz i dowolne wyra-żenie zawsze da fałsz Analogicznie jeśli A jest prawdziwe to wyrażenie jest prawdziwe iwartość B nie ma znaczenia

Poza zwiększoną szybkością zysk z takiego rozwiązania polega na możliwości stosowaniaefektoacutew ubocznych Idea efektu ubocznego opiera się na tym że w wyrażeniu można wywo-łać funkcje ktoacutere będą robiły poza zwracaniemwyniku inne rzeczy oraz używać podstawieńPopatrzmy na poniższy przykład

87 OPERATOR WYRAŻENIA WARUNKOWEGO 51

( (a gt 0) || (a lt 0) || (a = 1) )

Jeśli a będzie większe od to obliczona zostanie tylko wartość wyrażenia (a gt 0) mdash da onoprawdę czyli reszta obliczeń nie będzie potrzebna Jeśli a będzie mniejsze od zera najpierwzostanie obliczone pierwsze podwyrażenie a następnie drugie ktoacutere da prawdę Ciekawy bę-dzie jednak przypadek gdy a będzie roacutewne zero mdash do a zostanie wtedy podstawiona jedynkai całość wyrażenia zwroacuteci prawdę (bo jest traktowane jak prawda)

Efekty uboczne pozwalają na roacuteżne szaleństwa i wykonywanie złożonych operacji w sa-mych warunkach logicznych jednak przesadne używanie tego typu konstrukcji powodujeże kod staje się nieczytelny i jest uważane za zły styl programistyczny

87 Operator wyrażenia warunkowegoC posiada szczegoacutelny rodzaj operatora mdash to operator zwany też operatorem wyrażeniawarunkowego Jest to jedyny operator w tym języku przyjmujący trzy argumenty

a b c

Jego działanie wygląda następująco najpierw oceniana jest wartość logiczna wyrażenia ajeśli jest ono prawdziwe to zwracana jest wartość b jeśli natomiast wyrażenie a jest nie-prawdziwe zwracana jest wartość c

Praktyczne zastosowanie mdash znajdowanie większej z dwoacutech liczb

a = (bgt=c) b c Jeśli b jest większe bądź roacutewne c to zwroacuteć bW przeciwnym wypadku zwroacuteć c

lub zwracanie modułu liczby

a = a lt 0 -a a

Wartości wyrażeń są przy tym operatorze obliczane tylko jeżeli zachodzi taka potrzebanp w wyrażeniu 1 1 foo() funkcja foo() nie zostanie wywołana

88 Operator przecinekOperator przecinek jest dość dziwnym operatorem Powoduje on obliczanie wartości wyra-żeń od lewej do prawej po czym zwroacutecenie wartości ostatniego wyrażenia W zasadzie wnormalnym kodzie programu ma on niewielkie zastosowanie gdyż zamiast niego lepiej roz-dzielać instrukcje zwykłymi średnikami Ma on jednak zastosowanie w instrukcji sterującejfor

89 Operator sizeofOperator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanegotypu lub typu podanego wyrażenia Ma on dwa rodzaje sizeof(typ) lub sizeof wyrażeniePrzykładowo

include ltstdiohgt

int main()

52 ROZDZIAŁ 8 OPERATORY

printf(sizeof(short ) = dn sizeof(short ))printf(sizeof(int ) = dn sizeof(int ))printf(sizeof(long ) = dn sizeof(long ))printf(sizeof(float ) = dn sizeof(float ))printf(sizeof(double) = dn sizeof(double))return 0

Operator ten jest często wykorzystywany przy dynamicznej alokacji pamięci co zostanieopisane w rozdziale poświęconym wskaźnikom

Pomimo że w swej budowie operator sizeof bardzo przypomina funkcję to jednak niąnie jest Wynika to z trudności w implementacji takowej funkcji mdash jej specyfika musiałabyodnosić się bezpośrednio do kompilatora Ponadto jej argumentem musiałyby być typy anie zmienne W języku C nie jest możliwe przekazywanie typu jako argumentu Ponadtoczęsto zdarza się że rozmiar zmiennej musi być wiadomy jeszcze w czasie kompilacji mdash toewidentnie wyklucza implementację sizeof() jako funkcji

810 Inne operatory

Poza wyżej opisanymi operatorami istnieją jeszcze

operator []rdquo opisany przy okazji opisywania tablic

jednoargumentowe operatory rdquo i amprdquo opisane przy okazji opisywania wskaźnikoacutew

operatory rdquo i -gtrdquo opisywane przy okazji opisywania struktur i unii

operator ()rdquo będący operatorem wywołania funkcji

operator ()rdquo grupujący wyrażenia (np w celu zmiany kolejności obliczania

811 Priorytety i kolejność obliczeń

Jak w matematyce roacutewnież i w języku C obowiązuje pewna ustalona kolejność działań Abymoacutec ją określić należy ustalić dwa parametry danego operatora jego priorytet oraz łącz-ność Przykładowo operator mnożenia ma wyższy priorytet niż operator dodawania i z tegopowodu wwyrażeniu 2+2 middot2 najpierw wykonuje się mnożenie a dopiero potem dodawanie

Drugim parametrem jest łączność mdash określa ona od ktoacuterej stronywykonywane są działaniaw przypadku połączenia operatoroacutew o tym samym priorytecie Na przykład odejmowaniema łączność lewostronną i 2 minus 2 minus 2 da w wyniku - Gdyby miało łączność prawostronnąw wynikiem byłoby Przykładem matematycznego operatora ktoacutery ma łączność prawo-stronną jest potęgowanie np 322

jest roacutewne W języku C występuje dużo poziomoacutew operatoroacutew Poniżej przedstawiamy tabelkę ze

wszystkimi operatorami poczynając od tych z najwyższym priorytetem (wykonywanych napoczątku)

Duża liczba poziomoacutew pozwala czasami zaoszczędzić trochę milisekund w trakcie pisaniaprogramu i bajtoacutew na dysku gdyż często nawiasy nie są potrzebne nie należy jednak z tymprzesadzać gdyż kod programu może stać się mylący nie tylko dla innych ale po latach (czynawet i dniach) roacutewnież dla nas

812 KOLEJNOŚĆ WYLICZANIA ARGUMENTOacuteW OPERATORA 53

Tablica 81 Priorytety operatoroacutewOperator Łącznośćnawiasy nie dotyczyjednoargumentowe przyrostkowe [] -gt wywołanie funkcji postinkre-mentacja postdekrementacja

lewostronna

jednoargumentowe przedrostkowe ˜ + - amp sizeof preinkrementacjapredekrementacja rzutowanie

prawostronna

lewostronna+ - lewostronnaltlt gtgt lewostronnaltlt= gtgt= lewostronna== = lewostronnaamp lewostronnaˆ lewostronna| lewostronnaampamp lewostronna|| lewostronna prawostronnaoperatory przypisania prawostronna lewostronna

Warto także podkreślić że operator koniunkcji ma niższy priorytet niż operator poroacutew-nania3 Oznacza to że kod

if (flags amp FL_MASK == FL_FOO)

zazwyczaj da rezultat inny od oczekiwanego Najpierw bowiem wykona się poroacutewna-nie wartości FL MASK z wartością FL FOO a dopiero potem koniunkcja bitowa W takichsytuacjach należy pamiętać o użyciu nawiasoacutew

if ((flags amp FL_MASK) == FL_FOO)

Kompilator potrafi wykrywać takie błędy i aby to robił należy podać mu argument-Wparentheses

812 Kolejność wyliczania argumentoacutew operatoraW przypadku większości operatoroacutew (wyjątkami są tu ampamp || i przecinek) nie da się określićktoacutera wartość argumentu zostanie obliczona najpierw W większości przypadkoacutew nie mato większego znaczenia lecz w przypadku wyrażeń ktoacutere mają efekty uboczne wymuszeniekonkretnej kolejności może być potrzebne Weźmy dla przykładu program

include ltstdiohgt

int foo(int a) printf(dn a)

3Jest to zaszłość historyczna z czasoacutew gdy nie było logicznych operatoroacutew ampamp oraz || i zamiast nich stosowanooperatory bitowe amp oraz |

54 ROZDZIAŁ 8 OPERATORY

return 0

int main(void) return foo(1) + foo(2)

Otoacuteż nie wiemy czy najpierw zostanie wywołana funkcja foo z parametrem jeden czydwa Jeżeli ma to znaczenie należy użyć zmiennych pomocniczych zmieniając definicję funk-cji main na

int main(void) int tmp = foo(1)return tmp + foo(2)

Teraz już na pewno najpierw zostanie wypisana jedynka a potem dopiero dwoacutejka Sy-tuacja jeszcze bardziej się komplikuje gdy używamy wyrażeń z efektami ubocznymi jakoargumentoacutew funkcji np

include ltstdiohgt

int foo(int a) printf(dn a)return 0

int bar(int a int b int c int d) return a + b + c + d

int main(void) return foo(1) + foo(2) + foo(3) + foo(4)

Teraz też nie wiemy ktoacutera z permutacji liczb i zostanie wypisana i ponownienależy pomoacutec sobie zmiennymi tymczasowymi jeżeli zależy nam na konkretnej kolejności

int main(void) int tmp = foo(1)tmp += foo(2)tmp += foo(3)return tmp + foo(4)

813 Uwagi

W języku C++ wprowadzony został dodatkowo inny sposoacuteb zapisu rzutowania ktoacuterypozwala na łatwiejsze znalezienie w kodzie miejsc w ktoacuterych dokonujemy rzutowaniaWięcej na stronie C++Zmienne

814 ZOBACZ TEŻ 55

814 Zobacz też CSkładniaOperatory

56 ROZDZIAŁ 8 OPERATORY

Rozdział 9

Instrukcje sterujące

C jest językiem imperatywnym mdash oznacza to że instrukcje wykonują się jedna po drugiej wtakiej kolejności w jakiej są napisane Aby moacutec zmienić kolejność wykonywania instrukcjipotrzebne są instrukcje sterujące

Na wstępie przypomnijmy jeszcze informację z rozdziału Operatory że wyrażenie jestprawdziwe wtedy i tylko wtedy gdy jest roacuteżne od zera a fałszywe wtedy i tylko wtedy gdyjest roacutewne zeru

91 Instrukcje warunkowe

911 Instrukcja if

Użycie instrukcji if wygląda tak

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

dalsze instrukcje

Istnieje także możliwość reakcji na nieprawdziwość wyrażenia mdash wtedy należy zastosowaćsłowo kluczowe else

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

else blok wykonany jeśli wyrażenie jest nieprawdziwe

dalsze instrukcje

Przypatrzmy się bardziej ldquożyciowemurdquo programowi ktoacutery poroacutewnuje ze sobą dwie liczby

include ltstdiohgt

int main ()

int a ba = 4

57

58 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

b = 6if (a==b)

printf (a jest roacutewne bn) else

printf (a nie jest roacutewne bn)return 0

Stosowany jest też kroacutetszy zapis warunkoacutew logicznych korzystający z tego jak C rozumieprawdę i fałsz Jeśli zmienna a jest typu integer zamiast

if (a = 0) b = 1a

można napisać

if (a) b = 1a

a zamiast

if (a == 0) b = 1a

można napisać

if (a) b = 1a

Czasami zamiast pisać instrukcję if możemy użyć operatora wyrażenia warunkowego(patrz Operatory)

if (a = 0)b = 1a

elseb = 0

ma dokładnie taki sam efekt jak

b = (a =0) 1a 0

912 Instrukcja swit

Aby ograniczyćwielokrotne stosowanie instrukcji if możemy użyć swit Jej użyciewyglądatak

switch (wyrażenie) case wartość1 instrukcje jeśli wyrażenie == wartość1

breakcase wartość2 instrukcje jeśli wyrażenie == wartość2

break default instrukcje jeśli żaden z wcześniejszych warunkoacutew

break nie został spełniony

Należy pamiętać o użyciu break po zakończeniu listy instrukcji następujących po case Je-śli tego nie zrobimy program przejdzie do wykonywania instrukcji z następnego case Możemieć to fatalne skutki

91 INSTRUKCJE WARUNKOWE 59

include ltstdiohgt

int main ()

int a bprintf (Podaj a )scanf (d ampa)printf (Podaj b )scanf (d ampb)switch (b)

case 0 printf (Nie można dzielić przez 0n) tutaj zabrakło break default printf (ab=dn ab)

return 0

A czasami może być celowym zabiegiem (tzw ldquofall-throughrdquo) mdash woacutewczas warto zazna-czyć to w komentarzu Oto przykład

include ltstdiohgt

int main ()

int a = 4switch ((a3))

case 0printf (Liczba d dzieli się przez 3n a)break

case -2case -1case 1case 2printf (Liczba d nie dzieli się przez 3n a)break

return 0

Przeanalizujmy teraz działający przykład

include ltstdiohgt

int main ()

unsigned int dzieci = 3 podatek=1000switch (dzieci)

case 0 break brak dzieci - czyli brak ulgi case 1 ulga 2

podatek = podatek - (podatek100 2)break

case 2 ulga 5

60 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

podatek = podatek - (podatek100 5)break

default ulga 10 podatek = podatek - (podatek10010)break

printf (Do zapłaty dn podatek)

92 Pętle

921 Instrukcja while

Często zdarza się że nasz programmusi wielokrotnie powtarzać ten sam ciąg instrukcji Abynie przepisywać wiele razy tego samego kodu można skorzystać z tzw pętli Pętla wykonujesię dotąd dopoacuteki prawdziwy jest warunek

while (warunek) instrukcje do wykonania w pętli

dalsze instrukcje

Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie Załoacuteżmy żemamy obliczyć kwadraty liczb od do Piszemy zatem program

include ltstdiohgt

int main ()

int a = 1while (a lt= 10) dopoacuteki a nie przekracza 10

printf (dn aa) wypisz aa na ekran++a zwiększamy a o jeden

return 0

Po analizie kodu mogą nasunąć się dwa pytania

Po co zwiększać wartość a o jeden Otoacuteż gdybyśmy nie dodali instrukcji zwiększająceja to warunek zawsze byłby spełniony a pętla ldquokręciłabyrdquo się w nieskończoność

Dlaczego warunek to ldquoa lt= rdquo a nie ldquoa=rdquo Odpowiedź jest dość prosta Pętlasprawdza warunek przed wykonaniem kolejnego ldquoobroturdquo Dlatego też gdyby waru-nek brzmiał ldquoa=rdquo to dla a= jest on nieprawdziwy i pętla nie wykonałaby ostatniejiteracji przez co program generowałby kwadraty liczb od do a nie do

922 Instrukcja for

Od instrukcji while czasami wygodniejsza jest instrukcja for Umożliwia ona wpisanie usta-wiania zmiennej sprawdzania warunku i inkrementowania zmiennej w jednej linijce co czę-sto zwiększa czytelność kodu Instrukcję for stosuje się w następujący sposoacuteb

92 PĘTLE 61

for (wyrażenie1 wyrażenie2 wyrażenie3) instrukcje do wykonania w pętli

dalsze instrukcje

Jak widać pętla for znacznie roacuteżni się od tego typu pętli znanych w innych językachprogramowania Opiszemy więc co oznaczają poszczegoacutelne wyrażenia

wyrażenie mdash jest to instrukcja ktoacutera będzie wykonana przed pierwszym przebiegiempętli Zwykle jest to inicjalizacja zmiennej ktoacutera będzie służyła jako ldquolicznikrdquo przebie-goacutew pętli

wyrażenie mdash jest warunkiem zakończenia pętli Pętla wykonuje się tak długo jakprawdziwy jest ten warunek

wyrażenie mdash jest to instrukcja ktoacutera wykonywana będzie po każdym przejściu pętliZamieszczone są tu instrukcje ktoacutere zwiększają licznik o odpowiednią wartość

Jeżeli wewnątrz pętli nie ma żadnych instrukcji continue (opisanych niżej) to jest onaroacutewnoważna z

wyrażenie1while (wyrażenie2)

instrukcje do wykonania w pętli wyrażenie3

dalsze instrukcje

Ważną rzeczą jest tutaj to żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla forPoczątkującym programistom nieznajomość tego faktu sprawia wiele problemoacutew

W pierwszej kolejności w pętli for wykonuje się wyrażenie1 Wykonuje się ono zawszenawet jeżeli warunek przebiegu pętli jest od samego początku fałszywy Po wykonaniuwyrażenie1 pętla for sprawdza warunek zawarty w wyrażenie2 jeżeli jest on prawdziwyto wykonywana jest treść pętli for czyli najczęściej to co znajduje się między klamrami lubgdy ich nie ma następna pojedyncza instrukcja W szczegoacutelności musimy pamiętać że samśrednik też jest instrukcją mdash instrukcją pustą Gdy już zostanie wykonana treść pętli for na-stępuje wykonanie wyrażenie3 Należy zapamiętać że wyrażenie zostanie wykonane nawetjeżeli był to już ostatni obieg pętli Poniższe przykłady pętli for w rezultacie dadzą ten samwynik Wypiszą na ekran liczby od do

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 printf(d i++ ) )

Dwa pierwsze przykłady korzystają z własności struktury blokowej kolejny przykład jestjuż bardziej wyrafinowany i korzysta z tego że jako wyrażenie3może zostać podane dowolnebardziej skomplikowane wyrażenie zawierające w sobie inne podwyrażenia A oto kolejnyprogram ktoacutery najpierw wyświetla liczby w kolejności rosnącej a następnie wraca

62 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

include ltstdiohgtint main()int ifor(i=1 ilt=5 ++i)

printf(d i)

for( igt=1 i--)printf(d i)

return 0

Po analizie powyższego kodu początkujący programista może stwierdzić że pętla wy-pisze 123454321 Stanie się natomiast inaczej Wynikiem działania powyższego programubędzie ciąg cyfr 12345654321 Pierwsza pętla wypisze cyfry ldquordquo lecz po ostatnim swoimobiegu pętla for (tak jak zwykle) zinkrementuje zmienną i Gdy druga pętla przystąpi dopracy zacznie ona odliczać począwszy od liczby i= a nie By spowodować wyświetlanieliczb od do i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli fora pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej i o

Niech podsumowaniem będzie jakiś działający fragment kodu ktoacutery może obliczać war-tości kwadratoacutew liczb od do

include ltstdiohgt

int main ()

int afor (a=1 alt=10 ++a)

printf (dn aa)return 0

W kodzie źroacutedłowym spotyka się często inkrementację i++ Jest to zły zwyczaj biorącysię z wzorowania się na nazwie języka C++ Post-inkrementacja i++ powoduje że tworzonyjest obiekt tymczasowy ktoacutery jest zwracany jako wynik operacji (choć wynik ten nie jestnigdzie czytany) Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie ale wpętli ldquoforrdquo takie kopiowanie odbywa się po każdym przebiegu pętli Dodatkowo w C++ po-dobną konstrukcję stosuje się do obiektoacutew mdash kopiowanie obiektu może być już czasochłonnączynnością Dlatego w pętli ldquoforrdquo należy stosować wyłącznie ++i

923 Instrukcja dowhile

Pętle while i for mają jeden zasadniczy mankament mdash może się zdarzyć że nie wykonają sięani razu Aby mieć pewność że nasza pętla będzie miała co najmniej jeden przebieg musimyzastosować pętlę do while Wygląda ona następująco

92 PĘTLE 63

do instrukcje do wykonania w pętli

while (warunek) dalsze instrukcje

Zasadniczą roacuteżnicą pętli do while jest fakt iż sprawdza ona warunek pod koniec swojegoprzebiegu To właśnie ta cecha decyduje o tym że pętla wykona się co najmniej raz A terazprzykład działającego kodu ktoacutery tym razem będzie obliczał trzecią potęgę liczb od do

include ltstdiohgt

int main ()

int a = 1do

printf (dn aaa)++a

while (a lt= 10)return 0

Może się to wydać zaskakujące ale roacutewnież przy tej pętli zamiast bloku instrukcji możnazastosować pojedynczą instrukcję np

include ltstdiohgt

int main ()

int a = 1do printf (dn aaa) while (++a lt= 10)return 0

924 Instrukcja break

Instrukcja break pozwala na opuszczenie wykonywania pętli w dowolnym momencie Przy-kład użycia

int afor (a=1 a = 9 ++a)

if (a == 5) breakprintf (dn a)

Program wykona tylko przebiegi pętli gdyż przy przebiegu instrukcja break spowo-duje wyjście z pętli

Break i pętle nieskończone

W przypadku pętli for nie trzeba podawać warunku W takim przypadku kompilator przyj-mie że warunek jest stale spełniony Oznacza to że poniższe pętle są roacutewnoważne

64 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

for () for (1) for (aaa) gdzie a jest dowolną liczba rzeczywistą roacuteżną od 0while (1) do while (1)

Takie pętle nazywamy pętlami nieskończonymi ktoacutere przerwać może jedynie instrukcjabreak1(z racji tego że warunek pętli zawsze jest prawdziwy) 2

Wszystkie fragmenty kodu działają identycznie

int i = 0for (i=5++i)

kod

int i = 0for (++i)

if (i == 5) break

int i = 0for ()

if (i == 5) break++i

925 Instrukcja continue

W przeciwieństwie do break ktoacutera przerywa wykonywanie pętli instrukcja continue powo-duje przejście do następnej iteracji o ile tylko warunek pętli jest spełniony Przykład

int ifor (i = 0 i lt 100 ++i)

printf (Poczatekn)if (i gt 40) continue printf (Koniecn)

Dla wartości i większej od nie będzie wyświetlany komunikat ldquoKoniecrdquo Pętla wykonapełne przejść

Oto praktyczny przykład użycia tej instrukcji

include ltstdiohgtint main()

int i

1Tak naprawdę podobną operacje możemy wykonać za pomocą polecenia goto W praktyce jednak stosujesię zasadę że break stosuje się do przerwania działania pętli i wyjścia z niej goto stosuje się natomiast wtedykiedy chce się wydostać się z kilku zagnieżdżonych pętli za jednym zamachem Do przerwania pracy pętli mogąnam jeszcze posłużyć polecenia exit() lub return ale woacutewczas zakończymy nie tylko działanie pętli ale i całegoprogramufunkcji

2Żartobliwie można powiedzieć że stosując pętlę nieskończoną to najlepiej korzystać z pętli for() gdyżwymaga ona napisania najmniejszej liczby znakoacutew w poroacutewnaniu do innych konstrukcji

93 INSTRUKCJA GOTO 65

for (i = 1 i lt= 50 ++i) if (i4==0) continue printf (d i)

return 0

Powyższy program generuje liczby z zakresu od do ktoacutere nie są podzielne przez

93 Instrukcja goto

Istnieje także instrukcja ktoacutera dokonuje skoku do dowolnegomiejsca programu oznaczonegotzw etykietą

etykieta instrukcje goto etykieta

Uwaga kompilator w wersji i wyższych jest bardzo uczulony na etykiety za-mieszczone przed nawiasem klamrowym zamykającym blok instrukcji Innymi słowy nie-dopuszczalne jest umieszczanie etykiety zaraz przed klamrą ktoacutera kończy blok instrukcjizawartych np w pętli for Można natomiast stosować etykietę przed klamrą kończącą danąfunkcję

Instrukcja goto łamie sekwencję instrukcji i powoduje skok do dowolnie odległego miej-sca w programie - co może mieć nieprzewidziane skutki Zbyt częste używanie goto możeprowadzić do trudnych do zlokalizowania błędoacutew Oproacutecz tego kompilatory mają kłopotyz optymalizacją kodu w ktoacuterym występują skoki Z tego powodu zaleca się ograniczeniezastosowania tej instrukcji wyłącznie do opuszczania wielokrotnie zagnieżdżonych pętli

Przykład uzasadnionego użycia

int ijfor (i = 0 i lt 10 ++i)

for (j = i j lt i+10 ++j) if (i + j 21 == 0) goto koniec

koniec dalsza czesc programu

94 Natymiastowe kończenie programu mdash funkcja exit

Program może zostać w każdej chwili zakończony mdash do tego właśnie celu służy funkcja exitUżywamy jej następująco

exit (kod_wyjścia)

66 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

Liczba całkowita kod wyjścia jest przekazywana do procesu macierzystego dzięki czemudostaje on informację czy programw ktoacuterymwywołaliśmy tą funkcję zakończył się popraw-nie lub czy się tak nie stało Kody wyjścia są nieustandaryzowane i żeby program był w pełniprzenośny należy stosować makra EXIT SUCCESS i EXIT FAILURE choć na wielu systemach kod oznacza poprawne zakończenie a kod roacuteżny od błędne W każdym przypadku jeżeli naszprogram potrafi generować wiele roacuteżnych kodoacutew warto je wszystkie udokumentować w ewdokumentacji Są one też czasem pomocne przy wykrywaniu błędoacutew

95 Uwagi W języku C++ można deklarować zmienne w nagłoacutewku pętli ldquoforrdquo w następujący spo-

soacuteb for(int i=0 ilt10 ++i) (więcej informacji w C++Zmienne)

Rozdział 10

Podstawowe procedury wejścia iwyjścia

101 Wejściewyjście

Komputer byłby całkowicie bezużyteczny gdyby użytkownik nie moacutegł się z nim porozumieć(tj wprowadzić danych lub otrzymać wynikoacutew pracy programu) Programy komputerowesłużą w największym uproszczeniu do obroacutebki danych mdash więc muszą te dane jakoś od nasotrzymać przetworzyć i przekazać nam wynik

Takiewczytywanie i ldquowyrzucanierdquo danychw terminologii komputerowej nazywamywej-ściem (input) iwyjściem (output) Bardzo często moacutewi się o wejściu i wyjściu danych łączniemdash inputoutput albo po prostu IO

W C do komunikacji z użytkownikiem służą odpowiednie funkcje Zresztą do wielu za-dań w C służą funkcje Używając funkcji nie musimy wiedzieć w jaki sposoacuteb komputerwykonuje jakieś zadanie interesuje nas tylko to co ta funkcja robi Funkcje niejako ldquowyko-nują za nas część pracyrdquo ponieważ nie musimy pisać być może dziesiątek linijek kodu żebynp wypisać tekst na ekranie (wbrew pozorom mdash kod funkcji wyświetlającej tekst na ekraniejest dość skomplikowany) Jeszcze taka uwaga mdash gdy piszemy o jakiejś funkcji zazwyczajpodając jej nazwę dopisujemy na końcu nawias

printf()scanf()

żeby było jasne że chodzi o funkcję a nie o coś innego

Wyżej wymienione funkcje to jedne z najczęściej używanych funkcji w C mdash pierwszasłuży do wypisywania danych na ekran natomiast druga do wczytywania danych z klawia-tury1

1W zasadzie standard C nie definiuje czegoś takiego jak ekran i klawiatura mdash mowa w nim o standardowymwyjściu i standardowym wejściu Zazwyczaj jest to właśnie ekran i klawiatura ale nie zawsze W szczegoacutelności użyt-kownicy Linuksa lub innych systemoacutew uniksowych mogą być przyzwyczajeniu do przekierowania wejściawyjściazdo pliku czy łączenie komend w potoki (ang pipe) W takich sytuacjach dane nie są wyświetlane na ekranie aniodczytywane z klawiatury

67

68 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

102 Funkcje wyjścia

1021 Funkcja printf

W przykładzie ldquoHello Worldrdquo użyliśmy już jednej z dostępnych funkcji wyjścia a miano-wicie funkcji printf() Z punktu widzenia swoich możliwości jest to jedna z bardziej skom-plikowanych funkcji a jednocześnie jest jedną z najczęściej używanych Przyjrzyjmy sięponownie kodowi programu ldquoHello Worldrdquo

include ltstdiohgt

int main(void)

printf(Hello worldn)return 0

Po skompilowaniu i uruchomieniu program wypisze na ekranie

Hello world

W naszym przykładowym programie chcąc by funkcja printf() wypisała tekst na ekra-nie umieściliśmy go w cudzysłowach wewnątrz nawiasoacutew Ogoacutelnie wywołanie funkcjiprintf() wygląda następująco

printf(format argument1 argument2 )

Przykładowo

int i = 500printf(Liczbami całkowitymi są na przykład i oraz in 1 i)

wypisze

Liczbami całkowitymi są na przykład 1 oraz 500

Format to napis ujęty w cudzysłowy ktoacutery określa ogoacutelny kształt schemat tego co ma byćwyświetlone Format jest drukowany tak jak go napiszemy jednak niektoacutere znaki specjalnezostaną w nim podmienione na co innego Przykładowo znak specjalny n jest zamienianyna znak nowej linii 2 Natomiast procent jest podmieniany na jeden z argumentoacutew Po pro-cencie następuje specyfikacja jak wyświetlić dany argument W tym przykładzie i (od int)oznacza że argument ma być wyświetlony jak liczba całkowita W związku z tym że i mają specjalne znaczenie aby wydrukować je należy użyć ich podwoacutejnie

printf(Procent Backslash )

drukuje

Procent Backslash

(bez przejścia do nowej linii) Na liście argumentoacutew możemy mieszać ze sobą zmienne roacuteż-nych typoacutew liczby napisy itp w dowolnej liczbie Funkcja printf przyjmie ich tyle ile tylkonapiszemy Należy uważać by nie pomylić się w formatowaniu

2Zmiana ta następuje w momencie kompilacji programu i dotyczy wszystkich literałoacutew napisowych Nie jestto jakaś szczegoacutelna własność funkcji printf() Więcej o tego typu sekwencjach i ciągach znakoacutew w szczegoacutelnościopisane jest w rozdziale Napisy

103 FUNKCJA PUTS 69

int i = 5printf(i s i 5 4 napis) powinno być i i s

Przywłączeniu ostrzeżeń (opcja -Wall lub -WformatwGCC) kompilator powinien nas ostrzecgdy format nie odpowiada podanym elementom

Najczęstsze użycie printf()

printf(i i) gdy i jest typu int zamiast i można użyć d

printf(f i) gdy i jest typu float lub double

printf(c i) gdy i jest typu char (i chcemy wydrukować znak)

printf(s i) gdy i jest napisem (typu char)

Funkcja printf() nie jest żadną specjalną konstrukcją języka i łańcuch formatujący możebyć podany jako zmienna W związku z tym możliwa jest np taka konstrukcja

include ltstdiohgt

int main(void)

char buf[100]scanf(99s buf) funkcja wczytuje tekst do tablicy buf printf(buf)return 0

Program wczytuje tekst a następnie wypisuje go Jednak ponieważ znak procentu jesttraktowany w specjalny sposoacuteb toteż jeżeli na wejściu pojawi się ciąg znakoacutew zawierającyten znak mogą się stać roacuteżne dziwne rzeczy Między innymi z tego powodu w takich sytu-acjach lepiej używać funkcji puts() lub fputs() opisanych niżej lub wywołania printf(szmienna)

Więcej o funkcji printf()

103 Funkcja putsFunkcja puts() przyjmuje jako swoacutej argument ciąg znakoacutew ktoacutery następnie bezmyślnie wy-pisuje na ekran kończąc go znakiem przejścia do nowej linii W ten sposoacuteb nasz pierwszyprogram moglibyśmy napisać w ten sposoacuteb

include ltstdiohgt

int main(void)

puts(Hello world)return 0

W swoim działaniu funkcja ta jest w zasadzie identyczna do wywołania printf(snargument) jednak prawdopodobnie będzie działać szybciej Jedynym jejmankamentemmożebyć fakt że zawsze na końcu podawany jest znak przejścia do nowej linii Jeżeli jest to efektniepożądany (nie zawsze tak jest) należy skorzystać z funkcji fputs() opisanej niżej lub wy-wołania printf(s argument)

Więcej o funkcji puts()

70 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

104 Funkcja fputs

Opisując funkcję fputs() wybiegamy już trochę w przyszłość (a konkretnie do opisu operacjina plikach) ale warto o niej wspomnieć już teraz gdyż umożliwia ona wypisanie swojegoargumentu bez wypisania na końcu znaku przejścia do nowej linii

include ltstdiohgt

int main(void)

fputs(Hello worldn stdout)return 0

W chwili obecnej możesz się nie przejmować tym zagadkowym stdout wpisanym jakodrugi argument funkcji Jest to określenie strumienia wyjściowego (w naszymwypadku stan-dardowe wyjście mdash standard output)

Więcej o funkcji fputs()

1041 Funkcja putar

Funkcja putchar() służy do wypisywania pojedynczych znakoacutew Przykładowo jeżeli chcieli-byśmy napisać programwypisującyw prostej tabelce wszystkie liczby od do moglibyśmyto zrobić tak

include ltstdiohgt

int main(void)

int i = 0for ( ilt100 ++i) Nie jest to pierwsza liczba w wierszu if (i 10)

putchar( )printf(2d i) Jest to ostatnia liczba w wierszu if ((i 10)==9)

putchar(n)

return 0

Więcej o funkcji putchar()

105 FUNKCJE WEJŚCIA 71

105 Funkcje wejścia

1051 Funkcja scanf()

Teraz pomyślmy o sytuacji odwrotnej Tym razem to użytkownik musi powiedzieć coś pro-gramowi W poniższym przykładzie program podaje kwadrat liczby podanej przez użytkow-nika

include ltstdiohgt

int main ()

int liczba = 0printf (Podaj liczbę )scanf (d ampliczba)printf (dd=dn liczba liczba liczbaliczba)return 0

Zauważyłeś że w tej funkcji przy zmiennej pojawił się nowy operator mdash amp (etka) Jeston ważny gdyż bez niego funkcja scanf() nie skopiuje odczytanej wartości liczby do odpo-wiedniej zmiennej Właściwie oznacza przekazanie do funkcji adresu zmiennej by funkcjamogła zmienić jej wartość Nie musisz teraz rozumieć jak to się odbywa wszystko zostaniewyjaśnione w rozdziale Wskaźniki

Oznaczenia są podobne takie jak przy printf() czyli scanf(i ampliczba) wczytuje liczbętypu int scanf(f ampliczba) ndash liczbę typu float a scanf(s tablica znakoacutew) ciąg zna-koacutew Ale czemu w tym ostatnim przypadku nie ma etki Otoacuteż gdy podajemy jako argumentdo funkcji wyrażenie typu tablicowego zamieniane jest ono automatycznie na adres pierw-szego elementu tablicy Będzie to dokładniej opisane w rozdziale poświęconym wskaźnikom

Brak etki jest częstym błędem szczegoacutelnie wśroacuted początkujących programistoacutew Ponie-waż funkcja scanf() akceptuje zmienną liczbę argumentoacutew to nawet kompilator może miećkłopoty z wychwyceniem takich błędoacutew (konkretnie chodzi o to że standard nie wymagaod kompilatora wykrywania takich pomyłek) choć kompilator GCC radzi sobie z tym jeżelipodamy mu argument -Wformat

Należy jednak uważać na to ostatnie użycie Rozważmy na przykład poniższy kod

include ltstdiohgt

int main(void)

char tablica[100] 1 scanf(s tablica) 2 return 0

Robi on niewiele W linijce deklarujemy tablicę znakoacutew czyli mogącą przechowaćnapis długości znakoacutew Nie przejmuj się jeżeli nie do końca to wszystko rozumiesz mdash po-jęcia takie jak tablica czy ciąg znakoacutew staną się dla Ciebie jasne w miarę czytania kolejnych

72 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

rozdziałoacutew W linijce wywołujemy funkcję scanf() ktoacutera odczytuje tekst ze standardowegowejścia Nie zna ona jednak rozmiaru tablicy i nie wie ile znakoacutewmoże ona przechować przezco będzie czytać tyle znakoacutew aż napotka biały znak (format s nakazuje czytanie pojedyn-czego słowa) co może doprowadzić do przepełnienia bufora Niebezpieczne skutki czegośtakiego opisane są w rozdziale poświęconym napisom Na chwilę obecną musisz zapamiętaćżeby zaraz po znaku procentu podawać maksymalną liczbę znakoacutew ktoacutere może przechowaćbufor czyli liczbę o jeden mniejszą niż rozmiar tablicy Bezpieczna wersją powyższego kodujest

include ltstdiohgt

int main(void)

char tablica[100]scanf(99s tablica)return 0

Funkcja scanf() zwraca liczbę poprawnie wczytanych zmiennych lub EOF jeżeli nie majuż danych w strumieniu lub nastąpił błąd Załoacuteżmy dla przykładu że chcemy stworzyćprogram ktoacutery odczytuje po kolei liczby i wypisuje ich potęgi W pewnym momenciedane się kończą lub jest wprowadzana niepoprawna dana i woacutewczas nasz program powinienzakończyć działanie Aby to zrobić należy sprawdzać wartość zwracaną przez funkcję scanf()w warunku pętli

include ltstdiohgt

int main(void)

int nwhile (scanf(d ampn)==1)printf(dn nnn)

return 0

Podobnie możemy napisać program ktoacutery wczytuje po dwie liczby i je sumuje

include ltstdiohgt

int main(void)

int a bwhile (scanf(d d ampa ampb)==2)printf(dn a+b)

return 0

105 FUNKCJE WEJŚCIA 73

Rozpatrzmy teraz trochę bardziej skomplikowany przykład Otoacuteż ponownie jak poprzed-nio nasz program będzie wypisywał potęgę podanej liczby ale tym razem musi ignorowaćbłędne dane (tzn pomijać ciągi znakoacutew ktoacutere nie są liczbami) i kończyć działanie tylko wmomencie gdy nastąpi błąd odczytu lub koniec pliku3

include ltstdiohgt

int main(void)

int result ndoresult = scanf(d ampn)if (result==1)

printf(dn nnn)else if (result) result to to samo co result==0

result = scanf(s)

while (result=EOF)return 0

Zastanoacutewmy się przez chwilę co się dzieje w programie Najpierw wywoływana jestfunkcja scanf() i następuje proacuteba odczytu liczby typu int Jeżeli funkcja zwroacuteciła to liczbazostała poprawnie odczytana i następuje wypisanie jej trzeciej potęgi Jeżeli funkcja zwroacuteciła to na wejściu były jakieś dane ktoacutere nie wyglądały jak liczba W tej sytuacji wywołujemyfunkcję scanf() z formatem odczytującym dowolny ciąg znakoacutew nie będący białymi znakamiz jednoczesnym określeniem żeby nie zapisywała nigdzie wyniku W ten sposoacuteb niepopraw-nie wpisana dana jest omijana Pętla głoacutewna wykonuje się tak długo jak długo funkcja scanf()nie zwroacuteci wartości EOF

Więcej o funkcji scanf()

1052 Funkcja gets

Funkcja gets służy do wczytania pojedynczej linii Może Ci się to wydać dziwne ale funkcjitej nie należy używać pod żadnym pozorem Przyjmuje ona jeden argument mdash adres pierw-szego elementu tablicy do ktoacuterego należy zapisać odczytaną linię mdash i nic poza tym Z tegopowodu nie ma żadnej możliwości przekazania do tej funkcji rozmiaru bufora podanego jakoargument Podobnie jak w przypadku scanf() może to doprowadzić do przepełnienia buforaco może mieć tragiczne skutki Zamiast tej funkcji należy używać funkcji fgets()

Więcej o funkcji gets()

1053 Funkcja fgets

Funkcja fgets() jest bezpieczną wersją funkcji gets() ktoacutera dodatkowo może operować nadowolnych strumieniach wejściowych Jej użycie jest następujące

3Jak rozroacuteżniać te dwa zdarzenia dowiesz się w rozdziale Czytanie i pisanie do plikoacutew

74 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

fgets(tablica_znakoacutew rozmiar_tablicy_znakoacutew stdin)

Na chwilę obecną nie musisz się przejmować ostatnim argumentem (jest to określeniestrumienia w naszym przypadku standardowewejście mdash standard input) Funkcja czyta tekstaż do napotkania znaku przejścia do nowej linii ktoacutery także zapisuje w wynikowej tablicy(funkcja gets() tego nie robi) Jeżeli brakuje miejsca w tablicy to funkcja przerywa czytanie wten sposoacuteb aby sprawdzić czy została wczytana cała linia czy tylko jej część należy sprawdzićczy ostatnim znakiem nie jest znak przejścia do nowej linii Jeżeli nastąpił jakiś błąd lub nawejściu nie ma już danych funkcja zwraca wartość NULL

include ltstdiohgt

int main(void) char buffer[128] whole_line = 1 chwhile (fgets(buffer sizeof buffer stdin)) 1

if (whole_line) 2 putchar(gt)if (buffer[0]=gt)

putchar( )

fputs(buffer stdout) 3 for (ch = buffer ch ampamp ch=n ++ch) 4 whole_line = ch == n

if (whole_line)

putchar(n)return 0

Powyższy kod wczytuje dane ze standardowego wejścia mdash linia po linii mdash i dodaje napoczątku każdej linii znak większości po ktoacuterym dodaje spację jeżeli pierwszym znakiem nalinii nie jest znak większości W linijce następuje odczytywanie linii Jeżeli nie ma już wię-cej danych lub nastąpił błąd wejścia funkcja zwraca wartość NULL ktoacutera ma logiczną war-tość i woacutewczas pętla kończy działanie W przeciwnym wypadku funkcja zwraca po prostupierwszy argument ktoacutery ma wartość logiczną W linijce sprawdzamy czy poprzedniewywołanie funkcji wczytało całą linię czy tylko jej część mdash jeżeli całą to teraz jesteśmy napoczątku linii i należy dodać znakwiększości W linii najzwyczajniej w świecie wypisujemylinię W linii przeszukujemy tablicę znak po znaku aż do momentu gdy znajdziemy znako kodzie kończącym ciąg znakoacutew albo znak przejścia do nowej linii Ten drugi przypadekoznacza że funkcja fgets() wczytała całą linię

Więcej o funkcji fgets()

1054 Funkcja getar()

Jest to bardzo prosta funkcja wczytująca znak z klawiatury W wielu przypadkach danemogą być buforowane przez co wysyłane są do programu dopiero gdy bufor zostaje przepeł-niony lub na wejściu jest znak przejścia do nowej linii Z tego powodu po wpisaniu danegoznaku należy nacisnąć klawisz enter aczkolwiek trzeba pamiętać że w następnym wywoła-niu zostanie zwroacutecony znak przejścia do nowej linii Gdy nastąpił błąd lub nie ma już więcej

105 FUNKCJE WEJŚCIA 75

danych funkcja zwraca wartość EOF (ktoacutera ma jednak wartość logiczną toteż zwykła pętlawhile (getchar()) nie da oczekiwanego rezultatu)

include ltstdiohgt

int main(void)

int cwhile ((c = getchar())=EOF)

if (c== ) c = _

putchar(c)

return 0

Ten prosty program wczytuje dane znak po znaku i zamienia wszystkie spacje na znakipodkreślenia Może wydać się dziwne że zmienną c zdefiniowaliśmy jako trzymającą typ inta nie char Właśnie taki typ (tj int) zwraca funkcja getchar() i jest to konieczne ponieważwartość EOF wykracza poza zakres wartości typu char (gdyby tak nie było to nie byłobymożliwości rozroacuteżnienia wartości EOF od poprawnie wczytanego znaku) Więcej o funkcjigetchar()

76 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

Rozdział 11

Funkcje

W matematyce pod pojęciem funkcji rozumiemy twoacuter ktoacutery pobiera pewną liczbę argumen-toacutew i zwraca wynik1 Jeśli dla przykładu weźmiemy funkcję sin(x) to x będzie zmiennąrzeczywistą ktoacutera określa kąt a w rezultacie otrzymamy inną liczbę rzeczywistą mdash sinustego kąta

W C funkcja (czasami nazywana podprogramem rzadziej procedurą) to wydzielona częśćprogramu ktoacutera przetwarza argumenty i ewentualnie zwraca wartość ktoacutera następnie możebyć wykorzystana jako argument w innych działaniach lub funkcjach Funkcja może posia-dać własne zmienne lokalne W odroacuteżnieniu od funkcji matematycznych funkcje w C mogązwracać dla tych samych argumentoacutew roacuteżne wartości

Po lekturze poprzednich części podręcznika zapewne moacutegłbyś podać kilka przykładoacutewfunkcji z ktoacuterych korzystałeś Były to np

funkcja printf() drukująca tekst na ekranie czy

funkcja main() czyli głoacutewna funkcja programu

Głoacutewną motywacją tworzenia funkcji jest unikanie powtarzania kilka razy tego samegokodu W poniższym fragmencie

for(i=1 i lt= 5 ++i) printf(d ii)

for(i=1 i lt= 5 ++i)

printf(d iii)for(i=1 i lt= 5 ++i)

printf(d ii)

widzimy że pierwsza i trzecia pętla for są takie same Zamiast kopiować fragment kodukilka razy (co jest mało wygodne i może powodować błędy) lepszym rozwiązaniem mogłobybyć wydzielenie tego fragmentu tak by można go było wywoływać kilka razy Tak właśniedziałają funkcje

Innym niemniej ważnym powodem używania funkcji jest rozbicie programu na frag-menty wg ich funkcjonalności Oznacza to że z jeden duży program dzieli się na mniejsze

1Aby nie urażać matematykoacutew sprecyzujmy że chodzi o relację między zbiorami X i Y (X jest dziedziną Y jestprzeciwdziedziną) takie że każdy element zbioru X jest w relacji z dokładnie jednym elementem ze zbioru Y

77

78 ROZDZIAŁ 11 FUNKCJE

funkcje ktoacutere są ldquowyspecjalizowanerdquo w wykonywaniu określonych czynności Dzięki temułatwiej jest zlokalizować błąd Ponadto takie funkcje można potem przenieść do innych pro-gramoacutew

111 Tworzenie funkcji

Dobrze jest uczyć się na przykładach Rozważmy następujący kod

int iloczyn (int x int y)

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

int iloczyn (int x int y) to nagłoacutewek funkcji ktoacutery opisuje jakie argumenty przyj-muje funkcja i jaką wartość zwraca (funkcja może przyjmować wiele argumentoacutew lecz możezwracać tylko jedną wartość)2 Na początku podajemy typ zwracanej wartości mdash u nas intNastępnie mamy nazwę funkcji i w nawiasach listę argumentoacutew

Ciało funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasachklamrowych Pierwszą instrukcją jest deklaracja zmiennej mdash jest to zmienna lokalna czyliniewidoczna poza funkcją Dalej przeprowadzamy odpowiednie działania i zwracamy rezul-tat za pomocą instrukcji return

1111 Ogoacutelnie

Funkcję w języku C tworzy się następująco

typ identyfikator (typ1 argument1 typ2 argument2 typ_n argument_n)

instrukcje

Oczywiście istnieje możliwość utworzenia funkcji ktoacutera nie posiada żadnych argumen-toacutew Definiuje się ją tak samo jak funkcję z argumentami z tą tylko roacuteżnicą że międzyokrągłymi nawiasami nie znajduje się żaden argument lub pojedyncze słoacutewko void mdash w de-finicji funkcji nie ma to znaczenia jednak w deklaracji puste nawiasy oznaczają że prototypnie informuje jakie argumenty przyjmuje funkcja dlatego bezpieczniej jest stosować słoacutewkovoid

Funkcje definiuje się poza głoacutewną funkcją programu (main) W języku C nie można two-rzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji)

1112 Procedury

Przyjęło się że procedura od funkcji roacuteżni się tym że ta pierwsza nie zwraca żadnej wartościZatem aby stworzyć procedurę należy napisać

2Bardziej precyzyjnie można powiedzieć że funkcja może zwroacutecić tylko jedną wartość typu prostego lub jedenadres do jakiegoś obiektu w pamięci

112 WYWOŁYWANIE 79

void identyfikator (argument1 argument2 argumentn)

instrukcje

void (z ang pusty proacuteżny) jest słowem kluczowym mającym kilka znaczeń w tym przy-padku oznacza ldquobrak wartościrdquo

Generalnie w terminologii C pojęcie ldquoprocedurardquo nie jest używane moacutewi się raczej ldquofunk-cja zwracająca voidrdquo

Jeśli nie podamy typu danych zwracanych przez funkcję kompilator domyślnie przyjmietyp int choć już w standardzie C nieokreślenie wartości zwracanej jest błędem

1113 Stary sposoacuteb definiowania funkcji

Zanim powstał standard ANSI C w liście parametroacutew nie podawało się typoacutew argumentoacutewa jedynie ich nazwy Roacutewnież z tamtych czasoacutew wywodzi się oznaczenie iż puste nawiasy(w prototypie funkcji nie w definicji) oznaczają że funkcja przyjmuje nieokreśloną liczbęargumentoacutew Tego archaicznego sposobu definiowania funkcji nie należy już stosować aleponieważ w swojej przygodzie z językiem C Czytelnik może się na nią natknąć a co więcejstandard nadal (z powodu zgodności z wcześniejszymi wersjami) dopuszcza taką deklaracjęto należy tutaj o niej wspomnieć Otoacuteż wygląda ona następująco

typ_zwracany nazwa_funkcji(argument1 argument2 argumentn)typ1 argumenty typ2 argumenty

instrukcje

Na przykład wcześniejsza funkcja iloczyn wyglądałaby następująco

int iloczyn(x y)int x y

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

Najpoważniejszą wadą takiego sposobu jest fakt że w prototypie funkcji niema podanychtypoacutew argumentoacutew przez co kompilator nie jest w stanie sprawdzić poprawności wywołaniafunkcji Naprawiono to (wprowadzając definicje takie jak je znamy obecnie) najpierw wjęzyku C++ a potem rozwiązanie zapożyczono w standardzie ANSI C z roku

112 WywoływanieFunkcje wywołuje się następująco

80 ROZDZIAŁ 11 FUNKCJE

identyfikator (argument1 argument2 argumentn)

Jeśli chcemy aby przypisać zmiennej wartość ktoacuterą zwraca funkcja należy napisać tak

zmienna = funkcja (argument1 argument2 argumentn)

Programiści mający doświadczenia np z językiem Pascal mogą popełniać błąd polegającyna wywoływaniu funkcji bez nawiasoacutew okrągłych gdy nie przyjmuje ona żadnych argumen-toacutew

Przykładowo mamy funkcję

void pisz_komunikat()

printf(To jest komunikatn)

Jeśli teraz ją wywołamy

pisz_komunikat ŹLE pisz_komunikat() dobrze

to pierwsze polecenie nie spowoduje wywołania funkcji Dlaczego Aby kompilator Czrozumiał że chodzi nam o wywołanie funkcji musimy po jej nazwie dodać nawiasy okrą-głe nawet gdy funkcja nie ma argumentoacutew Użycie samej nazwy funkcji ma zupełnie inneznaczenie mdash oznacza pobranie jej adresu W jakim celu O tym będzie mowa w rozdzialeWskaźniki

PrzykładA oto działający przykład ktoacutery demonstruje wiadomości podane powyżej

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

int m = suma (4 5)printf (4+5=dn m)return 0

113 Zwracanie wartościreturn to słowo kluczowe języka C

W przypadku funkcji służy ono do

przerwania funkcji (i przejścia do następnej instrukcji w funkcji wywołującej)

114 ZWRACANA WARTOŚĆ 81

zwroacutecenia wartości

W przypadku procedur powoduje przerwania procedury bez zwracania wartościUżycie tej instrukcji jest bardzo proste i wygląda tak

return zwracana_wartość

lub dla procedur

return

Możliwe jest użycie kilku instrukcji returnw obrębie jednej funkcji Wielu programistoacutewuważa jednak że lepsze jest użycie jednej instrukcji return na końcu funkcji gdyż ułatwia tośledzenie przebiegu programu

114 Zwracana wartość

W C zwykle przyjmuje się że oznacza poprawne zakończenie funkcji

return 0 funkcja zakończona sukcesem

a inne wartości oznaczają niepoprawne zakończenie

return 1 funkcja zakończona niepowodzeniem

Ta wartość może być wykorzystana przez inne instrukcje np if

115 Funkcja main()

Do tej pory we wszystkich programach istniała funkcja main() Po co tak właściwie onajest Otoacuteż jest to funkcja ktoacutera zostaje wywołana przez fragment kodu inicjującego pracęprogramu Kod ten tworzony jest przez kompilator i nie mamy na niego wpływu Istotnejest że każdy program w języku C musi zawierać funkcję main()

Istnieją dwa możliwe prototypy (nagłoacutewki) omawianej funkcji

int main(void)

int main(int argc char argv) 3

Argument argc jest liczbą nieujemną określającą ile ciągoacutew znakoacutew przechowywanychjest w tablicy argv Wyrażenie argv[argc] ma zawsze wartość Pierwszym elementemtablicy argv (o ile istnieje4) jest nazwa programu czy komenda ktoacuterą program został urucho-miony Pozostałe przechowują argumenty podane przy uruchamianiu programu

Zazwyczaj jeśli program uruchomimy poleceniem

program argument1 argument2

3Czasami można się spotkać z prototypem int main(int argc char argv char env) ktoacutery jest definio-wany w standardzie ale wykracza już poza standard C

4Inne standardy mogą wymuszać istnienie tego elementu jednak jeśli chodzi o standard języka C to nic nie stoina przeszkodzie aby argument argc miał wartość zero

82 ROZDZIAŁ 11 FUNKCJE

to argc będzie roacutewne ( argumenty + nazwa programu) a argv będzie zawierać napisy pro-gram argument argument umieszczone w tablicy indeksowanej od do

Weźmy dla przykładu program ktoacutery wypisuje to co otrzymuje w argumentach argc iargv

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) while (argv)

puts(argv++) Ewentualnie można użycint ifor (i = 0 iltargc ++i)

puts(argv[i])return EXIT_SUCCESS

Uruchomiony w systemie typu UNIX poleceniem test foo bar baz powinien wypisać

testfoobarbaz

Na razie nie musisz rozumieć powyższych kodoacutew i opisoacutew gdyż odwołują się do pojęćtakich jak tablica oraz wskaźnik ktoacutere opisane zostaną w dalszej części podręcznika

Co ciekawe funkcja main nie roacuteżni się zanadto od innych funkcji i tak jak inne możewołać sama siebie (patrz rekurencja niżej) przykładowo powyższy program można zapisaćtak5

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) if (argv)

puts(argv)return main(argc-1 argv+1)

else return EXIT_SUCCESS

Ostatnią rzeczą dotyczącą funkcji main jest zwracana przez nią wartość Już przy oma-wianiu pierwszego programu wspomniane zostało że jedynymi wartościami ktoacutere znaczą

5Jeżeli ktoś lubi ekstrawagancki kod ciało funkcji main można zapisać jako return argv puts(argv)main(argc-1 argv+1) EXIT SUCCESS ale nie radzimy stosować tak skomplikowanych i bądź co bądź małoczytelnych konstrukcji

116 DALSZE INFORMACJE 83

zawsze to samowewszystkich implementacjach języka są EXIT SUCCESS i EXIT FAILURE6

zdefiniowane w pliku nagłoacutewkowym stdlibh Wartość i EXIT SUCCESS oznaczają po-prawne zakończenie programu (co wcale nie oznacza że makro EXIT SUCCESS ma wartośćzero) natomiast EXIT FAILURE zakończenie błędne Wszystkie inne wartości są zależne odimplementacji

116 Dalsze informacjePoniżej przekażemy ci parę bardziej zaawansowanych informacji o funkcjach w C jeśli niemasz ochoty wgłębiać się w szczegoacuteły możesz spokojnie pominąć tę część i wroacutecić tu poacuteźniej

1161 Jak zwroacutecić kilka wartości

Jeśli chcesz zwroacutecić z funkcji kilka wartości musisz zrobić to w trochę inny sposoacuteb Ge-neralnie możliwe są dwa podejścia jedno to ldquoupakowanierdquo zwracanych wartości ndash możnastworzyć tak zwaną strukturę ktoacutera będzie przechowywała kilka zmiennych (jest to opisanew rozdziale Typy złożone) Prostszym sposobem jest zwracanie jednej z wartości w normalnysposoacuteb a pozostałych jako parametroacutew Za chwilę dowiesz się jak to zrobić jeśli chcesz zo-baczyć przykład możesz przyjrzeć się funkcji scanf() z biblioteki standardowej

1162 Przekazywanie parametroacutew

Gdy wywołujemy funkcję wartość argumentoacutew z ktoacuterymi ją wywołujemy jest kopiowanado funkcji Kopiowana mdash to znaczy że nie możemy normalnie zmienić wartości zewnętrz-nych dla funkcji zmiennych Formalnie moacutewi się że w C argumenty są przekazywane przezwartość czyli wewnątrz funkcji operujemy tylko na ich kopiach

Możliwe jest modyfikowanie zmiennych przekazywanych do funkcji jako parametry mdashale do tego w C potrzebne są wskaźniki

1163 Funkcje rekurencyjne

Język Cmamożliwość tworzenia tzw funkcji rekurencyjny Jest to funkcja ktoacuteraw swojejwłasnej definicji (ciele) wywołuje samą siebie Najbardziej klasycznym przykładem może tubyć silnia Napiszmy sobie zatem naszą funkcję rekurencyjną ktoacutera oblicza silnię

int silnia (int liczba)

int silif (liczbalt0) return 0 wywołanie jest bezsensowne

zwracamy 0 jako kod błędu if (liczba==0 || liczba==1) return 1sil = liczbasilnia(liczba-1)return sil

Musimy być ostrożni przy funkcjach rekurencyjnych gdyż łatwo za ich pomocą utworzyćfunkcję ktoacutera będzie sama siebie wywoływała w nieskończoność a co za tym idzie będzie za-wieszała program Tutaj pierwszymi instrukcjami if ustalamy ldquowarunki stopurdquo gdzie kończy

6Uwaga Makra EXIT SUCCESS i EXIT FAILURE te służą tylko i wyłącznie jako wartości do zwracania przezfunkcję main() Nigdzie indziej nie mają one zastosowania

84 ROZDZIAŁ 11 FUNKCJE

się wywoływanie funkcji przez samą siebie a następnie określamy jak funkcja będzie wy-woływać samą siebie (odjęcie jedynki od argumentu co do ktoacuterego wiemy że jest dodatnigwarantuje że dojdziemy do warunku stopu w skończonej liczbie krokoacutew)

Warto też zauważyć że funkcje rekurencyjne czasami mogą być znacznie wolniejsze niżpodejście nierekurencyjne (iteracyjne przy użyciu pętli) Flagowym przykłademmoże tu byćfunkcja obliczająca wyrazy ciągu Fibonacciego

include ltstdiohgt

unsigned count

unsigned fib_rec(unsigned n) ++countreturn nlt2 n (fib_rec(n-2) + fib_rec(n-1))

unsigned fib_it (unsigned n) unsigned a = 0 b = 0 c = 1++countif (n) return 0while (--n)

++counta = bb = cc = a + b

return c

int main(void) unsigned n resultprintf(Ktory element ciagu Fibonacciego obliczyc )while (scanf(d ampn)==1)

count = 0result = fib_rec(n)printf(fib_ret(3u) = 6u (wywolan 5u)n n result count)

count = 0result = fib_it (n)printf(fib_it (3u) = 6u (wywolan 5u)n n result count)

return 0

W tym przypadku funkcja rekurencyjna choć łatwiejsza w napisaniu jest bardzo nie-efektywna

116 DALSZE INFORMACJE 85

1164 Deklarowanie funkcji

Czasami możemy chcieć przed napisaniem funkcji poinformować kompilator że dana funkcjaistnieje Niekiedy kompilator może zaprotestować jeśli użyjemy funkcji przed określeniemjaka to funkcja na przykład

int a()

return b(0)

int b(int p)

if( p == 0 )return 1

elsereturn a()

int main()

return b(1)

W tym przypadku nie jesteśmy w stanie zamienić a i b miejscami bo obie funkcje korzy-stają z siebie nawzajem Rozwiązaniem jest wcześniejsze zadeklarowanie funkcji Deklara-cja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji(przed otwierającym nawiasem klamrowym) z dodatkowo dodanym średnikiem na końcu Wnaszym przykładzie wystarczy na samym początku wstawić

int b(int p)

W deklaracji można pominąć nazwy parametroacutew funkcji

int b(int)

Bardzo częstym zwyczajem jest wypisanie przed funkcją main samych prototypoacutew funk-cji by ich definicje umieścić po definicji funkcji main np

int a(void)int b(int p)

int main()

return b(1)

int a()

return b(0)

86 ROZDZIAŁ 11 FUNKCJE

int b(int p)

if( p == 0 )return 1

elsereturn a()

Z poprzednich rozdziałoacutew pamiętasz że na początku programu dołączaliśmy tzw plikinagłoacutewkowe Zawierają one właśnie prototypy funkcji i ułatwiają pisanie dużych progra-moacutew Dalsze informacje o plikach nagłoacutewkowych zawarte są w rozdziale Tworzenie biblio-tek

1165 Zmienna liczba parametroacutew

Zauważyłeś zapewne że używając funkcji printf() lub scanf() po argumencie zawierającymtekst z odpowiednimi modyfikatorami mogłeś podać praktycznie nieograniczoną liczbę ar-gumentoacutew Zapewne deklaracja obu funkcji zadziwi Cię jeszcze bardziej

int printf(const char format )int scanf(const char format )

Jak widzisz w deklaracji zostały użyte kropki Otoacuteż język C ma możliwość przekazywa-nia nieograniczonej liczby argumentoacutew do funkcji (tzn jedynym ograniczeniem jest rozmiarstosu programu) Cała zabawa polega na tym aby umieć dostać się do odpowiedniego ar-gumentu oraz poznać jego typ (używając funkcji printf mogliśmy wpisać jako argument do-wolny typ danych) Do tego celu możemy użyć wszystkich ciekawostek zawartych w plikunagłoacutewkowym stdargh

Załoacuteżmy że chcemy napisać prostą funkcję ktoacutera dajmy na to mnoży wszystkie swojeargumenty (zakładamy że argumenty są typu int) Przyjmujemy przy tym że ostatni argu-ment będzie Będzie ona wyglądała tak

include ltstdarghgt

int mnoz (int pierwszy )

va_list argint iloczyn = 1 tva_start (arg pierwszy)for (t = pierwszy t t = va_arg(arg int))

iloczyn = tva_end (arg)return iloczyn

va list oznacza specjalny typ danych w ktoacuterym przechowywane będą argumenty prze-kazane do funkcji ldquova startrdquo inicjuje arg do dalszego użytku Jako drugi parametr musimypodać nazwę ostatniego znanego argumentu funkcji Makropolecenie va arg odczytuje ko-lejne argumenty i przekształca je do odpowiedniego typu danych Na zakończenie używanejest makro va end mdash jest ono obowiązkowe

117 ZOBACZ TEŻ 87

Oczywiście tak samo jak w przypadku funkcji printf() czy scanf() argumenty nie musząbyć takich samych typoacutew Rozważmy dla przykładu funkcję podobną do printf() ale znacznieuproszczoną

include ltstdarghgt

void wypisz(const char format ) va_list argva_start (arg format)for ( format ++format)

switch (format) case i printf(d va_arg(arg int)) breakcase I printf(u va_arg(arg unsigned)) breakcase l printf(ld va_arg(arg int)) breakcase L printf(lu va_arg(arg unsigned long)) breakcase f printf(f va_arg(arg double)) breakcase x printf(x va_arg(arg unsigned)) breakcase X printf(X va_arg(arg unsigned)) breakcase s printf(s va_arg(arg const char )) breakdefault putc(format)

va_end (arg)

Przyjmuje ona jako argument ciąg znakoacutew w ktoacuterych niektoacutere instruują funkcję bypobrała argument i go wypisała Nie przejmuj się jeżeli nie rozumiesz wyrażeń format i++format Istotne jest to że pętla sprawdza po kolei wszystkie znaki formatu

1166 Ezoteryka C

C ma wiele niuansoacutew o ktoacuterych wielu programistoacutew nie wie lub łatwo o nich zapomina

jeśli nie podamy typu wartości zwracanej w funkcji zostanie przyjęty typ int (wedługnajnowszego standardu C nie podanie typu wartości jest zwracane jako błąd)

jeśli nie podamy żadnych parametroacutew funkcji to funkcja będzie używała zmiennej ilo-ści parametroacutew (inaczej niż wC++ gdzie przyjęte zostanie że funkcja nie przyjmuje ar-gumentoacutew) Aby wymusić pustą listę argumentoacutew należy napisać int funkcja(void)(dotyczy to jedynie prototypoacutew czy deklaracji funkcji)

jeśli nie użyjemy w funkcji instrukcji return wartość zwracana będzie przypadkowa(dostaniemy śmieci z pamięci)

Kompilator C++ użyty do kompilacji kodu C najczęściej zaprotestuje i ostrzeże nas jeśliużyjemy powyższych konstrukcji Natomiast czysty kompilator C z domyślnymi ustawie-niami nie napisze nic i bez mrugnięcia okiem skompiluje taki kod

117 Zobacz też C++Funkcje inline mdash funkcje rozwijane wmiejscu wywoływania (dostępne też w stan-

dardzie C)

88 ROZDZIAŁ 11 FUNKCJE

C++Przeciążanie funkcji

Rozdział 12

Preprocesor

121 Wstęp

W języku C wszystkie linijki zaczynające się od symbolu rdquo nie podlegają bezpośrednio pro-cesowi kompilacji Są to natomiast instrukcje preprocesora mdash elementu kompilatora ktoacuteryanalizuje plik źroacutedłowy w poszukiwaniu wszystkich wyrażeń zaczynających się od rdquo Napodstawie tych instrukcji generuje on kod w czystymrdquo języku C ktoacutery następnie jest kom-pilowany przez kompilator Ponieważ za pomocą preprocesora można niemal sterowaćrdquokompilatorem daje on niezwykłe możliwości ktoacutere nie były dotąd znane w innych językachprogramowania Aby przekonać się jak wygląda kod przetworzony przez preprocesor użyj(w kompilatorze gcc) przełącznika -Erdquo

gcc testc -E -o testtxt

W pliku testtxt zostanie umieszczony cały kod w postaci ktoacutera zdatna jest do przetwo-rzenia przez kompilator

122 Dyrektywy preprocesora

Dyrektywy preprocesora są towyrażenia ktoacutere występują zaraz za symbolem rdquo i to właśnieza ich pomocą możemy używać preprocesora Dyrektywa zaczyna się od znaku i kończysię wraz z końcem linii Aby przenieść dalszą część dyrektywy do następnej linii należyzakończyć linię znakiem rdquo

define add(ab) a+b

Omoacutewimy teraz kilka ważniejszych dyrektyw

1221 include

Najpopularniejsza dyrektywa wstawiająca w swoje miejsce treść pliku podanego w nawia-sach ostrych lub cudzysłowie Składnia

89

90 ROZDZIAŁ 12 PREPROCESOR

Przykład 1

include ltplik_naglowkowy_do_dolaczeniagt

Za pomocą include możemy dołączyć dowolny plik mdash niekoniecznie plik nagłoacutewkowy

Przykład 2

include plik_naglowkowy_do_dolaczenia

Jeżeli nazwa pliku nagłoacutewkowego będzie ujęta w nawiasy ostre (przykład ) to kompi-lator poszuka go wśroacuted własnych plikoacutew nagłoacutewkowych (ktoacutere najczęściej się znajdują wpodkatalogu includesrdquo w katalogu kompilatora) Jeśli jednak nazwa ta będzie ujęta w po-dwoacutejne cudzysłowy(przykład ) to kompilator poszuka jej w katalogu w ktoacuterym znajdujesię kompilowany plik (można zmienić to zachowanie w opcjach niektoacuterych kompilatoroacutew)Przy użyciu tej dyrektywy można także wskazać dokładne położenie plikoacutew nagłoacutewkowychpoprzez wpisanie bezwzględnej lub względnej ścieżki dostępu do tego pliku nagłoacutewkowego

Przykład 3 mdash ścieżka bezwzględna do pliku nagłoacutewkowego w Linuksie i w Windowsie

Opis W miejsce jednej i drugiej linijki zostanie wczytany plik umieszczony w danej lokali-zacji

include usrincludeplik_nagłoacutewkowyhinclude Cborlandincludesplik_nagłoacutewkowyh

Przykład 4 mdash ścieżka względna do pliku nagłoacutewkowego

Opis W miejsce linijki zostanie wczytany plik umieszczony w katalogu katalogrdquo a tenkatalog jest w katalogu z plikiem źroacutedłowym Inaczej moacutewiąc jeśli plik źroacutedłowy jest wkatalogu homeuserdokumentyzrodlardquo to plik nagłoacutewkowy jest umieszczony w kataloguhomeuserdokumentyzrodlakatalogrdquo

include katalog1plik_naglowkowyh

Przykład 5 mdash ścieżka względna do pliku nagłoacutewkowego

Opis Jeśli plik źroacutedłowy jest umieszczony w katalogu homeuserdokumentyzrodlardquo toplik nagłoacutewkowy znajduje się w katalogu homeuserdokumentykatalogkatalogrdquo

include katalog1katalog2plik_naglowkowyh

Więcej informacji możesz uzyskać w rozdziale Biblioteki

1222 define

Linia pozwalająca zdefiniować stałą funkcję lub słowo kluczowe ktoacutere będzie potem pod-mienione w kodzie programu na odpowiednią wartość lub może zostać użyte w instrukcjachwarunkowych dla preprocesora Składnia

define NAZWA_STALEJ WARTOSC

lub

define NAZWA_STALEJ

122 DYREKTYWY PREPROCESORA 91

Przykład

define LICZBA mdash spowoduje że każde wystąpienie słowa LICZBA w kodzie zostanie za-stąpione oacutesemkądefine SUMA(ab) (a+b) mdash spowoduje ze każde wystąpienie wywołania funkcjirdquo SUMA zo-stanie zastąpione przez sumę argumentoacutew

1223 undef

Ta instrukcja odwołuje definicję wykonaną instrukcją define

undef STALA

1224 instrukcje warunkowe

Preprocesor zawiera roacutewnież instrukcje warunkowe pozwalające nawyboacuter tego coma zostaćskompilowane w zależności od tego czy stała jest zdefiniowana lub jaką ma wartość

if elif else endif

Te instrukcje uzależniają kompilacje od warunkoacutew Ich działanie jest podobne do instrukcjiwarunkowych w samym języku C I tak

if wprowadza warunek ktoacutery jeśli nie jest prawdziwy powoduje pominięcie kompilowaniakodu aż do napotkania jednej z poniższych instrukcji

else spowoduje skompilowanie kodu jeżeli warunek za if jest nieprawdziwy aż do napo-tkania ktoacuteregoś z poniższych instrukcji

elif wprowadza nowy warunek ktoacutery będzie sprawdzony jeżeli poprzedni był niepraw-dziwy Stanowi połączenie instrukcji if i else

endif zamyka blok ostatniej instrukcji warunkowej

Przykład

if INSTRUKCJE == 2printf (Podaj liczbę z przedziału 10 do 0n) 1

elif INSTRUKCJE == 1printf (Podaj liczbę ) 2

elseprintf (Podaj parametr ) 3

endifscanf (dn ampliczba)4

wiersz nr zostanie skompilowany jeżeli stała INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany gdy INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany w pozostałych wypadkach

wiersz nr będzie kompilowany zawsze

92 ROZDZIAŁ 12 PREPROCESOR

ifdef ifndef else endif

Te instrukcje warunkują kompilację od tego czy odpowiednia stała została zdefiniowana

ifdef spowoduje że kompilator skompiluje poniższy kod tylko gdy została zdefiniowanaodpowiednia stała

ifndef ma odwrotne działanie do ifdef a mianowicie brak definicji odpowiedniej stałejumożliwia kompilacje poniższego kodu

elseendif mają identyczne zastosowanie jak te z powyższej grupy

Przykład

define INFO definicja stałej INFOifdef INFO

printf (Twoacutercą tego programu jest Jan Kowalskin)1endififndef INFO

printf (Twoacutercą tego programu jest znany programistan)2endif

To czy dowiemy się kto jest twoacutercą tego programu zależy czy instrukcja definiująca stałąINFO będzie istnieć W powyższym przypadku na ekranie powinno się wyświetlić

Twoacutercą tego programu jest Jan Kowalski

1225 error

Powoduje przerwanie kompilacji i wyświetlenie tekstu ktoacutery znajduje się za tą instrukcjąPrzydatne gdy chcemy zabezpieczyć się przed zdefiniowaniem nieodpowiednich stałych

Przykład

if BLAD == 1error Poważny błąd kompilacjiendif

Co jeżeli zdefiniujemy stałą BLAD z wartością Spowoduje to wyświetlenie w trakcie kom-pilacji komunikatu podobnego do poniższego

Fatal error programc 6 Error directive Poważny błąd kompilacjiin function main() 1 errors in Compile

wraz z przerwaniem kompilacji

1226 warning

Wyświetla tekst zawarty w cudzysłowach jako ostrzeżenie Jest często używany do sygna-lizacji programiście że dana część programu jest przestarzała lub może sprawiać problemy

122 DYREKTYWY PREPROCESORA 93

Przykład

warning To jest bardzo prosty program

Spowoduje to takie oto zachowanie kompilatora

testc32 warning warning To jest bardzo prosty program

Użycie dyrektywy warning nie przerywa procesu kompilacji i służy tylko do wyświetlaniakomunikatoacutew dla programisty w czasie kompilacji programu

1227 line

Powoduje wyzerowanie licznika linii kompilatora ktoacutery jest używany przy wyświetlaniuopisu błędoacutew kompilacji Pozwala to na szybkie znalezienie możliwej przyczyny błędu wrozbudowanym programie

Przykład

printf (Podaj wartość funkcji)lineprintf (W przedziale od 10 do 0n) tutaj jest błąd - brak cudzysłowu zamykającego

Jeżeli teraz nastąpi proacuteba skompilowania tego kodu to kompilator poinformuje że wystąpiłbłąd składni w linii a nie np

1228 Makra

Preprocesor języka C umożliwia też tworzenie makr czyli automatycznie wykonywanychczynności Makra deklaruje się za pomocą dyrektywy define

define MAKRO(arg1 arg2 ) (wyrażenie)

Wmomencie wystąpienia MAKRA w tekście preprocesor automatycznie zamieni makrona wyrażenie Makra mogą być pewnego rodzaju alternatywami dla funkcji ale powinnosię ich używać tylko w specjalnych przypadkach Ponieważ makro sprowadza się do pro-stego zastąpienia przez preprocesor wywołania makra przez jego tekst jest bardzo podatnena trudne do zlokalizowania błędy (kompilator będzie podawał błędy wmiejscach w ktoacuterychnic nie widzimy mdash bo preprocesor wstawił tam tekst) Makra są szybsze (nie następuje wy-wołanie funkcji ktoacutere zawsze zajmuje trochę czasu1) ale też mniej bezpieczne i elastyczneniż funkcje

Przeanalizujmy teraz fragment kodu

include ltstdiohgtdefine KWADRAT(x) ((x)(x))

int main ()

printf (2 do kwadratu wynosi dn KWADRAT(2))return 0

1Tak naprawdę wg standardu C99 istnieje możliwość napisania funkcji ktoacuterej kod także będzie wstawiany wmiejscu wywołania Odbywa się to dzięki inline

94 ROZDZIAŁ 12 PREPROCESOR

Preprocesor w miejsce wyrażenia KWADRAT(2) wstawił ((2)(2)) Zastanoacutewmy się costałoby się gdybyśmy napisali KWADRAT(2) Preprocesor po prostu wstawi napis do koduco da wyrażenie ((2)(2)) ktoacutere jest nieprawidłowe Kompilator zgłosi błąd ale pro-gramista widzi tylko w kodzie użycie makra a nie prawdziwą przyczynę błędu Widać tu żebezpieczniejsze jest użycie funkcji ktoacutere dają możliwość wyspecyfikowania typoacutew argumen-toacutew

Nawet jeżeli program się skompiluje to makro może dawać nieoczekiwany wynik Jesttak w przypadku poniższego kodu

int x = 1int y = KWADRAT(++x)

Dzieje się tak dlatego że makra rozwijane są przez preprocesor i kompilator widzi kod

int x = 1int y = ((++x)(++x))

Roacutewnież poniższe makra są błędne pomimo że opisany problem w nich nie występuje

define SUMA(a b) a + bdefine ILOCZYN(a b) a b

Dają one nieoczekiwane wyniki dla wywołań

SUMA(2 2) 2 6 zamiast 8 ILOCZYN(2 + 2 2 + 2) 8 zamiast 16

Z tego powodu istotne jest użycie nawiasoacutew

define SUMA(a b) ((a) + (b))define ILOCZYN(a b) ((a) (b))

1229 oraz

Dość ciekawe możliwości ma w makrach znak rdquo Zamienia on stojący za nim identyfikatorna napis

include ltstdiohgtdefine wypisz(x) printf(s=in x x)

int main()

int i=1char a=5wypisz(i)wypisz(a)return 0

Program wypisze

i=1a=5

123 PREDEFINIOWANE MAKRA 95

Czyli wypisz(a) jest rozwijane w printf(s=in a a)Natomiast znaki rdquo łączą dwie nazwy w jedną Przykład

include ltstdiohgtdefine abc(x) int zmienna x

int main()

abc(nasza) dzięki temu zadeklarujemy zmienną o nazwie zmiennanasza zmiennanasza = 2return 0

Więcej o dobrych zwyczajach w tworzeniu makr można się dowiedzieć w rozdziale Po-wszechne praktyki

123 Predefiniowane makraW języku wprowadzono roacutewnież serię predefiniowanych makr ktoacutere mają ułatwić życie pro-gramiście Oto one

DATE mdash data w momencie kompilacji

TIME mdash godzina w momencie kompilacji

FILE mdash łańcuch ktoacutery zawiera nazwę pliku ktoacutery aktualnie jest kompilowany przezkompilator

LINE mdash definiuje numer linijki

STDC mdash w kompilatorach zgodnych ze standardem ANSI lub nowszym makro toprzyjmuje wartość

STDC VERSION mdash zależnie od poziomu zgodności kompilatora makro przyjmuje roacuteżnewartości

ndash jeżeli kompilator jest zgodny z ANSI (rok ) makro nie jest zdefiniowane

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199409L

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199901L

Warto roacutewnież wspomnieć o identyfikatorze func zdefiniowanym w standardzie Cktoacuterego wartość to nazwa funkcji

Sproacutebujmy użyć tych makr w praktyce

include ltstdiohgt

if __STDC_VERSION__ gt= 199901L Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go define BUG(message) fprintf(stderr sd s (w funkcji s)n

__FILE__ __LINE__ message __func__)else Jezeli __func__ nie ma to go nie używamy

96 ROZDZIAŁ 12 PREPROCESOR

define BUG(message) fprintf(stderr sd sn __FILE__ __LINE__ message)

endif

int main(void) printf(Program ABC data kompilacji s sn __DATE__ __TIME__)

BUG(Przykladowy komunikat bledu)return 0

Efekt działania programu gdy kompilowany jest kompilatorem C

Program ABC data kompilacji Sep 1 2008 191213testc17 Przykladowy komunikat bledu (w funkcji main)

Gdy kompilowany jest kompilatorem ANSI C

Program ABC data kompilacji Sep 1 2008 191316testc17 Przykladowy komunikat bledu

Rozdział 13

Biblioteka standardowa

131 Czym jest biblioteka

Bibliotekę w języku C stanowi zbioacuter skompilowanych wcześniej funkcji ktoacutery można łączyćz programem Biblioteki tworzy się aby udostępnić zbioacuter pewnych ldquowyspecjalizowanychrdquofunkcji do dyspozycji innych programoacutew Tworzenie bibliotek jest o tyle istotne że takiepodejście znacznie ułatwia tworzenie nowych programoacutew Łatwiej jest utworzyć program woparciu o istniejące biblioteki niż pisać programwraz zewszystkimi potrzebnymi funkcjami1

132 Po co nam biblioteka standardowa

W ktoacuterymś z początkowych rozdziałoacutew tego podręcznika napisane jest że czysty język C niemoże zbyt wiele Tak naprawdę to język C sam w sobie praktycznie nie ma mechanizmoacutewdo obsługi np wejścia-wyjścia Dlatego też większość systemoacutew operacyjnych posiada tzwbibliotekę standardową zwaną też biblioteką języka C To właśnie w niej zawarte są pod-stawowe funkcjonalności dzięki ktoacuterym twoacutej program może np napisać coś na ekranie

1321 Jak skonstruowana jest biblioteka standardowa

Zapytacie zapewne jak biblioteka standardowa realizuje te funkcje skoro sam język C tegonie potrafi Odpowiedź jest prosta mdash biblioteka standardowa nie jest napisana w samym ję-zyku C Ponieważ C jest językiem tłumaczonym do kodu maszynowego to w praktyce niema żadnych przeszkoacuted żeby np połączyć go z językiem niskiego poziomu jakim jest npasembler Dlatego biblioteka C z jednej strony udostępnia gotowe funkcje w języku C a zdrugiej za pomocą niskopoziomowych mechanizmoacutew2 komunikuje się z systemem operacyj-nym ktoacutery wykonuje odpowiednie czynności

133 Gdzie są funkcje z biblioteki standardowej

Pisząc program w języku C używamy roacuteżnego rodzaju funkcji takich jak np printf Niejesteśmy jednak ich autorami mało tego nie widzimy nawet deklaracji tych funkcji w naszymprogramie Pamiętacie program ldquoHello worldrdquo Zaczynał on się od takiej oto linijki

1Początkujący programista zapewne nie byłby w stanie napisać nawet funkcji printf2Takich jak np wywoływanie przerwań programowych

97

98 ROZDZIAŁ 13 BIBLIOTEKA STANDARDOWA

include ltstdiohgt

linijka ta oznacza ldquow tym miejscu wstaw zawartość pliku stdiohrdquo Nawiasy ldquoltrdquo i ldquogtrdquooznaczają że plik stdioh znajduje się w standardowym katalogu z plikami nagłoacutewkowymiWszystkie pliki z rozszerzeniem h są właśnie plikami nagłoacutewkowymi Wroacutećmy teraz do te-matu biblioteki standardowej Każdy system operacyjny ma za zadanie wykonywać pewnefunkcje na rzecz programoacutew Wszystkie te funkcje zawarte są właśnie w bibliotece standar-dowej W systemach z rodziny UNIX nazywa się ją LibC (biblioteka języka C) To tamwłaśnieznajduje się funkcja printf scanf puts i inne

Oproacutecz podstawowych funkcji wejścia-wyjścia biblioteka standardowa udostępnia teżmożliwość wykonywania funkcji matematycznych komunikacji przez sieć oraz wykonywa-nia wielu innych rzeczy

1331 Jeśli biblioteka nie jest potrzebna

Czasami korzystanie z funkcji bibliotecznych oraz standardowych plikoacutew nagłoacutewkowych jestniepożądane np wtedy gdy programista pisze swoacutej własny system operacyjny oraz biblio-tekę do niego Aby wyłączyć używanie biblioteki C w opcjach kompilatora GCC możemydodać następujące argumenty

-nostdinc -fno-builtin

134 Opis funkcji biblioteki standardowejPodręcznik C na Wikibooks zawiera opis dużej części biblioteki standardowej C

Indeks alfabetyczny

Indeks tematyczny

W systemach uniksowych możesz uzyskać pomoc dzięki narzędziu man przykładowopisząc

man printf

135 UwagiProgramy w języku C++ mogą dokładnie w ten sam sposoacuteb korzystać z biblioteki standar-dowej ale zalecane jest by robić to raczej w trochę odmienny sposoacuteb właściwy dla C++Szczegoacuteły w podręczniku C++

Rozdział 14

Czytanie i pisanie do plikoacutew

141 Pojęcie plikuNa początku dobrze by było abyś dowiedział się czym jest plik Odpowiedni artykuł do-stępny jest w Wikipedii Najprościej moacutewiąc plik to pewne dane zapisane na dysku

142 Identyfikacja plikuKażdy z nas korzystając na co dzień z komputera przyzwyczaił się do tego że plik ma okre-śloną nazwę Jednak w pisaniu programu posługiwanie się całą nazwą niosło by ze sobą conajmniej dwa problemy

pamięciożerność mdash przechowywanie całego (czasami nawet -bajtowego łańcucha)zajmuje niepotrzebnie pamięć

ryzyko błędoacutew (owe błędy szerzej omoacutewione zostały w rozdziale Napisy)

Aby uprościć korzystanie z plikoacutew programiści wpadli na pomysł aby identyfikatorempliku stała się liczba Dzięki temu kod programu stał się czytelniejszy oraz wyeliminowanokonieczność ciągłego korzystania z łańcuchoacutew Jednak sam plik nadal jest identyfikowany poswojej nazwie Aby ldquoprzetworzyćrdquo nazwę pliku na odpowiednią liczbę korzystamy z funkcjiopen lub fopen Roacuteżnica wyjaśniona jest poniżej

143 Podstawowa obsługa plikoacutewIstnieją dwie metody obsługi czytania i pisania do plikoacutew

wysokopoziomowa

niskopoziomowa

Nazwy funkcji z pierwszej grupy zaczynają się od litery ldquordquo (np fopen() fread() fclose())a identyfikatorem pliku jest wskaźnik na strukturę typu FILE Owa struktura to pewna grupazmiennych ktoacutera przechowuje dane o danym pliku mdash jak na przykład aktualną pozycję wnim Szczegoacutełami nie musisz się przejmować funkcje biblioteki standardowej same zajmująsię wykorzystaniem struktury FILE programista może więc zapomnieć czym tak naprawdęjest struktura FILE i traktować taką zmienną jako ldquouchwytrdquo identyfikator pliku

99

100 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

Druga grupa to funkcje typu read() open() write() i close()Podstawowym identyfikatorem pliku jest liczba całkowita ktoacutera jednoznacznie identy-

fikuje dany plik w systemie operacyjnym Liczba ta w systemach typu jest nazywanadeskryptorem pliku

Należy pamiętać że nie wolno nam używać funkcji z obu tych grup jednocześnie w sto-sunku do jednego otwartego pliku tzn nie można najpierw otworzyć pliku za pomocą fo-pen() a następnie odczytywać danych z tego samego pliku za pomocą read()

Czym roacuteżnią się oba podejścia do obsługi plikoacutew Otoacuteż metoda wysokopoziomowa maswoacutej własny bufor w ktoacuterym znajdują się dane po odczytaniu z dysku a przed wysłaniemich do programu użytkownika W przypadku funkcji niskopoziomowych dane kopiowane sąbezpośrednio z pliku do pamięci programu W praktyce używanie funkcji wysokopoziomo-wych jest prostsze a przy czytaniu danych małymi porcjami roacutewnież często szybsze i właśnieten model zostanie tutaj zaprezentowany

1431 Dane znakowe

Skupimy się teraz na najprostszym zmożliwych zagadnień mdash zapisie i odczycie pojedynczychznakoacutew oraz całych łańcuchoacutew

Napiszmy zatem nasz pierwszy program ktoacutery stworzy plik ldquotesttxtrdquo i umieści w nimtekst ldquoHello worldrdquo

include ltstdiohgtinclude ltstdlibhgt

int main ()

FILE fp używamy metody wysokopoziomowej musimy mieć zatem identyfikator pliku uwaga na gwiazdkę

char tekst[] = Hello worldif ((fp=fopen(testtxt w))==NULL)

printf (Nie mogę otworzyć pliku testtxt do zapisun)exit(1)

fprintf (fp s tekst) zapisz nasz łańcuch w pliku fclose (fp) zamknij plik return 0

Teraz omoacutewimy najważniejsze elementy programu Jak już było wspomniane wyżej doidentyfikacji pliku używa się wskaźnika na strukturę FILE (czyli FILE ) Funkcja fopenzwraca oacutew wskaźnik w przypadku poprawnego otwarcia pliku bądź też NULL gdy plik niemoże zostać otwarty Pierwszy argument funkcji to nazwa pliku natomiast drugi to trybdostępu mdash w oznacza ldquowriterdquo (pisanie) zwroacutecony ldquouchwytrdquo do pliku będzie moacutegł być wyko-rzystany jedynie w funkcjach zapisujących dane I odwrotnie gdy otworzymy plik podająctryb r (ldquoreadrdquo czytanie) będzie można z niego jedynie czytać dane Funkcja fopen zostaładokładniej opisana w odpowiedniej części rozdziału o bibliotece standardowej

Po zakończeniu korzystania z pliku należy plik zamknąć Robi się to za pomocą funk-cji fclose Jeśli zapomnimy o zamknięciu pliku wszystkie dokonane w nim zmiany zostanąutracone

143 PODSTAWOWA OBSŁUGA PLIKOacuteW 101

1432 Pliki a strumienie

Można zauważyć że do zapisu do pliku używamy funkcji fprintf ktoacutera wygląda bardzopodobnie do printf mdash jedyną roacuteżnicą jest to że w fprintf musimy jako pierwszy argu-ment podać identyfikator pliku Nie jest to przypadek mdash obie funkcje tak naprawdę robiątak samo Używana do wczytywania danych z klawiatury funkcja scanf też ma swoacutej od-powiednik wśroacuted funkcji operujących na plikach mdash jak nietrudno zgadnąć nosi ona nazwęfscanf

W rzeczywistości język C traktuje tak samo klawiaturę i plik mdash są to źroacutedła danych po-dobnie jak ekran i plik do ktoacuterych można dane kierować Jest to myślenie typowe dla sys-temoacutew typu UNIX jednak dla użytkownikoacutew przyzwyczajonych do systemu Windows albojęzykoacutew typu Pascal może być to co najmniej dziwne Nie da się ukryć że między klawia-turą i plikiem na dysku zachodzą podstawowe roacuteżnice i dostęp do nich odbywa się inaczejmdash jednak funkcje języka C pozwalają nam o tym zapomnieć i same zajmują się szczegoacutełamitechnicznymi Z punktu widzenia programisty urządzenia te sprowadzają się do nadanegoim identyfikatora Uogoacutelnione pliki nazywa się w C strumieniami

Każdy program w momencie uruchomienia ldquootrzymujerdquo od razu trzy otwarte strumienie

stdin (wejście)

stdout (wyjście)

stderr (wyjście błędoacutew)

(aby z nich korzystać należy dołączyć plik nagłoacutewkowy stdioh)Pierwszy z tych plikoacutew umożliwia odczytywanie danych wpisywanych przez użytkow-

nika natomiast pozostałe dwa służą do wyprowadzania informacji dla użytkownika oraz po-wiadamiania o błędach

Warto tutaj zauważyć że konstrukcja

fprintf (stdout Hej ja działam)

jest roacutewnoważna konstrukcji

printf (Hej ja działam)

Podobnie jest z funkcją scanf()

fscanf (stdin d ampzmienna)

działa tak samo jak

scanf(d ampzmienna)

1433 Obsługa błędoacutew

Jeśli nastąpił błąd możemy się dowiedzieć o jego przyczynie na podstawie zmiennej errnozadeklarowanej w pliku nagłoacutewkowym errnoh Możliwe jest też wydrukowanie komunikatuo błedzie za pomocą funkcji perror Na przykład używając

fp = fopen (tego pliku nie ma r)if( fp == NULL )

perror(błąd otwarcia pliku)exit(-10)

102 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

dostaniemy komunikat

błąd otwarcia pliku No such file or directory

1434 Zaawansowane operacje

Pora na kolejny tym razem bardziej złożony przykład Oto kroacutetki program ktoacutery swojewejście zapisuje do pliku o nazwie podanej w linii poleceń

include ltstdiohgtinclude ltstdlibhgt program udający bardzo prymitywną wersję programu tee(1)

int main (int argc char argv[])

FILE fpint cif (argc lt 2)

fprintf (stderr Uzycie s nazwa_plikun argv[0])exit (-1)

fp = fopen (argv[1] w)if (fp)

fprintf (stderr Nie moge otworzyc pliku sn argv[1])exit (-1)

printf(Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczycn)while ( (c = fgetc(stdin)) = EOF)

fputc (c stdout)fputc (c fp)

fclose(fp)return 0

Tym razem skorzystaliśmy już z dużo większego repertuaru funkcji Między innymimożna zauważyć tutaj funkcję fputc() ktoacutera umieszcza pojedynczy znak w pliku Ponadto wwyżej zaprezentowanym programie została użyta stała EOF ktoacutera reprezentuje koniec pliku(ang End Of File) Powyższy program otwiera plik ktoacuterego nazwa przekazywana jest jakopierwszy argument programu a następnie kopiuje dane z wejścia programu (stdin) na wyj-ście (stdout) oraz do utworzonego pliku (identyfikowanego za pomocą fp) Program robi todotąd aż naciśniemy kombinację klawiszy Ctrl+D(w systemach Unixowych) lub Ctrl+Z(wWindows) ktoacutera wyśle do programu informację że skończyliśmy wpisywać dane Programwyjdzie wtedy z pętli i zamknie utworzony plik

144 Rozmiar plikuDzięki standardowym funkcjom języka C możemy min określić długość pliku Do tego celusłużą funkcje fsetpos fgetpos oraz fseek Ponieważ przy każdym odczyciezapisie zdo plikuwskaźnik niejako ldquoprzesuwardquo się o liczbę przeczytanychzapisanych bajtoacutew Możemy jednak

145 PRZYKŁAD mdash PLIKI GRAFICZNY 103

ustawić wskaźnik w dowolnie wybranym miejscu Do tego właśnie służą wyżej wymienionefunkcje Aby odczytać rozmiar pliku powinniśmy ustawić nasz wskaźnik na koniec plikupo czym odczytać ile bajtoacutew od początku pliku się znajdujemy Wiem brzmi to strasznieale działa wyjątkowo prosto i skutecznie Użyjemy do tego tylko dwoacutech funkcji fseek orazfgetpos Pierwsza służy do ustawiania wskaźnika na odpowiedniej pozycji w pliku a drugado odczytywania na ktoacuterym bajcie pliku znajduje się wskaźnik Kod ktoacutery określa rozmiarpliku znajduje się tutaj

include ltstdiohgt

int main (int argc char argv)

FILE fp = NULLfpos_t dlugoscif (argc = 2)

printf (Użycie s ltnazwa plikugtn argv[0])return 1

if ((fp=fopen(argv[1] rb))==NULL) printf (Błąd otwarcia pliku sn argv[1])return 1

fseek (fp 0 SEEK_END) ustawiamy wskaźnik na koniec pliku fgetpos (fp ampdlugosc)printf (Rozmiar pliku dn dlugosc)fclose (fp)return 0

Znajomość rozmiaru pliku przydaje się w wielu roacuteżnych sytuacjach więc dobrze prze-analizuj przykład

145 Przykład mdash pliki graficznyNajprostszym przykładem rastrowego pliku graficznego jest plik Poniższy program po-kazuje jak utworzyć plik w katalogu roboczym programu Do zapisu

nagłoacutewka pliku używana jest funkcja fprintf

tablicy do pliku używana jest funkcja fwrite

include ltstdiohgtint main()

const int dimx = 800const int dimy = 800int i jFILE fp = fopen(firstppm wb) b - tryb binarny fprintf(fp P6nd dn255n dimx dimy)for(j=0 jltdimy ++j)

for(i=0 iltdimx ++i)static unsigned char color[3]

104 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

color[0]=i 255 red color[1]=j 255 green color[2]=(ij) 255 blue fwrite(color13fp)

fclose(fp)return 0

W powyższym przykładzie dostęp do danych jest sekwencyjny Jeśli chcemy mieć swo-bodny dostęp do danych to

korzystać z funkcji fsetpos fgetpos oraz fseek

utworzyć tablicę (dla dużych plikoacutew dynamiczną) zapisać do niej wszystkie dane anastępnie zapisać całą tablicę do pliku Ten sposoacuteb jest prostszy i szybszy Należyzwroacutecić uwagę że do obliczania rozmiaru całej tablicy nie możemy użyć funkcji sizeof

(a) Przykład użycia tej techniki sekwencyjnydostęp do danych (kod źroacutedłowy)

(b) Przykład użycia tej techniki swobodny do-stęp do danych (kod źroacutedłowy)

146 Co z katalogamiFaktycznie zapomnieliśmy o nich Jednak wynika to z tego że specyfikacja ANSI C nieuwzględnia obsługi katalogoacutew

Rozdział 15

Ćwiczenia dla początkujący

151 ĆwiczeniaWszystkie zamieszczone tutaj ćwiczenia mają na celu pomoacutec Ci w sprawdzeniu Twojej wie-dzy oraz umożliwieniu Tobie wykorzystania nowo nabytych wiadomości w praktyce Pa-miętaj także że ten podręcznik ma służyć także innym więc nie zamieszczaj tutaj Twoichrozwiązań Zachowaj je dla siebie

1511 Ćwiczenie 1

Napisz program ktoacutery wyświetli na ekranie twoje imię i nazwisko

1512 Ćwiczenie 2

Napisz program ktoacutery poprosi o podanie dwoacutech liczb rzeczywistych i wyświetli wynik mno-żenia obu zmiennych

1513 Ćwiczenie 3

Napisz program ktoacutery pobierze jako argumenty z linii komend nazwy dwoacutech plikoacutew i prze-kopiuje zawartość pierwszego pliku do drugiego (tworząc lub zamazując drugi)

1514 Ćwiczenie 4

Napisz program ktoacutery utworzy nowy plik (o dowolnie wybranej przez Ciebie nazwie) i za-pisze tam

Twoje imię

wiek

miasto w ktoacuterym mieszkasz

Przykładowy plik powinien wyglądać tak

Stanisław30Krakoacutew

105

106 ROZDZIAŁ 15 ĆWICZENIA DLA POCZĄTKUJĄCYCH

1515 Ćwiczenie 5

Napisz program generujący tabliczkę mnożenia x i wyświetlający ją na ekranie

1516 Ćwiczenie 6 mdash dla ętny

Napisz program znajdujący pierwiastki troacutejmianu kwadratowego ax2+bx+c= dla zadanychparametroacutew a b c

Rozdział 16

Tablice

W rozdziale Zmienne w C dowiedziałeś się jak przechowywać pojedyncze liczby oraz znakiCzasami zdarza się jednak że potrzebujemy przechować kilka kilkanaście albo iwięcej zmien-nych jednego typu Nie tworzymy wtedy np dwudziestu osobnych zmiennych W takichprzypadkach z pomocą przychodzi nam tablica

Rysunek 161 tablica 10-elementowa

Tablica to ciąg zmiennych jednego typu Ciąg taki posiada jedną nazwę a do jego po-szczegoacutelnych elementoacutew odnosimy się przez numer (indeks)

161 Wstęp

1611 Sposoby deklaracji tablic

Tablicę deklaruje się w następujący sposoacuteb

typ nazwa_tablicy[rozmiar]

gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy Zatemaby np zadeklarować tablicę mieszczącą liczb całkowitych możemy napisać tak

int tablica[20]

Podobnie jak przy deklaracji zmiennych także tablicy możemy nadać wartości począt-kowe przy jej deklaracji Odbywa się to przez umieszczenie wartości kolejnych elementoacutewoddzielonych przecinkami wewnątrz nawiasoacutew klamrowych

int tablica[3] = 123

Może to się wydać dziwne ale po ostatnim elemencie tablicy może występować przeci-nek Ponadto jeżeli poda się tylko część wartości w pozostałe wpisywane są zera

107

108 ROZDZIAŁ 16 TABLICE

int tablica[20] = 1

Niekoniecznie trzeba podawać rozmiar tablicy np

int tablica[] = 1 2 3 4 5

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku mdash elemen-toacutew)

Rozpatrzmy następujący kod

include ltstdiohgtdefine ROZMIAR 3int main()

int tab[ROZMIAR] = 368int iprintf (Druk tablicy tabn)

for (i=0 iltROZMIAR ++i) printf (Element numer d = dn i tab[i])

return 0

Wynik

Druk tablicy tabElement numer 0 = 3Element numer 1 = 6Element numer 2 = 8

Jak widać wszystko się zgadza W powyżej zamieszczonym przykładzie użyliśmy stałejdo podania rozmiaru tablicy Jest to o tyle pożądany zwyczaj że w razie konieczności zmianyrozmiaru tablicy zmieniana jest tylko jedna linijka kodu przy stałej a nie kilkadziesiąt innychlinijek rozsianych po kodzie całego programu

W pierwotnym standardzie języka C rozmiar tablicy nie moacutegł być określany przezzmienną lub nawet stałą zadeklarowaną przy użyciu słowa kluczowego const Dopiero wpoacuteźniejszej wersji standardu (tzw C) dopuszczono taką możliwość Dlatego do dekla-rowania rozmiaru tablic często używa się dyrektywy preprocesora define Powinni na tozwroacutecić uwagę zwłaszcza programiści C++ gdyż tam zawsze możliwe były oba sposoby

Innym sposobem jest użycie operatora sizeof do poznania wielkości tablicy Poniższy kodrobi to samo co przedstawiony

include ltstdiohgtint main()

int tab[3] = 368int i

162 ODCZYTZAPIS WARTOŚCI DO TABLICY 109

printf (Druk tablicy tabn)

for (i=0 ilt(sizeof tab sizeof tab) ++i) printf (Element numer d = dn i tab[i])

return 0

Należy pamiętać że działa on tylko dla tablic a nie wskaźnikoacutew (jak poacuteźniej się dowieszwskaźnik też można w pewnym stopniu traktować jak tablicę)

162 Odczytzapis wartości do tablicyTablicami posługujemy się tak samo jak zwykłymi zmiennymi Roacuteżnica polega jedynie napodaniu indeksu tablicy Określa on jednoznacznie z ktoacuterego elementu (wartości) chcemyskorzystać Indeksem jest liczba naturalna począwszy od zera To oznacza że pierwszy ele-ment tablicy ma indeks roacutewny drugi trzeci itd

Osoby ktoacutere wcześniej programowały w językach takich jak Pascal Basic czy Fortranmuszą przyzwyczaić się do tego że w języku C indeks numeruje się od Ponadto indeksempowinna być liczba - istnieje możliwość indeksowania za pomocą np pojedynczych znakoacutew(rsquoarsquo rsquobrsquo itp) jednak Cwewnętrznie konwertuje takie znaki na liczby im odpowiadające zatemtablica indeksowana znakami byłaby niepraktyczna i musiałaby mieć odpowiednio większyrozmiar

Sproacutebujmy przedstawić to na działającym przykładzie Przeanalizuj następujący kod

int tablica[5] = 0int i = 0tablica[2] = 3tablica[3] = 7for (i=0i=5++i)

printf (tablica[d]=dn i tablica[i])

Jak widać na początku deklarujemy -elementową tablicę ktoacuterą od razu zerujemy Na-stępnie pod trzeci i czwarty element podstawiamy liczby i Pętla ma za zadanie wypro-wadzić wynik naszych działań

163 Tablice znakoacutewTablice znakoacutew tj typu char oraz unsigned char posiadają dwie ogoacutelnie przyjęte nazwyzależnie od ich przeznaczenia

bufory mdash gdy wykorzystujemy je do przechowywania ogoacutelnie pojętych danych gdytraktujemy je jako po prostu ldquociągi bajtoacutewrdquo (typ char ma rozmiar bajta więc jestelastyczny do przechowywania np danych wczytanych z pliku przed ich przetworze-niem)

napisy mdash gdy zawarte w nich dane traktujemy jako ciągi liter jest im poświęconyosobny rozdział Napisy

110 ROZDZIAŁ 16 TABLICE

164 Tablice wielowymiarowe

Rysunek tablica dwuwymia-rowa (x)

Rozważmy teraz konieczność przechowania w pa-mięci komputera całej macierzy o wymiarach x Można by tego dokonać tworząc osobnych ta-blic jednowymiarowych reprezentujących poszcze-goacutelne wiersze macierzy Jednak język C dostarczanam dużo wygodniejszej metody ktoacutera w dodatkujest bardzo łatwa w użyciu Są to tablice wielowy-miarowe lub inaczej ldquotablice tablicrdquo Tablice wielo-wymiarowe definiujemy podając przy zmiennej kilkawymiaroacutew np

float macierz[10][10]

Tak samo wygląda dostęp do poszczegoacutelnych ele-mentoacutew tablicy

macierz[2][3] = 12

Jakwidać ten sposoacuteb jest dużowygodniejszy (i za-pewne dużo bardziej ldquonaturalnyrdquo) niż deklarowanie osobnych tablic jednowymiarowych Aby zaini-cjować tablicę wielowymiarową należy zastosowaćzagłębianie klamer np

float macierz[3][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz

Dodatkowo pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowy-miarowych) i woacutewczas kompilator sam ustali odpowiednią wielkość np

float macierz[][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz 63 27 57 27 czwarty wiersz

Innym bardziej elastycznym sposobem deklarowania tablic wielowymiarowych jest uży-cie wskaźnikoacutew Opisane to zostało w następnym rozdziale

165 Ograniczenia tablicPomimo swej wygody tablice mają ograniczony z goacutery zdefiniowany rozmiar ktoacuterego niemożna zmienić w trakcie działania programu Dlatego też w niektoacuterych zastosowaniach ta-blice zostały wyparte przez dynamiczną alokację pamięci Opisane to zostało w następnymrozdziale

166 CIEKAWOSTKI 111

Przy używaniu tablic trzeba być szczegoacutelnie ostrożnym przy konstruowaniu pętli ponie-waż ani kompilator ani skompilowany program nie będą w stanie wychwycić przekroczeniaprzez indeks rozmiaru tablicy 1 Efektem będzie odczyt lub zapis pamięci znajdującej się pozatablicą

Wystarczy pomylić się o jednomiejsce (tzw błąd off by one) by spowodować że działanieprogramu zostanie nagle przerwane przez system operacyjny

int foo[100]int i

for (i=0 ilt=100 ++i) powinno być ilt100 foo[i] = 0

166 CiekawostkiW pierwszej edycji konkursu IOCCC zwyciężył program napisany w C ktoacutery wyglądał dośćnietypowo

short main[] = 277 04735 -4129 25 0 477 1019 0xbef 0 12800-113 21119 0x52d7 -1006 -7151 0 0x4bc 02000414880 10541 2056 04010 4548 3044 -6716 0x94407 6 5568 1 -30460 0 0x9 5570 512 -304190x7e82 0760 6 0 4 02400 15 0 4 1280 4 04 0 0 0 0x8 0 4 0 0 12 0 4 0 0 020 0 4 0 30 0 026 0 0x6176 120 25712p 072163 r 29303 29801 e

Co ciekawe mdash program ten bez przeszkoacuted wykonywał się na komputerach VAX- orazPDP- Cały program to po prostu tablica z zawartym wewnątrz kodem maszynowym Taknaprawdę jest to wykorzystanie pewnych właściwości programu ktoacutery ostatecznie produ-kuje kod maszynowy Linker (to o nim mowa) nie rozroacuteżnia na dobrą sprawę nazw funkcjiod nazw zmiennych więc bez problemu ustawił punkt wejścia programu na tablicę wartościw ktoacuterych zapisany był kod maszynowy Tak przygotowany program został bez problemuwykonany przez komputer

1W zasadzie kompilatory mają możliwość dodania takiego sprawdzania ale nie robi się tego gdyż znaczniespowolniłoby to działanie programu Takie postępowanie jest jednak pożądane w okresie testowania programu

112 ROZDZIAŁ 16 TABLICE

Rozdział 17

Wskaźniki

Zobacz w WikipediiZmienna wskaźnikowaZmienne w komputerze są przechowywane w pamięci To wie każdy programista a dobry

programista potrafi kontrolować zachowanie komputera w przydzielaniu i obsługi pamięcidla zmiennych W tym celu pomocne są wskaźniki

171 Co to jest wskaźnik

Dla ułatwienia przyjęto poniżej że bajt ma bitoacutew typ int składa się z dwoacutech bajtoacutew(bitoacutew) typ long składa się z czterech bajtoacutew ( bitoacutew) oraz liczby zapisane są w formaciebig endian (tzn bardziej znaczący bajt na początku) co niekoniecznie musi być prawdą naTwoim komputerze

Rysunek Wskaźnik awskazu-jący na zmienną b Zauważmy żeb przechowuje liczbę podczas gdya przechowuje adres b w pamięci()

Wskaźnik (ang pointer) to specjalny rodzaj zmiennejw ktoacuterej zapisany jest adres w pamięci komputera tznwskaźnik wskazuje miejsce gdzie zapisana jest jakaśinformacja Oczywiście nic nie stoi na przeszkodzie abywskazywaną daną był innywskaźnik do kolejnegomiej-sca w pamięci

Obrazowo możemy wyobrazić sobie pamięć kom-putera jako bibliotekę a zmienne jako książki Zamiastbrać książkę z poacutełki samemu (analogicznie do korzy-stania wprost ze zwykłych zmiennych) możemy podaćbibliotekarzowi wypisany rewers z numerem katalogo-wym książki a on znajdzie ją za nas Analogia ta niejest doskonała ale pozwalawyobrazić sobie niektoacutere ce-chy wskaźnikoacutew kilka rewersoacutew może dotyczyć tej sa-mej książki numer w rewersie możemy skreślić i użyćgo do zamoacutewienia innej książki jeśli wpiszemy niepra-widłowy numer katalogowy to możemy dostać nie tąksiążkę ktoacuterą chcemy albo też nie dostać nic

Warto też poznać w tym miejscu definicję adresupamięci Możemy powiedzieć że adres to pewna liczba całkowita jednoznacznie definiującapołożenie pewnego obiektu (czyli np znaku czy liczby) w pamięci komputera Dokładniejsządefinicję możesz znaleźć w Wikipedii

113

114 ROZDZIAŁ 17 WSKAŹNIKI

172 Operowanie na wskaźnikaBy stworzyć wskaźnik do zmiennej i moacutec się nim posługiwać należy przypisać mu odpo-wiednią wartość (adres obiektu na jaki ma wskazywać) Skąd mamy znać ten adres Wy-starczy zapytać nasz komputer jaki adres przydzielił zmiennej ktoacuterą np wcześniej gdzieśstworzyliśmy Robi się to za pomocą operatora amp (operatora pobrania adresu) Przeanalizujnastępujący kod1

include ltstdiohgt

int main (void)

int liczba = 80printf(Zmienna znajduje sie pod adresem p i przechowuje wartosc dn

(void)ampliczba liczba)return 0

Program ten wypisuje adres pamięci pod ktoacuterym znajduje się zmienna oraz wartość jakąkryje zmienna przechowywana pod owym adresem

Aby moacutec zapisać gdzieś taki adres należy zadeklarować zmienną wskaźnikową Robi sięto poprzez dodanie (gwiazdki) po typie na jaki zmienna ma wskazywać np

int wskaznik1char wskaznik2floatwskaznik3

Niektoacuterzy programiści mogą nieco błędnie interpretować wskaźnik do typu jako nowytyp i uważać że jeśli napiszą

int abc

to otrzymają trzy wskaźniki do liczby całkowitej Tymczasem wskaźnikiem będzie tylkozmienna a natomiast b i c będą po prostu liczbami Powodem jest to że rdquogwiazdkaodnosi siędo zmiennej a nie do typu W tym przypadku trzy wskaźniki otrzymamy pisząc

int abc

Aby uniknąć pomyłek lepiej jest pisać gwiazdkę tuż przy zmiennej

int abc

albo jeszcze lepiej nie mieszać deklaracji wskaźnikoacutew i zmiennych

int aint bc

Aby dobrać się dowartości wskazywanej przez wskaźnik należy użyć unarnego operatora (gwiazdka) zwanego operatorem wyłuskania

1Warto zwroacutecić uwagę na rzutowanie do typu wskaźnik na void Rzutowanie to jest wymagane przez funkcjęprintf gdyż ta oczekuje że argumentem dla formatu p będzie właśnie wskaźnik na void gdy tymczasem w naszymprzykładzie wyrażenie ampliczba jest typu wskaźnik na int

172 OPEROWANIE NA WSKAŹNIKACH 115

include ltstdiohgt

int main (void)

int liczba = 80int wskaznik = ampliczbaprintf(Wartosc zmiennej d jej adres pn liczba (void)ampliczba)printf(Adres zapisany we wskazniku p wskazywana wartosc dn

(void)wskaznik wskaznik)

wskaznik = 42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

liczba = 0x42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

return 0

1721 O coodzi z tym typem na ktoacutery ma wskazywać Czemu to takieważne

Jest to ważne z kilku powodoacutewRoacuteżne typy zajmują w pamięci roacuteżną wielkość Przykładowo jeżeli w zmiennej typu

unsigned int zapiszemy liczbę to w pamięci będzie istnieć jako

+--------+--------+|komoacuterka1|komoacuterka2|+--------+--------+|11111111|11111010| = (unsigned int) 65530+--------+--------+

Wskaźnik do takiej zmiennej (jak i do dowolnej innej) będzie wskazywać na pierwsząkomoacuterkę w ktoacuterej ta zmienna ma swoją wartość

Jeżeli teraz stworzymy drugi wskaźnik do tego adresu tym razem typu unsigned arto wskaźnik przejmie ten adres prawidłowo2 lecz gdy sproacutebujemy odczytać wartość na jakąwskazuje ten wskaźnik to zostanie odczytana tylko pierwsza komoacuterka i wynik będzie roacutewny

+--------+|komoacuterka1|+--------+|11111111| = (unsigned char) 255+--------+

2Tak naprawdę nie zawsze można przypisywać wartości jednych wskaźnikoacutew do innych Standard C gwaran-tuje jedynie że można przypisać wskaźnikowi typu void wartość dowolnego wskaźnika a następnie przypisać tąwartość do wskaźnika pierwotnego typu oraz że dowolny wskaźnik można przypisać do wskaźnika typu char

116 ROZDZIAŁ 17 WSKAŹNIKI

Gdybyśmy natomiast stworzyli inny wskaźnik do tego adresu tym razem typu unsignedlong to przy proacutebie odczytu odczytane zostaną dwa bajty z wartością zapisaną w zmiennejunsigned int oraz dodatkowe dwa bajty z niewiadomą zawartością i woacutewczas wynik będzieroacutewny + przypadkowa wartość

+--------+--------+--------+--------+|komoacuterka1|komoacuterka2|komoacuterka3|komoacuterka4|+--------+--------+--------+--------+|11111111|11111010|||+--------+--------+--------+--------+

Ponadto zapis czy odczyt poza przydzielonym obszarem pamięci może prowadzić donieprzyjemnych skutkoacutew takich jak zmiana wartości innych zmiennych czy wręcz natych-miastowe przerwanie programu Jako przykład można podać ten (błędny) program3

include ltstdiohgt

int main(void)

unsigned char tab[10] = 100 101 102 103 104 105 106 107 108 109 unsigned short ptr = (unsigned short)amptab[2]unsigned i

ptr = 0xfffffor (i = 0 i lt 10 ++i)

printf(dn tab[i])tab[i] = tab[i] - 100

printf(poza tablica dn tab[10])tab[10] = -1return 0

Nie można roacutewnież zapominać że na niektoacuterych architekturach dane wielobajtowe mu-szą być odpowiednio wyroacutewnane w pamięci Np zmienna dwubajtowa może się znajdowaćjedynie pod parzystymi adresami Woacutewczas gdybyśmy chcieli adres zmiennej jednobajto-wej przypisać wskaźnikowi na zmienną dwubajtową mogłoby dojść do nieprzewidzianychbłędoacutew wynikających z proacuteby odczytu niewyroacutewnanej danej

Zaskakującemoże się okazać że roacuteżnewskaźniki mogąmieć roacuteżny rozmiar Np wskaźnikna ar może być większy od wskaźnika na int ale roacutewnież na odwroacutet Co więcej wskaźnikiroacuteżnych typoacutewmogą się roacuteżnić reprezentacją adresoacutew Dla przykładuwskaźnik naar możeprzechowywać adres do bajtu natomiast wskaźnik na int ten adres podzielony przez

Podsumowując roacuteżne wskaźniki to roacuteżne typy i nie należy beztrosko rzutować wyrażeńpomiędzy roacuteżnymi typami wskaźnikowymi bo grozi to nieprzewidywalnymi błędami

1722 Do czego służy typ void

Czasami zdarza się że nie wiemy na jaki typwskazuje danywskaźnik W takich przypadkachstosujemy typ void Sam void nie znaczy nic natomiast void oznacza ldquowskaźnik na obiekt

3Może się okazać że błąd nie będzie widoczny na Twoim komputerze

173 ARYTMETYKA WSKAŹNIKOacuteW 117

w pamięci niewiadomego typurdquo Taki wskaźnik możemy potem odnieść do konkretnego typudanych (w języku C++ wymagana jest do tego operacja rzutowania) Na przykład funkcjamalloc zwraca właśnie wskaźnik za pomocą void

173 Arytmetyka wskaźnikoacutew

W języku C do wskaźnikoacutew można dodawać lub odejmować liczby całkowite Istotne jestjednak że dodanie do wskaźnika liczby nie spowoduje przesunięcia się w pamięci kom-putera o dwa bajty Tak naprawdę przesuniemy się o rozmiar zmiennej Jest to bardzoważna informacja Początkujący programiści popełniają często dużo błędoacutew związanych znieprawidłową arytmetyką wskaźnikoacutew

Zobaczmy na przykład

int ptrint a[] = 1 2 3 5 7ptr = ampa[0]

Rysunek 172 Wskaźnik wskazuje na pierwszą komoacuterkę pamięci

Otrzymujemy następującą sytuacjęGdy wykonamy

ptr += 2

Rysunek 173 Przesunięcie wskaźnika na kolejne komoacuterki

wskaźnik ustawi się na trzecim elemencie tablicyWskaźniki można roacutewnież od siebie odejmować czego wynikiem jest odległość dwoacutech

wskazywanych wartości Odległość zwracana jest jako liczba obiektoacutew danego typu a nieliczba bajtoacutew Np

int a[] = 1 2 3 5 7int ptr = ampa[2]int diff = ptr - a diff ma wartość 2 (a nie 2sizeof(int))

118 ROZDZIAŁ 17 WSKAŹNIKI

Wynikiem może być oczywiście liczba ujemna Operacja jest przydatna do obliczaniawielkości tablicy (długości łańcucha znakoacutew) jeżeli mamy wskaźnik na jej pierwszy i ostatnielement

Operacje arytmetyczne na wskaźnikach mają pewne ograniczenia Przede wszystkim niemożna (tzn standard tego nie definiuje) skonstruować wskaźnika wskazującego gdzieś pozazadeklarowaną tablicę chyba że jest to obiekt zaraz za ostatnim (one past last) np

int a[] = 1 2 3 5 7int ptrptr = a + 10 niezdefiniowane ptr = a - 10 niezdefiniowane ptr = a + 5 zdefiniowane (element za ostatnim) ptr = 10 to już nie

Nie można4 roacutewnież odejmować od siebie wskaźnikoacutew wskazujących na obiekty znajdu-jące się w roacuteżnych tablicach np

int a[] = 1 2 3 b[] = 5 7int ptr1 = a ptr2 = bint diff = a - b niezdefiniowane

174 Tablice a wskaźniki

Trzeba wiedzieć że tablice to też rodzaj zmiennej wskaźnikowej Taki wskaźnik wskazuje namiejsce w pamięci gdzie przechowywany jest jej pierwszy element Następne elementy znaj-dują się bezpośrednio w następnych komoacuterkach pamięci w odstępie zgodnym z wielkościąodpowiedniego typu zmiennej

Na przykład tablica

int tab[] = 100200300

występuje w pamięci w sześciu komoacuterkach5

+--------+--------+--------+--------+--------+--------+|wartosc1| |wartosc2| |wartosc3| |+--------+--------+--------+--------+--------+--------+|00000000|01100100|00000000|11001000|00000001|00101100|+--------+--------+--------+--------+--------+--------+

Stąd do trzeciej wartości można się dostać tak (komoacuterki w tablicy numeruje się od zera)

zmienna = tab[2]

albo wykorzystując metodę wskaźnikową

zmienna = (tab + 2)

4To znaczy standard nie definiuje co się wtedy stanie aczkolwiek na większości architektur odejmowanie do-wolnych dwoacutech wskaźnikoacutew ma zdefiniowane zachowanie Pisząc przenośne programy nie można jednak na tympolegać zwłaszcza że odejmowanie wskaźnikoacutew wskazujących na elementy roacuteżnych tablic zazwyczaj nie ma sensu

5Ponownie przyjmując że bajt ma 8 bitoacutew int dwa bajty i liczby zapisywane są w formacie lile endian

175 GDY ARGUMENT JEST WSKAŹNIKIEM 119

Z definicji obie te metody są roacutewnoważneZ definicji (z wyjątkiem użycia operatora sizeo) wartością zmiennej lub wyrażenia typu tablico-

wego jest wskaźnik na jej pierwszy element (tab == amptab[0])Co więcej można poacutejść w drugą stronę i potraktować wskaźnik jak tablicę

int wskaznikwskaznik = amptab[1] lub wskaznik = tab + 1 zmienna = wskaznik[1] przypisze 300

Jako ciekawostkę podamy iż w języku C można odnosić się do elementoacutew tablicy jeszcze w innysposoacuteb

printf (dn 1[tab])

Skąd ta dziwna notacja Uzasadnienie jest proste

tab[1] = (tab + 1) = (1 + tab) = 1[tab]

Podobną składnię stosuje min asembler GNU

175 Gdy argument jest wskaźnikiem Czasami zdarza się że argumentem (lub argumentami) funkcji są wskaźniki W przypadku ldquonormal-nychrdquo zmiennych nasza funkcja działa tylko na lokalnych kopiach tychże argumentoacutew natomiast niezmienia zmiennych ktoacutere zostały podane jako argument Natomiast w przypadku wskaźnika każdaoperacja na wartości wskazywanej powoduje zmianę wartości zmiennej zewnętrznej Sproacutebujmy roz-patrzeć poniższy przykład

include ltstdiohgt

void func (int zmienna)

zmienna = 5

int main ()

int z=3printf (z=dn z) wypisze 3 func(ampz)printf (z=dn z) wypisze 5

Widzimy że funkcje w języku C nie tylko potrafią zwracać określoną wartość lecz także zmieniaćdane podane im jako argumenty Ten sposoacuteb przekazywania argumentoacutew do funkcji jest nazywanyprzekazywaniem przez wskaźnik (w przeciwieństwie do normalnego przekazywania przez wartość)

Zwroacutećmy uwagę na wywołanie func(ampz) Należy pamiętać by do funkcji przekazać adres zmien-nej a nie samą zmienną Jeśli byśmy napisali func(z) to funkcja starałaby się zmienić komoacuterkę pamięcio numerze Kompilator powinien ostrzec w takim przypadku o konwersji z typu int do wskaźnikaale często kompiluje taki program pozostając na ostrzeżeniu

Nie gra roli czy przy deklaracji funkcji jako argument funkcji podamy wskaźnik czy tablicę (z po-danym rozmiarem lub nie) np poniższe deklaracje są identyczne

120 ROZDZIAŁ 17 WSKAŹNIKI

void func(int ptr[])void func(int ptr)

Można przyjąć konwencję że deklaracja określa czy funkcji przekazujemy wskaźnik do pojedyn-czego argumentu czy do sekwencji ale roacutewnie dobrze można za każdym razem stosować gwiazdkę

176 Pułapki wskaźnikoacutewWażne jest aby przy posługiwaniu się wskaźnikami nigdy nie proacutebować odwoływać się do komoacuterkiwskazywanej przez wskaźnik o wartości lub niezainicjowany wskaźnik Przykładem nieprawi-dłowego kodu może być np

int wskprintf (zawartosc komorki dn (wsk)) Błąd wsk = 0 0 w kontekście wskaźnikoacutew oznacza wskaźnik NULL printf (zawartosc komorki dn (wsk)) Błąd

Należy roacutewnież uważać aby nie odwoływać się do komoacuterek poza przydzieloną pamięcią np

int tab[] = 0 1 2 tab[3] = 3 Błąd

Pamiętaj też że możesz być rozczarowany używając operatora sizeof podając zmienną wskaźni-kową Uzyskana wielkość będzie wielkością wskaźnika a nie wielkością typu użytego podczas deklaro-wania naszego wskaźnika Wielkość ta będzie zawsze miała taki sam rozmiar dla każdego wskaźnikaw zależności od kompilatora a także docelowej platformy Zamiast tego używaj sizeof(wskaźnik)Przykład

char zmiennaint a = sizeof zmienna a wynosi np 4 tj sizeof(char) a = sizeof(char) robimy to samo co wyżej a = sizeof zmienna zmienna a ma teraz przypisany rozmiar

pojedynczego znaku tj 1 a = sizeof(char) robimy to samo co wyżej

177 Na co wskazuje Analizując kody źroacutedłowe programoacutew często można spotkać taki oto zapis

void wskaznik = NULL lub = 0

Wiesz już że nie możemy odwołać się pod komoacuterkę pamięci wskazywaną przez wskaźnik Poco zatem przypisywać wskaźnikowi Odpowiedź może być zaskakująca właśnie po to aby uniknąćbłędoacutew Wydaje się to zabawne ale większość (jeśli nie wszystkie) funkcji ktoacutere zwracają wskaźnikw przypadku błędu zwroacuteci właśnie czyli zero Tutaj rodzi się kolejna wskazoacutewka jeśli w danejzmiennej przechowujemy wskaźnik zwroacutecony wcześniej przez jakąś funkcję zawsze sprawdzajmy czynie jest on roacutewny () Wtedy mamy pewność że funkcja zadziałała poprawnie

Dokładniej nie jest słowem kluczowym lecz stałą (makrem) zadeklarowaną przez dyrektywypreprocesora Deklaracja taka może być albo wartością albo też wartością zrzutowaną na void(((void )0)) ale też jakimś słowem kluczowym deklarowanym przez kompilator

Warto zauważyć że pomimo przypisywania wskaźnikowi zera nie oznacza to że wskaźnik jest reprezentowany przez same zerowe bity Co więcej wskaźniki roacuteżnych typoacutew mogą miećroacuteżną wartość Z tego powodu poniższy kod jest niepoprawny

int tablica_wskaznikow = calloc(100 sizeof tablica_wskaznikow)

178 STAŁE WSKAŹNIKI 121

Zakłada on że w reprezentacji wskaźnika występują same zera Poprawnym zainicjowaniemdynamicznej tablicy wskaźnikoacutew wartościami jest (pomijamy sprawzdanie wartości zwroacuteconejprzez malloc())

int tablica_wskaznikow = malloc(100 sizeof tablica_wskaznikow)int i = 0while (ilt100)

tablica_wskaznikow[i++] = 0

178 Stałe wskaźnikiTak jak istnieją zwykłe stałe tak samo możemy mieć stałe wskaźniki mdash jednak są ich dwa rodzajeWskaźniki na stałą wartość

const int a lub roacutewnoważnie int const a

oraz stałe wskaźniki

int const b

Pierwszy to wskaźnik ktoacuterym nie można zmienić wskazywanej wartości Drugi to wskaźnik ktoacute-rego nie można przestawić na inny adres Dodatkowo można zadeklarować stały wskaźnik ktoacuterym niemożna zmienić wartości wskazywanej zmiennej i roacutewnież można zrobić to na dwa sposoby

const int const c alternatywnie int const const c

int i=0const int a=ampiint const b=ampiint const const c=ampia = 1 kompilator zaprotestuje b = 2 ok c = 3 kompilator zaprotestuje a = b ok b = a kompilator zaprotestuje c = a kompilator zaprotestuje

Wskaźniki na stałą wartość są przydatne między innymi w sytuacji gdy mamy duży obiekt (naprzykład strukturę z kilkoma polami) Jeśli przypiszemy taką zmienną do innej zmiennej kopiowaniemoże potrwać dużo czasu a oproacutecz tego zostanie zajęte dużo pamięci Przekazanie takiej struktury dofunkcji albo zwroacutecenie jej jako wartość funkcji wiąże się z takim samym narzutem W takim wypadkudobrze jest użyć wskaźnika na stałą wartość

void funkcja(const duza_struktura ds)

czytamy z ds i wykonujemy obliczenia

funkcja(ampdane) mamy pewność że zmienna dane nie zostanie zmieniona

122 ROZDZIAŁ 17 WSKAŹNIKI

179 Dynamiczna alokacja pamięciMając styczność z tablicami można się zastanowić czy nie dałoby się mieć tablic ktoacuterych rozmiardostosowuje się do naszych potrzeb a nie jest na stałe zaszyty w kodzie programu Chcąc pomieścićwięcej danych możemy po prostu zwiększyć rozmiar tablicy mdash ale gdy do przechowania będzie mniejelementoacutew okaże się że marnujemy pamięć Język C umożliwia dzięki wskaźnikom i dynamicznejalokacji pamięci tworzenie tablic takiej wielkości jakiej akurat potrzebujemy

1791 O co odziCzym jest dynamiczna alokacja pamięci Normalnie zmienne programu przechowywane są na tzwstosie (ang sta) mdash powstają gdy program wchodzi do bloku w ktoacuterym zmienne są zadeklarowane azwalniane w momencie kiedy program opuszcza ten blok Jeśli deklarujemy tak tablice to ich rozmiarmusi być znanywmomencie kompilacji mdash żeby kompilator wygenerował kod rezerwujący odpowiedniąilość pamięci Dostępny jest jednak drugi rodzaj rezerwacji (czyli alokacji) pamięci Jest to alokacja nastercie (ang heap) Sterta to obszar pamięci wspoacutelny dla całego programu przechowywane są w nimzmienne ktoacuterych czas życia nie jest związany z poszczegoacutelnymi blokami Musimy sami rezerwować dlanich miejsce i to miejsce zwalniać ale dzięki temu możemy to zrobić w dowolnym momencie działaniaprogramu

Należy pamiętać że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analo-giczne działania na stosie Dodatkowo zmienna zajmuje na stercie więcej miejsca niż na stosie mdash stertautrzymuje specjalną strukturę w ktoacuterej trzymane są wolne partie (może to być np lista) Tak więcużywajmy dynamicznej alokacji tam gdzie jest potrzebna mdash dla danych ktoacuterych rozmiaru nie jesteśmyw stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem w ktoacuterymzostały zaalokowane

1792 Obsługa pamięciPodstawową funkcją do rezerwacji pamięci jest funkcja malloc Jest to niezbyt skomplikowana funkcjamdash podając jej rozmiar (w bajtach) potrzebnej pamięci dostajemy wskaźnik do zaalokowanego obszaru

Załoacuteżmy że chcemy stworzyć tablicę liczb typu float

int rozmiarfloat tablica

rozmiar = 3tablica = (float) malloc(rozmiar sizeof tablica)tablica[0] = 01

Przeanalizujmy teraz po kolei co dzieje się w powyższym fragmencie Najpierw deklarujemyzmiennemdash rozmiar tablicy i wskaźnik ktoacutery będzie wskazywał obszarw pamięci gdzie będzie trzymanatablica Do zmiennej rozmiar możemy w trakcie działania programu przypisać cokolwiek mdash wczytaćją z pliku z klawiatury obliczyć wylosować mdash nie jest to istotne rozmiar sizeof tablica obliczapotrzebną wielkość tablicy Dla każdej zmiennej float potrzebujemy tyle bajtoacutew ile zajmuje ten typdanych Ponieważ może się to roacuteżnić na rozmaitych maszynach istnieje operator sizeof zwracającydla danego wyrażenia rozmiar jego typu w bajtach

W wielu książkach (roacutewnież KampRv) i w Internecie stosuje się inny schemat użycia funkcji malloca mianowicie tablica = (float)malloc(rozmiar sizeof(float)) Takie użycie należy traktowaćjako błędne gdyż nie sprzyja ono poprawnemu wykrywaniu błędoacutew

Rozważmy sytuację gdy programista zapomni dodać plik nagłoacutewkowy stdlibh woacutewczas kompila-tor (z braku deklaracji funkcji malloc) przyjmie że zwraca ona typ int zatem do zmiennej tablica (ktoacuterajest wskaźnikiem) będzie przypisywana liczba całkowita co od razu spowoduje błąd kompilacji (a przy-najmniej ostrzeżenie) dzięki czemu będzie można szybko poprawić kod programu Rzutowanie jestkonieczne tylko w języku C++ gdzie konwersja z void na inne typy wskaźnikowe nie jest domyślnaale język ten oferuje nowe sposoby alokacji pamięci

179 DYNAMICZNA ALOKACJA PAMIĘCI 123

Teraz rozważmy sytuację gdy zdecydujemy się zwiększyć dokładność obliczeń i zamiast typu floatużyć typu double Będziemy musieli wyszukać wszystkie wywołania funkcji malloc calloc i reallocodnoszące się do naszej tablicy i zmieniać wszędzie sizeof(float) na sizeof(double) Aby temu zapo-biec lepiej od razu użyć sizeof tablica (lub jeśli ktoś woli z nawiasami sizeof(tablica)) woacutewczaszmiana typu zmiennej tablica na double zostanie od razu uwzględniona przy alokacji pamięci

Dodatkowo należy sprawdzić czy funkcja malloc nie zwroacuteciła wartości mdash dzieje się tak gdyzabrakło pamięci Ale uwaga może się tak stać roacutewnież jeżeli jako argument funkcji podano zero

Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić aby sys-tem operacyjny moacutegł go przydzielić innym potrzebującym procesom Do zwolnienia obszaru pamięciużywamy funkcji free() ktoacutera przyjmuje tylko jeden argument mdash wskaźnik ktoacutery otrzymaliśmy wwyniku działania funkcji malloc()

free (addr)

Należy pamiętać o zwalnianiu pamięci mdash inaczej dojdzie do tzw wycieku pamięci mdash program będzierezerwował nową pamięć ale nie zwracał jej z powrotem i w końcu pamięci może mu zabraknąć

Należy też uważać by nie zwalniać dwa razy tego samegomiejsca Po wywołaniu free wskaźnik niezmienia wartości pamięć wskazywana przez niego może też nie od razu ulec zmianie Czasemmożemywięc korzystać ze wskaźnika (zwłaszcza czytać) po wywołaniu free nie orientując się że robimy coś źlemdash i w pewnym momencie dostać komunikat o nieprawidłowym dostępie do pamięci Z tego powoduzaraz po wywołaniu funkcji free można przypisać wskaźnikowi wartość

Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci Tu z pomocąprzychodzi funkcja realloc

tablica = realloc(tablica 2rozmiarsizeof tablica)

Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub gdy zabrakło pa-mięci) Uwaga mdash może to być inny wskaźnik Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanymaktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca funkcja znajdzie nowe miejscei przekopiuje tam starą zawartość Jak widać wywołanie tej funkcji może być więc kosztowne podwzględem czasu

Ostatnią funkcją jest funkcja calloc() Przyjmuje ona dwa argumenty liczbę elementoacutew tablicyoraz wielkość pojedynczego elementu Podstawową roacuteżnicą pomiędzy funkcjami malloc() i calloc() jestto że ta druga zeruje wartość przydzielonej pamięci (do wszystkich bajtoacutew wpisuje wartość )

1793 Tablice wielowymiarowe

Rysunek 174 tablica dwuwymiarowa mdash w rzeczywistości tablica ze wskaźnikami do tablic

W rozdziale Tablice pokazaliśmy jak tworzyć tablice wielowymiarowe gdy ich rozmiar jest znanyw czasie kompilacji Teraz zaprezentujemy jak to wykonać za pomocą wskaźnikoacutew i to w sytuacji gdyrozmiar może się zmieniać Załoacuteżmy że chcemy stworzyć tabliczkę mnożenia

124 ROZDZIAŁ 17 WSKAŹNIKI

int rozmiarint iint tabliczka

printf(Podaj rozmiar tabliczki mnozenia )scanf(i amprozmiar) dla prostoty nie będziemy sprawdzali

czy użytkownik wpisał sensowną wartość

tabliczka = malloc(rozmiar sizeof tabliczka) 1 for (i = 0 iltrozmiar ++i) 2

tabliczka[i] = malloc(rozmiar sizeof tabliczka) 3 4

for (i = 0 iltrozmiar ++i) int jfor (j = 0 jltrozmiar ++j)

tabliczka[i][j] = (i+1)(j+1)

Najpierw musimy przydzielić pamięć mdash najpierw dla ldquotablicy tablicrdquo () a potem dla każdej z pod-tablic osobno (-) Ponieważ tablica jest typu int to nasza tablica tablic będzie wskaźnikiem na intczyli int Podobnie osobno ale w odwrotnej kolejności będziemy zwalniać tablicę wielowymiarową

for (i = 0 iltrozmiar ++i) free(tabliczka[i])

free(tabliczka)

Należy nie pomylić kolejności po wykonaniu free(tabliczka) nie będziemy mieli prawa odwoły-wać się do tabliczka[i] (bo wcześniej dokonaliśmy zwolnienia tego obszaru pamięci)

Można także zastosować bardziej oszczędny sposoacuteb alokowania tablicy wielowymiarowej a mia-nowicie

define ROZMIAR 10int iint tabliczka = malloc(ROZMIAR sizeof tabliczka)tabliczka = malloc(ROZMIAR ROZMIAR sizeof tabliczka)for (i = 1 iltROZMIAR ++i)

tabliczka[i] = tabliczka[0] + (i ROZMIAR)

for (i = 0 iltROZMIAR ++i) int jfor (j = 0 jltROZMIAR ++j)

tabliczka[i][j] = (i+1)(j+1)

free(tabliczka)free(tabliczka)

Powyższy kod działa w ten sposoacuteb że zamiast dla poszczegoacutelnych wierszy alokować osobno pamięćalokuje pamięć dla wszystkich elementoacutew tablicy i dopiero poacuteźniej przypisuje wskazania poszczegoacutel-nych wskaźnikoacutew-wierszy na kolejne bloki po elementoacutew

1710 WSKAŹNIKI NA FUNKCJE 125

Sposoacuteb ten jest bardziej oszczędny z dwoacutech powodoacutew Po pierwsze wykonywanych jest mniej ope-racji przydzielania pamięci (bo tylko dwie) Po drugie za każdym razem gdy alokuje się pamięć trochęmiejsca się marnuje gdyż funkcja malloc musi w stogu przechowywać roacuteżne dodatkowe informacje natemat każdej zaalokowanej przestrzeni Ponadto czasami alokacja odbywa się blokami i gdy zażąda sięniepełny blok to reszta bloku jest tracona

Zauważmy że w ten sposoacuteb możemy uzyskać nie tylko normalną ldquokwadratowąrdquo tablicę (dla dwoacutechwymiaroacutew) Możliwe jest np uzyskanie tablicy troacutejkątnej

0123012010

lub tablicy o dowolnym innym rozkładzie długości wierszy np

const size_t wymiary[] = 2 4 6 8 1 3 5 7 9 int iint tablica = malloc((sizeof wymiary sizeof wymiary) sizeof tablica)for (i = 0 ilt10 ++i)

tablica[i] = malloc(wymiary[i] sizeof tablica)

Gdy nabierzesz wprawy w używaniu wskaźnikoacutew oraz innych funkcji malloc i realloc nauczyszsię wykonywać roacuteżne inne operacje takie jak dodawanie kolejnych wierszy usuwanie wierszy zmianarozmiaru wierszy zamiana wierszy miejscami itp

1710 Wskaźniki na funkcjeDotychczas zajmowaliśmy się sytuacją gdy wskaźnik wskazywał na jakąś zmienną Jednak nie tylkozmienna ma swoacutej adres w pamięci Oproacutecz zmiennej także i funkcja musi mieć swoje określone miejscew pamięci A ponieważ funkcja ma swoacutej adres6 to nie ma przeszkoacuted aby i na nią wskazywał jakiśwskaźnik

17101 Deklaracja wskaźnika na funkcjęTak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresufunkcji Wskaźnik na funkcję roacuteżni się od innych rodzajoacutew wskaźnikoacutew Jedną z głoacutewnych roacuteżnic jestjego deklaracja Zwykle wygląda ona tak

typ_zwracanej_wartości (nazwa_wskaźnika)(typ1 parametr1 typ2 parametr2)

Oczywiście parametroacutew może być więcej (albo też w ogoacutele może ich nie być) Oto przykład wyko-rzystania wskaźnika na funkcję

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

6Tak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresu funkcji

126 ROZDZIAŁ 17 WSKAŹNIKI

int (wsk_suma)(int a int b)wsk_suma = sumaprintf(4+5=dn wsk_suma(45))return 0

Zwroacutećmy uwagę na dwie rzeczy

przypisując nazwę funkcji bez nawiasoacutew do wskaźnika automatycznie informujemy kompilatorże chodzi nam o adres funkcji

wskaźnika używamy tak jak normalnej funkcji na ktoacuterą on wskazuje

17102 Do czego można użyć wskaźnikoacutew na funkcjeJęzyk C jest językiem strukturalnym jednak dzięki wskaźnikom istnieje w nim możliwość ldquozaszczepie-niardquo pewnych obiektowych właściwości Wskaźnik na funkcję może być np elementem struktury mdashwtedy mamy bardzo prymitywną namiastkę klasy ktoacuterą dobrze znają programiści piszący w językuC++ Ponadto dzięki wskaźnikom możemy tworzyć mechanizmy działające na zasadzie funkcji zwrot-nej7 Dobrym przykładem może być np tworzenie sterownikoacutew gdzie musimy poinformować roacuteżnepodsystemy jakie funkcje w naszym kodzie służą do wykonywania określonych czynności Przykład

struct urzadzenie int (otworz)(void)void (zamknij)(void)

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

int rejestruj_urzadzenie(struct urzadzenie u) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzenieotworz = moje_urzadzenie_otworzmoje_urzadzeniezamknij = moje_urzadzenie_zamknijrejestruj_urzadzenie(ampmoje_urzadzenie)

Wten sposoacutebwpamięci każda klasamusi przechowywaćwszystkiewskaźniki dowszystkichmetodInnym rozwiązaniem może być stworzenie statycznej struktury ze wskaźnikami do funkcji i woacutewczasw strukturze będzie przechowywany jedynie wskaźnik do tej struktury np

struct urzadzenie_metody

7Funkcje zwrotne znalazły zastosowanie głoacutewnie w programowaniu

1711 MOŻLIWE DEKLARACJE WSKAŹNIKOacuteW 127

int (otworz)(void)void (zamknij)(void)

struct urzadzenie const struct urzadzenie_metody m

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

static const struct urzadzenie_metodymoje_urzadzenie_metody = moje_urzadzenie_otworzmoje_urzadzenie_zamknij

int rejestruj_urzadzenie(struct urzadzenie ampu) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzeniem = ampmoje_urzadzenie_metodyrejestruj_urzadzenie(ampmoje_urzadzenie)

1711 Możliwe deklaracje wskaźnikoacutew

Tutaj znajduje się kroacutetkie kompendium jak definiować wskaźniki oraz co oznaczają poszczegoacutelne defi-nicje

1712 Popularne błędy

Jednym z najczęstszych błędoacutew oproacutecz proacuteb wykonania operacji na wskaźniku są odwołania siędo obszaru pamięci po jego zwolnieniu Po wykonaniu funkcji free() nie możemy już wykonywaćżadnych odwołań do zwolnionego obszaru Innym rodzajem błędoacutew są

odwołania do adresoacutew pamięci ktoacutere są poza obszarem przydzielonym funkcją malloc()

brak sprawdzania czy dany wskaźnik nie ma wartości

wycieki pamięci czyli niezwalnianie całej przydzielonej wcześniej pamięci

128 ROZDZIAŁ 17 WSKAŹNIKI

i zmienna całkowita (typu int) ip wskaźnik p wskazujący na zmienną całkowitąa[] tablica a liczb całkowitych typu intf() funkcja f zwracająca liczbę całkowitą typu intpp wskaźnik pp na wskaźnik wskazujący na liczbę całkowitą typu int

(pa)[] wskaźnik pa wskazujący na tablicę liczb całkowitych typu int(pf)() wskaźnik pf na funkcję zwracającą liczbę całkowitą typu intap[] tablica ap wskaźnikoacutew na liczby całkowite typu intfp() funkcja fp ktoacutera zwraca wskaźnik na zmienną typu intppp wskaźnik ppp wskazujący na wskaźnik wskazujący na wskaźnik wskazu-

jący na liczbę typu int(ppa)[] wskaźnik ppa na wskaźnik wskazujący na tablicę liczb całkowitych typu

int(ppf)() wskaźnik ppf wskazujący na wskaźnik funkcji zwracającej dane typu int(pap)[] wskaźnik pap wskazujący na tablicę wskaźnikoacutew na typ int(pfp)() wskaźnik pfp na funkcję zwracającą wskaźnik na typ intapp[] tablica wskaźnikoacutew app wskazujących na typ int

(apa[])[] tablica wskaźnikoacutew apa wskazujących wskaźniki na typ int(apf[])() tablica wskaźnikoacutew apf na funkcję ktoacutere zwracają wskaźniki na typ intfpp() funkcja fpp ktoacutera zwraca wskaźnik na wskaźnik na wskaźnik ktoacutery wska-

zuje typ int(fpa())[] funkcja fpa ktoacutera zwraca wskaźnik na tablicę liczb typu int(fpf())() funkcja fpf ktoacutera zwraca wskaźnik na funkcję ktoacutera zwraca dane typu int

1713 Ciekawostki w rozdziale Zmienne pisaliśmy o stałych Normalnie nie mamy możliwości zmiany ich wartości

ale z użyciem wskaźnikoacutew staje się to możliwe

const int CONST=0int c=ampCONSTc = 1printf(inCONST) wypisuje 1

Konstrukcja taka może jednak wywołać ostrzeżenie kompilatora bądź nawet jego błąd mdash wtedymoże pomoacutec jawne rzutowanie z const int na int

język C++ oferuje mechanizm podobny do wskaźnikoacutew ale nieco wygodniejszy ndash referencje

język C++ dostarcza też innego sposobu dynamicznej alokacji i zwalniania pamięci mdash przez ope-ratory new i delete

w rozdziale Typy złożone znajduje się opis implementacji listy za pomocą wskaźnikoacutew Przy-kład ten może być bardzo przydatny przy zrozumieniu po co istnieją wskaźniki jak się nimiposługiwać oraz jak dobrze zarządzać pamięcią

Rozdział 18

Napisy

W dzisiejszych czasach komputer przestał być narzędziem tylko i wyłącznie do przetwarzania danychOd programoacutew komputerowych zaczęto wymagać czegoś nowego mdash program w wyniku swojego dzia-łania nie ma zwracać danych rozumianych tylko przez autora programu lecz powinien być na tylekomunikatywny aby przeciętny użytkownik komputera moacutegł bez problemu tenże komputer obsłużyćDo przechowywania tychże komunikatoacutew służą tzw ldquołańcuchyrdquo (ang string) czyli ciągi znakoacutew

Język C nie jest wygodnym narzędziem do manipulacji napisami Jak się wkroacutetce przekonamyzestaw funkcji umożliwiających operacje na napisach w bibliotece standardowej C jest raczej skromnyDodatkowo problemem jest sposoacuteb w jaki łańcuchy przechowywane są w pamięci

Napisy w języku Cmogą być przyczyną wielu trudnych do wykrycia błędoacuteww programach Wartodobrze zrozumieć jak należy operować na łańcuchach znakoacutew i zachować szczegoacutelną ostrożność w tychmiejscach gdzie napisoacutew używamy

181 Łańcuy znakoacutew w języku CNapis jest zapisywany w kodzie programu jako ciąg znakoacutew zawarty pomiędzy dwoma cudzysłowami

printf (Napis w języku C)

Wpamięci taki łańcuch jest następującympo sobie ciągiem znakoacutew (char) ktoacutery kończy się znakiemldquonullrdquo (czyli po prostu liczbą zero) zapisywanym jako rsquorsquo

Jeśli mamy napis do poszczegoacutelnych znakoacutew odwołujemy się jak w tablicy

char tekst = Jakiś tam tekstprintf(cn przykład[0]) wypisze p - znaki w napisach są numerowane od zera printf(cn tekst[2]) wypisze k

Ponieważ napis w pamięci kończy się zerem umieszczonym tuż za jego zawartością odwołanie się doznaku o indeksie roacutewnym długości napisu zwroacuteci zero

printf(d test[4]) wypisze 0

Napisy możemywczytywać z klawiatury i wypisywać na ekran przy pomocy dobrze znanych funk-cji scanf printf i pokrewnych Formatem używanym dla napisoacutew jest s

printf(s tekst)

129

130 ROZDZIAŁ 18 NAPISY

Większość funkcji działających na napisach znajduje się w pliku nagłoacutewkowym stringhJeśli łańcuch jest zbyt długi można zapisać gow kilku linijkach ale wtedy przechodząc do następnej

linii musimy na końcu postawić znak ldquordquo

printf(Ten napis zajmuje więcej niż jedną linię)

Instrukcja taka wydrukuje

Ten napis zajmuje więcej niż jedną linię

Możemy zauważyć że napis ktoacutery w programie zajął więcej niż jedną linię na ekranie zajął tylkojedną Jest tak ponieważ ldquordquo informuje kompilator że łańcuch będzie kontynuowany w następnej liniikodumdash niemawpływu na prezentację łańcucha Abywydrukować napis w kilku liniach należywstawićdo niego n (ldquonrdquo pochodzi tu od ldquonew linerdquo czyli ldquonowa liniardquo)

printf(Ten napisnna ekranienzajmie więcej niż jedną linię)

W wyniku otrzymamy

Ten napisna ekraniezajmie więcej niż jedną linię

1811 Jak komputer przeowuje w pamięci łańcu

Rysunek 181 Napis ldquoMerkkijonordquo przechowywany w pamięci

Zmienna ktoacutera przechowuje łańcuch znakoacutew jest tak naprawdę wskaźnikiem do ciągu znakoacutew(bajtoacutew) w pamięci Możemy też myśleć o napisie jako o tablicy znakoacutew (jak wyjaśnialiśmy wcześniejtablice to też wskaźniki)

Możemy wygodnie zadeklarować napis

char tekst = Jakiś tam tekst Umieszcza napis w obszarze danych programu i przypisuje adres

char tekst[] = Jakiś tam tekst Umieszcza napis w tablicy char tekst[] = Jakis tam tekst0

Tekst to taka tablica jak każda inna

Kompilator automatycznie przydziela wtedy odpowiednią ilość pamięci (tyle bajtoacutew ile jest literplus jeden dla kończącego nulla) Jeśli natomiast wiemy że dany łańcuch powinien przechowywaćokreśloną ilość znakoacutew (nawet jeśli w deklaracji tego łańcucha podajemy mniej znakoacutew) deklarujemygo w taki sam sposoacuteb jak tablicę jednowymiarową

char tekst[80] = Ten tekst musi być kroacutetszy niż 80 znakoacutew

Należy cały czas pamiętać że napis jest tak naprawdę tablicą Jeśli zarezerwowaliśmy dla napisu znakoacutew to przypisanie do niego dłuższego napisu spowoduje pisanie po pamięci

Uwaga Deklaracja char tekst = cokolwiek oraz char tekst = cokolwiek pomimo że wyglądająbardzo podobnie bardzo się od siebie roacuteżnią W przypadku pierwszej deklaracji proacuteba zmodyfikowania

181 ŁAŃCUCHY ZNAKOacuteW W JĘZYKU C 131

napisu (np tekst[0] = C) może mieć nieprzyjemne skutki Dzieje się tak dlatego że char tekst =cokolwiek deklaruje wskaźnik na stały obszar pamięci1

Pisanie po pamięci może czasami skończyć się błędem dostępu do pamięci (ldquosegmentation faultrdquow systemach ) i zamknięciem programu jednak może zdarzyć się jeszcze gorsza ewentualność mdashmożemy zmienić w ten sposoacuteb przypadkowo wartość innych zmiennych Program zacznie wtedy za-chowywać się nieprzewidywalnie mdash zmienne a nawet stałe co do ktoacuterych zakładaliśmy że ich wartośćbędzie ściśle ustalona mogą przyjąć taką wartość jaka absolutnie nie powinna mieć miejsca Wartowięc stosować zabezpieczenia typu makra assert

Kluczowy jest też kończący napis znak null W zasadzie wszystkie funkcje operujące na napisachopierają właśnie na nim Na przykład strlen szuka rozmiaru napisu idąc od początku i zliczając znaki ażnie natrafi na znak o kodzie zero Jeśli nasz napis nie kończy się znakiem null funkcja będzie szła dalejpo pamięci Na szczęście wszystkie operacje podstawienia typu tekst = ldquoTekstrdquo powodują zakończenienapisu nullem (o ile jest na niego miejsce) 2

1812 Znaki specjalneJak zapewne zauważyłeś w poprzednim przykładzie w łańcuchu ostatnim znakiem jest znak o wartościzero (rsquorsquo) Jednak łańcuchy mogą zawierać inne znaki specjalne(sekwencje sterujące) np

rsquoarsquo - alarm (sygnał akustyczny terminala)

rsquobrsquo - backspace (usuwa poprzedzający znak)

rsquorsquo - wysuniecie strony (np w drukarce)

rsquorrsquo - powroacutet kursora (karetki) do początku wiersza

rsquonrsquo - znak nowego wiersza

rsquordquo - cudzysłoacutew

rsquordquo - apostrof rsquorsquo - ukośnik wsteczny (backslash)

rsquotrsquo - tabulacja pozioma

rsquovrsquo - tabulacja pionowa

rsquorsquo - znak zapytania (pytajnik)

rsquoooorsquo - liczba zapisana w systemie oktalnym (oacutesemkowym) gdzie rsquoooorsquo należy zastąpić trzycy-frową liczbą w tym systemie

rsquoxhhrsquo - liczba zapisana w systemie heksadecymalnym (szesnastkowym) gdzie rsquohhrsquo należy za-stąpić dwucyfrową liczbą w tym systemie

rsquounnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnrsquo należy zastąpić czterocyfrowym identyfika-torem znaku w systemie szesnatkowym rsquonnnnrsquo odpowiada dłuższej formie w postaci rsquonnnnrsquo

rsquounnnnnnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnnnnnrsquo należy zastąpić ośmiocyfrowymidentyfikatorem znaku w systemie szesnatkowym

Warto zaznaczyć że znak nowej linii (rsquonrsquo) jest w roacuteżny sposoacuteb przechowywany w roacuteżnych sys-temach operacyjnych Wiąże się to z pewnymi historycznymi uwarunkowaniami W niektoacuterych sys-temach używa się do tego jednego znaku o kodzie xA (Line Feed mdash nowa linia) Do tej rodzinyzaliczamy systemy z rodziny Unix Linux BSD Mac OS X inne Drugą konwencją jest zapisywaniersquonrsquo za pomocą dwoacutech znakoacutew LF (Line Feed) + CR (Carriage return mdash powroacutet karetki) Znak CRreprezentowany jest przez wartość xD Kombinacji tych dwoacutech znakoacutew używają min CPM DOSOS Microso Windows Trzecia grupa systemoacutew używa do tego celu samego znaku CR Są to sys-temy działające na komputerach Commodore Apple II oraz Mac OS do wersji W związku z tym plikutworzony w systemie Linux może wyglądać dziwnie pod systemem Windows

1Można się zatem zastanawiać czemu kompilator dopuszcza przypisanie do zwykłego wskaźnika wskazania nastały obszar skoro kod const int foo int bar = foo generuje ostrzeżenie lub wręcz się nie kompiluje Jest topewna zaszłość historyczna wynikająca z faktu że słoacutewko const zostało wprowadzone do języka gdy już był on wpowszechnym użyciu

2Nie należy mylić znaku null (czyli znaku o kodzie zero) ze wskaźnikiem null (czy też )

132 ROZDZIAŁ 18 NAPISY

182 Operacje na łańcua

1821 Poroacutewnywanie łańcuoacutewNapisy to tak naprawdęwskaźniki Tak więc używając zwykłego operatora poroacutewnania == otrzymamywynik poroacutewnania adresoacutew a nie tekstoacutew

Do poroacutewnywania dwoacutech ciągoacutew znakoacutew należy użyć funkcji strcmp zadeklarowanej w pliku na-głoacutewkowym stringh Jako argument przyjmuje ona dwa napisy i zwraca wartość ujemną jeżeli napispierwszy jestmniejszy od drugiego jeżeli napisy są roacutewne lub wartość dodatnią jeżeli napis pierwszyjest większy od drugiego Ciągi znakoacutew poroacutewnywalne są leksykalnie kody znakoacutew czyli np (przyj-mując kodowanie ASCII) a jest mniejsze od b ale jest większe od B Np

include ltstdiohgtinclude ltstringhgt

int main(void) char str1[100] str2[100]int cmp

puts(Podaj dwa ciagi znakow )fgets(str1 sizeof str1 stdin)fgets(str2 sizeof str2 stdin)

cmp = strcmp(str1 str2)if (cmplt0)

puts(Pierwszy napis jest mniejszy) else if (cmpgt0)

puts(Pierwszy napis jest wiekszy) else

puts(Napisy sa takie same)

return 0

Czasami możemy chcieć poroacutewnać tylko fragment napisu np sprawdzić czy zaczyna się od jakie-goś ciągu W takich sytuacjach pomocna jest funkcja strncmp W poroacutewnaniu do strcmp() przyjmujeona jeszcze jeden argument oznaczający maksymalną liczbę znakoacutew do poroacutewnania

include ltstdiohgtinclude ltstringhgt

int main(void) char str[100]int cmp

fputs(Podaj ciag znakow stdout)fgets(str sizeof str stdin)

if (strncmp(str foo 3)) puts(Podany ciag zaczyna sie od foo)

return 0

182 OPERACJE NA ŁAŃCUCHACH 133

1822 Kopiowanie napisoacutewDo kopiowania ciągoacutew znakoacutew służy funkcja strcpy ktoacutera kopiuje drugi napis w miejsce pierwszegoMusimy pamiętać by w pierwszym łańcuchu było wystarczająco dużo miejsca

char napis[100]strcpy(napis Ala ma kota)

Znacznie bezpieczniej jest używać funkcji strncpy ktoacutera kopiuje co najwyżej tyle bajtoacutew ile podanojako trzeci parametr Uwaga Jeżeli drugi napis jest za długi funkcja nie kopiuje znaku null na koniecpierwszego napisu dlatego zawsze trzeba to robić ręcznie

char napis[100]strncpy(napis Ala ma kota sizeof napis - 1)napis[sizeof napis - 1] = 0

1823 Łączenie napisoacutewDo łączenia napisoacutew służy funkcja strcat ktoacutera kopiuje drugi napis do pierwszego Ponownie jak wprzypadku strcpymusimy zagwarantować by w pierwszym łańcuchu było wystarczająco dużo miejsca

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrcat(napis1 napis2)puts(napis1)return 0

I ponownie jak w przypadku strcpy istnieje funkcja strncat ktoacutera skopiuje co najwyżej tyle bajtoacutewile podano jako trzeci argument i dodatkowo dopisze znak null Przykładowo powyższy kod bezpieczniejzapisać jako

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrncat(napis1 napis2 sizeof napis1 - 1)puts(napis1)return 0

Osoby ktoacutere programowały w językach skryptowych muszą bardzo uważać na łączenie i kopiowa-nie napisoacutew Kompilator języka C nie wykryje nadpisania pamięci za zmienną łańcuchową i nie przy-dzieli dodatkowego obszaru pamięci Może się zdarzyć że program pomimo nadpisywania pamięci załańcuchem będzie nadal działał co bardzo utrudni wykrywanie tego typu błędoacutew

134 ROZDZIAŁ 18 NAPISY

183 Bezpieczeństwo kodu a łańcuy

1831 Przepełnienie buforaO co właściwie chodzi z tymi funkcjami strncpy i strncat Otoacuteż niewinnie wyglądające łańcuchy mogąokazać się zaboacutejcze dla bezpieczeństwa programu a przez to nawet dla systemu w ktoacuterym ten programdziała Może brzmi to strasznie lecz jest to prawda Może pojawić się tutaj pytanie ldquow jaki sposoacutebłańcuch może zaszkodzić programowirdquo Otoacuteż może i to całkiem łatwo Przeanalizujmy następującykod

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strcpy(haslo argv[1]) tutaj następuje przepełnienie bufora if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Jest to bardzo prosty program ktoacutery wykonuje jakąś akcję jeżeli podane jako pierwszy argumenthasło jest poprawne Sprawdźmy czy działa

$ aout niepoprawnePodales bledne haslo$ aout poprawneWitaj wprowadziles poprawne haslo

Jednak okazuje się że z powodu użycia funkcji strcpy włamywacz nie musi znać hasła aby programuznał że zna hasło np

$ aout 11111111111111111111111111111111Witaj wprowadziles poprawne haslo

Co się stało Podaliśmy ciąg jedynek dłuższy niż miejsce przewidziane na hasło Funkcja strcpy()kopiując znaki z argv[1] do tablicy (bufora) haslo przekroczyła przewidziane dla niego miejsce i szładalej mdash gdzie znajdowała się zmienna haslo poprawne strcpy() kopiowała znaki już tam gdzie znajdo-wały się inne dane mdash między innymi wpisała jedynkę do haslo poprawne

Podany przykład może się roacuteżnie zachowywać w zależności od kompilatora jakim został skompi-lowany i systemu na jakim działa ale ogoacutelnie mamy do czynienia z poważnym niebezpieczeństwem

183 BEZPIECZEŃSTWO KODU A ŁAŃCUCHY 135

Taką sytuację nazywamy przepełnieniem bufora Może umożliwić dostęp do komputera osobomnieuprzywilejowanym Należy wystrzegać się tego typu konstrukcji a wmiejsce niebezpiecznej funkcjistrcpy stosować bardziej bezpieczną strncpy

Oto bezpieczna wersja poprzedniego programu

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strncpy(haslo argv[1] sizeof haslo - 1)haslo[sizeof haslo - 1] = 0if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Bezpiecznymi alternatywami do strcpy i strcat są też funkcje strlcpy oraz strlcat opracowane przezprojekt OpenBSD i dostępne do ściągnięcia na wolnej licencji strlcpy strlcat strlcpy() działa podobniedo strncpy strlcpy (buf argv[1] sizeof buf) jednak jest szybsza (nie wypełnia pustego miejscazerami) i zawsze kończy napis nullem (czego nie gwarantuje strncpy) strlcat(dst src size) działanatomiast jak strncat(dst src size-1)

Do innych niebezpiecznych funkcji należy np gets zamiast ktoacuterej należy używać fgetsZawsze możemy też alokować napisy dynamicznie

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

136 ROZDZIAŁ 18 NAPISY

haslo = malloc(strlen(argv[1]) + 1) +1 dla znaku null if (haslo)

fputs(Za malo pamiecin stderr)return EXIT_FAILURE

strcpy(haslo argv[1])if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)free(haslo)return EXIT_SUCCESS

1832 Nadużycia z udziałem ciągoacutew formatującyJednak to nie koniec kłopotoacutew z napisami Wielu programistoacutew nieświadomych zagrożenia częstoużywa tego typu konstrukcji

include ltstdiohgtint main (int argc char argv[])

printf (argv[1])

Z punktu widzenia bezpieczeństwa jest to bardzo poważny błąd programu ktoacutery może nieść zesobą katastrofalne skutki Prawidłowo napisany kod powinien wyglądać następująco

include ltstdiohgtint main (int argc char argv[])

printf (s argv[1])

lub

include ltstdiohgtint main (int argc char argv[])

fputs (argv[1] stdout)

Źroacutedło problemu leży w konstrukcji funkcji printf Przyjmuje ona bowiem za pierwszy parametrłańcuch ktoacutery następnie przetwarza Jeśli w pierwszym parametrze wstawimy jakąś zmienną to funk-cja printf potraktuje ją jako ciąg znakoacutew razem ze znakami formatującymi Zatem ważne aby wcześniewyrobić sobie nawyk stosowania funkcji printf z co najmniej dwoma parametrami nawet w przypadkuwyświetlenia samego tekstu

184 KONWERSJE 137

184 KonwersjeCzasami zdarza się że łańcuch można interpretować nie tylko jako ciąg znakoacutew lecz np jako liczbęJednak aby dało się taką liczbę przetworzyć musimy skopiować ją do pewnej zmiennej Aby ułatwićprogramistom tego typu zamiany powstał zestaw funkcji bibliotecznych Należą do nich

atol strtol mdash zamienia łańcuch na liczbę całkowitą typu long

atoi mdash zamienia łańcuch na liczbę całkowitą typu int

atoll strtoll mdash zamienia łańcuch na liczbę całkowitą typu long long ( bity) dodatkowo istniejeprzestarzała funkcja atoq będąca rozszerzeniem

atof strtod mdash przekształca łańcuch na liczbę typu double

Ogoacutelnie rzecz ujmując funkcje z serii ato nie pozwalają na wykrycie błędoacutew przy konwersji i dla-tego gdy jest to potrzebne należy stosować funkcje strto

Czasami przydaje się też konwersja w drugą stronę tzn z liczby na łańcuch Do tego celu możeposłużyć funkcja sprintf lub snprintf sprintf jest bardzo podobna do printf tyle że wyniki jej praczwracane są do pewnego łańcucha a nie wyświetlane np na ekranie monitora Należy jednak uwa-żać przy jej użyciu (patrz mdash Bezpieczeństwo kodu a łańcuchy) snprintf (zdefiniowana w nowszymstandardzie) dodatkowo przyjmuje jako argument wielkość bufora docelowego

185 Operacje na znakaWarto też powiedzieć w tymmiejscu o operacjach na samych znakach Spoacutejrzmy na poniższy program

include ltstdiohgtinclude ltctypehgtinclude ltstringhgt

int main()

int znakwhile ((znak = getchar())=EOF)

if( islower(znak) ) znak = toupper(znak)

else if( isupper](znak) ) znak = tolower(znak)

putchar(znak)

return 0

Program ten zmieniawewczytywanym tekściewielkie litery namałe i odwrotnie Wykorzystujemyfunkcje operujące na znakach z pliku nagłoacutewkowego ctypeh isupper sprawdza czy znak jest wielkąliterą natomiast toupper zmienia znak (o ile jest literą) na wielką literę Analogicznie jest dla funkcjiislower i tolower

Jako ćwiczenie możesz tak zmodyfikować program żeby odczytywał dane z pliku podanego jakoargument lub wprowadzonego z klawiatury

186 Częste błędy pisanie do niezaalokowanego miejsca

138 ROZDZIAŁ 18 NAPISY

char tekstscanf(s tekst)

zapominanie o kończącym napis nullu

char test[4] = test nie zmieścił się null kończący napis

nieprawidłowe poroacutewnywanie łańcuchoacutew

char tekst1[] = jakis tekstchar tekst2[] = jakis tekstif( tekst1 == tekst2 ) tu zawsze będzie fałsz bo == poroacutewnuje adresy należy użyć strcmp()

187 UnicodeZobacz w Wikipedii Uni-code W dzisiejszych czasach brak obsługi wielu językoacutew praktycznie marginalizowałoby język Dlatego też

C wprowadza możliwość zapisu znakoacutew wg norm Unicode

1871 Jaki typDo przechowywania znakoacutew zakodowanych w Unicode powinno się korzystać z typu war t Jegodomyślny rozmiar jest zależny od użytego kompilatora lecz w większości zaktualizowanych kompila-toroacutew powinny to być bajty Typ ten jest częścią języka C++ natomiast w C znajduje się w plikunagłoacutewkowym stddefh

Alternatywą jest wykorzystanie gotowych bibliotek dla Unicode (większość jest dostępnych jedyniedla C++ nie wspoacutełpracuje z C) ktoacutere często mają zdefiniowane własne typy jednak zmuszeni jesteśmywtedy do przejścia ze znanych nam już funkcji jak np strcpy strcmp na funkcje dostarczane przezbibliotekę co jest dość niewygodne My zajmiemy się pierwszym wyjściem

1872 Jaki rozmiar i jakie kodowanieUnicode określa jedynie jakiej liczbie odpowiada jaki znak nie moacutewi zaś nic o sposobie dekodowania(tzn jaka sekwencja znakoacutew odpowiada jakiemu znakuznakom) Jako że Unicode obejmuje tysznakoacutew zmienna zdolna pomieścić go w całości musi mieć przynajmniej bajty Niestety procesory niefunkcjonują na zmiennych o tym rozmiarze pracują jedynie na zmiennych o wielkościach oraz bajtoacutew (kolejne potęgi liczby ) Dlatego też jeśli wciąż uparcie chcemy być dokładni i zastosowaćprzejrzyste kodowanie musimy skorzystać ze zmiennej -bajtowej ( bity) Tak do sprawy podeszlitwoacutercy kodowania Unicode nazwanego -UCS- Ten typ kodowania po prostu przydziela każ-Zobacz w Wikipedii -32demu znakowi Unicode kolejne liczby Jest to najbardziej intuicyjny i wygodny typ kodowania ale jakwidać ciągi znakoacutew zakodowane w nim są bardzo obszerne co zajmuje dostępną pamięć spowalniadziałanie programu oraz drastycznie pogarsza wydajność podczas transferu przez sieć Poza -istnieje jeszcze wiele innych kodowań Najpopularniejsze z nich to

- mdash od do bajtoacutew (dla znakoacutew poniżej do bajtoacutew) na znak przez co jest skraj-nie niewygodny gdy chcemy przeprowadzać jakiekolwiek operacje na tekście bez korzystania zgotowych funkcji

- mdash lub bajty na znak ręczne modyfikacje łańcucha są bardziej skomplikowane niż przy-

UCS- mdash bajty na znak przez co znaki z numerami powyżej nie są uwzględnione roacutewniewygodny w użytkowaniu co -

187 UNICODE 139

Ręczne operacje na ciągach zakodowanych w - i - są utrudnione ponieważ w przeci-wieństwie do - gdzie można określić iż powiedzmy znak ciągu zajmuje bajty od do (gdyżz goacutery wiemy że znak zajął bajty od do ) w tych kodowaniach musimy najpierw określić rozmiar znaku Ponadto gdy korzystamy z nich nie działają wtedy funkcje udostępniane przez biblioteki Cdo operowania na ciągach znakoacutew

Priorytet Proponowane kodowaniamały rozmiar -8

łatwa i wydajna edycja -32 lub -2przenośność -83

ogoacutelna szybkość -2 lub -8

Co należy zrobić by zacząć korzystać z kodowania - (domyślne kodowanie dla C)

powinniśmy korzystać z typu wchar t (ang ldquowide characterrdquo) jednak jeśli chcemy udostępniaćkod źroacutedłowy programu do kompilacji na innych platformach powinniśmy ustawić odpowiednieparametry dla kompilatoroacutew by rozmiar był identyczny niezależnie od platformy

korzystamy z odpowiednikoacutew funkcji operujących na typie char pracujących na wchar t (z re-guły składnia jest identyczna z tą roacuteżnicą że w nazwach funkcji zastępujemy ldquostrrdquo na ldquowcsrdquo npstrcpy mdash wcscpy strcmp mdash wcscmp)

jeśli przyzwyczajeni jesteśmy do korzystania z klasy string powinniśmy zamiast niej korzystaćz wstring ktoacutera posiada zbliżoną składnię ale pracuje na typie wchar t

Co należy zrobić by zacząć korzystać z Unicode

gdy korzystamy z kodowań innych niż - i - powinniśmy zdefiniować własny typ

w wykorzystywanych przez nas bibliotekach podajemy typ wykorzystanego kodowania

gdy chcemy ręcznie modyfikować ciąg musimy przeczytać specyfikację danego kodowania sąone wyczerpująco opisane na siostrzanym projekcie Wikibooks mdash Wikipedii

Przykład użycia kodowania -

include ltstddefhgt jeśli używamy C++ możemy opuścić tę linijkę include ltstdiohgtinclude ltstringhgt

int main() wchar_t wcs1 = LAla ma kotawchar_t wcs2 = LKot ma Alewchar_t calosc[25]

wcscpy(calosc wcs1)(calosc + wcslen(wcs1)) = L wcscpy(calosc + wcslen(wcs1) + 1 wcs2)

printf(lancuch wyjsciowy lsn calosc)return 0

140 ROZDZIAŁ 18 NAPISY

Rozdział 19

Typy złożone

191 typedefJest to słowo kluczowe ktoacutere służy do definiowania typoacutew pochodnych np

typedef stara_nazwa nowa_nazwatypedef int mojInttypedef int WskNaInt

od tej pory mozna używać typoacutew mojInt i WskNaInt

192 Typ wyliczeniowySłuży do tworzenia zmiennych ktoacutere powinny przechowywać tylko pewne z goacutery ustalone wartości

enum Nazwa WARTOSC_1 WARTOSC_2 WARTOSC_N

Na przykład można w ten sposoacuteb stworzyć zmienną przechowującą kierunek

enum Kierunek W_GORE W_DOL W_LEWO W_PRAWO

enum Kierunek kierunek = W_GORE

ktoacuterą można na przykład wykorzystać w instrukcji switch

switch(kierunek)

case W_GOREprintf(w goacuteręn)break

case W_DOLprintf(w doacutełn)break

defaultprintf(gdzieś w bokn)

Tradycyjnie przechowywane wielkości zapisuje się wielkimi literami (W GORE W DOL)Tak naprawdę C przechowuje wartości typu wyliczeniowego jako liczby całkowite o czym można

się łatwo przekonać

141

142 ROZDZIAŁ 19 TYPY ZŁOŻONE

kierunek = W_DOLprintf(in kierunek) wypisze 1

Kolejne wartości to po prostu liczby naturalne domyślnie pierwsza to zero druga jeden itp Mo-żemy przy deklarowaniu typu wyliczeniowego zmienić domyślne przyporządkowanie

enum Kierunek W_GORE W_DOL = 8 W_LEWO W_PRAWO printf(i in W_DOL W_LEWO) wypisze 8 9

Co więcej liczby mogą się powtarzać i wcale nie muszą być ustawione w kolejności rosnącej

enum Kierunek W_GORE = 5 W_DOL = 5 W_LEWO = 2 W_PRAWO = 1 printf(i in W_DOL W_LEWO) wypisze 5 2

Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajną ich obsługęale stwarza niebezpieczeństwa mdash można przypisywać pod typ wyliczeniowy liczby nawet nie mająceodpowiednika w wartościach a kompilator może o tym nawet nie ostrzec

kierunek = 40

193 StrukturyStruktury to specjalny typ danych mogący przechowywać wiele wartości w jednej zmiennej Od tablicjednakże roacuteżni się tym iż te wartości mogą być roacuteżnych typoacutew

Struktury definiuje się w następujący sposoacuteb

struct Struktura int pole1int pole2char pole3

gdzie ldquoStrukturardquo to nazwa tworzonej strukturyNazewnictwo ilość i typ poacutel definiuje programista według własnego uznaniaZmienną posiadającą strukturę tworzy się podając jako jej typ nazwę struktury

struct Struktura zmienna

Dostęp do poszczegoacutelnych poacutel uzyskuje się przy pomocy operatora wyboru składnika kropki (rsquorsquo)

zmiennaSpole1 = 60 przypisanie liczb do poacutel zmiennaSpole2 = 2zmiennaSpole3 = a a teraz znaku

194 UnieUnie to kolejny sposoacuteb prezentacji danychw pamięci Na pierwszy rzut oka wyglądają bardzo podobniedo struktur

union Nazwa typ1 nazwa1typ2 nazwa2

Na przykład

194 UNIE 143

union LiczbaLubZnak int calkowitachar znakdouble rzeczywista

Pola w unii nakładają się na siebie w ten sposoacuteb że w danej chwili można w niej przechowywaćwartość tylko jednego typu Unia zajmuje w pamięci tyle miejsca ile zajmuje największa z jej składo-wych W powyższym przypadku unia będzie miała prawdopodobnie rozmiar typu double czyli często bity a całkowita i znak będą wskazywały odpowiednio na pierwsze cztery bajty lub na pierwszy bajtunii (choć nie musi tak być zawsze) Dlaczego tak Taka forma często przydaje się np do konwersji po-między roacuteżnymi typami danych Możemy dzięki unii podzielić zmienną -bitową na cztery składowezmienne o długości bitoacutew każda

Do konkretnych wartości poacutel unii odwołujemy się podobnie jak w przypadku struktur za pomocąkropki

union LiczbaLubZnak liczbaliczbacalkowita = 10printf(dn liczbacalkowita)

Zazwyczaj użycie unii ma na celu zmniejszenie zapotrzebowania na pamięć gdy naraz będzie wy-korzystywane tylko jedno pole i jest często łączone z użyciem struktur

Przyjrzyjmy się teraz przykładowi ktoacutery powinien dobitnie zademonstrować działanie unii

include ltstdiohgt

struct adres_bajtowy __uint8_t a__uint8_t b__uint8_t c__uint8_t d

union adres __uint32_t ipstruct adres_bajtowy badres

int main ()

union adres addraddrbadresa = 192addrbadresb = 168addrbadresc = 1addrbadresd = 1printf (Adres IP w postaci 32-bitowej zmiennej 08xnaddrip)return 0

Zauważyłeś pewien ciekawy efekt Jeśli uruchomiłeś ten program na typowym komputerze domo-wym (rodzina i) na ekranie zapewne pojawił Ci się taki oto napis

Adres IP w postaci 32-bitowej zmiennej 0101a8c0

Dlaczego jedynki są na początku zmiennej skoro w programie były to dwa ostatnie bajty (pola c id struktury) Jest to problem kolejności bajtoacutew Aby dowiedzieć się o nim więcej przeczytaj rozdział

144 ROZDZIAŁ 19 TYPY ZŁOŻONE

przenośność programoacutew Zauważyłeś zatem że za pomocą tego programu w prosty sposoacuteb zamienili-śmy cztery zmienne jednobajtowe w jedną czterobajtową Jest to tylko jedno z możliwych zastosowańunii

195 Inicjalizacja struktur i uniiJeśli tworzymy nową strukturę lub unię możemy zaraz po jej deklaracji wypełnić ją określonymi da-nymi Rozważmy tutaj przykład

struct moja_struct int achar b moja = 1c

Wzasadzie taka deklaracja nie roacuteżni się niczym odwypełnienia np tablicy danymi Jednak standardC wprowadza pewne udogodnienie zaroacutewno przy deklaracji struktur jak i unii Polega ono na tymże w nawiasie klamrowym możemy podać nazwy poacutel struktury lub unii ktoacuterym przypisujemy wartośćnp

struct moja_struct int achar b moja = b = c pozostawiamy pole a niewypełnione żadną konkretną wartością

196 Wspoacutelnewłasności typoacutewwyliczeniowy unii i struk-tur

Warto w zwroacutecić uwagę że język C++ przy deklaracji zmiennych typoacutew wyliczeniowych unii lubstruktur nie wymaga przed nazwą typu odpowiedniego słowa kluczowego Na przykład poniższy kodjest poprawnym programem C++

enum Enum A B C union Union int a float b struct Struct int a float b int main()

Enum eUnion uStruct se = Aua = 0sa = 0return e + ua + sa

Nie jest to jednak poprawny kod C i należy o tym pamiętać szczegoacutelnie jeżeli uczysz się języka Ckorzystając z kompilatora C++

Należy roacutewnież pamiętać że po klamrze zamykającej definicje musi następować średnik Brak tegośrednika jest częstym błędem powodującym czasami niezrozumiałe komunikaty błędoacutew Jedynym wy-jątkiem jest natychmiastowa definicja zmiennych danego typu na przykład

struct Struktura int pole

s1 s2 s3

196 WSPOacuteLNE WŁASNOŚCI TYPOacuteW WYLICZENIOWYCH UNII I STRUKTUR 145

Definicja typoacutew wyliczeniowych unii i struktur jest lokalna do bloku To znaczy możemy zdefi-niować strukturę wewnątrz jednej z funkcji (czy wręcz wewnątrz jakiegoś bloku funkcji) i tylko tambędzie można używać tego typu

Częstym idiomem w C jest użycie typedef od razu z definicją typu by uniknąć pisania enum unionczy struct przy deklaracji zmiennych danego typu

typedef struct struktura int pole

StrukturaStruktura s1struct struktura s2

W tym przypadku zmienne s i s są tego samego typu Możemy też zrezygnować z nazywaniasamej struktury

typedef struct int pole

StrukturaStruktura s1

1961 Wskaźnik na unię i strukturęPodobnie jak na każdą inną zmienna wskaźnik może wskazywać także na unię lub strukturę Otoprzykład

typedef struct int p1 p2

Struktura

int main ()

Struktura s = 0 0 Struktura wsk = ampswsk-gtp1 = 2wsk-gtp2 = 3return 0

Zapis wsk-gtp1 jest (z definicji) roacutewnoważny (wsk)p1 ale bardziej przejrzysty i powszechnie sto-sowany Wyrażenie wskp1 spowoduje błąd kompilacji (strukturą jest wsk a nie wsk)

1962 Zobacz też Powszechne praktyki mdash konstruktory i destruktory

1963 Pola bitoweStruktury mają pewne dodatkowe możliwości w stosunku do zmiennych Mowa tutaj o rozmiarzeelementu struktury W przeciwieństwie do zmiennej może on mieć nawet bit Aby moacutec zdefiniowaćtaką zmienną musimy użyć tzw pola bitowego Wygląda ono tak

struct moja unsigned int a14 4 bity

a28 8 bitoacutew (często 1 bajt) a31 1 bit a43 3 bity

146 ROZDZIAŁ 19 TYPY ZŁOŻONE

Wszystkie pola tej struktury mają w sumie rozmiar bitoacutew jednak możemy odwoływać się donichw taki sam sposoacuteb jak do innych elementoacutew struktury W ten sposoacuteb efektywniej wykorzystujemypamięć jednak istnieją pewne zjawiska ktoacuterych musimy być świadomi przy stosowaniu poacutel bitowychWięcej na ten temat w rozdziale przenośność programoacutew

Pola bitowe znalazły zastosowanie głoacutewnie w implementacjach protokołoacutew sieciowych

197 Studium przypadku mdash implementacja listy wskaźniko-wej

Zobacz w Wikipedii ListaRozważmy teraz coś co każdy z nas może spotkać w codziennym życiu Każdy z nas widział kiedyśjakiś przykład listy (czy to zakupoacutew czy też listę wierzycieli) Język C też oferuje listy jednak w progra-mowaniu listy będą służyły do czegoś innego Wyobraźmy sobie sytuację w ktoacuterej jesteśmy autoramigenialnego programu ktoacutery znajduje kolejne liczby pierwsze Oczywiście każdą kolejną liczbę pierw-szą może wyświetlać na ekran jednak z matematyki wiemy że dana liczba jest liczbą pierwszą jeśli niedzieli się przez żadną liczbę pierwszą ją poprzedzającą mniejszą od pierwiastka z badanej liczby Uffmniej więcej chodzi o to że moglibyśmy wykorzystać znalezione wcześniej liczby do przyspieszeniadziałania naszego programu Jednak nasze liczby trzeba jakoś mądrze przechować w pamięci Tablicemają ograniczenie mdash musimy z goacutery znać ich rozmiar Jeśli zapełnilibyśmy tablicę to przy znalezieniukażdej kolejnej liczby musielibyśmy

przydzielać nowy obszar pamięci o rozmiarze poprzedniego rozmiaru + rozmiar zmiennej prze-chowującej nowo znalezioną liczbę

kopiować zawartość starego obszaru do nowego

zwalniać stary nieużywany obszar pamięci

w ostatnim elemencie nowej tablicy zapisać znalezioną liczbę

Coacuteż trochę tutaj roboty jest a kopiowanie całej zawartości jednego obszaru w drugi jest czaso-chłonne W takim przypadku możemy użyć listy Tworząc listę możemy w prosty sposoacuteb przechowaćnowo znalezione liczby Przy użyciu listy nasze postępowanie ograniczy się do

przydzielenia obszaru pamięci aby przechować wartość obliczeń

dodać do listy nowy element

Prawda że proste Dodatkowo lista zajmuje w pamięci tylko tyle pamięci ile potrzeba na aktualnąliczbę elementoacutew Pusta tablica zajmuje natomiast tyle samo miejsca co pełna tablica

1971 Implementacja listyW języku C aby stworzyć listę musimy użyć struktur Dlaczego Ponieważ musimy przechować conajmniej dwie wartości

pewną zmienną (np liczbę pierwszą z przykładu)

wskaźnik na kolejny element listy

Przyjmijmy że szukając liczb pierwszych nie przekroczymy możliwości typu unsigned long

typedef struct element struct element next wskaźnik na kolejny element listy unsigned long val przechowywana wartość

el_listy

Zacznijmy zatem pisać nasz eksperymentalny program do wyszukiwania liczb pierwszych Pierw-szą liczbą pierwszą jest liczba Pierwszym elementem naszej listy będzie zatem struktura ktoacutera będzieprzechowywała liczbę Na co będzie wskazywało pole next Ponieważ na początku działania pro-gramu będziemy mieć tylko jeden element listy pole next powinno wskazywać na Umoacutewmy sięzatem że pole next ostatniego elementu listy będzie wskazywało mdash po tym poznamy że lista sięskończyła

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 147

include ltstdiohgtinclude ltstdlibhgttypedef struct element

struct element nextunsigned long val

el_listy

el_listy first pierwszy element listy

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (ilt=END++i) tutaj powinien znajdować się kod ktoacutery sprawdza podzielność sprawdzanej liczby przezpoprzednio znalezione liczby pierwsze oraz dodaje liczbę do listy w przypadku stwierdzenia

że jest ona liczbą pierwszą

wypisz_liste(first)return 0

Na początek zajmiemy się wypisywaniem listy W tym celu będziemy musieli ldquoodwiedzićrdquo każdyelement listy Elementy listy są połączone polem next aby przeglądnąć listę użyjemy następującegoalgorytmu

Ustaw wskaźnik roboczy na pierwszym elemencie listy

Jeśli wskaźnik ma wartość przerwij

Wypisz element wskazywany przez wskaźnik

Przesuń wskaźnik na element ktoacutery jest wskazywany przez pole next

Wroacuteć do punktu

void wypisz_liste(el_listy lista)

el_listy wsk=lista 1 while( wsk = NULL ) 2

printf (lun wsk-gtval) 3 wsk = wsk-gtnext 4 5

Zastanoacutewmy się teraz jak powinien wyglądać kod ktoacutery dodaje do listy następny element Takafunkcja powinna

znaleźć ostatni element (tj element ktoacuterego pole next == )

przydzielić odpowiedni obszar pamięci

skopiować w pole val w nowo przydzielonym obszarze znalezioną liczbę pierwszą

nadać polu next ostatniego elementu listy wartość

w pole next ostatniego elementu listy wpisać adres nowo przydzielonego obszaru

148 ROZDZIAŁ 19 TYPY ZŁOŻONE

Napiszmy zatem odpowiednią funkcję

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL) 1

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy)) 2 nowy-gtval = liczba 3 nowy-gtnext = NULL 4 wsk-gtnext = nowy 5

Ihellip to już właściwie koniec naszej funkcji (warto zwroacutecić uwagę że funkcja w tej wersji zakłada żena liście jest już przynajmniej jeden element) Wstaw ją do kodu przed funkcją main Został namjeszcze jeden problem w pętli for musimy dodać kod ktoacutery odpowiednio będzie ldquobadałrdquo liczby oraz wprzypadku stwierdzenia pierwszeństwa liczby będzie dodawał ją do listy Ten kod powinien wyglądaćmniej więcej tak

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczba wsk-gtval)==0) return 0 jeśli reszta z dzielenialiczby przez ktoacuterąkolwiek z poprzednio znalezionychliczb pierwszych jest roacutewna zero to znaczy że liczba tanie jest liczbą pierwszą

wsk = wsk-gtnext

natomiast jeśli sprawdzimy wszystkie poprzednio znalezione liczbyi żadna z nich nie będzie dzieliła liczby imożemy liczbę i dodać do listy liczb pierwszych

return 1for (ilt=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (firsti)

Podsumujmy teraz efekty naszej pracy Oto cały kod naszego programu

include ltstdiohgtinclude ltstdlibhgt

typedef struct element struct element nextunsigned long val

el_listy

el_listy first

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 149

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL)

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy))nowy-gtval = liczbanowy-gtnext = NULLwsk-gtnext = nowy podczepiamy nowy element do ostatniego z listy

void wypisz_liste(el_listy lista)

el_listy wsk=listawhile( wsk = NULL )

printf (lun wsk-gtval)wsk = wsk-gtnext

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczbawsk-gtval)==0) return 0wsk = wsk-gtnext

return 1

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (i=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (first i)

wypisz_liste(first)return 0

Możemy jeszcze pomyśleć jak można by wykonać usuwanie elementu z listy Najprościej byłobyzrobić

wsk-gtnext = wsk-gtnext-gtnext

150 ROZDZIAŁ 19 TYPY ZŁOŻONE

ale wtedy element na ktoacutery wskazywał wcześniej wsk-gtnext przestaje być dostępny i zaśmieca pa-mięć Trzeba go usunąć Zauważmy że aby usunąć element potrzebujemy wskaźnika do elementu gopoprzedzającego (po to by nie rozerwać listy) Popatrzmy na poniższą funkcję

void usun_z_listy(el_listy lista int element)

el_listy wsk=listawhile (wsk-gtnext = NULL)

if (wsk-gtnext-gtval == element) musimy mieć wskaźnik do elementu poprzedzającego el_listy usuwany=wsk-gtnext zapamiętujemy usuwany element wsk-gtnext = usuwany-gtnext przestawiamy wskaźnik next by omijał usuwany element free(usuwany) usuwamy z pamięci else

wsk = wsk-gtnext idziemy dalej tylko wtedy kiedy nie usuwaliśmy bo nie chcemy zostawić duplikatoacutew

Funkcja ta jest tak napisana by usuwała z listy wszystkie wystąpienia danego elementu (w naszymprogramie nie ma to miejsca ale lista jest zrobiona tak że może trzymać dowolne liczby) Zauważmyże wskaźnik wsk jest przesuwany tylko wtedy gdy nie kasowaliśmy Gdybyśmy zawsze go przesuwaliprzegapilibyśmy element gdyby występował kilka razy pod rząd

Funkcja ta działa poprawnie tylko wtedy gdy nie chcemy usuwać pierwszego elementu Można topoprawić mdash dodając instrukcję warunkową do funkcji lub dodając do listy ldquogłowęrdquo mdash pierwszy elementnie przechowujący niczego ale upraszczający operacje na liście Zostawiamy to do samodzielnej pracy

Cały powyższy przykład omawiał tylko jeden przypadek listy mdash listę jednokierunkową Jednakistnieją jeszcze inne typy list np lista jednokierunkowa cykliczna lista dwukierunkowa oraz dwukie-runkowa cykliczna Roacuteżnią się one od siebie tylko tym że

w przypadku list dwukierunkowych mdashw strukturze el listy znajduje się jeszcze pole ktoacutere wska-zuje na element poprzedni

w przypadku list cyklicznych mdash ostatni element wskazuje na pierwszy (nie rozroacuteżnia się wtedyelementu pierwszego ani ostatniego)

Rozdział 20

Biblioteki

201 Czym jest bibliotekaBiblioteka jest to zbioacuter funkcji ktoacutere zostały wydzielone po to aby dało się z nich korzystać wwielu pro-gramach Ułatwia to programowanie mdash nie musimy np sami tworzyć funkcji printf Każda bibliotekaposiada swoje pliki nagłoacutewkowe ktoacutere zawierają deklaracje funkcji bibliotecznych oraz często zawartesą w nich komentarze jak używać danej funkcji W tej części podręcznika nauczymy się tworzyć naszewłasne biblioteki

202 Jak zbudowana jest bibliotekaKażda biblioteka składa się z co najmniej dwoacutech części

pliku nagłoacutewkowego z deklaracjami funkcji (plik z rozszerzeniem h)

pliku źroacutedłowego zawierającego ciała funkcji (plik z rozszerzeniem c)

2021 Budowa pliku nagłoacutewkowegoOto najprostszy możliwy plik nagłoacutewkowy

ifndef PLIK_Hdefine PLIK_H tutaj są wpisane deklaracje funkcji endif PLIK_H

Zapewne zapytasz się na co komu instrukcje ifndef define oraz endif Otoacuteż często się zdarzaże w programie korzystamy z plikoacutew nagłoacutewkowych ktoacutere dołączają się wzajemnie Oznaczałoby to żew kodzie programu kilka razy pojawiła by się zawartość tego samego pliku nagłoacutewkowego Instrukcjaifndef i define temu zapobiega Dzięki temu kompilator nie musi kilkakrotnie kompilować tegosamego kodu

W plikach nagłoacutewkowych często umieszcza się też definicje typoacutew z ktoacuterych korzysta bibliotekaalbo np makr

2022 Budowa najprostszej bibliotekiZałoacuteżmy że nasza biblioteka będzie zawierała jedną funkcję ktoacuterawypisuje na ekran tekst ldquoplWikibooksrdquoUtwoacuterzmy zatem nasz plik nagłoacutewkowy

151

152 ROZDZIAŁ 20 BIBLIOTEKI

ifndef WIKI_Hdefine WIKI_Hvoid wiki (void)endif

Należy pamiętać o podaniu void w liście argumentoacutew funkcji nie przyjmujących argumentoacutew Oile przy definicji funkcji nie trzeba tego robić (jak to często czyniliśmy w przypadku funkcji main) otyle w prototypie brak słoacutewka void oznacza że w prototypie nie ma informacji na temat tego jakieargumenty funkcja przyjmuje

Plik nagłoacutewkowy zapisujemy jako ldquowikihrdquo Teraz napiszmy ciało tej funkcji

include wikihinclude ltstdiohgt

void wiki (void)

printf (plWikibooksn)

Ważne jest dołączenie na początku pliku nagłoacutewkowego Dlaczego Plik nagłoacutewkowy zawieradeklaracje naszych funkcji mdash jeśli popełniliśmy błąd i deklaracja nie zgadza się z definicją kompilatorod razu nas o tym powiadomi Oproacutecz tego plik nagłoacutewkowy może zawierać definicje istotnych typoacutewlub makr

Zapiszmy naszą bibliotekę jako plik ldquowikicrdquo Teraz należy ją skompilować Robi się to trochę ina-czej niż normalny program Należy po prostu do opcji kompilatora gcc dodać opcję ldquo-crdquo

gcc wikic -c -o wikio

Rozszerzenie ldquoordquo jest domyślnym rozszerzeniem dla bibliotek statycznych (typowych bibliotek łą-czonych z resztą programu na etapie kompilacji) Teraz możemy spokojnie skorzystać z naszej nowejbiblioteki Napiszmy nasz program

include wikih

int main ()

wiki()return 0

Zapiszmy program jako ldquomaincrdquo Teraz musimy odpowiednio skompilować nasz program

gcc mainc wikio -o main

Uruchamiamy nasz program

mainplWikibooks

Jak widać nasza pierwsza biblioteka działaZauważmy że kompilatorowi podajemy i pliki z kodem źroacutedłowym (mainc) i pliki ze skompilo-

wanymi bibliotekami (wikio) by uzyskać plik wykonywalny (main) Jeśli nie podalibyśmy plikoacutew zbibliotekami mainc co prawda skompilowałby się ale błąd zostałby zgłoszony przez linker mdash częśćkompilatora odpowiedzialna za wstawienie w miejsce wywołań funkcji ich adresoacutew (takiego adresulinker nie moacutegłby znaleźć)

202 JAK ZBUDOWANA JEST BIBLIOTEKA 153

2023 Zmiana dostępu do funkcji i zmienny (static i extern)Język C w przeciwieństwie do swego młodszego krewnego mdash C++ nie posiada praktycznie żadnychmechanizmoacutew ochrony kodu biblioteki przed modyfikacjami C++ ma w swoim asortymencie minsterowanie uprawnieniami roacuteżnych elementoacutew klasy Jednak programista piszący program w C niejest tak do końca bezradny Autorzy C dali mu do ręki dwa narzędzia extern oraz static Pierwsze ztych słoacutew kluczowych informuje kompilator że dana funkcja lub zmienna istnieje ale w innymmiejscui zostanie dołączona do kodu programu w czasie łączenia go z biblioteką

extern przydaje się gdy zmienna lub funkcja jest zadeklarowana w bibliotece ale nie jest udostęp-niona na zewnątrz (nie pojawia się w pliku nagłoacutewkowym) Przykładowo

bibliotekah extern char zmienna_dzielona[]

bibliotekac include bibliotekah

char zmienna_dzielona[] = Zawartosc

mainc include ltstdiohgtinclude bibliotekah

int main()

printf(sn zmienna_dzielona)return 0

Gdybyśmy tu nie zastosowali extern kompilator (nie linker) zaprotestowałby że nie zna zmiennejzmienna dzielona Proacuteba dopisania deklaracji char zmienna dzielona stworzyłaby nową zmienną iutracilibyśmy dostęp do interesującej nas zawartości

Odwrotne działanie ma słowo kluczowe static użyte w tym kontekście (użyte wewnątrz bloku two-rzy zmienną statyczną więcej informacji w rozdziale Zmienne) Może ono odnosić się zaroacutewno dozmiennych jak i do funkcji globalnych Powoduje że dana zmienna lub funkcja jest niedostępna nazewnątrz biblioteki1 Możemy dzięki temu ukryć np funkcje ktoacutere używane są przez samą bibliotekęby nie dało się ich wykorzystać przez extern

1Tak naprawdę całe ldquoukrycierdquo funkcji polega na zmianie niektoacuterych danych w pliku z kodem binarnym danejbiblioteki (pliku o) przez co linker powoduje wygenerowanie komunikatu o błędzie w czasie łączenia biblioteki zprogramem

154 ROZDZIAŁ 20 BIBLIOTEKI

Rozdział 21

Więcej o kompilowaniu

211 Ciekawe opcje kompilatora Emdash powoduje wygenerowanie kodu programu ze zmianami wprowadzonymi przez preprocesor

S mdash zamiana kodu w języku C na kod asemblera (komenda gcc -S plikc spowoduje utworzeniepliku o nazwie pliks w ktoacuterym znajdzie się kod asemblera)

c mdash kompilacja bez łączenia z bibliotekami

Ikatalog mdash ustawienie domyślnego katalogu z plikami nagłoacutewkowymi na katalog

lbiblioteka mdash wymusza łączenie programu z podaną biblioteką (np -lGL)

212 Program makeDość często może się zdarzyć że nasz program składa się z kilku plikoacutew źroacutedłowych Jeśli tych plikoacutewjest mało (np -) możemy jeszcze proacutebować ręcznie kompilować każdy z nich Jednak jeśli tych plikoacutewjest dużo lub chcemy pokazać nasz program innym użytkownikom musimy stworzyć elegancki sposoacutebkompilacji naszego programu Właśnie po to aby zautomatyzować proces kompilacji powstał programmake Program make analizuje pliki Makefile i na ich podstawie wykonuje określone czynności

2121 Budowa pliku MakefileUwaga poniżej został omoacutewiony Makefile dla Make Istnieją inne programy make i mogą używaćinnej składni Na Wikibooks został też obszernie opisany program make firmy Borland

Najważniejszym elementem pliku Makefile są zależności oraz reguły przetwarzania Zależnościpolegają na tym że np jeśli nasz program ma być zbudowany z plikoacutew to najpierw należy skom-pilować każdy z tych plikoacutew a dopiero poacuteźniej połączyć je w jeden cały program Zatem zależnościokreślają kolejność wykonywanych czynności Natomiast reguły określają jak skompilować dany plikZależności tworzy się tak

co od_czegoreguły

Dzięki temu program make zna już kolejność wykonywanych działań oraz czynności jakie ma wy-konać Aby zbudować ldquocordquo należy wykonać polecenie make co Pierwsza reguła w pliku Makefile jestregułą domyślną Jeśli wydamy polecenie make bez parametroacutew zostanie zbudowana właśnie reguładomyślna Tak więc dobrze jest jako pierwszą regułę wstawić regułę budującą końcowy plik wykony-walny zwyczajowo regułę tą nazywa się all

155

156 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Należy pamiętać by sekcji ldquocordquo niewcinać natomiast ldquoregułyrdquowcinać tabulatorem Część ldquood czegordquomoże być pusta

Plik Makefile umożliwia też definiowanie pewnych zmiennych Nie trzeba tutaj się już troszczyć otyp zmiennej wystarczy napisać

nazwa_zmiennej = wartość

Wten sposoacutebmożemy zadeklarować dowolnie dużo zmiennych Zmiennemogą być roacuteżnemdash nazwakompilatora jego parametry iwiele innych Zmiennej używamywnastępujący sposoacuteb $(nazwa zmiennej)

Komentarze w pliku Makefile tworzymy zaczynając linię od znaku hash ()

2122 Przykładowy plik MakefileDość tej teorii teraz zajmiemy się działającym przykładem Załoacuteżmy że nasz przykładowy programnazywa się test oraz składa się z czterech plikoacutew pierwszyc drugic trzecic czwartyc

Odpowiedni plik Makefile powinien wyglądać mniej więcej tak

Moacutej plik makefile - wpisz make all aby skompilować cały program (właściwie wystarczy wpisać make - all jest domyślny jako pierwszy cel)CC = gcc

all pierwszyo drugio trzecio czwartyo$(CC) pierwszyo drugio trzecio czwartyo -o test

pierwszyo pierwszyc$(CC) pierwszyc -c -o pierwszyo

drugio drugic$(CC) drugic -c -o drugio

trzecio trzecic$(CC) trzecic -c -o trzecio

czwartyo czwartyc$(CC) czwartyc -c -o czwartyo

Widzimy że nasz program zależy od plikoacutew z rozszerzeniem o (pierwszyo itd) potem każdy ztych plikoacutew zależy od plikoacutew c ktoacutere program make skompiluje w pierwszej kolejności a następniepołączy w jeden program (test) Nazwę kompilatora zapisaliśmy jako zmienną ponieważ powtarza sięi zmienna jest sposobem by zmienić ją wszędzie za jednym zamachem

Zatem jak widać używanie pliku Makefile jest bardzo proste Warto na koniec naszego przykładudodać regułę ktoacutera wyczyści katalog z plikoacutew o

cleanrm -f o test

Ta reguła spowoduje usunięcie wszystkich plikoacutew o oraz naszego programu jeśli napiszemy makeclean

Możemy też ukryć wykonywane komendy albo dopisać własny opis czynności

cleanecho Usuwam gotowe plikirm -f o test

Ten sam plik Makefile moacutegłby wyglądać inaczej

213 OPTYMALIZACJE 157

CFLAGS = -g -O tutaj można dodawać inne flagi kompilatoraLIBS = -lm tutaj można dodawać biblioteki

OBJ =pierwszyo drugio trzecio czwartyo

all main

cleanrm -f o test

co$(CC) -c $(INCLUDES) $(CFLAGS) $lt

main $(OBJ)$(CC) $(OBJ) $(LIBS) -o test

Tak naprawdę jest to dopiero bardzo podstawowe wprowadzenie do używania programu makejednak jest ono wystarczające byś zaczął z niego korzystać Wyczerpujące omoacutewienie całego programuniestety przekracza zakres tego podręcznika

213 OptymalizacjeKompilator umożliwia generację kodu zoptymalizowanego dla konkretnej architektury Służą dotego opcje -mar= i -mtune= Stopień optymalizacji ustalamy za pomocą opcji -Ox gdzie x jest nume-rem stopnia optymalizacji (od do ) Możliwe jest też użycie opcji -Os ktoacutera powoduje generowaniekodu o jak najmniejszym rozmiarze Aby skompilować dany plik z optymalizacjami dla procesora Ath-lon należy napisać tak

gcc programc -o program -march=athlon-xp -O3

Z optymalizacjami należy uważać gdyż często zdarza się że kod skompilowany bez optymalizacjidziała zupełnie inaczej niż ten ktoacutery został skompilowany z optymalizacjami

2131 WyroacutewnywanieWyroacutewnywanie jest pewnym zjawiskiem na ktoacutere w bardzo wielu podręcznikach moacutewiących o Cw ogoacutele się nie wspomina Ten rozdział ma za zadanie wyjaśnienie tego zjawiska oraz uprzedzenieprogramisty o pewnych faktach ktoacutere w poacuteźniejszej jego ldquotwoacuterczościrdquo mogą zminimalizować czas naznalezienie pewnych informacji ktoacutere mogą wpływać na to że jego program nie będzie działał popraw-nie

Często zdarza się że kompilator w ramach optymalizacji ldquowyroacutewnujerdquo elementy struktury tak abyprocesor moacutegł łatwiej odczytać i przetworzyć dane Przyjrzyjmy się bliżej następującemu fragmentowikodu

typedef struct unsigned char wiek 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

158 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Aby procesor moacutegł łatwiej przetworzyć dane kompilator może dodać do tej struktury jedno ośmio-bitowe pole Wtedy struktura będzie wyglądała tak

typedef struct unsigned char wiek 8 bitoacutew unsigned char fill[1] 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

Wtedy rozmiar zmiennych przechowujących wiek płeć oraz dochoacuted będzie wynosił bity mdashbędzie zatem potęgą liczby dwa i procesorowi dużo łatwiej będzie tak ułożoną strukturę przechowywaćw pamięci cache Jednak taka sytuacja nie zawsze jest pożądana Może się okazać że nasza strukturamusi odzwierciedlać np pojedynczy pakiet danych przesyłanych przez sieć Nie może być w niejzatem żadnych innych poacutel poza tymi ktoacutere są istotne do transmisji Aby wymusić na kompilatorzewyroacutewnanie -bajtowe (co w praktyce wyłącza je) należy przed definicją struktury dodać dwie linijkiTen kod działa pod Visual C++

pragma pack(push)pragma pack(1)

struct struktura

pragma pack(pop)

W kompilatorze należy po deklaracji struktury dodajemy przed średnikiem kończącym jednąlinijkę

__attribute__ ((packed))

Działa ona dokładnie tak samo jak makra pragma jednak jest ona obecna tylko w kompilatorze

Dzięki użyciu tego atrybutu kompilator zostanie ldquozmuszonyrdquo do braku ingerencji w naszą strukturęJest jednak jeszcze jeden być może bardziej elegancki sposoacuteb na obejście dopełniania Zauważyłeś żedopełnienie dodane przez kompilator pojawiło się między polem o długości bitoacutew (plec) oraz polem odługości bitoacutew (dochod) Wyroacutewnywanie polega na tym że dana zmienna powinna być umieszczonapod adresem będącym wielokrotnością jej rozmiaru Oznacza to że jeśli np mamy w strukturze napoczątku dwie zmienne o rozmiarze jednego bajta a potem jedną zmienną o rozmiarze bajtoacutew topomiędzy polami o rozmiarze bajtoacutew a polem czterobajtowym pojawi się dwubajtowe dopełnienieMoże Ci się wydawać że jest to tylko niepotrzebne mącenie w głowie jednak niektoacutere architektury(zwłaszcza typu ) mogą nie wykonać kodu ktoacutery nie został wyroacutewnany Dlatego naszą strukturępowinniśmy zapisać mniej więcej tak

typedef struct unsigned short dochod 16 bitoacutew unsigned char wiek 8 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

W ten sposoacuteb wyroacutewnana struktura nie będzie podlegała modyfikacjom przez kompilator oraz bę-dzie przenośna pomiędzy roacuteżnymi kompilatorami

Wyroacutewnywanie działa także na pojedynczych zmiennych w programie jednak ten problem nie po-woduje tyle zamieszania co ingerencja kompilatora w układ poacutel struktury Wyroacutewnywanie zmiennychpolega tylko na tym że kompilator umieszcza je pod adresami ktoacutere są wielokrotnością ich rozmiaru

214 KOMPILACJA KRZYŻOWA 159

214 Kompilacja krzyżowaMając w domu dwa komputery o odmiennych architekturach (np i oraz Sparc) możemy potrze-bować stworzyć program dla jednej maszyny mając do dyspozycji tylko drugi komputer Nie musimywtedy latać do znajomego posiadającego odpowiedni sprzęt Możemy skorzystać z tzw kompilacjikrzyżowej (ang cross-compile) Polega ona na tym że program nie jest kompilowany pod procesorna ktoacuterym działa kompilator lecz na inną zdefiniowaną wcześniej maszynę Efekt będzie taki sam askompilowany program możemy bez problemu uruchomić na drugim komputerze

215 Inne narzędziaWśroacuted przydatnych narzędzi warto wymienić roacutewnież program objdump (zaroacutewno pod Unix jak ipod Windows) oraz readelf (tylko Unix) Objdump służy do deasemblacji i analizy skompilowanychprogramoacutew Readelf służy do analizy pliku wykonywalnego w formacie (używanego w większościsystemoacutew z rodziny Unix) Więcej informacji możesz uzyskać pisząc (w systemach Unix)

man 1 objdumpman 1 readelf

160 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Rozdział 22

Zaawansowane operacjematematyczne

221 Biblioteka matematycznaAby moacutec korzystać z wszystkich dobrodziejstw funkcji matematycznych musimy na początku dołączyćplik mathh

include ltmathhgt

A w procesie kompilacji (dotyczy kompilatora GCC) musimy niekiedy dodać flagę ldquo-lmrdquo

gcc plikc -o plik -lm

Funkcje matematyczne ktoacutere znajdują się w bibliotece standardowej możesz znaleźć tutaj Przykorzystaniu z nich musisz wziąć pod uwagę min to że biblioteka matematyczna prowadzi kalkulacjęw oparciu o radiany a nie stopnie

2211 Stałe matematyczneW pliku mathh zdefiniowane są pewne stałe ktoacutere mogą być przydatne do obliczeń Są to min

M E mdash podstawa logarytmu naturalnego (e liczba Eulera)

M LOG2E mdash logarytm o podstawie z liczby e

M LOG10E mdash logarytm o podstawie z liczby e

M LN2 mdash logarytm naturalny z liczby

M LN10 mdash logarytm naturalny z liczby

M PI mdash liczba π

M PI 2 mdash liczba π

M PI 4 mdash liczba π

M 1 PI mdash liczba π

M 2 PI mdash liczba π

161

162 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

222 Prezentacja liczb rzeczywisty w pamięci komputeraByć może ten temat może wydać Ci się niepotrzebnym lecz w wielu książkach nie ma w ogoacutele tegotematu Dzięki niemu zrozumiesz jak komputer radzi sobie z przecinkiem oraz dlaczego niektoacutere ob-liczenia dają niezbyt dokładne wyniki Na początek trochę teorii do przechowywania liczb rzeczywi-stych przeznaczone są typy float double oraz long double Zajmują one odpowiednio oraz bitoacutew Wiemy też że komputer nie ma fizycznej możliwości zapisania przecinka Sproacutebujmy terazzapisać jakąś liczbę wymierną w formie liczb binarnych Nasza liczba to powiedzmy 425 Sproacutebujmy jąrozbić na sumę potęg dwoacutejki 4 = 1 middot22 +0 middot21 +0 middot20 Dobra mdash rozpisaliśmy liczbę 4 ale co z częściądziesiętną Skorzystajmy z zasad matematyki 025 = 2minus2 Zatem nasza liczba powinna wyglądaćtak

10001Ponieważ komputer nie jest w stanie przechować pozycji przecinka ktoś wpadł na prosty ale

sprytny pomysł ustawienia przecinka jak najbliżej początku liczby i tylko mnożenia jej przez odpowied-nią potęgę dwoacutejki Taki sposoacuteb przechowywania liczb nazywamy zmiennoprzecinkowym a procesprzekształcania naszej liczby z postaci czytelnej przez człowieka na format zmiennoprzecinkowy na-zywamy normalizacją Wroacutećmy do naszej liczby mdash 425 W postaci binarnej wygląda ona tak 10001natomiast po normalizacji będzie wyglądała tak 10001 middot 22 W ten sposoacuteb w pamięci komputeraznajdą się dwie informacje liczba zakodowana w pamięci z ldquowirtualnymrdquo przecinkiem oraz numerpotęgi dwoacutejki Te dwie informacje wystarczają do przechowania wartości liczby Jednak pojawia sięinny problem mdash co się stanie jeśli np będziemy chcieli przełożyć liczbę typu 1

3 Otoacuteż tutaj wychodzą

na wierzch pewne niedociągnięcia komputera w dziedzinie samej matematyki daje w rozwinięciudziesiętnym 0(3) Jak zatem zapisać taką liczbę Otoacuteż nie możemy przechować całego jej rozwinięcia(wynika to z ograniczeń typu danych mdash ma on niestety skończoną liczbę bitoacutew) Dlatego przechowujesię tylko pewne przybliżenie liczby Jest ono tym bardziej dokładne im dany typ ma więcej bitoacutew Za-tem do obliczeń wymagających dokładnych danych powinniśmy użyć typu double lub long double Naszczęście w większości przeciętnych programoacutew tego typu problemy zwykle nie występują A ponie-waż początkujący programista nie odpowiada za tworzenie programoacutew sterujących np lotem statkukosmicznego więc drobne przekłamania na odległych miejscach po przecinku nie stanowią większegoproblemu

Należy brać pod uwagę że w komputerze liczby rzeczywiste nie są tym samym czym w mate-matyce Komputery nie potrafią przechować każdej liczby zmiennoprzecinkowej w związku z tymobliczenia prowadzone przy użyciu komputera mogą być niedokładne i odbiegać od prawidłowych wy-nikoacutew Szczegoacutelnie ważne jest to przy programowaniu aplikacji inżynieryjnych oraz w medycyniegdzie takie błędy mogą skutkować katastrofą ilub narażeniem ludzkiego życia oraz zdrowia

Na ile poważny jest to problem Sproacutebujmy przyjrzeć się działaniu polegającym na -krotnymdodawaniu do liczby wartości Oto kod

include ltstdiohgt

int main ()

float a = 0int i = 0for (ilt1000i++)

a += 1030printf (fn a)

Z matematyki wynika że 1000 middot 13

= 333(3) podczas gdy komputer wypisze wynik nieco roacuteżniącysię od oczekiwanego (w moim przypadku)

223 LICZBY ZESPOLONE 163

333334106

Błąd pojawił się na cyfrze części tysięcznej liczby Nie jest to może poważny błąd jednak zastanoacutewmysię czy ten błąd nie będzie się powiększał Zamieniamy w kodzie ilość iteracji z na Tymrazem moacutej komputer wskazał już nieco inny wynik

33356554688

Błąd przesunął się na cyfrę dziesiątek w liczbie Tak więc nie należy do końca polegać na prezentacjiliczb zmiennoprzecinkowych w komputerze

223 Liczby zespoloneOperacje na liczba zespolony są częścią uaktualnionego standardu języka C o nazwie C ktoacuteryjest obsługiwany jedynie przez część kompilatoroacutew

Podane tutaj informacje zostały sprawdzone na systemie Gentoo Linux z biblioteką GNU libc wwersji i kompilatorem GCC w wersji

Dotychczas korzystaliśmy tylko z liczb rzeczywistych lecz najnowsze standardy języka C umożli-wiają korzystanie także z innych liczb mdash np z liczb zespolonych

Abymoacutec korzystać z liczb zespolonychwnaszymprogramie należywnagłoacutewku programu umieścićnastępującą linijkę

include ltcomplexhgt

Wiemy że liczba zespolona zdeklarowana jest następująco

z = a+bi gdzie a b są liczbami rzeczywistymi a ii=(-1)

W pliku complexh liczba i zdefiniowana jest jako I Zatem wyproacutebujmy możliwości liczb zespolo-nych

include ltmathhgtinclude ltcomplexhgtinclude ltstdiohgt

int main ()

float _Complex z = 4+25Iprintf (Liczba z f+fin creal(z) cimag (z))return 0

następnie kompilujemy nasz program

gcc plik1c -o plik1 -lm

Po wykonaniu naszego programu powinniśmy otrzymać

Liczba z 400+250i

W programie zamieszczonym powyżej użyliśmy dwoacutech funkcji mdash creal i cimag

creal mdash zwraca część rzeczywistą liczby zespolonej

cimag mdash zwraca część urojoną liczby zespolonej

164 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

Rozdział 23

Powszene praktyki

Rozdział tenma za zadanie pokazać powszechnie stosowanemetody programowania wC Nie będziemytu uczyć jak należy stawiać nawiasy klamrowe ani ktoacutery sposoacuteb nazewnictwa zmiennych jest najlep-szy mdash prowadzone są o to spory z ktoacuterych niewiele wynika Zaprezentowane tu rozwiązania mająkonkretny wpływ na jakość tworzonych programoacutew

231 Konstruktory i destruktoryW większości obiektowych językoacutew programowania obiekty nie mogą być tworzone bezpośrednio mdashobiekty otrzymuje się wywołując specjalną metodę danej klasy zwaną konstruktorem Konstruktorysą ważne ponieważ pozwalają zapewnić obiektowi odpowiedni stan początkowy Destruktory wywo-ływane na końcu czasu życia obiektu są istotne gdy obiekt ma wyłączny dostęp do pewnych zasoboacutewi konieczne jest upewnienie się czy te zasoby zostaną zwolnione

Ponieważ C nie jest językiem obiektowym nie ma wbudowanego wsparcia dla konstruktoroacutew idestruktoroacutew Często programiści bezpośrednio modyfikują tworzone obiekty i struktury Jednakżeprowadzi to do potencjalnych błędoacutew ponieważ operacje na obiekcie mogą się nie powieść lub zacho-wać się nieprzewidywalnie jeśli obiekt nie został prawidłowo zainicjalizowany Lepszym podejściemjest stworzenie funkcji ktoacutera tworzy instancję obiektu ewentualnie przyjmując pewne parametry

struct string size_t sizechar data

struct string create_string(const char initial) assert (initial = NULL)struct string new_string = malloc(sizeof(new_string))if (new_string = NULL)

new_string-gtsize = strlen(initial)new_string-gtdata = strdup(initial)

return new_string

Podobnie bezpośrednie usuwanie obiektoacutew może nie do końca się udać prowadząc do wyciekuzasoboacutew Lepiej jest użyć destruktora

void free_string(struct string s)

165

166 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

assert (s = NULL)free(s-gtdata) zwalniamy pamięć zajmowaną przez strukturę free(s) usuwamy samą strukturę

Często łączy się destruktory z zerowaniem zwolnionych wskaźnikoacutewCzasami dobrze jest ukryć definicję obiektu żeby mieć pewność że użytkownicy nie utworzą go

ręcznie Aby to zapewnić struktura jest definiowanaw pliku źroacutedłowym (lub prywatnymnagłoacutewku nie-dostępnym dla użytkownikoacutew) zamiast w pliku nagłoacutewkowym a deklaracja wyprzedzająca jest umiesz-czona w pliku nagłoacutewkowym

struct stringstruct string create_string(const char initial)void free_string(struct string s)

232 Zerowanie zwolniony wskaźnikoacutewJak powiedziano już wcześniej po wywołaniu free() dla wskaźnika staje się on ldquowiszącym wskaź-nikiemrdquo Co gorsze większość nowoczesnych platform nie potrafi wykryć kiedy taki wskaźnik jestużywany zanim zostanie ponownie przypisany

Jednym z prostych rozwiązań tego problemu jest zapewnienie że każdy wskaźnik jest zerowanynatychmiast po zwolnieniu

free(p)p = NULL

Inaczej niż w przypadku ldquowiszących wskaźnikoacutewrdquo na wielu nowoczesnych architekturach przyproacutebie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek Dodatkowo programy mogązawierać sprawdzanie błędoacutew dla zerowych wartości ale nie dla ldquowiszących wskaźnikoacutewrdquo Aby zapew-nić że jest to wykonywane dla każdego wskaźnika możemy użyć makra

define FREE(p) do free(p) (p) = NULL while(0)

(aby zobaczyć dlaczego makro jest napisane w ten sposoacuteb zobacz Konwencje pisania makr)Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik ktoacutery przekazuje się do

nich więc argument musi być do nich przekazywany przez referencję Na przykład oto zaktualizowanydestruktor z sekcji Konstruktory i destruktory

void free_string(struct string s)

assert(s = NULL ampamp s = NULL)FREE((s)-gtdata) zwalniamy pamięć zajmowaną przez strukturę FREE(s) usuwamy strukturę

Niestety ten idiom nie jest wstanie pomoacutec wwypadkuwskazywania przez inne wskaźniki zwolnio-nej pamięci Z tego powodu niektoacuterzy eksperci C uważają go za niebezpieczny jako kreujący fałszywepoczucie bezpieczeństwa

233 Konwencje pisania makrPonieważ makra preprocesora działają na zasadzie zwykłego zastępowania napisoacutew są podatne nawiele kłopotliwych błędoacutew z ktoacuterych części można uniknąć przez stosowanie się do poniższych reguł

234 JAK DOSTAĆ SIĘ DO KONKRETNEGO BITU 167

Umieszczaj nawiasy dookoła argumentoacutew makra kiedy to tylko możliwe Zapewnia to że gdysą wyrażeniami kolejność działań nie zostanie zmieniona Na przykład

Źle define kwadrat(x) (xx)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Załoacuteżmy że w programie makro kwadrat() zdefiniowane bez nawiasoacutew zostałowywołane następująco kwadrat(a+b) Wtedy zostanie ono zamienione przez preprocesorna (a+ba+b) Z kolejności działań wiemy że najpierw zostanie wykonane mnożeniewięc wartość wyrażenia kwadrat(a+b) będzie roacuteżna od kwadratu wyrażenia a+b

Umieszczaj nawiasy dookoła całegomakra jeśli jest pojedynczymwyrażeniem Ponownie chronito przed zaburzeniem kolejności działań

Źle define kwadrat(x) (x)(x)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Definiujemy makro define suma(a b) (a)+(b) i wywołujemy je w kodziewynik = suma(3 4) 5 Makro zostanie rozwinięte jako wynik = (3)+(4)5 co mdash zpowodu kolejności działań mdash da wynik inny niż pożądany

Jeśli makro składa się z wielu instrukcji lub deklaruje zmienne powinno być umieszczone w pętlido while(0) bez kończącego średnika Pozwala to na użycie makra jak pojedynczejinstrukcji w każdym miejscu jak ciało innego wyrażenia pozwalając jednocześnie na umiesz-czenie średnika po makrze bez tworzenia zerowego wyrażenia Należy uważać by zmienne wmakrze potencjalnie nie kolidowały z argumentami makra

Źle define FREE(p) free(p) p = NULL

Dobrze define FREE(p) do free(p) p = NULL while(0)

Unikaj używania argumentoacutew makra więcej niż raz wewnątrz makra Może to spowodowaćkłopoty gdy argument makra ma efekty uboczne (np zawiera operator inkrementacji)

Przykład define kwadrat(x) ((x)(x)) nie powinno być wywoływane z operatoreminkrementacji kwadrat(a++) ponieważ zostanie to rozwinięte jako ((a++) (a++)) co jestniezgodne ze specyfikacją języka i zachowanie takiego wyrażenia jest niezdefiniowane(dwukrotna inkrementacja w tym samym wyrażeniu)

Jeśli makro może być w przyszłości zastąpione przez funkcję rozważ użycie w nazwie małychliter jak w funkcji

234 Jak dostać się do konkretnego bituWiemy że komputer to maszyna ktoacuterej najmniejszą jednostką pamięci jest bit jednak w C najmniejszazmienna ma rozmiar bitoacutew (czyli jednego bajtu) Jak zatem można odczytać wartość pojedynczychbitoacutew W bardzo prosty sposoacuteb mdashw zestawie operatoroacutew języka C znajdują się tzw operatory bitoweSą to m in

amp mdash logiczne ldquoirdquo

| mdash logiczne ldquolubrdquo

˜ mdash logiczne ldquonierdquo

Oproacutecz tego są także przesunięcia (ltlt oraz gtgt) Zastanoacutewmy się teraz jak je wykorzystać w prak-tyce Załoacuteżmy że zajmujemy się jednobajtową zmienną

unsigned char i = 2

Zmatematyki wiemy że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej) Jeśli teraz np chcielibyśmy ldquozapalićrdquo drugi bit od lewej (tj bit ktoacuterego zapalenie niejako ldquododardquo doliczby wartość 6) powinniśmy użyć logicznego lub

168 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

unsigned char i = 2i |= 64

Gdzie =6 Odczytywanie wykonuje się za pomocą tzw maski bitowej Polega to na

wyzerowaniu bitoacutew ktoacutere są nam w danej chwili niepotrzebne

odpowiedniemu przesunięciu bitoacutew dzięki czemu szukany bit znajdzie się na pozycji pierwszegobitu od prawej

Do ldquowyłuskaniardquo odpowiedniego bitu możemy posłużyć się operacją ldquoirdquo mdash czyli operatorem ampWygląda to analogicznie do posługiwania się operatorem ldquolubrdquo

unsigned char i = 3 bitowo 00000011 unsigned char temp = 0temp = i amp 1 sprawdzamy najmniej znaczący bit - czyli pierwszy z prawej if (temp)

printf (bit zapalony)else

printf (bit zgaszony)

Jeśli nie władasz biegle kodem binarnym tworzenie masek bitowych ułatwią ci przesunięcia bitoweAby uzyskać liczbę ktoacutera ma zapalony bit o numerze n (bity są liczone od zera) przesuwamy bitowo wlewo jedynkę o n pozycji

1 ltlt n

Jeśli chcemy uzyskać liczbę w ktoacuterej zapalone są bity na pozycjach l m n mdash używamy sumylogicznej (ldquolubrdquo)

(1 ltlt l) | (1 ltlt m) | (1 ltlt n)

Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone sąwszystkie bity poza n odwracamy ją za pomocąoperatora logicznej negacji

~(1 ltlt n)

Warto władać biegle operacjami na bitach ale początkujący mogą (po uprzednim przeanalizowa-niu) zdefiniować następujące makra i ich używać

Sprawdzenie czy w liczbie k jest zapalony bit n define IS_BIT_SET(k n) ((k) amp (1 ltlt (n)))

Zapalenie bitu n w zmiennej k define SET_BIT(k n) (k |= (1 ltlt (n)))

Zgaszenie bitu n w zmiennej k define RESET_BIT(k n) (k amp= ~(1 ltlt (n)))

235 Skroacutety notacjiIstnieją pewne sposoby ograniczenia ilości niepotrzebnego kodu Przykładem może być wykonywaniejednej operacji w razie wystąpienia jakiegoś warunku np zamiast pisać

if (warunek) printf (Warunek prawdziwyn)

235 SKROacuteTY NOTACJI 169

możesz skroacutecić notację do

if (warunek)printf (Warunek prawdziwyn)

Podobnie jest w przypadku pętli for

for (warunek)printf (Wyświetlam się w pętlin)

Niestety ograniczeniemw tymwypadku jest to że można w ten sposoacuteb zapisać tylko jedną instruk-cję

170 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

Rozdział 24

Przenośność programoacutew

Jak dowiedziałeś się z poprzednich rozdziałoacutew tego podręcznika język C umożliwia tworzenie progra-moacutew ktoacutere mogą być uruchamiane na roacuteżnych platformach sprzętowych pod warunkiem ich powtoacuter-nej kompilacji Język C należy do grupy językoacutew wysokiego poziomu ktoacutere tłumaczone są do poziomukodumaszynowego (tzn kod źroacutedłowy jest kompilowany) Z jednej strony jest to korzystne posunięciegdyż programy są szybsze i mniejsze niż programy napisane w językach interpretowanych (takich wktoacuterych kod źroacutedłowy nie jest kompilowany do kodu maszynowego tylko na bieżąco interpretowanyprzez tzw interpreter) Jednak istnieje także druga strona medalu mdash pewne zawiłości sprzętu ktoacutereograniczają przenośność programoacutew Ten rozdział ma wyjaśnić Ci mechanizmy działania sprzętu wtaki sposoacuteb abyś bez problemu moacutegł tworzyć poprawne i całkowicie przenośne programy

241 Niezdefiniowane zaowanie i zaowanie zależne odimplementacji

Wtrakcie czytania kolejnych rozdziałoacutewmożna było się natknąć na zwroty takie jak zachowanie niezde-finiowane (ang undefined behaviour) czy zachowanie zależne od implementacji (ang implementation-defined behaviour) Coacuteż one tak właściwie oznaczają

Zacznijmy od tego drugiego Autorzy standardu języka C czuli że wymuszanie jakiegoś konkret-nego działania danego wyrażenia byłoby zbytnim obciążeniem dla osoacuteb piszących kompilatory gdyżdany wymoacuteg moacutegłby być bardzo trudny do zrealizowania na konkretnej architekturze Dla przykładugdyby standard wymagał że typ unsigned char ma dokładnie bitoacutew to napisanie kompilatora dla ar-chitektury na ktoacuterej bajt ma bitoacutew byłoby cokolwiek kłopotliwe a z pewnością wynikowy programdziałałby o wiele wolniej niżby to było możliwe

Z tego właśnie powodu niektoacutere aspekty języka nie są określone bezpośrednio w standardzie i sąpozostawione do decyzji zespołu (osoby) piszącego konkretną implementację W ten sposoacuteb nie mażadnych przeciwwskazań (ze strony standardu) aby na architekturze gdzie bajty mają bitoacutew typchar roacutewnież miał tyle bitoacutew Dokonany wyboacuter musi być jednak opisany w dokumentacji kompilatoratak żeby osoba pisząca program w C mogła sprawdzić jak dana konstrukcja zadziała

Należy zatem pamiętać że poleganie na jakimś konkretnym działaniu programu w przypadkachzachowania zależnego od implementacji drastycznie zmniejsza przenośność kodu źroacutedłowego

Zachowania niezdefiniowane są o wiele groźniejsze gdyż zaistnienie takowego może spowodo-wać dowolny efekt ktoacutery nie musi być nigdzie udokumentowany Przykładem może tutaj być proacutebaodwołania się do wartości wskazywanej przez wskaźnik o wartości

Jeżeli gdzieś w naszym programie zaistnieje sytuacja niezdefiniowanego zachowania to nie jest jużto kwestia przenośności kodu ale po prostu błędu w kodzie chyba że świadomie korzystamy z roz-szerzenia naszego kompilatora Rozważmy odwoływanie się do wartości wskazywanej przez wskaźniko wartości Ponieważ według standardu operacja taka ma niezdefiniowany skutek to w szcze-goacutelności może wywołać jakąś z goacutery określoną funkcję mdash kompilator może coś takiego zrealizować

171

172 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

sprawdzając wartość wskaźnika przed każdą dereferencją w ten sposoacuteb niezdefiniowane zachowaniedla konkretnego kompilatora stanie się jak najbardziej zdefiniowane

Sytuacją wziętą z życia są operatory przesunięć bitowych gdy działają na liczbach ze znakiemKonkretnie przesuwanie w lewo liczb jest dla wielu przypadkoacutew niezdefiniowane Bardzo często jed-nak w dokumentacji kompilatora działanie przesunięć bitowych jest dokładnie opisane Jest to o tyleinteresujący fakt iż wielu programistoacutew nie zdaje sobie z niego sprawy i nieświadomie korzysta z roz-szerzeń kompilatora

Istnieje jeszcze trzecia klasa zachowań Zachowania nieokreślone (ang unspecified behaviour)Są to sytuacje gdy standard określa kilka możliwych sposoboacutew w jaki dane wyrażenie może działaći pozostawia kompilatorowi decyzję co z tym dalej zrobić Coś takiego nie musi być nigdzie opisanew dokumentacji i znowu poleganie na konkretnym zachowaniu jest błędem Klasycznym przykłademmoże być kolejność obliczania argumentoacutew wywołania funkcji

242 Rozmiar zmiennyRozmiar poszczegoacutelnych typoacutew danych (np char int czy long) jest roacuteżna na roacuteżnych platformachgdyż nie jest definiowany w sztywny sposoacuteb jak np ldquolong int zawsze powinien mieć bityrdquo (takieokreślenie wiązałoby się z wyżej opisanymi trudnościami) lecz w na zasadzie zależności typu ldquolongpowinien być nie kroacutetszy niż intrdquo ldquoshort nie powinien być dłuższy od intrdquo Pierwsza standaryzacjajęzyka C zakładała że typ int będzie miał taki rozmiar jak domyślna długość liczb całkowitych nadanym komputerze natomiast modyfikatory short oraz long zmieniały długość tego typu tylko wtedygdy dana maszyna obsługiwała typy o mniejszej lub większej długości1

Z tego powodu nigdy nie zakładaj że dany typ będzie miał określony rozmiar Jeżeli potrzebujesztypu o konkretnym rozmiarze (a dokładnej konkretnej liczbie bitoacutew wartości) możesz skorzystać z plikunagłoacutewkowego stdinth wprowadzonego do języka przez standard ISO C z roku Definiuje on typyint t int t int t int t uint t uint t uint t i uint t (o ile w danej architekturze występujątypy o konkretnej liczbie bitoacutew)

Jednak możemy posiadać implementację ktoacutera nie posiada tego pliku nagłoacutewkowego W takiej sy-tuacji nie pozostaje nam nic innego jak tworzyć własny plik nagłoacutewkowy w ktoacuterym za pomocą słoacutewkatypedef sami zdefiniujemy potrzebne nam typy Np

typedef unsigned char u8typedef signed char s8typedef unsigned short u16typedef signed short s16typedef unsigned long u32typedef signed long s32typedef unsigned long long u64typedef signed long long s64

Aczkolwiek należy pamiętać że taki plik będzie trzeba pisać od nowa dla każdej architektury najakiej chcemy kompilować nasz program

243 Porządek bajtoacutew i bitoacutew

2431 Bajty i słowaWiesz zapewne że podstawową jednostką danych jest bit ktoacutery może mieć wartość lub Kilkakolejnych bitoacutew2 stanowi bajt (dla skupienia uwagi przyjmijmy że bajt składa się z bitoacutew) Częstotyp short ma wielkość dwoacutech bajtoacutew i woacutewczas pojawia się pytanie w jaki sposoacuteb są one zapisane

1Dokładniejszy opis rozmiaroacutew dostępny jest w rozdziale Składnia2Standard wymaga aby było ich co najmniej 8 i liczba bitoacutew w bajcie w konkretnej implementacji jest określona

przez makro CHAR BIT zdefiniowane w pliku nagłoacutewkowym limitsh

243 PORZĄDEK BAJTOacuteW I BITOacuteW 173

w pamięci mdash czy najpierw ten bardziej znaczący mdash big-endian czy najpierw ten mniej znaczący mdashlittle-endian

Skąd takie nazwy Otoacuteż pochodzą one z książki Podroacuteże Guliwera w ktoacuterej liliputy kłoacuteciły się ostronę od ktoacuterej należy rozbijać jajko na twardo Jedni uważali że trzeba je rozbijać od grubszegokońca (big-endian) a drudzy że od cieńszego (lile-endian) Nazwy te są o tyle trafne że w wypadkuprocesoroacutew wyboacuter kolejności bajtoacutew jest sprawą czysto polityczną ktoacutera jest technicznie neutralna

Sprawa się jeszcze bardziej komplikuje w przypadku typoacutew ktoacutere składają się np z bajtoacutew Woacutew-czas są aż ( silnia) sposoby zapisania kolejnych fragmentoacutew takiego typu W praktyce zapewne spo-tkasz się jedynie z kolejnościami big-endian lub lile-endian co nie zmienia faktu że inne możliwościtakże istnieją i przy pisaniu programoacutew ktoacutere mają być przenośne należy to brać pod uwagę

Poniższy przykład dobrze obrazuje oba sposoby przechowywania zawartości zmiennych w pamięcikomputera (przyjmujemy CHAR BIT == oraz sizeof(long) == bez bitoacutew wypełnienia (ang paddingbits)) unsigned long zmienna = 0x01020304 w pamięci komputera będzie przechowywana tak

adres | 0 | 1 | 2 | 3 |big-endian |0x01|0x02|0x03|0x04|little-endian |0x04|0x03|0x02|0x01|

2432 Konwersja z jednego porządku do innegoCzasami zdarza się że napisany przez nas program musi się komunikować z innym programem (możeteż przez nas napisanym) ktoacutery działa na komputerze o (potencjalnie) innym porządku bajtoacutew Częstonajprościej jest przesyłać liczby jako tekst gdyż jest on niezależny od innych czynnikoacutew jednak takiformat zajmuje więcej miejsca a nie zawsze możemy sobie pozwolić na taką rozrzutność

Przykładem może być komunikacja sieciowa w ktoacuterej przyjęło się że dane przesyłane są w po-rządku big-endian Aby moacutec łatwo operować na takich danych w standardzie zdefiniowanonastępujące funkcje (w zasadzie zazwyczaj są to makra)

include ltarpainethgtuint32_t htonl(uint32_t hostlong)uint16_t htons(uint16_t hostshort)uint32_t ntohl(uint32_t netlong)uint16_t ntohs(uint16_t netshort)

Pierwsze dwie konwertują liczbę z reprezentacji lokalnej na reprezentację big-endian (host to ne-twork) natomiast kolejne dwie dokonują konwersji w drugą stronę (network to host)

Można roacutewnież skorzystać z pliku nagłoacutewkowego endianh w ktoacuterym definiowane są makra po-zwalające określić porządek bajtoacutew

include ltendianhgtinclude ltstdiohgt

int main() if __BYTE_ORDER == __BIG_ENDIAN

printf(Porządek big-endian (4321)n)elif __BYTE_ORDER == __LITTLE_ENDIAN

printf(Porządek little-endian (1234)n)elif defined __PDP_ENDIAN ampamp __BYTE_ORDER == __PDP_ENDIAN

printf(Porządek PDP (3412)n)else

printf(Inny porządek (d)n __BYTE_ORDER)endif

return 0

174 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

Na podstawie makra BYTE ORDER można skonstruować funkcję ktoacutera będzie konwertowaćliczby pomiędzy porządkiem roacuteżnymi porządkami

include ltendianhgtinclude ltstdiohgtinclude ltstdinthgt

uint32_t convert_order32(uint32_t val unsigned from unsigned to) if (from==to)

return val else

uint32_t ret = 0unsigned char tmp[5] = 0 0 0 0 0 unsigned char ptr = (unsigned char)ampvalunsigned div = 1000do tmp[from div 10] = ptr++ while ((div = 10))ptr = (unsigned char)ampretdiv = 1000do ptr++ = tmp[to div 10] while ((div = 10))return ret

define LE_TO_H(val) convert_order32((val) 1234 __BYTE_ORDER)define H_TO_LE(val) convert_order32((val) __BYTE_ORDER 1234)define BE_TO_H(val) convert_order32((val) 4321 __BYTE_ORDER)define H_TO_BE(val) convert_order32((val) __BYTE_ORDER 4321)define PDP_TO_H(val) convert_order32((val) 3412 __BYTE_ORDER)define H_TO_PDP(val) convert_order32((val) __BYTE_ORDER 3412)

int main ()

printf(08xn LE_TO_H(0x01020304))printf(08xn H_TO_LE(0x01020304))printf(08xn BE_TO_H(0x01020304))printf(08xn H_TO_BE(0x01020304))printf(08xn PDP_TO_H(0x01020304))printf(08xn H_TO_PDP(0x01020304))return 0

Ciągle jednak polegamy na niestandardowym pliku nagłoacutewkowym endianh Można go wyelimi-nować sprawdzając porządek bajtoacutew w czasie wykonywania programu

include ltstdiohgtinclude ltstdinthgt

int main() uint32_t val = 0x04030201unsigned char v = (unsigned char )ampvalint byte_order = v[0] 1000 + v[1] 100 + v[2] 10 + v[3]

if (byte_order == 4321) printf(Porządek big-endian (4321)n)

else if (byte_order == 1234)

244 BIBLIOTECZNE PROBLEMY 175

printf(Porządek little-endian (1234)n) else if (byte_order == 3412)

printf(Porządek PDP (3412)n) else

printf(Inny porządek (d)n byte_order)return 0

Powyższe przykłady opisują jedynie część problemoacutew jakie mogą wynikać z proacuteby przenoszeniabinarnych danych pomiędzy wieloma platformami Wszystkie co więcej zakładają że bajt ma bitoacutewco wcale nie musi być prawdą dla konkretnej architektury na ktoacuterą piszemy aplikację Co więcej liczbymogą posiadać w swojej reprezentacje bity wypełnienia (ang padding bits) ktoacutere nie biorą udziaływ przechowywaniu wartości liczby Te wszystkie roacuteżnice mogą dodatkowo skomplikować kod Toteżnależy być świadomym iż przenosząc dane binarnie musimy uważać na roacuteżne reprezentacje liczb

244 Biblioteczne problemy

2441 Dostępność bibliotekPisząc programy nieraz będziemy musieli korzystać z roacuteżnych bibliotek Problem polega na tym żenie zawsze będą one dostępne na komputerze na ktoacuterym inny użytkownik naszego programu będzieproacutebował go kompilować Dlatego też ważne jest abyśmy korzystali z łatwo dostępnych bibliotek ktoacuteredostępne są na wiele roacuteżnych systemoacutew i platform sprzętowych Zapamiętaj Twoacutej program jest natyle przenośny na ile przenośne są biblioteki z ktoacuterych korzysta

2442 Odmiany bibliotekPod Windows funkcje atan floor i fabs są w tej samej bibliotece co standardowe funkcje C

Pod Uniksami są w osobnej bibliotece matematycznej libm w wersji

statycznej (zwykle usrliblibma) i pliku nagłoacutewkowym mathh (zwykle usrincludemathh)3

ladowanej dynamicznie ( usrliblibmso )

Aby korzystać z tych funkcji potrzebujemy

dodać include ltmathhgt

przy kompilacji dołączyć bibliotekę libm gcc mainc -lm

Opcja -lm używa libmso albo libma w zależności od tego ktoacutere są znalezione i w zależności odobecności opcji -static45

245 Kompilacja warunkowaPrzy zwiększaniu przenośności kodu może pomoacutec preprocessor Przyjmijmy np że chcemy korzy-stać ze słoacutewka kluczowego inline wprowadzonego w standardzie C ale roacutewnocześnie chcemy abynasz program był rozumiany przez kompilatory ANSI CWoacutewczas możemy skorzystać z następującegokodu

ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline

3An Introduction to mdashfor the compilers gcc and g++ 27 Linking with external libraries4man ld5Dyskusja na grupie plcomposlinuxprogramowanie na temat c gc atan2 floor fabs

176 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

else define __inline__ endifendif

a w kodzie programu zamiast słoacutewka inline stosować inline Co więcej kompilator rozumiesłoacutewka kluczowe tak tworzone i w jego przypadku warto nie redefiniować ich wartości

ifndef __GNUC__ ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline else define __inline__ endif endifendif

Korzystając z kompilacji warunkowej można także korzystać z roacuteżnego kodu zależnie od (np) sys-temu operacyjnego Przykładowo przed kompilacją na konkretnej platformie tworzymy odpowiedniplik configh ktoacutery następnie dołączamy do wszystkich plikoacutew źroacutedłowych w ktoacuterych podejmujemydecyzje na podstawie zdefiniowanych makr Dla przykładu plik configh

ifndef CONFIG_Hdefine CONFIG_H

Uncomment if using Windows define USE_WINDOWS

Uncomment if using Linux define USE_LINUX

error You must edit configh fileerror Edit it and remove those error lines

endif

Jakiś plik źroacutedłowy

include configh

ifdef USE_WINDOWSrob_cos_wersja_dla_windows()

elserob_cos_wersja_dla_linux()

endif

Istnieją roacuteżne narzędzia ktoacutere pozwalają na automatyczne tworzenie takich plikoacutew configh dziękiczemu użytkownik przed skompilowaniem programu nie musi się trudzić i edytować ich ręcznie ajedynie uruchomić odpowiednie polecenie Przykładem jest zestaw autoconf i automake

Rozdział 25

Łączenie z innymi językami

Programista pisząc jakiś program ma problem z wyborem najbardziej odpowiedniego języka do utwo-rzenia tego programu Niekiedy zdarza się że najlepiej byłoby pisać program korzystając z roacuteżnychjęzykoacutew Język C może być z łatwością łączony z innymi językami programowania ktoacutere podlegająkompilacji bezpośrednio do kodu maszynowego (Asembler Fortran czy też C++) Ponadto dzięki spe-cjalnym bibliotekom można go łączyć z językami bardzo wysokiego poziomu (takimi jak np Pythonczy też Ruby) Ten rozdział ma za zadanie wytłumaczyć Ci w jaki sposoacuteb można mieszać roacuteżne językiprogramowania w jednym programie

251 Język C i Asembler

Informacje zawarte w tym rozdziale odnoszą się do komputeroacutew z procesorem i i pokrewnych

Łączenie języka C i języka asemblera jest dość powszechnym zjawiskiem Dzięki możliwości połączeniaobu tych językoacutew programowania można było utworzyć bibliotekę dla języka C ktoacutera niskopoziomowokomunikuje się z jądrem systemu operacyjnego komputera Ponieważ zaroacutewno asembler jak i C sąjęzykami tłumaczonymi do poziomu kodu maszynowego za ich łączenie odpowiada program zwanylinkerem (popularny ld) Ponadto niektoacuterzy producenci kompilatoroacutew umożliwiają stosowanie tzwwstawek asemblerowy ktoacutere umieszcza się bezpośrednio w kodzie programu napisanego w językuC Kompilator kompilując taki kod wstawi w miejsce tychże wstawek odpowiedni kod maszynowyktoacutery jest efektem przetłumaczenia kodu asemblera zawartegow takiej wstawce Opiszę tu oba sposobyłączenia obydwu językoacutew

2511 Łączenie na poziomie kodu maszynowegoW naszym przykładzie założymy że w pliku fS zawarty będzie kod napisany w asemblerze a fcto kod z programem w języku C Program w języku C będzie wykorzystywał jedną funkcję napisanąw języku asemblera ktoacutera wyświetli prosty napis ldquoHello worldrdquo Z powodu ograniczeń technicznychzakładamy że program uruchomiony zostanie w środowisku POSIX na platformie i i skompilowanykompilatorem gcc Używaną składnią asemblera będzie ATampT (domyślna dla asemblera ) Oto plikfS

text

globl _f1_f1

pushl ebpmovl esp ebp

177

178 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

movl $4 eax 4 to funkcja systemowa write movl $1 ebx 1 to stdout movl $tekst ecx adres naszego napisu movl $len edx długość napisu w bajtach int $0x80 wywołanie przerwania systemowego popl ebpret

datatekst

string Hello worldnlen = - tekst

W systemach z rodziny UNIX należy pominąć znak rdquo rdquoprzed nazwą funkcji f

Teraz kolej na fc

extern void f1 (void) musimy użyć słowa extern int main ()

f1()return 0

Teraz możemy skompilować oba programy

as f1S -o f1ogcc f2c -c -o f2ogcc f2o f1o -o program

W ten sposoacuteb uzyskujemy plik wykonywalny o nazwie ldquoprogramrdquo Efekt działania programu powinienbyć następujący

Hello world

Na razie utworzyliśmy bardzo prostą funkcję ktoacutera w zasadzie nie komunikuje się z językiem Cczyli nie zwraca żadnej wartości ani nie pobiera argumentoacutew Jednak aby zacząć pisać obsługę funk-cji ktoacutera będzie pobierała argumenty i zwracała wyniki musimy poznać działanie języka C od trochęniższego poziomu

Argumenty

Do komunikacji z funkcją język C korzysta ze stosu Argumenty odkładane sąw kolejności od ostatniegodo pierwszego Ponadto na końcu odkładany jest tzw adres powrotu dzięki czemu po wykonaniufunkcji program ldquowierdquo w ktoacuterym miejscu ma kontynuować działanie Ponadto początek funkcji wasemblerze wygląda tak

pushl ebpmovl esp ebp

Zatem na stosie znajdują się kolejno zawartość rejestru EBP adres powrotu a następnie argumenty odpierwszego do n-tego

251 JĘZYK C I ASEMBLER 179

Zwracanie wartości

Na architekturze i do zwracaniawynikoacutew pracy programu używa się rejestru EAX bądź jego ldquomniej-szychrdquo odpowiednikoacutew tj AX i AHAL Zatem aby funkcja napisana w asemblerze zwroacuteciła ldquordquo przedrozkazem ret należy napisać

movl $1 eax

Nazewnictwo

Kompilatory języka CC++ dodają podkreślnik ldquo rdquo na początku każdej nazwy Dla przykładu funkcja

void funkcja()

W pliku wyjściowym będzie posiadać nazwę funkcja Dlatego aby korzystać z poziomu języka C zfunkcji zakodowanych w asemblerze muszą one mieć przy definicji w pliku asemblera wspomnianydodatkowy podkreślnik na początku

Łączymy wszystko w całość

Pora abyśmy napisali jakąś funkcję ktoacutera pobierze argumenty i zwroacuteci jakiś konkretny wynik Otokod fS

text

globl _funkcja_funkcja

pushl ebpmovl esp ebpmovl 8(esp) eax kopiujemy pierwszy argument do eax addl 12(esp) eax do pierwszego argumentu w eax dodajemy drugi argument popl ebpret i zwracamy wynik dodawania

oraz fc

include ltstdiohgtextern int funkcja (int a int b)int main ()printf (2+3=dn funkcja(23))return 0

Po skompilowaniu i uruchomieniu programu powinniśmy otrzymać wydruk +=

2512 Wstawki asembleroweOproacutecz możliwości wstępnie skompilowanych modułoacutew możesz posłużyć się także tzw wstawkamiasemblerowymi Ich użycie powoduje wstawienie w miejsce wystąpienia wstawki odpowiedniegokodumaszynowego ktoacutery powstanie po przetłumaczeniu kodu asemblerowego Ponieważ jednakwstawkiasemblerowe nie są standardowym elementem języka C każdy kompilator ma całkowicie odmiennąfilozofię ich stosowania (lub nie ma ich w ogoacutele) Ponieważ w tym podręczniku używamy głoacutewniekompilatora więc w tym rozdziale zostanie omoacutewiona filozofia stosowania wstawek asemblerawedług programistoacutew

Ze wstawek asemblerowych korzysta się tak

180 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

int main ()

asm (nop)

W tym wypadku wstawiona zostanie instrukcja ldquonoprdquo (no operation) ktoacutera tak naprawdę służytylko i wyłącznie do konstruowania pętli opoacuteźniających

252 C++Język C++ z racji swojego podobieństwa do C będzie wyjątkowo łatwy do łączenia Pewnym utrudnie-niem może być obiektowość języka C++ oraz występowanie w nim przestrzeni nazw oraz możliwośćprzeciążania funkcji Oczywiście nadal zakładamy że głoacutewny program piszemy w C natomiast korzy-stamy tylko z pojedynczych funkcji napisanych w C++ Ponieważ język C nie oferuje tego wszystkiegoco daje programiście język C++ to musimy ldquozmusićrdquo C++ do wyłączenia pewnych swoich możliwościaby można było połączyć ze sobą elementy programu napisane w dwoacutech roacuteżnych językach Używa siędo tego następującej konstrukcji

extern C funkcje zmienne i wszystko to co będziemy łączyć z programem w C

W zrozumieniu teorii pomoże Ci prosty przykład plik fc

include ltstdiohgtextern int f2(int a)

int main ()

printf (dn f2(2))return 0

oraz plik fcpp

include ltiostreamgtusing namespace stdextern C

int f2 (int a)

cout ltlt a= ltlt a ltlt endlreturn a2

Teraz oba pliki kompilujemy

gcc f1c -c -o f1og++ f2cpp -c -o f2o

Przy łączeniu obu tych plikoacutew musimy pamiętać że język C++ także korzysta ze swojej bibliotekiZatem poprawna postać polecenia kompilacji powinna wyglądać

gcc f1o f2o -o program -lstdc++

(stdc++ mdash biblioteka standardowa języka C++) Bardzo istotne jest tutaj to abyśmy zawsze pamiętalio extern ldquoCrdquo gdyż w przeciwnym razie funkcje napisane w C++ będą dla programu w C całkowicieniewidoczne

Dodatek A

Indeks alfabetyczny

Alfabetyczny spis funkcji biblioteki standardowej ANSI C (tzw libc) w wersji C

A01 A abort()

abs()

acos()

asctime()

asin()

assert()

atan()

atan()

atexit()

atof()

atoi()

atol()

A02 B bsearch()

A03 C calloc()

ceil()

clearerr()

clock()

cos()

cosh()

ctime()

A04 D diime()

div()

A05 E errno (zmienna)

exit()

exp()

A06 F fabs()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

floor()

fmod()

fopen()

fprintf()

fputc()

fputs()

fread()

free()

freopen()

frexp()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

A07 G getc()

getchar()

getenv()

gets()

gmtime()

A08 I isalnum()

isalpha()

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

181

182 DODATEK A INDEKS ALFABETYCZNY

A09 L labs()

ldexp()

ldiv()

localeconv()

localtime()

log()

log()

longjmp()

A010 M malloc()

mblen()

mbstowcs()

mbtowc()

memchr()

memcmp()

memcpy()

memmove()

memset()

mktime()

modf()

A011 O offsetof()

A012 P perror()

pow()

printf()

putc()

putchar()

puts()

A013 Q qsort()

A014 R raise()

rand()

realloc()

remove()

rename()

rewind()

A015 S scanf()

setbuf()

setjmp()

setlocale()

setvbuf()

signal()

sin()

sinh()

sprintf()

sqrt()

srand()

sscanf()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strime()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtod()

strtok()

strtol()

strtoul()

strxfrm()

system()

A016 T tan()

tanh()

time()

tm (struktura)

tmpfile()

tmpnam()

tolower()

toupper()

A017 U ungetc()

A018 V va arg()

va end()

va start()

vfprintf()

vprintf()

vsprintf()

A019 W wcstombs()

wctomb()

Dodatek B

Indeks tematyczny

Spis plikoacutew nagłoacutewkowych oraz zawartych w nich funkcji i makr biblioteki standardowej C Funkcjemakra i typy wprowadzone dopiero w standardzie C zostały oznaczone poprzez ldquo[C]rdquo po nazwie

B1 asserthMakro asercji

assert()

B2 ctypehKlasyfikowanie znakoacutew

isalnum()

isalpha()

isblank() [C]

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

tolower()

toupper()

B3 errnohDeklaracje kodoacutew błędoacutew

EDOM (makro)

EILSEQ (makro) [C]

ERANGE (makro)

errno (zmienna)

B4 floathWłaściwości typoacutew zmiennoprzecinkowych zależne od implementacji

B5 limitshWłaściwości typoacutew całkowitych zależne od implementacji

183

184 DODATEK B INDEKS TEMATYCZNY

B6 localehUstawienia międzynarodowe

localeconv()

setlocale()

B7 mathhFunkcje matematyczne

FP FAST FMAF (makro) [C]

FP FAST FMAL (makro) [C]

FP FAST FMA (makro) [C]

FP ILOGB (makro) [C]

FP ILOGBNAN (makro) [C]

FP INFINITE (makro) [C]

FP NAN (makro) [C]

FP NORMAL (makro) [C]

FP SUBNORMAL (makro) [C]

FP ZERO (makro) [C]

HUGE VALF (makro) [C]

HUGE VALL (makro) [C]

HUGE VAL (makro)

INFINITY (makro) [C]

MATH ERREXCEPT (makro) [C]

MATH ERRNO (makro) [C]

NAN (makro) [C]

acosh()

acos()

asinh()

asin()

atan()

atanh()

atan()

cbrt() [C]

ceil()

copysign() [C]

cosh()

cos()

double t (typ) [C]

erfc() [C]

erf() [C]

exp() [C]

expm() [C]

exp()

fabs()

fdim() [C]

flaot t (typ) [C]

floor()

fmax() [C]

fma() [C]

fmin() [C]

fmod()

fpclassify() [C]

frexp()

hypot() [C]

ilogb() [C]

isfinite() [C]

isgreaterequal() [C]

isgreater() [C]

isinf() [C]

islessequal() [C]

islessgreater() [C]

isless() [C]

isnan() [C]

isnormal() [C]

isunordered() [C]

ldexp()

lgamma() [C]

llrint() [C]

llround() [C]

log()

logp() [C]

log() [C]

logb() [C]

log()

B8 SETJMPH 185

lrint() [C]

lround() [C]

math errhandling (makro) [C]

modf()

nan() [C]

nearbyint() [C]

nextaer() [C]

nexoward() [C]

pow()

remainder() [C]

remquo() [C]

rint() [C]

round() [C]

scalbln() [C]

scalbn() [C]

signbit() [C]

sinh()

sin()

sqrt()

tanh()

tan()

tgamma() [C]

trunc() [C]

B8 setjmphObsługa nielokalnych skokoacutew

longjmp()

setjmp()

B9 signalhObsługa sygnałoacutew

raise()

signal()

B10 stdarghNarzędzia dla funkcji ze zmienną liczbą argumentoacutew

va arg()

va end()

va start()

B11 stddefhStandardowe definicje

offsetof()

B12 stdiohStandard InputOutput czyli standardowe wejście-wyjście

clearerr()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

fopen()

186 DODATEK B INDEKS TEMATYCZNY

fprintf()

fputc()

fputs()

fread()

freopen()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

getc()

getchar()

gets()

perror()

printf()

putc()

putchar()

puts()

remove()

rename()

rewind()

scanf()

setbuf()

setvbuf()

sprintf()

sscanf()

tmpfile()

tmpnam()

ungetc()

vfprintf()

vprintf()

vsprintf()

B13 stdlibhNajbardziej podstawowe funkcje

abort()

abs()

atexit()

atof()

atoi()

atol()

bsearch()

calloc()

div()

exit()

free()

getenv()

labs()

ldiv()

malloc()

mblen()

mbstowcs()

mbtowc()

qsort()

rand()

realloc()

srand()

strtod()

strtol()

strtoul()

system()

wctomb()

wcstombs()

B14 stringhOperacje na łańcuchach znakoacutew

memchr()

memcmp()

memcpy()

memmove()

memset()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtok()

strxfrm()

strdup()

B15 timehFunkcje obsługi czasu

B15 TIMEH 187

asctime()

clock()

ctime()

diime()

gmtime()

localtime()

mktime()

strime()

time()

tm (struktura)

188 DODATEK B INDEKS TEMATYCZNY

Dodatek C

Wybrane funkcje bibliotekistandardowej

C1 assert

C11 Deklaracjadefine assert(expr)

C12 Plik nagłoacutewkowyasserth

C13 OpisMakro przypominające w użyciu funkcję służy do debuggowania programoacutew Gdy testowany waruneklogiczny expr przyjmuje wartość fałsz na standardowe wyjście błędoacutew wypisywany jest komunikat obłędzie (zawierające min argument wywołania makra nazwę funkcji w ktoacuterej zostało wywołanenazwę pliku źroacutedłowego oraz numer linii w formacie zależnym od implementacji) i program jest prze-rywany poprzez wywołanie funkcji abort

W ten sposoacuteb możemy oznaczyć w programie niezmienniki czyli warunki ktoacutere niezależnie odwartości zmiennych muszą pozostać prawdziwe Jeśli asercja zawiedzie oznacza to że popełniliśmybłąd w algorytmie piszemy sobie po pamięci (nadając zmiennym wartości ktoacuterych nigdy nie powinnymieć) albo nastąpiła po drodze sytuacja wyjątkowa na przykład związana z obsługą operacji wejścia-wyjścia

Można łatwo pozbyć się asercji uwalniając kod od spowalniających obciążeń a jednocześnie niemusząc kasować wystąpień assert i zachowując je na przyszłość Aby to zrobić należy przed dołą-czeniem pliku nagłoacutewkowego asserth zdefiniować makro NDEBUG woacutewczas makro assert przyjmujepostać

define assert(ignore) ((void)0)

Makro assert jest redefiniowane za każdym dołączeniem pliku nagłoacutewkowego asserth

C14 Wartość zwracanaMakro nie zwraca żadnej wartości

189

190 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C15 Przykładinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

Program wypisze komunikat podobny do

Assertion failed err==0 file testc line 6

Natomiast jeśli uruchomimy

define NDEBUGinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

nie pojawi się żaden komunikat o błędach

C2 atoi

C21 Deklaracjaint atoi (const char string)

C22 Plik nagłoacutewkowystdlibh

C23 OpisFunkcja jako argument pobiera liczbę w postaci ciągu znakoacutew ASCII a następnie zwraca jej wartość wformacie int Liczbę może poprzedzać dowolona ilość białych znakoacutew (spacje tabulatory itp) oraz jejznak (plus (+) lub minus (-)) Funkcja atoi() kończy wczytywać znaki w momencie napotkania jakiego-kowiek znaku ktoacutery nie jest cyfrą

C24 Wartość zwracanaW przypadku gdy ciąg nie zawiera cyfr zwracana jest wartość

C25 UwagiZnak musi bezpośrednio poprzedzać liczbę czyli możliwy jest zapis ldquo-rdquo natomiast proacuteba potraktowa-nia funkcją atoi ciągu ldquo- rdquo skutkuje zwracaną wartością

C3 ISALNUM 191

C26 Przykładinclude ltstdiohgtinclude ltstdlibhgtint main(void)

char c_Numer = nt 2004uint i_Numeri_Numer = atoi(c_Numer)printf(n Liczba typu int d oraz jako ciąg znakoacutew s n i_Numer c_Numer)return 0

C3 isalnum

C31 Deklaracjainclude ltctypehgt

int isalnum(int c)int isalpha(int c)int isblank(int c)int iscntrl(int c)int isdigit(int c)int isgraph(int c)int islower(int c)int isprint(int c)int ispuntc(int c)int isspace(int c)int isupper(int c)int isxdigit(int c)

C32 Argumentyc wartość znaku reprezentowana w jako typ unsigned char lub wartość makra EOF Z tego powodu

przed przekazaniem funkcji argumentu typu char lub signed char należy go zrzutować na typunsigned char lub unsigned int

C33 OpisFunkcje sprawdzają czy podany znak spełnia jakiś konkretny warunek Biorą pod uwagę ustawieniajęzyka i dla roacuteżnych znakoacutew w roacuteżnych localersquoach mogą zwracać roacuteżne wartości

isalnum sprawdza czy znak jest liczbą lub literą

isalpha sprawdza czy znak jest literą

isblank sprawdza czy znak jest znakiem odstępu służącym do oddzielania wyrazoacutew (standardowymiznakami odstępu są spacja i znak tabulacji)

iscntrl sprawdza czy znak jest znakiem sterującym

isdigit sprawdza czy znak jest cyfrą dziesiętna

isgraph sprawdza czy znak jest znakiem drukowalnym roacuteżnym od spacji

islower sprawdza czy znak jest małą literą

isprint sprawdza czy znak jest znakiem drukowalnym (włączając w to spację)

192 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

ispunct sprawdza czy znak jest znakiem przestankowym dla ktoacuterego ani isspace ani isalnum nie sąprawdziwe (standardowo są to wszystkie znaki drukowalne dla ktoacuterych te funkcje zwracajązero)

isspace sprawdza czy znak jest tzw białym znakiem (standardowymi białymi znakami są spacjawysunięcie strony rsquorsquo znak przejścia do nowej linii rsquonrsquo znak powrotu karetki rsquorrsquo tabulacjapozioma rsquotrsquo i tabulacja pionowa rsquovrsquo)

isupper sprawdza czy znak jest dużą literą

isxdigit sprawdza czy znak jest cyfrą szesnastkową tj cyfrą dziesiętną lub literą od rsquoarsquo do rsquorsquo niezależnieod wielkości

Funkcja isblank niewystępowaław oryginalnym standardzie ANSI C z roku (tzw C) i zostaładodana dopiero w nowszym standardzie z roku (tzw C)

C34 Wartość zwracanaLiczba niezerowa gdy podany argument spełnia konkretny warunek w przeciwnym wypadku mdash zero

C35 Przykład użyciainclude ltctypehgt funkcje is include ltlocalehgt setlocale include ltstdiohgt printf i scanf

void identify_char(int c) printf( Litera lub cyfra sn isalnum (c) tak nie)

if __STDC_VERSION__ gt= 199901Lprintf( Odstęp sn isblank (c) tak nie)

endifprintf( Znak sterujący sn iscntrl (c) tak nie)printf( Cyfra dziesiętna sn isdigit (c) tak nie)printf( Graficzny sn isgraph (c) tak nie)printf( Mała litera sn islower (c) tak nie)printf( Drukowalny sn isprint (c) tak nie)printf( Przestankowy sn ispunct (c) tak nie)printf( Biały znak sn isspace (c) tak nie)printf( Wielka litera sn isupper (c) tak nie)printf( Cyfra szesnastkowa sn isxdigit(c) tak nie)

int main() unsigned char cprintf(Naciśnij jakiś klawiszn)if (scanf(c ampc)==1)

identify_char(c)setlocale(LC_ALL pl_PL) przystosowanie do warunkoacutew polskich puts(Po zmianie ustawień języka)identify_char(c)

return 0

C36 Zobacz też tolower toupper

C4 MALLOC 193

C4 malloc

C41 Deklaracjainclude ltstdlibhgt

void calloc(size_t nmeb size_t size)void malloc(size_t size)void free(void ptr)void realloc(void ptr size_t size)

C42 Argumentynmeb liczba elementoacutew dla ktoacuterych ma być przydzielona pamięć

size rozmiar (w bajtach) pamięci do zarezerwowania bądź rozmiar pojedynczego elementu

ptr wskaźnik zwroacutecony przez poprzednie wywołanie jednej z funkcji lub

C43 OpisFunkcja calloc przydziela pamięć dla nmeb elementoacutew o rozmiarze size każdy i zeruje przydzielonąpamięć

Funkcja malloc przydziela pamięć o wielkości size bajtoacutewFunkcja free zwalnia blok pamięci wskazywany przez ptr wcześniej przydzielony przez jedną z

funkcji malloc calloc lub realloc Jeżeli ptr ma wartość funkcja nie robi nicFunkcja realloc zmienia rozmiar przydzielonego wcześniej bloku pamięci wskazywanego przez ptr

do size bajtoacutew Pierwsze n bajtoacutew bloku nie ulegnie zmianie gdzie n jest minimum z rozmiaru staregobloku i size Jeżeli ptr jest roacutewny zero (tj ) funkcja zachowuje się tak samo jako malloc

C44 Wartość zwracanaJeżeli przydzielanie pamięci się powiodło funkcje calloc malloc i realloc zwracają wskaźnik do nowoprzydzielonego bloku pamięci W przypadku funkcji realloc może to być wartość inna niż ptr

Jeśli jako size nmeb podano zero zwracany jest albo wskaźnik albo prawidłowy wskaźnikktoacutery można podać do funkcji free (zauważmy że jest też prawidłowym argumentem free)

Jeśli działanie funkcji nie powiedzie się zwracany jest i odpowiedni kod błędu jest wpisywanydo zmiennej errno Dzieje się tak zazwyczaj gdy nie ma wystarczająco dużo miejsca w pamięci

C45 Przykładinclude ltstdiohgtinclude ltstdlibhgt

int main(void)

size_t size num = 0float tab tmp

Przydzielenie początkowego bloku pamięci size = 64tab = malloc(size sizeof tab)if (tab)

perror(malloc)return EXIT_FAILURE

194 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Odczyt liczb while (scanf(f amptmp)==1)

Jeżeli zapełniono całą tablicę trzeba ją zwiększyć if (num==size)float ptr = realloc(tab (size = 2) sizeof ptr)if (ptr)

free(tab)perror(realloc)return EXIT_FAILURE

tab = ptr

tab[num++] = tmp

Wypisanie w odwrotnej kolejnosci while (num)

printf(fn tab[--num])

Zwolnienie pamieci i zakonczenie programu free(tab)return EXIT_SUCCESS

C46 Uwagi

Użycie rzutowania przy wywołaniach funkcji malloc realloc oraz calloc w języku C jest zbędne i szko-dliwe W przypadku braku deklaracji tych funkcji (np gdy programista zapomni dodać plik nagłoacutew-kowy stdlibh) kompilator przyjmuje domyślną deklaracje w ktoacuterej funkcja zwraca int Przy brakurzutowania spowoduje to błąd kompilacji (z powodu niemożności skonwertowania liczby na wskaźnik)co pozwoli na szybkie wychwycenie błędu w programie Rzutowanie powoduje że kompilator zostajezmuszony do przeprowadzenia konwersji typoacutew i nie wyświetla żadnych błędoacutew W przypadku językaC++ rzutowanie jest konieczne

Zastosowanie operatora sizeof z wyrażeniem (np sizeof tablica) a nie typem (np sizeof float)ułatwia poacuteźniejszą modyfikację programoacutew Gdyby w pewnym momencie programista zdecydował sięzmienić tablicę z tablicy floatoacutew na tablice doublersquoi musiałby wyszukiwać wszystkie wywołania funkcjimalloc realloc i calloc co nie jest konieczne przy użyciu operatora sizeof z wyrażeniem

Ponieważ dla parametru size roacutewnego zero funkcja może zwroacutecić albo wskaźnik roacuteżny od wartości albo jej roacutewny zwykłe sprawdzanie poprawności wywołania poprzez przyroacutewnanie zwroacuteconejwartości do zera może nie dać prawidłowego wyniku

C47 Zobacz też

Wskaźniki (dokładne omoacutewienie zastosowania)

C5 PRINTF 195

C5 printf

C51 Deklaracjainclude ltstdiohgt

int printf(const char format )int fprintf(FILE stream const char format )int sprintf(char str const char format )int snprintf(char str size_t size const char format )

include ltstdarghgt

int vprintf(const char format va_list ap)int vfprintf(FILE stream const char format va_list ap)int vsprintf(char str const char format va_list ap)int vsnprintf(char str size_t size const char format va_list ap)

C52 OpisFunkcje formatują tekst zgodnie z podanym formatem opisanym poniżej Funkcje printf i vprintf wy-pisują tekst na standardowe wyjście (tj do stdout) fprintf i vfprintf do strumienia podanego jakoargument a sprintf vsprintf snprintf i vsnprintf zapisują go w podanej jako argument tablicy znakoacutew

Funkcje vprintf vfprintf vsprintf i vsnprintf roacuteżnią się od odpowiadających im funkcjom printffprintf sprintf i snprintf tym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

Funkcje snprintf i vsnprintf roacuteżnią się od sprintf i vsprintf tym że nie zapisuje do tablicy nie wię-cej niż size znakoacutew (wliczając kończący znak rsquorsquo) Oznacza to że można je używać bez obawy owystąpienie przepełnienia bufora

C53 Argumentyformat format w jakim zostaną wypisane następne argumenty

stream strumień wyjściowy do ktoacuterego mają być zapisane dane

str tablica znakoacutew do ktoacuterej ma być zapisany sformatowany tekst

size rozmiar tablicy znakoacutew

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

C54 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) ktoacutere są kopiowane bez zmian na wyjścieoraz sekwencji sterujących zaczynających się od symbolu procenta po ktoacuterym następuje

dowolna liczba flag

opcjonalne określenie minimalnej szerokości pola

opcjonalne określenie precyzji

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

196 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Flagi

W sekwencji możliwe są następujące flagi

- (minus) oznacza że pole ma być wyroacutewnane do lewej a nie do prawej

+ (plus) oznacza że dane liczbowe zawsze poprzedzone są znakiem (plusem dla liczb nieujem-nych lub minusem dla ujemnych)

spacja oznacza że liczby nieujemne poprzedzone są dodatkową spacją jeżeli flaga plus i spacjasą użyte jednocześnie to spacja jest ignorowana

(hash) powoduje że wynik jest przedstawiony w alternatywnej postaci

ndash dla formatu o powoduje to zwiększenie precyzji jeżeli jest to konieczne aby na początkuwyniku było zero

ndash dla formatoacutew x i X niezerowa liczba poprzedzona jest ciągiem x lub X

ndash dla formatoacutew a A e E f F g i G wynik zawsze zawiera kropkę nawet jeżeli nie ma za niążadnych cyfr

ndash dla formatoacutew g i G końcowe zera nie są usuwane

(zero) dla formatoacutew d i o u xX aA e E f F g iG do wyroacutewnania pola wykorzystywane sązera zamiast spacji za wyjątkiem wypisywania wartości nieskończoność i NaN Jeżeli obie flagi i mdash są obecne to flaga zero jest ignorowana Dla formatoacutew d i o u x i X jeżeli określona jestprecyzja flaga ta jest ignorowana

Szerokość pola i precyzja

Minimalna szerokość pola oznacza ile najmniej znakoacutew ma zająć dane pole Jeżeli wartość po formato-waniu zajmuje mniej miejsca jest ona wyroacutewnywana spacjami z lewej strony (chyba że podano flagiktoacutere modyfikują to zachowanie) Domyślna wartość tego pola to

Precyzja dla formatoacutew

d i o u x iX określa minimalną liczbę cyfr ktoacutere mają być wyświetlone i ma domyślną wartość

a A e E f i F mdash liczbę cyfr ktoacutere mają być wyświetlone po kropce i ma domyślną wartość

g i G określa liczbę cyfr znaczących i ma domyślną wartość

dla formatu s mdash maksymalną liczbę znakoacutew ktoacutere mają być wypisane

Szerokość pola może być albo dodatnią liczbą zaczynającą się od cyfry roacuteżnej od zera albo gwiazdkąPodobnie precyzja z tą roacuteżnicą że jest jeszcze poprzedzona kropką Gwiazdka oznacza że brany jestkolejny z argumentoacutew ktoacutery musi być typu int Wartość ujemna przy określeniu szerokości jest trak-towana tak jakby podano flagę - (minus)

Rozmiar argumentu

Dla formatoacutew d i i można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu signed char

h mdash oznacza że format odnosi się do argumentu typu short

l (el) mdash oznacza że format odnosi się do argumentu typu long

ll (el el) mdash oznacza że format odnosi się do argumentu typu long long

j mdash oznacza że format odnosi się do argumentu typu intmax t

z mdash oznacza że że format odnosi się do argumentu typu będącego odpowiednikiem typu size tze znakiem

t mdash oznacza że że format odnosi się do argumentu typu ptrdiff t

C5 PRINTF 197

Dla formatoacutew o u x i X można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d ioznaczają one że format odnosi się do argumentu odpowiedniego typu bez znaku

Dla formatu n można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d i oznaczająone że format odnosi się do argumentu będącego wskaźnikiem na dany typ

Dla formatoacutew a A e E f F g iGmożna użyć modyfikatoroacutew rozmiaru L ktoacutery oznacza że formatodnosi się do argumentu typu long double

Dodatkowo modyfikator l (el) dla formatu c oznacza że odnosi się on do argumentu typu wint ta dla formatu s że odnosi się on do argumentu typu wskaźnik na wchar t

Format

Funkcje z rodziny printf obsługują następujące formaty

d i mdash argument typu int jest przedstawiany jako liczba całkowita ze znakiem w postaci [-]ddd

o u x X mdash argument typu unsigned int jest przedstawiany jako nieujemna liczba całkowitazapisana w systemie oktalnym (o) dziesiętnym (u) lub heksadecymalnym (x i X)

f F mdash argument typu double jest przedstawiany w postaci [-]dddddd

e E mdash argument typu double jest reprezentowany w postaci [i]dddde+dd gdzie liczba przedkropką dziesiętną jest roacuteżna od zera jeżeli liczba jest roacuteżna od zera a + oznacza znak wykładnikaFormat E używa wielkiej litery E zamiast małej

g G mdash argument typu double jest reprezentowany w formacie takim jak f lub e (odpowiednio Flub E) zależnie od liczby znaczących cyfr w liczbie oraz określonej precyzji

a A mdash argument typu double przedstawiany jest w formacie [-]xhhhhp+d czyli analogiczniejak dla e i E tyle że liczba zapisana jest w systemie heksadecymalnym

c mdash argument typu int jest konwertowany do unsigned char i wynikowy znak jest wypisywanyJeżeli podanomodyfikator rozmiaru l argument typuwint t konwertowany jest dowielobajtowejsekwencji i wypisywany

s mdash argument powinien być typu wskaźnik na char (lub wchar t) Wszystkie znaki z podanejtablicy aż do i z wyłączeniem znaku null są wypisywane

pmdashargument powinien być typuwskaźnik na void Jest to konwertowany na serię drukowalnychznakoacutew w sposoacuteb zależny od implementacji

n mdash argument powinien być wskaźnikiem na liczbę całkowitą ze znakiem do ktoacuterego zapisanajest liczba zapisanych znakoacutew

W przypadku formatoacutew f F e E gG a iAwartość nieskończoność jest przedstawiana w formacie[-]inf lub [-]infinity zależnie od implementacji Wartość NaN jest przedstawiana w postaci [-]nan lub[i]nan(sekwencja) gdzie sekwencja jest zależna od implementacji W przypadku formatoacutew określo-nych wielką literą roacutewnież wynikowy ciąg znakoacutew jest wypisywany wielką literą

C55 Wartość zwracana

Jeżeli funkcje zakończą się sukcesem zwracają liczbę znakoacutew w tekście (wypisanym na standardowewyjście do podanego strumienia lub tablicy znakoacutew) nie wliczając kończącego rsquorsquo W przeciwnymwypadku zwracana jest liczba ujemna

Wyjątkami są funkcje snprintf i vsnprintf ktoacutere zwracają liczbę znakoacutew ktoacutere zostałyby zapisanedo tablicy znakoacutew gdyby była wystarczająco duża

198 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C56 Przykład użyciainclude ltstdiohgtint main()

int i = 4float f = 31415char s = Monty Pythonprintf(i = inf = 1fnWskaźnik s wskazuje na napis sn i f s)return 0

Wyświetli

i = 4f = 31Wskaźnik s wskazuje na napis Monty Python

Funkcja formatująca ciąg znakoacutew i alokująca odpowiednią ilość pamięci

include ltstdarghgtinclude ltstdlibhgt

char sprintfalloc(const char format ) int retsize_t size = 100char str = malloc(size)if (str)

return 0

for()va_list apchar tmp

va_start(ap format)ret = vsnprintf(str size format ap)va_end(ap)

if (retltsize) break

tmp = realloc(str (size_t)ret + 1)if (tmp) ret = -1break

else str = tmpsize = (size_t)ret + 1

if (retlt0) free(str)str = 0

C6 SCANF 199

else if (size-1gtret) char tmp = realloc(str (size_t)ret + 1)if (tmp) str = tmp

return str

C57 Uwagi

Funkcje snprintf i vsnprintf nie były zdefiniowane w standardzie C Zostały one dodane dopiero wstandardzie C

Biblioteka glibc do wersji włącznie posiadała implementacje funkcji snprintf oraz vsnprintfktoacutere były niezgodne ze standardem gdyż zwracały - w przypadku gdy wynikowy tekst nie mieściłsię w podanej tablicy znakoacutew

C6 scanf

C61 Deklaracja

W pliku nagłoacutewkowym stdioh

int scanf(const char format )int fscanf(FILE stream const char format )int sscanf(const char str const char format )

W pliku nagłoacutewkowym stdargh

int vscanf(const char format va_list ap)int vsscanf(const char str const char format va_list ap)int vfscanf(FILE stream const char format va_list ap)

C62 Opis

Funkcje odczytują dane zgodnie z podanym formatem opisanym niżej Funkcje scanf i vscanf odczytujądane ze standardowego wejścia (tj stdin) fscanf i vfscanf ze strumienia podanego jako argument asscanf i vsscanf z podanego ciągu znakoacutew

Funkcje vscanf vfscanf i vsscanf roacuteżnią się od odpowiadających im funkcjom scanf fscanf i sscanftym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

C63 Argumenty

format format odczytu danych

stream strumień wejściowy z ktoacuterego mają być odczytane dane

str tablica znakoacutew z ktoacuterej mają być odczytane dane

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

200 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C64 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) oraz sekwencji sterujących zaczynającychsię od symbolu procenta po ktoacuterym następuje

opcjonalna gwiazdka

opcjonalne maksymalna szerokość pola

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

Wystąpienie w formacie białego znaku powoduje że funkcje z rodziny scanf będą odczytywać iodrzucać znaki aż do napotkania pierwszego znaku nie będącego białym znakiem

Wszystkie inne znaki (tj nie białe znaki oraz nie sekwencje sterujące) muszą dokładnie pasowaćdo danych wejściowych

Wszystkie białe znaki z wejścia są ignorowane chyba że sekwencja sterująca określa format [ c lubn

Jeżeli w sekwencji sterującej występuje gwiazdka to dane z wejścia zostaną pobrane zgodnie zformatem ale wynik konwersji nie zostanie nigdzie zapisany W ten sposoacuteb można pomijać częśćdanych

Maksymalna szerokość pola przyjmuje postać dodatniej liczby całkowitej zaczynającej się od cyfryroacuteżnej od zera Określa ona ile maksymalnie znakoacutew dany format może odczytać Jest to szczegoacutelnieprzydatne przy odczytywaniu ciągu znakoacutew gdyż dzięki temu można podać wielkość tablicy (minusjeden) i tym samym uniknąć błędoacutew przepełnienia bufora

Rozmiar argumentu

Dla formatoacutew d i o u x i n można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu wskaźnik na signed char lub unsignedchar

h mdash oznacza że format odnosi się do argumentu typu wskaźnik na short lub wskaźnik na unsi-gned short

l (el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long lub wskaźnik naunsigned long

ll (el el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long long lub wskaźnikna unsigned long long

j mdash oznacza że format odnosi się do argumentu typu wskaźnik na intmax t lub wskaźnik nauintmax t

z mdash oznacza że że format odnosi się do argumentu typu wskaźnik na size t lub odpowiedni typze znakiem

t mdash oznacza że że format odnosi się do argumentu typu wskaźnik na ptrdiff t lub odpowiednityp bez znaku

Dla formatoacutew a e f i g można użyć modyfikatoroacutew rozmiaru

l ktoacutery oznacza że format odnosi się do argumenty typu wskaźnik na double lub

L ktoacutery oznacza że format odnosi się do argumentu typu wskaźnik na long double

Dla formatoacutew c s i [ modyfikator l oznacza że format odnosi się do argumentu typu wskaźnik nawchar t

C6 SCANF 201

Format

Funkcje z rodziny scanf obsługują następujące formaty

d i odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przywywołaniufunkcji strtol z argumentem base roacutewnym odpowiednio dla d lub dla i argument powinienbyć wskaźnikiem na int

o u x odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przy wy-wołaniu funkcji strtoul z argumentem base roacutewnym odpowiednio dla o dla u lub dla xargument powinien być wskaźnikiem na unsigned int

a e f g odczytuje liczbę rzeczywistą nieskończoność lub NaN ktoacuterych format jest taki sam jakoczekiwany przy wywołaniu funkcji strtod argument powinien być wskaźnikiem na float

c odczytuje dokładnie tyle znakoacutew ile określono w maksymalnym rozmiarze pola (domyślnie )argument powinien być wskaźnikiem na char

s odczytuje sekwencje znakoacutew nie będących białymi znakami argument powinien być wskaźni-kiem na char

[ odczytuje niepusty ciąg znakoacutew z ktoacuterych każdymusi należeć do określonego zbioru argumentpowinien być wskaźnikiem na char

p odczytuje sekwencje znakoacutew zależną od implementacji odpowiadającą ciągowi wypisywa-nemu przez funkcję printf gdy podano sekwencję p argument powinien być typu wskaźnikna wskaźnik na void

n nie odczytuje żadnych znakoacutew ale zamiast tego zapisuje do podanej zmiennej liczbę odczyta-nych do tej pory znakoacutew argument powinien być typu wskaźnik na int

Słoacutewko więcej o formacie [ Po otwierającym nawiasie następuje ciąg określający znaki jakie mogąwystępować w odczytanym napisie i kończy się on nawiasem zamykającym tj ] Znaki pomiędzynawiasami (tzw scanlist) określają możliwe znaki chyba że pierwszym znakiem jest ˆ mdash woacutewczasw odczytanym ciągu znakoacutew mogą występować znaki nie występujące w scanlist Jeżeli sekwencjazaczyna się od [] lub [ˆ] to ten pierwszy nawias zamykający nie jest traktowany jako koniec sekwencjitylko jak zwykły znak Jeżeli wewnątrz sekwencji występuje znak - (minus) ktoacutery nie jest pierwszymlub drugim jeżeli pierwszym jest ˆ ani ostatnim znakiem zachowanie jest zależne od implementacji

Formaty A E F G i X są roacutewnież dopuszczalne i mają takie same działanie jak a e f g i x

C65 Wartość zwracanaFunkcja zwraca EOF jeżeli nastąpi koniec danych lub błąd odczytu zanim jakiekolwiek konwersje zo-staną dokonane lub liczbę poprawnie wczytanych poacutel (ktoacutera może być roacutewna zero)

202 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Dodatek D

Składnia

D1 Symbole i słowa kluczoweJęzyk C definiuje pewną ilość słoacutew za pomocą ktoacuterych tworzy się np pętle itp Są to tzw słowakluczowe tzn nie można użyć ich jako nazwy zmiennej czy też stałej (o nich poniżej) Oto lista słoacutewkluczowych języka C (według norm ANSI C z roku oraz ISO C z roku )

203

204 DODATEK D SKŁADNIA

Tablica D1 Symbole i słowa kluczoweSłowo Opis w tym podręcznikuauto Zmiennebreak Instrukcje sterującecase Instrukcje sterującear Zmienneconst Zmienne

continue Instrukcje sterującedefault Instrukcje sterujące

do Instrukcje sterującedouble Zmienneelse Instrukcje sterująceenum Typy złożoneextern Bibliotekifloat Zmiennefor Instrukcje sterującegoto Instrukcje sterująceif Instrukcje sterująceint Zmiennelong Zmienne

register Zmiennereturn Procedury i funkcjeshort Zmiennesigned Zmiennesizeof Zmiennestatic Biblioteki Zmiennestruct Typy złożoneswit Instrukcje sterującetypedef Typy złożoneunion Typy złożone

unsigned Zmiennevoid Wskaźniki

volatile Zmiennewhile Instrukcje sterujące

D2 POLSKIE ZNAKI 205

Specyfikacja ISO C z roku dodaje następujące słowa

Bool

Complex

Imaginary

inline

restrict

D2 Polskie znakiPisząc program możemy stosować polskie litery (tj ldquoąćęłńoacuteśźżrdquo) tylko w

komentarzach

ciągach znakoacutew (łańcuchach)

Niedopuszczalne jest stosowanie polskich znakoacutew w innych miejscach

D3 Operatory

D31 Operatory arytmetyczneSą to operatory wykonujące znane wszystkim dodawanie odejmowanie itp

operator znaczenie+ dodawanie- odejmowanie mnożenie dzielenie dzielenie modulo mdash daje w wyniku samą resztę z dzielenia= operator przypisania mdash wykonuje działanie po prawej stronie i wynik

przypisuje obiektowi po lewej

D32 Operatory logiczneSłużą poroacutewnaniu zawartości dwoacutech zmiennych według określonych kryterioacutew

Operator Rodzaj poroacutewnania== czy roacutewnegt większygt= większy bądź roacutewnylt mniejszylt= mniejszy bądź roacutewny= czy roacuteżny(nieroacutewny)

Są jeszcze operatory służące do grupowania poroacutewnań (patrz też logika w Wikipedii)

|| lub(OR)ampamp ioraz(AND) negacja(NOT)

206 DODATEK D SKŁADNIA

D33 Operatory binarne

Są to operatory ktoacutere działają na bitach

operator funkcja przykład| suma bitowa(OR) 5 | 2 da w wyniku 7 ( 00000101 OR 00000010 =

00000111)amp iloczyn bitowy 7 amp 2 da w wyniku 2 ( 00000111 AND 00000010

= 00000010)~ negacja bitowa 2 da wwyniku 253 (NOT 00000010 = 11111101

)gtgt przesunięcie bitoacutew o X w prawo 7 gtgt 2 da w wyniku 1 ( 00000111 gtgt 2 =

00000001)ltlt przesunięcie bitoacutew o X w lewo 7 ltlt 2 da w wyniku 28 ( 00000111 ltlt 2 =

00011100)^ alternatywa wyłączna 7 ˆ 2 da w wyniku 5 ( 00000111 ˆ 00000010 =

00000101)

D34 Operatory inkrementacjidekrementacji

Służą do dodawaniaodejmowania od liczby wartości jeden

Przykłady

Operacja Opis operacji Wartość wyrażeniax++ zwiększy wartość w x o jeden wartość zmiennej x przed zmianą++x zwiększy wartość w x o jeden wartość zmiennej x powiększona o jedenxndash zmniejszy wartość w x o jeden wartość zmiennej x przed zmianąndashx zmniejszy wartość w x o jeden wartość zmiennej x pomniejszona o jeden

Parę przykładoacutew dla zrozumienia

int a=7if ((a++)==7) najpierw poroacutewnuje potem dodaje

printf (dna) wypisze 8 if ((++a)==9) najpierw dodaje potem poroacutewnuje

printf (dn a) wypisze 9

Analogicznie ma się sytuacja z operatorami dekrementacji

D4 TYPY DANYCH 207

D35 PozostałeOperacja Opis operacji Wartość wyrażenia

x operator wyłuskania dla wskaźnika wartość trzymana w pamięci pod adre-sem przechowywanym we wskaźniku

ampx operator pobrania adresu zwraca adres zmiennejx[a] operator wybrania elementu tablicy zwraca element tablicy o indeksie a

(numerowanym od zera)xa operator wyboru składnika a ze zmien-

nej xwybiera składnik ze struktury lub unii

x-gta operator wyboru składnika a przezwskaźnik do zmiennej x

wybiera składnik ze struktury gdy uży-wamy wskaźnika do struktury zamiastzwykłej zmiennej

sizeof (typ) operator pobrania rozmiaru typu zwraca rozmiar typu w bajtachsizeof wyrażenie operator pobrania rozmiaru typu zwraca rozmiar typu rezultatu wyraże-

nia

D36 Operator ternarnyIstnieje jeden operator przyjmujący trzy argumenty mdash jest to operator wyrażenia warunkowego a b c Zwraca on b gdy a jest prawdą lub c w przeciwnym wypadku

D4 Typy dany

Tablica D Typy danych według roacuteżnych specyfikacji języka C

Typ Opis Inne nazwyTypy dany wg norm C i C

ar Służy głoacutewnie do przechowywania znakoacutew Od kom-pilatora zależy czy jest to liczba ze znakiem czy bez wwiększości kompilatoroacutew jest liczbą ze znakiem

signed ar Typ char ze znakiemunsigned ar Typ char bez znakushort Występuje gdy docelowa maszyna wyszczegoacutelnia

kroacutetki typ danych całkowitych w przeciwnym wy-padku jest tożsamy z typem int Często ma rozmiarjednego słowa maszynowego

short int signed shortsigned short int

unsigned short Liczba typu short bez znaku Podobnie jak short uży-wana do zredukowania zużycia pamięci przez program

unsigned short int

int Liczba całkowita odpowiadająca podstawowemu roz-miarowi liczby całkowitej w danym komputerze Pod-stawowy typ dla liczb całkowitych

signed int signed

unsigned Liczba całkowita bez znaku unsigned intlong Długa liczba całkowita long int signed long

signed long intunsigned long Długa liczba całkowita bez znaku unsigned long intfloat Podstawowy typ do przechowywania liczb zmienno-

przecinkowych W nowszym standardzie zgodny jest znormą IEEE Nie można stosować go z modyfika-torem signed ani unsigned

double Liczba zmiennoprzecinkowa podwoacutejnej precyzji Po-dobnie jak float nie łączy się z modyfikatorem signedani unsigned

208 DODATEK D SKŁADNIA

long double Największa możliwa dokładność liczb zmiennoprzecin-kowych Nie łączy się z modyfikatorem signed aniunsigned

Typy dany według normy CBool Przechowuje wartości lub long long Nowy typ umożliwiający obliczeniach na bardzo du-

żych liczbach całkowitych bez użycia typu floatlong long int signedlong long signed longlong int

unsigned long long Długie liczby całkowite bez znaku unsigned long long intfloat Complex Słuzy do przechowywania liczb zespolonychdouble Complex Słuzy do przechowywania liczb zespolonychlong double Complex Słuzy do przechowywania liczb zespolonych

Typy dany definiowane przez użytkownikastruct Więcej o kompilowaniuunion Rozmiar typu jest taki jak rozmiar największego polatypedef Nowo zdefiniowany typ przyjmuje taki sam rozmiar

jak typ macierzystyenum Zwykle elementy mają taką samą długość jak typ int

Zależności rozmiaru typoacutew danych są następujące

sizeof(cokolwiek) = sizeof(signed cokolwiek) = sizeof(unsigned cokolwiek)

= sizeof(ar) le sizeof(short) le sizeof(int) le sizeof(long) le sizeof(long long)

sizeof(float) le sizeof(double) le sizeof(long double)

sizeof(cokolwiek Complex) = sizeof(cokolwiek)

sizeof(void ) = sizeof(ar ) ge sizeof(cokolwiek )

sizeof(cokolwiek ) = sizeof(signed cokolwiek ) = sizeof(unsigned cokolwiek )

sizeof(cokolwiek ) = sizeof(const cokolwiek )

Dodatkowo jeżeli przez V(typ) oznaczymy liczbę bitoacutew wykorzystywanych w typie to zachodzi

le V(ar) = V(signed ar) = V(unsigned ar)

le V(short) = V(unsigned short)

le V(int) = V(unsigned int)

le V(long) = V(unsigned long)

le V(long long) = V(unsigned long long)

V(ar) le V(short) le V(int) le V(long) le V(long long)

Dodatek E

Przykłady z komentarzem

E01 Liczby losowePoniższy program generuje wiersz po wierszu macierz o określonych przez użytkownika wymiarachzawierającą losowo wybrane liczby Każdy wygenerowany wiersz macierzy zapisywany jest w plikutekstowym o wprowadzonej przez użytkownika nazwie W pierwszym wierszu pliku wynikowego za-pisano wymiary utworzonej macierzy Program napisany i skompilowany został w środowisku GNU-Linux

include ltstdiohgtinclude ltstdlibhgt dla funkcji rand() oraz srand() include lttimehgt dla funkcji [time()

main()

int i j n mfloat reFILE fpchar fileName[128]

printf(Wprowadz nazwe pliku wynikowegon)scanf(sampfileName)

printf(Wprowadz po sobie liczbe wierszy i kolumn macierzy oddzielone spacjąn)scanf(d d ampn ampm)

jeżeli wystąpił błąd w otwieraniu pliku i go nie otwartowoacutewczas funkcja fclose(fp) wywołana na końcu programu zgłosi błądwykonania i wysypie nam program z działania stąd musimy umieścićwarunek ktoacutery w kontrolowany sposoacuteb zatrzyma program (funkcja exit)

if ( (fp = fopen(fileName w)) == NULL )

puts(Otwarcie pliku nie jest mozliwe)exit jeśli w procedurze glownej

to piszemy bez nawiasow

else puts(Plik otwarty prawidłowo)

209

210 DODATEK E PRZYKŁADY Z KOMENTARZEM

fprintf(fp d dn n m) w pierwszym wierszu umieszczono wymiary macierzy

srand( (unsigned int) time(0) )for (i=1 ilt=n ++i)

for (j=1 jlt=m ++j)re = ((rand() 200)-100) 100fprintf(fp1f re )if (j=m) fprintf(fp )

fprintf(fpn)fclose(fp)return 0

E02 Zamiana liczb dziesiętny na liczby w systemie dwoacutejkowym

Zajmijmy się teraz innym zagadnieniem Wiemy że komputer zapisuje wszystkie liczby w postacibinarnej (czyli za pomocą jedynek i zer) Sproacutebujmy zatem zamienić liczbę zapisaną w ldquonaszymrdquo dzie-siątkowym systemie na zapis binarny Uwaga Program działa jedynie dla liczb od do maksymalnejwartości ktoacuterą może przyjąć typ unsigned short int w twoim kompilatorze

include ltstdiohgtinclude ltlimitshgt

void dectobin (unsigned short a)

int licznik

CHAR_BIT to liczba bitoacutew w bajcie licznik = CHAR_BIT sizeof(a)while (--licznik gt= 0)

putchar(((a gtgt licznik) amp 1)) 1 0)

int main ()

unsigned short a

printf (Podaj liczbę od 0 do hd USHRT_MAX)scanf (hd ampa)printf (hd(10) = a)dectobin(a)printf ((2)n)

return 0

211

E03 Zalążek przeglądarki

Zajmiemy się tym razem inną kwestią a mianowicie programowaniem sieci Jest to zagadnienie bar-dzo ostatnio popularne Nasz program będzie miał za zadanie połączyć się z serwerem ktoacuterego adresużytkownik będzie podawał jako pierwszy parametr programu wysłać zapytanie HTTP i odebrać treśćktoacuterą wyśle do nas serwer Zacznijmy może od tego że obsługa sieci jest niemal identyczna w roacuteżnychsystemach operacyjnych Na przykład między systemami z rodziny Unix oraz Windowsem roacuteżnica po-lega tylko na dołączeniu innych plikoacutew nagłoacutewkowych (dla Windowsa mdash winsockh) Przeanalizujmyzatem poniższy kod

include ltstdiohgtinclude ltstdlibhgtinclude ltstringhgtinclude ltunistdhgtinclude ltarpainethgtinclude ltsystypeshgtinclude ltnetinetinhgtinclude ltsyssockethgt

define MAXRCVLEN 512define PORTNUM 80

char query = GET HTTP11nn

int main(int argc char argv[])

char buffer[MAXRCVLEN+1]int len mysocketstruct sockaddr_in destchar host_ip = NULLif (argc = 2)

printf (Podaj adres serweran)exit (1)

host_ip = argv[1]mysocket = socket(AF_INET SOCK_STREAM 0)

destsin_family = AF_INETdestsin_addrs_addr = inet_addr(host_ip) ustawiamy adres hosta destsin_port = htons (PORTNUM) numer portu przechowuje dwubajtowa zmienna -

musimy ustalić porządek sieciowy - Big Endian memset(amp(destsin_zero) 0 8) zerowanie reszty struktury

connect(mysocket (struct sockaddr )ampdestsizeof(struct sockaddr)) łączymy się z hostem write (mysocket query strlen(query)) wysyłamy zapytanie len=read(mysocket buffer MAXRCVLEN) i pobieramy odpowiedź

buffer[len]=0

printf(Rcvd sbuffer)close(mysocket) zamykamy gniazdo return EXIT_SUCCESS

212 DODATEK E PRZYKŁADY Z KOMENTARZEM

Powyższy przykład może być odrobinę niezrozumiały dlatego przyda się kilka słoacutew wyjaśnieniaPliki nagłoacutewkowe ktoacutere dołączamy zawierają deklarację nowych dla Ciebie funkcji mdash socket() con-nect() write() oraz read() Oproacutecz tego spotkałeś się z nową strukturą mdash sockaddr in Wszystkie teobiekty są niezbędne do stworzenia połączenia

Dodatek F

Informacje o pliku i historia

F1 HistoriaTa książka została stworzona na polskojęzycznej wersji projektu Wikibooks przez autoroacutew wymie-nionych poniżej w sekcji Autorzy Najnowsza wersja podręcznika jest dostępna pod adresem httpplwikibooksorgwikiC

F2 Informacje o pliku i historia został utworzony przez Derbetha dnia listopada na podstawie wersji z listopada podręcznika na Wikibooks Wykorzystany został poprawiony program WikiLaTeX autorstwa użyt-kownika angielskichWikibooks Hagindaza Wynikowy kod po ręcznych poprawkach został przekształ-cony w książkę za pomocą systemu składu XeLaTeX Wykorzystano wolną dostępną na licencjach i czcionkę Linux Libertine oraz wolną czcionkę DejaVu Sans Mono

Najnowsza wersja tego -u jest postępna pod adresem httpplwikibooksorgwikiImageCpdf

F3 AutorzyAdam majewski Adiblol Akira Albmont Ananas Arfrever BartekChom Bercik Bla Bociex CathyRichards Cnr CzarnyInaczej CzarnyZajaczek DaniXTeam Derbeth Equadus Faw GDR GangGk Gynvael Incuś Karol Ossowski Kazet Kj Lethern MTM Marcin MastiBot MeaglinMerdis Michael Migol Mina MonteChristof Mt Myki Mythov Narf Noisy Norill PawelkgPawlosck Peter de Sowaro Piotr Pkierski Ponton Przykuta RedRad Sasek Sblive Silbarad T zielWarszk Webprog Wentuq ZiomekPL Zjem ci chleb i anonimowi autorzy

F4 GrafikiAutorzy i licencje grafik

grafika na okładce Saint-Elme Gautier rycina z książki Le Corset agrave travers les acircges Paryż źroacutedło Wikimedia Commons public domain

logoWikibooks zastrzeżony znak towarowycopy ampAll rights reserved Wikimedia FoundationInc

grafika a (strona ) autor Claudio Rocchini źroacutedło Wikimedia Commons licencja

grafika b (strona ) autor Adam majewski źroacutedło Wikimedia Commons licencja CreativeCommons Aribution Unported

213

214 DODATEK F INFORMACJE O PLIKU

grafia (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Daniel B źroacutedo Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Derrick Coetzee źroacutedło Wikimedia Commons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

Indeks

adres alternatywa

biblioteka standardowa big endian blok

Cjęzyk

dekrementacja dynamiczna alokacja pamięci

enum

funkcja definicja deklaracja rekurencyjna

inkrementacja

komentarz kompilacja

warunkowa kompilator

lista używanie

koniunkcja konwersja

libc lile endian Porownaj big endian

main makefile

napis poroacutewnywanie

negacja

operatordekrementacji inkrementacji modulo

pobrania adresu sizeof wyrażenia warunkowego wyłuskania

plikczytanie i pisanie nagłowkowy

porządek bajtoacutew prawda i fałsz preprocesor procedury prototyp funkcji przekazywanie argumentoacutew do funkcji

przez wartość przez wskaźnik

przepełnienie bufora przesunięcie bitowe

rzutowanie

sizeof stała struktura słowa kluczowe

tablica wielowymiarowa znakoacutew

typ definiowanie wyliczeniowy

unia

Valgrind void

jako typ zwracany na liście argumentoacutew void

volatile

wejściewyjście wskaźnik wyciek pamięci

215

216 INDEKS

wyroacutewnywanie

zmienna globalna lokalna statyczna

znaki specjalne

  • O podręczniku
    • O czym moacutewi ten podręcznik
    • Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika
    • Konwencje przyjęte w tym podręczniku
    • Czy mogę pomoacutec
    • Autorzy
    • Źroacutedła
      • O języku C
        • Historia C
        • Zastosowania języka C
        • Przyszłość C
          • Czego potrzebujesz
            • Czego potrzebujesz
            • Zintegrowane Środowiska Programistyczne
            • Dodatkowe narzędzia
              • Używanie kompilatora
                • GCC
                • Borland
                • Czytanie komunikatoacutew o błędach
                  • Pierwszy program
                    • Twoacutej pierwszy program
                    • Rozwiązywanie problemoacutew
                      • Podstawy
                        • Kompilacja Jak działa C
                        • Co może C
                        • Struktura blokowa
                        • Zasięg
                        • Funkcje
                        • Biblioteki standardowe
                        • Komentarze i styl
                        • Preprocesor
                        • Nazwy zmiennych stałych i funkcji
                          • Zmienne
                            • Czym są zmienne
                            • Typy zmiennych
                            • Specyfikatory
                            • Modyfikatory
                            • Uwagi
                              • Operatory
                                • Przypisanie
                                • Rzutowanie
                                • Operatory arytmetyczne
                                • Operacje bitowe
                                • Poroacutewnanie
                                • Operatory logiczne
                                • Operator wyrażenia warunkowego
                                • Operator przecinek
                                • Operator sizeof
                                • Inne operatory
                                • Priorytety i kolejność obliczeń
                                • Kolejność wyliczania argumentoacutew operatora
                                • Uwagi
                                • Zobacz też
                                  • Instrukcje sterujące
                                    • Instrukcje warunkowe
                                    • Pętle
                                    • Instrukcja goto
                                    • Natychmiastowe kończenie programu --- funkcja exit
                                    • Uwagi
                                      • Podstawowe procedury wejścia i wyjścia
                                        • Wejściewyjście
                                        • Funkcje wyjścia
                                        • Funkcja puts
                                        • Funkcja fputs
                                        • Funkcje wejścia
                                          • Funkcje
                                            • Tworzenie funkcji
                                            • Wywoływanie
                                            • Zwracanie wartości
                                            • Zwracana wartość
                                            • Funkcja main()
                                            • Dalsze informacje
                                            • Zobacz też
                                              • Preprocesor
                                                • Wstęp
                                                • Dyrektywy preprocesora
                                                • Predefiniowane makra
                                                  • Biblioteka standardowa
                                                    • Czym jest biblioteka
                                                    • Po co nam biblioteka standardowa
                                                    • Gdzie są funkcje z biblioteki standardowej
                                                    • Opis funkcji biblioteki standardowej
                                                    • Uwagi
                                                      • Czytanie i pisanie do plikoacutew
                                                        • Pojęcie pliku
                                                        • Identyfikacja pliku
                                                        • Podstawowa obsługa plikoacutew
                                                        • Rozmiar pliku
                                                        • Przykład --- pliki graficzny
                                                        • Co z katalogami
                                                          • Ćwiczenia dla początkujących
                                                            • Ćwiczenia
                                                              • Tablice
                                                                • Wstęp
                                                                • Odczytzapis wartości do tablicy
                                                                • Tablice znakoacutew
                                                                • Tablice wielowymiarowe
                                                                • Ograniczenia tablic
                                                                • Ciekawostki
                                                                  • Wskaźniki
                                                                    • Co to jest wskaźnik
                                                                    • Operowanie na wskaźnikach
                                                                    • Arytmetyka wskaźnikoacutew
                                                                    • Tablice a wskaźniki
                                                                    • Gdy argument jest wskaźnikiem
                                                                    • Pułapki wskaźnikoacutew
                                                                    • Na co wskazuje NULL
                                                                    • Stałe wskaźniki
                                                                    • Dynamiczna alokacja pamięci
                                                                    • Wskaźniki na funkcje
                                                                    • Możliwe deklaracje wskaźnikoacutew
                                                                    • Popularne błędy
                                                                    • Ciekawostki
                                                                      • Napisy
                                                                        • Łańcuchy znakoacutew w języku C
                                                                        • Operacje na łańcuchach
                                                                        • Bezpieczeństwo kodu a łańcuchy
                                                                        • Konwersje
                                                                        • Operacje na znakach
                                                                        • Częste błędy
                                                                        • Unicode
                                                                          • Typy złożone
                                                                            • typedef
                                                                            • Typ wyliczeniowy
                                                                            • Struktury
                                                                            • Unie
                                                                            • Inicjalizacja struktur i unii
                                                                            • Wspoacutelne własności typoacutew wyliczeniowych unii i struktur
                                                                            • Studium przypadku --- implementacja listy wskaźnikowej
                                                                              • Biblioteki
                                                                                • Czym jest biblioteka
                                                                                • Jak zbudowana jest biblioteka
                                                                                  • Więcej o kompilowaniu
                                                                                    • Ciekawe opcje kompilatora GCC
                                                                                    • Program make
                                                                                    • Optymalizacje
                                                                                    • Kompilacja krzyżowa
                                                                                    • Inne narzędzia
                                                                                      • Zaawansowane operacje matematyczne
                                                                                        • Biblioteka matematyczna
                                                                                        • Prezentacja liczb rzeczywistych w pamięci komputera
                                                                                        • Liczby zespolone
                                                                                          • Powszechne praktyki
                                                                                            • Konstruktory i destruktory
                                                                                            • Zerowanie zwolnionych wskaźnikoacutew
                                                                                            • Konwencje pisania makr
                                                                                            • Jak dostać się do konkretnego bitu
                                                                                            • Skroacutety notacji
                                                                                              • Przenośność programoacutew
                                                                                                • Niezdefiniowane zachowanie i zachowanie zależne od implementacji
                                                                                                • Rozmiar zmiennych
                                                                                                • Porządek bajtoacutew i bitoacutew
                                                                                                • Biblioteczne problemy
                                                                                                • Kompilacja warunkowa
                                                                                                  • Łączenie z innymi językami
                                                                                                    • Język C i Asembler
                                                                                                    • C++
                                                                                                      • Indeks alfabetyczny
                                                                                                      • Indeks tematyczny
                                                                                                        • asserth
                                                                                                        • ctypeh
                                                                                                        • errnoh
                                                                                                        • floath
                                                                                                        • limitsh
                                                                                                        • localeh
                                                                                                        • mathh
                                                                                                        • setjmph
                                                                                                        • signalh
                                                                                                        • stdargh
                                                                                                        • stddefh
                                                                                                        • stdioh
                                                                                                        • stdlibh
                                                                                                        • stringh
                                                                                                        • timeh
                                                                                                          • Wybrane funkcje biblioteki standardowej
                                                                                                            • assert
                                                                                                            • atoi
                                                                                                            • isalnum
                                                                                                            • malloc
                                                                                                            • printf
                                                                                                            • scanf
                                                                                                              • Składnia
                                                                                                                • Symbole i słowa kluczowe
                                                                                                                • Polskie znaki
                                                                                                                • Operatory
                                                                                                                • Typy danych
                                                                                                                  • Przykłady z komentarzem
                                                                                                                  • Informacje o pliku
                                                                                                                    • Historia
                                                                                                                    • Informacje o pliku PDF i historia
                                                                                                                    • Autorzy
                                                                                                                    • Grafiki
                                                                                                                      • Skorowidz
Page 2: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ

Wersja z dnia listopada Copyrightcopy - użytkownicy Wikibooks

Udziela się zezwolenia do kopiowania rozpowszechniania lub modyfikacji tego dokumentuzgodnie z zasadami Licencji Creative Commons Uznanie autorstwa-Na ty samy warunka Unported lub dowolnej poacuteźniejszej wersji licencji opublikowanej przez Creative Com-mons ktoacutera zawiera te same elementy co niniejsza licencja

Tekst licencji można znaleźć na stronie httpcreativecommonsorglicensesby-sa30deedpl

Wikibooks nie udziela żadnych gwarancji zapewnień ani obietnic dotyczących poprawnościpublikowanych treści Nie udziela też żadnych innych gwarancji zaroacutewno jednoznacznychjak i dorozumianych

Spis tresci

O podręczniku O czym moacutewi ten podręcznik 11 Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika 11 Konwencje przyjęte w tym podręczniku 11 Czy mogę pomoacutec 12 Autorzy 12 Źroacutedła 12

O języku C Historia C 13 Zastosowania języka C 15 Przyszłość C 15

Czego potrzebujesz Czego potrzebujesz 17 Zintegrowane Środowiska Programistyczne 18 Dodatkowe narzędzia 18

Używanie kompilatora GCC 19 Borland 20 Czytanie komunikatoacutew o błędach 20

Pierwszy program Twoacutej pierwszy program 23 Rozwiązywanie problemoacutew 24

Podstawy Kompilacja Jak działa C 27 Co może C 27 Struktura blokowa 28 Zasięg 28 Funkcje 29 Biblioteki standardowe 29 Komentarze i styl 30 Preprocesor 32 Nazwy zmiennych stałych i funkcji 32

3

Zmienne Czym są zmienne 33 Typy zmiennych 36 Specyfikatory 39 Modyfikatory 40 Uwagi 41

Operatory Przypisanie 43 Rzutowanie 44 Operatory arytmetyczne 45 Operacje bitowe 46 Poroacutewnanie 48 Operatory logiczne 50 Operator wyrażenia warunkowego 51 Operator przecinek 51 Operator sizeof 51 Inne operatory 52 Priorytety i kolejność obliczeń 52 Kolejność wyliczania argumentoacutew operatora 53 Uwagi 54 Zobacz też 55

Instrukcje sterujące Instrukcje warunkowe 57 Pętle 60 Instrukcja goto 65 Natychmiastowe kończenie programu mdash funkcja exit 65 Uwagi 66

Podstawowe procedury wejścia i wyjścia Wejściewyjście 67 Funkcje wyjścia 68 Funkcja puts 69 Funkcja fputs 70 Funkcje wejścia 71

Funkcje Tworzenie funkcji 78 Wywoływanie 79 Zwracanie wartości 80 Zwracana wartość 81 Funkcja main() 81 Dalsze informacje 83 Zobacz też 87

Preprocesor Wstęp 89 Dyrektywy preprocesora 89 Predefiniowane makra 95

4

Biblioteka standardowa Czym jest biblioteka 97 Po co nam biblioteka standardowa 97 Gdzie są funkcje z biblioteki standardowej 97 Opis funkcji biblioteki standardowej 98 Uwagi 98

Czytanie i pisanie do plikoacutew Pojęcie pliku 99 Identyfikacja pliku 99 Podstawowa obsługa plikoacutew 99 Rozmiar pliku 102 Przykład mdash pliki graficzny 103 Co z katalogami 104

Ćwiczenia dla początkujący Ćwiczenia 105

Tablice Wstęp 107 Odczytzapis wartości do tablicy 109 Tablice znakoacutew 109 Tablice wielowymiarowe 110 Ograniczenia tablic 110 Ciekawostki 111

Wskaźniki Co to jest wskaźnik 113 Operowanie na wskaźnikach 114 Arytmetyka wskaźnikoacutew 117 Tablice a wskaźniki 118 Gdy argument jest wskaźnikiem 119 Pułapki wskaźnikoacutew 120 Na co wskazuje 120 Stałe wskaźniki 121 Dynamiczna alokacja pamięci 122 Wskaźniki na funkcje 125 Możliwe deklaracje wskaźnikoacutew 127 Popularne błędy 127 Ciekawostki 128

Napisy Łańcuchy znakoacutew w języku C 129 Operacje na łańcuchach 132 Bezpieczeństwo kodu a łańcuchy 134 Konwersje 137 Operacje na znakach 137 Częste błędy 137 Unicode 138

5

Typy złożone typedef 141 Typ wyliczeniowy 141 Struktury 142 Unie 142 Inicjalizacja struktur i unii 144 Wspoacutelne własności typoacutew wyliczeniowych unii i struktur 144 Studium przypadku mdash implementacja listy wskaźnikowej 146

Biblioteki Czym jest biblioteka 151 Jak zbudowana jest biblioteka 151

Więcej o kompilowaniu Ciekawe opcje kompilatora 155 Program make 155 Optymalizacje 157 Kompilacja krzyżowa 159 Inne narzędzia 159

Zaawansowane operacje matematyczne Biblioteka matematyczna 161 Prezentacja liczb rzeczywistych w pamięci komputera 162 Liczby zespolone 163

Powszene praktyki Konstruktory i destruktory 165 Zerowanie zwolnionych wskaźnikoacutew 166 Konwencje pisania makr 166 Jak dostać się do konkretnego bitu 167 Skroacutety notacji 168

Przenośność programoacutew Niezdefiniowane zachowanie i zachowanie zależne od implementacji 171 Rozmiar zmiennych 172 Porządek bajtoacutew i bitoacutew 172 Biblioteczne problemy 175 Kompilacja warunkowa 175

Łączenie z innymi językami Język C i Asembler 177 C++ 180

A Indeks alfabetyczny

B Indeks tematyczny B asserth 183B ctypeh 183B errnoh 183B floath 183B limitsh 183

6

B localeh 184B mathh 184B setjmph 185B signalh 185B stdargh 185B stddefh 185B stdioh 185B stdlibh 186B stringh 186B timeh 186

C Wybrane funkcje biblioteki standardowej C assert 189C atoi 190C isalnum 191C malloc 193C printf 195C scanf 199

D Składnia D Symbole i słowa kluczowe 203D Polskie znaki 205D Operatory 205D Typy danych 207

E Przykłady z komentarzem

F Informacje o pliku F Historia 213F Informacje o pliku i historia 213F Autorzy 213F Grafiki 213

Skorowidz

7

8

Spis tablic

Priorytety operatoroacutew 53

D Symbole i słowa kluczowe 204D Typy danych według roacuteżnych specyfikacji języka C 207

9

Rozdział 1

O podręczniku

11 O czym moacutewi ten podręcznikNiniejszy podręcznik stanowi przewodnik dla początkujących programistoacutew po języku pro-gramowania C

12 Co trzeba wiedzieć żeby skorzystać z niniejszego pod-ręcznika

Ten podręcznik ma nauczyć programowania w C od podstaw do poziomu zaawansowanegoDo zrozumienia rozdziału dla początkujących wymagana jest jedynie znajomość podstawo-wych pojęć z zakresu algebry oraz terminoacutew komputerowych Doświadczenie w programo-waniu w innych językach bardzo pomaga ale nie jest konieczne

13 Konwencje przyjęte w tym podręcznikuInformacje ważne oznaczamy w następujący sposoacuteb

Ważna informacja

Dodatkowe informacje ktoacutere odrobinę wykraczają poza zakres podręcznika a także wy-jaśniają kwestie niezwiązane bezpośrednio z językiem C oznaczamy tak

Wyjaśnienie

Ponadto kod w języku C będzie prezentowany w następujący sposoacuteb

include ltstdiohgt

int main (int argc char argv[])

return 0

11

12 ROZDZIAŁ 1 O PODRĘCZNIKU

Innego rodzaju przykłady dialog użytkownika z konsolą i programem wejście wyjścieprogramu informacje teoretyczne będą wyglądały tak

typ zmienna = wartość

14 Czy mogę pomoacutecOczywiście że możesz Mało tego będziemy zadowoleni z każdej pomocy ndash możesz pisaćrozdziały lub tłumaczyć je z angielskiej wersji tego podręcznika Nie musisz pytać się nikogoo zgodę mdash jeśli chcesz możesz zacząć już teraz Prosimy jedynie o zapoznanie się ze stylempodręcznika użytymi w nim szablonami i zachowanie układu rozdziałoacutew Propozycje zmianyspisu treści należy zgłaszać na stronie dyskusji

Jeśli znalazłeś jakiś błąd a nie umiesz go poprawić koniecznie powiadom o tym fakcieautoroacutew tego podręcznika za pomocą strony dyskusji danego modułu książki Dzięki temuprzyczyniasz się do rozwoju tego podręcznika

15 AutorzyIstotny wkład w powstanie podręcznika mają

CzarnyZajaczek

Derbeth

Kj

mina

Dodatkowo w rozwoju podręcznika pomagali między innymi

Lrds

Noisy

16 Źroacutedła podręcznik C Programming na anglojęzycznej wersji Wikibooks licencja GFDL

Brian W Kernighan Dennis M Ritchie Język ANSI C

ISO C Commiee Dra stycznia

Bruce Eckel inking in C++ Rozdział Język C w programie C++

Rozdział 2

O języku C

Zobacz w Wikipedii C (ję-zyk programowania)C jest językiem programowania wysokiego poziomu Jego nazwę interpretuje się jako na-

stępną literę po B (nazwa jego poprzednika) lub drugą literę języka BCPL (poprzednik językaB)

21 Historia CW roku trzej naukowcy z Bell Telephone Laboratories mdashWilliam Shockley Walter Brat-tain i John Bardeen mdash stworzyli pierwszy tranzystor w roku w MIT skonstruowanopierwszy komputer oparty wyłącznie na tranzystorach TX-O w roku Jack Kilby z Te-xas Instruments skonstruował układ scalony Ale zanim powstał pierwszy układ scalonypierwszy język wysokiego poziomu został już napisany

W powstał Fortran (Formula Translator) ktoacutery zapoczątkował napisanie języka For-tran I () Poacuteźniej powstały kolejno

Algol mdash Algorithmic Language w r

Algol ()

CPL mdash Combined Programming Language ()

BCPL mdash Basic CPL ()

B ()

i C w oparciu o BB został stworzony przez Kena ompsona z Bell Labs był to język interpretowany uży-

wany we wczesnych wewnętrznych wersjach systemu operacyjnego UNIX Inni pracownicyBell Labs ompson i Dennis Richie rozwinęli B nazywając go NB dalszy rozwoacutej NB dał Cmdash język kompilowany Większa część UNIX-a została ponownie napisana w NB a następniew C co dało w efekcie bardziej przenośny system operacyjny W roku wydana zostałaksiążka pt ldquoe C Programming Languagerdquo ktoacutera stała się pierwszym podręcznikiem donauki języka C

Możliwość uruchamiania UNIX-a na roacuteżnych komputerach była głoacutewną przyczyną po-czątkowej popularności zaroacutewno UNIX-a jak i C zamiast tworzyć nowy system operacyjnyprogramiści mogli po prostu napisać tylko te części systemu ktoacuterych wymagał inny sprzętoraz napisać kompilator C dla nowego systemu Odkąd większa część narzędzi systemowychbyła napisana w C logiczne było pisanie kolejnych w tym samym języku

13

14 ROZDZIAŁ 2 O JĘZYKU C

Kilka z obecnie powszechnie stosowanych systemoacutew operacyjnych takich jak Linux Mi-croso Windows zostały napisane w języku C

211 Standaryzacje

W roku Ritchie i Kerninghan opublikowali pierwszą książkę nt języka C mdash ldquoe CProgramming Languagerdquo Owa książka przez wiele lat była swoistym ldquowyznacznikiemrdquo jakprogramować w języku C Była więc to niejako pierwsza standaryzacja nazywana od na-zwisk twoacutercoacutew ldquoKampRrdquo Oto nowości wprowadzone przez nią do języka C w stosunku dojego pierwszych wersji (pochodzących z początku lat )

możliwość tworzenia struktur (słowo struct)

dłuższe typy danych (modyfikator long)

liczby całkowite bez znaku (modyfikator unsigned)

zmieniono operator ldquo=+rdquo na ldquo+=rdquo

Ponadto producenci kompilatoroacutew (zwłaszcza ATampT) wprowadzali swoje zmiany nieob-jęte standardem

funkcje nie zwracające wartości (void) oraz typ void

funkcje zwracające struktury i unie

przypisywanie wartości strukturom

wprowadzenie słowa kluczowego const

utworzenie biblioteki standardowej

wprowadzenie słowa kluczowego enum

Owe nieoficjalne rozszerzenia zagroziły spoacutejności języka dlatego też powstał standardregulujący wprowadzone nowinki Od roku trwały prace standaryzacyjne aby w roku wydać standard C (poprawna nazwa to ANSI X-) Niektoacutere zmiany wpro-wadzono z języka C++ jednak rewolucję miał dopiero przynieść standard C ktoacutery wpro-wadził min

funkcje inline

nowe typy danych (np long long int)

nowy sposoacuteb komentowania zapożyczony od C++ ()

przechowywanie liczb zmiennoprzecinkowych zostało zaadaptowane do norm IEEE

utworzono kilka nowych plikoacutew nagłoacutewkowych (stdboolh inypesh)

Na dzień dzisiejszy normą obowiązującą jest norma C

22 ZASTOSOWANIA JĘZYKA C 15

22 Zastosowania języka CJęzyk C został opracowany jako strukturalny język programowania do celoacutew ogoacutelnych Przezcałą swą historię (czyli ponad lat) służył do tworzenia przeroacuteżnych programoacutewmdash od syste-moacutew operacyjnych po programy nadzorujące pracę urządzeń przemysłowych C jako językdużo szybszy od językoacutew interpretowanych (Perl Python) oraz uruchamianych w maszy-nach wirtualnych (np C Java) może bez problemu wykonywać złożone operacje nawetwtedy gdy nałożone są dość duże limity czasu wykonywania pewnych operacji Jest on przytym bardzo przenośny mdash może działać praktycznie na każdej architekturze sprzętowej podwarunkiem opracowania odpowiedniego kompilatora Często wykorzystywany jest takżedo oprogramowywania mikrokontroleroacutew i systemoacutew wbudowanych Jednak w niektoacuterychsytuacjach język C okazuje się być mało przydatny zwłaszcza chodzi tu o obliczenia mate-matyczne wymagające dużej precyzji (w tej dziedzinie znakomicie spisuje się Fortran) lubteż dużej optymalizacji dla danego sprzętu (wtedy niezastąpiony jest język asemblera)

Kolejną zaletą C jest jego dostępność mdash właściwie każdy system typu UNIX posiada kom-pilator C w C pisane są funkcje systemowe

Problemem w przypadku C jest zarządzanie pamięcią ktoacutere nie wybacza programiściebłędoacutew niewygodne operowanie napisami i niestety pewna liczba ldquokruczkoacutewrdquo ktoacutere mogązaskakiwać nowicjuszy Na tle młodszych językoacutew programowania C jest językiem dosyćniskiego poziomu więc wiele rzeczy trzeba w nim robić ręcznie jednak zarazem umożliwia torobienie rzeczy nieprzewidzianych w samym języku (np implementację liczb bitowych)a także łatwe łączenie C z Asemblerem

23 Przyszłość CPomimo sędziwego już wieku (C ma ponad lat) nadal jest on jednym z najczęściej stosowa-nych językoacutew programowania Doczekał się już swoich następcoacutew z ktoacuterymi w niektoacuterychdziedzinach nadal udaje mu się wygrywać Widać zatem że pomimo pozornej prostoty iniewielkich możliwości język C nadal spełnia stawiane przed nim wymagania Warto zatemuczyć się języka C gdyż nadal jest on wykorzystywany (i nic nie wskazuje na to by miałosię to zmienić) a wiedza ktoacuterą zdobędziesz ucząc się C na pewno się nie zmarnuje Skład-nia języka C pomimo że przez wielu uważana za nieczytelną stała się podstawą dla takichjęzykoacutew jak C++ C czy też Java

16 ROZDZIAŁ 2 O JĘZYKU C

Rozdział 3

Czego potrzebujesz

31 Czego potrzebujeszWbrew powszechnej opinii nauczenie się ktoacuteregoś z językoacutew programowania (w tym językaC) nie jest takie trudne Do nauki wystarczą Ci

komputer z dowolnym systemem operacyjnym takim jak FreeBSD Linux Windows

Język C jest bardzo przenośny więc będzie działał właściwie na każdej platformiesprzętowej i w każdym nowoczesnym systemie operacyjnym

kompilator języka C

Kompilator języka C jest programem ktoacutery tłumaczy kod źroacutedłowy napisany przeznas do języka asembler a następnie do postaci zrozumiałej dla komputera (maszynycyfrowej) czyli do postaci ciągu zer i jedynek ktoacutere sterują pracą poszczegoacutelnych ele-mentoacutew komputera Kompilator języka C można dostać za darmo Przykładem sągcc pod systemy uniksowe DJGPP pod systemy DOS MinGW oraz lcc pod systemytypu Windows Jako kompilator C może dobrze służyć kompilator języka C++ (roacuteżnicemiędzy tymi językami przy pisaniu prostych programoacutew są nieistotne) Spokojnie mo-żesz więc użyć na przykład Microso Visual C++reg lub kompilatoroacutew firmy BorlandJeśli lubisz eksperymentować wyproacutebuj Tiny C Compiler bardzo szybki kompilatoro ciekawych funkcjach Możesz ponadto wyproacutebować interpreter języka C Więcejinformacji na Wikipedii

Linker (często jest razem z kompilatorem)

Linker jest to program ktoacutery uruchamiany jest po etapie kompilacji jednego lub kilkuplikoacutew źroacutedłowych (pliki z rozszerzeniem c cpp lub innym) skompilowanych do-wolnym kompilatorem Taki program łączy wszystkie nasze skompilowane pliki źroacute-dłowe i inne funkcje (np printf scan) ktoacutere były użyte (dołączone do naszego pro-gramu poprzez użycie dyrektywy include) w naszym programie a nie były zdefinio-wane(napisane przez nas) w naszych plikach źroacutedłowych lub nagłoacutewkowych Linkerjest to czasami jeden program połączony z kompilatorem Wywoływany jest on naogoacuteł automatycznie przez kompilator w wyniku czego dostajemy gotowy program douruchomienia

Debuger (opcjonalnie według potrzeb)

17

18 ROZDZIAŁ 3 CZEGO POTRZEBUJESZ

Debugger jest to program ktoacutery umożliwia prześledzenie(określenie wartości poszcze-goacutelnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijcewykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu Używasię go w celu określenia czemu nasz program nie działa po naszej myśli lub czemu pro-gram niespodziewanie kończy działanie bez powodu Aby użyć debuggera kompilatormusi dołączyć kod źroacutedłowy do gotowego skompilowanego programu Przykładowymidebuggerami są gdb pod Linuksem lub debugger firmy Borland pod Windowsa

edytora tekstowego

Systemy uniksowe oferują wiele edytoroacutew przydatnych dla programisty jak choćbyvim i Emacs w trybie tekstowym Kate w KDE czy gedit w GNOME Windows maedytor całkowicie wystarczający do pisania programoacutew w C mdash nieśmiertelny Notatnikmdash ale z łatwością znajdziesz w Internecie wiele wygodniejszych narzędzi takich jak npNotepad++ Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE

dużo chęci i dobrej motywacji

32 Zintegrowane Środowiska ProgramistyczneZamiast osobnego kompilatora i edytora możesz wybrać Zintegrowane Środowisko Progra-mistyczne (Integrated Development Environment IDE) IDE jest zestawem wszystkich pro-gramoacutew ktoacutere potrzebuje programista najczęściej z interfejsem graficznym IDE zawierakompilator linker i edytor z reguły roacutewnież debugger

Bardzo popularny IDE to płatny (istnieje także jego darmowa wersja) Microso VisualC++ (MS VC++) popularne darmowe IDE to np

CodeBlocks dla Windows jak i Linux dostępny na stronie wwwcodeblocksorg

KDevelop (Linux) dla KDE

NetBeans multiplatformowy darmowy do ściągnięcia na stronie wwwnetbeansorg

Eclipse z wtyczką CDT (wspoacutełpracuje z MinGW i GCC)

Borland C++ Builder dostępny za darmo do użytku prywatnego

Xcode dlaMac OS X i nowszy kompatybilny z procesorami PowerPC i Intel (moż-liwość stworzenia Universal Binary)

Geany dla systemoacutewWindows i Linux wspoacutełpracuje zMinGW iGCCwwwgeanyorg

Pelles C wwwsmorgasbordetcom

Dev-C++ dla Windows dostępny na stronie wwwbloodshednet

33 Dodatkowe narzędziaWśroacuted narzędzi ktoacutere nie są niezbędne ale zasługują na uwagę można wymienić Valgrindandash specjalnego rodzaju debugger Valgrind kontroluje wykonanie programu i wykrywa nie-prawidłowe operacje w pamięci oraz wycieki pamięci Użycie Valgrinda jest proste mdash kom-pilujemy program jak do debugowania następnie podajemy jako argument Valgrindowi

Rozdział 4

Używanie kompilatora

Język C jest językiem kompilowanym co oznacza że potrzebuje specjalnego programu mdashkompilatora mdash ktoacutery tłumaczy kod źroacutedłowy pisany przez człowieka na język rozkazoacutew da-nego komputera W skroacutecie działanie kompilatora sprowadza się do czytania tekstowegopliku z kodem programu raportowania ewentualnych błędoacutew i produkowania pliku wyniko-wego

Kompilator uruchamiamy ze Zintegrowanego Środowiska Programistycznego lub z kon-soli (linii poleceń) Przejść do konsoli można dla systemoacutew typu UNIX w trybie graficz-nym użyć programoacutew gnome-terminal konsole albo xterm w Windows ldquoWiersz poleceniardquo(można go znaleźćwmenuAkcesoria albo uruchomićwpisującw Start -gtUruchom ldquocmdrdquo)

41 GCCZobacz w Wikipedii GCC

GCC jest to darmowy zestaw kompilatoroacutew min języka C rozwijany w ramach projektuGNU Dostępny jest on na dużą ilość platform sprzętowych obsługiwanych przez takie sys-temy operacyjne jak AIX BSD Linux Mac OS X SunOS Windows Na niektoacuterych sys-temach (np Windows) nie jest on jednak dostępny automatycznie Należy zainstalowaćodpowiednie narzędza (poprzedni rozdział)

Aby skompilować kod języka C za pomocą kompilatora GCC napisany wcześniej w do-wolnym edytorze tekstu należy uruchomić program z odpowiednimi parametrami Podsta-wowym parametrem ktoacutery jest wymagany jest nazwa pliku zawierającego kod programuktoacutery chcemy skompilować

gcc kodc

Rezultatem kompilacji będzie plik wykonywalny z domyślną nazwą (w systemach Unixjest to ldquoaoutrdquo) Jest to metoda niezalecana ponieważ jeżeli skompilujemy w tym samymkatalogu kilka plikoacutew z kodem kolejne pliki wykonywalne zostaną nadpisane i w rezultacieotrzymamy tylko jeden (ten ostatni) skompilowany kod Aby wymusić na GCC nazwę plikuwykonywalnego musimy skorzystać z parametru ldquo-o ltnazwagtrdquo

gcc -o program kodc

W rezultacie otrzymujemy plik wykonywalny o nazwie programPracując nad złożonym programem składającym się z kilku plikoacutew źroacutedłowych (c) mo-

żemy skompilować je niezależnie od siebie tworząc tak zwane pliki typu obiekt z rozszerze-niem o (ang Object File) Następnie możemy stworzyć z nich jednolity program w procesie

19

20 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

konsolidacji (linkowaniu) Jest to bardzo wygodne i praktyczne rozwiązanie ze względu nato iż nie jesteśmy zmuszeni kompilować wszystkich plikoacutew tworzących program za każdymrazem na nowo a jedynie te w ktoacuterych wprowadziliśmy zmiany Aby skompilować plik bezlinkowania używamy parametru ldquo-c ltplikgtrdquo

gcc -o program1o -c kod1cgcc -o program2o -c kod2c

Otrzymujemy w ten sposoacuteb pliki typu obiekt programo i programo A następnie two-rzymy z nich program głoacutewny

gcc -o program program1o program2o

Możemy użyć roacutewnież flag min aby włączyć dokładne rygorystyczne sprawdzanie na-pisanego kodu (co może być przydatne jeśli chcemy dążyć do perfekcji) używamy przełącz-nikoacutew

gcc kodc -o program -Werror -Wall -W -pedantic -ansi

Więcej informacji na temat parametroacutew i działania kompilatora GCC można znaleźć na

Strona domowa projektu GNU GCC

Kroacutetki przekrojowy opis GCC po polsku

Strona podręcznika systemu UNIX (man)

42 BorlandZobacz podręcznik Borland C++ Compiler

43 Czytanie komunikatoacutew o błędaJedną z najbardziej podstawowych umiejętności ktoacutere musi posiąść początkujący progra-mista jest umiejętność rozumienia komunikatoacutew o roacuteżnego rodzaju błędach ktoacutere sygnali-zuje kompilator Wszystkie te informacje pomogą Ci szybko wychwycić ewentualne błędy(ktoacuterych na początku zawsze jest bardzo dużo) Nie martw się że na początku dość częstobędziesz oglądał wydruki błędoacutew zasygnalizowanych przez kompilator mdash nawet zaawanso-wanym programistom się to zdarza Kompilator ma za zadanie pomoacutec Ci w szybkiej popra-wie ewentualnych błędoacutew dlatego też umiejętność analizy komunikatoacutew o błędach jest takważna

431 GCC

Kompilator jest w stanie wychwycić błędy składniowe ktoacutere z pewnością będziesz popełniałKompilator GCC wyświetla je w następującej formie

nazwa_plikucnumer_linijkiopis błędu

Kompilator dość często podaje także nazwę funkcji w ktoacuterej wystąpił błąd Przykładowobłąd deklaracji zmiennej w pliku testc

43 CZYTANIE KOMUNIKATOacuteW O BŁĘDACH 21

include ltstdiohgt

int main ()

intr rprintf (dn r)

Spowoduje wygenerowanie następującego komunikatu o błędzie

testc In function lsquomainrsquotestc5 error lsquointrrsquo undeclared (first use in this function)testc5 error (Each undeclared identifier is reported only oncetestc5 error for each function it appears in)testc5 error syntax error before lsquorrsquotestc6 error lsquorrsquo undeclared (first use in this function)

Co widzimy w raporcie o błędach W linii użyliśmy nieznanego (undeclared) identy-fikatora intr mdash kompilator moacutewi że nie zna tego identyfikatora jest to pierwsze użycie wdanej funkcji i że więcej nie ostrzeże o użyciu tego identyfykatora w tej funkcji Ponieważintr nie został rozpoznany jako żaden znany typ linijka intr r nie została rozpoznana jakodeklaracja zmiennej i kompilator zgłasza błąd składniowy (syntax error) W konsekwencji rnie zostało rozpoznane jako zmienna i kompilator zgłosi to jeszcze w następnej linijce gdzieużywamy r

22 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

Rozdział 5

Pierwszy program

51 Twoacutej pierwszy program

Przyjęło się że pierwszy program napisany w dowolnym języku programowania powinienwyświetlić tekst ldquoHello Worldrdquo (Witaj Świecie) Zauważ że sam język C nie ma żadnychmechanizmoacutew przeznaczonych do wprowadzania i wypisywania danych mdash musimy zatemskorzystać z odpowiadających za to funkcji mdash w tym przypadku printf zawartej w standar-dowej bibliotece C (ang C Standard Library) (podobnie jak w Pascalu używa się do tegoprocedur Pascalowskim odpowiednikiem funkcji printf są procedury writewriteln)

W języku C deklaracje funkcji zawarte są w plika nagłoacutewkowy posiadających naj-częściej rozszerzenie h choć można także spotkać rozszerzenie hpp przy czym to drugiezwykło się stosować w języku C++ (rozszerzenie nie ma swych ldquotechnicznychrdquo korzeni mdash jestto tylko pewna konwencja) W celu umieszczenia w swoim kodzie pewnego pliku nagłoacutewko-wego używamy dyrektywy kompilacyjnej include Przed procesem kompilacji w miejscetej dyrektywy wstawiana jest treść podanego pliku nagłoacutewkowego dostarczając deklaracjifunkcji

Poniższy przykład obrazuje jak przy użyciu dyrektywy include umieścimyw kodzie plikstandardowej biblioteki C stdioh (Standard InputOutputHeaderfile) zawierającą definicjęfunkcji printf

include ltstdiohgt

W nawiasach troacutejkątnych lt gt umieszcza się nazwy standardowych plikoacutew nagłoacutewko-wych1 Żeby włączyć inny plik nagłoacutewkowy (np własny) znajdujący się w katalogu z kodemprogramu trzeba go wpisać w cudzysłoacutew

include moacutej_plik_nagłoacutewkowyh

Mamy więc funkcję printf jak i wiele innych do wprowadzania i wypisywania danychczas na pisanie programu

W programie definujemy głoacutewną funkcję main uruchamianą przy starcie programu za-wierającą właściwy kod Definicja funkcji zawiera oproacutecz nazwy i kodu także typ wartościzwracanej i argumentoacutew pobieranych Konstrukcja funkcji main

1Domyślne pliki nagłoacutewkowe znajdują się w katalogu z plikami nagłoacutewkowymi kompilatora W systemach zrodziny Unix będzie to katalog usrinclude natomiast w systemie Windows oacutew katalog będzie umieszczony wkatalogu z kompilatorem

23

24 ROZDZIAŁ 5 PIERWSZY PROGRAM

int main (void)

Typem zwracany przez funkcję jest int (Integer) czyli liczba całkowita (w przypadkumainbędzie to kod wyjściowy programu) W nawiasach umieszczane są argumenty funkcji tutajzapis void oznacza ich pominięcie Funkcja main jako argumenty może pobierać parametrylinii poleceń z jakimi program został uruchomiony i pełną ścieżkę do katalogu z programem

Kod funkcji umieszcza się w nawiasach klamrowych i Wewnątrz funkcji możemy wpisać poniższy kod

printf(Hello World)return 0

Wszystkie polecenia kończymy średnikiem return określa wartość jaką zwroacuteci funkcja(program) Liczba zero zwracana przez funkcję main() oznacza że program zakończył siębez błędoacutew błędne zakończenie często (choć nie zawsze) określane jest przez liczbę jeden2Funkcję main kończymy nawiasem klamrowym zamykającym

Twoacutej kod powinien wyglądać jak poniżej

include ltstdiohgtint main (void)

printf (Hello World)return 0

Teraz wystarczy go tylko skompilować i uruchomić

52 Rozwiązywanie problemoacutewJeśli nie możesz skompilować powyższego programu to najprawdopodobniej popełniłeś li-teroacutewkę przy ręcznym przepisywaniu go Zobacz też instrukcje na temat używania kompi-latora

Może też się zdarzyć że program skompiluje się uruchomi ale jego efektu działania niebędzie widać Dzieje się tak ponieważ nasz pierwszy program po prostu wypisuje komunikati od razu kończy działanie nie czekając na reakcję użytkownika Nie jest to problememgdy program jest uruchamiany z konsoli tekstowej ale w innych przypadkach nie widzimyefektoacutew jego działania

Jeśli korzystasz ze Zintegrowanego Środowiska Programistycznego (ang IDE) możeszzaznaczyć by nie zamykało ono programu po zakończeniu jego działania Innym sposobemjest dodanie instrukcji ktoacutere wstrzymywałyby zakończenie programu Można to zrobić do-dając przed linią z return funkcję pobierającą znak z wejścia

getchar()

2Jeżeli chcesz mieć pewność że twoacutej program będzie działał poprawnie roacutewnież na platformach gdzie 1 oznaczapoprawne zakończenie (lub nie oznacza nic) możesz skorzystać z makr EXIT SUCCESS i EXIT FAILURE zdefiniowanychw pliku nagłoacutewkowym stdlibh

52 ROZWIĄZYWANIE PROBLEMOacuteW 25

Jest też prostszy (choć nieprzenośny) sposoacuteb mianowicie wywołanie polecenia systemo-wego W zależności od używanego systemu operacyjnego mamy do dyspozycji roacuteżne po-lecenia powodujące roacuteżne efekty Do tego celu skorzystamy z funkcji system() ktoacutera jakoparametr przyjmuje polecenie systemowe ktoacutere chcemy wykonać np

Rodzina systemoacutew UnixLinux

system(sleep 10) oczekiwanie 10 s system(read discard) oczekiwanie na wpisanie tekstu

Rodzina systemoacutew oraz MS Windows

system(pause) oczekiwanie na wciśnięcie dowolnego klawisza

Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnychWindows w ktoacuterychto z reguły pracuje się w trybie okienkowym a z konsoli korzystamy tylko podczas urucha-mianiu programu Z kolei w systemach UnixLinux jest ona praktycznie w ogoacutele nie używanaw tym celu ze względu na uruchamianie programu bezpośrednio z konsoli

26 ROZDZIAŁ 5 PIERWSZY PROGRAM

Rozdział 6

Podstawy

Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogoacutel-nych informacji

61 Kompilacja Jak działa C

Jak każdy język programowania C sam w sobie jest niezrozumiały dla procesora Został onstworzony w celu umożliwienia ludziom łatwego pisania kodu ktoacutery może zostać przetwo-rzony na kod maszynowy Program ktoacutery zamienia kod C na wykonywalny kod binarnyto kompilator Jeśli pracujesz nad projektem ktoacutery wymaga kilku plikoacutew kodu źroacutedłowego(np pliki nagłoacutewkowe) wtedy jest uruchamiany kolejny program mdash linker Linker służy dopołączenia roacuteżnych plikoacutew i stworzenia jednej aplikacji lub biblioteki (library) Bibliotekajest zestawem procedur ktoacutery sam w sobie nie jest wykonywalny ale może być używanaprzez inne programy Kompilacja i łączenie plikoacutew są ze sobą bardzo ściśle powiązane stądsą przez wielu traktowane jako jeden proces Jedną rzecz warto sobie uświadomić mdash kompila-cja jest jednokierunkowa przekształcenie kodu źroacutedłowego C w kod maszynowy jest bardzoproste natomiast odwrotnie mdash nie Dekompilatory co prawda istnieją ale rzadko tworząużyteczny kod C

Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collec-tion dostępny na stronie gccgnuorg

62 Co może C

Pewnie zaskoczy Cię to że tak naprawdę ldquoczystyrdquo język C nie może zbyt wiele Język Cw grupie językoacutew programowania wysokiego poziomu jest stosunkowo nisko Dzięki temukod napisany w języku C można dość łatwo przetłumaczyć na kod asemblera Bardzo łatwojest też łączyć ze sobą kod napisany w języku asemblera z kodem napisanym w C Dla bar-dzo wielu ludzi przeszkodą jest także dość duża liczba i częsta dwuznaczność operatoroacutewPoczątkujący programista czytający kod programu w C może odnieść bardzo nieprzyjemnewrażenie ktoacutere można opisać cytatem ldquoja nigdy tego nie opanujęrdquo Wszystkie te elementyjęzyka C ktoacutere wydają Ci się dziwne i nielogiczne wmiarę jak będziesz nabierał doświadcze-nia nagle okażą się całkiem przemyślanie dobrane i takie a nie inne konstrukcje przypadnąCi do gustu Dalsza lektura tego podręcznika oraz zaznajamianie się z funkcjami z roacuteżnychbibliotek ukażą Ci całą gamę możliwości ktoacutere daje język C doświadczonemu programiście

27

28 ROZDZIAŁ 6 PODSTAWY

63 Struktura blokowaTeraz omoacutewimy podstawową strukturę programu napisanego w C Jeśli miałeś styczność zjęzykiem Pascal to pewnie słyszałeś o nim że jest to język programowania strukturalny WC nie ma tak ścisłej struktury blokowej mimo to jest bardzo ważne zrozumienie co oznaczastruktura blokowa Blok jest grupą instrukcji połączonych w ten sposoacuteb że są traktowanejak jedna całość W C blok zawiera się pomiędzy nawiasami klamrowymi Blok możetakże zawierać kolejne bloki

Zawartość bloku Generalnie blok zawiera ciąg kolejno wykonywanych poleceń Polece-nia zawsze (z nielicznymi wyjątkami) kończą się średnikiem () W jednej linii może znajdo-wać się wiele poleceń choć dla zwiększenia czytelności kodu najczęściej pisze się pojedyncząinstrukcję w każdej linii Jest kilka rodzajoacutew poleceń np instrukcje przypisania warunkoweczy pętli W dużej części tego podręcznika będziemy zajmować się właśnie instrukcjami

Pomiędzy poleceniami są roacutewnież odstępymdash spacje tabulacje oraz przejścia do następnejlinii przy czym dla kompilatora te trzy rodzaje odstępoacutew mają takie samo znaczenie Dlaprzykładu poniższe trzy fragmenty kodu źroacutedłowego dla kompilatora są takie same

printf(Hello world) return 0

printf(Hello world)return 0

printf(Hello world)

return 0

W tej regule istnieje jednak jeden wyjątek Dotyczy on stałych tekstowych W powyż-szych przykładach stałą tekstową jest ldquoHello worldrdquo Gdy jednak rozbijemy ten napis kom-pilator zasygnalizuje błąd

printf(Helloworld)return 0

Należy tylko zapamiętać że stałe tekstowe powinny zaczynać się i kończyć w tej samejlini (można ominąć to ograniczenie mdash więcej w rozdziale Napisy) Oproacutecz tego jednego przy-padku dla kompilatora ma znaczenie samo istnienie odstępu a nie jego wielkość czy rodzajJednak stosowanie odstępoacutew jest bardzo ważne dla zwiększenia czytelności kodu mdash dziękiczemu możemy zaoszczędzić sporo czasu i nerwoacutew ponieważ znalezienie błędu (ktoacutere sięzdarzają każdemu) w nieczytelnym kodzie może być bardzo trudne

64 ZasięgPojęcie to dotyczy zmiennych (ktoacutere przechowują dane przetwarzane przez program) Wkaż-dym programie (oproacutecz tych najprostszych) są zaroacutewno zmienne wykorzystywane przez całyczas działania programu oraz takie ktoacutere są używane przez pojedynczy blok programu (npfunkcję) Na przykład w pewnym programie w pewnym momencie jest wykonywane skom-plikowane obliczenie ktoacutere wymaga zadeklarowania wielu zmiennych do przechowywaniapośrednich wynikoacutew Ale przez większą część tego działania te zmienne są niepotrzebne

65 FUNKCJE 29

i zajmują tylko miejsce w pamięci mdash najlepiej gdyby to miejsce zostało zarezerwowane tużprzed wykonaniemwspomnianych obliczeń a zaraz po ich wykonaniu zwolnione Dlatego wC istnieją zmienne globalne oraz lokalne Zmienne globalne mogą być używane w każdymmiejscu programu natomiast lokalne mdash tylko w określonym bloku czy funkcji (oraz blokachw nim zawartych) Generalnie mdash zmienna zadeklarowanaw danym bloku jest dostępna tylkowewnątrz niego

65 Funkcje

Funkcje są ściśle związane ze strukturą blokową mdash funkcją jest po prostu blok instrukcjiktoacutery jest potem wywoływany w programie za pomocą pojedynczego polecenia Zazwyczajfunkcja wykonuje pewne określone zadanie np we wspomnianym programie wykonują-cym pewne skomplikowane obliczenie Każda funkcja ma swoją nazwę za pomocą ktoacuterejjest potem wywoływana w programie oraz blok wykonywanych poleceń Wiele funkcji po-biera pewne dane czyli argumenty funkcji wiele funkcji także zwraca pewną wartość pozakończeniu wykonywania Dobrym nawykiem jest dzielenie dużego programu na zestawmniejszych funkcji mdash dzięki temu będziesz moacutegł łatwiej odnaleźć błąd w programie

Jeśli chcesz użyć jakiejś funkcji to powinieneś wiedzieć

jakie zadanie wykonuje dana funkcja

rodzaj wczytywanych argumentoacutew i do czego są one potrzebne tej funkcji

rodzaj zwroacuteconych danych i co one oznaczają

W programach w języku C jedna funkcja ma szczegoacutelne znaczenie mdash jest tomain() Funk-cję tę zwaną funkcją głoacutewną musi zawierać każdy program W niej zawiera się głoacutewny kodprogramu przekazywane są do niej argumenty z ktoacuterymi wywoływany jest program (jakoparametry argc i argv) Więcej o funkcji main() dowiesz się poacuteźniej w rozdziale Funkcje

66 Biblioteki standardowe

Język C w przeciwieństwie do innych językoacutew programowania (np Fortranu czy Pascala)nie posiada absolutnie żadny słoacutew kluczowych ktoacutere odpowiedzialne by były za obsługęwejścia i wyjścia Może się to wydawać dziwne mdash język ktoacutery sam w sobie nie posiadapodstawowych funkcji musi być językiem o ograniczonym zastosowaniu Jednak brak pod-stawowych funkcji wejścia-wyjścia jest jedną z największych zalet tego języka Jego składniaopracowana jest tak by można było bardzo łatwo przełożyć ją na kod maszynowy To wła-śnie dzięki temu programy napisane w języku C są takie szybkie Pozostaje jednak pytaniemdash jak umożliwić programom komunikację z użytkownikiem

W roku kiedy zapoczątkowano prace nad standaryzacją C zdecydowano że po-winien być zestaw instrukcji identycznych w każdej implementacji C Nazwano je BibliotekąStandardową (czasemnazywaną ldquolibcrdquo) Zawiera ona podstawowe funkcje ktoacutere umożliwiająwykonywanie takich zadań jak wczytywanie i zwracanie danych modyfikowanie zmiennychłańcuchowych działania matematyczne operacje na plikach i wiele innych jednak nie za-wiera żadnych funkcji ktoacutere mogą być zależne od systemu operacyjnego czy sprzętu jakgrafika dźwięk czy obsługa sieci W programie ldquoHello Worldrdquo użyto funkcji z biblioteki stan-dardowej mdash printf ktoacutera wyświetla na ekranie sformatowany tekst

30 ROZDZIAŁ 6 PODSTAWY

67 Komentarze i stylKomentarze mdash to tekst włączony do kodu źroacutedłowego ktoacutery jest pomijany przez kompilatori służy jedynie dokumentacji W języku C komentarze zaczynają się od

a kończą

Dobre komentowanie ma duże znaczenie dla rozwijania oprogramowania nie tylko dlategoże inni będą kiedyś potrzebowali przeczytać napisany przez ciebie kod źroacutedłowy ale takżemożesz chcieć po dłuższym czasie powroacutecić do swojego programu i możesz zapomnieć doczego służy dany blok kodu albo dlaczego akurat użyłeś tego polecenia a nie innego Wchwili pisania programu to może być dla ciebie oczywiste ale po dłuższym czasie możeszmieć problemy ze zrozumieniem własnego kodu Jednak nie należy też wstawiać zbyt dużokomentarzy ponieważ wtedy kod może stać się jeszcze mniej czytelny mdash najlepiej komen-tować fragmenty ktoacutere nie są oczywiste dla programisty oraz te o szczegoacutelnym znaczeniuAle tego nauczysz się już w praktyce

Dobry styl pisania kodu jest o tyle ważny że powinien on być czytelny i zrozumiały po tow końcu wymyślono języki programowania wysokiego poziomu (w tym C) aby kod było ła-two zrozumieć ) I tak mdash należy stosować wcięcia dla odroacuteżnienia blokoacutew kolejnego poziomu(zawartych w innym bloku podrzędnych) nawiasy klamrowe otwierające i zamykające blokpowinny mieć takie same wcięcia staraj się aby nazwy funkcji i zmiennych kojarzyły się zzadaniem jakie dana funkcja czy zmienna pełni w programie W dalszej części podręcznikamożesz napotkać więcej zaleceń dotyczących stylu pisania kodu Staraj się stosować do tychzaleceń mdash dzięki temu kod pisanych przez ciebie programoacutew będzie łatwiejszy do czytania izrozumienia

Jeśli masz doświadczenia z językiem C++ pamiętaj że w C nie powinno się stosowaćkomentarzy zaczynających się od dwoacutech znakoacutew slash tak nie komentujemy w CJest to niezgodne ze standardem ANSI C i niektoacutere kompilatory mogą nie skompilować koduz komentarzami w stylu C++ (choć standard ISO C dopuszcza komentarze w stylu C++)

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentoacutew kodu Jeśli częśćprogramu źle działa i chcemy ją chwilowo wyłączyć albo fragment kodu jest nam już nie-potrzebny ale mamy wątpliwości czy w przyszłości nie będziemy chcieli go użyć mdash umiesz-czamy go po prostu wewnątrz komentarza

Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uważać najedną subtelność Otoacuteż komentarze w języku C nie mogą być zagnieżdżone Trzebana to uważać gdy chcemy objąć komentarzem obszar w ktoacuterym już istnieje komentarz (na-leży wtedy usunąć wewnętrzny komentarz) W nowszym standardzie C dopuszcza się abykomentarz typu zawierał w sobie komentarz

671 Po polsku czy angielsku

Jak jużwcześniej byłowspomniane zmiennym i funkcjom powinno się nadawać nazwy ktoacutereodpowiadają ich znaczeniu Zdecydowanie łatwiej jest czytać kod gdy średnią liczb przecho-wuje zmienna srednia niż a a znajdowaniemmaksimumw ciągu liczb zajmuje się funkcja maxalbo znajdz max niż nazwana f Często nazwy funkcji to właśnie czasowniki

67 KOMENTARZE I STYL 31

Powstaje pytanie w jakim języku należy pisać nazwy Jeśli chcemy by nasz kod mogłyczytać osoby nieznające polskiego mdash warto użyć języka angielskiego Jeśli nie mdash można bezproblemu użyć polskiego Bardzo istotne jest jednak by nie mieszać językoacutew Jeśli zdecy-dowaliśmy się używać polskiego używajmy go od początku do końca przeplatanie ze sobądwoacutech językoacutew robi złe wrażenie

Warto roacutewnież zdecydować się na sposoacuteb zapisywania nazw składających się z więcej niżjednego słowa Istnieje kilka możliwości najważniejsze z nich

oddzielanie podkreśleniem int to str

ldquokonwencja pascalowskardquo każde słowo dużą literą IntToStr

ldquokonwencja wielbłądziardquo pierwsze słowo małą kolejne dużą literą intToStr

Ponownie najlepiej stosować konsekwentnie jedną z konwencji i nie mieszać ze sobąkilku

672 Notacja węgierska

Czasem programista może zapomnieć jakiego typu była dana zmienna Wtedy musi znaleźćodpowiednią deklarację (co nie zawsze jest łatwe) Dlatego więc wymyślono sposoacuteb by temuzaradzić Pomyślano by w nazwie zmiennej (bądź wskaźnika na zmienną) napisać jakiegojest ona typu np

a liczba (liczba typu int)

w ll dlugaLiczba (wskaźnik na zmienną typu long long)

t5x5 ch tabliczka (tablica x elementoacutew typu char)

func i silnia (funkcja zwracająca int)

Jest to bardzo wygodne przy bardzo zagmatwanych zmiennych

w t4 w t2x2 s pomieszaniec (wskaźnik na tablicę czterech wskaźnikoacutew na tablice dwu-wymiarowe zmiennych typu short)

Lub gdy nie pamiętamy wymiaroacutew tablicy

t4x5x6 f powalonaKostkaRubika (od razu wiemy żet4x5x6 f powalonaKostkaRubika[5][4][6] jest niewłaściwe)

Taki zapis ma też swoje wady Gdy zdecydujemy się zmienić typ zmiennej zamiast poprostu przemienić w deklaracji int na long musimy zmieniać nazwy w całym programieCzęsto takie nazwy są po prostu długie i nie chce nam się ich pisać (no coacuteż programista teżczłowiek) więc wolimy wprowadzić pomieszaniec zamiast w t4 w t2x2 s pomieszaniec Naj-ważniejsze to jednak trzymać się rozwiązania ktoacutere wybraliśmy na początku bo mieszaniejest przerażające

32 ROZDZIAŁ 6 PODSTAWY

68 PreprocesorNie cały napisany przez ciebie kod będzie przekształcany przez kompilator bezpośrednio nakodwykonywalny programu Wwielu przypadkach będziesz używać poleceń ldquoskierowanychdo kompilatorardquo tzw dyrektyw kompilacyjnych Na początku procesu kompilacji specjalnypodprogram tzw preprocesor wyszukuje wszystkie dyrektywy kompilacyjne i wykonujeodpowiednie akcje mdash ktoacutere polegają notabene na edycji kodu źroacutedłowego (np wstawieniudeklaracji funkcji zamianie jednego ciągu znakoacutew na inny) Właściwy kompilator zamie-niający kod C na kod wykonywalny nie napotka już dyrektyw kompilacyjnych ponieważzostały one przez preprocesor usunięte po wykonaniu odpowiednich akcji

W C dyrektywy kompilacyjne zaczynają się od znaku hash () Przykładem najczęściejużywanej dyrektywy jest include ktoacutera jest użyta nawet w tak prostym programie jakldquoHello Worldrdquo include nakazuje preprocesorowi włączyć (ang include) w tym miejscuzawartość podanego pliku tzw pliku nagłoacutewkowego najczęściej to będzie plik zawierającyfunkcje z ktoacuterejś biblioteki standardowej (stdioh mdash STandard Input-Output rozszerzenie hoznacza plik nagłoacutewkowy C) Dzięki temu zamiast wklejać do kodu swojego programu dekla-racje kilkunastu a nawet kilkudziesięciu funkcji wystarczy wpisać jedną magiczną linijkę

69 Nazwy zmienny stały i funkcjiIdentyfikatory czyli nazwy zmiennych stałych i funkcji mogą składać się z liter (bez polskichznakoacutew) cyfr i znaku podkreślenia z tym że nazwa taka nie może zaczynać się od cyfry Niemożna używać nazw zarezerwowanych (patrz Składnia)

Przykłady błędnych nazw

2liczba (nie można zaczynać nazwy od cyfry)moja funkcja (nie można używać spacji)$i (nie można używać znaku $)if (if to słowo kluczowe)

Aby kod był bardziej czytelny przestrzegajmy poniższych (umownych) reguł

nazwy zmiennych piszemy małymi literami i file

nazwy stałych (zadeklarowanych przy pomocy define) piszemy wielkimi literamiSIZE

nazwy funkcji piszemy małymi literami print

wyrazy w nazwach oddzielamy znakiem podkreślenia open file close all files

Są to tylko konwencje mdash żaden kompilator nie zgłosi błędu jeśli wprowadzimy swoacutej wła-sny system nazewnictwa Jednak warto pamiętać że być może nad naszym kodem będą pra-cowali także inni programiści ktoacuterzy mogą mieć trudności z analizą kodu niespełniającegopewnych zasad

Rozdział 7

Zmienne

Procesor komputera stworzony jest tak aby przetwarzał dane znajdujące się w pamięci kom-putera Z punktu widzenia programu napisanego w języku C (ktoacutery jak wiadomo jest języ-kiem wysokiego poziomu) dane umieszczane są w postaci tzw zmienny Zmienne uła-twiają programiście pisanie programu Dzięki nim programista nie musi się przejmowaćgdzie w pamięci owe zmienne się znajdują tzn nie operuje fizycznymi adresami pamięcijak np 0x14613467 tylko prostą do zapamiętania nazwą zmiennej

71 Czym są zmienneZmienna jest to pewien fragment pamięci o ustalonym rozmiarze ktoacutery posiada własny iden-tyfikator (nazwę) oraz może przechowywać pewną wartość zależną od typu zmiennej

711 Deklaracja zmienny

Aby moacutec skorzystać ze zmiennej należy ją przed użyciem zadeklarować to znaczy poinfor-mować kompilator jak zmienna będzie się nazywać i jaki typ ma mieć Zmienne deklarujesię w sposoacuteb następujący

typ nazwa_zmiennej

Oto deklaracja zmiennej o nazwie ldquowiekrdquo typu ldquointrdquo czyli liczby całkowitej

int wiek

Zmiennej w momencie zadeklarowania można od razu przypisać wartość

int wiek = 17

W języku C zmienne deklaruje się na samym początku bloku (czyli przed pierwszą in-strukcją)

int wiek = 17printf(dn wiek)int kopia_wieku tu stary kompilator C zgłosi błąd

deklaracja występuje po instrukcji (printf) kopia_wieku = wiek

33

34 ROZDZIAŁ 7 ZMIENNE

Według nowszych standardoacutewmożliwe jest deklarowanie zmiennej w dowolnymmiejscuprogramu ale wtedy musimy pamiętać aby zadeklarować zmienną przed jej użyciem Toznaczy że taki kod jest niepoprawny

printf (Mam d latn wiek)int wiek = 17

Należy go zapisać tak

int wiek = 17printf (Mam d latn wiek)

Język C nie inicjalizuje zmiennych lokalnych Oznacza to że w nowo zadeklarowanejzmiennej znajdują się śmieci - to co wcześniej zawierał przydzielony zmiennej fragmentpamięci Aby uniknąć ciężkich do wykrycia błędoacutew dobrze jest inicjalizować (przypisywaćwartość) wszystkie zmienne w momencie zadeklarowania

712 Zasięg zmiennej

Zmienne mogą być dostępne dla wszystkich funkcji programu mdash nazywamy je wtedy zmien-nymi globalnymi Deklaruje się je przed wszystkimi funkcjami programu

include ltstdiohgt

int ab nasze zmienne globalne

void func1 ()

instrukcje a=3 dalsze instrukcje

int main ()

b=3a=2return 0

Zmienne globalne jeśli programista nie przypisze im innej wartości podczas definiowa-nia są inicjalizowane wartością

Zmienne ktoacutere funkcja deklaruje do ldquowłasnych potrzebrdquo nazywamy zmiennymi lokal-nymi Nasuwa się pytanie ldquoczy będzie błędem nazwanie tą samą nazwą zmiennej globalneji lokalnejrdquo Otoacuteż odpowiedź może być zaskakująca nie Natomiast w danej funkcji da sięużywać tylko jej zmiennej lokalnej Tej konstrukcji należy z wiadomych względoacutew unikać

int a=1 zmienna globalna

int main()

71 CZYM SĄ ZMIENNE 35

int a=2 to już zmienna lokalna printf(d a) wypisze 2

713 Czas życia

Czas życia to czas od momentu przydzielenia dla zmiennej miejsca w pamięci (stworzenieobiektu) do momentu zwolnienia miejsca w pamięci (likwidacja obiektu)

Zakres ważności to część programu w ktoacuterej nazwa znana jest kompilatorowi

main()

int a = 10 otwarcie lokalnego bloku

int b = 10printf(d d a b)

zamknięcie lokalnego bloku zmienna b jest usuwana

printf(d d a b) BŁĄD b juz nie istnieje tu usuwana jest zmienna a

Zdefiniowaliśmy dwie zmienne typu int Zaroacutewno a i b istnieją przez cały program (czasżycia) Nazwa zmiennej a jest znana kompilatorowi przez cały program Nazwa zmiennej bjest znana tylko w lokalnym bloku dlatego nastąpi błąd w ostatniej instrukcji

Niektoacutere kompilatory (prawdopodobniemożna tu zaliczyćMicrosoVisual C++ dowersji) uznają powyższy kod za poprawny W dodatku można ustawić w opcjach niektoacuterychkompilatoroacutew zachowanie w takiej sytuacji włącznie z zachowaniami niezgodnymi ze stan-dardem języka

Możemy świadomie ograniczyć ważność zmiennej do kilku linijek programu (tak jak ro-biliśmy wyżej) tworząc blok Nazwa zmiennej jest znana tylko w tym bloku

714 Stałe

Stała roacuteżni się od zmiennej tylko tym że nie można jej przypisać innej wartości w trak-cie działania programu Wartość stałej ustala się w kodzie programu i nigdy ona nie ulegazmianie Stałą deklaruje się z użyciem słowa kluczowego const w sposoacuteb następujący

const typ nazwa_stałej=wartość

Dobrze jest używać stałych w programie ponieważ unikniemy wtedy przypadkowychpomyłek a kompilator może często zoptymalizować ich użycie (np od razu podstawiając ichwartość do kodu)

36 ROZDZIAŁ 7 ZMIENNE

const int WARTOSC_POCZATKOWA=5int i=WARTOSC_POCZATKOWAWARTOSC_POCZATKOWA=4 tu kompilator zaprotestuje int j=WARTOSC_POCZATKOWA

Przykład pokazuje dobry zwyczaj programistyczny jakim jest zastępowanie umieszczo-nych na stałe w kodzie liczb stałymi W ten sposoacuteb będziemy mieli większą kontrolę nadkodem mdash stałe umieszczone w jednym miejscu można łatwo modyfikować zamiast szukaćpo całym kodzie liczb ktoacutere chcemy zmienić

Nie mamy jednak pełnej gwarancji że stała będzie miała tę samą wartość przez cały czaswykonania programu możliwe jest bowiem dostanie się do wartości stałej (miejsca jej prze-chowywania w pamięci) pośrednio mdash za pomocą wskaźnikoacutew Można zatem dojść do wnio-sku że słowo kluczowe const służy tylko do poinformowania kompilatora aby ten nie zezwa-lał na jawną zmianę wartości stałej Z drugiej strony zgodnie ze standardem proacuteba mody-fikacji wartości stałej ma niezdefiniowane działanie (tzw undefined behaviour) i w związkuz tym może się powieść lub nie ale może też spowodować jakieś subtelne zmiany ktoacutere wefekcie spowodują że program będzie źle działał

Podobnie do zdefiniowania stałej możemy użyć dyrektywy preprocesora define (opi-sanej w dalszej części podręcznika) Tak zdefiniowaną stałą nazywamy stałą symbolicznąW przeciwieństwie do stałej zadeklarowanej z użyciem słowa const stała zdefiniowana przyużyciu define jest zastępowana daną wartością w każdym miejscu gdzie występuje dlategoteż może być używana w miejscach gdzie ldquonormalnardquo stała nie mogłaby dobrze spełnić swejroli

W przeciwieństwie do języka C++ w C stała to cały czas zmienna ktoacuterej kompilatorpilnuje by nie zmieniła się

72 Typy zmienny

Każdy program w C operuje na zmiennych mdash wydzielonych w pamięci komputera obsza-rach ktoacutere mogą reprezentować obiekty nam znane takie jak liczby znaki czy też bardziejzłożone obiekty Jednak dla komputera każdy obszar w pamięci jest taki sam mdash to ciąg zeri jedynek w takiej postaci zupełnie nieprzydatny dla programisty i użytkownika Podczaspisania programu musimy wskazać w jaki sposoacuteb ten ciąg ma być interpretowany

Typ zmiennej wskazuje właśnie sposoacuteb w jaki pamięć w ktoacuterej znajduje się zmiennabędzie wykorzystywana Określając go przekazuje się kompilatorowi informację ile pamięcitrzeba zarezerwować dla zmiennej a także w jaki sposoacuteb wykonywać na nim operacje

Każda zmienna musi mieć określony swoacutej typ w miejscu deklaracji i tego typu nie możejuż zmienić Lecz co jeśli mamy zmienną jednego typu ale potrzebujemy w pewnymmiejscuprogramu innego typu danych W takimwypadku stosujemy konwersję (rzutowanie) jednejzmiennej na inną zmienną Rzutowanie zostanie opisane poacuteźniej w rozdziale Operatory

Istnieją wbudowane i zdefiniowane przez użytkownika typy danych Wbudowane typydanych to te ktoacutere zna kompilator są one w nim bezpośrednio ldquozaszyterdquo Można też tworzyćwłasne typy danych ale należy je kompilatorowi opisać Więcej informacji znajduje się wrozdziale Typy złożone

W języku C wyroacuteżniamy podstawowe typy zmiennych Są to

char mdash jednobajtowe liczby całkowite służy do przechowywania znakoacutew

int mdash typ całkowity o długości domyślnej dla danej architektury komputera

72 TYPY ZMIENNYCH 37

float mdash typ zmiennopozycyjny (zwany roacutewnież zmiennoprzecinkowym) reprezentującyliczby rzeczywiste ( bajty)

double mdash typ zmiennopozycyjny podwoacutejnej precyzji ( bajtoacutew)

Typy zmiennoprzecinkowe zostały dokładnie opisane w IEEE

W języku C nie istnieje specjalny typ zmiennych przeznaczony na zmienne typu logicz-nego (albo ldquoprawda albo ldquofałszrdquo) Jest to inne podejście niż na przykład w językach Pascalalbo Java - definiujących osobny typ ldquobooleanrdquo ktoacuterego nie można ldquomieszaćz innymi typamizmiennych W C do przechowywania wartości logicznych zazwyczaj używa się typu ldquointrdquoWięcej na temat tego jak język C rozumie prawdę i fałsz znajduje się w rozdziale Operatory

721 int

Ten typ przeznaczony jest do liczb całkowitych Liczby temożemy zapisać na kilka sposoboacutew

System dziesiętny

12 13 45 35 itd

System oacutesemkowy (oktalny)

010 czyli 8016 czyli 8 + 6 = 14018 BŁĄD

System ten operuje na cyfrach od do Tak wiec jest niedozwolona Jeżeli chcemyużyć takiego zapisu musimy zacząć liczbę od

System szesnastkowy (heksadecymalny)

0x10 czyli 116 + 0 = 160x12 czyli 116 + 2 = 180xff czyli 1516 + 15 = 255

W tym systemie możliwe cyfry to hellip i dodatkowo a b c d e f ktoacutere oznaczają Aby użyć takiego systemu musimy poprzedzić liczbę ciągiem 0x Wielkośćznakoacutew w takich literałach nie ma znaczenia

Ponadto w niektoacuterych kompilatorach przeznaczonych głoacutewnie domikrokontroleroacutew spo-tyka się jeszcze użycie systemu binarnego Zazwyczaj dodaje się przedrostek 0b przed liczbą(analogicznie do zapisu spotykanego w języku Python) W tym systemie możemy oczywiścieużywać tylko i wyłącznie cyfr i Tego typu rozszerzenie bardzo ułatwia programowanieniskopoziomowe układoacutew Należy jednak pamiętać że jest to tylko i wyłącznie rozszerzenie

38 ROZDZIAŁ 7 ZMIENNE

722 float

Ten typ oznacza liczby zmiennoprzecinkowe czyli ułamki Istnieją dwa sposoby zapisu

System dziesiętny

314 45644 2354 321 itd

System ldquonaukowyrdquo mdash wykładniczy

6e2 czyli 6 102 czyli 60015e3 czyli 15 103 czyli 150034e-3 czyli 34 10minus3 czyli 00034

Należy wziąć pod uwagę że reprezentacja liczb rzeczywistych w komputerze jest niedo-skonała i możemy otrzymywać wyniki o zauważalnej niedokładności

723 double

Doublemdash czyli ldquopodwoacutejnyrdquomdash oznacza liczby zmiennoprzecinkowe podwoacutejnej precyzji Ozna-cza to że liczba taka zajmuje zazwyczaj w pamięci dwa razy więcej miejsca niż float (np bity wobec dla float) ale ma też dwa razy lepszą dokładność

Domyślnie ułamki wpisane w kodzie są typu double Możemy to zmienić dodając nakońcu literę ldquordquo

15f (float)15 (double)

724 ar

Jest to typ znakowy umożliwiający zapis znakoacutew ASCII Może też być traktowany jako liczbaz zakresu Znaki zapisujemywpojedynczych cudzysłowach (czasami nazywanymi apo-strofami) by odroacuteżnić je od łańcuchoacutew tekstowych (pisanych w podwoacutejnych cudzysłowach)

a 7 $

Pojedynczy cudzysłoacutew rsquo zapisujemy tak a null (czyli zero ktoacutere między innymikończy napisy) tak 0 Więcej znakoacutew specjalnych

Warto zauważyć że typ char to zwykły typ liczbowy i można go używać tak samo jaktypu int (zazwyczaj ma jednak mniejszy zakres) Co więcej literały znakowe (np rsquoarsquo) sątraktowane jako liczby i w języku C są typu int (w języku C++ są typu char)

725 void

Słowa kluczowego void można w określonych sytuacjach użyć tam gdzie oczekiwana jestnazwa typu void nie jest właściwym typem bo nie można utworzyć zmiennej takiego typujest to ldquopustyrdquo typ (ang void znaczy ldquopustyrdquo) Typ void przydaje się do zaznaczania żefunkcja nie zwraca żadnej wartości lub że nie przyjmuje żadnych parametroacutew (więcej o tymw rozdziale Funkcje) Można też tworzyć zmienne będące typu ldquowskaźnik na voidrdquo

73 SPECYFIKATORY 39

73 Specyfikatory

Specyfikatory to słowa kluczowe ktoacutere postawione przy typie danych zmieniają jego zna-czenie

731 signed i unsigned

Na początku zastanoacutewmy się jak komputer może przechować liczbę ujemną Otoacuteż w przy-padku przechowywania liczb ujemnych musimyw zmiennej przechować jeszcze jej znak Jakwiadomo zmienna składa się z szeregu bitoacutew W przypadku użycia zmiennej pierwszy bit zlewej strony (nazywany także bitem najbardziej znaczącym) przechowuje znak liczby Efek-tem tego jest spadek ldquopojemnościrdquo zmiennej czyli zmniejszenie największej wartości ktoacuterąmożemy przechować w zmiennej

Signed oznacza liczbę ze znakiem unsigned mdash bez znaku (nieujemną) Mogą być zasto-sowane do typoacutew char i int i łączone ze specyfikatorami short i long (gdy ma to sens)

Jeśli przy signed lub unsigned nie napiszemy o jaki typ nam chodzi kompilator przyjmiewartość domyślną czyli int

Przykładowo dla zmiennej char(zajmującej bitoacutew zapisanej w formacie uzupełnień dodwoacutech) wygląda to tak

signed char a zmienna a przyjmuje wartości od -128 do 127 unsigned char b zmienna b przyjmuje wartości od 0 do 255 unsigned short cunsigned long int d

Jeżeli nie podamy żadnego ze specyfikatora wtedy liczba jest domyślnie przyjmowanajako signed (nie dotyczy to typu char dla ktoacuterego jest to zależne od kompilatora)

signed int i = 0 jest roacutewnoznaczne zint i = 0

Liczby bez znaku pozwalają nam zapisać większe liczby przy tej samej wielkości zmiennejmdash ale trzeba uważać by nie zejść z nimi poniżej zera mdash wtedy ldquoprzewijająrdquo się na sam konieczakresu co może powodować trudne do wykrycia błędy w programach

732 short i long

Short i long są wskazoacutewkami dla kompilatora by zarezerwował dla danego typu mniej (od-powiednio mdash więcej) pamięci Mogą być zastosowane do dwoacutech typoacutew int i double (tylkolong) mając roacuteżne znaczenie

Jeśli przy short lub long nie napiszemy o jaki typ nam chodzi kompilator przyjmie war-tość domyślną czyli int

Należy pamiętać że to jedynie życzenie wobec kompilatora mdash w wielu kompilatorachtypy int i long int mają ten sam rozmiar Standard języka C nakłada jedynie na kompilatorynastępujące ograniczenia int mdash nie może być kroacutetszy niż bitoacutew int mdash musi byćdłuższy lub roacutewny short a nie może być dłuższy niż long short int mdash nie może byćkroacutetszy niż bitoacutew long int mdash nie może być kroacutetszy niż bity

Zazwyczaj typ int jest typem danych o długości odpowiadającej wielkości rejestroacutew pro-cesora czyli na procesorze szesnastobitowym ma bitoacutew na trzydziestodwubitowym mdash

40 ROZDZIAŁ 7 ZMIENNE

itd1 Z tego powodu jeśli to tylko możliwe do reprezentacji liczb całkowitych preferowanejest użycie typu int bez żadnych specyfikatoroacutew rozmiaru

74 Modyfikatory

741 volatile

volatile znaczy ulotny Oznacza to że kompilator wyłączy dla takiej zmiennej optymaliza-cje typu zastąpienia przez stałą lub zawartość rejestru za to wygeneruje kod ktoacutery będzieodwoływał się zawsze do komoacuterek pamięci danego obiektu Zapobiegnie to błędowi gdyobiekt zostaje zmieniony przez część programu ktoacutera nie ma zauważalnego dla kompilatorazwiązku z danym fragmentem kodu lub nawet przez zupełnie inny proces

volatile float liczba1float liczba2

printf (fnfn liczba1 liczba2) instrukcje nie związane ze zmiennymi printf (fnf liczba1 liczba2)

Jeżeli zmienne liczba i liczba zmienią się niezauważalnie dla kompilatora to odczytując

liczba mdash nastąpi odwołanie do komoacuterek pamięci Kompilator pobierze nową wartośćzmiennej

liczba mdash kompilator może wypisać poprzednią wartość ktoacuterą przechowywał w reje-strze

Modyfikator volatile jest rzadko stosowany i przydaje się w wąskich zastosowaniachjak wspoacutełbieżność i wspoacutełdzielenie zasoboacutew oraz przerwania systemowe

742 register

Jeżeli utworzymy zmienną ktoacuterej będziemy używać w swoim programie bardzo często mo-żemy wykorzystać modyfikator register Kompilator może wtedy umieścić zmienną w re-jestrze do ktoacuterego ma szybki dostęp co przyśpieszy odwołania do tej zmiennej

register int liczba

W nowoczesnych kompilatorach ten modyfikator praktycznie nie ma wpływu na pro-gram Optymalizator sam decyduje czy i co należy umieścić w rejestrze Nie mamy żadnejgwarancji że zmienna tak zadeklarowana rzeczywiście się tam znajdzie chociaż dostęp doniej może zostać przyspieszony w inny sposoacuteb Raczej powinno się unikać tego typu kon-strukcji w programie

1Wiąże się to z pewnymi uwarunkowaniami historycznymi Podręcznik do języka C duetu KampR zakładał żetyp int miał się odnosić do typowej dla danego procesora długości liczby całkowitej Natomiast jeśli procesor moacutegłobsługiwać typy dłuższe lub kroacutetsze stosownego znaczenia nabierałymodyfikatory short i long Dobrymprzykłademmoże być architektura i386 ktoacutera umożliwia obliczenia na liczbach 16-bitowych Dlatego też modyfikator shortpowoduje skroacutecenie zmiennej do 16 bitoacutew

75 UWAGI 41

743 static

Pozwala na zdefiniowanie zmiennej statycznej ldquoStatycznośćrdquo polega na zachowaniu warto-ści pomiędzy kolejnymi definicjami tej samej zmiennej Jest to przede wszystkim przydatnew funkcjach Gdy zdefiniujemy zmienną w ciele funkcji to zmienna ta będzie od nowa defi-niowana wraz z domyślną wartością (jeżeli taką podano) W wypadku zmiennej określonejjako statyczna jej wartość się nie zmieni przy ponownym wywołaniu funkcji Na przykład

void dodaj(int liczba)

int zmienna = 0 bez staticzmienna = zmienna + liczbaprintf (Wartosc zmiennej dn zmienna)

Gdy wywołamy tę funkcję np razy w ten sposoacuteb

dodaj(3)dodaj(5)dodaj(4)

to ujrzymy na ekranie

Wartosc zmiennej 3Wartosc zmiennej 5Wartosc zmiennej 4

jeżeli jednak deklarację zmiennej zmienimy na static int zmienna = 0 to wartość zmiennejzostanie zachowana i po podobnym wykonaniu funkcji powinnyśmy ujrzeć

Wartosc zmiennej 3Wartosc zmiennej 8Wartosc zmiennej 12

Zupełnie co innego oznacza static zastosowane dla zmiennej globalnej Jest ona wtedywidoczna tylko w jednym pliku Zobacz też rozdział Biblioteki

744 extern

Przez extern oznacza się zmienne globalne zadeklarowane w innych plikach mdash informujemyw ten sposoacuteb kompilator żeby nie szukał jej w aktualnym pliku Zobacz też rozdział Biblio-teki

745 auto

Zupełnym archaizmem jest modyfikator auto ktoacutery oznacza tyle że zmienna jest lokalnaPonieważ zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna modyfikator tennie ma obecnie żadnego zastosowania praktycznego auto jest spadkiem po wcześniejszychjęzykach programowania na ktoacuterych oparty jest C (np B)

75 Uwagi Język C++ pozwala na mieszanie deklaracji zmiennych z kodem Więcej informacji w

C++Zmienne

42 ROZDZIAŁ 7 ZMIENNE

Rozdział 8

Operatory

81 Przypisanie

Operator przypisania (=rdquo) jak sama nazwa wskazuje przypisuje wartość prawego argu-mentu lewemu np

int a = 5 bb = aprintf(dn b) wypisze 5

Operator ten ma łączność prawostronną tzn obliczanie przypisań następuje z prawa nalewo i zwraca on przypisaną wartość dzięki czemu może być użyty kaskadowo

int a b ca = b = c = 3printf(d d dn a b c) wypisze 3 3 3

811 Skroacutecony zapis

C umożliwia też skroacutecony zapis postaci a = b gdzie jest jednym z operatoroacutew + - amp | ˆ ltlt lub gtgt (opisanych niżej) Ogoacutelnie rzecz ujmując zapis a = b jest roacutewnoważnyzapisowi a = a (b) np

int a = 1a += 5 to samo co a = a + 5 a = a + 2 to samo co a = a (a + 2) a = 2 to samo co a = a 2

Początkowo skroacutecona notacja miała następującą składnię a = b co często prowadziło doniejasności np i =- (i = - czy też i = i-) Dlatego też zdecydowano się zmienić kolejnośćoperatoroacutew

43

44 ROZDZIAŁ 8 OPERATORY

82 Rzutowanie

Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu Konwer-sja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podanaexplicite przez programistę) Oto kilka przykładoacutew konwersji niejawnej

int i = 427 konwersja z double do int float f = i konwersja z int do float double d = f konwersja z float do double unsigned u = i konwersja z int do unsigned int f = 42 konwersja z double do float i = d konwersja z double do int char str = foo konwersja z const char do char [1] const char cstr = str konwersja z char do const char void ptr = str konwersja z char do void

Podczas konwersji zmiennych zawierających większe ilości danych do typoacutew prostszych(np double do int) musimy liczyć się z utratą informacji jak to miało miejsce w pierwszejlinijce mdash zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta iw rezultacie zmiennej została przypisana wartość

Zaskakująca może się wydać linijka oznaczona przez 1 Niejawna konwersja z typu constchar do typu char nie jest dopuszczana przez standard C Jednak literały napisowe (ktoacutere sątypu const char) stanowią tutaj wyjątek Wynika on z faktu że były one używane na długoprzed wprowadzeniem słoacutewka const do języka i brak wspomnianegowyjątku spowodowałbyże duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod

Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania np

double d = 314int pi = (int)d 1 pi = (unsigned)pi gtgt 4 2

W pierwszym przypadku operator został użyty by zwroacutecić uwagę na utratę precyzji Wdrugim dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej

Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne kon-wersje (tj konwersja z double do int oraz z int do unsigned int) jednak niektoacutere konwersjesą błędne np

const char cstr = foochar str = cstr

W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję

const char cstr = foochar str = (char)cstr

Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompi-lator Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzieon robił i czy nie ma innego sposobu wykonania danej operacji ktoacutery nie wymagałby podej-mowania tak drastycznych krokoacutew

83 OPERATORY ARYTMETYCZNE 45

83 Operatory arytmetyczne

W arytmetyce komputerowej nie działa prawo łączności oraz rozdzielności Wynika toz ograniczonego rozmiaru zmiennych ktoacutere przechowują wartości Przykład dla zmiennycho długości bitoacutew (bez znaku) Maksymalna wartość ktoacuterą może przechowywać typ to216minus1 = 65535 Zatem operacja typu 65530+10minus20 zapisana jako (65530+10)minus20 możezaowocować czymś zupełnie innym niż 65530+(10minus20) W pierwszym przypadku zapewnedojdzie do tzw przepełnienia - procesor nie będzie miał miejsca aby zapisać dodatkowybit Zachowanie programu będzie w takim przypadku zależało od architektury procesoraAnalogiczny przykład możemy podać dla rozdzielności mnożenia względem dodawania

Język C definiuje następujące dwuargumentowe operatory arytmetyczne

dodawanie (+rdquo)

odejmowanie (-rdquo)

mnożenie (rdquo)

dzielenie (rdquo)

reszta z dzielenia (rdquo) określona tylko dla liczb całkowitych (tzw dzielenie modulo)

int a=7 b=2 cc = a bprintf (dnc) wypisze 1

Należy pamiętać że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak naj-większy z argumentoacutew Oznacza to że operacja wykonana na dwoacutech liczbach całkowitychnadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej Dla przy-kładu poniższy kod

float a = 7 2printf(fn a)

wypisze (wbrew oczekiwaniu początkujących programistoacutew) 30 a nie 35 Odnosi sięto nie tylko do dzielenia ale także mnożenia np

float a = 1000 1000 1000 1000 1000 1000printf(fn a)

prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali Aby wymusićobliczenia rzeczywiste należy zmienić typ jednego z argumentoacutew na liczbę rzeczywistą poprostu zmieniając literał lub korzystając z rzutowania np

float a = 70 2float b = (float)1000 1000 1000 1000 1000 1000printf(fn a)printf(fn b)

Operatory dodawania i odejmowania są określone roacutewnież gdy jednym z argumentoacutewjest wskaźnik a drugim liczba całkowita Ten drugi jest także określony gdy oba argumentysą wskaźnikami O takim użyciu tych operatoroacutew dowiesz się więcej CWskaźniki|w dalszejczęści książki

46 ROZDZIAŁ 8 OPERATORY

831 Inkrementacja i dekrementacja

Aby skroacutecić zapis wprowadzono dodatkowe operatory inkrementacji (++rdquo) i dekrementa-cji (ndashrdquo) ktoacutere dodatkowo mogą być pre- lub postfiksowe W rezultacie mamy więc czteryoperatory

pre-inkrementacja (++irdquo)

post-inkrementacja (i++rdquo)

pre-dekrementacja (ndashirdquo) i

post-dekrementacja (indashrdquo)

Operatory inkrementacji zwiększa a dekrementacji zmniejsza argument o jeden Ponadtooperatory pre- zwracają nową wartość argumentu natomiast post- starą wartość argumentu

int a b ca = 3b = a-- po operacji b=3 a=2 c = --b po operacji b=2 c=2

Czasami (szczegoacutelnie w C++) użycie operatoroacutew stawianych za argumentem jest niecomniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tym-czasową)

Bardzo ważne jest abyśmy poprawnie stosowali operatory dekrementacji i inkrementa-cji Chodzi o to aby w jednej instrukcji nie umieszczać kilku operatoroacutew ktoacutere modyfikująten sam obiekt (zmienną) Jeżeli taka sytuacja zaistnieje to efekt działania instrukcji jestnieokreślony Prostym przykładem mogą być następujące instrukcje

int a = 1a = a++a = ++aa = a++ + ++aprintf(d dn ++a ++a)printf(d dn a++ a++)

Kompilator potrafi ostrzegać przed takimi błędami - aby to czynił należy podać mujako argument opcję -Wsequence-point

84 Operacje bitoweOproacutecz operacji znanych z lekcji matematyki w podstawoacutewce język C został wyposażonytakże w operatory bitowe zdefiniowane dla liczb całkowitych Są to

negacja bitowa (˜rdquo)

koniunkcja bitowa (amprdquo)

alternatywa bitowa (|rdquo) i

84 OPERACJE BITOWE 47

alternatywa rozłączna () (ˆrdquo)

Działają one na poszczegoacutelnych bitach przez co mogą być szybsze od innych operacjiDziałanie tych operatoroacutew można zdefiniować za pomocą poniższych tabel

~ | 0 1 amp | 0 1 | | 0 1 ^ | 0 1-----+----- -----+----- -----+----- -----+-----

| 1 0 0 | 0 0 0 | 0 1 0 | 0 11 | 0 1 1 | 1 1 1 | 1 0

a | 0101 = 5b | 0011 = 3

-------+------~a | 1010 = 10~b | 1100 = 12

a amp b | 0001 = 1a | b | 0111 = 7a ^ b | 0110 = 6

Lub bardziej opisowo

negacja bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych argument miał bity roacutewne zero

koniunkcja bitowa daje wwyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych oba argumenty miały bity roacutewne jeden (mnemonik gdy wszystkie)

alternatywa bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden na wszystkichtych pozycjach na ktoacuterych jeden z argumentoacutew miał bit roacutewny jeden (mnemonik jeśli jest )

alternatywa rozłączna daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tychpozycjach na ktoacuterych tylko jeden z argumentoacutew miał bit roacutewny jeden (mnemonik gdy roacuteżne)

Przy okazji warto zauważyć że aˆbˆb to po prostu a Właściwość ta została wykorzystanaw roacuteżnych algorytmach szyfrowania oraz funkcjach haszujących Alternatywę wyłączną sto-suje się np do szyfrowania kodu wirusoacutew polimorficznych

841 Przesunięcie bitowe

Dodatkowo język C wyposażony jest w operatory przesunięcia bitowego w lewo (ltltrdquo) iprawo (gtgtrdquo) Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycjipodaną jako prawy argument Brzmi to może strasznie ale wcale takie nie jest Rozważmy-bitowe liczby bez znaku (taki hipotetyczny unsigned int) woacutewczas

a | altlt1 | altlt2 | agtgt1 | agtgt2------+------+------+------+------0001 | 0010 | 0100 | 0000 | 00000011 | 0110 | 1100 | 0001 | 00000101 | 1010 | 0100 | 0010 | 0001

48 ROZDZIAŁ 8 OPERATORY

1000 | 0000 | 0000 | 0100 | 00101111 | 1110 | 1100 | 0111 | 00111001 | 0010 | 0100 | 0100 | 0010

Nie jest to zatem takie straszne na jakie wygląda Widać że bity będące na skraju sątracone a w pusterdquo miejsca wpisywane są zera

Inaczej rzecz się ma jeżeli lewy argument jest liczbą ze znakiem Dla przesunięcia bito-wego w lewo a ltlt b jeżeli a jest nieujemna i wartość a middot 2b mieści się w zakresie liczby tojest to wynikiem operacji W przeciwnym wypadku działanie jest niezdefiniowane1

Dla przesunięcia bitowego w lewo jeżeli lewy argument jest nieujemny to operacja za-chowuje się tak jak w przypadku liczb bez znaku Jeżeli jest on ujemny to zachowanie jestzależne od implementacji

Zazwyczaj operacja przesunięcia w lewo zachowuje się tak samo jak dla liczb bez znakunatomiast przy przesuwaniu w prawo bit znaku nie zmienia się2

a | agtgt1 | agtgt2------+------+------0001 | 0000 | 00000011 | 0001 | 00000101 | 0010 | 00011000 | 1100 | 11101111 | 1111 | 11111001 | 1100 | 1110

Przesunięcie bitowe w lewo odpowiada pomnożeniu natomiast przesunięcie bitowe wprawo podzieleniu liczby przez dwa do potęgi jaką wyznacza prawy argument Jeżeli prawyargument jest ujemny lub większy lub roacutewny liczbie bitoacutew w typie działanie jest niezdefi-niowane

include ltstdiohgt

int main ()

int a = 6printf (6 ltlt 2 = dn altlt2) wypisze 24 printf (6 gtgt 2 = dn agtgt2) wypisze 1 return 0

85 PoroacutewnanieW języku C występują następujące operatory poroacutewnania

roacutewne (==rdquo)

roacuteżne (=rdquo)

mniejsze (ltrdquo)

1Niezdefiniowane w takim samym sensie jak niezdefiniowane jest zachowanie programu gdy proacutebujemy odwo-łać się do wartości wskazywanej przez wartość czy do zmiennych poza tablicą

2ale jeżeli zależy Ci na przenośności kodu nie możesz na tym polegać

85 POROacuteWNANIE 49

większe (gtrdquo)

mniejsze lub roacutewne (lt=rdquo) i

większe lub roacutewne (gt=rdquo)

Wykonują one odpowiednie poroacutewnanie swoich argumentoacutew i zwracają jedynkę jeżeliwarunek jest spełniony lub zero jeżeli nie jest

851 Częste błędy

Osoby ktoacutere poprzednio uczyły się innych językoacutew programowania często mają nawykużywania w instrukcjach logicznych zamiast operatora poroacutewnania == operatora przypi-sania = Ma to często zgubne efekty gdyż przypisanie zwraca wartość przypisaną lewemuargumentowi

Poroacutewnajmy ze sobą dwa warunki

(a = 1)(a == 1)

Pierwszy z nich zawsze będzie prawdziwy niezależnie od wartości zmiennej a Dziejesię tak ponieważ zostaje wykonane przypisanie do a wartości a następnie jako wartość jestzwracane to co zostało przypisane mdash czyli jeden Drugi natomiast będzie prawdziwy tylkogdy a jest roacutewne

W celu uniknięcia takich błędoacutew niektoacuterzy programiści zamiast pisać a == 1 piszą 1 == adzięki czemu pomyłka spowoduje że kompilator zgłosi błąd

Warto zauważyć że kompilator potrafi w pewnych sytuacjach wychwycić taki błądAby zaczął to robić należy podać mu argument -Wparentheses

Innym błędem jest użycie zwykłych operatoroacutew poroacutewnania do sprawdzania relacji po-między liczbami rzeczywistymi Ponieważ operacje zmiennoprzecinkowe wykonywane są zpewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie roacutewne Dlaprzykładu

include ltstdiohgtint main ()

float a b ca = 1e10 tj 10 do potęgi 10 b = 1e-10 tj 10 do potęgi -10 c = b c = b c = c + a c = b + a (teoretycznie) c = c - a c = b + a - a = b (teoretycznie) printf(dn c == b) wypisze 0

Obejściem jest poroacutewnywanie modułu roacuteżnicy liczb Roacutewnież i takie błędy kompilator potrafi wykrywać mdash aby to robił należy podać mu argument -Wfloat-equal

50 ROZDZIAŁ 8 OPERATORY

86 Operatory logiczneAnalogicznie do części operatoroacutew bitowych w C definiuje się operatory logiczne miano-wicie

negację (zaprzeczenie)

koniunkcję (ldquoirdquo) ampamp

alternatywę (ldquolubrdquo) ||

Działają one bardzo podobnie do operatoroacutew bitowych jednak zamiast operować na po-szczegoacutelnych bitach biorą pod uwagę wartość logiczną argumentoacutew

861 ldquoPrawdardquo i ldquofałszrdquo w języku C

Język C nie przewiduje specjalnego typu danych do operacji logicznych mdash operatory logicznemożna stosować do liczb (np typu int) tak samo jak operatory bitowe albo arytmetyczne

Wyrażenie ma wartość logiczną wtedy i tylko wtedy gdy jest roacutewne (jest ldquofałszywerdquo)W przeciwnym wypadku ma wartość (jest ldquoprawdziwerdquo) Operatory logiczne w wynikudają zawsze albo albo

Żeby w pełni uzmysłowić sobie co to to oznacza spoacutejrzmy na wynik wykonania poniż-szych trzech linijek

printf(koniunkcja dn 18 ampamp 19)printf(alternatywa dn a || b)printf(negacja dn 20)

koniunkcja 1alternatywa 1negacja 0

Liczba nie jest roacutewna więc ma wartość logiczną Podobnie ma wartość logiczną Dlatego ich koniunkcja jest roacutewna Znaki a i b zostaną w wyrażeniu logicznympotraktowane jako liczby o wartości odpowiadającej kodowi znaku mdash czyli oba będąmiały wartość logiczną

862 Skroacutecone obliczanie wyrażeń logiczny

Język C wykonuje skroacutecone obliczanie wyrażeń logicznych mdash to znaczy oblicza wyrażenietylko tak długo jak nie wie jaka będzie jego ostateczna wartość To znaczy idzie od lewejdo prawej obliczając kolejne wyrażenia (dodatkowo na kolejność wpływ mają nawiasy) i gdybędzie miał na tyle informacji by obliczyć wartość całości nie liczy reszty Może to wydawaćsię niejasne ale przyjrzyjmy się wyrażeniom logicznym

A ampamp BA || B

Jeśli A jest fałszywe to nie trzeba liczyć B w pierwszym wyrażeniu bo fałsz i dowolne wyra-żenie zawsze da fałsz Analogicznie jeśli A jest prawdziwe to wyrażenie jest prawdziwe iwartość B nie ma znaczenia

Poza zwiększoną szybkością zysk z takiego rozwiązania polega na możliwości stosowaniaefektoacutew ubocznych Idea efektu ubocznego opiera się na tym że w wyrażeniu można wywo-łać funkcje ktoacutere będą robiły poza zwracaniemwyniku inne rzeczy oraz używać podstawieńPopatrzmy na poniższy przykład

87 OPERATOR WYRAŻENIA WARUNKOWEGO 51

( (a gt 0) || (a lt 0) || (a = 1) )

Jeśli a będzie większe od to obliczona zostanie tylko wartość wyrażenia (a gt 0) mdash da onoprawdę czyli reszta obliczeń nie będzie potrzebna Jeśli a będzie mniejsze od zera najpierwzostanie obliczone pierwsze podwyrażenie a następnie drugie ktoacutere da prawdę Ciekawy bę-dzie jednak przypadek gdy a będzie roacutewne zero mdash do a zostanie wtedy podstawiona jedynkai całość wyrażenia zwroacuteci prawdę (bo jest traktowane jak prawda)

Efekty uboczne pozwalają na roacuteżne szaleństwa i wykonywanie złożonych operacji w sa-mych warunkach logicznych jednak przesadne używanie tego typu konstrukcji powodujeże kod staje się nieczytelny i jest uważane za zły styl programistyczny

87 Operator wyrażenia warunkowegoC posiada szczegoacutelny rodzaj operatora mdash to operator zwany też operatorem wyrażeniawarunkowego Jest to jedyny operator w tym języku przyjmujący trzy argumenty

a b c

Jego działanie wygląda następująco najpierw oceniana jest wartość logiczna wyrażenia ajeśli jest ono prawdziwe to zwracana jest wartość b jeśli natomiast wyrażenie a jest nie-prawdziwe zwracana jest wartość c

Praktyczne zastosowanie mdash znajdowanie większej z dwoacutech liczb

a = (bgt=c) b c Jeśli b jest większe bądź roacutewne c to zwroacuteć bW przeciwnym wypadku zwroacuteć c

lub zwracanie modułu liczby

a = a lt 0 -a a

Wartości wyrażeń są przy tym operatorze obliczane tylko jeżeli zachodzi taka potrzebanp w wyrażeniu 1 1 foo() funkcja foo() nie zostanie wywołana

88 Operator przecinekOperator przecinek jest dość dziwnym operatorem Powoduje on obliczanie wartości wyra-żeń od lewej do prawej po czym zwroacutecenie wartości ostatniego wyrażenia W zasadzie wnormalnym kodzie programu ma on niewielkie zastosowanie gdyż zamiast niego lepiej roz-dzielać instrukcje zwykłymi średnikami Ma on jednak zastosowanie w instrukcji sterującejfor

89 Operator sizeofOperator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanegotypu lub typu podanego wyrażenia Ma on dwa rodzaje sizeof(typ) lub sizeof wyrażeniePrzykładowo

include ltstdiohgt

int main()

52 ROZDZIAŁ 8 OPERATORY

printf(sizeof(short ) = dn sizeof(short ))printf(sizeof(int ) = dn sizeof(int ))printf(sizeof(long ) = dn sizeof(long ))printf(sizeof(float ) = dn sizeof(float ))printf(sizeof(double) = dn sizeof(double))return 0

Operator ten jest często wykorzystywany przy dynamicznej alokacji pamięci co zostanieopisane w rozdziale poświęconym wskaźnikom

Pomimo że w swej budowie operator sizeof bardzo przypomina funkcję to jednak niąnie jest Wynika to z trudności w implementacji takowej funkcji mdash jej specyfika musiałabyodnosić się bezpośrednio do kompilatora Ponadto jej argumentem musiałyby być typy anie zmienne W języku C nie jest możliwe przekazywanie typu jako argumentu Ponadtoczęsto zdarza się że rozmiar zmiennej musi być wiadomy jeszcze w czasie kompilacji mdash toewidentnie wyklucza implementację sizeof() jako funkcji

810 Inne operatory

Poza wyżej opisanymi operatorami istnieją jeszcze

operator []rdquo opisany przy okazji opisywania tablic

jednoargumentowe operatory rdquo i amprdquo opisane przy okazji opisywania wskaźnikoacutew

operatory rdquo i -gtrdquo opisywane przy okazji opisywania struktur i unii

operator ()rdquo będący operatorem wywołania funkcji

operator ()rdquo grupujący wyrażenia (np w celu zmiany kolejności obliczania

811 Priorytety i kolejność obliczeń

Jak w matematyce roacutewnież i w języku C obowiązuje pewna ustalona kolejność działań Abymoacutec ją określić należy ustalić dwa parametry danego operatora jego priorytet oraz łącz-ność Przykładowo operator mnożenia ma wyższy priorytet niż operator dodawania i z tegopowodu wwyrażeniu 2+2 middot2 najpierw wykonuje się mnożenie a dopiero potem dodawanie

Drugim parametrem jest łączność mdash określa ona od ktoacuterej stronywykonywane są działaniaw przypadku połączenia operatoroacutew o tym samym priorytecie Na przykład odejmowaniema łączność lewostronną i 2 minus 2 minus 2 da w wyniku - Gdyby miało łączność prawostronnąw wynikiem byłoby Przykładem matematycznego operatora ktoacutery ma łączność prawo-stronną jest potęgowanie np 322

jest roacutewne W języku C występuje dużo poziomoacutew operatoroacutew Poniżej przedstawiamy tabelkę ze

wszystkimi operatorami poczynając od tych z najwyższym priorytetem (wykonywanych napoczątku)

Duża liczba poziomoacutew pozwala czasami zaoszczędzić trochę milisekund w trakcie pisaniaprogramu i bajtoacutew na dysku gdyż często nawiasy nie są potrzebne nie należy jednak z tymprzesadzać gdyż kod programu może stać się mylący nie tylko dla innych ale po latach (czynawet i dniach) roacutewnież dla nas

812 KOLEJNOŚĆ WYLICZANIA ARGUMENTOacuteW OPERATORA 53

Tablica 81 Priorytety operatoroacutewOperator Łącznośćnawiasy nie dotyczyjednoargumentowe przyrostkowe [] -gt wywołanie funkcji postinkre-mentacja postdekrementacja

lewostronna

jednoargumentowe przedrostkowe ˜ + - amp sizeof preinkrementacjapredekrementacja rzutowanie

prawostronna

lewostronna+ - lewostronnaltlt gtgt lewostronnaltlt= gtgt= lewostronna== = lewostronnaamp lewostronnaˆ lewostronna| lewostronnaampamp lewostronna|| lewostronna prawostronnaoperatory przypisania prawostronna lewostronna

Warto także podkreślić że operator koniunkcji ma niższy priorytet niż operator poroacutew-nania3 Oznacza to że kod

if (flags amp FL_MASK == FL_FOO)

zazwyczaj da rezultat inny od oczekiwanego Najpierw bowiem wykona się poroacutewna-nie wartości FL MASK z wartością FL FOO a dopiero potem koniunkcja bitowa W takichsytuacjach należy pamiętać o użyciu nawiasoacutew

if ((flags amp FL_MASK) == FL_FOO)

Kompilator potrafi wykrywać takie błędy i aby to robił należy podać mu argument-Wparentheses

812 Kolejność wyliczania argumentoacutew operatoraW przypadku większości operatoroacutew (wyjątkami są tu ampamp || i przecinek) nie da się określićktoacutera wartość argumentu zostanie obliczona najpierw W większości przypadkoacutew nie mato większego znaczenia lecz w przypadku wyrażeń ktoacutere mają efekty uboczne wymuszeniekonkretnej kolejności może być potrzebne Weźmy dla przykładu program

include ltstdiohgt

int foo(int a) printf(dn a)

3Jest to zaszłość historyczna z czasoacutew gdy nie było logicznych operatoroacutew ampamp oraz || i zamiast nich stosowanooperatory bitowe amp oraz |

54 ROZDZIAŁ 8 OPERATORY

return 0

int main(void) return foo(1) + foo(2)

Otoacuteż nie wiemy czy najpierw zostanie wywołana funkcja foo z parametrem jeden czydwa Jeżeli ma to znaczenie należy użyć zmiennych pomocniczych zmieniając definicję funk-cji main na

int main(void) int tmp = foo(1)return tmp + foo(2)

Teraz już na pewno najpierw zostanie wypisana jedynka a potem dopiero dwoacutejka Sy-tuacja jeszcze bardziej się komplikuje gdy używamy wyrażeń z efektami ubocznymi jakoargumentoacutew funkcji np

include ltstdiohgt

int foo(int a) printf(dn a)return 0

int bar(int a int b int c int d) return a + b + c + d

int main(void) return foo(1) + foo(2) + foo(3) + foo(4)

Teraz też nie wiemy ktoacutera z permutacji liczb i zostanie wypisana i ponownienależy pomoacutec sobie zmiennymi tymczasowymi jeżeli zależy nam na konkretnej kolejności

int main(void) int tmp = foo(1)tmp += foo(2)tmp += foo(3)return tmp + foo(4)

813 Uwagi

W języku C++ wprowadzony został dodatkowo inny sposoacuteb zapisu rzutowania ktoacuterypozwala na łatwiejsze znalezienie w kodzie miejsc w ktoacuterych dokonujemy rzutowaniaWięcej na stronie C++Zmienne

814 ZOBACZ TEŻ 55

814 Zobacz też CSkładniaOperatory

56 ROZDZIAŁ 8 OPERATORY

Rozdział 9

Instrukcje sterujące

C jest językiem imperatywnym mdash oznacza to że instrukcje wykonują się jedna po drugiej wtakiej kolejności w jakiej są napisane Aby moacutec zmienić kolejność wykonywania instrukcjipotrzebne są instrukcje sterujące

Na wstępie przypomnijmy jeszcze informację z rozdziału Operatory że wyrażenie jestprawdziwe wtedy i tylko wtedy gdy jest roacuteżne od zera a fałszywe wtedy i tylko wtedy gdyjest roacutewne zeru

91 Instrukcje warunkowe

911 Instrukcja if

Użycie instrukcji if wygląda tak

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

dalsze instrukcje

Istnieje także możliwość reakcji na nieprawdziwość wyrażenia mdash wtedy należy zastosowaćsłowo kluczowe else

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

else blok wykonany jeśli wyrażenie jest nieprawdziwe

dalsze instrukcje

Przypatrzmy się bardziej ldquożyciowemurdquo programowi ktoacutery poroacutewnuje ze sobą dwie liczby

include ltstdiohgt

int main ()

int a ba = 4

57

58 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

b = 6if (a==b)

printf (a jest roacutewne bn) else

printf (a nie jest roacutewne bn)return 0

Stosowany jest też kroacutetszy zapis warunkoacutew logicznych korzystający z tego jak C rozumieprawdę i fałsz Jeśli zmienna a jest typu integer zamiast

if (a = 0) b = 1a

można napisać

if (a) b = 1a

a zamiast

if (a == 0) b = 1a

można napisać

if (a) b = 1a

Czasami zamiast pisać instrukcję if możemy użyć operatora wyrażenia warunkowego(patrz Operatory)

if (a = 0)b = 1a

elseb = 0

ma dokładnie taki sam efekt jak

b = (a =0) 1a 0

912 Instrukcja swit

Aby ograniczyćwielokrotne stosowanie instrukcji if możemy użyć swit Jej użyciewyglądatak

switch (wyrażenie) case wartość1 instrukcje jeśli wyrażenie == wartość1

breakcase wartość2 instrukcje jeśli wyrażenie == wartość2

break default instrukcje jeśli żaden z wcześniejszych warunkoacutew

break nie został spełniony

Należy pamiętać o użyciu break po zakończeniu listy instrukcji następujących po case Je-śli tego nie zrobimy program przejdzie do wykonywania instrukcji z następnego case Możemieć to fatalne skutki

91 INSTRUKCJE WARUNKOWE 59

include ltstdiohgt

int main ()

int a bprintf (Podaj a )scanf (d ampa)printf (Podaj b )scanf (d ampb)switch (b)

case 0 printf (Nie można dzielić przez 0n) tutaj zabrakło break default printf (ab=dn ab)

return 0

A czasami może być celowym zabiegiem (tzw ldquofall-throughrdquo) mdash woacutewczas warto zazna-czyć to w komentarzu Oto przykład

include ltstdiohgt

int main ()

int a = 4switch ((a3))

case 0printf (Liczba d dzieli się przez 3n a)break

case -2case -1case 1case 2printf (Liczba d nie dzieli się przez 3n a)break

return 0

Przeanalizujmy teraz działający przykład

include ltstdiohgt

int main ()

unsigned int dzieci = 3 podatek=1000switch (dzieci)

case 0 break brak dzieci - czyli brak ulgi case 1 ulga 2

podatek = podatek - (podatek100 2)break

case 2 ulga 5

60 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

podatek = podatek - (podatek100 5)break

default ulga 10 podatek = podatek - (podatek10010)break

printf (Do zapłaty dn podatek)

92 Pętle

921 Instrukcja while

Często zdarza się że nasz programmusi wielokrotnie powtarzać ten sam ciąg instrukcji Abynie przepisywać wiele razy tego samego kodu można skorzystać z tzw pętli Pętla wykonujesię dotąd dopoacuteki prawdziwy jest warunek

while (warunek) instrukcje do wykonania w pętli

dalsze instrukcje

Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie Załoacuteżmy żemamy obliczyć kwadraty liczb od do Piszemy zatem program

include ltstdiohgt

int main ()

int a = 1while (a lt= 10) dopoacuteki a nie przekracza 10

printf (dn aa) wypisz aa na ekran++a zwiększamy a o jeden

return 0

Po analizie kodu mogą nasunąć się dwa pytania

Po co zwiększać wartość a o jeden Otoacuteż gdybyśmy nie dodali instrukcji zwiększająceja to warunek zawsze byłby spełniony a pętla ldquokręciłabyrdquo się w nieskończoność

Dlaczego warunek to ldquoa lt= rdquo a nie ldquoa=rdquo Odpowiedź jest dość prosta Pętlasprawdza warunek przed wykonaniem kolejnego ldquoobroturdquo Dlatego też gdyby waru-nek brzmiał ldquoa=rdquo to dla a= jest on nieprawdziwy i pętla nie wykonałaby ostatniejiteracji przez co program generowałby kwadraty liczb od do a nie do

922 Instrukcja for

Od instrukcji while czasami wygodniejsza jest instrukcja for Umożliwia ona wpisanie usta-wiania zmiennej sprawdzania warunku i inkrementowania zmiennej w jednej linijce co czę-sto zwiększa czytelność kodu Instrukcję for stosuje się w następujący sposoacuteb

92 PĘTLE 61

for (wyrażenie1 wyrażenie2 wyrażenie3) instrukcje do wykonania w pętli

dalsze instrukcje

Jak widać pętla for znacznie roacuteżni się od tego typu pętli znanych w innych językachprogramowania Opiszemy więc co oznaczają poszczegoacutelne wyrażenia

wyrażenie mdash jest to instrukcja ktoacutera będzie wykonana przed pierwszym przebiegiempętli Zwykle jest to inicjalizacja zmiennej ktoacutera będzie służyła jako ldquolicznikrdquo przebie-goacutew pętli

wyrażenie mdash jest warunkiem zakończenia pętli Pętla wykonuje się tak długo jakprawdziwy jest ten warunek

wyrażenie mdash jest to instrukcja ktoacutera wykonywana będzie po każdym przejściu pętliZamieszczone są tu instrukcje ktoacutere zwiększają licznik o odpowiednią wartość

Jeżeli wewnątrz pętli nie ma żadnych instrukcji continue (opisanych niżej) to jest onaroacutewnoważna z

wyrażenie1while (wyrażenie2)

instrukcje do wykonania w pętli wyrażenie3

dalsze instrukcje

Ważną rzeczą jest tutaj to żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla forPoczątkującym programistom nieznajomość tego faktu sprawia wiele problemoacutew

W pierwszej kolejności w pętli for wykonuje się wyrażenie1 Wykonuje się ono zawszenawet jeżeli warunek przebiegu pętli jest od samego początku fałszywy Po wykonaniuwyrażenie1 pętla for sprawdza warunek zawarty w wyrażenie2 jeżeli jest on prawdziwyto wykonywana jest treść pętli for czyli najczęściej to co znajduje się między klamrami lubgdy ich nie ma następna pojedyncza instrukcja W szczegoacutelności musimy pamiętać że samśrednik też jest instrukcją mdash instrukcją pustą Gdy już zostanie wykonana treść pętli for na-stępuje wykonanie wyrażenie3 Należy zapamiętać że wyrażenie zostanie wykonane nawetjeżeli był to już ostatni obieg pętli Poniższe przykłady pętli for w rezultacie dadzą ten samwynik Wypiszą na ekran liczby od do

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 printf(d i++ ) )

Dwa pierwsze przykłady korzystają z własności struktury blokowej kolejny przykład jestjuż bardziej wyrafinowany i korzysta z tego że jako wyrażenie3może zostać podane dowolnebardziej skomplikowane wyrażenie zawierające w sobie inne podwyrażenia A oto kolejnyprogram ktoacutery najpierw wyświetla liczby w kolejności rosnącej a następnie wraca

62 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

include ltstdiohgtint main()int ifor(i=1 ilt=5 ++i)

printf(d i)

for( igt=1 i--)printf(d i)

return 0

Po analizie powyższego kodu początkujący programista może stwierdzić że pętla wy-pisze 123454321 Stanie się natomiast inaczej Wynikiem działania powyższego programubędzie ciąg cyfr 12345654321 Pierwsza pętla wypisze cyfry ldquordquo lecz po ostatnim swoimobiegu pętla for (tak jak zwykle) zinkrementuje zmienną i Gdy druga pętla przystąpi dopracy zacznie ona odliczać począwszy od liczby i= a nie By spowodować wyświetlanieliczb od do i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli fora pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej i o

Niech podsumowaniem będzie jakiś działający fragment kodu ktoacutery może obliczać war-tości kwadratoacutew liczb od do

include ltstdiohgt

int main ()

int afor (a=1 alt=10 ++a)

printf (dn aa)return 0

W kodzie źroacutedłowym spotyka się często inkrementację i++ Jest to zły zwyczaj biorącysię z wzorowania się na nazwie języka C++ Post-inkrementacja i++ powoduje że tworzonyjest obiekt tymczasowy ktoacutery jest zwracany jako wynik operacji (choć wynik ten nie jestnigdzie czytany) Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie ale wpętli ldquoforrdquo takie kopiowanie odbywa się po każdym przebiegu pętli Dodatkowo w C++ po-dobną konstrukcję stosuje się do obiektoacutew mdash kopiowanie obiektu może być już czasochłonnączynnością Dlatego w pętli ldquoforrdquo należy stosować wyłącznie ++i

923 Instrukcja dowhile

Pętle while i for mają jeden zasadniczy mankament mdash może się zdarzyć że nie wykonają sięani razu Aby mieć pewność że nasza pętla będzie miała co najmniej jeden przebieg musimyzastosować pętlę do while Wygląda ona następująco

92 PĘTLE 63

do instrukcje do wykonania w pętli

while (warunek) dalsze instrukcje

Zasadniczą roacuteżnicą pętli do while jest fakt iż sprawdza ona warunek pod koniec swojegoprzebiegu To właśnie ta cecha decyduje o tym że pętla wykona się co najmniej raz A terazprzykład działającego kodu ktoacutery tym razem będzie obliczał trzecią potęgę liczb od do

include ltstdiohgt

int main ()

int a = 1do

printf (dn aaa)++a

while (a lt= 10)return 0

Może się to wydać zaskakujące ale roacutewnież przy tej pętli zamiast bloku instrukcji możnazastosować pojedynczą instrukcję np

include ltstdiohgt

int main ()

int a = 1do printf (dn aaa) while (++a lt= 10)return 0

924 Instrukcja break

Instrukcja break pozwala na opuszczenie wykonywania pętli w dowolnym momencie Przy-kład użycia

int afor (a=1 a = 9 ++a)

if (a == 5) breakprintf (dn a)

Program wykona tylko przebiegi pętli gdyż przy przebiegu instrukcja break spowo-duje wyjście z pętli

Break i pętle nieskończone

W przypadku pętli for nie trzeba podawać warunku W takim przypadku kompilator przyj-mie że warunek jest stale spełniony Oznacza to że poniższe pętle są roacutewnoważne

64 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

for () for (1) for (aaa) gdzie a jest dowolną liczba rzeczywistą roacuteżną od 0while (1) do while (1)

Takie pętle nazywamy pętlami nieskończonymi ktoacutere przerwać może jedynie instrukcjabreak1(z racji tego że warunek pętli zawsze jest prawdziwy) 2

Wszystkie fragmenty kodu działają identycznie

int i = 0for (i=5++i)

kod

int i = 0for (++i)

if (i == 5) break

int i = 0for ()

if (i == 5) break++i

925 Instrukcja continue

W przeciwieństwie do break ktoacutera przerywa wykonywanie pętli instrukcja continue powo-duje przejście do następnej iteracji o ile tylko warunek pętli jest spełniony Przykład

int ifor (i = 0 i lt 100 ++i)

printf (Poczatekn)if (i gt 40) continue printf (Koniecn)

Dla wartości i większej od nie będzie wyświetlany komunikat ldquoKoniecrdquo Pętla wykonapełne przejść

Oto praktyczny przykład użycia tej instrukcji

include ltstdiohgtint main()

int i

1Tak naprawdę podobną operacje możemy wykonać za pomocą polecenia goto W praktyce jednak stosujesię zasadę że break stosuje się do przerwania działania pętli i wyjścia z niej goto stosuje się natomiast wtedykiedy chce się wydostać się z kilku zagnieżdżonych pętli za jednym zamachem Do przerwania pracy pętli mogąnam jeszcze posłużyć polecenia exit() lub return ale woacutewczas zakończymy nie tylko działanie pętli ale i całegoprogramufunkcji

2Żartobliwie można powiedzieć że stosując pętlę nieskończoną to najlepiej korzystać z pętli for() gdyżwymaga ona napisania najmniejszej liczby znakoacutew w poroacutewnaniu do innych konstrukcji

93 INSTRUKCJA GOTO 65

for (i = 1 i lt= 50 ++i) if (i4==0) continue printf (d i)

return 0

Powyższy program generuje liczby z zakresu od do ktoacutere nie są podzielne przez

93 Instrukcja goto

Istnieje także instrukcja ktoacutera dokonuje skoku do dowolnegomiejsca programu oznaczonegotzw etykietą

etykieta instrukcje goto etykieta

Uwaga kompilator w wersji i wyższych jest bardzo uczulony na etykiety za-mieszczone przed nawiasem klamrowym zamykającym blok instrukcji Innymi słowy nie-dopuszczalne jest umieszczanie etykiety zaraz przed klamrą ktoacutera kończy blok instrukcjizawartych np w pętli for Można natomiast stosować etykietę przed klamrą kończącą danąfunkcję

Instrukcja goto łamie sekwencję instrukcji i powoduje skok do dowolnie odległego miej-sca w programie - co może mieć nieprzewidziane skutki Zbyt częste używanie goto możeprowadzić do trudnych do zlokalizowania błędoacutew Oproacutecz tego kompilatory mają kłopotyz optymalizacją kodu w ktoacuterym występują skoki Z tego powodu zaleca się ograniczeniezastosowania tej instrukcji wyłącznie do opuszczania wielokrotnie zagnieżdżonych pętli

Przykład uzasadnionego użycia

int ijfor (i = 0 i lt 10 ++i)

for (j = i j lt i+10 ++j) if (i + j 21 == 0) goto koniec

koniec dalsza czesc programu

94 Natymiastowe kończenie programu mdash funkcja exit

Program może zostać w każdej chwili zakończony mdash do tego właśnie celu służy funkcja exitUżywamy jej następująco

exit (kod_wyjścia)

66 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

Liczba całkowita kod wyjścia jest przekazywana do procesu macierzystego dzięki czemudostaje on informację czy programw ktoacuterymwywołaliśmy tą funkcję zakończył się popraw-nie lub czy się tak nie stało Kody wyjścia są nieustandaryzowane i żeby program był w pełniprzenośny należy stosować makra EXIT SUCCESS i EXIT FAILURE choć na wielu systemach kod oznacza poprawne zakończenie a kod roacuteżny od błędne W każdym przypadku jeżeli naszprogram potrafi generować wiele roacuteżnych kodoacutew warto je wszystkie udokumentować w ewdokumentacji Są one też czasem pomocne przy wykrywaniu błędoacutew

95 Uwagi W języku C++ można deklarować zmienne w nagłoacutewku pętli ldquoforrdquo w następujący spo-

soacuteb for(int i=0 ilt10 ++i) (więcej informacji w C++Zmienne)

Rozdział 10

Podstawowe procedury wejścia iwyjścia

101 Wejściewyjście

Komputer byłby całkowicie bezużyteczny gdyby użytkownik nie moacutegł się z nim porozumieć(tj wprowadzić danych lub otrzymać wynikoacutew pracy programu) Programy komputerowesłużą w największym uproszczeniu do obroacutebki danych mdash więc muszą te dane jakoś od nasotrzymać przetworzyć i przekazać nam wynik

Takiewczytywanie i ldquowyrzucanierdquo danychw terminologii komputerowej nazywamywej-ściem (input) iwyjściem (output) Bardzo często moacutewi się o wejściu i wyjściu danych łączniemdash inputoutput albo po prostu IO

W C do komunikacji z użytkownikiem służą odpowiednie funkcje Zresztą do wielu za-dań w C służą funkcje Używając funkcji nie musimy wiedzieć w jaki sposoacuteb komputerwykonuje jakieś zadanie interesuje nas tylko to co ta funkcja robi Funkcje niejako ldquowyko-nują za nas część pracyrdquo ponieważ nie musimy pisać być może dziesiątek linijek kodu żebynp wypisać tekst na ekranie (wbrew pozorom mdash kod funkcji wyświetlającej tekst na ekraniejest dość skomplikowany) Jeszcze taka uwaga mdash gdy piszemy o jakiejś funkcji zazwyczajpodając jej nazwę dopisujemy na końcu nawias

printf()scanf()

żeby było jasne że chodzi o funkcję a nie o coś innego

Wyżej wymienione funkcje to jedne z najczęściej używanych funkcji w C mdash pierwszasłuży do wypisywania danych na ekran natomiast druga do wczytywania danych z klawia-tury1

1W zasadzie standard C nie definiuje czegoś takiego jak ekran i klawiatura mdash mowa w nim o standardowymwyjściu i standardowym wejściu Zazwyczaj jest to właśnie ekran i klawiatura ale nie zawsze W szczegoacutelności użyt-kownicy Linuksa lub innych systemoacutew uniksowych mogą być przyzwyczajeniu do przekierowania wejściawyjściazdo pliku czy łączenie komend w potoki (ang pipe) W takich sytuacjach dane nie są wyświetlane na ekranie aniodczytywane z klawiatury

67

68 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

102 Funkcje wyjścia

1021 Funkcja printf

W przykładzie ldquoHello Worldrdquo użyliśmy już jednej z dostępnych funkcji wyjścia a miano-wicie funkcji printf() Z punktu widzenia swoich możliwości jest to jedna z bardziej skom-plikowanych funkcji a jednocześnie jest jedną z najczęściej używanych Przyjrzyjmy sięponownie kodowi programu ldquoHello Worldrdquo

include ltstdiohgt

int main(void)

printf(Hello worldn)return 0

Po skompilowaniu i uruchomieniu program wypisze na ekranie

Hello world

W naszym przykładowym programie chcąc by funkcja printf() wypisała tekst na ekra-nie umieściliśmy go w cudzysłowach wewnątrz nawiasoacutew Ogoacutelnie wywołanie funkcjiprintf() wygląda następująco

printf(format argument1 argument2 )

Przykładowo

int i = 500printf(Liczbami całkowitymi są na przykład i oraz in 1 i)

wypisze

Liczbami całkowitymi są na przykład 1 oraz 500

Format to napis ujęty w cudzysłowy ktoacutery określa ogoacutelny kształt schemat tego co ma byćwyświetlone Format jest drukowany tak jak go napiszemy jednak niektoacutere znaki specjalnezostaną w nim podmienione na co innego Przykładowo znak specjalny n jest zamienianyna znak nowej linii 2 Natomiast procent jest podmieniany na jeden z argumentoacutew Po pro-cencie następuje specyfikacja jak wyświetlić dany argument W tym przykładzie i (od int)oznacza że argument ma być wyświetlony jak liczba całkowita W związku z tym że i mają specjalne znaczenie aby wydrukować je należy użyć ich podwoacutejnie

printf(Procent Backslash )

drukuje

Procent Backslash

(bez przejścia do nowej linii) Na liście argumentoacutew możemy mieszać ze sobą zmienne roacuteż-nych typoacutew liczby napisy itp w dowolnej liczbie Funkcja printf przyjmie ich tyle ile tylkonapiszemy Należy uważać by nie pomylić się w formatowaniu

2Zmiana ta następuje w momencie kompilacji programu i dotyczy wszystkich literałoacutew napisowych Nie jestto jakaś szczegoacutelna własność funkcji printf() Więcej o tego typu sekwencjach i ciągach znakoacutew w szczegoacutelnościopisane jest w rozdziale Napisy

103 FUNKCJA PUTS 69

int i = 5printf(i s i 5 4 napis) powinno być i i s

Przywłączeniu ostrzeżeń (opcja -Wall lub -WformatwGCC) kompilator powinien nas ostrzecgdy format nie odpowiada podanym elementom

Najczęstsze użycie printf()

printf(i i) gdy i jest typu int zamiast i można użyć d

printf(f i) gdy i jest typu float lub double

printf(c i) gdy i jest typu char (i chcemy wydrukować znak)

printf(s i) gdy i jest napisem (typu char)

Funkcja printf() nie jest żadną specjalną konstrukcją języka i łańcuch formatujący możebyć podany jako zmienna W związku z tym możliwa jest np taka konstrukcja

include ltstdiohgt

int main(void)

char buf[100]scanf(99s buf) funkcja wczytuje tekst do tablicy buf printf(buf)return 0

Program wczytuje tekst a następnie wypisuje go Jednak ponieważ znak procentu jesttraktowany w specjalny sposoacuteb toteż jeżeli na wejściu pojawi się ciąg znakoacutew zawierającyten znak mogą się stać roacuteżne dziwne rzeczy Między innymi z tego powodu w takich sytu-acjach lepiej używać funkcji puts() lub fputs() opisanych niżej lub wywołania printf(szmienna)

Więcej o funkcji printf()

103 Funkcja putsFunkcja puts() przyjmuje jako swoacutej argument ciąg znakoacutew ktoacutery następnie bezmyślnie wy-pisuje na ekran kończąc go znakiem przejścia do nowej linii W ten sposoacuteb nasz pierwszyprogram moglibyśmy napisać w ten sposoacuteb

include ltstdiohgt

int main(void)

puts(Hello world)return 0

W swoim działaniu funkcja ta jest w zasadzie identyczna do wywołania printf(snargument) jednak prawdopodobnie będzie działać szybciej Jedynym jejmankamentemmożebyć fakt że zawsze na końcu podawany jest znak przejścia do nowej linii Jeżeli jest to efektniepożądany (nie zawsze tak jest) należy skorzystać z funkcji fputs() opisanej niżej lub wy-wołania printf(s argument)

Więcej o funkcji puts()

70 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

104 Funkcja fputs

Opisując funkcję fputs() wybiegamy już trochę w przyszłość (a konkretnie do opisu operacjina plikach) ale warto o niej wspomnieć już teraz gdyż umożliwia ona wypisanie swojegoargumentu bez wypisania na końcu znaku przejścia do nowej linii

include ltstdiohgt

int main(void)

fputs(Hello worldn stdout)return 0

W chwili obecnej możesz się nie przejmować tym zagadkowym stdout wpisanym jakodrugi argument funkcji Jest to określenie strumienia wyjściowego (w naszymwypadku stan-dardowe wyjście mdash standard output)

Więcej o funkcji fputs()

1041 Funkcja putar

Funkcja putchar() służy do wypisywania pojedynczych znakoacutew Przykładowo jeżeli chcieli-byśmy napisać programwypisującyw prostej tabelce wszystkie liczby od do moglibyśmyto zrobić tak

include ltstdiohgt

int main(void)

int i = 0for ( ilt100 ++i) Nie jest to pierwsza liczba w wierszu if (i 10)

putchar( )printf(2d i) Jest to ostatnia liczba w wierszu if ((i 10)==9)

putchar(n)

return 0

Więcej o funkcji putchar()

105 FUNKCJE WEJŚCIA 71

105 Funkcje wejścia

1051 Funkcja scanf()

Teraz pomyślmy o sytuacji odwrotnej Tym razem to użytkownik musi powiedzieć coś pro-gramowi W poniższym przykładzie program podaje kwadrat liczby podanej przez użytkow-nika

include ltstdiohgt

int main ()

int liczba = 0printf (Podaj liczbę )scanf (d ampliczba)printf (dd=dn liczba liczba liczbaliczba)return 0

Zauważyłeś że w tej funkcji przy zmiennej pojawił się nowy operator mdash amp (etka) Jeston ważny gdyż bez niego funkcja scanf() nie skopiuje odczytanej wartości liczby do odpo-wiedniej zmiennej Właściwie oznacza przekazanie do funkcji adresu zmiennej by funkcjamogła zmienić jej wartość Nie musisz teraz rozumieć jak to się odbywa wszystko zostaniewyjaśnione w rozdziale Wskaźniki

Oznaczenia są podobne takie jak przy printf() czyli scanf(i ampliczba) wczytuje liczbętypu int scanf(f ampliczba) ndash liczbę typu float a scanf(s tablica znakoacutew) ciąg zna-koacutew Ale czemu w tym ostatnim przypadku nie ma etki Otoacuteż gdy podajemy jako argumentdo funkcji wyrażenie typu tablicowego zamieniane jest ono automatycznie na adres pierw-szego elementu tablicy Będzie to dokładniej opisane w rozdziale poświęconym wskaźnikom

Brak etki jest częstym błędem szczegoacutelnie wśroacuted początkujących programistoacutew Ponie-waż funkcja scanf() akceptuje zmienną liczbę argumentoacutew to nawet kompilator może miećkłopoty z wychwyceniem takich błędoacutew (konkretnie chodzi o to że standard nie wymagaod kompilatora wykrywania takich pomyłek) choć kompilator GCC radzi sobie z tym jeżelipodamy mu argument -Wformat

Należy jednak uważać na to ostatnie użycie Rozważmy na przykład poniższy kod

include ltstdiohgt

int main(void)

char tablica[100] 1 scanf(s tablica) 2 return 0

Robi on niewiele W linijce deklarujemy tablicę znakoacutew czyli mogącą przechowaćnapis długości znakoacutew Nie przejmuj się jeżeli nie do końca to wszystko rozumiesz mdash po-jęcia takie jak tablica czy ciąg znakoacutew staną się dla Ciebie jasne w miarę czytania kolejnych

72 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

rozdziałoacutew W linijce wywołujemy funkcję scanf() ktoacutera odczytuje tekst ze standardowegowejścia Nie zna ona jednak rozmiaru tablicy i nie wie ile znakoacutewmoże ona przechować przezco będzie czytać tyle znakoacutew aż napotka biały znak (format s nakazuje czytanie pojedyn-czego słowa) co może doprowadzić do przepełnienia bufora Niebezpieczne skutki czegośtakiego opisane są w rozdziale poświęconym napisom Na chwilę obecną musisz zapamiętaćżeby zaraz po znaku procentu podawać maksymalną liczbę znakoacutew ktoacutere może przechowaćbufor czyli liczbę o jeden mniejszą niż rozmiar tablicy Bezpieczna wersją powyższego kodujest

include ltstdiohgt

int main(void)

char tablica[100]scanf(99s tablica)return 0

Funkcja scanf() zwraca liczbę poprawnie wczytanych zmiennych lub EOF jeżeli nie majuż danych w strumieniu lub nastąpił błąd Załoacuteżmy dla przykładu że chcemy stworzyćprogram ktoacutery odczytuje po kolei liczby i wypisuje ich potęgi W pewnym momenciedane się kończą lub jest wprowadzana niepoprawna dana i woacutewczas nasz program powinienzakończyć działanie Aby to zrobić należy sprawdzać wartość zwracaną przez funkcję scanf()w warunku pętli

include ltstdiohgt

int main(void)

int nwhile (scanf(d ampn)==1)printf(dn nnn)

return 0

Podobnie możemy napisać program ktoacutery wczytuje po dwie liczby i je sumuje

include ltstdiohgt

int main(void)

int a bwhile (scanf(d d ampa ampb)==2)printf(dn a+b)

return 0

105 FUNKCJE WEJŚCIA 73

Rozpatrzmy teraz trochę bardziej skomplikowany przykład Otoacuteż ponownie jak poprzed-nio nasz program będzie wypisywał potęgę podanej liczby ale tym razem musi ignorowaćbłędne dane (tzn pomijać ciągi znakoacutew ktoacutere nie są liczbami) i kończyć działanie tylko wmomencie gdy nastąpi błąd odczytu lub koniec pliku3

include ltstdiohgt

int main(void)

int result ndoresult = scanf(d ampn)if (result==1)

printf(dn nnn)else if (result) result to to samo co result==0

result = scanf(s)

while (result=EOF)return 0

Zastanoacutewmy się przez chwilę co się dzieje w programie Najpierw wywoływana jestfunkcja scanf() i następuje proacuteba odczytu liczby typu int Jeżeli funkcja zwroacuteciła to liczbazostała poprawnie odczytana i następuje wypisanie jej trzeciej potęgi Jeżeli funkcja zwroacuteciła to na wejściu były jakieś dane ktoacutere nie wyglądały jak liczba W tej sytuacji wywołujemyfunkcję scanf() z formatem odczytującym dowolny ciąg znakoacutew nie będący białymi znakamiz jednoczesnym określeniem żeby nie zapisywała nigdzie wyniku W ten sposoacuteb niepopraw-nie wpisana dana jest omijana Pętla głoacutewna wykonuje się tak długo jak długo funkcja scanf()nie zwroacuteci wartości EOF

Więcej o funkcji scanf()

1052 Funkcja gets

Funkcja gets służy do wczytania pojedynczej linii Może Ci się to wydać dziwne ale funkcjitej nie należy używać pod żadnym pozorem Przyjmuje ona jeden argument mdash adres pierw-szego elementu tablicy do ktoacuterego należy zapisać odczytaną linię mdash i nic poza tym Z tegopowodu nie ma żadnej możliwości przekazania do tej funkcji rozmiaru bufora podanego jakoargument Podobnie jak w przypadku scanf() może to doprowadzić do przepełnienia buforaco może mieć tragiczne skutki Zamiast tej funkcji należy używać funkcji fgets()

Więcej o funkcji gets()

1053 Funkcja fgets

Funkcja fgets() jest bezpieczną wersją funkcji gets() ktoacutera dodatkowo może operować nadowolnych strumieniach wejściowych Jej użycie jest następujące

3Jak rozroacuteżniać te dwa zdarzenia dowiesz się w rozdziale Czytanie i pisanie do plikoacutew

74 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

fgets(tablica_znakoacutew rozmiar_tablicy_znakoacutew stdin)

Na chwilę obecną nie musisz się przejmować ostatnim argumentem (jest to określeniestrumienia w naszym przypadku standardowewejście mdash standard input) Funkcja czyta tekstaż do napotkania znaku przejścia do nowej linii ktoacutery także zapisuje w wynikowej tablicy(funkcja gets() tego nie robi) Jeżeli brakuje miejsca w tablicy to funkcja przerywa czytanie wten sposoacuteb aby sprawdzić czy została wczytana cała linia czy tylko jej część należy sprawdzićczy ostatnim znakiem nie jest znak przejścia do nowej linii Jeżeli nastąpił jakiś błąd lub nawejściu nie ma już danych funkcja zwraca wartość NULL

include ltstdiohgt

int main(void) char buffer[128] whole_line = 1 chwhile (fgets(buffer sizeof buffer stdin)) 1

if (whole_line) 2 putchar(gt)if (buffer[0]=gt)

putchar( )

fputs(buffer stdout) 3 for (ch = buffer ch ampamp ch=n ++ch) 4 whole_line = ch == n

if (whole_line)

putchar(n)return 0

Powyższy kod wczytuje dane ze standardowego wejścia mdash linia po linii mdash i dodaje napoczątku każdej linii znak większości po ktoacuterym dodaje spację jeżeli pierwszym znakiem nalinii nie jest znak większości W linijce następuje odczytywanie linii Jeżeli nie ma już wię-cej danych lub nastąpił błąd wejścia funkcja zwraca wartość NULL ktoacutera ma logiczną war-tość i woacutewczas pętla kończy działanie W przeciwnym wypadku funkcja zwraca po prostupierwszy argument ktoacutery ma wartość logiczną W linijce sprawdzamy czy poprzedniewywołanie funkcji wczytało całą linię czy tylko jej część mdash jeżeli całą to teraz jesteśmy napoczątku linii i należy dodać znakwiększości W linii najzwyczajniej w świecie wypisujemylinię W linii przeszukujemy tablicę znak po znaku aż do momentu gdy znajdziemy znako kodzie kończącym ciąg znakoacutew albo znak przejścia do nowej linii Ten drugi przypadekoznacza że funkcja fgets() wczytała całą linię

Więcej o funkcji fgets()

1054 Funkcja getar()

Jest to bardzo prosta funkcja wczytująca znak z klawiatury W wielu przypadkach danemogą być buforowane przez co wysyłane są do programu dopiero gdy bufor zostaje przepeł-niony lub na wejściu jest znak przejścia do nowej linii Z tego powodu po wpisaniu danegoznaku należy nacisnąć klawisz enter aczkolwiek trzeba pamiętać że w następnym wywoła-niu zostanie zwroacutecony znak przejścia do nowej linii Gdy nastąpił błąd lub nie ma już więcej

105 FUNKCJE WEJŚCIA 75

danych funkcja zwraca wartość EOF (ktoacutera ma jednak wartość logiczną toteż zwykła pętlawhile (getchar()) nie da oczekiwanego rezultatu)

include ltstdiohgt

int main(void)

int cwhile ((c = getchar())=EOF)

if (c== ) c = _

putchar(c)

return 0

Ten prosty program wczytuje dane znak po znaku i zamienia wszystkie spacje na znakipodkreślenia Może wydać się dziwne że zmienną c zdefiniowaliśmy jako trzymającą typ inta nie char Właśnie taki typ (tj int) zwraca funkcja getchar() i jest to konieczne ponieważwartość EOF wykracza poza zakres wartości typu char (gdyby tak nie było to nie byłobymożliwości rozroacuteżnienia wartości EOF od poprawnie wczytanego znaku) Więcej o funkcjigetchar()

76 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

Rozdział 11

Funkcje

W matematyce pod pojęciem funkcji rozumiemy twoacuter ktoacutery pobiera pewną liczbę argumen-toacutew i zwraca wynik1 Jeśli dla przykładu weźmiemy funkcję sin(x) to x będzie zmiennąrzeczywistą ktoacutera określa kąt a w rezultacie otrzymamy inną liczbę rzeczywistą mdash sinustego kąta

W C funkcja (czasami nazywana podprogramem rzadziej procedurą) to wydzielona częśćprogramu ktoacutera przetwarza argumenty i ewentualnie zwraca wartość ktoacutera następnie możebyć wykorzystana jako argument w innych działaniach lub funkcjach Funkcja może posia-dać własne zmienne lokalne W odroacuteżnieniu od funkcji matematycznych funkcje w C mogązwracać dla tych samych argumentoacutew roacuteżne wartości

Po lekturze poprzednich części podręcznika zapewne moacutegłbyś podać kilka przykładoacutewfunkcji z ktoacuterych korzystałeś Były to np

funkcja printf() drukująca tekst na ekranie czy

funkcja main() czyli głoacutewna funkcja programu

Głoacutewną motywacją tworzenia funkcji jest unikanie powtarzania kilka razy tego samegokodu W poniższym fragmencie

for(i=1 i lt= 5 ++i) printf(d ii)

for(i=1 i lt= 5 ++i)

printf(d iii)for(i=1 i lt= 5 ++i)

printf(d ii)

widzimy że pierwsza i trzecia pętla for są takie same Zamiast kopiować fragment kodukilka razy (co jest mało wygodne i może powodować błędy) lepszym rozwiązaniem mogłobybyć wydzielenie tego fragmentu tak by można go było wywoływać kilka razy Tak właśniedziałają funkcje

Innym niemniej ważnym powodem używania funkcji jest rozbicie programu na frag-menty wg ich funkcjonalności Oznacza to że z jeden duży program dzieli się na mniejsze

1Aby nie urażać matematykoacutew sprecyzujmy że chodzi o relację między zbiorami X i Y (X jest dziedziną Y jestprzeciwdziedziną) takie że każdy element zbioru X jest w relacji z dokładnie jednym elementem ze zbioru Y

77

78 ROZDZIAŁ 11 FUNKCJE

funkcje ktoacutere są ldquowyspecjalizowanerdquo w wykonywaniu określonych czynności Dzięki temułatwiej jest zlokalizować błąd Ponadto takie funkcje można potem przenieść do innych pro-gramoacutew

111 Tworzenie funkcji

Dobrze jest uczyć się na przykładach Rozważmy następujący kod

int iloczyn (int x int y)

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

int iloczyn (int x int y) to nagłoacutewek funkcji ktoacutery opisuje jakie argumenty przyj-muje funkcja i jaką wartość zwraca (funkcja może przyjmować wiele argumentoacutew lecz możezwracać tylko jedną wartość)2 Na początku podajemy typ zwracanej wartości mdash u nas intNastępnie mamy nazwę funkcji i w nawiasach listę argumentoacutew

Ciało funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasachklamrowych Pierwszą instrukcją jest deklaracja zmiennej mdash jest to zmienna lokalna czyliniewidoczna poza funkcją Dalej przeprowadzamy odpowiednie działania i zwracamy rezul-tat za pomocą instrukcji return

1111 Ogoacutelnie

Funkcję w języku C tworzy się następująco

typ identyfikator (typ1 argument1 typ2 argument2 typ_n argument_n)

instrukcje

Oczywiście istnieje możliwość utworzenia funkcji ktoacutera nie posiada żadnych argumen-toacutew Definiuje się ją tak samo jak funkcję z argumentami z tą tylko roacuteżnicą że międzyokrągłymi nawiasami nie znajduje się żaden argument lub pojedyncze słoacutewko void mdash w de-finicji funkcji nie ma to znaczenia jednak w deklaracji puste nawiasy oznaczają że prototypnie informuje jakie argumenty przyjmuje funkcja dlatego bezpieczniej jest stosować słoacutewkovoid

Funkcje definiuje się poza głoacutewną funkcją programu (main) W języku C nie można two-rzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji)

1112 Procedury

Przyjęło się że procedura od funkcji roacuteżni się tym że ta pierwsza nie zwraca żadnej wartościZatem aby stworzyć procedurę należy napisać

2Bardziej precyzyjnie można powiedzieć że funkcja może zwroacutecić tylko jedną wartość typu prostego lub jedenadres do jakiegoś obiektu w pamięci

112 WYWOŁYWANIE 79

void identyfikator (argument1 argument2 argumentn)

instrukcje

void (z ang pusty proacuteżny) jest słowem kluczowym mającym kilka znaczeń w tym przy-padku oznacza ldquobrak wartościrdquo

Generalnie w terminologii C pojęcie ldquoprocedurardquo nie jest używane moacutewi się raczej ldquofunk-cja zwracająca voidrdquo

Jeśli nie podamy typu danych zwracanych przez funkcję kompilator domyślnie przyjmietyp int choć już w standardzie C nieokreślenie wartości zwracanej jest błędem

1113 Stary sposoacuteb definiowania funkcji

Zanim powstał standard ANSI C w liście parametroacutew nie podawało się typoacutew argumentoacutewa jedynie ich nazwy Roacutewnież z tamtych czasoacutew wywodzi się oznaczenie iż puste nawiasy(w prototypie funkcji nie w definicji) oznaczają że funkcja przyjmuje nieokreśloną liczbęargumentoacutew Tego archaicznego sposobu definiowania funkcji nie należy już stosować aleponieważ w swojej przygodzie z językiem C Czytelnik może się na nią natknąć a co więcejstandard nadal (z powodu zgodności z wcześniejszymi wersjami) dopuszcza taką deklaracjęto należy tutaj o niej wspomnieć Otoacuteż wygląda ona następująco

typ_zwracany nazwa_funkcji(argument1 argument2 argumentn)typ1 argumenty typ2 argumenty

instrukcje

Na przykład wcześniejsza funkcja iloczyn wyglądałaby następująco

int iloczyn(x y)int x y

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

Najpoważniejszą wadą takiego sposobu jest fakt że w prototypie funkcji niema podanychtypoacutew argumentoacutew przez co kompilator nie jest w stanie sprawdzić poprawności wywołaniafunkcji Naprawiono to (wprowadzając definicje takie jak je znamy obecnie) najpierw wjęzyku C++ a potem rozwiązanie zapożyczono w standardzie ANSI C z roku

112 WywoływanieFunkcje wywołuje się następująco

80 ROZDZIAŁ 11 FUNKCJE

identyfikator (argument1 argument2 argumentn)

Jeśli chcemy aby przypisać zmiennej wartość ktoacuterą zwraca funkcja należy napisać tak

zmienna = funkcja (argument1 argument2 argumentn)

Programiści mający doświadczenia np z językiem Pascal mogą popełniać błąd polegającyna wywoływaniu funkcji bez nawiasoacutew okrągłych gdy nie przyjmuje ona żadnych argumen-toacutew

Przykładowo mamy funkcję

void pisz_komunikat()

printf(To jest komunikatn)

Jeśli teraz ją wywołamy

pisz_komunikat ŹLE pisz_komunikat() dobrze

to pierwsze polecenie nie spowoduje wywołania funkcji Dlaczego Aby kompilator Czrozumiał że chodzi nam o wywołanie funkcji musimy po jej nazwie dodać nawiasy okrą-głe nawet gdy funkcja nie ma argumentoacutew Użycie samej nazwy funkcji ma zupełnie inneznaczenie mdash oznacza pobranie jej adresu W jakim celu O tym będzie mowa w rozdzialeWskaźniki

PrzykładA oto działający przykład ktoacutery demonstruje wiadomości podane powyżej

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

int m = suma (4 5)printf (4+5=dn m)return 0

113 Zwracanie wartościreturn to słowo kluczowe języka C

W przypadku funkcji służy ono do

przerwania funkcji (i przejścia do następnej instrukcji w funkcji wywołującej)

114 ZWRACANA WARTOŚĆ 81

zwroacutecenia wartości

W przypadku procedur powoduje przerwania procedury bez zwracania wartościUżycie tej instrukcji jest bardzo proste i wygląda tak

return zwracana_wartość

lub dla procedur

return

Możliwe jest użycie kilku instrukcji returnw obrębie jednej funkcji Wielu programistoacutewuważa jednak że lepsze jest użycie jednej instrukcji return na końcu funkcji gdyż ułatwia tośledzenie przebiegu programu

114 Zwracana wartość

W C zwykle przyjmuje się że oznacza poprawne zakończenie funkcji

return 0 funkcja zakończona sukcesem

a inne wartości oznaczają niepoprawne zakończenie

return 1 funkcja zakończona niepowodzeniem

Ta wartość może być wykorzystana przez inne instrukcje np if

115 Funkcja main()

Do tej pory we wszystkich programach istniała funkcja main() Po co tak właściwie onajest Otoacuteż jest to funkcja ktoacutera zostaje wywołana przez fragment kodu inicjującego pracęprogramu Kod ten tworzony jest przez kompilator i nie mamy na niego wpływu Istotnejest że każdy program w języku C musi zawierać funkcję main()

Istnieją dwa możliwe prototypy (nagłoacutewki) omawianej funkcji

int main(void)

int main(int argc char argv) 3

Argument argc jest liczbą nieujemną określającą ile ciągoacutew znakoacutew przechowywanychjest w tablicy argv Wyrażenie argv[argc] ma zawsze wartość Pierwszym elementemtablicy argv (o ile istnieje4) jest nazwa programu czy komenda ktoacuterą program został urucho-miony Pozostałe przechowują argumenty podane przy uruchamianiu programu

Zazwyczaj jeśli program uruchomimy poleceniem

program argument1 argument2

3Czasami można się spotkać z prototypem int main(int argc char argv char env) ktoacutery jest definio-wany w standardzie ale wykracza już poza standard C

4Inne standardy mogą wymuszać istnienie tego elementu jednak jeśli chodzi o standard języka C to nic nie stoina przeszkodzie aby argument argc miał wartość zero

82 ROZDZIAŁ 11 FUNKCJE

to argc będzie roacutewne ( argumenty + nazwa programu) a argv będzie zawierać napisy pro-gram argument argument umieszczone w tablicy indeksowanej od do

Weźmy dla przykładu program ktoacutery wypisuje to co otrzymuje w argumentach argc iargv

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) while (argv)

puts(argv++) Ewentualnie można użycint ifor (i = 0 iltargc ++i)

puts(argv[i])return EXIT_SUCCESS

Uruchomiony w systemie typu UNIX poleceniem test foo bar baz powinien wypisać

testfoobarbaz

Na razie nie musisz rozumieć powyższych kodoacutew i opisoacutew gdyż odwołują się do pojęćtakich jak tablica oraz wskaźnik ktoacutere opisane zostaną w dalszej części podręcznika

Co ciekawe funkcja main nie roacuteżni się zanadto od innych funkcji i tak jak inne możewołać sama siebie (patrz rekurencja niżej) przykładowo powyższy program można zapisaćtak5

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) if (argv)

puts(argv)return main(argc-1 argv+1)

else return EXIT_SUCCESS

Ostatnią rzeczą dotyczącą funkcji main jest zwracana przez nią wartość Już przy oma-wianiu pierwszego programu wspomniane zostało że jedynymi wartościami ktoacutere znaczą

5Jeżeli ktoś lubi ekstrawagancki kod ciało funkcji main można zapisać jako return argv puts(argv)main(argc-1 argv+1) EXIT SUCCESS ale nie radzimy stosować tak skomplikowanych i bądź co bądź małoczytelnych konstrukcji

116 DALSZE INFORMACJE 83

zawsze to samowewszystkich implementacjach języka są EXIT SUCCESS i EXIT FAILURE6

zdefiniowane w pliku nagłoacutewkowym stdlibh Wartość i EXIT SUCCESS oznaczają po-prawne zakończenie programu (co wcale nie oznacza że makro EXIT SUCCESS ma wartośćzero) natomiast EXIT FAILURE zakończenie błędne Wszystkie inne wartości są zależne odimplementacji

116 Dalsze informacjePoniżej przekażemy ci parę bardziej zaawansowanych informacji o funkcjach w C jeśli niemasz ochoty wgłębiać się w szczegoacuteły możesz spokojnie pominąć tę część i wroacutecić tu poacuteźniej

1161 Jak zwroacutecić kilka wartości

Jeśli chcesz zwroacutecić z funkcji kilka wartości musisz zrobić to w trochę inny sposoacuteb Ge-neralnie możliwe są dwa podejścia jedno to ldquoupakowanierdquo zwracanych wartości ndash możnastworzyć tak zwaną strukturę ktoacutera będzie przechowywała kilka zmiennych (jest to opisanew rozdziale Typy złożone) Prostszym sposobem jest zwracanie jednej z wartości w normalnysposoacuteb a pozostałych jako parametroacutew Za chwilę dowiesz się jak to zrobić jeśli chcesz zo-baczyć przykład możesz przyjrzeć się funkcji scanf() z biblioteki standardowej

1162 Przekazywanie parametroacutew

Gdy wywołujemy funkcję wartość argumentoacutew z ktoacuterymi ją wywołujemy jest kopiowanado funkcji Kopiowana mdash to znaczy że nie możemy normalnie zmienić wartości zewnętrz-nych dla funkcji zmiennych Formalnie moacutewi się że w C argumenty są przekazywane przezwartość czyli wewnątrz funkcji operujemy tylko na ich kopiach

Możliwe jest modyfikowanie zmiennych przekazywanych do funkcji jako parametry mdashale do tego w C potrzebne są wskaźniki

1163 Funkcje rekurencyjne

Język Cmamożliwość tworzenia tzw funkcji rekurencyjny Jest to funkcja ktoacuteraw swojejwłasnej definicji (ciele) wywołuje samą siebie Najbardziej klasycznym przykładem może tubyć silnia Napiszmy sobie zatem naszą funkcję rekurencyjną ktoacutera oblicza silnię

int silnia (int liczba)

int silif (liczbalt0) return 0 wywołanie jest bezsensowne

zwracamy 0 jako kod błędu if (liczba==0 || liczba==1) return 1sil = liczbasilnia(liczba-1)return sil

Musimy być ostrożni przy funkcjach rekurencyjnych gdyż łatwo za ich pomocą utworzyćfunkcję ktoacutera będzie sama siebie wywoływała w nieskończoność a co za tym idzie będzie za-wieszała program Tutaj pierwszymi instrukcjami if ustalamy ldquowarunki stopurdquo gdzie kończy

6Uwaga Makra EXIT SUCCESS i EXIT FAILURE te służą tylko i wyłącznie jako wartości do zwracania przezfunkcję main() Nigdzie indziej nie mają one zastosowania

84 ROZDZIAŁ 11 FUNKCJE

się wywoływanie funkcji przez samą siebie a następnie określamy jak funkcja będzie wy-woływać samą siebie (odjęcie jedynki od argumentu co do ktoacuterego wiemy że jest dodatnigwarantuje że dojdziemy do warunku stopu w skończonej liczbie krokoacutew)

Warto też zauważyć że funkcje rekurencyjne czasami mogą być znacznie wolniejsze niżpodejście nierekurencyjne (iteracyjne przy użyciu pętli) Flagowym przykłademmoże tu byćfunkcja obliczająca wyrazy ciągu Fibonacciego

include ltstdiohgt

unsigned count

unsigned fib_rec(unsigned n) ++countreturn nlt2 n (fib_rec(n-2) + fib_rec(n-1))

unsigned fib_it (unsigned n) unsigned a = 0 b = 0 c = 1++countif (n) return 0while (--n)

++counta = bb = cc = a + b

return c

int main(void) unsigned n resultprintf(Ktory element ciagu Fibonacciego obliczyc )while (scanf(d ampn)==1)

count = 0result = fib_rec(n)printf(fib_ret(3u) = 6u (wywolan 5u)n n result count)

count = 0result = fib_it (n)printf(fib_it (3u) = 6u (wywolan 5u)n n result count)

return 0

W tym przypadku funkcja rekurencyjna choć łatwiejsza w napisaniu jest bardzo nie-efektywna

116 DALSZE INFORMACJE 85

1164 Deklarowanie funkcji

Czasami możemy chcieć przed napisaniem funkcji poinformować kompilator że dana funkcjaistnieje Niekiedy kompilator może zaprotestować jeśli użyjemy funkcji przed określeniemjaka to funkcja na przykład

int a()

return b(0)

int b(int p)

if( p == 0 )return 1

elsereturn a()

int main()

return b(1)

W tym przypadku nie jesteśmy w stanie zamienić a i b miejscami bo obie funkcje korzy-stają z siebie nawzajem Rozwiązaniem jest wcześniejsze zadeklarowanie funkcji Deklara-cja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji(przed otwierającym nawiasem klamrowym) z dodatkowo dodanym średnikiem na końcu Wnaszym przykładzie wystarczy na samym początku wstawić

int b(int p)

W deklaracji można pominąć nazwy parametroacutew funkcji

int b(int)

Bardzo częstym zwyczajem jest wypisanie przed funkcją main samych prototypoacutew funk-cji by ich definicje umieścić po definicji funkcji main np

int a(void)int b(int p)

int main()

return b(1)

int a()

return b(0)

86 ROZDZIAŁ 11 FUNKCJE

int b(int p)

if( p == 0 )return 1

elsereturn a()

Z poprzednich rozdziałoacutew pamiętasz że na początku programu dołączaliśmy tzw plikinagłoacutewkowe Zawierają one właśnie prototypy funkcji i ułatwiają pisanie dużych progra-moacutew Dalsze informacje o plikach nagłoacutewkowych zawarte są w rozdziale Tworzenie biblio-tek

1165 Zmienna liczba parametroacutew

Zauważyłeś zapewne że używając funkcji printf() lub scanf() po argumencie zawierającymtekst z odpowiednimi modyfikatorami mogłeś podać praktycznie nieograniczoną liczbę ar-gumentoacutew Zapewne deklaracja obu funkcji zadziwi Cię jeszcze bardziej

int printf(const char format )int scanf(const char format )

Jak widzisz w deklaracji zostały użyte kropki Otoacuteż język C ma możliwość przekazywa-nia nieograniczonej liczby argumentoacutew do funkcji (tzn jedynym ograniczeniem jest rozmiarstosu programu) Cała zabawa polega na tym aby umieć dostać się do odpowiedniego ar-gumentu oraz poznać jego typ (używając funkcji printf mogliśmy wpisać jako argument do-wolny typ danych) Do tego celu możemy użyć wszystkich ciekawostek zawartych w plikunagłoacutewkowym stdargh

Załoacuteżmy że chcemy napisać prostą funkcję ktoacutera dajmy na to mnoży wszystkie swojeargumenty (zakładamy że argumenty są typu int) Przyjmujemy przy tym że ostatni argu-ment będzie Będzie ona wyglądała tak

include ltstdarghgt

int mnoz (int pierwszy )

va_list argint iloczyn = 1 tva_start (arg pierwszy)for (t = pierwszy t t = va_arg(arg int))

iloczyn = tva_end (arg)return iloczyn

va list oznacza specjalny typ danych w ktoacuterym przechowywane będą argumenty prze-kazane do funkcji ldquova startrdquo inicjuje arg do dalszego użytku Jako drugi parametr musimypodać nazwę ostatniego znanego argumentu funkcji Makropolecenie va arg odczytuje ko-lejne argumenty i przekształca je do odpowiedniego typu danych Na zakończenie używanejest makro va end mdash jest ono obowiązkowe

117 ZOBACZ TEŻ 87

Oczywiście tak samo jak w przypadku funkcji printf() czy scanf() argumenty nie musząbyć takich samych typoacutew Rozważmy dla przykładu funkcję podobną do printf() ale znacznieuproszczoną

include ltstdarghgt

void wypisz(const char format ) va_list argva_start (arg format)for ( format ++format)

switch (format) case i printf(d va_arg(arg int)) breakcase I printf(u va_arg(arg unsigned)) breakcase l printf(ld va_arg(arg int)) breakcase L printf(lu va_arg(arg unsigned long)) breakcase f printf(f va_arg(arg double)) breakcase x printf(x va_arg(arg unsigned)) breakcase X printf(X va_arg(arg unsigned)) breakcase s printf(s va_arg(arg const char )) breakdefault putc(format)

va_end (arg)

Przyjmuje ona jako argument ciąg znakoacutew w ktoacuterych niektoacutere instruują funkcję bypobrała argument i go wypisała Nie przejmuj się jeżeli nie rozumiesz wyrażeń format i++format Istotne jest to że pętla sprawdza po kolei wszystkie znaki formatu

1166 Ezoteryka C

C ma wiele niuansoacutew o ktoacuterych wielu programistoacutew nie wie lub łatwo o nich zapomina

jeśli nie podamy typu wartości zwracanej w funkcji zostanie przyjęty typ int (wedługnajnowszego standardu C nie podanie typu wartości jest zwracane jako błąd)

jeśli nie podamy żadnych parametroacutew funkcji to funkcja będzie używała zmiennej ilo-ści parametroacutew (inaczej niż wC++ gdzie przyjęte zostanie że funkcja nie przyjmuje ar-gumentoacutew) Aby wymusić pustą listę argumentoacutew należy napisać int funkcja(void)(dotyczy to jedynie prototypoacutew czy deklaracji funkcji)

jeśli nie użyjemy w funkcji instrukcji return wartość zwracana będzie przypadkowa(dostaniemy śmieci z pamięci)

Kompilator C++ użyty do kompilacji kodu C najczęściej zaprotestuje i ostrzeże nas jeśliużyjemy powyższych konstrukcji Natomiast czysty kompilator C z domyślnymi ustawie-niami nie napisze nic i bez mrugnięcia okiem skompiluje taki kod

117 Zobacz też C++Funkcje inline mdash funkcje rozwijane wmiejscu wywoływania (dostępne też w stan-

dardzie C)

88 ROZDZIAŁ 11 FUNKCJE

C++Przeciążanie funkcji

Rozdział 12

Preprocesor

121 Wstęp

W języku C wszystkie linijki zaczynające się od symbolu rdquo nie podlegają bezpośrednio pro-cesowi kompilacji Są to natomiast instrukcje preprocesora mdash elementu kompilatora ktoacuteryanalizuje plik źroacutedłowy w poszukiwaniu wszystkich wyrażeń zaczynających się od rdquo Napodstawie tych instrukcji generuje on kod w czystymrdquo języku C ktoacutery następnie jest kom-pilowany przez kompilator Ponieważ za pomocą preprocesora można niemal sterowaćrdquokompilatorem daje on niezwykłe możliwości ktoacutere nie były dotąd znane w innych językachprogramowania Aby przekonać się jak wygląda kod przetworzony przez preprocesor użyj(w kompilatorze gcc) przełącznika -Erdquo

gcc testc -E -o testtxt

W pliku testtxt zostanie umieszczony cały kod w postaci ktoacutera zdatna jest do przetwo-rzenia przez kompilator

122 Dyrektywy preprocesora

Dyrektywy preprocesora są towyrażenia ktoacutere występują zaraz za symbolem rdquo i to właśnieza ich pomocą możemy używać preprocesora Dyrektywa zaczyna się od znaku i kończysię wraz z końcem linii Aby przenieść dalszą część dyrektywy do następnej linii należyzakończyć linię znakiem rdquo

define add(ab) a+b

Omoacutewimy teraz kilka ważniejszych dyrektyw

1221 include

Najpopularniejsza dyrektywa wstawiająca w swoje miejsce treść pliku podanego w nawia-sach ostrych lub cudzysłowie Składnia

89

90 ROZDZIAŁ 12 PREPROCESOR

Przykład 1

include ltplik_naglowkowy_do_dolaczeniagt

Za pomocą include możemy dołączyć dowolny plik mdash niekoniecznie plik nagłoacutewkowy

Przykład 2

include plik_naglowkowy_do_dolaczenia

Jeżeli nazwa pliku nagłoacutewkowego będzie ujęta w nawiasy ostre (przykład ) to kompi-lator poszuka go wśroacuted własnych plikoacutew nagłoacutewkowych (ktoacutere najczęściej się znajdują wpodkatalogu includesrdquo w katalogu kompilatora) Jeśli jednak nazwa ta będzie ujęta w po-dwoacutejne cudzysłowy(przykład ) to kompilator poszuka jej w katalogu w ktoacuterym znajdujesię kompilowany plik (można zmienić to zachowanie w opcjach niektoacuterych kompilatoroacutew)Przy użyciu tej dyrektywy można także wskazać dokładne położenie plikoacutew nagłoacutewkowychpoprzez wpisanie bezwzględnej lub względnej ścieżki dostępu do tego pliku nagłoacutewkowego

Przykład 3 mdash ścieżka bezwzględna do pliku nagłoacutewkowego w Linuksie i w Windowsie

Opis W miejsce jednej i drugiej linijki zostanie wczytany plik umieszczony w danej lokali-zacji

include usrincludeplik_nagłoacutewkowyhinclude Cborlandincludesplik_nagłoacutewkowyh

Przykład 4 mdash ścieżka względna do pliku nagłoacutewkowego

Opis W miejsce linijki zostanie wczytany plik umieszczony w katalogu katalogrdquo a tenkatalog jest w katalogu z plikiem źroacutedłowym Inaczej moacutewiąc jeśli plik źroacutedłowy jest wkatalogu homeuserdokumentyzrodlardquo to plik nagłoacutewkowy jest umieszczony w kataloguhomeuserdokumentyzrodlakatalogrdquo

include katalog1plik_naglowkowyh

Przykład 5 mdash ścieżka względna do pliku nagłoacutewkowego

Opis Jeśli plik źroacutedłowy jest umieszczony w katalogu homeuserdokumentyzrodlardquo toplik nagłoacutewkowy znajduje się w katalogu homeuserdokumentykatalogkatalogrdquo

include katalog1katalog2plik_naglowkowyh

Więcej informacji możesz uzyskać w rozdziale Biblioteki

1222 define

Linia pozwalająca zdefiniować stałą funkcję lub słowo kluczowe ktoacutere będzie potem pod-mienione w kodzie programu na odpowiednią wartość lub może zostać użyte w instrukcjachwarunkowych dla preprocesora Składnia

define NAZWA_STALEJ WARTOSC

lub

define NAZWA_STALEJ

122 DYREKTYWY PREPROCESORA 91

Przykład

define LICZBA mdash spowoduje że każde wystąpienie słowa LICZBA w kodzie zostanie za-stąpione oacutesemkądefine SUMA(ab) (a+b) mdash spowoduje ze każde wystąpienie wywołania funkcjirdquo SUMA zo-stanie zastąpione przez sumę argumentoacutew

1223 undef

Ta instrukcja odwołuje definicję wykonaną instrukcją define

undef STALA

1224 instrukcje warunkowe

Preprocesor zawiera roacutewnież instrukcje warunkowe pozwalające nawyboacuter tego coma zostaćskompilowane w zależności od tego czy stała jest zdefiniowana lub jaką ma wartość

if elif else endif

Te instrukcje uzależniają kompilacje od warunkoacutew Ich działanie jest podobne do instrukcjiwarunkowych w samym języku C I tak

if wprowadza warunek ktoacutery jeśli nie jest prawdziwy powoduje pominięcie kompilowaniakodu aż do napotkania jednej z poniższych instrukcji

else spowoduje skompilowanie kodu jeżeli warunek za if jest nieprawdziwy aż do napo-tkania ktoacuteregoś z poniższych instrukcji

elif wprowadza nowy warunek ktoacutery będzie sprawdzony jeżeli poprzedni był niepraw-dziwy Stanowi połączenie instrukcji if i else

endif zamyka blok ostatniej instrukcji warunkowej

Przykład

if INSTRUKCJE == 2printf (Podaj liczbę z przedziału 10 do 0n) 1

elif INSTRUKCJE == 1printf (Podaj liczbę ) 2

elseprintf (Podaj parametr ) 3

endifscanf (dn ampliczba)4

wiersz nr zostanie skompilowany jeżeli stała INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany gdy INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany w pozostałych wypadkach

wiersz nr będzie kompilowany zawsze

92 ROZDZIAŁ 12 PREPROCESOR

ifdef ifndef else endif

Te instrukcje warunkują kompilację od tego czy odpowiednia stała została zdefiniowana

ifdef spowoduje że kompilator skompiluje poniższy kod tylko gdy została zdefiniowanaodpowiednia stała

ifndef ma odwrotne działanie do ifdef a mianowicie brak definicji odpowiedniej stałejumożliwia kompilacje poniższego kodu

elseendif mają identyczne zastosowanie jak te z powyższej grupy

Przykład

define INFO definicja stałej INFOifdef INFO

printf (Twoacutercą tego programu jest Jan Kowalskin)1endififndef INFO

printf (Twoacutercą tego programu jest znany programistan)2endif

To czy dowiemy się kto jest twoacutercą tego programu zależy czy instrukcja definiująca stałąINFO będzie istnieć W powyższym przypadku na ekranie powinno się wyświetlić

Twoacutercą tego programu jest Jan Kowalski

1225 error

Powoduje przerwanie kompilacji i wyświetlenie tekstu ktoacutery znajduje się za tą instrukcjąPrzydatne gdy chcemy zabezpieczyć się przed zdefiniowaniem nieodpowiednich stałych

Przykład

if BLAD == 1error Poważny błąd kompilacjiendif

Co jeżeli zdefiniujemy stałą BLAD z wartością Spowoduje to wyświetlenie w trakcie kom-pilacji komunikatu podobnego do poniższego

Fatal error programc 6 Error directive Poważny błąd kompilacjiin function main() 1 errors in Compile

wraz z przerwaniem kompilacji

1226 warning

Wyświetla tekst zawarty w cudzysłowach jako ostrzeżenie Jest często używany do sygna-lizacji programiście że dana część programu jest przestarzała lub może sprawiać problemy

122 DYREKTYWY PREPROCESORA 93

Przykład

warning To jest bardzo prosty program

Spowoduje to takie oto zachowanie kompilatora

testc32 warning warning To jest bardzo prosty program

Użycie dyrektywy warning nie przerywa procesu kompilacji i służy tylko do wyświetlaniakomunikatoacutew dla programisty w czasie kompilacji programu

1227 line

Powoduje wyzerowanie licznika linii kompilatora ktoacutery jest używany przy wyświetlaniuopisu błędoacutew kompilacji Pozwala to na szybkie znalezienie możliwej przyczyny błędu wrozbudowanym programie

Przykład

printf (Podaj wartość funkcji)lineprintf (W przedziale od 10 do 0n) tutaj jest błąd - brak cudzysłowu zamykającego

Jeżeli teraz nastąpi proacuteba skompilowania tego kodu to kompilator poinformuje że wystąpiłbłąd składni w linii a nie np

1228 Makra

Preprocesor języka C umożliwia też tworzenie makr czyli automatycznie wykonywanychczynności Makra deklaruje się za pomocą dyrektywy define

define MAKRO(arg1 arg2 ) (wyrażenie)

Wmomencie wystąpienia MAKRA w tekście preprocesor automatycznie zamieni makrona wyrażenie Makra mogą być pewnego rodzaju alternatywami dla funkcji ale powinnosię ich używać tylko w specjalnych przypadkach Ponieważ makro sprowadza się do pro-stego zastąpienia przez preprocesor wywołania makra przez jego tekst jest bardzo podatnena trudne do zlokalizowania błędy (kompilator będzie podawał błędy wmiejscach w ktoacuterychnic nie widzimy mdash bo preprocesor wstawił tam tekst) Makra są szybsze (nie następuje wy-wołanie funkcji ktoacutere zawsze zajmuje trochę czasu1) ale też mniej bezpieczne i elastyczneniż funkcje

Przeanalizujmy teraz fragment kodu

include ltstdiohgtdefine KWADRAT(x) ((x)(x))

int main ()

printf (2 do kwadratu wynosi dn KWADRAT(2))return 0

1Tak naprawdę wg standardu C99 istnieje możliwość napisania funkcji ktoacuterej kod także będzie wstawiany wmiejscu wywołania Odbywa się to dzięki inline

94 ROZDZIAŁ 12 PREPROCESOR

Preprocesor w miejsce wyrażenia KWADRAT(2) wstawił ((2)(2)) Zastanoacutewmy się costałoby się gdybyśmy napisali KWADRAT(2) Preprocesor po prostu wstawi napis do koduco da wyrażenie ((2)(2)) ktoacutere jest nieprawidłowe Kompilator zgłosi błąd ale pro-gramista widzi tylko w kodzie użycie makra a nie prawdziwą przyczynę błędu Widać tu żebezpieczniejsze jest użycie funkcji ktoacutere dają możliwość wyspecyfikowania typoacutew argumen-toacutew

Nawet jeżeli program się skompiluje to makro może dawać nieoczekiwany wynik Jesttak w przypadku poniższego kodu

int x = 1int y = KWADRAT(++x)

Dzieje się tak dlatego że makra rozwijane są przez preprocesor i kompilator widzi kod

int x = 1int y = ((++x)(++x))

Roacutewnież poniższe makra są błędne pomimo że opisany problem w nich nie występuje

define SUMA(a b) a + bdefine ILOCZYN(a b) a b

Dają one nieoczekiwane wyniki dla wywołań

SUMA(2 2) 2 6 zamiast 8 ILOCZYN(2 + 2 2 + 2) 8 zamiast 16

Z tego powodu istotne jest użycie nawiasoacutew

define SUMA(a b) ((a) + (b))define ILOCZYN(a b) ((a) (b))

1229 oraz

Dość ciekawe możliwości ma w makrach znak rdquo Zamienia on stojący za nim identyfikatorna napis

include ltstdiohgtdefine wypisz(x) printf(s=in x x)

int main()

int i=1char a=5wypisz(i)wypisz(a)return 0

Program wypisze

i=1a=5

123 PREDEFINIOWANE MAKRA 95

Czyli wypisz(a) jest rozwijane w printf(s=in a a)Natomiast znaki rdquo łączą dwie nazwy w jedną Przykład

include ltstdiohgtdefine abc(x) int zmienna x

int main()

abc(nasza) dzięki temu zadeklarujemy zmienną o nazwie zmiennanasza zmiennanasza = 2return 0

Więcej o dobrych zwyczajach w tworzeniu makr można się dowiedzieć w rozdziale Po-wszechne praktyki

123 Predefiniowane makraW języku wprowadzono roacutewnież serię predefiniowanych makr ktoacutere mają ułatwić życie pro-gramiście Oto one

DATE mdash data w momencie kompilacji

TIME mdash godzina w momencie kompilacji

FILE mdash łańcuch ktoacutery zawiera nazwę pliku ktoacutery aktualnie jest kompilowany przezkompilator

LINE mdash definiuje numer linijki

STDC mdash w kompilatorach zgodnych ze standardem ANSI lub nowszym makro toprzyjmuje wartość

STDC VERSION mdash zależnie od poziomu zgodności kompilatora makro przyjmuje roacuteżnewartości

ndash jeżeli kompilator jest zgodny z ANSI (rok ) makro nie jest zdefiniowane

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199409L

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199901L

Warto roacutewnież wspomnieć o identyfikatorze func zdefiniowanym w standardzie Cktoacuterego wartość to nazwa funkcji

Sproacutebujmy użyć tych makr w praktyce

include ltstdiohgt

if __STDC_VERSION__ gt= 199901L Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go define BUG(message) fprintf(stderr sd s (w funkcji s)n

__FILE__ __LINE__ message __func__)else Jezeli __func__ nie ma to go nie używamy

96 ROZDZIAŁ 12 PREPROCESOR

define BUG(message) fprintf(stderr sd sn __FILE__ __LINE__ message)

endif

int main(void) printf(Program ABC data kompilacji s sn __DATE__ __TIME__)

BUG(Przykladowy komunikat bledu)return 0

Efekt działania programu gdy kompilowany jest kompilatorem C

Program ABC data kompilacji Sep 1 2008 191213testc17 Przykladowy komunikat bledu (w funkcji main)

Gdy kompilowany jest kompilatorem ANSI C

Program ABC data kompilacji Sep 1 2008 191316testc17 Przykladowy komunikat bledu

Rozdział 13

Biblioteka standardowa

131 Czym jest biblioteka

Bibliotekę w języku C stanowi zbioacuter skompilowanych wcześniej funkcji ktoacutery można łączyćz programem Biblioteki tworzy się aby udostępnić zbioacuter pewnych ldquowyspecjalizowanychrdquofunkcji do dyspozycji innych programoacutew Tworzenie bibliotek jest o tyle istotne że takiepodejście znacznie ułatwia tworzenie nowych programoacutew Łatwiej jest utworzyć program woparciu o istniejące biblioteki niż pisać programwraz zewszystkimi potrzebnymi funkcjami1

132 Po co nam biblioteka standardowa

W ktoacuterymś z początkowych rozdziałoacutew tego podręcznika napisane jest że czysty język C niemoże zbyt wiele Tak naprawdę to język C sam w sobie praktycznie nie ma mechanizmoacutewdo obsługi np wejścia-wyjścia Dlatego też większość systemoacutew operacyjnych posiada tzwbibliotekę standardową zwaną też biblioteką języka C To właśnie w niej zawarte są pod-stawowe funkcjonalności dzięki ktoacuterym twoacutej program może np napisać coś na ekranie

1321 Jak skonstruowana jest biblioteka standardowa

Zapytacie zapewne jak biblioteka standardowa realizuje te funkcje skoro sam język C tegonie potrafi Odpowiedź jest prosta mdash biblioteka standardowa nie jest napisana w samym ję-zyku C Ponieważ C jest językiem tłumaczonym do kodu maszynowego to w praktyce niema żadnych przeszkoacuted żeby np połączyć go z językiem niskiego poziomu jakim jest npasembler Dlatego biblioteka C z jednej strony udostępnia gotowe funkcje w języku C a zdrugiej za pomocą niskopoziomowych mechanizmoacutew2 komunikuje się z systemem operacyj-nym ktoacutery wykonuje odpowiednie czynności

133 Gdzie są funkcje z biblioteki standardowej

Pisząc program w języku C używamy roacuteżnego rodzaju funkcji takich jak np printf Niejesteśmy jednak ich autorami mało tego nie widzimy nawet deklaracji tych funkcji w naszymprogramie Pamiętacie program ldquoHello worldrdquo Zaczynał on się od takiej oto linijki

1Początkujący programista zapewne nie byłby w stanie napisać nawet funkcji printf2Takich jak np wywoływanie przerwań programowych

97

98 ROZDZIAŁ 13 BIBLIOTEKA STANDARDOWA

include ltstdiohgt

linijka ta oznacza ldquow tym miejscu wstaw zawartość pliku stdiohrdquo Nawiasy ldquoltrdquo i ldquogtrdquooznaczają że plik stdioh znajduje się w standardowym katalogu z plikami nagłoacutewkowymiWszystkie pliki z rozszerzeniem h są właśnie plikami nagłoacutewkowymi Wroacutećmy teraz do te-matu biblioteki standardowej Każdy system operacyjny ma za zadanie wykonywać pewnefunkcje na rzecz programoacutew Wszystkie te funkcje zawarte są właśnie w bibliotece standar-dowej W systemach z rodziny UNIX nazywa się ją LibC (biblioteka języka C) To tamwłaśnieznajduje się funkcja printf scanf puts i inne

Oproacutecz podstawowych funkcji wejścia-wyjścia biblioteka standardowa udostępnia teżmożliwość wykonywania funkcji matematycznych komunikacji przez sieć oraz wykonywa-nia wielu innych rzeczy

1331 Jeśli biblioteka nie jest potrzebna

Czasami korzystanie z funkcji bibliotecznych oraz standardowych plikoacutew nagłoacutewkowych jestniepożądane np wtedy gdy programista pisze swoacutej własny system operacyjny oraz biblio-tekę do niego Aby wyłączyć używanie biblioteki C w opcjach kompilatora GCC możemydodać następujące argumenty

-nostdinc -fno-builtin

134 Opis funkcji biblioteki standardowejPodręcznik C na Wikibooks zawiera opis dużej części biblioteki standardowej C

Indeks alfabetyczny

Indeks tematyczny

W systemach uniksowych możesz uzyskać pomoc dzięki narzędziu man przykładowopisząc

man printf

135 UwagiProgramy w języku C++ mogą dokładnie w ten sam sposoacuteb korzystać z biblioteki standar-dowej ale zalecane jest by robić to raczej w trochę odmienny sposoacuteb właściwy dla C++Szczegoacuteły w podręczniku C++

Rozdział 14

Czytanie i pisanie do plikoacutew

141 Pojęcie plikuNa początku dobrze by było abyś dowiedział się czym jest plik Odpowiedni artykuł do-stępny jest w Wikipedii Najprościej moacutewiąc plik to pewne dane zapisane na dysku

142 Identyfikacja plikuKażdy z nas korzystając na co dzień z komputera przyzwyczaił się do tego że plik ma okre-śloną nazwę Jednak w pisaniu programu posługiwanie się całą nazwą niosło by ze sobą conajmniej dwa problemy

pamięciożerność mdash przechowywanie całego (czasami nawet -bajtowego łańcucha)zajmuje niepotrzebnie pamięć

ryzyko błędoacutew (owe błędy szerzej omoacutewione zostały w rozdziale Napisy)

Aby uprościć korzystanie z plikoacutew programiści wpadli na pomysł aby identyfikatorempliku stała się liczba Dzięki temu kod programu stał się czytelniejszy oraz wyeliminowanokonieczność ciągłego korzystania z łańcuchoacutew Jednak sam plik nadal jest identyfikowany poswojej nazwie Aby ldquoprzetworzyćrdquo nazwę pliku na odpowiednią liczbę korzystamy z funkcjiopen lub fopen Roacuteżnica wyjaśniona jest poniżej

143 Podstawowa obsługa plikoacutewIstnieją dwie metody obsługi czytania i pisania do plikoacutew

wysokopoziomowa

niskopoziomowa

Nazwy funkcji z pierwszej grupy zaczynają się od litery ldquordquo (np fopen() fread() fclose())a identyfikatorem pliku jest wskaźnik na strukturę typu FILE Owa struktura to pewna grupazmiennych ktoacutera przechowuje dane o danym pliku mdash jak na przykład aktualną pozycję wnim Szczegoacutełami nie musisz się przejmować funkcje biblioteki standardowej same zajmująsię wykorzystaniem struktury FILE programista może więc zapomnieć czym tak naprawdęjest struktura FILE i traktować taką zmienną jako ldquouchwytrdquo identyfikator pliku

99

100 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

Druga grupa to funkcje typu read() open() write() i close()Podstawowym identyfikatorem pliku jest liczba całkowita ktoacutera jednoznacznie identy-

fikuje dany plik w systemie operacyjnym Liczba ta w systemach typu jest nazywanadeskryptorem pliku

Należy pamiętać że nie wolno nam używać funkcji z obu tych grup jednocześnie w sto-sunku do jednego otwartego pliku tzn nie można najpierw otworzyć pliku za pomocą fo-pen() a następnie odczytywać danych z tego samego pliku za pomocą read()

Czym roacuteżnią się oba podejścia do obsługi plikoacutew Otoacuteż metoda wysokopoziomowa maswoacutej własny bufor w ktoacuterym znajdują się dane po odczytaniu z dysku a przed wysłaniemich do programu użytkownika W przypadku funkcji niskopoziomowych dane kopiowane sąbezpośrednio z pliku do pamięci programu W praktyce używanie funkcji wysokopoziomo-wych jest prostsze a przy czytaniu danych małymi porcjami roacutewnież często szybsze i właśnieten model zostanie tutaj zaprezentowany

1431 Dane znakowe

Skupimy się teraz na najprostszym zmożliwych zagadnień mdash zapisie i odczycie pojedynczychznakoacutew oraz całych łańcuchoacutew

Napiszmy zatem nasz pierwszy program ktoacutery stworzy plik ldquotesttxtrdquo i umieści w nimtekst ldquoHello worldrdquo

include ltstdiohgtinclude ltstdlibhgt

int main ()

FILE fp używamy metody wysokopoziomowej musimy mieć zatem identyfikator pliku uwaga na gwiazdkę

char tekst[] = Hello worldif ((fp=fopen(testtxt w))==NULL)

printf (Nie mogę otworzyć pliku testtxt do zapisun)exit(1)

fprintf (fp s tekst) zapisz nasz łańcuch w pliku fclose (fp) zamknij plik return 0

Teraz omoacutewimy najważniejsze elementy programu Jak już było wspomniane wyżej doidentyfikacji pliku używa się wskaźnika na strukturę FILE (czyli FILE ) Funkcja fopenzwraca oacutew wskaźnik w przypadku poprawnego otwarcia pliku bądź też NULL gdy plik niemoże zostać otwarty Pierwszy argument funkcji to nazwa pliku natomiast drugi to trybdostępu mdash w oznacza ldquowriterdquo (pisanie) zwroacutecony ldquouchwytrdquo do pliku będzie moacutegł być wyko-rzystany jedynie w funkcjach zapisujących dane I odwrotnie gdy otworzymy plik podająctryb r (ldquoreadrdquo czytanie) będzie można z niego jedynie czytać dane Funkcja fopen zostaładokładniej opisana w odpowiedniej części rozdziału o bibliotece standardowej

Po zakończeniu korzystania z pliku należy plik zamknąć Robi się to za pomocą funk-cji fclose Jeśli zapomnimy o zamknięciu pliku wszystkie dokonane w nim zmiany zostanąutracone

143 PODSTAWOWA OBSŁUGA PLIKOacuteW 101

1432 Pliki a strumienie

Można zauważyć że do zapisu do pliku używamy funkcji fprintf ktoacutera wygląda bardzopodobnie do printf mdash jedyną roacuteżnicą jest to że w fprintf musimy jako pierwszy argu-ment podać identyfikator pliku Nie jest to przypadek mdash obie funkcje tak naprawdę robiątak samo Używana do wczytywania danych z klawiatury funkcja scanf też ma swoacutej od-powiednik wśroacuted funkcji operujących na plikach mdash jak nietrudno zgadnąć nosi ona nazwęfscanf

W rzeczywistości język C traktuje tak samo klawiaturę i plik mdash są to źroacutedła danych po-dobnie jak ekran i plik do ktoacuterych można dane kierować Jest to myślenie typowe dla sys-temoacutew typu UNIX jednak dla użytkownikoacutew przyzwyczajonych do systemu Windows albojęzykoacutew typu Pascal może być to co najmniej dziwne Nie da się ukryć że między klawia-turą i plikiem na dysku zachodzą podstawowe roacuteżnice i dostęp do nich odbywa się inaczejmdash jednak funkcje języka C pozwalają nam o tym zapomnieć i same zajmują się szczegoacutełamitechnicznymi Z punktu widzenia programisty urządzenia te sprowadzają się do nadanegoim identyfikatora Uogoacutelnione pliki nazywa się w C strumieniami

Każdy program w momencie uruchomienia ldquootrzymujerdquo od razu trzy otwarte strumienie

stdin (wejście)

stdout (wyjście)

stderr (wyjście błędoacutew)

(aby z nich korzystać należy dołączyć plik nagłoacutewkowy stdioh)Pierwszy z tych plikoacutew umożliwia odczytywanie danych wpisywanych przez użytkow-

nika natomiast pozostałe dwa służą do wyprowadzania informacji dla użytkownika oraz po-wiadamiania o błędach

Warto tutaj zauważyć że konstrukcja

fprintf (stdout Hej ja działam)

jest roacutewnoważna konstrukcji

printf (Hej ja działam)

Podobnie jest z funkcją scanf()

fscanf (stdin d ampzmienna)

działa tak samo jak

scanf(d ampzmienna)

1433 Obsługa błędoacutew

Jeśli nastąpił błąd możemy się dowiedzieć o jego przyczynie na podstawie zmiennej errnozadeklarowanej w pliku nagłoacutewkowym errnoh Możliwe jest też wydrukowanie komunikatuo błedzie za pomocą funkcji perror Na przykład używając

fp = fopen (tego pliku nie ma r)if( fp == NULL )

perror(błąd otwarcia pliku)exit(-10)

102 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

dostaniemy komunikat

błąd otwarcia pliku No such file or directory

1434 Zaawansowane operacje

Pora na kolejny tym razem bardziej złożony przykład Oto kroacutetki program ktoacutery swojewejście zapisuje do pliku o nazwie podanej w linii poleceń

include ltstdiohgtinclude ltstdlibhgt program udający bardzo prymitywną wersję programu tee(1)

int main (int argc char argv[])

FILE fpint cif (argc lt 2)

fprintf (stderr Uzycie s nazwa_plikun argv[0])exit (-1)

fp = fopen (argv[1] w)if (fp)

fprintf (stderr Nie moge otworzyc pliku sn argv[1])exit (-1)

printf(Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczycn)while ( (c = fgetc(stdin)) = EOF)

fputc (c stdout)fputc (c fp)

fclose(fp)return 0

Tym razem skorzystaliśmy już z dużo większego repertuaru funkcji Między innymimożna zauważyć tutaj funkcję fputc() ktoacutera umieszcza pojedynczy znak w pliku Ponadto wwyżej zaprezentowanym programie została użyta stała EOF ktoacutera reprezentuje koniec pliku(ang End Of File) Powyższy program otwiera plik ktoacuterego nazwa przekazywana jest jakopierwszy argument programu a następnie kopiuje dane z wejścia programu (stdin) na wyj-ście (stdout) oraz do utworzonego pliku (identyfikowanego za pomocą fp) Program robi todotąd aż naciśniemy kombinację klawiszy Ctrl+D(w systemach Unixowych) lub Ctrl+Z(wWindows) ktoacutera wyśle do programu informację że skończyliśmy wpisywać dane Programwyjdzie wtedy z pętli i zamknie utworzony plik

144 Rozmiar plikuDzięki standardowym funkcjom języka C możemy min określić długość pliku Do tego celusłużą funkcje fsetpos fgetpos oraz fseek Ponieważ przy każdym odczyciezapisie zdo plikuwskaźnik niejako ldquoprzesuwardquo się o liczbę przeczytanychzapisanych bajtoacutew Możemy jednak

145 PRZYKŁAD mdash PLIKI GRAFICZNY 103

ustawić wskaźnik w dowolnie wybranym miejscu Do tego właśnie służą wyżej wymienionefunkcje Aby odczytać rozmiar pliku powinniśmy ustawić nasz wskaźnik na koniec plikupo czym odczytać ile bajtoacutew od początku pliku się znajdujemy Wiem brzmi to strasznieale działa wyjątkowo prosto i skutecznie Użyjemy do tego tylko dwoacutech funkcji fseek orazfgetpos Pierwsza służy do ustawiania wskaźnika na odpowiedniej pozycji w pliku a drugado odczytywania na ktoacuterym bajcie pliku znajduje się wskaźnik Kod ktoacutery określa rozmiarpliku znajduje się tutaj

include ltstdiohgt

int main (int argc char argv)

FILE fp = NULLfpos_t dlugoscif (argc = 2)

printf (Użycie s ltnazwa plikugtn argv[0])return 1

if ((fp=fopen(argv[1] rb))==NULL) printf (Błąd otwarcia pliku sn argv[1])return 1

fseek (fp 0 SEEK_END) ustawiamy wskaźnik na koniec pliku fgetpos (fp ampdlugosc)printf (Rozmiar pliku dn dlugosc)fclose (fp)return 0

Znajomość rozmiaru pliku przydaje się w wielu roacuteżnych sytuacjach więc dobrze prze-analizuj przykład

145 Przykład mdash pliki graficznyNajprostszym przykładem rastrowego pliku graficznego jest plik Poniższy program po-kazuje jak utworzyć plik w katalogu roboczym programu Do zapisu

nagłoacutewka pliku używana jest funkcja fprintf

tablicy do pliku używana jest funkcja fwrite

include ltstdiohgtint main()

const int dimx = 800const int dimy = 800int i jFILE fp = fopen(firstppm wb) b - tryb binarny fprintf(fp P6nd dn255n dimx dimy)for(j=0 jltdimy ++j)

for(i=0 iltdimx ++i)static unsigned char color[3]

104 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

color[0]=i 255 red color[1]=j 255 green color[2]=(ij) 255 blue fwrite(color13fp)

fclose(fp)return 0

W powyższym przykładzie dostęp do danych jest sekwencyjny Jeśli chcemy mieć swo-bodny dostęp do danych to

korzystać z funkcji fsetpos fgetpos oraz fseek

utworzyć tablicę (dla dużych plikoacutew dynamiczną) zapisać do niej wszystkie dane anastępnie zapisać całą tablicę do pliku Ten sposoacuteb jest prostszy i szybszy Należyzwroacutecić uwagę że do obliczania rozmiaru całej tablicy nie możemy użyć funkcji sizeof

(a) Przykład użycia tej techniki sekwencyjnydostęp do danych (kod źroacutedłowy)

(b) Przykład użycia tej techniki swobodny do-stęp do danych (kod źroacutedłowy)

146 Co z katalogamiFaktycznie zapomnieliśmy o nich Jednak wynika to z tego że specyfikacja ANSI C nieuwzględnia obsługi katalogoacutew

Rozdział 15

Ćwiczenia dla początkujący

151 ĆwiczeniaWszystkie zamieszczone tutaj ćwiczenia mają na celu pomoacutec Ci w sprawdzeniu Twojej wie-dzy oraz umożliwieniu Tobie wykorzystania nowo nabytych wiadomości w praktyce Pa-miętaj także że ten podręcznik ma służyć także innym więc nie zamieszczaj tutaj Twoichrozwiązań Zachowaj je dla siebie

1511 Ćwiczenie 1

Napisz program ktoacutery wyświetli na ekranie twoje imię i nazwisko

1512 Ćwiczenie 2

Napisz program ktoacutery poprosi o podanie dwoacutech liczb rzeczywistych i wyświetli wynik mno-żenia obu zmiennych

1513 Ćwiczenie 3

Napisz program ktoacutery pobierze jako argumenty z linii komend nazwy dwoacutech plikoacutew i prze-kopiuje zawartość pierwszego pliku do drugiego (tworząc lub zamazując drugi)

1514 Ćwiczenie 4

Napisz program ktoacutery utworzy nowy plik (o dowolnie wybranej przez Ciebie nazwie) i za-pisze tam

Twoje imię

wiek

miasto w ktoacuterym mieszkasz

Przykładowy plik powinien wyglądać tak

Stanisław30Krakoacutew

105

106 ROZDZIAŁ 15 ĆWICZENIA DLA POCZĄTKUJĄCYCH

1515 Ćwiczenie 5

Napisz program generujący tabliczkę mnożenia x i wyświetlający ją na ekranie

1516 Ćwiczenie 6 mdash dla ętny

Napisz program znajdujący pierwiastki troacutejmianu kwadratowego ax2+bx+c= dla zadanychparametroacutew a b c

Rozdział 16

Tablice

W rozdziale Zmienne w C dowiedziałeś się jak przechowywać pojedyncze liczby oraz znakiCzasami zdarza się jednak że potrzebujemy przechować kilka kilkanaście albo iwięcej zmien-nych jednego typu Nie tworzymy wtedy np dwudziestu osobnych zmiennych W takichprzypadkach z pomocą przychodzi nam tablica

Rysunek 161 tablica 10-elementowa

Tablica to ciąg zmiennych jednego typu Ciąg taki posiada jedną nazwę a do jego po-szczegoacutelnych elementoacutew odnosimy się przez numer (indeks)

161 Wstęp

1611 Sposoby deklaracji tablic

Tablicę deklaruje się w następujący sposoacuteb

typ nazwa_tablicy[rozmiar]

gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy Zatemaby np zadeklarować tablicę mieszczącą liczb całkowitych możemy napisać tak

int tablica[20]

Podobnie jak przy deklaracji zmiennych także tablicy możemy nadać wartości począt-kowe przy jej deklaracji Odbywa się to przez umieszczenie wartości kolejnych elementoacutewoddzielonych przecinkami wewnątrz nawiasoacutew klamrowych

int tablica[3] = 123

Może to się wydać dziwne ale po ostatnim elemencie tablicy może występować przeci-nek Ponadto jeżeli poda się tylko część wartości w pozostałe wpisywane są zera

107

108 ROZDZIAŁ 16 TABLICE

int tablica[20] = 1

Niekoniecznie trzeba podawać rozmiar tablicy np

int tablica[] = 1 2 3 4 5

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku mdash elemen-toacutew)

Rozpatrzmy następujący kod

include ltstdiohgtdefine ROZMIAR 3int main()

int tab[ROZMIAR] = 368int iprintf (Druk tablicy tabn)

for (i=0 iltROZMIAR ++i) printf (Element numer d = dn i tab[i])

return 0

Wynik

Druk tablicy tabElement numer 0 = 3Element numer 1 = 6Element numer 2 = 8

Jak widać wszystko się zgadza W powyżej zamieszczonym przykładzie użyliśmy stałejdo podania rozmiaru tablicy Jest to o tyle pożądany zwyczaj że w razie konieczności zmianyrozmiaru tablicy zmieniana jest tylko jedna linijka kodu przy stałej a nie kilkadziesiąt innychlinijek rozsianych po kodzie całego programu

W pierwotnym standardzie języka C rozmiar tablicy nie moacutegł być określany przezzmienną lub nawet stałą zadeklarowaną przy użyciu słowa kluczowego const Dopiero wpoacuteźniejszej wersji standardu (tzw C) dopuszczono taką możliwość Dlatego do dekla-rowania rozmiaru tablic często używa się dyrektywy preprocesora define Powinni na tozwroacutecić uwagę zwłaszcza programiści C++ gdyż tam zawsze możliwe były oba sposoby

Innym sposobem jest użycie operatora sizeof do poznania wielkości tablicy Poniższy kodrobi to samo co przedstawiony

include ltstdiohgtint main()

int tab[3] = 368int i

162 ODCZYTZAPIS WARTOŚCI DO TABLICY 109

printf (Druk tablicy tabn)

for (i=0 ilt(sizeof tab sizeof tab) ++i) printf (Element numer d = dn i tab[i])

return 0

Należy pamiętać że działa on tylko dla tablic a nie wskaźnikoacutew (jak poacuteźniej się dowieszwskaźnik też można w pewnym stopniu traktować jak tablicę)

162 Odczytzapis wartości do tablicyTablicami posługujemy się tak samo jak zwykłymi zmiennymi Roacuteżnica polega jedynie napodaniu indeksu tablicy Określa on jednoznacznie z ktoacuterego elementu (wartości) chcemyskorzystać Indeksem jest liczba naturalna począwszy od zera To oznacza że pierwszy ele-ment tablicy ma indeks roacutewny drugi trzeci itd

Osoby ktoacutere wcześniej programowały w językach takich jak Pascal Basic czy Fortranmuszą przyzwyczaić się do tego że w języku C indeks numeruje się od Ponadto indeksempowinna być liczba - istnieje możliwość indeksowania za pomocą np pojedynczych znakoacutew(rsquoarsquo rsquobrsquo itp) jednak Cwewnętrznie konwertuje takie znaki na liczby im odpowiadające zatemtablica indeksowana znakami byłaby niepraktyczna i musiałaby mieć odpowiednio większyrozmiar

Sproacutebujmy przedstawić to na działającym przykładzie Przeanalizuj następujący kod

int tablica[5] = 0int i = 0tablica[2] = 3tablica[3] = 7for (i=0i=5++i)

printf (tablica[d]=dn i tablica[i])

Jak widać na początku deklarujemy -elementową tablicę ktoacuterą od razu zerujemy Na-stępnie pod trzeci i czwarty element podstawiamy liczby i Pętla ma za zadanie wypro-wadzić wynik naszych działań

163 Tablice znakoacutewTablice znakoacutew tj typu char oraz unsigned char posiadają dwie ogoacutelnie przyjęte nazwyzależnie od ich przeznaczenia

bufory mdash gdy wykorzystujemy je do przechowywania ogoacutelnie pojętych danych gdytraktujemy je jako po prostu ldquociągi bajtoacutewrdquo (typ char ma rozmiar bajta więc jestelastyczny do przechowywania np danych wczytanych z pliku przed ich przetworze-niem)

napisy mdash gdy zawarte w nich dane traktujemy jako ciągi liter jest im poświęconyosobny rozdział Napisy

110 ROZDZIAŁ 16 TABLICE

164 Tablice wielowymiarowe

Rysunek tablica dwuwymia-rowa (x)

Rozważmy teraz konieczność przechowania w pa-mięci komputera całej macierzy o wymiarach x Można by tego dokonać tworząc osobnych ta-blic jednowymiarowych reprezentujących poszcze-goacutelne wiersze macierzy Jednak język C dostarczanam dużo wygodniejszej metody ktoacutera w dodatkujest bardzo łatwa w użyciu Są to tablice wielowy-miarowe lub inaczej ldquotablice tablicrdquo Tablice wielo-wymiarowe definiujemy podając przy zmiennej kilkawymiaroacutew np

float macierz[10][10]

Tak samo wygląda dostęp do poszczegoacutelnych ele-mentoacutew tablicy

macierz[2][3] = 12

Jakwidać ten sposoacuteb jest dużowygodniejszy (i za-pewne dużo bardziej ldquonaturalnyrdquo) niż deklarowanie osobnych tablic jednowymiarowych Aby zaini-cjować tablicę wielowymiarową należy zastosowaćzagłębianie klamer np

float macierz[3][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz

Dodatkowo pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowy-miarowych) i woacutewczas kompilator sam ustali odpowiednią wielkość np

float macierz[][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz 63 27 57 27 czwarty wiersz

Innym bardziej elastycznym sposobem deklarowania tablic wielowymiarowych jest uży-cie wskaźnikoacutew Opisane to zostało w następnym rozdziale

165 Ograniczenia tablicPomimo swej wygody tablice mają ograniczony z goacutery zdefiniowany rozmiar ktoacuterego niemożna zmienić w trakcie działania programu Dlatego też w niektoacuterych zastosowaniach ta-blice zostały wyparte przez dynamiczną alokację pamięci Opisane to zostało w następnymrozdziale

166 CIEKAWOSTKI 111

Przy używaniu tablic trzeba być szczegoacutelnie ostrożnym przy konstruowaniu pętli ponie-waż ani kompilator ani skompilowany program nie będą w stanie wychwycić przekroczeniaprzez indeks rozmiaru tablicy 1 Efektem będzie odczyt lub zapis pamięci znajdującej się pozatablicą

Wystarczy pomylić się o jednomiejsce (tzw błąd off by one) by spowodować że działanieprogramu zostanie nagle przerwane przez system operacyjny

int foo[100]int i

for (i=0 ilt=100 ++i) powinno być ilt100 foo[i] = 0

166 CiekawostkiW pierwszej edycji konkursu IOCCC zwyciężył program napisany w C ktoacutery wyglądał dośćnietypowo

short main[] = 277 04735 -4129 25 0 477 1019 0xbef 0 12800-113 21119 0x52d7 -1006 -7151 0 0x4bc 02000414880 10541 2056 04010 4548 3044 -6716 0x94407 6 5568 1 -30460 0 0x9 5570 512 -304190x7e82 0760 6 0 4 02400 15 0 4 1280 4 04 0 0 0 0x8 0 4 0 0 12 0 4 0 0 020 0 4 0 30 0 026 0 0x6176 120 25712p 072163 r 29303 29801 e

Co ciekawe mdash program ten bez przeszkoacuted wykonywał się na komputerach VAX- orazPDP- Cały program to po prostu tablica z zawartym wewnątrz kodem maszynowym Taknaprawdę jest to wykorzystanie pewnych właściwości programu ktoacutery ostatecznie produ-kuje kod maszynowy Linker (to o nim mowa) nie rozroacuteżnia na dobrą sprawę nazw funkcjiod nazw zmiennych więc bez problemu ustawił punkt wejścia programu na tablicę wartościw ktoacuterych zapisany był kod maszynowy Tak przygotowany program został bez problemuwykonany przez komputer

1W zasadzie kompilatory mają możliwość dodania takiego sprawdzania ale nie robi się tego gdyż znaczniespowolniłoby to działanie programu Takie postępowanie jest jednak pożądane w okresie testowania programu

112 ROZDZIAŁ 16 TABLICE

Rozdział 17

Wskaźniki

Zobacz w WikipediiZmienna wskaźnikowaZmienne w komputerze są przechowywane w pamięci To wie każdy programista a dobry

programista potrafi kontrolować zachowanie komputera w przydzielaniu i obsługi pamięcidla zmiennych W tym celu pomocne są wskaźniki

171 Co to jest wskaźnik

Dla ułatwienia przyjęto poniżej że bajt ma bitoacutew typ int składa się z dwoacutech bajtoacutew(bitoacutew) typ long składa się z czterech bajtoacutew ( bitoacutew) oraz liczby zapisane są w formaciebig endian (tzn bardziej znaczący bajt na początku) co niekoniecznie musi być prawdą naTwoim komputerze

Rysunek Wskaźnik awskazu-jący na zmienną b Zauważmy żeb przechowuje liczbę podczas gdya przechowuje adres b w pamięci()

Wskaźnik (ang pointer) to specjalny rodzaj zmiennejw ktoacuterej zapisany jest adres w pamięci komputera tznwskaźnik wskazuje miejsce gdzie zapisana jest jakaśinformacja Oczywiście nic nie stoi na przeszkodzie abywskazywaną daną był innywskaźnik do kolejnegomiej-sca w pamięci

Obrazowo możemy wyobrazić sobie pamięć kom-putera jako bibliotekę a zmienne jako książki Zamiastbrać książkę z poacutełki samemu (analogicznie do korzy-stania wprost ze zwykłych zmiennych) możemy podaćbibliotekarzowi wypisany rewers z numerem katalogo-wym książki a on znajdzie ją za nas Analogia ta niejest doskonała ale pozwalawyobrazić sobie niektoacutere ce-chy wskaźnikoacutew kilka rewersoacutew może dotyczyć tej sa-mej książki numer w rewersie możemy skreślić i użyćgo do zamoacutewienia innej książki jeśli wpiszemy niepra-widłowy numer katalogowy to możemy dostać nie tąksiążkę ktoacuterą chcemy albo też nie dostać nic

Warto też poznać w tym miejscu definicję adresupamięci Możemy powiedzieć że adres to pewna liczba całkowita jednoznacznie definiującapołożenie pewnego obiektu (czyli np znaku czy liczby) w pamięci komputera Dokładniejsządefinicję możesz znaleźć w Wikipedii

113

114 ROZDZIAŁ 17 WSKAŹNIKI

172 Operowanie na wskaźnikaBy stworzyć wskaźnik do zmiennej i moacutec się nim posługiwać należy przypisać mu odpo-wiednią wartość (adres obiektu na jaki ma wskazywać) Skąd mamy znać ten adres Wy-starczy zapytać nasz komputer jaki adres przydzielił zmiennej ktoacuterą np wcześniej gdzieśstworzyliśmy Robi się to za pomocą operatora amp (operatora pobrania adresu) Przeanalizujnastępujący kod1

include ltstdiohgt

int main (void)

int liczba = 80printf(Zmienna znajduje sie pod adresem p i przechowuje wartosc dn

(void)ampliczba liczba)return 0

Program ten wypisuje adres pamięci pod ktoacuterym znajduje się zmienna oraz wartość jakąkryje zmienna przechowywana pod owym adresem

Aby moacutec zapisać gdzieś taki adres należy zadeklarować zmienną wskaźnikową Robi sięto poprzez dodanie (gwiazdki) po typie na jaki zmienna ma wskazywać np

int wskaznik1char wskaznik2floatwskaznik3

Niektoacuterzy programiści mogą nieco błędnie interpretować wskaźnik do typu jako nowytyp i uważać że jeśli napiszą

int abc

to otrzymają trzy wskaźniki do liczby całkowitej Tymczasem wskaźnikiem będzie tylkozmienna a natomiast b i c będą po prostu liczbami Powodem jest to że rdquogwiazdkaodnosi siędo zmiennej a nie do typu W tym przypadku trzy wskaźniki otrzymamy pisząc

int abc

Aby uniknąć pomyłek lepiej jest pisać gwiazdkę tuż przy zmiennej

int abc

albo jeszcze lepiej nie mieszać deklaracji wskaźnikoacutew i zmiennych

int aint bc

Aby dobrać się dowartości wskazywanej przez wskaźnik należy użyć unarnego operatora (gwiazdka) zwanego operatorem wyłuskania

1Warto zwroacutecić uwagę na rzutowanie do typu wskaźnik na void Rzutowanie to jest wymagane przez funkcjęprintf gdyż ta oczekuje że argumentem dla formatu p będzie właśnie wskaźnik na void gdy tymczasem w naszymprzykładzie wyrażenie ampliczba jest typu wskaźnik na int

172 OPEROWANIE NA WSKAŹNIKACH 115

include ltstdiohgt

int main (void)

int liczba = 80int wskaznik = ampliczbaprintf(Wartosc zmiennej d jej adres pn liczba (void)ampliczba)printf(Adres zapisany we wskazniku p wskazywana wartosc dn

(void)wskaznik wskaznik)

wskaznik = 42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

liczba = 0x42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

return 0

1721 O coodzi z tym typem na ktoacutery ma wskazywać Czemu to takieważne

Jest to ważne z kilku powodoacutewRoacuteżne typy zajmują w pamięci roacuteżną wielkość Przykładowo jeżeli w zmiennej typu

unsigned int zapiszemy liczbę to w pamięci będzie istnieć jako

+--------+--------+|komoacuterka1|komoacuterka2|+--------+--------+|11111111|11111010| = (unsigned int) 65530+--------+--------+

Wskaźnik do takiej zmiennej (jak i do dowolnej innej) będzie wskazywać na pierwsząkomoacuterkę w ktoacuterej ta zmienna ma swoją wartość

Jeżeli teraz stworzymy drugi wskaźnik do tego adresu tym razem typu unsigned arto wskaźnik przejmie ten adres prawidłowo2 lecz gdy sproacutebujemy odczytać wartość na jakąwskazuje ten wskaźnik to zostanie odczytana tylko pierwsza komoacuterka i wynik będzie roacutewny

+--------+|komoacuterka1|+--------+|11111111| = (unsigned char) 255+--------+

2Tak naprawdę nie zawsze można przypisywać wartości jednych wskaźnikoacutew do innych Standard C gwaran-tuje jedynie że można przypisać wskaźnikowi typu void wartość dowolnego wskaźnika a następnie przypisać tąwartość do wskaźnika pierwotnego typu oraz że dowolny wskaźnik można przypisać do wskaźnika typu char

116 ROZDZIAŁ 17 WSKAŹNIKI

Gdybyśmy natomiast stworzyli inny wskaźnik do tego adresu tym razem typu unsignedlong to przy proacutebie odczytu odczytane zostaną dwa bajty z wartością zapisaną w zmiennejunsigned int oraz dodatkowe dwa bajty z niewiadomą zawartością i woacutewczas wynik będzieroacutewny + przypadkowa wartość

+--------+--------+--------+--------+|komoacuterka1|komoacuterka2|komoacuterka3|komoacuterka4|+--------+--------+--------+--------+|11111111|11111010|||+--------+--------+--------+--------+

Ponadto zapis czy odczyt poza przydzielonym obszarem pamięci może prowadzić donieprzyjemnych skutkoacutew takich jak zmiana wartości innych zmiennych czy wręcz natych-miastowe przerwanie programu Jako przykład można podać ten (błędny) program3

include ltstdiohgt

int main(void)

unsigned char tab[10] = 100 101 102 103 104 105 106 107 108 109 unsigned short ptr = (unsigned short)amptab[2]unsigned i

ptr = 0xfffffor (i = 0 i lt 10 ++i)

printf(dn tab[i])tab[i] = tab[i] - 100

printf(poza tablica dn tab[10])tab[10] = -1return 0

Nie można roacutewnież zapominać że na niektoacuterych architekturach dane wielobajtowe mu-szą być odpowiednio wyroacutewnane w pamięci Np zmienna dwubajtowa może się znajdowaćjedynie pod parzystymi adresami Woacutewczas gdybyśmy chcieli adres zmiennej jednobajto-wej przypisać wskaźnikowi na zmienną dwubajtową mogłoby dojść do nieprzewidzianychbłędoacutew wynikających z proacuteby odczytu niewyroacutewnanej danej

Zaskakującemoże się okazać że roacuteżnewskaźniki mogąmieć roacuteżny rozmiar Np wskaźnikna ar może być większy od wskaźnika na int ale roacutewnież na odwroacutet Co więcej wskaźnikiroacuteżnych typoacutewmogą się roacuteżnić reprezentacją adresoacutew Dla przykładuwskaźnik naar możeprzechowywać adres do bajtu natomiast wskaźnik na int ten adres podzielony przez

Podsumowując roacuteżne wskaźniki to roacuteżne typy i nie należy beztrosko rzutować wyrażeńpomiędzy roacuteżnymi typami wskaźnikowymi bo grozi to nieprzewidywalnymi błędami

1722 Do czego służy typ void

Czasami zdarza się że nie wiemy na jaki typwskazuje danywskaźnik W takich przypadkachstosujemy typ void Sam void nie znaczy nic natomiast void oznacza ldquowskaźnik na obiekt

3Może się okazać że błąd nie będzie widoczny na Twoim komputerze

173 ARYTMETYKA WSKAŹNIKOacuteW 117

w pamięci niewiadomego typurdquo Taki wskaźnik możemy potem odnieść do konkretnego typudanych (w języku C++ wymagana jest do tego operacja rzutowania) Na przykład funkcjamalloc zwraca właśnie wskaźnik za pomocą void

173 Arytmetyka wskaźnikoacutew

W języku C do wskaźnikoacutew można dodawać lub odejmować liczby całkowite Istotne jestjednak że dodanie do wskaźnika liczby nie spowoduje przesunięcia się w pamięci kom-putera o dwa bajty Tak naprawdę przesuniemy się o rozmiar zmiennej Jest to bardzoważna informacja Początkujący programiści popełniają często dużo błędoacutew związanych znieprawidłową arytmetyką wskaźnikoacutew

Zobaczmy na przykład

int ptrint a[] = 1 2 3 5 7ptr = ampa[0]

Rysunek 172 Wskaźnik wskazuje na pierwszą komoacuterkę pamięci

Otrzymujemy następującą sytuacjęGdy wykonamy

ptr += 2

Rysunek 173 Przesunięcie wskaźnika na kolejne komoacuterki

wskaźnik ustawi się na trzecim elemencie tablicyWskaźniki można roacutewnież od siebie odejmować czego wynikiem jest odległość dwoacutech

wskazywanych wartości Odległość zwracana jest jako liczba obiektoacutew danego typu a nieliczba bajtoacutew Np

int a[] = 1 2 3 5 7int ptr = ampa[2]int diff = ptr - a diff ma wartość 2 (a nie 2sizeof(int))

118 ROZDZIAŁ 17 WSKAŹNIKI

Wynikiem może być oczywiście liczba ujemna Operacja jest przydatna do obliczaniawielkości tablicy (długości łańcucha znakoacutew) jeżeli mamy wskaźnik na jej pierwszy i ostatnielement

Operacje arytmetyczne na wskaźnikach mają pewne ograniczenia Przede wszystkim niemożna (tzn standard tego nie definiuje) skonstruować wskaźnika wskazującego gdzieś pozazadeklarowaną tablicę chyba że jest to obiekt zaraz za ostatnim (one past last) np

int a[] = 1 2 3 5 7int ptrptr = a + 10 niezdefiniowane ptr = a - 10 niezdefiniowane ptr = a + 5 zdefiniowane (element za ostatnim) ptr = 10 to już nie

Nie można4 roacutewnież odejmować od siebie wskaźnikoacutew wskazujących na obiekty znajdu-jące się w roacuteżnych tablicach np

int a[] = 1 2 3 b[] = 5 7int ptr1 = a ptr2 = bint diff = a - b niezdefiniowane

174 Tablice a wskaźniki

Trzeba wiedzieć że tablice to też rodzaj zmiennej wskaźnikowej Taki wskaźnik wskazuje namiejsce w pamięci gdzie przechowywany jest jej pierwszy element Następne elementy znaj-dują się bezpośrednio w następnych komoacuterkach pamięci w odstępie zgodnym z wielkościąodpowiedniego typu zmiennej

Na przykład tablica

int tab[] = 100200300

występuje w pamięci w sześciu komoacuterkach5

+--------+--------+--------+--------+--------+--------+|wartosc1| |wartosc2| |wartosc3| |+--------+--------+--------+--------+--------+--------+|00000000|01100100|00000000|11001000|00000001|00101100|+--------+--------+--------+--------+--------+--------+

Stąd do trzeciej wartości można się dostać tak (komoacuterki w tablicy numeruje się od zera)

zmienna = tab[2]

albo wykorzystując metodę wskaźnikową

zmienna = (tab + 2)

4To znaczy standard nie definiuje co się wtedy stanie aczkolwiek na większości architektur odejmowanie do-wolnych dwoacutech wskaźnikoacutew ma zdefiniowane zachowanie Pisząc przenośne programy nie można jednak na tympolegać zwłaszcza że odejmowanie wskaźnikoacutew wskazujących na elementy roacuteżnych tablic zazwyczaj nie ma sensu

5Ponownie przyjmując że bajt ma 8 bitoacutew int dwa bajty i liczby zapisywane są w formacie lile endian

175 GDY ARGUMENT JEST WSKAŹNIKIEM 119

Z definicji obie te metody są roacutewnoważneZ definicji (z wyjątkiem użycia operatora sizeo) wartością zmiennej lub wyrażenia typu tablico-

wego jest wskaźnik na jej pierwszy element (tab == amptab[0])Co więcej można poacutejść w drugą stronę i potraktować wskaźnik jak tablicę

int wskaznikwskaznik = amptab[1] lub wskaznik = tab + 1 zmienna = wskaznik[1] przypisze 300

Jako ciekawostkę podamy iż w języku C można odnosić się do elementoacutew tablicy jeszcze w innysposoacuteb

printf (dn 1[tab])

Skąd ta dziwna notacja Uzasadnienie jest proste

tab[1] = (tab + 1) = (1 + tab) = 1[tab]

Podobną składnię stosuje min asembler GNU

175 Gdy argument jest wskaźnikiem Czasami zdarza się że argumentem (lub argumentami) funkcji są wskaźniki W przypadku ldquonormal-nychrdquo zmiennych nasza funkcja działa tylko na lokalnych kopiach tychże argumentoacutew natomiast niezmienia zmiennych ktoacutere zostały podane jako argument Natomiast w przypadku wskaźnika każdaoperacja na wartości wskazywanej powoduje zmianę wartości zmiennej zewnętrznej Sproacutebujmy roz-patrzeć poniższy przykład

include ltstdiohgt

void func (int zmienna)

zmienna = 5

int main ()

int z=3printf (z=dn z) wypisze 3 func(ampz)printf (z=dn z) wypisze 5

Widzimy że funkcje w języku C nie tylko potrafią zwracać określoną wartość lecz także zmieniaćdane podane im jako argumenty Ten sposoacuteb przekazywania argumentoacutew do funkcji jest nazywanyprzekazywaniem przez wskaźnik (w przeciwieństwie do normalnego przekazywania przez wartość)

Zwroacutećmy uwagę na wywołanie func(ampz) Należy pamiętać by do funkcji przekazać adres zmien-nej a nie samą zmienną Jeśli byśmy napisali func(z) to funkcja starałaby się zmienić komoacuterkę pamięcio numerze Kompilator powinien ostrzec w takim przypadku o konwersji z typu int do wskaźnikaale często kompiluje taki program pozostając na ostrzeżeniu

Nie gra roli czy przy deklaracji funkcji jako argument funkcji podamy wskaźnik czy tablicę (z po-danym rozmiarem lub nie) np poniższe deklaracje są identyczne

120 ROZDZIAŁ 17 WSKAŹNIKI

void func(int ptr[])void func(int ptr)

Można przyjąć konwencję że deklaracja określa czy funkcji przekazujemy wskaźnik do pojedyn-czego argumentu czy do sekwencji ale roacutewnie dobrze można za każdym razem stosować gwiazdkę

176 Pułapki wskaźnikoacutewWażne jest aby przy posługiwaniu się wskaźnikami nigdy nie proacutebować odwoływać się do komoacuterkiwskazywanej przez wskaźnik o wartości lub niezainicjowany wskaźnik Przykładem nieprawi-dłowego kodu może być np

int wskprintf (zawartosc komorki dn (wsk)) Błąd wsk = 0 0 w kontekście wskaźnikoacutew oznacza wskaźnik NULL printf (zawartosc komorki dn (wsk)) Błąd

Należy roacutewnież uważać aby nie odwoływać się do komoacuterek poza przydzieloną pamięcią np

int tab[] = 0 1 2 tab[3] = 3 Błąd

Pamiętaj też że możesz być rozczarowany używając operatora sizeof podając zmienną wskaźni-kową Uzyskana wielkość będzie wielkością wskaźnika a nie wielkością typu użytego podczas deklaro-wania naszego wskaźnika Wielkość ta będzie zawsze miała taki sam rozmiar dla każdego wskaźnikaw zależności od kompilatora a także docelowej platformy Zamiast tego używaj sizeof(wskaźnik)Przykład

char zmiennaint a = sizeof zmienna a wynosi np 4 tj sizeof(char) a = sizeof(char) robimy to samo co wyżej a = sizeof zmienna zmienna a ma teraz przypisany rozmiar

pojedynczego znaku tj 1 a = sizeof(char) robimy to samo co wyżej

177 Na co wskazuje Analizując kody źroacutedłowe programoacutew często można spotkać taki oto zapis

void wskaznik = NULL lub = 0

Wiesz już że nie możemy odwołać się pod komoacuterkę pamięci wskazywaną przez wskaźnik Poco zatem przypisywać wskaźnikowi Odpowiedź może być zaskakująca właśnie po to aby uniknąćbłędoacutew Wydaje się to zabawne ale większość (jeśli nie wszystkie) funkcji ktoacutere zwracają wskaźnikw przypadku błędu zwroacuteci właśnie czyli zero Tutaj rodzi się kolejna wskazoacutewka jeśli w danejzmiennej przechowujemy wskaźnik zwroacutecony wcześniej przez jakąś funkcję zawsze sprawdzajmy czynie jest on roacutewny () Wtedy mamy pewność że funkcja zadziałała poprawnie

Dokładniej nie jest słowem kluczowym lecz stałą (makrem) zadeklarowaną przez dyrektywypreprocesora Deklaracja taka może być albo wartością albo też wartością zrzutowaną na void(((void )0)) ale też jakimś słowem kluczowym deklarowanym przez kompilator

Warto zauważyć że pomimo przypisywania wskaźnikowi zera nie oznacza to że wskaźnik jest reprezentowany przez same zerowe bity Co więcej wskaźniki roacuteżnych typoacutew mogą miećroacuteżną wartość Z tego powodu poniższy kod jest niepoprawny

int tablica_wskaznikow = calloc(100 sizeof tablica_wskaznikow)

178 STAŁE WSKAŹNIKI 121

Zakłada on że w reprezentacji wskaźnika występują same zera Poprawnym zainicjowaniemdynamicznej tablicy wskaźnikoacutew wartościami jest (pomijamy sprawzdanie wartości zwroacuteconejprzez malloc())

int tablica_wskaznikow = malloc(100 sizeof tablica_wskaznikow)int i = 0while (ilt100)

tablica_wskaznikow[i++] = 0

178 Stałe wskaźnikiTak jak istnieją zwykłe stałe tak samo możemy mieć stałe wskaźniki mdash jednak są ich dwa rodzajeWskaźniki na stałą wartość

const int a lub roacutewnoważnie int const a

oraz stałe wskaźniki

int const b

Pierwszy to wskaźnik ktoacuterym nie można zmienić wskazywanej wartości Drugi to wskaźnik ktoacute-rego nie można przestawić na inny adres Dodatkowo można zadeklarować stały wskaźnik ktoacuterym niemożna zmienić wartości wskazywanej zmiennej i roacutewnież można zrobić to na dwa sposoby

const int const c alternatywnie int const const c

int i=0const int a=ampiint const b=ampiint const const c=ampia = 1 kompilator zaprotestuje b = 2 ok c = 3 kompilator zaprotestuje a = b ok b = a kompilator zaprotestuje c = a kompilator zaprotestuje

Wskaźniki na stałą wartość są przydatne między innymi w sytuacji gdy mamy duży obiekt (naprzykład strukturę z kilkoma polami) Jeśli przypiszemy taką zmienną do innej zmiennej kopiowaniemoże potrwać dużo czasu a oproacutecz tego zostanie zajęte dużo pamięci Przekazanie takiej struktury dofunkcji albo zwroacutecenie jej jako wartość funkcji wiąże się z takim samym narzutem W takim wypadkudobrze jest użyć wskaźnika na stałą wartość

void funkcja(const duza_struktura ds)

czytamy z ds i wykonujemy obliczenia

funkcja(ampdane) mamy pewność że zmienna dane nie zostanie zmieniona

122 ROZDZIAŁ 17 WSKAŹNIKI

179 Dynamiczna alokacja pamięciMając styczność z tablicami można się zastanowić czy nie dałoby się mieć tablic ktoacuterych rozmiardostosowuje się do naszych potrzeb a nie jest na stałe zaszyty w kodzie programu Chcąc pomieścićwięcej danych możemy po prostu zwiększyć rozmiar tablicy mdash ale gdy do przechowania będzie mniejelementoacutew okaże się że marnujemy pamięć Język C umożliwia dzięki wskaźnikom i dynamicznejalokacji pamięci tworzenie tablic takiej wielkości jakiej akurat potrzebujemy

1791 O co odziCzym jest dynamiczna alokacja pamięci Normalnie zmienne programu przechowywane są na tzwstosie (ang sta) mdash powstają gdy program wchodzi do bloku w ktoacuterym zmienne są zadeklarowane azwalniane w momencie kiedy program opuszcza ten blok Jeśli deklarujemy tak tablice to ich rozmiarmusi być znanywmomencie kompilacji mdash żeby kompilator wygenerował kod rezerwujący odpowiedniąilość pamięci Dostępny jest jednak drugi rodzaj rezerwacji (czyli alokacji) pamięci Jest to alokacja nastercie (ang heap) Sterta to obszar pamięci wspoacutelny dla całego programu przechowywane są w nimzmienne ktoacuterych czas życia nie jest związany z poszczegoacutelnymi blokami Musimy sami rezerwować dlanich miejsce i to miejsce zwalniać ale dzięki temu możemy to zrobić w dowolnym momencie działaniaprogramu

Należy pamiętać że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analo-giczne działania na stosie Dodatkowo zmienna zajmuje na stercie więcej miejsca niż na stosie mdash stertautrzymuje specjalną strukturę w ktoacuterej trzymane są wolne partie (może to być np lista) Tak więcużywajmy dynamicznej alokacji tam gdzie jest potrzebna mdash dla danych ktoacuterych rozmiaru nie jesteśmyw stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem w ktoacuterymzostały zaalokowane

1792 Obsługa pamięciPodstawową funkcją do rezerwacji pamięci jest funkcja malloc Jest to niezbyt skomplikowana funkcjamdash podając jej rozmiar (w bajtach) potrzebnej pamięci dostajemy wskaźnik do zaalokowanego obszaru

Załoacuteżmy że chcemy stworzyć tablicę liczb typu float

int rozmiarfloat tablica

rozmiar = 3tablica = (float) malloc(rozmiar sizeof tablica)tablica[0] = 01

Przeanalizujmy teraz po kolei co dzieje się w powyższym fragmencie Najpierw deklarujemyzmiennemdash rozmiar tablicy i wskaźnik ktoacutery będzie wskazywał obszarw pamięci gdzie będzie trzymanatablica Do zmiennej rozmiar możemy w trakcie działania programu przypisać cokolwiek mdash wczytaćją z pliku z klawiatury obliczyć wylosować mdash nie jest to istotne rozmiar sizeof tablica obliczapotrzebną wielkość tablicy Dla każdej zmiennej float potrzebujemy tyle bajtoacutew ile zajmuje ten typdanych Ponieważ może się to roacuteżnić na rozmaitych maszynach istnieje operator sizeof zwracającydla danego wyrażenia rozmiar jego typu w bajtach

W wielu książkach (roacutewnież KampRv) i w Internecie stosuje się inny schemat użycia funkcji malloca mianowicie tablica = (float)malloc(rozmiar sizeof(float)) Takie użycie należy traktowaćjako błędne gdyż nie sprzyja ono poprawnemu wykrywaniu błędoacutew

Rozważmy sytuację gdy programista zapomni dodać plik nagłoacutewkowy stdlibh woacutewczas kompila-tor (z braku deklaracji funkcji malloc) przyjmie że zwraca ona typ int zatem do zmiennej tablica (ktoacuterajest wskaźnikiem) będzie przypisywana liczba całkowita co od razu spowoduje błąd kompilacji (a przy-najmniej ostrzeżenie) dzięki czemu będzie można szybko poprawić kod programu Rzutowanie jestkonieczne tylko w języku C++ gdzie konwersja z void na inne typy wskaźnikowe nie jest domyślnaale język ten oferuje nowe sposoby alokacji pamięci

179 DYNAMICZNA ALOKACJA PAMIĘCI 123

Teraz rozważmy sytuację gdy zdecydujemy się zwiększyć dokładność obliczeń i zamiast typu floatużyć typu double Będziemy musieli wyszukać wszystkie wywołania funkcji malloc calloc i reallocodnoszące się do naszej tablicy i zmieniać wszędzie sizeof(float) na sizeof(double) Aby temu zapo-biec lepiej od razu użyć sizeof tablica (lub jeśli ktoś woli z nawiasami sizeof(tablica)) woacutewczaszmiana typu zmiennej tablica na double zostanie od razu uwzględniona przy alokacji pamięci

Dodatkowo należy sprawdzić czy funkcja malloc nie zwroacuteciła wartości mdash dzieje się tak gdyzabrakło pamięci Ale uwaga może się tak stać roacutewnież jeżeli jako argument funkcji podano zero

Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić aby sys-tem operacyjny moacutegł go przydzielić innym potrzebującym procesom Do zwolnienia obszaru pamięciużywamy funkcji free() ktoacutera przyjmuje tylko jeden argument mdash wskaźnik ktoacutery otrzymaliśmy wwyniku działania funkcji malloc()

free (addr)

Należy pamiętać o zwalnianiu pamięci mdash inaczej dojdzie do tzw wycieku pamięci mdash program będzierezerwował nową pamięć ale nie zwracał jej z powrotem i w końcu pamięci może mu zabraknąć

Należy też uważać by nie zwalniać dwa razy tego samegomiejsca Po wywołaniu free wskaźnik niezmienia wartości pamięć wskazywana przez niego może też nie od razu ulec zmianie Czasemmożemywięc korzystać ze wskaźnika (zwłaszcza czytać) po wywołaniu free nie orientując się że robimy coś źlemdash i w pewnym momencie dostać komunikat o nieprawidłowym dostępie do pamięci Z tego powoduzaraz po wywołaniu funkcji free można przypisać wskaźnikowi wartość

Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci Tu z pomocąprzychodzi funkcja realloc

tablica = realloc(tablica 2rozmiarsizeof tablica)

Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub gdy zabrakło pa-mięci) Uwaga mdash może to być inny wskaźnik Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanymaktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca funkcja znajdzie nowe miejscei przekopiuje tam starą zawartość Jak widać wywołanie tej funkcji może być więc kosztowne podwzględem czasu

Ostatnią funkcją jest funkcja calloc() Przyjmuje ona dwa argumenty liczbę elementoacutew tablicyoraz wielkość pojedynczego elementu Podstawową roacuteżnicą pomiędzy funkcjami malloc() i calloc() jestto że ta druga zeruje wartość przydzielonej pamięci (do wszystkich bajtoacutew wpisuje wartość )

1793 Tablice wielowymiarowe

Rysunek 174 tablica dwuwymiarowa mdash w rzeczywistości tablica ze wskaźnikami do tablic

W rozdziale Tablice pokazaliśmy jak tworzyć tablice wielowymiarowe gdy ich rozmiar jest znanyw czasie kompilacji Teraz zaprezentujemy jak to wykonać za pomocą wskaźnikoacutew i to w sytuacji gdyrozmiar może się zmieniać Załoacuteżmy że chcemy stworzyć tabliczkę mnożenia

124 ROZDZIAŁ 17 WSKAŹNIKI

int rozmiarint iint tabliczka

printf(Podaj rozmiar tabliczki mnozenia )scanf(i amprozmiar) dla prostoty nie będziemy sprawdzali

czy użytkownik wpisał sensowną wartość

tabliczka = malloc(rozmiar sizeof tabliczka) 1 for (i = 0 iltrozmiar ++i) 2

tabliczka[i] = malloc(rozmiar sizeof tabliczka) 3 4

for (i = 0 iltrozmiar ++i) int jfor (j = 0 jltrozmiar ++j)

tabliczka[i][j] = (i+1)(j+1)

Najpierw musimy przydzielić pamięć mdash najpierw dla ldquotablicy tablicrdquo () a potem dla każdej z pod-tablic osobno (-) Ponieważ tablica jest typu int to nasza tablica tablic będzie wskaźnikiem na intczyli int Podobnie osobno ale w odwrotnej kolejności będziemy zwalniać tablicę wielowymiarową

for (i = 0 iltrozmiar ++i) free(tabliczka[i])

free(tabliczka)

Należy nie pomylić kolejności po wykonaniu free(tabliczka) nie będziemy mieli prawa odwoły-wać się do tabliczka[i] (bo wcześniej dokonaliśmy zwolnienia tego obszaru pamięci)

Można także zastosować bardziej oszczędny sposoacuteb alokowania tablicy wielowymiarowej a mia-nowicie

define ROZMIAR 10int iint tabliczka = malloc(ROZMIAR sizeof tabliczka)tabliczka = malloc(ROZMIAR ROZMIAR sizeof tabliczka)for (i = 1 iltROZMIAR ++i)

tabliczka[i] = tabliczka[0] + (i ROZMIAR)

for (i = 0 iltROZMIAR ++i) int jfor (j = 0 jltROZMIAR ++j)

tabliczka[i][j] = (i+1)(j+1)

free(tabliczka)free(tabliczka)

Powyższy kod działa w ten sposoacuteb że zamiast dla poszczegoacutelnych wierszy alokować osobno pamięćalokuje pamięć dla wszystkich elementoacutew tablicy i dopiero poacuteźniej przypisuje wskazania poszczegoacutel-nych wskaźnikoacutew-wierszy na kolejne bloki po elementoacutew

1710 WSKAŹNIKI NA FUNKCJE 125

Sposoacuteb ten jest bardziej oszczędny z dwoacutech powodoacutew Po pierwsze wykonywanych jest mniej ope-racji przydzielania pamięci (bo tylko dwie) Po drugie za każdym razem gdy alokuje się pamięć trochęmiejsca się marnuje gdyż funkcja malloc musi w stogu przechowywać roacuteżne dodatkowe informacje natemat każdej zaalokowanej przestrzeni Ponadto czasami alokacja odbywa się blokami i gdy zażąda sięniepełny blok to reszta bloku jest tracona

Zauważmy że w ten sposoacuteb możemy uzyskać nie tylko normalną ldquokwadratowąrdquo tablicę (dla dwoacutechwymiaroacutew) Możliwe jest np uzyskanie tablicy troacutejkątnej

0123012010

lub tablicy o dowolnym innym rozkładzie długości wierszy np

const size_t wymiary[] = 2 4 6 8 1 3 5 7 9 int iint tablica = malloc((sizeof wymiary sizeof wymiary) sizeof tablica)for (i = 0 ilt10 ++i)

tablica[i] = malloc(wymiary[i] sizeof tablica)

Gdy nabierzesz wprawy w używaniu wskaźnikoacutew oraz innych funkcji malloc i realloc nauczyszsię wykonywać roacuteżne inne operacje takie jak dodawanie kolejnych wierszy usuwanie wierszy zmianarozmiaru wierszy zamiana wierszy miejscami itp

1710 Wskaźniki na funkcjeDotychczas zajmowaliśmy się sytuacją gdy wskaźnik wskazywał na jakąś zmienną Jednak nie tylkozmienna ma swoacutej adres w pamięci Oproacutecz zmiennej także i funkcja musi mieć swoje określone miejscew pamięci A ponieważ funkcja ma swoacutej adres6 to nie ma przeszkoacuted aby i na nią wskazywał jakiśwskaźnik

17101 Deklaracja wskaźnika na funkcjęTak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresufunkcji Wskaźnik na funkcję roacuteżni się od innych rodzajoacutew wskaźnikoacutew Jedną z głoacutewnych roacuteżnic jestjego deklaracja Zwykle wygląda ona tak

typ_zwracanej_wartości (nazwa_wskaźnika)(typ1 parametr1 typ2 parametr2)

Oczywiście parametroacutew może być więcej (albo też w ogoacutele może ich nie być) Oto przykład wyko-rzystania wskaźnika na funkcję

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

6Tak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresu funkcji

126 ROZDZIAŁ 17 WSKAŹNIKI

int (wsk_suma)(int a int b)wsk_suma = sumaprintf(4+5=dn wsk_suma(45))return 0

Zwroacutećmy uwagę na dwie rzeczy

przypisując nazwę funkcji bez nawiasoacutew do wskaźnika automatycznie informujemy kompilatorże chodzi nam o adres funkcji

wskaźnika używamy tak jak normalnej funkcji na ktoacuterą on wskazuje

17102 Do czego można użyć wskaźnikoacutew na funkcjeJęzyk C jest językiem strukturalnym jednak dzięki wskaźnikom istnieje w nim możliwość ldquozaszczepie-niardquo pewnych obiektowych właściwości Wskaźnik na funkcję może być np elementem struktury mdashwtedy mamy bardzo prymitywną namiastkę klasy ktoacuterą dobrze znają programiści piszący w językuC++ Ponadto dzięki wskaźnikom możemy tworzyć mechanizmy działające na zasadzie funkcji zwrot-nej7 Dobrym przykładem może być np tworzenie sterownikoacutew gdzie musimy poinformować roacuteżnepodsystemy jakie funkcje w naszym kodzie służą do wykonywania określonych czynności Przykład

struct urzadzenie int (otworz)(void)void (zamknij)(void)

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

int rejestruj_urzadzenie(struct urzadzenie u) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzenieotworz = moje_urzadzenie_otworzmoje_urzadzeniezamknij = moje_urzadzenie_zamknijrejestruj_urzadzenie(ampmoje_urzadzenie)

Wten sposoacutebwpamięci każda klasamusi przechowywaćwszystkiewskaźniki dowszystkichmetodInnym rozwiązaniem może być stworzenie statycznej struktury ze wskaźnikami do funkcji i woacutewczasw strukturze będzie przechowywany jedynie wskaźnik do tej struktury np

struct urzadzenie_metody

7Funkcje zwrotne znalazły zastosowanie głoacutewnie w programowaniu

1711 MOŻLIWE DEKLARACJE WSKAŹNIKOacuteW 127

int (otworz)(void)void (zamknij)(void)

struct urzadzenie const struct urzadzenie_metody m

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

static const struct urzadzenie_metodymoje_urzadzenie_metody = moje_urzadzenie_otworzmoje_urzadzenie_zamknij

int rejestruj_urzadzenie(struct urzadzenie ampu) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzeniem = ampmoje_urzadzenie_metodyrejestruj_urzadzenie(ampmoje_urzadzenie)

1711 Możliwe deklaracje wskaźnikoacutew

Tutaj znajduje się kroacutetkie kompendium jak definiować wskaźniki oraz co oznaczają poszczegoacutelne defi-nicje

1712 Popularne błędy

Jednym z najczęstszych błędoacutew oproacutecz proacuteb wykonania operacji na wskaźniku są odwołania siędo obszaru pamięci po jego zwolnieniu Po wykonaniu funkcji free() nie możemy już wykonywaćżadnych odwołań do zwolnionego obszaru Innym rodzajem błędoacutew są

odwołania do adresoacutew pamięci ktoacutere są poza obszarem przydzielonym funkcją malloc()

brak sprawdzania czy dany wskaźnik nie ma wartości

wycieki pamięci czyli niezwalnianie całej przydzielonej wcześniej pamięci

128 ROZDZIAŁ 17 WSKAŹNIKI

i zmienna całkowita (typu int) ip wskaźnik p wskazujący na zmienną całkowitąa[] tablica a liczb całkowitych typu intf() funkcja f zwracająca liczbę całkowitą typu intpp wskaźnik pp na wskaźnik wskazujący na liczbę całkowitą typu int

(pa)[] wskaźnik pa wskazujący na tablicę liczb całkowitych typu int(pf)() wskaźnik pf na funkcję zwracającą liczbę całkowitą typu intap[] tablica ap wskaźnikoacutew na liczby całkowite typu intfp() funkcja fp ktoacutera zwraca wskaźnik na zmienną typu intppp wskaźnik ppp wskazujący na wskaźnik wskazujący na wskaźnik wskazu-

jący na liczbę typu int(ppa)[] wskaźnik ppa na wskaźnik wskazujący na tablicę liczb całkowitych typu

int(ppf)() wskaźnik ppf wskazujący na wskaźnik funkcji zwracającej dane typu int(pap)[] wskaźnik pap wskazujący na tablicę wskaźnikoacutew na typ int(pfp)() wskaźnik pfp na funkcję zwracającą wskaźnik na typ intapp[] tablica wskaźnikoacutew app wskazujących na typ int

(apa[])[] tablica wskaźnikoacutew apa wskazujących wskaźniki na typ int(apf[])() tablica wskaźnikoacutew apf na funkcję ktoacutere zwracają wskaźniki na typ intfpp() funkcja fpp ktoacutera zwraca wskaźnik na wskaźnik na wskaźnik ktoacutery wska-

zuje typ int(fpa())[] funkcja fpa ktoacutera zwraca wskaźnik na tablicę liczb typu int(fpf())() funkcja fpf ktoacutera zwraca wskaźnik na funkcję ktoacutera zwraca dane typu int

1713 Ciekawostki w rozdziale Zmienne pisaliśmy o stałych Normalnie nie mamy możliwości zmiany ich wartości

ale z użyciem wskaźnikoacutew staje się to możliwe

const int CONST=0int c=ampCONSTc = 1printf(inCONST) wypisuje 1

Konstrukcja taka może jednak wywołać ostrzeżenie kompilatora bądź nawet jego błąd mdash wtedymoże pomoacutec jawne rzutowanie z const int na int

język C++ oferuje mechanizm podobny do wskaźnikoacutew ale nieco wygodniejszy ndash referencje

język C++ dostarcza też innego sposobu dynamicznej alokacji i zwalniania pamięci mdash przez ope-ratory new i delete

w rozdziale Typy złożone znajduje się opis implementacji listy za pomocą wskaźnikoacutew Przy-kład ten może być bardzo przydatny przy zrozumieniu po co istnieją wskaźniki jak się nimiposługiwać oraz jak dobrze zarządzać pamięcią

Rozdział 18

Napisy

W dzisiejszych czasach komputer przestał być narzędziem tylko i wyłącznie do przetwarzania danychOd programoacutew komputerowych zaczęto wymagać czegoś nowego mdash program w wyniku swojego dzia-łania nie ma zwracać danych rozumianych tylko przez autora programu lecz powinien być na tylekomunikatywny aby przeciętny użytkownik komputera moacutegł bez problemu tenże komputer obsłużyćDo przechowywania tychże komunikatoacutew służą tzw ldquołańcuchyrdquo (ang string) czyli ciągi znakoacutew

Język C nie jest wygodnym narzędziem do manipulacji napisami Jak się wkroacutetce przekonamyzestaw funkcji umożliwiających operacje na napisach w bibliotece standardowej C jest raczej skromnyDodatkowo problemem jest sposoacuteb w jaki łańcuchy przechowywane są w pamięci

Napisy w języku Cmogą być przyczyną wielu trudnych do wykrycia błędoacuteww programach Wartodobrze zrozumieć jak należy operować na łańcuchach znakoacutew i zachować szczegoacutelną ostrożność w tychmiejscach gdzie napisoacutew używamy

181 Łańcuy znakoacutew w języku CNapis jest zapisywany w kodzie programu jako ciąg znakoacutew zawarty pomiędzy dwoma cudzysłowami

printf (Napis w języku C)

Wpamięci taki łańcuch jest następującympo sobie ciągiem znakoacutew (char) ktoacutery kończy się znakiemldquonullrdquo (czyli po prostu liczbą zero) zapisywanym jako rsquorsquo

Jeśli mamy napis do poszczegoacutelnych znakoacutew odwołujemy się jak w tablicy

char tekst = Jakiś tam tekstprintf(cn przykład[0]) wypisze p - znaki w napisach są numerowane od zera printf(cn tekst[2]) wypisze k

Ponieważ napis w pamięci kończy się zerem umieszczonym tuż za jego zawartością odwołanie się doznaku o indeksie roacutewnym długości napisu zwroacuteci zero

printf(d test[4]) wypisze 0

Napisy możemywczytywać z klawiatury i wypisywać na ekran przy pomocy dobrze znanych funk-cji scanf printf i pokrewnych Formatem używanym dla napisoacutew jest s

printf(s tekst)

129

130 ROZDZIAŁ 18 NAPISY

Większość funkcji działających na napisach znajduje się w pliku nagłoacutewkowym stringhJeśli łańcuch jest zbyt długi można zapisać gow kilku linijkach ale wtedy przechodząc do następnej

linii musimy na końcu postawić znak ldquordquo

printf(Ten napis zajmuje więcej niż jedną linię)

Instrukcja taka wydrukuje

Ten napis zajmuje więcej niż jedną linię

Możemy zauważyć że napis ktoacutery w programie zajął więcej niż jedną linię na ekranie zajął tylkojedną Jest tak ponieważ ldquordquo informuje kompilator że łańcuch będzie kontynuowany w następnej liniikodumdash niemawpływu na prezentację łańcucha Abywydrukować napis w kilku liniach należywstawićdo niego n (ldquonrdquo pochodzi tu od ldquonew linerdquo czyli ldquonowa liniardquo)

printf(Ten napisnna ekranienzajmie więcej niż jedną linię)

W wyniku otrzymamy

Ten napisna ekraniezajmie więcej niż jedną linię

1811 Jak komputer przeowuje w pamięci łańcu

Rysunek 181 Napis ldquoMerkkijonordquo przechowywany w pamięci

Zmienna ktoacutera przechowuje łańcuch znakoacutew jest tak naprawdę wskaźnikiem do ciągu znakoacutew(bajtoacutew) w pamięci Możemy też myśleć o napisie jako o tablicy znakoacutew (jak wyjaśnialiśmy wcześniejtablice to też wskaźniki)

Możemy wygodnie zadeklarować napis

char tekst = Jakiś tam tekst Umieszcza napis w obszarze danych programu i przypisuje adres

char tekst[] = Jakiś tam tekst Umieszcza napis w tablicy char tekst[] = Jakis tam tekst0

Tekst to taka tablica jak każda inna

Kompilator automatycznie przydziela wtedy odpowiednią ilość pamięci (tyle bajtoacutew ile jest literplus jeden dla kończącego nulla) Jeśli natomiast wiemy że dany łańcuch powinien przechowywaćokreśloną ilość znakoacutew (nawet jeśli w deklaracji tego łańcucha podajemy mniej znakoacutew) deklarujemygo w taki sam sposoacuteb jak tablicę jednowymiarową

char tekst[80] = Ten tekst musi być kroacutetszy niż 80 znakoacutew

Należy cały czas pamiętać że napis jest tak naprawdę tablicą Jeśli zarezerwowaliśmy dla napisu znakoacutew to przypisanie do niego dłuższego napisu spowoduje pisanie po pamięci

Uwaga Deklaracja char tekst = cokolwiek oraz char tekst = cokolwiek pomimo że wyglądająbardzo podobnie bardzo się od siebie roacuteżnią W przypadku pierwszej deklaracji proacuteba zmodyfikowania

181 ŁAŃCUCHY ZNAKOacuteW W JĘZYKU C 131

napisu (np tekst[0] = C) może mieć nieprzyjemne skutki Dzieje się tak dlatego że char tekst =cokolwiek deklaruje wskaźnik na stały obszar pamięci1

Pisanie po pamięci może czasami skończyć się błędem dostępu do pamięci (ldquosegmentation faultrdquow systemach ) i zamknięciem programu jednak może zdarzyć się jeszcze gorsza ewentualność mdashmożemy zmienić w ten sposoacuteb przypadkowo wartość innych zmiennych Program zacznie wtedy za-chowywać się nieprzewidywalnie mdash zmienne a nawet stałe co do ktoacuterych zakładaliśmy że ich wartośćbędzie ściśle ustalona mogą przyjąć taką wartość jaka absolutnie nie powinna mieć miejsca Wartowięc stosować zabezpieczenia typu makra assert

Kluczowy jest też kończący napis znak null W zasadzie wszystkie funkcje operujące na napisachopierają właśnie na nim Na przykład strlen szuka rozmiaru napisu idąc od początku i zliczając znaki ażnie natrafi na znak o kodzie zero Jeśli nasz napis nie kończy się znakiem null funkcja będzie szła dalejpo pamięci Na szczęście wszystkie operacje podstawienia typu tekst = ldquoTekstrdquo powodują zakończenienapisu nullem (o ile jest na niego miejsce) 2

1812 Znaki specjalneJak zapewne zauważyłeś w poprzednim przykładzie w łańcuchu ostatnim znakiem jest znak o wartościzero (rsquorsquo) Jednak łańcuchy mogą zawierać inne znaki specjalne(sekwencje sterujące) np

rsquoarsquo - alarm (sygnał akustyczny terminala)

rsquobrsquo - backspace (usuwa poprzedzający znak)

rsquorsquo - wysuniecie strony (np w drukarce)

rsquorrsquo - powroacutet kursora (karetki) do początku wiersza

rsquonrsquo - znak nowego wiersza

rsquordquo - cudzysłoacutew

rsquordquo - apostrof rsquorsquo - ukośnik wsteczny (backslash)

rsquotrsquo - tabulacja pozioma

rsquovrsquo - tabulacja pionowa

rsquorsquo - znak zapytania (pytajnik)

rsquoooorsquo - liczba zapisana w systemie oktalnym (oacutesemkowym) gdzie rsquoooorsquo należy zastąpić trzycy-frową liczbą w tym systemie

rsquoxhhrsquo - liczba zapisana w systemie heksadecymalnym (szesnastkowym) gdzie rsquohhrsquo należy za-stąpić dwucyfrową liczbą w tym systemie

rsquounnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnrsquo należy zastąpić czterocyfrowym identyfika-torem znaku w systemie szesnatkowym rsquonnnnrsquo odpowiada dłuższej formie w postaci rsquonnnnrsquo

rsquounnnnnnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnnnnnrsquo należy zastąpić ośmiocyfrowymidentyfikatorem znaku w systemie szesnatkowym

Warto zaznaczyć że znak nowej linii (rsquonrsquo) jest w roacuteżny sposoacuteb przechowywany w roacuteżnych sys-temach operacyjnych Wiąże się to z pewnymi historycznymi uwarunkowaniami W niektoacuterych sys-temach używa się do tego jednego znaku o kodzie xA (Line Feed mdash nowa linia) Do tej rodzinyzaliczamy systemy z rodziny Unix Linux BSD Mac OS X inne Drugą konwencją jest zapisywaniersquonrsquo za pomocą dwoacutech znakoacutew LF (Line Feed) + CR (Carriage return mdash powroacutet karetki) Znak CRreprezentowany jest przez wartość xD Kombinacji tych dwoacutech znakoacutew używają min CPM DOSOS Microso Windows Trzecia grupa systemoacutew używa do tego celu samego znaku CR Są to sys-temy działające na komputerach Commodore Apple II oraz Mac OS do wersji W związku z tym plikutworzony w systemie Linux może wyglądać dziwnie pod systemem Windows

1Można się zatem zastanawiać czemu kompilator dopuszcza przypisanie do zwykłego wskaźnika wskazania nastały obszar skoro kod const int foo int bar = foo generuje ostrzeżenie lub wręcz się nie kompiluje Jest topewna zaszłość historyczna wynikająca z faktu że słoacutewko const zostało wprowadzone do języka gdy już był on wpowszechnym użyciu

2Nie należy mylić znaku null (czyli znaku o kodzie zero) ze wskaźnikiem null (czy też )

132 ROZDZIAŁ 18 NAPISY

182 Operacje na łańcua

1821 Poroacutewnywanie łańcuoacutewNapisy to tak naprawdęwskaźniki Tak więc używając zwykłego operatora poroacutewnania == otrzymamywynik poroacutewnania adresoacutew a nie tekstoacutew

Do poroacutewnywania dwoacutech ciągoacutew znakoacutew należy użyć funkcji strcmp zadeklarowanej w pliku na-głoacutewkowym stringh Jako argument przyjmuje ona dwa napisy i zwraca wartość ujemną jeżeli napispierwszy jestmniejszy od drugiego jeżeli napisy są roacutewne lub wartość dodatnią jeżeli napis pierwszyjest większy od drugiego Ciągi znakoacutew poroacutewnywalne są leksykalnie kody znakoacutew czyli np (przyj-mując kodowanie ASCII) a jest mniejsze od b ale jest większe od B Np

include ltstdiohgtinclude ltstringhgt

int main(void) char str1[100] str2[100]int cmp

puts(Podaj dwa ciagi znakow )fgets(str1 sizeof str1 stdin)fgets(str2 sizeof str2 stdin)

cmp = strcmp(str1 str2)if (cmplt0)

puts(Pierwszy napis jest mniejszy) else if (cmpgt0)

puts(Pierwszy napis jest wiekszy) else

puts(Napisy sa takie same)

return 0

Czasami możemy chcieć poroacutewnać tylko fragment napisu np sprawdzić czy zaczyna się od jakie-goś ciągu W takich sytuacjach pomocna jest funkcja strncmp W poroacutewnaniu do strcmp() przyjmujeona jeszcze jeden argument oznaczający maksymalną liczbę znakoacutew do poroacutewnania

include ltstdiohgtinclude ltstringhgt

int main(void) char str[100]int cmp

fputs(Podaj ciag znakow stdout)fgets(str sizeof str stdin)

if (strncmp(str foo 3)) puts(Podany ciag zaczyna sie od foo)

return 0

182 OPERACJE NA ŁAŃCUCHACH 133

1822 Kopiowanie napisoacutewDo kopiowania ciągoacutew znakoacutew służy funkcja strcpy ktoacutera kopiuje drugi napis w miejsce pierwszegoMusimy pamiętać by w pierwszym łańcuchu było wystarczająco dużo miejsca

char napis[100]strcpy(napis Ala ma kota)

Znacznie bezpieczniej jest używać funkcji strncpy ktoacutera kopiuje co najwyżej tyle bajtoacutew ile podanojako trzeci parametr Uwaga Jeżeli drugi napis jest za długi funkcja nie kopiuje znaku null na koniecpierwszego napisu dlatego zawsze trzeba to robić ręcznie

char napis[100]strncpy(napis Ala ma kota sizeof napis - 1)napis[sizeof napis - 1] = 0

1823 Łączenie napisoacutewDo łączenia napisoacutew służy funkcja strcat ktoacutera kopiuje drugi napis do pierwszego Ponownie jak wprzypadku strcpymusimy zagwarantować by w pierwszym łańcuchu było wystarczająco dużo miejsca

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrcat(napis1 napis2)puts(napis1)return 0

I ponownie jak w przypadku strcpy istnieje funkcja strncat ktoacutera skopiuje co najwyżej tyle bajtoacutewile podano jako trzeci argument i dodatkowo dopisze znak null Przykładowo powyższy kod bezpieczniejzapisać jako

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrncat(napis1 napis2 sizeof napis1 - 1)puts(napis1)return 0

Osoby ktoacutere programowały w językach skryptowych muszą bardzo uważać na łączenie i kopiowa-nie napisoacutew Kompilator języka C nie wykryje nadpisania pamięci za zmienną łańcuchową i nie przy-dzieli dodatkowego obszaru pamięci Może się zdarzyć że program pomimo nadpisywania pamięci załańcuchem będzie nadal działał co bardzo utrudni wykrywanie tego typu błędoacutew

134 ROZDZIAŁ 18 NAPISY

183 Bezpieczeństwo kodu a łańcuy

1831 Przepełnienie buforaO co właściwie chodzi z tymi funkcjami strncpy i strncat Otoacuteż niewinnie wyglądające łańcuchy mogąokazać się zaboacutejcze dla bezpieczeństwa programu a przez to nawet dla systemu w ktoacuterym ten programdziała Może brzmi to strasznie lecz jest to prawda Może pojawić się tutaj pytanie ldquow jaki sposoacutebłańcuch może zaszkodzić programowirdquo Otoacuteż może i to całkiem łatwo Przeanalizujmy następującykod

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strcpy(haslo argv[1]) tutaj następuje przepełnienie bufora if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Jest to bardzo prosty program ktoacutery wykonuje jakąś akcję jeżeli podane jako pierwszy argumenthasło jest poprawne Sprawdźmy czy działa

$ aout niepoprawnePodales bledne haslo$ aout poprawneWitaj wprowadziles poprawne haslo

Jednak okazuje się że z powodu użycia funkcji strcpy włamywacz nie musi znać hasła aby programuznał że zna hasło np

$ aout 11111111111111111111111111111111Witaj wprowadziles poprawne haslo

Co się stało Podaliśmy ciąg jedynek dłuższy niż miejsce przewidziane na hasło Funkcja strcpy()kopiując znaki z argv[1] do tablicy (bufora) haslo przekroczyła przewidziane dla niego miejsce i szładalej mdash gdzie znajdowała się zmienna haslo poprawne strcpy() kopiowała znaki już tam gdzie znajdo-wały się inne dane mdash między innymi wpisała jedynkę do haslo poprawne

Podany przykład może się roacuteżnie zachowywać w zależności od kompilatora jakim został skompi-lowany i systemu na jakim działa ale ogoacutelnie mamy do czynienia z poważnym niebezpieczeństwem

183 BEZPIECZEŃSTWO KODU A ŁAŃCUCHY 135

Taką sytuację nazywamy przepełnieniem bufora Może umożliwić dostęp do komputera osobomnieuprzywilejowanym Należy wystrzegać się tego typu konstrukcji a wmiejsce niebezpiecznej funkcjistrcpy stosować bardziej bezpieczną strncpy

Oto bezpieczna wersja poprzedniego programu

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strncpy(haslo argv[1] sizeof haslo - 1)haslo[sizeof haslo - 1] = 0if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Bezpiecznymi alternatywami do strcpy i strcat są też funkcje strlcpy oraz strlcat opracowane przezprojekt OpenBSD i dostępne do ściągnięcia na wolnej licencji strlcpy strlcat strlcpy() działa podobniedo strncpy strlcpy (buf argv[1] sizeof buf) jednak jest szybsza (nie wypełnia pustego miejscazerami) i zawsze kończy napis nullem (czego nie gwarantuje strncpy) strlcat(dst src size) działanatomiast jak strncat(dst src size-1)

Do innych niebezpiecznych funkcji należy np gets zamiast ktoacuterej należy używać fgetsZawsze możemy też alokować napisy dynamicznie

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

136 ROZDZIAŁ 18 NAPISY

haslo = malloc(strlen(argv[1]) + 1) +1 dla znaku null if (haslo)

fputs(Za malo pamiecin stderr)return EXIT_FAILURE

strcpy(haslo argv[1])if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)free(haslo)return EXIT_SUCCESS

1832 Nadużycia z udziałem ciągoacutew formatującyJednak to nie koniec kłopotoacutew z napisami Wielu programistoacutew nieświadomych zagrożenia częstoużywa tego typu konstrukcji

include ltstdiohgtint main (int argc char argv[])

printf (argv[1])

Z punktu widzenia bezpieczeństwa jest to bardzo poważny błąd programu ktoacutery może nieść zesobą katastrofalne skutki Prawidłowo napisany kod powinien wyglądać następująco

include ltstdiohgtint main (int argc char argv[])

printf (s argv[1])

lub

include ltstdiohgtint main (int argc char argv[])

fputs (argv[1] stdout)

Źroacutedło problemu leży w konstrukcji funkcji printf Przyjmuje ona bowiem za pierwszy parametrłańcuch ktoacutery następnie przetwarza Jeśli w pierwszym parametrze wstawimy jakąś zmienną to funk-cja printf potraktuje ją jako ciąg znakoacutew razem ze znakami formatującymi Zatem ważne aby wcześniewyrobić sobie nawyk stosowania funkcji printf z co najmniej dwoma parametrami nawet w przypadkuwyświetlenia samego tekstu

184 KONWERSJE 137

184 KonwersjeCzasami zdarza się że łańcuch można interpretować nie tylko jako ciąg znakoacutew lecz np jako liczbęJednak aby dało się taką liczbę przetworzyć musimy skopiować ją do pewnej zmiennej Aby ułatwićprogramistom tego typu zamiany powstał zestaw funkcji bibliotecznych Należą do nich

atol strtol mdash zamienia łańcuch na liczbę całkowitą typu long

atoi mdash zamienia łańcuch na liczbę całkowitą typu int

atoll strtoll mdash zamienia łańcuch na liczbę całkowitą typu long long ( bity) dodatkowo istniejeprzestarzała funkcja atoq będąca rozszerzeniem

atof strtod mdash przekształca łańcuch na liczbę typu double

Ogoacutelnie rzecz ujmując funkcje z serii ato nie pozwalają na wykrycie błędoacutew przy konwersji i dla-tego gdy jest to potrzebne należy stosować funkcje strto

Czasami przydaje się też konwersja w drugą stronę tzn z liczby na łańcuch Do tego celu możeposłużyć funkcja sprintf lub snprintf sprintf jest bardzo podobna do printf tyle że wyniki jej praczwracane są do pewnego łańcucha a nie wyświetlane np na ekranie monitora Należy jednak uwa-żać przy jej użyciu (patrz mdash Bezpieczeństwo kodu a łańcuchy) snprintf (zdefiniowana w nowszymstandardzie) dodatkowo przyjmuje jako argument wielkość bufora docelowego

185 Operacje na znakaWarto też powiedzieć w tymmiejscu o operacjach na samych znakach Spoacutejrzmy na poniższy program

include ltstdiohgtinclude ltctypehgtinclude ltstringhgt

int main()

int znakwhile ((znak = getchar())=EOF)

if( islower(znak) ) znak = toupper(znak)

else if( isupper](znak) ) znak = tolower(znak)

putchar(znak)

return 0

Program ten zmieniawewczytywanym tekściewielkie litery namałe i odwrotnie Wykorzystujemyfunkcje operujące na znakach z pliku nagłoacutewkowego ctypeh isupper sprawdza czy znak jest wielkąliterą natomiast toupper zmienia znak (o ile jest literą) na wielką literę Analogicznie jest dla funkcjiislower i tolower

Jako ćwiczenie możesz tak zmodyfikować program żeby odczytywał dane z pliku podanego jakoargument lub wprowadzonego z klawiatury

186 Częste błędy pisanie do niezaalokowanego miejsca

138 ROZDZIAŁ 18 NAPISY

char tekstscanf(s tekst)

zapominanie o kończącym napis nullu

char test[4] = test nie zmieścił się null kończący napis

nieprawidłowe poroacutewnywanie łańcuchoacutew

char tekst1[] = jakis tekstchar tekst2[] = jakis tekstif( tekst1 == tekst2 ) tu zawsze będzie fałsz bo == poroacutewnuje adresy należy użyć strcmp()

187 UnicodeZobacz w Wikipedii Uni-code W dzisiejszych czasach brak obsługi wielu językoacutew praktycznie marginalizowałoby język Dlatego też

C wprowadza możliwość zapisu znakoacutew wg norm Unicode

1871 Jaki typDo przechowywania znakoacutew zakodowanych w Unicode powinno się korzystać z typu war t Jegodomyślny rozmiar jest zależny od użytego kompilatora lecz w większości zaktualizowanych kompila-toroacutew powinny to być bajty Typ ten jest częścią języka C++ natomiast w C znajduje się w plikunagłoacutewkowym stddefh

Alternatywą jest wykorzystanie gotowych bibliotek dla Unicode (większość jest dostępnych jedyniedla C++ nie wspoacutełpracuje z C) ktoacutere często mają zdefiniowane własne typy jednak zmuszeni jesteśmywtedy do przejścia ze znanych nam już funkcji jak np strcpy strcmp na funkcje dostarczane przezbibliotekę co jest dość niewygodne My zajmiemy się pierwszym wyjściem

1872 Jaki rozmiar i jakie kodowanieUnicode określa jedynie jakiej liczbie odpowiada jaki znak nie moacutewi zaś nic o sposobie dekodowania(tzn jaka sekwencja znakoacutew odpowiada jakiemu znakuznakom) Jako że Unicode obejmuje tysznakoacutew zmienna zdolna pomieścić go w całości musi mieć przynajmniej bajty Niestety procesory niefunkcjonują na zmiennych o tym rozmiarze pracują jedynie na zmiennych o wielkościach oraz bajtoacutew (kolejne potęgi liczby ) Dlatego też jeśli wciąż uparcie chcemy być dokładni i zastosowaćprzejrzyste kodowanie musimy skorzystać ze zmiennej -bajtowej ( bity) Tak do sprawy podeszlitwoacutercy kodowania Unicode nazwanego -UCS- Ten typ kodowania po prostu przydziela każ-Zobacz w Wikipedii -32demu znakowi Unicode kolejne liczby Jest to najbardziej intuicyjny i wygodny typ kodowania ale jakwidać ciągi znakoacutew zakodowane w nim są bardzo obszerne co zajmuje dostępną pamięć spowalniadziałanie programu oraz drastycznie pogarsza wydajność podczas transferu przez sieć Poza -istnieje jeszcze wiele innych kodowań Najpopularniejsze z nich to

- mdash od do bajtoacutew (dla znakoacutew poniżej do bajtoacutew) na znak przez co jest skraj-nie niewygodny gdy chcemy przeprowadzać jakiekolwiek operacje na tekście bez korzystania zgotowych funkcji

- mdash lub bajty na znak ręczne modyfikacje łańcucha są bardziej skomplikowane niż przy-

UCS- mdash bajty na znak przez co znaki z numerami powyżej nie są uwzględnione roacutewniewygodny w użytkowaniu co -

187 UNICODE 139

Ręczne operacje na ciągach zakodowanych w - i - są utrudnione ponieważ w przeci-wieństwie do - gdzie można określić iż powiedzmy znak ciągu zajmuje bajty od do (gdyżz goacutery wiemy że znak zajął bajty od do ) w tych kodowaniach musimy najpierw określić rozmiar znaku Ponadto gdy korzystamy z nich nie działają wtedy funkcje udostępniane przez biblioteki Cdo operowania na ciągach znakoacutew

Priorytet Proponowane kodowaniamały rozmiar -8

łatwa i wydajna edycja -32 lub -2przenośność -83

ogoacutelna szybkość -2 lub -8

Co należy zrobić by zacząć korzystać z kodowania - (domyślne kodowanie dla C)

powinniśmy korzystać z typu wchar t (ang ldquowide characterrdquo) jednak jeśli chcemy udostępniaćkod źroacutedłowy programu do kompilacji na innych platformach powinniśmy ustawić odpowiednieparametry dla kompilatoroacutew by rozmiar był identyczny niezależnie od platformy

korzystamy z odpowiednikoacutew funkcji operujących na typie char pracujących na wchar t (z re-guły składnia jest identyczna z tą roacuteżnicą że w nazwach funkcji zastępujemy ldquostrrdquo na ldquowcsrdquo npstrcpy mdash wcscpy strcmp mdash wcscmp)

jeśli przyzwyczajeni jesteśmy do korzystania z klasy string powinniśmy zamiast niej korzystaćz wstring ktoacutera posiada zbliżoną składnię ale pracuje na typie wchar t

Co należy zrobić by zacząć korzystać z Unicode

gdy korzystamy z kodowań innych niż - i - powinniśmy zdefiniować własny typ

w wykorzystywanych przez nas bibliotekach podajemy typ wykorzystanego kodowania

gdy chcemy ręcznie modyfikować ciąg musimy przeczytać specyfikację danego kodowania sąone wyczerpująco opisane na siostrzanym projekcie Wikibooks mdash Wikipedii

Przykład użycia kodowania -

include ltstddefhgt jeśli używamy C++ możemy opuścić tę linijkę include ltstdiohgtinclude ltstringhgt

int main() wchar_t wcs1 = LAla ma kotawchar_t wcs2 = LKot ma Alewchar_t calosc[25]

wcscpy(calosc wcs1)(calosc + wcslen(wcs1)) = L wcscpy(calosc + wcslen(wcs1) + 1 wcs2)

printf(lancuch wyjsciowy lsn calosc)return 0

140 ROZDZIAŁ 18 NAPISY

Rozdział 19

Typy złożone

191 typedefJest to słowo kluczowe ktoacutere służy do definiowania typoacutew pochodnych np

typedef stara_nazwa nowa_nazwatypedef int mojInttypedef int WskNaInt

od tej pory mozna używać typoacutew mojInt i WskNaInt

192 Typ wyliczeniowySłuży do tworzenia zmiennych ktoacutere powinny przechowywać tylko pewne z goacutery ustalone wartości

enum Nazwa WARTOSC_1 WARTOSC_2 WARTOSC_N

Na przykład można w ten sposoacuteb stworzyć zmienną przechowującą kierunek

enum Kierunek W_GORE W_DOL W_LEWO W_PRAWO

enum Kierunek kierunek = W_GORE

ktoacuterą można na przykład wykorzystać w instrukcji switch

switch(kierunek)

case W_GOREprintf(w goacuteręn)break

case W_DOLprintf(w doacutełn)break

defaultprintf(gdzieś w bokn)

Tradycyjnie przechowywane wielkości zapisuje się wielkimi literami (W GORE W DOL)Tak naprawdę C przechowuje wartości typu wyliczeniowego jako liczby całkowite o czym można

się łatwo przekonać

141

142 ROZDZIAŁ 19 TYPY ZŁOŻONE

kierunek = W_DOLprintf(in kierunek) wypisze 1

Kolejne wartości to po prostu liczby naturalne domyślnie pierwsza to zero druga jeden itp Mo-żemy przy deklarowaniu typu wyliczeniowego zmienić domyślne przyporządkowanie

enum Kierunek W_GORE W_DOL = 8 W_LEWO W_PRAWO printf(i in W_DOL W_LEWO) wypisze 8 9

Co więcej liczby mogą się powtarzać i wcale nie muszą być ustawione w kolejności rosnącej

enum Kierunek W_GORE = 5 W_DOL = 5 W_LEWO = 2 W_PRAWO = 1 printf(i in W_DOL W_LEWO) wypisze 5 2

Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajną ich obsługęale stwarza niebezpieczeństwa mdash można przypisywać pod typ wyliczeniowy liczby nawet nie mająceodpowiednika w wartościach a kompilator może o tym nawet nie ostrzec

kierunek = 40

193 StrukturyStruktury to specjalny typ danych mogący przechowywać wiele wartości w jednej zmiennej Od tablicjednakże roacuteżni się tym iż te wartości mogą być roacuteżnych typoacutew

Struktury definiuje się w następujący sposoacuteb

struct Struktura int pole1int pole2char pole3

gdzie ldquoStrukturardquo to nazwa tworzonej strukturyNazewnictwo ilość i typ poacutel definiuje programista według własnego uznaniaZmienną posiadającą strukturę tworzy się podając jako jej typ nazwę struktury

struct Struktura zmienna

Dostęp do poszczegoacutelnych poacutel uzyskuje się przy pomocy operatora wyboru składnika kropki (rsquorsquo)

zmiennaSpole1 = 60 przypisanie liczb do poacutel zmiennaSpole2 = 2zmiennaSpole3 = a a teraz znaku

194 UnieUnie to kolejny sposoacuteb prezentacji danychw pamięci Na pierwszy rzut oka wyglądają bardzo podobniedo struktur

union Nazwa typ1 nazwa1typ2 nazwa2

Na przykład

194 UNIE 143

union LiczbaLubZnak int calkowitachar znakdouble rzeczywista

Pola w unii nakładają się na siebie w ten sposoacuteb że w danej chwili można w niej przechowywaćwartość tylko jednego typu Unia zajmuje w pamięci tyle miejsca ile zajmuje największa z jej składo-wych W powyższym przypadku unia będzie miała prawdopodobnie rozmiar typu double czyli często bity a całkowita i znak będą wskazywały odpowiednio na pierwsze cztery bajty lub na pierwszy bajtunii (choć nie musi tak być zawsze) Dlaczego tak Taka forma często przydaje się np do konwersji po-między roacuteżnymi typami danych Możemy dzięki unii podzielić zmienną -bitową na cztery składowezmienne o długości bitoacutew każda

Do konkretnych wartości poacutel unii odwołujemy się podobnie jak w przypadku struktur za pomocąkropki

union LiczbaLubZnak liczbaliczbacalkowita = 10printf(dn liczbacalkowita)

Zazwyczaj użycie unii ma na celu zmniejszenie zapotrzebowania na pamięć gdy naraz będzie wy-korzystywane tylko jedno pole i jest często łączone z użyciem struktur

Przyjrzyjmy się teraz przykładowi ktoacutery powinien dobitnie zademonstrować działanie unii

include ltstdiohgt

struct adres_bajtowy __uint8_t a__uint8_t b__uint8_t c__uint8_t d

union adres __uint32_t ipstruct adres_bajtowy badres

int main ()

union adres addraddrbadresa = 192addrbadresb = 168addrbadresc = 1addrbadresd = 1printf (Adres IP w postaci 32-bitowej zmiennej 08xnaddrip)return 0

Zauważyłeś pewien ciekawy efekt Jeśli uruchomiłeś ten program na typowym komputerze domo-wym (rodzina i) na ekranie zapewne pojawił Ci się taki oto napis

Adres IP w postaci 32-bitowej zmiennej 0101a8c0

Dlaczego jedynki są na początku zmiennej skoro w programie były to dwa ostatnie bajty (pola c id struktury) Jest to problem kolejności bajtoacutew Aby dowiedzieć się o nim więcej przeczytaj rozdział

144 ROZDZIAŁ 19 TYPY ZŁOŻONE

przenośność programoacutew Zauważyłeś zatem że za pomocą tego programu w prosty sposoacuteb zamienili-śmy cztery zmienne jednobajtowe w jedną czterobajtową Jest to tylko jedno z możliwych zastosowańunii

195 Inicjalizacja struktur i uniiJeśli tworzymy nową strukturę lub unię możemy zaraz po jej deklaracji wypełnić ją określonymi da-nymi Rozważmy tutaj przykład

struct moja_struct int achar b moja = 1c

Wzasadzie taka deklaracja nie roacuteżni się niczym odwypełnienia np tablicy danymi Jednak standardC wprowadza pewne udogodnienie zaroacutewno przy deklaracji struktur jak i unii Polega ono na tymże w nawiasie klamrowym możemy podać nazwy poacutel struktury lub unii ktoacuterym przypisujemy wartośćnp

struct moja_struct int achar b moja = b = c pozostawiamy pole a niewypełnione żadną konkretną wartością

196 Wspoacutelnewłasności typoacutewwyliczeniowy unii i struk-tur

Warto w zwroacutecić uwagę że język C++ przy deklaracji zmiennych typoacutew wyliczeniowych unii lubstruktur nie wymaga przed nazwą typu odpowiedniego słowa kluczowego Na przykład poniższy kodjest poprawnym programem C++

enum Enum A B C union Union int a float b struct Struct int a float b int main()

Enum eUnion uStruct se = Aua = 0sa = 0return e + ua + sa

Nie jest to jednak poprawny kod C i należy o tym pamiętać szczegoacutelnie jeżeli uczysz się języka Ckorzystając z kompilatora C++

Należy roacutewnież pamiętać że po klamrze zamykającej definicje musi następować średnik Brak tegośrednika jest częstym błędem powodującym czasami niezrozumiałe komunikaty błędoacutew Jedynym wy-jątkiem jest natychmiastowa definicja zmiennych danego typu na przykład

struct Struktura int pole

s1 s2 s3

196 WSPOacuteLNE WŁASNOŚCI TYPOacuteW WYLICZENIOWYCH UNII I STRUKTUR 145

Definicja typoacutew wyliczeniowych unii i struktur jest lokalna do bloku To znaczy możemy zdefi-niować strukturę wewnątrz jednej z funkcji (czy wręcz wewnątrz jakiegoś bloku funkcji) i tylko tambędzie można używać tego typu

Częstym idiomem w C jest użycie typedef od razu z definicją typu by uniknąć pisania enum unionczy struct przy deklaracji zmiennych danego typu

typedef struct struktura int pole

StrukturaStruktura s1struct struktura s2

W tym przypadku zmienne s i s są tego samego typu Możemy też zrezygnować z nazywaniasamej struktury

typedef struct int pole

StrukturaStruktura s1

1961 Wskaźnik na unię i strukturęPodobnie jak na każdą inną zmienna wskaźnik może wskazywać także na unię lub strukturę Otoprzykład

typedef struct int p1 p2

Struktura

int main ()

Struktura s = 0 0 Struktura wsk = ampswsk-gtp1 = 2wsk-gtp2 = 3return 0

Zapis wsk-gtp1 jest (z definicji) roacutewnoważny (wsk)p1 ale bardziej przejrzysty i powszechnie sto-sowany Wyrażenie wskp1 spowoduje błąd kompilacji (strukturą jest wsk a nie wsk)

1962 Zobacz też Powszechne praktyki mdash konstruktory i destruktory

1963 Pola bitoweStruktury mają pewne dodatkowe możliwości w stosunku do zmiennych Mowa tutaj o rozmiarzeelementu struktury W przeciwieństwie do zmiennej może on mieć nawet bit Aby moacutec zdefiniowaćtaką zmienną musimy użyć tzw pola bitowego Wygląda ono tak

struct moja unsigned int a14 4 bity

a28 8 bitoacutew (często 1 bajt) a31 1 bit a43 3 bity

146 ROZDZIAŁ 19 TYPY ZŁOŻONE

Wszystkie pola tej struktury mają w sumie rozmiar bitoacutew jednak możemy odwoływać się donichw taki sam sposoacuteb jak do innych elementoacutew struktury W ten sposoacuteb efektywniej wykorzystujemypamięć jednak istnieją pewne zjawiska ktoacuterych musimy być świadomi przy stosowaniu poacutel bitowychWięcej na ten temat w rozdziale przenośność programoacutew

Pola bitowe znalazły zastosowanie głoacutewnie w implementacjach protokołoacutew sieciowych

197 Studium przypadku mdash implementacja listy wskaźniko-wej

Zobacz w Wikipedii ListaRozważmy teraz coś co każdy z nas może spotkać w codziennym życiu Każdy z nas widział kiedyśjakiś przykład listy (czy to zakupoacutew czy też listę wierzycieli) Język C też oferuje listy jednak w progra-mowaniu listy będą służyły do czegoś innego Wyobraźmy sobie sytuację w ktoacuterej jesteśmy autoramigenialnego programu ktoacutery znajduje kolejne liczby pierwsze Oczywiście każdą kolejną liczbę pierw-szą może wyświetlać na ekran jednak z matematyki wiemy że dana liczba jest liczbą pierwszą jeśli niedzieli się przez żadną liczbę pierwszą ją poprzedzającą mniejszą od pierwiastka z badanej liczby Uffmniej więcej chodzi o to że moglibyśmy wykorzystać znalezione wcześniej liczby do przyspieszeniadziałania naszego programu Jednak nasze liczby trzeba jakoś mądrze przechować w pamięci Tablicemają ograniczenie mdash musimy z goacutery znać ich rozmiar Jeśli zapełnilibyśmy tablicę to przy znalezieniukażdej kolejnej liczby musielibyśmy

przydzielać nowy obszar pamięci o rozmiarze poprzedniego rozmiaru + rozmiar zmiennej prze-chowującej nowo znalezioną liczbę

kopiować zawartość starego obszaru do nowego

zwalniać stary nieużywany obszar pamięci

w ostatnim elemencie nowej tablicy zapisać znalezioną liczbę

Coacuteż trochę tutaj roboty jest a kopiowanie całej zawartości jednego obszaru w drugi jest czaso-chłonne W takim przypadku możemy użyć listy Tworząc listę możemy w prosty sposoacuteb przechowaćnowo znalezione liczby Przy użyciu listy nasze postępowanie ograniczy się do

przydzielenia obszaru pamięci aby przechować wartość obliczeń

dodać do listy nowy element

Prawda że proste Dodatkowo lista zajmuje w pamięci tylko tyle pamięci ile potrzeba na aktualnąliczbę elementoacutew Pusta tablica zajmuje natomiast tyle samo miejsca co pełna tablica

1971 Implementacja listyW języku C aby stworzyć listę musimy użyć struktur Dlaczego Ponieważ musimy przechować conajmniej dwie wartości

pewną zmienną (np liczbę pierwszą z przykładu)

wskaźnik na kolejny element listy

Przyjmijmy że szukając liczb pierwszych nie przekroczymy możliwości typu unsigned long

typedef struct element struct element next wskaźnik na kolejny element listy unsigned long val przechowywana wartość

el_listy

Zacznijmy zatem pisać nasz eksperymentalny program do wyszukiwania liczb pierwszych Pierw-szą liczbą pierwszą jest liczba Pierwszym elementem naszej listy będzie zatem struktura ktoacutera będzieprzechowywała liczbę Na co będzie wskazywało pole next Ponieważ na początku działania pro-gramu będziemy mieć tylko jeden element listy pole next powinno wskazywać na Umoacutewmy sięzatem że pole next ostatniego elementu listy będzie wskazywało mdash po tym poznamy że lista sięskończyła

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 147

include ltstdiohgtinclude ltstdlibhgttypedef struct element

struct element nextunsigned long val

el_listy

el_listy first pierwszy element listy

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (ilt=END++i) tutaj powinien znajdować się kod ktoacutery sprawdza podzielność sprawdzanej liczby przezpoprzednio znalezione liczby pierwsze oraz dodaje liczbę do listy w przypadku stwierdzenia

że jest ona liczbą pierwszą

wypisz_liste(first)return 0

Na początek zajmiemy się wypisywaniem listy W tym celu będziemy musieli ldquoodwiedzićrdquo każdyelement listy Elementy listy są połączone polem next aby przeglądnąć listę użyjemy następującegoalgorytmu

Ustaw wskaźnik roboczy na pierwszym elemencie listy

Jeśli wskaźnik ma wartość przerwij

Wypisz element wskazywany przez wskaźnik

Przesuń wskaźnik na element ktoacutery jest wskazywany przez pole next

Wroacuteć do punktu

void wypisz_liste(el_listy lista)

el_listy wsk=lista 1 while( wsk = NULL ) 2

printf (lun wsk-gtval) 3 wsk = wsk-gtnext 4 5

Zastanoacutewmy się teraz jak powinien wyglądać kod ktoacutery dodaje do listy następny element Takafunkcja powinna

znaleźć ostatni element (tj element ktoacuterego pole next == )

przydzielić odpowiedni obszar pamięci

skopiować w pole val w nowo przydzielonym obszarze znalezioną liczbę pierwszą

nadać polu next ostatniego elementu listy wartość

w pole next ostatniego elementu listy wpisać adres nowo przydzielonego obszaru

148 ROZDZIAŁ 19 TYPY ZŁOŻONE

Napiszmy zatem odpowiednią funkcję

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL) 1

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy)) 2 nowy-gtval = liczba 3 nowy-gtnext = NULL 4 wsk-gtnext = nowy 5

Ihellip to już właściwie koniec naszej funkcji (warto zwroacutecić uwagę że funkcja w tej wersji zakłada żena liście jest już przynajmniej jeden element) Wstaw ją do kodu przed funkcją main Został namjeszcze jeden problem w pętli for musimy dodać kod ktoacutery odpowiednio będzie ldquobadałrdquo liczby oraz wprzypadku stwierdzenia pierwszeństwa liczby będzie dodawał ją do listy Ten kod powinien wyglądaćmniej więcej tak

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczba wsk-gtval)==0) return 0 jeśli reszta z dzielenialiczby przez ktoacuterąkolwiek z poprzednio znalezionychliczb pierwszych jest roacutewna zero to znaczy że liczba tanie jest liczbą pierwszą

wsk = wsk-gtnext

natomiast jeśli sprawdzimy wszystkie poprzednio znalezione liczbyi żadna z nich nie będzie dzieliła liczby imożemy liczbę i dodać do listy liczb pierwszych

return 1for (ilt=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (firsti)

Podsumujmy teraz efekty naszej pracy Oto cały kod naszego programu

include ltstdiohgtinclude ltstdlibhgt

typedef struct element struct element nextunsigned long val

el_listy

el_listy first

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 149

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL)

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy))nowy-gtval = liczbanowy-gtnext = NULLwsk-gtnext = nowy podczepiamy nowy element do ostatniego z listy

void wypisz_liste(el_listy lista)

el_listy wsk=listawhile( wsk = NULL )

printf (lun wsk-gtval)wsk = wsk-gtnext

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczbawsk-gtval)==0) return 0wsk = wsk-gtnext

return 1

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (i=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (first i)

wypisz_liste(first)return 0

Możemy jeszcze pomyśleć jak można by wykonać usuwanie elementu z listy Najprościej byłobyzrobić

wsk-gtnext = wsk-gtnext-gtnext

150 ROZDZIAŁ 19 TYPY ZŁOŻONE

ale wtedy element na ktoacutery wskazywał wcześniej wsk-gtnext przestaje być dostępny i zaśmieca pa-mięć Trzeba go usunąć Zauważmy że aby usunąć element potrzebujemy wskaźnika do elementu gopoprzedzającego (po to by nie rozerwać listy) Popatrzmy na poniższą funkcję

void usun_z_listy(el_listy lista int element)

el_listy wsk=listawhile (wsk-gtnext = NULL)

if (wsk-gtnext-gtval == element) musimy mieć wskaźnik do elementu poprzedzającego el_listy usuwany=wsk-gtnext zapamiętujemy usuwany element wsk-gtnext = usuwany-gtnext przestawiamy wskaźnik next by omijał usuwany element free(usuwany) usuwamy z pamięci else

wsk = wsk-gtnext idziemy dalej tylko wtedy kiedy nie usuwaliśmy bo nie chcemy zostawić duplikatoacutew

Funkcja ta jest tak napisana by usuwała z listy wszystkie wystąpienia danego elementu (w naszymprogramie nie ma to miejsca ale lista jest zrobiona tak że może trzymać dowolne liczby) Zauważmyże wskaźnik wsk jest przesuwany tylko wtedy gdy nie kasowaliśmy Gdybyśmy zawsze go przesuwaliprzegapilibyśmy element gdyby występował kilka razy pod rząd

Funkcja ta działa poprawnie tylko wtedy gdy nie chcemy usuwać pierwszego elementu Można topoprawić mdash dodając instrukcję warunkową do funkcji lub dodając do listy ldquogłowęrdquo mdash pierwszy elementnie przechowujący niczego ale upraszczający operacje na liście Zostawiamy to do samodzielnej pracy

Cały powyższy przykład omawiał tylko jeden przypadek listy mdash listę jednokierunkową Jednakistnieją jeszcze inne typy list np lista jednokierunkowa cykliczna lista dwukierunkowa oraz dwukie-runkowa cykliczna Roacuteżnią się one od siebie tylko tym że

w przypadku list dwukierunkowych mdashw strukturze el listy znajduje się jeszcze pole ktoacutere wska-zuje na element poprzedni

w przypadku list cyklicznych mdash ostatni element wskazuje na pierwszy (nie rozroacuteżnia się wtedyelementu pierwszego ani ostatniego)

Rozdział 20

Biblioteki

201 Czym jest bibliotekaBiblioteka jest to zbioacuter funkcji ktoacutere zostały wydzielone po to aby dało się z nich korzystać wwielu pro-gramach Ułatwia to programowanie mdash nie musimy np sami tworzyć funkcji printf Każda bibliotekaposiada swoje pliki nagłoacutewkowe ktoacutere zawierają deklaracje funkcji bibliotecznych oraz często zawartesą w nich komentarze jak używać danej funkcji W tej części podręcznika nauczymy się tworzyć naszewłasne biblioteki

202 Jak zbudowana jest bibliotekaKażda biblioteka składa się z co najmniej dwoacutech części

pliku nagłoacutewkowego z deklaracjami funkcji (plik z rozszerzeniem h)

pliku źroacutedłowego zawierającego ciała funkcji (plik z rozszerzeniem c)

2021 Budowa pliku nagłoacutewkowegoOto najprostszy możliwy plik nagłoacutewkowy

ifndef PLIK_Hdefine PLIK_H tutaj są wpisane deklaracje funkcji endif PLIK_H

Zapewne zapytasz się na co komu instrukcje ifndef define oraz endif Otoacuteż często się zdarzaże w programie korzystamy z plikoacutew nagłoacutewkowych ktoacutere dołączają się wzajemnie Oznaczałoby to żew kodzie programu kilka razy pojawiła by się zawartość tego samego pliku nagłoacutewkowego Instrukcjaifndef i define temu zapobiega Dzięki temu kompilator nie musi kilkakrotnie kompilować tegosamego kodu

W plikach nagłoacutewkowych często umieszcza się też definicje typoacutew z ktoacuterych korzysta bibliotekaalbo np makr

2022 Budowa najprostszej bibliotekiZałoacuteżmy że nasza biblioteka będzie zawierała jedną funkcję ktoacuterawypisuje na ekran tekst ldquoplWikibooksrdquoUtwoacuterzmy zatem nasz plik nagłoacutewkowy

151

152 ROZDZIAŁ 20 BIBLIOTEKI

ifndef WIKI_Hdefine WIKI_Hvoid wiki (void)endif

Należy pamiętać o podaniu void w liście argumentoacutew funkcji nie przyjmujących argumentoacutew Oile przy definicji funkcji nie trzeba tego robić (jak to często czyniliśmy w przypadku funkcji main) otyle w prototypie brak słoacutewka void oznacza że w prototypie nie ma informacji na temat tego jakieargumenty funkcja przyjmuje

Plik nagłoacutewkowy zapisujemy jako ldquowikihrdquo Teraz napiszmy ciało tej funkcji

include wikihinclude ltstdiohgt

void wiki (void)

printf (plWikibooksn)

Ważne jest dołączenie na początku pliku nagłoacutewkowego Dlaczego Plik nagłoacutewkowy zawieradeklaracje naszych funkcji mdash jeśli popełniliśmy błąd i deklaracja nie zgadza się z definicją kompilatorod razu nas o tym powiadomi Oproacutecz tego plik nagłoacutewkowy może zawierać definicje istotnych typoacutewlub makr

Zapiszmy naszą bibliotekę jako plik ldquowikicrdquo Teraz należy ją skompilować Robi się to trochę ina-czej niż normalny program Należy po prostu do opcji kompilatora gcc dodać opcję ldquo-crdquo

gcc wikic -c -o wikio

Rozszerzenie ldquoordquo jest domyślnym rozszerzeniem dla bibliotek statycznych (typowych bibliotek łą-czonych z resztą programu na etapie kompilacji) Teraz możemy spokojnie skorzystać z naszej nowejbiblioteki Napiszmy nasz program

include wikih

int main ()

wiki()return 0

Zapiszmy program jako ldquomaincrdquo Teraz musimy odpowiednio skompilować nasz program

gcc mainc wikio -o main

Uruchamiamy nasz program

mainplWikibooks

Jak widać nasza pierwsza biblioteka działaZauważmy że kompilatorowi podajemy i pliki z kodem źroacutedłowym (mainc) i pliki ze skompilo-

wanymi bibliotekami (wikio) by uzyskać plik wykonywalny (main) Jeśli nie podalibyśmy plikoacutew zbibliotekami mainc co prawda skompilowałby się ale błąd zostałby zgłoszony przez linker mdash częśćkompilatora odpowiedzialna za wstawienie w miejsce wywołań funkcji ich adresoacutew (takiego adresulinker nie moacutegłby znaleźć)

202 JAK ZBUDOWANA JEST BIBLIOTEKA 153

2023 Zmiana dostępu do funkcji i zmienny (static i extern)Język C w przeciwieństwie do swego młodszego krewnego mdash C++ nie posiada praktycznie żadnychmechanizmoacutew ochrony kodu biblioteki przed modyfikacjami C++ ma w swoim asortymencie minsterowanie uprawnieniami roacuteżnych elementoacutew klasy Jednak programista piszący program w C niejest tak do końca bezradny Autorzy C dali mu do ręki dwa narzędzia extern oraz static Pierwsze ztych słoacutew kluczowych informuje kompilator że dana funkcja lub zmienna istnieje ale w innymmiejscui zostanie dołączona do kodu programu w czasie łączenia go z biblioteką

extern przydaje się gdy zmienna lub funkcja jest zadeklarowana w bibliotece ale nie jest udostęp-niona na zewnątrz (nie pojawia się w pliku nagłoacutewkowym) Przykładowo

bibliotekah extern char zmienna_dzielona[]

bibliotekac include bibliotekah

char zmienna_dzielona[] = Zawartosc

mainc include ltstdiohgtinclude bibliotekah

int main()

printf(sn zmienna_dzielona)return 0

Gdybyśmy tu nie zastosowali extern kompilator (nie linker) zaprotestowałby że nie zna zmiennejzmienna dzielona Proacuteba dopisania deklaracji char zmienna dzielona stworzyłaby nową zmienną iutracilibyśmy dostęp do interesującej nas zawartości

Odwrotne działanie ma słowo kluczowe static użyte w tym kontekście (użyte wewnątrz bloku two-rzy zmienną statyczną więcej informacji w rozdziale Zmienne) Może ono odnosić się zaroacutewno dozmiennych jak i do funkcji globalnych Powoduje że dana zmienna lub funkcja jest niedostępna nazewnątrz biblioteki1 Możemy dzięki temu ukryć np funkcje ktoacutere używane są przez samą bibliotekęby nie dało się ich wykorzystać przez extern

1Tak naprawdę całe ldquoukrycierdquo funkcji polega na zmianie niektoacuterych danych w pliku z kodem binarnym danejbiblioteki (pliku o) przez co linker powoduje wygenerowanie komunikatu o błędzie w czasie łączenia biblioteki zprogramem

154 ROZDZIAŁ 20 BIBLIOTEKI

Rozdział 21

Więcej o kompilowaniu

211 Ciekawe opcje kompilatora Emdash powoduje wygenerowanie kodu programu ze zmianami wprowadzonymi przez preprocesor

S mdash zamiana kodu w języku C na kod asemblera (komenda gcc -S plikc spowoduje utworzeniepliku o nazwie pliks w ktoacuterym znajdzie się kod asemblera)

c mdash kompilacja bez łączenia z bibliotekami

Ikatalog mdash ustawienie domyślnego katalogu z plikami nagłoacutewkowymi na katalog

lbiblioteka mdash wymusza łączenie programu z podaną biblioteką (np -lGL)

212 Program makeDość często może się zdarzyć że nasz program składa się z kilku plikoacutew źroacutedłowych Jeśli tych plikoacutewjest mało (np -) możemy jeszcze proacutebować ręcznie kompilować każdy z nich Jednak jeśli tych plikoacutewjest dużo lub chcemy pokazać nasz program innym użytkownikom musimy stworzyć elegancki sposoacutebkompilacji naszego programu Właśnie po to aby zautomatyzować proces kompilacji powstał programmake Program make analizuje pliki Makefile i na ich podstawie wykonuje określone czynności

2121 Budowa pliku MakefileUwaga poniżej został omoacutewiony Makefile dla Make Istnieją inne programy make i mogą używaćinnej składni Na Wikibooks został też obszernie opisany program make firmy Borland

Najważniejszym elementem pliku Makefile są zależności oraz reguły przetwarzania Zależnościpolegają na tym że np jeśli nasz program ma być zbudowany z plikoacutew to najpierw należy skom-pilować każdy z tych plikoacutew a dopiero poacuteźniej połączyć je w jeden cały program Zatem zależnościokreślają kolejność wykonywanych czynności Natomiast reguły określają jak skompilować dany plikZależności tworzy się tak

co od_czegoreguły

Dzięki temu program make zna już kolejność wykonywanych działań oraz czynności jakie ma wy-konać Aby zbudować ldquocordquo należy wykonać polecenie make co Pierwsza reguła w pliku Makefile jestregułą domyślną Jeśli wydamy polecenie make bez parametroacutew zostanie zbudowana właśnie reguładomyślna Tak więc dobrze jest jako pierwszą regułę wstawić regułę budującą końcowy plik wykony-walny zwyczajowo regułę tą nazywa się all

155

156 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Należy pamiętać by sekcji ldquocordquo niewcinać natomiast ldquoregułyrdquowcinać tabulatorem Część ldquood czegordquomoże być pusta

Plik Makefile umożliwia też definiowanie pewnych zmiennych Nie trzeba tutaj się już troszczyć otyp zmiennej wystarczy napisać

nazwa_zmiennej = wartość

Wten sposoacutebmożemy zadeklarować dowolnie dużo zmiennych Zmiennemogą być roacuteżnemdash nazwakompilatora jego parametry iwiele innych Zmiennej używamywnastępujący sposoacuteb $(nazwa zmiennej)

Komentarze w pliku Makefile tworzymy zaczynając linię od znaku hash ()

2122 Przykładowy plik MakefileDość tej teorii teraz zajmiemy się działającym przykładem Załoacuteżmy że nasz przykładowy programnazywa się test oraz składa się z czterech plikoacutew pierwszyc drugic trzecic czwartyc

Odpowiedni plik Makefile powinien wyglądać mniej więcej tak

Moacutej plik makefile - wpisz make all aby skompilować cały program (właściwie wystarczy wpisać make - all jest domyślny jako pierwszy cel)CC = gcc

all pierwszyo drugio trzecio czwartyo$(CC) pierwszyo drugio trzecio czwartyo -o test

pierwszyo pierwszyc$(CC) pierwszyc -c -o pierwszyo

drugio drugic$(CC) drugic -c -o drugio

trzecio trzecic$(CC) trzecic -c -o trzecio

czwartyo czwartyc$(CC) czwartyc -c -o czwartyo

Widzimy że nasz program zależy od plikoacutew z rozszerzeniem o (pierwszyo itd) potem każdy ztych plikoacutew zależy od plikoacutew c ktoacutere program make skompiluje w pierwszej kolejności a następniepołączy w jeden program (test) Nazwę kompilatora zapisaliśmy jako zmienną ponieważ powtarza sięi zmienna jest sposobem by zmienić ją wszędzie za jednym zamachem

Zatem jak widać używanie pliku Makefile jest bardzo proste Warto na koniec naszego przykładudodać regułę ktoacutera wyczyści katalog z plikoacutew o

cleanrm -f o test

Ta reguła spowoduje usunięcie wszystkich plikoacutew o oraz naszego programu jeśli napiszemy makeclean

Możemy też ukryć wykonywane komendy albo dopisać własny opis czynności

cleanecho Usuwam gotowe plikirm -f o test

Ten sam plik Makefile moacutegłby wyglądać inaczej

213 OPTYMALIZACJE 157

CFLAGS = -g -O tutaj można dodawać inne flagi kompilatoraLIBS = -lm tutaj można dodawać biblioteki

OBJ =pierwszyo drugio trzecio czwartyo

all main

cleanrm -f o test

co$(CC) -c $(INCLUDES) $(CFLAGS) $lt

main $(OBJ)$(CC) $(OBJ) $(LIBS) -o test

Tak naprawdę jest to dopiero bardzo podstawowe wprowadzenie do używania programu makejednak jest ono wystarczające byś zaczął z niego korzystać Wyczerpujące omoacutewienie całego programuniestety przekracza zakres tego podręcznika

213 OptymalizacjeKompilator umożliwia generację kodu zoptymalizowanego dla konkretnej architektury Służą dotego opcje -mar= i -mtune= Stopień optymalizacji ustalamy za pomocą opcji -Ox gdzie x jest nume-rem stopnia optymalizacji (od do ) Możliwe jest też użycie opcji -Os ktoacutera powoduje generowaniekodu o jak najmniejszym rozmiarze Aby skompilować dany plik z optymalizacjami dla procesora Ath-lon należy napisać tak

gcc programc -o program -march=athlon-xp -O3

Z optymalizacjami należy uważać gdyż często zdarza się że kod skompilowany bez optymalizacjidziała zupełnie inaczej niż ten ktoacutery został skompilowany z optymalizacjami

2131 WyroacutewnywanieWyroacutewnywanie jest pewnym zjawiskiem na ktoacutere w bardzo wielu podręcznikach moacutewiących o Cw ogoacutele się nie wspomina Ten rozdział ma za zadanie wyjaśnienie tego zjawiska oraz uprzedzenieprogramisty o pewnych faktach ktoacutere w poacuteźniejszej jego ldquotwoacuterczościrdquo mogą zminimalizować czas naznalezienie pewnych informacji ktoacutere mogą wpływać na to że jego program nie będzie działał popraw-nie

Często zdarza się że kompilator w ramach optymalizacji ldquowyroacutewnujerdquo elementy struktury tak abyprocesor moacutegł łatwiej odczytać i przetworzyć dane Przyjrzyjmy się bliżej następującemu fragmentowikodu

typedef struct unsigned char wiek 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

158 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Aby procesor moacutegł łatwiej przetworzyć dane kompilator może dodać do tej struktury jedno ośmio-bitowe pole Wtedy struktura będzie wyglądała tak

typedef struct unsigned char wiek 8 bitoacutew unsigned char fill[1] 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

Wtedy rozmiar zmiennych przechowujących wiek płeć oraz dochoacuted będzie wynosił bity mdashbędzie zatem potęgą liczby dwa i procesorowi dużo łatwiej będzie tak ułożoną strukturę przechowywaćw pamięci cache Jednak taka sytuacja nie zawsze jest pożądana Może się okazać że nasza strukturamusi odzwierciedlać np pojedynczy pakiet danych przesyłanych przez sieć Nie może być w niejzatem żadnych innych poacutel poza tymi ktoacutere są istotne do transmisji Aby wymusić na kompilatorzewyroacutewnanie -bajtowe (co w praktyce wyłącza je) należy przed definicją struktury dodać dwie linijkiTen kod działa pod Visual C++

pragma pack(push)pragma pack(1)

struct struktura

pragma pack(pop)

W kompilatorze należy po deklaracji struktury dodajemy przed średnikiem kończącym jednąlinijkę

__attribute__ ((packed))

Działa ona dokładnie tak samo jak makra pragma jednak jest ona obecna tylko w kompilatorze

Dzięki użyciu tego atrybutu kompilator zostanie ldquozmuszonyrdquo do braku ingerencji w naszą strukturęJest jednak jeszcze jeden być może bardziej elegancki sposoacuteb na obejście dopełniania Zauważyłeś żedopełnienie dodane przez kompilator pojawiło się między polem o długości bitoacutew (plec) oraz polem odługości bitoacutew (dochod) Wyroacutewnywanie polega na tym że dana zmienna powinna być umieszczonapod adresem będącym wielokrotnością jej rozmiaru Oznacza to że jeśli np mamy w strukturze napoczątku dwie zmienne o rozmiarze jednego bajta a potem jedną zmienną o rozmiarze bajtoacutew topomiędzy polami o rozmiarze bajtoacutew a polem czterobajtowym pojawi się dwubajtowe dopełnienieMoże Ci się wydawać że jest to tylko niepotrzebne mącenie w głowie jednak niektoacutere architektury(zwłaszcza typu ) mogą nie wykonać kodu ktoacutery nie został wyroacutewnany Dlatego naszą strukturępowinniśmy zapisać mniej więcej tak

typedef struct unsigned short dochod 16 bitoacutew unsigned char wiek 8 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

W ten sposoacuteb wyroacutewnana struktura nie będzie podlegała modyfikacjom przez kompilator oraz bę-dzie przenośna pomiędzy roacuteżnymi kompilatorami

Wyroacutewnywanie działa także na pojedynczych zmiennych w programie jednak ten problem nie po-woduje tyle zamieszania co ingerencja kompilatora w układ poacutel struktury Wyroacutewnywanie zmiennychpolega tylko na tym że kompilator umieszcza je pod adresami ktoacutere są wielokrotnością ich rozmiaru

214 KOMPILACJA KRZYŻOWA 159

214 Kompilacja krzyżowaMając w domu dwa komputery o odmiennych architekturach (np i oraz Sparc) możemy potrze-bować stworzyć program dla jednej maszyny mając do dyspozycji tylko drugi komputer Nie musimywtedy latać do znajomego posiadającego odpowiedni sprzęt Możemy skorzystać z tzw kompilacjikrzyżowej (ang cross-compile) Polega ona na tym że program nie jest kompilowany pod procesorna ktoacuterym działa kompilator lecz na inną zdefiniowaną wcześniej maszynę Efekt będzie taki sam askompilowany program możemy bez problemu uruchomić na drugim komputerze

215 Inne narzędziaWśroacuted przydatnych narzędzi warto wymienić roacutewnież program objdump (zaroacutewno pod Unix jak ipod Windows) oraz readelf (tylko Unix) Objdump służy do deasemblacji i analizy skompilowanychprogramoacutew Readelf służy do analizy pliku wykonywalnego w formacie (używanego w większościsystemoacutew z rodziny Unix) Więcej informacji możesz uzyskać pisząc (w systemach Unix)

man 1 objdumpman 1 readelf

160 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Rozdział 22

Zaawansowane operacjematematyczne

221 Biblioteka matematycznaAby moacutec korzystać z wszystkich dobrodziejstw funkcji matematycznych musimy na początku dołączyćplik mathh

include ltmathhgt

A w procesie kompilacji (dotyczy kompilatora GCC) musimy niekiedy dodać flagę ldquo-lmrdquo

gcc plikc -o plik -lm

Funkcje matematyczne ktoacutere znajdują się w bibliotece standardowej możesz znaleźć tutaj Przykorzystaniu z nich musisz wziąć pod uwagę min to że biblioteka matematyczna prowadzi kalkulacjęw oparciu o radiany a nie stopnie

2211 Stałe matematyczneW pliku mathh zdefiniowane są pewne stałe ktoacutere mogą być przydatne do obliczeń Są to min

M E mdash podstawa logarytmu naturalnego (e liczba Eulera)

M LOG2E mdash logarytm o podstawie z liczby e

M LOG10E mdash logarytm o podstawie z liczby e

M LN2 mdash logarytm naturalny z liczby

M LN10 mdash logarytm naturalny z liczby

M PI mdash liczba π

M PI 2 mdash liczba π

M PI 4 mdash liczba π

M 1 PI mdash liczba π

M 2 PI mdash liczba π

161

162 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

222 Prezentacja liczb rzeczywisty w pamięci komputeraByć może ten temat może wydać Ci się niepotrzebnym lecz w wielu książkach nie ma w ogoacutele tegotematu Dzięki niemu zrozumiesz jak komputer radzi sobie z przecinkiem oraz dlaczego niektoacutere ob-liczenia dają niezbyt dokładne wyniki Na początek trochę teorii do przechowywania liczb rzeczywi-stych przeznaczone są typy float double oraz long double Zajmują one odpowiednio oraz bitoacutew Wiemy też że komputer nie ma fizycznej możliwości zapisania przecinka Sproacutebujmy terazzapisać jakąś liczbę wymierną w formie liczb binarnych Nasza liczba to powiedzmy 425 Sproacutebujmy jąrozbić na sumę potęg dwoacutejki 4 = 1 middot22 +0 middot21 +0 middot20 Dobra mdash rozpisaliśmy liczbę 4 ale co z częściądziesiętną Skorzystajmy z zasad matematyki 025 = 2minus2 Zatem nasza liczba powinna wyglądaćtak

10001Ponieważ komputer nie jest w stanie przechować pozycji przecinka ktoś wpadł na prosty ale

sprytny pomysł ustawienia przecinka jak najbliżej początku liczby i tylko mnożenia jej przez odpowied-nią potęgę dwoacutejki Taki sposoacuteb przechowywania liczb nazywamy zmiennoprzecinkowym a procesprzekształcania naszej liczby z postaci czytelnej przez człowieka na format zmiennoprzecinkowy na-zywamy normalizacją Wroacutećmy do naszej liczby mdash 425 W postaci binarnej wygląda ona tak 10001natomiast po normalizacji będzie wyglądała tak 10001 middot 22 W ten sposoacuteb w pamięci komputeraznajdą się dwie informacje liczba zakodowana w pamięci z ldquowirtualnymrdquo przecinkiem oraz numerpotęgi dwoacutejki Te dwie informacje wystarczają do przechowania wartości liczby Jednak pojawia sięinny problem mdash co się stanie jeśli np będziemy chcieli przełożyć liczbę typu 1

3 Otoacuteż tutaj wychodzą

na wierzch pewne niedociągnięcia komputera w dziedzinie samej matematyki daje w rozwinięciudziesiętnym 0(3) Jak zatem zapisać taką liczbę Otoacuteż nie możemy przechować całego jej rozwinięcia(wynika to z ograniczeń typu danych mdash ma on niestety skończoną liczbę bitoacutew) Dlatego przechowujesię tylko pewne przybliżenie liczby Jest ono tym bardziej dokładne im dany typ ma więcej bitoacutew Za-tem do obliczeń wymagających dokładnych danych powinniśmy użyć typu double lub long double Naszczęście w większości przeciętnych programoacutew tego typu problemy zwykle nie występują A ponie-waż początkujący programista nie odpowiada za tworzenie programoacutew sterujących np lotem statkukosmicznego więc drobne przekłamania na odległych miejscach po przecinku nie stanowią większegoproblemu

Należy brać pod uwagę że w komputerze liczby rzeczywiste nie są tym samym czym w mate-matyce Komputery nie potrafią przechować każdej liczby zmiennoprzecinkowej w związku z tymobliczenia prowadzone przy użyciu komputera mogą być niedokładne i odbiegać od prawidłowych wy-nikoacutew Szczegoacutelnie ważne jest to przy programowaniu aplikacji inżynieryjnych oraz w medycyniegdzie takie błędy mogą skutkować katastrofą ilub narażeniem ludzkiego życia oraz zdrowia

Na ile poważny jest to problem Sproacutebujmy przyjrzeć się działaniu polegającym na -krotnymdodawaniu do liczby wartości Oto kod

include ltstdiohgt

int main ()

float a = 0int i = 0for (ilt1000i++)

a += 1030printf (fn a)

Z matematyki wynika że 1000 middot 13

= 333(3) podczas gdy komputer wypisze wynik nieco roacuteżniącysię od oczekiwanego (w moim przypadku)

223 LICZBY ZESPOLONE 163

333334106

Błąd pojawił się na cyfrze części tysięcznej liczby Nie jest to może poważny błąd jednak zastanoacutewmysię czy ten błąd nie będzie się powiększał Zamieniamy w kodzie ilość iteracji z na Tymrazem moacutej komputer wskazał już nieco inny wynik

33356554688

Błąd przesunął się na cyfrę dziesiątek w liczbie Tak więc nie należy do końca polegać na prezentacjiliczb zmiennoprzecinkowych w komputerze

223 Liczby zespoloneOperacje na liczba zespolony są częścią uaktualnionego standardu języka C o nazwie C ktoacuteryjest obsługiwany jedynie przez część kompilatoroacutew

Podane tutaj informacje zostały sprawdzone na systemie Gentoo Linux z biblioteką GNU libc wwersji i kompilatorem GCC w wersji

Dotychczas korzystaliśmy tylko z liczb rzeczywistych lecz najnowsze standardy języka C umożli-wiają korzystanie także z innych liczb mdash np z liczb zespolonych

Abymoacutec korzystać z liczb zespolonychwnaszymprogramie należywnagłoacutewku programu umieścićnastępującą linijkę

include ltcomplexhgt

Wiemy że liczba zespolona zdeklarowana jest następująco

z = a+bi gdzie a b są liczbami rzeczywistymi a ii=(-1)

W pliku complexh liczba i zdefiniowana jest jako I Zatem wyproacutebujmy możliwości liczb zespolo-nych

include ltmathhgtinclude ltcomplexhgtinclude ltstdiohgt

int main ()

float _Complex z = 4+25Iprintf (Liczba z f+fin creal(z) cimag (z))return 0

następnie kompilujemy nasz program

gcc plik1c -o plik1 -lm

Po wykonaniu naszego programu powinniśmy otrzymać

Liczba z 400+250i

W programie zamieszczonym powyżej użyliśmy dwoacutech funkcji mdash creal i cimag

creal mdash zwraca część rzeczywistą liczby zespolonej

cimag mdash zwraca część urojoną liczby zespolonej

164 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

Rozdział 23

Powszene praktyki

Rozdział tenma za zadanie pokazać powszechnie stosowanemetody programowania wC Nie będziemytu uczyć jak należy stawiać nawiasy klamrowe ani ktoacutery sposoacuteb nazewnictwa zmiennych jest najlep-szy mdash prowadzone są o to spory z ktoacuterych niewiele wynika Zaprezentowane tu rozwiązania mająkonkretny wpływ na jakość tworzonych programoacutew

231 Konstruktory i destruktoryW większości obiektowych językoacutew programowania obiekty nie mogą być tworzone bezpośrednio mdashobiekty otrzymuje się wywołując specjalną metodę danej klasy zwaną konstruktorem Konstruktorysą ważne ponieważ pozwalają zapewnić obiektowi odpowiedni stan początkowy Destruktory wywo-ływane na końcu czasu życia obiektu są istotne gdy obiekt ma wyłączny dostęp do pewnych zasoboacutewi konieczne jest upewnienie się czy te zasoby zostaną zwolnione

Ponieważ C nie jest językiem obiektowym nie ma wbudowanego wsparcia dla konstruktoroacutew idestruktoroacutew Często programiści bezpośrednio modyfikują tworzone obiekty i struktury Jednakżeprowadzi to do potencjalnych błędoacutew ponieważ operacje na obiekcie mogą się nie powieść lub zacho-wać się nieprzewidywalnie jeśli obiekt nie został prawidłowo zainicjalizowany Lepszym podejściemjest stworzenie funkcji ktoacutera tworzy instancję obiektu ewentualnie przyjmując pewne parametry

struct string size_t sizechar data

struct string create_string(const char initial) assert (initial = NULL)struct string new_string = malloc(sizeof(new_string))if (new_string = NULL)

new_string-gtsize = strlen(initial)new_string-gtdata = strdup(initial)

return new_string

Podobnie bezpośrednie usuwanie obiektoacutew może nie do końca się udać prowadząc do wyciekuzasoboacutew Lepiej jest użyć destruktora

void free_string(struct string s)

165

166 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

assert (s = NULL)free(s-gtdata) zwalniamy pamięć zajmowaną przez strukturę free(s) usuwamy samą strukturę

Często łączy się destruktory z zerowaniem zwolnionych wskaźnikoacutewCzasami dobrze jest ukryć definicję obiektu żeby mieć pewność że użytkownicy nie utworzą go

ręcznie Aby to zapewnić struktura jest definiowanaw pliku źroacutedłowym (lub prywatnymnagłoacutewku nie-dostępnym dla użytkownikoacutew) zamiast w pliku nagłoacutewkowym a deklaracja wyprzedzająca jest umiesz-czona w pliku nagłoacutewkowym

struct stringstruct string create_string(const char initial)void free_string(struct string s)

232 Zerowanie zwolniony wskaźnikoacutewJak powiedziano już wcześniej po wywołaniu free() dla wskaźnika staje się on ldquowiszącym wskaź-nikiemrdquo Co gorsze większość nowoczesnych platform nie potrafi wykryć kiedy taki wskaźnik jestużywany zanim zostanie ponownie przypisany

Jednym z prostych rozwiązań tego problemu jest zapewnienie że każdy wskaźnik jest zerowanynatychmiast po zwolnieniu

free(p)p = NULL

Inaczej niż w przypadku ldquowiszących wskaźnikoacutewrdquo na wielu nowoczesnych architekturach przyproacutebie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek Dodatkowo programy mogązawierać sprawdzanie błędoacutew dla zerowych wartości ale nie dla ldquowiszących wskaźnikoacutewrdquo Aby zapew-nić że jest to wykonywane dla każdego wskaźnika możemy użyć makra

define FREE(p) do free(p) (p) = NULL while(0)

(aby zobaczyć dlaczego makro jest napisane w ten sposoacuteb zobacz Konwencje pisania makr)Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik ktoacutery przekazuje się do

nich więc argument musi być do nich przekazywany przez referencję Na przykład oto zaktualizowanydestruktor z sekcji Konstruktory i destruktory

void free_string(struct string s)

assert(s = NULL ampamp s = NULL)FREE((s)-gtdata) zwalniamy pamięć zajmowaną przez strukturę FREE(s) usuwamy strukturę

Niestety ten idiom nie jest wstanie pomoacutec wwypadkuwskazywania przez inne wskaźniki zwolnio-nej pamięci Z tego powodu niektoacuterzy eksperci C uważają go za niebezpieczny jako kreujący fałszywepoczucie bezpieczeństwa

233 Konwencje pisania makrPonieważ makra preprocesora działają na zasadzie zwykłego zastępowania napisoacutew są podatne nawiele kłopotliwych błędoacutew z ktoacuterych części można uniknąć przez stosowanie się do poniższych reguł

234 JAK DOSTAĆ SIĘ DO KONKRETNEGO BITU 167

Umieszczaj nawiasy dookoła argumentoacutew makra kiedy to tylko możliwe Zapewnia to że gdysą wyrażeniami kolejność działań nie zostanie zmieniona Na przykład

Źle define kwadrat(x) (xx)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Załoacuteżmy że w programie makro kwadrat() zdefiniowane bez nawiasoacutew zostałowywołane następująco kwadrat(a+b) Wtedy zostanie ono zamienione przez preprocesorna (a+ba+b) Z kolejności działań wiemy że najpierw zostanie wykonane mnożeniewięc wartość wyrażenia kwadrat(a+b) będzie roacuteżna od kwadratu wyrażenia a+b

Umieszczaj nawiasy dookoła całegomakra jeśli jest pojedynczymwyrażeniem Ponownie chronito przed zaburzeniem kolejności działań

Źle define kwadrat(x) (x)(x)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Definiujemy makro define suma(a b) (a)+(b) i wywołujemy je w kodziewynik = suma(3 4) 5 Makro zostanie rozwinięte jako wynik = (3)+(4)5 co mdash zpowodu kolejności działań mdash da wynik inny niż pożądany

Jeśli makro składa się z wielu instrukcji lub deklaruje zmienne powinno być umieszczone w pętlido while(0) bez kończącego średnika Pozwala to na użycie makra jak pojedynczejinstrukcji w każdym miejscu jak ciało innego wyrażenia pozwalając jednocześnie na umiesz-czenie średnika po makrze bez tworzenia zerowego wyrażenia Należy uważać by zmienne wmakrze potencjalnie nie kolidowały z argumentami makra

Źle define FREE(p) free(p) p = NULL

Dobrze define FREE(p) do free(p) p = NULL while(0)

Unikaj używania argumentoacutew makra więcej niż raz wewnątrz makra Może to spowodowaćkłopoty gdy argument makra ma efekty uboczne (np zawiera operator inkrementacji)

Przykład define kwadrat(x) ((x)(x)) nie powinno być wywoływane z operatoreminkrementacji kwadrat(a++) ponieważ zostanie to rozwinięte jako ((a++) (a++)) co jestniezgodne ze specyfikacją języka i zachowanie takiego wyrażenia jest niezdefiniowane(dwukrotna inkrementacja w tym samym wyrażeniu)

Jeśli makro może być w przyszłości zastąpione przez funkcję rozważ użycie w nazwie małychliter jak w funkcji

234 Jak dostać się do konkretnego bituWiemy że komputer to maszyna ktoacuterej najmniejszą jednostką pamięci jest bit jednak w C najmniejszazmienna ma rozmiar bitoacutew (czyli jednego bajtu) Jak zatem można odczytać wartość pojedynczychbitoacutew W bardzo prosty sposoacuteb mdashw zestawie operatoroacutew języka C znajdują się tzw operatory bitoweSą to m in

amp mdash logiczne ldquoirdquo

| mdash logiczne ldquolubrdquo

˜ mdash logiczne ldquonierdquo

Oproacutecz tego są także przesunięcia (ltlt oraz gtgt) Zastanoacutewmy się teraz jak je wykorzystać w prak-tyce Załoacuteżmy że zajmujemy się jednobajtową zmienną

unsigned char i = 2

Zmatematyki wiemy że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej) Jeśli teraz np chcielibyśmy ldquozapalićrdquo drugi bit od lewej (tj bit ktoacuterego zapalenie niejako ldquododardquo doliczby wartość 6) powinniśmy użyć logicznego lub

168 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

unsigned char i = 2i |= 64

Gdzie =6 Odczytywanie wykonuje się za pomocą tzw maski bitowej Polega to na

wyzerowaniu bitoacutew ktoacutere są nam w danej chwili niepotrzebne

odpowiedniemu przesunięciu bitoacutew dzięki czemu szukany bit znajdzie się na pozycji pierwszegobitu od prawej

Do ldquowyłuskaniardquo odpowiedniego bitu możemy posłużyć się operacją ldquoirdquo mdash czyli operatorem ampWygląda to analogicznie do posługiwania się operatorem ldquolubrdquo

unsigned char i = 3 bitowo 00000011 unsigned char temp = 0temp = i amp 1 sprawdzamy najmniej znaczący bit - czyli pierwszy z prawej if (temp)

printf (bit zapalony)else

printf (bit zgaszony)

Jeśli nie władasz biegle kodem binarnym tworzenie masek bitowych ułatwią ci przesunięcia bitoweAby uzyskać liczbę ktoacutera ma zapalony bit o numerze n (bity są liczone od zera) przesuwamy bitowo wlewo jedynkę o n pozycji

1 ltlt n

Jeśli chcemy uzyskać liczbę w ktoacuterej zapalone są bity na pozycjach l m n mdash używamy sumylogicznej (ldquolubrdquo)

(1 ltlt l) | (1 ltlt m) | (1 ltlt n)

Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone sąwszystkie bity poza n odwracamy ją za pomocąoperatora logicznej negacji

~(1 ltlt n)

Warto władać biegle operacjami na bitach ale początkujący mogą (po uprzednim przeanalizowa-niu) zdefiniować następujące makra i ich używać

Sprawdzenie czy w liczbie k jest zapalony bit n define IS_BIT_SET(k n) ((k) amp (1 ltlt (n)))

Zapalenie bitu n w zmiennej k define SET_BIT(k n) (k |= (1 ltlt (n)))

Zgaszenie bitu n w zmiennej k define RESET_BIT(k n) (k amp= ~(1 ltlt (n)))

235 Skroacutety notacjiIstnieją pewne sposoby ograniczenia ilości niepotrzebnego kodu Przykładem może być wykonywaniejednej operacji w razie wystąpienia jakiegoś warunku np zamiast pisać

if (warunek) printf (Warunek prawdziwyn)

235 SKROacuteTY NOTACJI 169

możesz skroacutecić notację do

if (warunek)printf (Warunek prawdziwyn)

Podobnie jest w przypadku pętli for

for (warunek)printf (Wyświetlam się w pętlin)

Niestety ograniczeniemw tymwypadku jest to że można w ten sposoacuteb zapisać tylko jedną instruk-cję

170 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

Rozdział 24

Przenośność programoacutew

Jak dowiedziałeś się z poprzednich rozdziałoacutew tego podręcznika język C umożliwia tworzenie progra-moacutew ktoacutere mogą być uruchamiane na roacuteżnych platformach sprzętowych pod warunkiem ich powtoacuter-nej kompilacji Język C należy do grupy językoacutew wysokiego poziomu ktoacutere tłumaczone są do poziomukodumaszynowego (tzn kod źroacutedłowy jest kompilowany) Z jednej strony jest to korzystne posunięciegdyż programy są szybsze i mniejsze niż programy napisane w językach interpretowanych (takich wktoacuterych kod źroacutedłowy nie jest kompilowany do kodu maszynowego tylko na bieżąco interpretowanyprzez tzw interpreter) Jednak istnieje także druga strona medalu mdash pewne zawiłości sprzętu ktoacutereograniczają przenośność programoacutew Ten rozdział ma wyjaśnić Ci mechanizmy działania sprzętu wtaki sposoacuteb abyś bez problemu moacutegł tworzyć poprawne i całkowicie przenośne programy

241 Niezdefiniowane zaowanie i zaowanie zależne odimplementacji

Wtrakcie czytania kolejnych rozdziałoacutewmożna było się natknąć na zwroty takie jak zachowanie niezde-finiowane (ang undefined behaviour) czy zachowanie zależne od implementacji (ang implementation-defined behaviour) Coacuteż one tak właściwie oznaczają

Zacznijmy od tego drugiego Autorzy standardu języka C czuli że wymuszanie jakiegoś konkret-nego działania danego wyrażenia byłoby zbytnim obciążeniem dla osoacuteb piszących kompilatory gdyżdany wymoacuteg moacutegłby być bardzo trudny do zrealizowania na konkretnej architekturze Dla przykładugdyby standard wymagał że typ unsigned char ma dokładnie bitoacutew to napisanie kompilatora dla ar-chitektury na ktoacuterej bajt ma bitoacutew byłoby cokolwiek kłopotliwe a z pewnością wynikowy programdziałałby o wiele wolniej niżby to było możliwe

Z tego właśnie powodu niektoacutere aspekty języka nie są określone bezpośrednio w standardzie i sąpozostawione do decyzji zespołu (osoby) piszącego konkretną implementację W ten sposoacuteb nie mażadnych przeciwwskazań (ze strony standardu) aby na architekturze gdzie bajty mają bitoacutew typchar roacutewnież miał tyle bitoacutew Dokonany wyboacuter musi być jednak opisany w dokumentacji kompilatoratak żeby osoba pisząca program w C mogła sprawdzić jak dana konstrukcja zadziała

Należy zatem pamiętać że poleganie na jakimś konkretnym działaniu programu w przypadkachzachowania zależnego od implementacji drastycznie zmniejsza przenośność kodu źroacutedłowego

Zachowania niezdefiniowane są o wiele groźniejsze gdyż zaistnienie takowego może spowodo-wać dowolny efekt ktoacutery nie musi być nigdzie udokumentowany Przykładem może tutaj być proacutebaodwołania się do wartości wskazywanej przez wskaźnik o wartości

Jeżeli gdzieś w naszym programie zaistnieje sytuacja niezdefiniowanego zachowania to nie jest jużto kwestia przenośności kodu ale po prostu błędu w kodzie chyba że świadomie korzystamy z roz-szerzenia naszego kompilatora Rozważmy odwoływanie się do wartości wskazywanej przez wskaźniko wartości Ponieważ według standardu operacja taka ma niezdefiniowany skutek to w szcze-goacutelności może wywołać jakąś z goacutery określoną funkcję mdash kompilator może coś takiego zrealizować

171

172 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

sprawdzając wartość wskaźnika przed każdą dereferencją w ten sposoacuteb niezdefiniowane zachowaniedla konkretnego kompilatora stanie się jak najbardziej zdefiniowane

Sytuacją wziętą z życia są operatory przesunięć bitowych gdy działają na liczbach ze znakiemKonkretnie przesuwanie w lewo liczb jest dla wielu przypadkoacutew niezdefiniowane Bardzo często jed-nak w dokumentacji kompilatora działanie przesunięć bitowych jest dokładnie opisane Jest to o tyleinteresujący fakt iż wielu programistoacutew nie zdaje sobie z niego sprawy i nieświadomie korzysta z roz-szerzeń kompilatora

Istnieje jeszcze trzecia klasa zachowań Zachowania nieokreślone (ang unspecified behaviour)Są to sytuacje gdy standard określa kilka możliwych sposoboacutew w jaki dane wyrażenie może działaći pozostawia kompilatorowi decyzję co z tym dalej zrobić Coś takiego nie musi być nigdzie opisanew dokumentacji i znowu poleganie na konkretnym zachowaniu jest błędem Klasycznym przykłademmoże być kolejność obliczania argumentoacutew wywołania funkcji

242 Rozmiar zmiennyRozmiar poszczegoacutelnych typoacutew danych (np char int czy long) jest roacuteżna na roacuteżnych platformachgdyż nie jest definiowany w sztywny sposoacuteb jak np ldquolong int zawsze powinien mieć bityrdquo (takieokreślenie wiązałoby się z wyżej opisanymi trudnościami) lecz w na zasadzie zależności typu ldquolongpowinien być nie kroacutetszy niż intrdquo ldquoshort nie powinien być dłuższy od intrdquo Pierwsza standaryzacjajęzyka C zakładała że typ int będzie miał taki rozmiar jak domyślna długość liczb całkowitych nadanym komputerze natomiast modyfikatory short oraz long zmieniały długość tego typu tylko wtedygdy dana maszyna obsługiwała typy o mniejszej lub większej długości1

Z tego powodu nigdy nie zakładaj że dany typ będzie miał określony rozmiar Jeżeli potrzebujesztypu o konkretnym rozmiarze (a dokładnej konkretnej liczbie bitoacutew wartości) możesz skorzystać z plikunagłoacutewkowego stdinth wprowadzonego do języka przez standard ISO C z roku Definiuje on typyint t int t int t int t uint t uint t uint t i uint t (o ile w danej architekturze występujątypy o konkretnej liczbie bitoacutew)

Jednak możemy posiadać implementację ktoacutera nie posiada tego pliku nagłoacutewkowego W takiej sy-tuacji nie pozostaje nam nic innego jak tworzyć własny plik nagłoacutewkowy w ktoacuterym za pomocą słoacutewkatypedef sami zdefiniujemy potrzebne nam typy Np

typedef unsigned char u8typedef signed char s8typedef unsigned short u16typedef signed short s16typedef unsigned long u32typedef signed long s32typedef unsigned long long u64typedef signed long long s64

Aczkolwiek należy pamiętać że taki plik będzie trzeba pisać od nowa dla każdej architektury najakiej chcemy kompilować nasz program

243 Porządek bajtoacutew i bitoacutew

2431 Bajty i słowaWiesz zapewne że podstawową jednostką danych jest bit ktoacutery może mieć wartość lub Kilkakolejnych bitoacutew2 stanowi bajt (dla skupienia uwagi przyjmijmy że bajt składa się z bitoacutew) Częstotyp short ma wielkość dwoacutech bajtoacutew i woacutewczas pojawia się pytanie w jaki sposoacuteb są one zapisane

1Dokładniejszy opis rozmiaroacutew dostępny jest w rozdziale Składnia2Standard wymaga aby było ich co najmniej 8 i liczba bitoacutew w bajcie w konkretnej implementacji jest określona

przez makro CHAR BIT zdefiniowane w pliku nagłoacutewkowym limitsh

243 PORZĄDEK BAJTOacuteW I BITOacuteW 173

w pamięci mdash czy najpierw ten bardziej znaczący mdash big-endian czy najpierw ten mniej znaczący mdashlittle-endian

Skąd takie nazwy Otoacuteż pochodzą one z książki Podroacuteże Guliwera w ktoacuterej liliputy kłoacuteciły się ostronę od ktoacuterej należy rozbijać jajko na twardo Jedni uważali że trzeba je rozbijać od grubszegokońca (big-endian) a drudzy że od cieńszego (lile-endian) Nazwy te są o tyle trafne że w wypadkuprocesoroacutew wyboacuter kolejności bajtoacutew jest sprawą czysto polityczną ktoacutera jest technicznie neutralna

Sprawa się jeszcze bardziej komplikuje w przypadku typoacutew ktoacutere składają się np z bajtoacutew Woacutew-czas są aż ( silnia) sposoby zapisania kolejnych fragmentoacutew takiego typu W praktyce zapewne spo-tkasz się jedynie z kolejnościami big-endian lub lile-endian co nie zmienia faktu że inne możliwościtakże istnieją i przy pisaniu programoacutew ktoacutere mają być przenośne należy to brać pod uwagę

Poniższy przykład dobrze obrazuje oba sposoby przechowywania zawartości zmiennych w pamięcikomputera (przyjmujemy CHAR BIT == oraz sizeof(long) == bez bitoacutew wypełnienia (ang paddingbits)) unsigned long zmienna = 0x01020304 w pamięci komputera będzie przechowywana tak

adres | 0 | 1 | 2 | 3 |big-endian |0x01|0x02|0x03|0x04|little-endian |0x04|0x03|0x02|0x01|

2432 Konwersja z jednego porządku do innegoCzasami zdarza się że napisany przez nas program musi się komunikować z innym programem (możeteż przez nas napisanym) ktoacutery działa na komputerze o (potencjalnie) innym porządku bajtoacutew Częstonajprościej jest przesyłać liczby jako tekst gdyż jest on niezależny od innych czynnikoacutew jednak takiformat zajmuje więcej miejsca a nie zawsze możemy sobie pozwolić na taką rozrzutność

Przykładem może być komunikacja sieciowa w ktoacuterej przyjęło się że dane przesyłane są w po-rządku big-endian Aby moacutec łatwo operować na takich danych w standardzie zdefiniowanonastępujące funkcje (w zasadzie zazwyczaj są to makra)

include ltarpainethgtuint32_t htonl(uint32_t hostlong)uint16_t htons(uint16_t hostshort)uint32_t ntohl(uint32_t netlong)uint16_t ntohs(uint16_t netshort)

Pierwsze dwie konwertują liczbę z reprezentacji lokalnej na reprezentację big-endian (host to ne-twork) natomiast kolejne dwie dokonują konwersji w drugą stronę (network to host)

Można roacutewnież skorzystać z pliku nagłoacutewkowego endianh w ktoacuterym definiowane są makra po-zwalające określić porządek bajtoacutew

include ltendianhgtinclude ltstdiohgt

int main() if __BYTE_ORDER == __BIG_ENDIAN

printf(Porządek big-endian (4321)n)elif __BYTE_ORDER == __LITTLE_ENDIAN

printf(Porządek little-endian (1234)n)elif defined __PDP_ENDIAN ampamp __BYTE_ORDER == __PDP_ENDIAN

printf(Porządek PDP (3412)n)else

printf(Inny porządek (d)n __BYTE_ORDER)endif

return 0

174 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

Na podstawie makra BYTE ORDER można skonstruować funkcję ktoacutera będzie konwertowaćliczby pomiędzy porządkiem roacuteżnymi porządkami

include ltendianhgtinclude ltstdiohgtinclude ltstdinthgt

uint32_t convert_order32(uint32_t val unsigned from unsigned to) if (from==to)

return val else

uint32_t ret = 0unsigned char tmp[5] = 0 0 0 0 0 unsigned char ptr = (unsigned char)ampvalunsigned div = 1000do tmp[from div 10] = ptr++ while ((div = 10))ptr = (unsigned char)ampretdiv = 1000do ptr++ = tmp[to div 10] while ((div = 10))return ret

define LE_TO_H(val) convert_order32((val) 1234 __BYTE_ORDER)define H_TO_LE(val) convert_order32((val) __BYTE_ORDER 1234)define BE_TO_H(val) convert_order32((val) 4321 __BYTE_ORDER)define H_TO_BE(val) convert_order32((val) __BYTE_ORDER 4321)define PDP_TO_H(val) convert_order32((val) 3412 __BYTE_ORDER)define H_TO_PDP(val) convert_order32((val) __BYTE_ORDER 3412)

int main ()

printf(08xn LE_TO_H(0x01020304))printf(08xn H_TO_LE(0x01020304))printf(08xn BE_TO_H(0x01020304))printf(08xn H_TO_BE(0x01020304))printf(08xn PDP_TO_H(0x01020304))printf(08xn H_TO_PDP(0x01020304))return 0

Ciągle jednak polegamy na niestandardowym pliku nagłoacutewkowym endianh Można go wyelimi-nować sprawdzając porządek bajtoacutew w czasie wykonywania programu

include ltstdiohgtinclude ltstdinthgt

int main() uint32_t val = 0x04030201unsigned char v = (unsigned char )ampvalint byte_order = v[0] 1000 + v[1] 100 + v[2] 10 + v[3]

if (byte_order == 4321) printf(Porządek big-endian (4321)n)

else if (byte_order == 1234)

244 BIBLIOTECZNE PROBLEMY 175

printf(Porządek little-endian (1234)n) else if (byte_order == 3412)

printf(Porządek PDP (3412)n) else

printf(Inny porządek (d)n byte_order)return 0

Powyższe przykłady opisują jedynie część problemoacutew jakie mogą wynikać z proacuteby przenoszeniabinarnych danych pomiędzy wieloma platformami Wszystkie co więcej zakładają że bajt ma bitoacutewco wcale nie musi być prawdą dla konkretnej architektury na ktoacuterą piszemy aplikację Co więcej liczbymogą posiadać w swojej reprezentacje bity wypełnienia (ang padding bits) ktoacutere nie biorą udziaływ przechowywaniu wartości liczby Te wszystkie roacuteżnice mogą dodatkowo skomplikować kod Toteżnależy być świadomym iż przenosząc dane binarnie musimy uważać na roacuteżne reprezentacje liczb

244 Biblioteczne problemy

2441 Dostępność bibliotekPisząc programy nieraz będziemy musieli korzystać z roacuteżnych bibliotek Problem polega na tym żenie zawsze będą one dostępne na komputerze na ktoacuterym inny użytkownik naszego programu będzieproacutebował go kompilować Dlatego też ważne jest abyśmy korzystali z łatwo dostępnych bibliotek ktoacuteredostępne są na wiele roacuteżnych systemoacutew i platform sprzętowych Zapamiętaj Twoacutej program jest natyle przenośny na ile przenośne są biblioteki z ktoacuterych korzysta

2442 Odmiany bibliotekPod Windows funkcje atan floor i fabs są w tej samej bibliotece co standardowe funkcje C

Pod Uniksami są w osobnej bibliotece matematycznej libm w wersji

statycznej (zwykle usrliblibma) i pliku nagłoacutewkowym mathh (zwykle usrincludemathh)3

ladowanej dynamicznie ( usrliblibmso )

Aby korzystać z tych funkcji potrzebujemy

dodać include ltmathhgt

przy kompilacji dołączyć bibliotekę libm gcc mainc -lm

Opcja -lm używa libmso albo libma w zależności od tego ktoacutere są znalezione i w zależności odobecności opcji -static45

245 Kompilacja warunkowaPrzy zwiększaniu przenośności kodu może pomoacutec preprocessor Przyjmijmy np że chcemy korzy-stać ze słoacutewka kluczowego inline wprowadzonego w standardzie C ale roacutewnocześnie chcemy abynasz program był rozumiany przez kompilatory ANSI CWoacutewczas możemy skorzystać z następującegokodu

ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline

3An Introduction to mdashfor the compilers gcc and g++ 27 Linking with external libraries4man ld5Dyskusja na grupie plcomposlinuxprogramowanie na temat c gc atan2 floor fabs

176 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

else define __inline__ endifendif

a w kodzie programu zamiast słoacutewka inline stosować inline Co więcej kompilator rozumiesłoacutewka kluczowe tak tworzone i w jego przypadku warto nie redefiniować ich wartości

ifndef __GNUC__ ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline else define __inline__ endif endifendif

Korzystając z kompilacji warunkowej można także korzystać z roacuteżnego kodu zależnie od (np) sys-temu operacyjnego Przykładowo przed kompilacją na konkretnej platformie tworzymy odpowiedniplik configh ktoacutery następnie dołączamy do wszystkich plikoacutew źroacutedłowych w ktoacuterych podejmujemydecyzje na podstawie zdefiniowanych makr Dla przykładu plik configh

ifndef CONFIG_Hdefine CONFIG_H

Uncomment if using Windows define USE_WINDOWS

Uncomment if using Linux define USE_LINUX

error You must edit configh fileerror Edit it and remove those error lines

endif

Jakiś plik źroacutedłowy

include configh

ifdef USE_WINDOWSrob_cos_wersja_dla_windows()

elserob_cos_wersja_dla_linux()

endif

Istnieją roacuteżne narzędzia ktoacutere pozwalają na automatyczne tworzenie takich plikoacutew configh dziękiczemu użytkownik przed skompilowaniem programu nie musi się trudzić i edytować ich ręcznie ajedynie uruchomić odpowiednie polecenie Przykładem jest zestaw autoconf i automake

Rozdział 25

Łączenie z innymi językami

Programista pisząc jakiś program ma problem z wyborem najbardziej odpowiedniego języka do utwo-rzenia tego programu Niekiedy zdarza się że najlepiej byłoby pisać program korzystając z roacuteżnychjęzykoacutew Język C może być z łatwością łączony z innymi językami programowania ktoacutere podlegająkompilacji bezpośrednio do kodu maszynowego (Asembler Fortran czy też C++) Ponadto dzięki spe-cjalnym bibliotekom można go łączyć z językami bardzo wysokiego poziomu (takimi jak np Pythonczy też Ruby) Ten rozdział ma za zadanie wytłumaczyć Ci w jaki sposoacuteb można mieszać roacuteżne językiprogramowania w jednym programie

251 Język C i Asembler

Informacje zawarte w tym rozdziale odnoszą się do komputeroacutew z procesorem i i pokrewnych

Łączenie języka C i języka asemblera jest dość powszechnym zjawiskiem Dzięki możliwości połączeniaobu tych językoacutew programowania można było utworzyć bibliotekę dla języka C ktoacutera niskopoziomowokomunikuje się z jądrem systemu operacyjnego komputera Ponieważ zaroacutewno asembler jak i C sąjęzykami tłumaczonymi do poziomu kodu maszynowego za ich łączenie odpowiada program zwanylinkerem (popularny ld) Ponadto niektoacuterzy producenci kompilatoroacutew umożliwiają stosowanie tzwwstawek asemblerowy ktoacutere umieszcza się bezpośrednio w kodzie programu napisanego w językuC Kompilator kompilując taki kod wstawi w miejsce tychże wstawek odpowiedni kod maszynowyktoacutery jest efektem przetłumaczenia kodu asemblera zawartegow takiej wstawce Opiszę tu oba sposobyłączenia obydwu językoacutew

2511 Łączenie na poziomie kodu maszynowegoW naszym przykładzie założymy że w pliku fS zawarty będzie kod napisany w asemblerze a fcto kod z programem w języku C Program w języku C będzie wykorzystywał jedną funkcję napisanąw języku asemblera ktoacutera wyświetli prosty napis ldquoHello worldrdquo Z powodu ograniczeń technicznychzakładamy że program uruchomiony zostanie w środowisku POSIX na platformie i i skompilowanykompilatorem gcc Używaną składnią asemblera będzie ATampT (domyślna dla asemblera ) Oto plikfS

text

globl _f1_f1

pushl ebpmovl esp ebp

177

178 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

movl $4 eax 4 to funkcja systemowa write movl $1 ebx 1 to stdout movl $tekst ecx adres naszego napisu movl $len edx długość napisu w bajtach int $0x80 wywołanie przerwania systemowego popl ebpret

datatekst

string Hello worldnlen = - tekst

W systemach z rodziny UNIX należy pominąć znak rdquo rdquoprzed nazwą funkcji f

Teraz kolej na fc

extern void f1 (void) musimy użyć słowa extern int main ()

f1()return 0

Teraz możemy skompilować oba programy

as f1S -o f1ogcc f2c -c -o f2ogcc f2o f1o -o program

W ten sposoacuteb uzyskujemy plik wykonywalny o nazwie ldquoprogramrdquo Efekt działania programu powinienbyć następujący

Hello world

Na razie utworzyliśmy bardzo prostą funkcję ktoacutera w zasadzie nie komunikuje się z językiem Cczyli nie zwraca żadnej wartości ani nie pobiera argumentoacutew Jednak aby zacząć pisać obsługę funk-cji ktoacutera będzie pobierała argumenty i zwracała wyniki musimy poznać działanie języka C od trochęniższego poziomu

Argumenty

Do komunikacji z funkcją język C korzysta ze stosu Argumenty odkładane sąw kolejności od ostatniegodo pierwszego Ponadto na końcu odkładany jest tzw adres powrotu dzięki czemu po wykonaniufunkcji program ldquowierdquo w ktoacuterym miejscu ma kontynuować działanie Ponadto początek funkcji wasemblerze wygląda tak

pushl ebpmovl esp ebp

Zatem na stosie znajdują się kolejno zawartość rejestru EBP adres powrotu a następnie argumenty odpierwszego do n-tego

251 JĘZYK C I ASEMBLER 179

Zwracanie wartości

Na architekturze i do zwracaniawynikoacutew pracy programu używa się rejestru EAX bądź jego ldquomniej-szychrdquo odpowiednikoacutew tj AX i AHAL Zatem aby funkcja napisana w asemblerze zwroacuteciła ldquordquo przedrozkazem ret należy napisać

movl $1 eax

Nazewnictwo

Kompilatory języka CC++ dodają podkreślnik ldquo rdquo na początku każdej nazwy Dla przykładu funkcja

void funkcja()

W pliku wyjściowym będzie posiadać nazwę funkcja Dlatego aby korzystać z poziomu języka C zfunkcji zakodowanych w asemblerze muszą one mieć przy definicji w pliku asemblera wspomnianydodatkowy podkreślnik na początku

Łączymy wszystko w całość

Pora abyśmy napisali jakąś funkcję ktoacutera pobierze argumenty i zwroacuteci jakiś konkretny wynik Otokod fS

text

globl _funkcja_funkcja

pushl ebpmovl esp ebpmovl 8(esp) eax kopiujemy pierwszy argument do eax addl 12(esp) eax do pierwszego argumentu w eax dodajemy drugi argument popl ebpret i zwracamy wynik dodawania

oraz fc

include ltstdiohgtextern int funkcja (int a int b)int main ()printf (2+3=dn funkcja(23))return 0

Po skompilowaniu i uruchomieniu programu powinniśmy otrzymać wydruk +=

2512 Wstawki asembleroweOproacutecz możliwości wstępnie skompilowanych modułoacutew możesz posłużyć się także tzw wstawkamiasemblerowymi Ich użycie powoduje wstawienie w miejsce wystąpienia wstawki odpowiedniegokodumaszynowego ktoacutery powstanie po przetłumaczeniu kodu asemblerowego Ponieważ jednakwstawkiasemblerowe nie są standardowym elementem języka C każdy kompilator ma całkowicie odmiennąfilozofię ich stosowania (lub nie ma ich w ogoacutele) Ponieważ w tym podręczniku używamy głoacutewniekompilatora więc w tym rozdziale zostanie omoacutewiona filozofia stosowania wstawek asemblerawedług programistoacutew

Ze wstawek asemblerowych korzysta się tak

180 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

int main ()

asm (nop)

W tym wypadku wstawiona zostanie instrukcja ldquonoprdquo (no operation) ktoacutera tak naprawdę służytylko i wyłącznie do konstruowania pętli opoacuteźniających

252 C++Język C++ z racji swojego podobieństwa do C będzie wyjątkowo łatwy do łączenia Pewnym utrudnie-niem może być obiektowość języka C++ oraz występowanie w nim przestrzeni nazw oraz możliwośćprzeciążania funkcji Oczywiście nadal zakładamy że głoacutewny program piszemy w C natomiast korzy-stamy tylko z pojedynczych funkcji napisanych w C++ Ponieważ język C nie oferuje tego wszystkiegoco daje programiście język C++ to musimy ldquozmusićrdquo C++ do wyłączenia pewnych swoich możliwościaby można było połączyć ze sobą elementy programu napisane w dwoacutech roacuteżnych językach Używa siędo tego następującej konstrukcji

extern C funkcje zmienne i wszystko to co będziemy łączyć z programem w C

W zrozumieniu teorii pomoże Ci prosty przykład plik fc

include ltstdiohgtextern int f2(int a)

int main ()

printf (dn f2(2))return 0

oraz plik fcpp

include ltiostreamgtusing namespace stdextern C

int f2 (int a)

cout ltlt a= ltlt a ltlt endlreturn a2

Teraz oba pliki kompilujemy

gcc f1c -c -o f1og++ f2cpp -c -o f2o

Przy łączeniu obu tych plikoacutew musimy pamiętać że język C++ także korzysta ze swojej bibliotekiZatem poprawna postać polecenia kompilacji powinna wyglądać

gcc f1o f2o -o program -lstdc++

(stdc++ mdash biblioteka standardowa języka C++) Bardzo istotne jest tutaj to abyśmy zawsze pamiętalio extern ldquoCrdquo gdyż w przeciwnym razie funkcje napisane w C++ będą dla programu w C całkowicieniewidoczne

Dodatek A

Indeks alfabetyczny

Alfabetyczny spis funkcji biblioteki standardowej ANSI C (tzw libc) w wersji C

A01 A abort()

abs()

acos()

asctime()

asin()

assert()

atan()

atan()

atexit()

atof()

atoi()

atol()

A02 B bsearch()

A03 C calloc()

ceil()

clearerr()

clock()

cos()

cosh()

ctime()

A04 D diime()

div()

A05 E errno (zmienna)

exit()

exp()

A06 F fabs()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

floor()

fmod()

fopen()

fprintf()

fputc()

fputs()

fread()

free()

freopen()

frexp()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

A07 G getc()

getchar()

getenv()

gets()

gmtime()

A08 I isalnum()

isalpha()

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

181

182 DODATEK A INDEKS ALFABETYCZNY

A09 L labs()

ldexp()

ldiv()

localeconv()

localtime()

log()

log()

longjmp()

A010 M malloc()

mblen()

mbstowcs()

mbtowc()

memchr()

memcmp()

memcpy()

memmove()

memset()

mktime()

modf()

A011 O offsetof()

A012 P perror()

pow()

printf()

putc()

putchar()

puts()

A013 Q qsort()

A014 R raise()

rand()

realloc()

remove()

rename()

rewind()

A015 S scanf()

setbuf()

setjmp()

setlocale()

setvbuf()

signal()

sin()

sinh()

sprintf()

sqrt()

srand()

sscanf()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strime()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtod()

strtok()

strtol()

strtoul()

strxfrm()

system()

A016 T tan()

tanh()

time()

tm (struktura)

tmpfile()

tmpnam()

tolower()

toupper()

A017 U ungetc()

A018 V va arg()

va end()

va start()

vfprintf()

vprintf()

vsprintf()

A019 W wcstombs()

wctomb()

Dodatek B

Indeks tematyczny

Spis plikoacutew nagłoacutewkowych oraz zawartych w nich funkcji i makr biblioteki standardowej C Funkcjemakra i typy wprowadzone dopiero w standardzie C zostały oznaczone poprzez ldquo[C]rdquo po nazwie

B1 asserthMakro asercji

assert()

B2 ctypehKlasyfikowanie znakoacutew

isalnum()

isalpha()

isblank() [C]

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

tolower()

toupper()

B3 errnohDeklaracje kodoacutew błędoacutew

EDOM (makro)

EILSEQ (makro) [C]

ERANGE (makro)

errno (zmienna)

B4 floathWłaściwości typoacutew zmiennoprzecinkowych zależne od implementacji

B5 limitshWłaściwości typoacutew całkowitych zależne od implementacji

183

184 DODATEK B INDEKS TEMATYCZNY

B6 localehUstawienia międzynarodowe

localeconv()

setlocale()

B7 mathhFunkcje matematyczne

FP FAST FMAF (makro) [C]

FP FAST FMAL (makro) [C]

FP FAST FMA (makro) [C]

FP ILOGB (makro) [C]

FP ILOGBNAN (makro) [C]

FP INFINITE (makro) [C]

FP NAN (makro) [C]

FP NORMAL (makro) [C]

FP SUBNORMAL (makro) [C]

FP ZERO (makro) [C]

HUGE VALF (makro) [C]

HUGE VALL (makro) [C]

HUGE VAL (makro)

INFINITY (makro) [C]

MATH ERREXCEPT (makro) [C]

MATH ERRNO (makro) [C]

NAN (makro) [C]

acosh()

acos()

asinh()

asin()

atan()

atanh()

atan()

cbrt() [C]

ceil()

copysign() [C]

cosh()

cos()

double t (typ) [C]

erfc() [C]

erf() [C]

exp() [C]

expm() [C]

exp()

fabs()

fdim() [C]

flaot t (typ) [C]

floor()

fmax() [C]

fma() [C]

fmin() [C]

fmod()

fpclassify() [C]

frexp()

hypot() [C]

ilogb() [C]

isfinite() [C]

isgreaterequal() [C]

isgreater() [C]

isinf() [C]

islessequal() [C]

islessgreater() [C]

isless() [C]

isnan() [C]

isnormal() [C]

isunordered() [C]

ldexp()

lgamma() [C]

llrint() [C]

llround() [C]

log()

logp() [C]

log() [C]

logb() [C]

log()

B8 SETJMPH 185

lrint() [C]

lround() [C]

math errhandling (makro) [C]

modf()

nan() [C]

nearbyint() [C]

nextaer() [C]

nexoward() [C]

pow()

remainder() [C]

remquo() [C]

rint() [C]

round() [C]

scalbln() [C]

scalbn() [C]

signbit() [C]

sinh()

sin()

sqrt()

tanh()

tan()

tgamma() [C]

trunc() [C]

B8 setjmphObsługa nielokalnych skokoacutew

longjmp()

setjmp()

B9 signalhObsługa sygnałoacutew

raise()

signal()

B10 stdarghNarzędzia dla funkcji ze zmienną liczbą argumentoacutew

va arg()

va end()

va start()

B11 stddefhStandardowe definicje

offsetof()

B12 stdiohStandard InputOutput czyli standardowe wejście-wyjście

clearerr()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

fopen()

186 DODATEK B INDEKS TEMATYCZNY

fprintf()

fputc()

fputs()

fread()

freopen()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

getc()

getchar()

gets()

perror()

printf()

putc()

putchar()

puts()

remove()

rename()

rewind()

scanf()

setbuf()

setvbuf()

sprintf()

sscanf()

tmpfile()

tmpnam()

ungetc()

vfprintf()

vprintf()

vsprintf()

B13 stdlibhNajbardziej podstawowe funkcje

abort()

abs()

atexit()

atof()

atoi()

atol()

bsearch()

calloc()

div()

exit()

free()

getenv()

labs()

ldiv()

malloc()

mblen()

mbstowcs()

mbtowc()

qsort()

rand()

realloc()

srand()

strtod()

strtol()

strtoul()

system()

wctomb()

wcstombs()

B14 stringhOperacje na łańcuchach znakoacutew

memchr()

memcmp()

memcpy()

memmove()

memset()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtok()

strxfrm()

strdup()

B15 timehFunkcje obsługi czasu

B15 TIMEH 187

asctime()

clock()

ctime()

diime()

gmtime()

localtime()

mktime()

strime()

time()

tm (struktura)

188 DODATEK B INDEKS TEMATYCZNY

Dodatek C

Wybrane funkcje bibliotekistandardowej

C1 assert

C11 Deklaracjadefine assert(expr)

C12 Plik nagłoacutewkowyasserth

C13 OpisMakro przypominające w użyciu funkcję służy do debuggowania programoacutew Gdy testowany waruneklogiczny expr przyjmuje wartość fałsz na standardowe wyjście błędoacutew wypisywany jest komunikat obłędzie (zawierające min argument wywołania makra nazwę funkcji w ktoacuterej zostało wywołanenazwę pliku źroacutedłowego oraz numer linii w formacie zależnym od implementacji) i program jest prze-rywany poprzez wywołanie funkcji abort

W ten sposoacuteb możemy oznaczyć w programie niezmienniki czyli warunki ktoacutere niezależnie odwartości zmiennych muszą pozostać prawdziwe Jeśli asercja zawiedzie oznacza to że popełniliśmybłąd w algorytmie piszemy sobie po pamięci (nadając zmiennym wartości ktoacuterych nigdy nie powinnymieć) albo nastąpiła po drodze sytuacja wyjątkowa na przykład związana z obsługą operacji wejścia-wyjścia

Można łatwo pozbyć się asercji uwalniając kod od spowalniających obciążeń a jednocześnie niemusząc kasować wystąpień assert i zachowując je na przyszłość Aby to zrobić należy przed dołą-czeniem pliku nagłoacutewkowego asserth zdefiniować makro NDEBUG woacutewczas makro assert przyjmujepostać

define assert(ignore) ((void)0)

Makro assert jest redefiniowane za każdym dołączeniem pliku nagłoacutewkowego asserth

C14 Wartość zwracanaMakro nie zwraca żadnej wartości

189

190 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C15 Przykładinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

Program wypisze komunikat podobny do

Assertion failed err==0 file testc line 6

Natomiast jeśli uruchomimy

define NDEBUGinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

nie pojawi się żaden komunikat o błędach

C2 atoi

C21 Deklaracjaint atoi (const char string)

C22 Plik nagłoacutewkowystdlibh

C23 OpisFunkcja jako argument pobiera liczbę w postaci ciągu znakoacutew ASCII a następnie zwraca jej wartość wformacie int Liczbę może poprzedzać dowolona ilość białych znakoacutew (spacje tabulatory itp) oraz jejznak (plus (+) lub minus (-)) Funkcja atoi() kończy wczytywać znaki w momencie napotkania jakiego-kowiek znaku ktoacutery nie jest cyfrą

C24 Wartość zwracanaW przypadku gdy ciąg nie zawiera cyfr zwracana jest wartość

C25 UwagiZnak musi bezpośrednio poprzedzać liczbę czyli możliwy jest zapis ldquo-rdquo natomiast proacuteba potraktowa-nia funkcją atoi ciągu ldquo- rdquo skutkuje zwracaną wartością

C3 ISALNUM 191

C26 Przykładinclude ltstdiohgtinclude ltstdlibhgtint main(void)

char c_Numer = nt 2004uint i_Numeri_Numer = atoi(c_Numer)printf(n Liczba typu int d oraz jako ciąg znakoacutew s n i_Numer c_Numer)return 0

C3 isalnum

C31 Deklaracjainclude ltctypehgt

int isalnum(int c)int isalpha(int c)int isblank(int c)int iscntrl(int c)int isdigit(int c)int isgraph(int c)int islower(int c)int isprint(int c)int ispuntc(int c)int isspace(int c)int isupper(int c)int isxdigit(int c)

C32 Argumentyc wartość znaku reprezentowana w jako typ unsigned char lub wartość makra EOF Z tego powodu

przed przekazaniem funkcji argumentu typu char lub signed char należy go zrzutować na typunsigned char lub unsigned int

C33 OpisFunkcje sprawdzają czy podany znak spełnia jakiś konkretny warunek Biorą pod uwagę ustawieniajęzyka i dla roacuteżnych znakoacutew w roacuteżnych localersquoach mogą zwracać roacuteżne wartości

isalnum sprawdza czy znak jest liczbą lub literą

isalpha sprawdza czy znak jest literą

isblank sprawdza czy znak jest znakiem odstępu służącym do oddzielania wyrazoacutew (standardowymiznakami odstępu są spacja i znak tabulacji)

iscntrl sprawdza czy znak jest znakiem sterującym

isdigit sprawdza czy znak jest cyfrą dziesiętna

isgraph sprawdza czy znak jest znakiem drukowalnym roacuteżnym od spacji

islower sprawdza czy znak jest małą literą

isprint sprawdza czy znak jest znakiem drukowalnym (włączając w to spację)

192 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

ispunct sprawdza czy znak jest znakiem przestankowym dla ktoacuterego ani isspace ani isalnum nie sąprawdziwe (standardowo są to wszystkie znaki drukowalne dla ktoacuterych te funkcje zwracajązero)

isspace sprawdza czy znak jest tzw białym znakiem (standardowymi białymi znakami są spacjawysunięcie strony rsquorsquo znak przejścia do nowej linii rsquonrsquo znak powrotu karetki rsquorrsquo tabulacjapozioma rsquotrsquo i tabulacja pionowa rsquovrsquo)

isupper sprawdza czy znak jest dużą literą

isxdigit sprawdza czy znak jest cyfrą szesnastkową tj cyfrą dziesiętną lub literą od rsquoarsquo do rsquorsquo niezależnieod wielkości

Funkcja isblank niewystępowaław oryginalnym standardzie ANSI C z roku (tzw C) i zostaładodana dopiero w nowszym standardzie z roku (tzw C)

C34 Wartość zwracanaLiczba niezerowa gdy podany argument spełnia konkretny warunek w przeciwnym wypadku mdash zero

C35 Przykład użyciainclude ltctypehgt funkcje is include ltlocalehgt setlocale include ltstdiohgt printf i scanf

void identify_char(int c) printf( Litera lub cyfra sn isalnum (c) tak nie)

if __STDC_VERSION__ gt= 199901Lprintf( Odstęp sn isblank (c) tak nie)

endifprintf( Znak sterujący sn iscntrl (c) tak nie)printf( Cyfra dziesiętna sn isdigit (c) tak nie)printf( Graficzny sn isgraph (c) tak nie)printf( Mała litera sn islower (c) tak nie)printf( Drukowalny sn isprint (c) tak nie)printf( Przestankowy sn ispunct (c) tak nie)printf( Biały znak sn isspace (c) tak nie)printf( Wielka litera sn isupper (c) tak nie)printf( Cyfra szesnastkowa sn isxdigit(c) tak nie)

int main() unsigned char cprintf(Naciśnij jakiś klawiszn)if (scanf(c ampc)==1)

identify_char(c)setlocale(LC_ALL pl_PL) przystosowanie do warunkoacutew polskich puts(Po zmianie ustawień języka)identify_char(c)

return 0

C36 Zobacz też tolower toupper

C4 MALLOC 193

C4 malloc

C41 Deklaracjainclude ltstdlibhgt

void calloc(size_t nmeb size_t size)void malloc(size_t size)void free(void ptr)void realloc(void ptr size_t size)

C42 Argumentynmeb liczba elementoacutew dla ktoacuterych ma być przydzielona pamięć

size rozmiar (w bajtach) pamięci do zarezerwowania bądź rozmiar pojedynczego elementu

ptr wskaźnik zwroacutecony przez poprzednie wywołanie jednej z funkcji lub

C43 OpisFunkcja calloc przydziela pamięć dla nmeb elementoacutew o rozmiarze size każdy i zeruje przydzielonąpamięć

Funkcja malloc przydziela pamięć o wielkości size bajtoacutewFunkcja free zwalnia blok pamięci wskazywany przez ptr wcześniej przydzielony przez jedną z

funkcji malloc calloc lub realloc Jeżeli ptr ma wartość funkcja nie robi nicFunkcja realloc zmienia rozmiar przydzielonego wcześniej bloku pamięci wskazywanego przez ptr

do size bajtoacutew Pierwsze n bajtoacutew bloku nie ulegnie zmianie gdzie n jest minimum z rozmiaru staregobloku i size Jeżeli ptr jest roacutewny zero (tj ) funkcja zachowuje się tak samo jako malloc

C44 Wartość zwracanaJeżeli przydzielanie pamięci się powiodło funkcje calloc malloc i realloc zwracają wskaźnik do nowoprzydzielonego bloku pamięci W przypadku funkcji realloc może to być wartość inna niż ptr

Jeśli jako size nmeb podano zero zwracany jest albo wskaźnik albo prawidłowy wskaźnikktoacutery można podać do funkcji free (zauważmy że jest też prawidłowym argumentem free)

Jeśli działanie funkcji nie powiedzie się zwracany jest i odpowiedni kod błędu jest wpisywanydo zmiennej errno Dzieje się tak zazwyczaj gdy nie ma wystarczająco dużo miejsca w pamięci

C45 Przykładinclude ltstdiohgtinclude ltstdlibhgt

int main(void)

size_t size num = 0float tab tmp

Przydzielenie początkowego bloku pamięci size = 64tab = malloc(size sizeof tab)if (tab)

perror(malloc)return EXIT_FAILURE

194 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Odczyt liczb while (scanf(f amptmp)==1)

Jeżeli zapełniono całą tablicę trzeba ją zwiększyć if (num==size)float ptr = realloc(tab (size = 2) sizeof ptr)if (ptr)

free(tab)perror(realloc)return EXIT_FAILURE

tab = ptr

tab[num++] = tmp

Wypisanie w odwrotnej kolejnosci while (num)

printf(fn tab[--num])

Zwolnienie pamieci i zakonczenie programu free(tab)return EXIT_SUCCESS

C46 Uwagi

Użycie rzutowania przy wywołaniach funkcji malloc realloc oraz calloc w języku C jest zbędne i szko-dliwe W przypadku braku deklaracji tych funkcji (np gdy programista zapomni dodać plik nagłoacutew-kowy stdlibh) kompilator przyjmuje domyślną deklaracje w ktoacuterej funkcja zwraca int Przy brakurzutowania spowoduje to błąd kompilacji (z powodu niemożności skonwertowania liczby na wskaźnik)co pozwoli na szybkie wychwycenie błędu w programie Rzutowanie powoduje że kompilator zostajezmuszony do przeprowadzenia konwersji typoacutew i nie wyświetla żadnych błędoacutew W przypadku językaC++ rzutowanie jest konieczne

Zastosowanie operatora sizeof z wyrażeniem (np sizeof tablica) a nie typem (np sizeof float)ułatwia poacuteźniejszą modyfikację programoacutew Gdyby w pewnym momencie programista zdecydował sięzmienić tablicę z tablicy floatoacutew na tablice doublersquoi musiałby wyszukiwać wszystkie wywołania funkcjimalloc realloc i calloc co nie jest konieczne przy użyciu operatora sizeof z wyrażeniem

Ponieważ dla parametru size roacutewnego zero funkcja może zwroacutecić albo wskaźnik roacuteżny od wartości albo jej roacutewny zwykłe sprawdzanie poprawności wywołania poprzez przyroacutewnanie zwroacuteconejwartości do zera może nie dać prawidłowego wyniku

C47 Zobacz też

Wskaźniki (dokładne omoacutewienie zastosowania)

C5 PRINTF 195

C5 printf

C51 Deklaracjainclude ltstdiohgt

int printf(const char format )int fprintf(FILE stream const char format )int sprintf(char str const char format )int snprintf(char str size_t size const char format )

include ltstdarghgt

int vprintf(const char format va_list ap)int vfprintf(FILE stream const char format va_list ap)int vsprintf(char str const char format va_list ap)int vsnprintf(char str size_t size const char format va_list ap)

C52 OpisFunkcje formatują tekst zgodnie z podanym formatem opisanym poniżej Funkcje printf i vprintf wy-pisują tekst na standardowe wyjście (tj do stdout) fprintf i vfprintf do strumienia podanego jakoargument a sprintf vsprintf snprintf i vsnprintf zapisują go w podanej jako argument tablicy znakoacutew

Funkcje vprintf vfprintf vsprintf i vsnprintf roacuteżnią się od odpowiadających im funkcjom printffprintf sprintf i snprintf tym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

Funkcje snprintf i vsnprintf roacuteżnią się od sprintf i vsprintf tym że nie zapisuje do tablicy nie wię-cej niż size znakoacutew (wliczając kończący znak rsquorsquo) Oznacza to że można je używać bez obawy owystąpienie przepełnienia bufora

C53 Argumentyformat format w jakim zostaną wypisane następne argumenty

stream strumień wyjściowy do ktoacuterego mają być zapisane dane

str tablica znakoacutew do ktoacuterej ma być zapisany sformatowany tekst

size rozmiar tablicy znakoacutew

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

C54 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) ktoacutere są kopiowane bez zmian na wyjścieoraz sekwencji sterujących zaczynających się od symbolu procenta po ktoacuterym następuje

dowolna liczba flag

opcjonalne określenie minimalnej szerokości pola

opcjonalne określenie precyzji

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

196 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Flagi

W sekwencji możliwe są następujące flagi

- (minus) oznacza że pole ma być wyroacutewnane do lewej a nie do prawej

+ (plus) oznacza że dane liczbowe zawsze poprzedzone są znakiem (plusem dla liczb nieujem-nych lub minusem dla ujemnych)

spacja oznacza że liczby nieujemne poprzedzone są dodatkową spacją jeżeli flaga plus i spacjasą użyte jednocześnie to spacja jest ignorowana

(hash) powoduje że wynik jest przedstawiony w alternatywnej postaci

ndash dla formatu o powoduje to zwiększenie precyzji jeżeli jest to konieczne aby na początkuwyniku było zero

ndash dla formatoacutew x i X niezerowa liczba poprzedzona jest ciągiem x lub X

ndash dla formatoacutew a A e E f F g i G wynik zawsze zawiera kropkę nawet jeżeli nie ma za niążadnych cyfr

ndash dla formatoacutew g i G końcowe zera nie są usuwane

(zero) dla formatoacutew d i o u xX aA e E f F g iG do wyroacutewnania pola wykorzystywane sązera zamiast spacji za wyjątkiem wypisywania wartości nieskończoność i NaN Jeżeli obie flagi i mdash są obecne to flaga zero jest ignorowana Dla formatoacutew d i o u x i X jeżeli określona jestprecyzja flaga ta jest ignorowana

Szerokość pola i precyzja

Minimalna szerokość pola oznacza ile najmniej znakoacutew ma zająć dane pole Jeżeli wartość po formato-waniu zajmuje mniej miejsca jest ona wyroacutewnywana spacjami z lewej strony (chyba że podano flagiktoacutere modyfikują to zachowanie) Domyślna wartość tego pola to

Precyzja dla formatoacutew

d i o u x iX określa minimalną liczbę cyfr ktoacutere mają być wyświetlone i ma domyślną wartość

a A e E f i F mdash liczbę cyfr ktoacutere mają być wyświetlone po kropce i ma domyślną wartość

g i G określa liczbę cyfr znaczących i ma domyślną wartość

dla formatu s mdash maksymalną liczbę znakoacutew ktoacutere mają być wypisane

Szerokość pola może być albo dodatnią liczbą zaczynającą się od cyfry roacuteżnej od zera albo gwiazdkąPodobnie precyzja z tą roacuteżnicą że jest jeszcze poprzedzona kropką Gwiazdka oznacza że brany jestkolejny z argumentoacutew ktoacutery musi być typu int Wartość ujemna przy określeniu szerokości jest trak-towana tak jakby podano flagę - (minus)

Rozmiar argumentu

Dla formatoacutew d i i można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu signed char

h mdash oznacza że format odnosi się do argumentu typu short

l (el) mdash oznacza że format odnosi się do argumentu typu long

ll (el el) mdash oznacza że format odnosi się do argumentu typu long long

j mdash oznacza że format odnosi się do argumentu typu intmax t

z mdash oznacza że że format odnosi się do argumentu typu będącego odpowiednikiem typu size tze znakiem

t mdash oznacza że że format odnosi się do argumentu typu ptrdiff t

C5 PRINTF 197

Dla formatoacutew o u x i X można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d ioznaczają one że format odnosi się do argumentu odpowiedniego typu bez znaku

Dla formatu n można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d i oznaczająone że format odnosi się do argumentu będącego wskaźnikiem na dany typ

Dla formatoacutew a A e E f F g iGmożna użyć modyfikatoroacutew rozmiaru L ktoacutery oznacza że formatodnosi się do argumentu typu long double

Dodatkowo modyfikator l (el) dla formatu c oznacza że odnosi się on do argumentu typu wint ta dla formatu s że odnosi się on do argumentu typu wskaźnik na wchar t

Format

Funkcje z rodziny printf obsługują następujące formaty

d i mdash argument typu int jest przedstawiany jako liczba całkowita ze znakiem w postaci [-]ddd

o u x X mdash argument typu unsigned int jest przedstawiany jako nieujemna liczba całkowitazapisana w systemie oktalnym (o) dziesiętnym (u) lub heksadecymalnym (x i X)

f F mdash argument typu double jest przedstawiany w postaci [-]dddddd

e E mdash argument typu double jest reprezentowany w postaci [i]dddde+dd gdzie liczba przedkropką dziesiętną jest roacuteżna od zera jeżeli liczba jest roacuteżna od zera a + oznacza znak wykładnikaFormat E używa wielkiej litery E zamiast małej

g G mdash argument typu double jest reprezentowany w formacie takim jak f lub e (odpowiednio Flub E) zależnie od liczby znaczących cyfr w liczbie oraz określonej precyzji

a A mdash argument typu double przedstawiany jest w formacie [-]xhhhhp+d czyli analogiczniejak dla e i E tyle że liczba zapisana jest w systemie heksadecymalnym

c mdash argument typu int jest konwertowany do unsigned char i wynikowy znak jest wypisywanyJeżeli podanomodyfikator rozmiaru l argument typuwint t konwertowany jest dowielobajtowejsekwencji i wypisywany

s mdash argument powinien być typu wskaźnik na char (lub wchar t) Wszystkie znaki z podanejtablicy aż do i z wyłączeniem znaku null są wypisywane

pmdashargument powinien być typuwskaźnik na void Jest to konwertowany na serię drukowalnychznakoacutew w sposoacuteb zależny od implementacji

n mdash argument powinien być wskaźnikiem na liczbę całkowitą ze znakiem do ktoacuterego zapisanajest liczba zapisanych znakoacutew

W przypadku formatoacutew f F e E gG a iAwartość nieskończoność jest przedstawiana w formacie[-]inf lub [-]infinity zależnie od implementacji Wartość NaN jest przedstawiana w postaci [-]nan lub[i]nan(sekwencja) gdzie sekwencja jest zależna od implementacji W przypadku formatoacutew określo-nych wielką literą roacutewnież wynikowy ciąg znakoacutew jest wypisywany wielką literą

C55 Wartość zwracana

Jeżeli funkcje zakończą się sukcesem zwracają liczbę znakoacutew w tekście (wypisanym na standardowewyjście do podanego strumienia lub tablicy znakoacutew) nie wliczając kończącego rsquorsquo W przeciwnymwypadku zwracana jest liczba ujemna

Wyjątkami są funkcje snprintf i vsnprintf ktoacutere zwracają liczbę znakoacutew ktoacutere zostałyby zapisanedo tablicy znakoacutew gdyby była wystarczająco duża

198 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C56 Przykład użyciainclude ltstdiohgtint main()

int i = 4float f = 31415char s = Monty Pythonprintf(i = inf = 1fnWskaźnik s wskazuje na napis sn i f s)return 0

Wyświetli

i = 4f = 31Wskaźnik s wskazuje na napis Monty Python

Funkcja formatująca ciąg znakoacutew i alokująca odpowiednią ilość pamięci

include ltstdarghgtinclude ltstdlibhgt

char sprintfalloc(const char format ) int retsize_t size = 100char str = malloc(size)if (str)

return 0

for()va_list apchar tmp

va_start(ap format)ret = vsnprintf(str size format ap)va_end(ap)

if (retltsize) break

tmp = realloc(str (size_t)ret + 1)if (tmp) ret = -1break

else str = tmpsize = (size_t)ret + 1

if (retlt0) free(str)str = 0

C6 SCANF 199

else if (size-1gtret) char tmp = realloc(str (size_t)ret + 1)if (tmp) str = tmp

return str

C57 Uwagi

Funkcje snprintf i vsnprintf nie były zdefiniowane w standardzie C Zostały one dodane dopiero wstandardzie C

Biblioteka glibc do wersji włącznie posiadała implementacje funkcji snprintf oraz vsnprintfktoacutere były niezgodne ze standardem gdyż zwracały - w przypadku gdy wynikowy tekst nie mieściłsię w podanej tablicy znakoacutew

C6 scanf

C61 Deklaracja

W pliku nagłoacutewkowym stdioh

int scanf(const char format )int fscanf(FILE stream const char format )int sscanf(const char str const char format )

W pliku nagłoacutewkowym stdargh

int vscanf(const char format va_list ap)int vsscanf(const char str const char format va_list ap)int vfscanf(FILE stream const char format va_list ap)

C62 Opis

Funkcje odczytują dane zgodnie z podanym formatem opisanym niżej Funkcje scanf i vscanf odczytujądane ze standardowego wejścia (tj stdin) fscanf i vfscanf ze strumienia podanego jako argument asscanf i vsscanf z podanego ciągu znakoacutew

Funkcje vscanf vfscanf i vsscanf roacuteżnią się od odpowiadających im funkcjom scanf fscanf i sscanftym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

C63 Argumenty

format format odczytu danych

stream strumień wejściowy z ktoacuterego mają być odczytane dane

str tablica znakoacutew z ktoacuterej mają być odczytane dane

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

200 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C64 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) oraz sekwencji sterujących zaczynającychsię od symbolu procenta po ktoacuterym następuje

opcjonalna gwiazdka

opcjonalne maksymalna szerokość pola

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

Wystąpienie w formacie białego znaku powoduje że funkcje z rodziny scanf będą odczytywać iodrzucać znaki aż do napotkania pierwszego znaku nie będącego białym znakiem

Wszystkie inne znaki (tj nie białe znaki oraz nie sekwencje sterujące) muszą dokładnie pasowaćdo danych wejściowych

Wszystkie białe znaki z wejścia są ignorowane chyba że sekwencja sterująca określa format [ c lubn

Jeżeli w sekwencji sterującej występuje gwiazdka to dane z wejścia zostaną pobrane zgodnie zformatem ale wynik konwersji nie zostanie nigdzie zapisany W ten sposoacuteb można pomijać częśćdanych

Maksymalna szerokość pola przyjmuje postać dodatniej liczby całkowitej zaczynającej się od cyfryroacuteżnej od zera Określa ona ile maksymalnie znakoacutew dany format może odczytać Jest to szczegoacutelnieprzydatne przy odczytywaniu ciągu znakoacutew gdyż dzięki temu można podać wielkość tablicy (minusjeden) i tym samym uniknąć błędoacutew przepełnienia bufora

Rozmiar argumentu

Dla formatoacutew d i o u x i n można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu wskaźnik na signed char lub unsignedchar

h mdash oznacza że format odnosi się do argumentu typu wskaźnik na short lub wskaźnik na unsi-gned short

l (el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long lub wskaźnik naunsigned long

ll (el el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long long lub wskaźnikna unsigned long long

j mdash oznacza że format odnosi się do argumentu typu wskaźnik na intmax t lub wskaźnik nauintmax t

z mdash oznacza że że format odnosi się do argumentu typu wskaźnik na size t lub odpowiedni typze znakiem

t mdash oznacza że że format odnosi się do argumentu typu wskaźnik na ptrdiff t lub odpowiednityp bez znaku

Dla formatoacutew a e f i g można użyć modyfikatoroacutew rozmiaru

l ktoacutery oznacza że format odnosi się do argumenty typu wskaźnik na double lub

L ktoacutery oznacza że format odnosi się do argumentu typu wskaźnik na long double

Dla formatoacutew c s i [ modyfikator l oznacza że format odnosi się do argumentu typu wskaźnik nawchar t

C6 SCANF 201

Format

Funkcje z rodziny scanf obsługują następujące formaty

d i odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przywywołaniufunkcji strtol z argumentem base roacutewnym odpowiednio dla d lub dla i argument powinienbyć wskaźnikiem na int

o u x odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przy wy-wołaniu funkcji strtoul z argumentem base roacutewnym odpowiednio dla o dla u lub dla xargument powinien być wskaźnikiem na unsigned int

a e f g odczytuje liczbę rzeczywistą nieskończoność lub NaN ktoacuterych format jest taki sam jakoczekiwany przy wywołaniu funkcji strtod argument powinien być wskaźnikiem na float

c odczytuje dokładnie tyle znakoacutew ile określono w maksymalnym rozmiarze pola (domyślnie )argument powinien być wskaźnikiem na char

s odczytuje sekwencje znakoacutew nie będących białymi znakami argument powinien być wskaźni-kiem na char

[ odczytuje niepusty ciąg znakoacutew z ktoacuterych każdymusi należeć do określonego zbioru argumentpowinien być wskaźnikiem na char

p odczytuje sekwencje znakoacutew zależną od implementacji odpowiadającą ciągowi wypisywa-nemu przez funkcję printf gdy podano sekwencję p argument powinien być typu wskaźnikna wskaźnik na void

n nie odczytuje żadnych znakoacutew ale zamiast tego zapisuje do podanej zmiennej liczbę odczyta-nych do tej pory znakoacutew argument powinien być typu wskaźnik na int

Słoacutewko więcej o formacie [ Po otwierającym nawiasie następuje ciąg określający znaki jakie mogąwystępować w odczytanym napisie i kończy się on nawiasem zamykającym tj ] Znaki pomiędzynawiasami (tzw scanlist) określają możliwe znaki chyba że pierwszym znakiem jest ˆ mdash woacutewczasw odczytanym ciągu znakoacutew mogą występować znaki nie występujące w scanlist Jeżeli sekwencjazaczyna się od [] lub [ˆ] to ten pierwszy nawias zamykający nie jest traktowany jako koniec sekwencjitylko jak zwykły znak Jeżeli wewnątrz sekwencji występuje znak - (minus) ktoacutery nie jest pierwszymlub drugim jeżeli pierwszym jest ˆ ani ostatnim znakiem zachowanie jest zależne od implementacji

Formaty A E F G i X są roacutewnież dopuszczalne i mają takie same działanie jak a e f g i x

C65 Wartość zwracanaFunkcja zwraca EOF jeżeli nastąpi koniec danych lub błąd odczytu zanim jakiekolwiek konwersje zo-staną dokonane lub liczbę poprawnie wczytanych poacutel (ktoacutera może być roacutewna zero)

202 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Dodatek D

Składnia

D1 Symbole i słowa kluczoweJęzyk C definiuje pewną ilość słoacutew za pomocą ktoacuterych tworzy się np pętle itp Są to tzw słowakluczowe tzn nie można użyć ich jako nazwy zmiennej czy też stałej (o nich poniżej) Oto lista słoacutewkluczowych języka C (według norm ANSI C z roku oraz ISO C z roku )

203

204 DODATEK D SKŁADNIA

Tablica D1 Symbole i słowa kluczoweSłowo Opis w tym podręcznikuauto Zmiennebreak Instrukcje sterującecase Instrukcje sterującear Zmienneconst Zmienne

continue Instrukcje sterującedefault Instrukcje sterujące

do Instrukcje sterującedouble Zmienneelse Instrukcje sterująceenum Typy złożoneextern Bibliotekifloat Zmiennefor Instrukcje sterującegoto Instrukcje sterująceif Instrukcje sterująceint Zmiennelong Zmienne

register Zmiennereturn Procedury i funkcjeshort Zmiennesigned Zmiennesizeof Zmiennestatic Biblioteki Zmiennestruct Typy złożoneswit Instrukcje sterującetypedef Typy złożoneunion Typy złożone

unsigned Zmiennevoid Wskaźniki

volatile Zmiennewhile Instrukcje sterujące

D2 POLSKIE ZNAKI 205

Specyfikacja ISO C z roku dodaje następujące słowa

Bool

Complex

Imaginary

inline

restrict

D2 Polskie znakiPisząc program możemy stosować polskie litery (tj ldquoąćęłńoacuteśźżrdquo) tylko w

komentarzach

ciągach znakoacutew (łańcuchach)

Niedopuszczalne jest stosowanie polskich znakoacutew w innych miejscach

D3 Operatory

D31 Operatory arytmetyczneSą to operatory wykonujące znane wszystkim dodawanie odejmowanie itp

operator znaczenie+ dodawanie- odejmowanie mnożenie dzielenie dzielenie modulo mdash daje w wyniku samą resztę z dzielenia= operator przypisania mdash wykonuje działanie po prawej stronie i wynik

przypisuje obiektowi po lewej

D32 Operatory logiczneSłużą poroacutewnaniu zawartości dwoacutech zmiennych według określonych kryterioacutew

Operator Rodzaj poroacutewnania== czy roacutewnegt większygt= większy bądź roacutewnylt mniejszylt= mniejszy bądź roacutewny= czy roacuteżny(nieroacutewny)

Są jeszcze operatory służące do grupowania poroacutewnań (patrz też logika w Wikipedii)

|| lub(OR)ampamp ioraz(AND) negacja(NOT)

206 DODATEK D SKŁADNIA

D33 Operatory binarne

Są to operatory ktoacutere działają na bitach

operator funkcja przykład| suma bitowa(OR) 5 | 2 da w wyniku 7 ( 00000101 OR 00000010 =

00000111)amp iloczyn bitowy 7 amp 2 da w wyniku 2 ( 00000111 AND 00000010

= 00000010)~ negacja bitowa 2 da wwyniku 253 (NOT 00000010 = 11111101

)gtgt przesunięcie bitoacutew o X w prawo 7 gtgt 2 da w wyniku 1 ( 00000111 gtgt 2 =

00000001)ltlt przesunięcie bitoacutew o X w lewo 7 ltlt 2 da w wyniku 28 ( 00000111 ltlt 2 =

00011100)^ alternatywa wyłączna 7 ˆ 2 da w wyniku 5 ( 00000111 ˆ 00000010 =

00000101)

D34 Operatory inkrementacjidekrementacji

Służą do dodawaniaodejmowania od liczby wartości jeden

Przykłady

Operacja Opis operacji Wartość wyrażeniax++ zwiększy wartość w x o jeden wartość zmiennej x przed zmianą++x zwiększy wartość w x o jeden wartość zmiennej x powiększona o jedenxndash zmniejszy wartość w x o jeden wartość zmiennej x przed zmianąndashx zmniejszy wartość w x o jeden wartość zmiennej x pomniejszona o jeden

Parę przykładoacutew dla zrozumienia

int a=7if ((a++)==7) najpierw poroacutewnuje potem dodaje

printf (dna) wypisze 8 if ((++a)==9) najpierw dodaje potem poroacutewnuje

printf (dn a) wypisze 9

Analogicznie ma się sytuacja z operatorami dekrementacji

D4 TYPY DANYCH 207

D35 PozostałeOperacja Opis operacji Wartość wyrażenia

x operator wyłuskania dla wskaźnika wartość trzymana w pamięci pod adre-sem przechowywanym we wskaźniku

ampx operator pobrania adresu zwraca adres zmiennejx[a] operator wybrania elementu tablicy zwraca element tablicy o indeksie a

(numerowanym od zera)xa operator wyboru składnika a ze zmien-

nej xwybiera składnik ze struktury lub unii

x-gta operator wyboru składnika a przezwskaźnik do zmiennej x

wybiera składnik ze struktury gdy uży-wamy wskaźnika do struktury zamiastzwykłej zmiennej

sizeof (typ) operator pobrania rozmiaru typu zwraca rozmiar typu w bajtachsizeof wyrażenie operator pobrania rozmiaru typu zwraca rozmiar typu rezultatu wyraże-

nia

D36 Operator ternarnyIstnieje jeden operator przyjmujący trzy argumenty mdash jest to operator wyrażenia warunkowego a b c Zwraca on b gdy a jest prawdą lub c w przeciwnym wypadku

D4 Typy dany

Tablica D Typy danych według roacuteżnych specyfikacji języka C

Typ Opis Inne nazwyTypy dany wg norm C i C

ar Służy głoacutewnie do przechowywania znakoacutew Od kom-pilatora zależy czy jest to liczba ze znakiem czy bez wwiększości kompilatoroacutew jest liczbą ze znakiem

signed ar Typ char ze znakiemunsigned ar Typ char bez znakushort Występuje gdy docelowa maszyna wyszczegoacutelnia

kroacutetki typ danych całkowitych w przeciwnym wy-padku jest tożsamy z typem int Często ma rozmiarjednego słowa maszynowego

short int signed shortsigned short int

unsigned short Liczba typu short bez znaku Podobnie jak short uży-wana do zredukowania zużycia pamięci przez program

unsigned short int

int Liczba całkowita odpowiadająca podstawowemu roz-miarowi liczby całkowitej w danym komputerze Pod-stawowy typ dla liczb całkowitych

signed int signed

unsigned Liczba całkowita bez znaku unsigned intlong Długa liczba całkowita long int signed long

signed long intunsigned long Długa liczba całkowita bez znaku unsigned long intfloat Podstawowy typ do przechowywania liczb zmienno-

przecinkowych W nowszym standardzie zgodny jest znormą IEEE Nie można stosować go z modyfika-torem signed ani unsigned

double Liczba zmiennoprzecinkowa podwoacutejnej precyzji Po-dobnie jak float nie łączy się z modyfikatorem signedani unsigned

208 DODATEK D SKŁADNIA

long double Największa możliwa dokładność liczb zmiennoprzecin-kowych Nie łączy się z modyfikatorem signed aniunsigned

Typy dany według normy CBool Przechowuje wartości lub long long Nowy typ umożliwiający obliczeniach na bardzo du-

żych liczbach całkowitych bez użycia typu floatlong long int signedlong long signed longlong int

unsigned long long Długie liczby całkowite bez znaku unsigned long long intfloat Complex Słuzy do przechowywania liczb zespolonychdouble Complex Słuzy do przechowywania liczb zespolonychlong double Complex Słuzy do przechowywania liczb zespolonych

Typy dany definiowane przez użytkownikastruct Więcej o kompilowaniuunion Rozmiar typu jest taki jak rozmiar największego polatypedef Nowo zdefiniowany typ przyjmuje taki sam rozmiar

jak typ macierzystyenum Zwykle elementy mają taką samą długość jak typ int

Zależności rozmiaru typoacutew danych są następujące

sizeof(cokolwiek) = sizeof(signed cokolwiek) = sizeof(unsigned cokolwiek)

= sizeof(ar) le sizeof(short) le sizeof(int) le sizeof(long) le sizeof(long long)

sizeof(float) le sizeof(double) le sizeof(long double)

sizeof(cokolwiek Complex) = sizeof(cokolwiek)

sizeof(void ) = sizeof(ar ) ge sizeof(cokolwiek )

sizeof(cokolwiek ) = sizeof(signed cokolwiek ) = sizeof(unsigned cokolwiek )

sizeof(cokolwiek ) = sizeof(const cokolwiek )

Dodatkowo jeżeli przez V(typ) oznaczymy liczbę bitoacutew wykorzystywanych w typie to zachodzi

le V(ar) = V(signed ar) = V(unsigned ar)

le V(short) = V(unsigned short)

le V(int) = V(unsigned int)

le V(long) = V(unsigned long)

le V(long long) = V(unsigned long long)

V(ar) le V(short) le V(int) le V(long) le V(long long)

Dodatek E

Przykłady z komentarzem

E01 Liczby losowePoniższy program generuje wiersz po wierszu macierz o określonych przez użytkownika wymiarachzawierającą losowo wybrane liczby Każdy wygenerowany wiersz macierzy zapisywany jest w plikutekstowym o wprowadzonej przez użytkownika nazwie W pierwszym wierszu pliku wynikowego za-pisano wymiary utworzonej macierzy Program napisany i skompilowany został w środowisku GNU-Linux

include ltstdiohgtinclude ltstdlibhgt dla funkcji rand() oraz srand() include lttimehgt dla funkcji [time()

main()

int i j n mfloat reFILE fpchar fileName[128]

printf(Wprowadz nazwe pliku wynikowegon)scanf(sampfileName)

printf(Wprowadz po sobie liczbe wierszy i kolumn macierzy oddzielone spacjąn)scanf(d d ampn ampm)

jeżeli wystąpił błąd w otwieraniu pliku i go nie otwartowoacutewczas funkcja fclose(fp) wywołana na końcu programu zgłosi błądwykonania i wysypie nam program z działania stąd musimy umieścićwarunek ktoacutery w kontrolowany sposoacuteb zatrzyma program (funkcja exit)

if ( (fp = fopen(fileName w)) == NULL )

puts(Otwarcie pliku nie jest mozliwe)exit jeśli w procedurze glownej

to piszemy bez nawiasow

else puts(Plik otwarty prawidłowo)

209

210 DODATEK E PRZYKŁADY Z KOMENTARZEM

fprintf(fp d dn n m) w pierwszym wierszu umieszczono wymiary macierzy

srand( (unsigned int) time(0) )for (i=1 ilt=n ++i)

for (j=1 jlt=m ++j)re = ((rand() 200)-100) 100fprintf(fp1f re )if (j=m) fprintf(fp )

fprintf(fpn)fclose(fp)return 0

E02 Zamiana liczb dziesiętny na liczby w systemie dwoacutejkowym

Zajmijmy się teraz innym zagadnieniem Wiemy że komputer zapisuje wszystkie liczby w postacibinarnej (czyli za pomocą jedynek i zer) Sproacutebujmy zatem zamienić liczbę zapisaną w ldquonaszymrdquo dzie-siątkowym systemie na zapis binarny Uwaga Program działa jedynie dla liczb od do maksymalnejwartości ktoacuterą może przyjąć typ unsigned short int w twoim kompilatorze

include ltstdiohgtinclude ltlimitshgt

void dectobin (unsigned short a)

int licznik

CHAR_BIT to liczba bitoacutew w bajcie licznik = CHAR_BIT sizeof(a)while (--licznik gt= 0)

putchar(((a gtgt licznik) amp 1)) 1 0)

int main ()

unsigned short a

printf (Podaj liczbę od 0 do hd USHRT_MAX)scanf (hd ampa)printf (hd(10) = a)dectobin(a)printf ((2)n)

return 0

211

E03 Zalążek przeglądarki

Zajmiemy się tym razem inną kwestią a mianowicie programowaniem sieci Jest to zagadnienie bar-dzo ostatnio popularne Nasz program będzie miał za zadanie połączyć się z serwerem ktoacuterego adresużytkownik będzie podawał jako pierwszy parametr programu wysłać zapytanie HTTP i odebrać treśćktoacuterą wyśle do nas serwer Zacznijmy może od tego że obsługa sieci jest niemal identyczna w roacuteżnychsystemach operacyjnych Na przykład między systemami z rodziny Unix oraz Windowsem roacuteżnica po-lega tylko na dołączeniu innych plikoacutew nagłoacutewkowych (dla Windowsa mdash winsockh) Przeanalizujmyzatem poniższy kod

include ltstdiohgtinclude ltstdlibhgtinclude ltstringhgtinclude ltunistdhgtinclude ltarpainethgtinclude ltsystypeshgtinclude ltnetinetinhgtinclude ltsyssockethgt

define MAXRCVLEN 512define PORTNUM 80

char query = GET HTTP11nn

int main(int argc char argv[])

char buffer[MAXRCVLEN+1]int len mysocketstruct sockaddr_in destchar host_ip = NULLif (argc = 2)

printf (Podaj adres serweran)exit (1)

host_ip = argv[1]mysocket = socket(AF_INET SOCK_STREAM 0)

destsin_family = AF_INETdestsin_addrs_addr = inet_addr(host_ip) ustawiamy adres hosta destsin_port = htons (PORTNUM) numer portu przechowuje dwubajtowa zmienna -

musimy ustalić porządek sieciowy - Big Endian memset(amp(destsin_zero) 0 8) zerowanie reszty struktury

connect(mysocket (struct sockaddr )ampdestsizeof(struct sockaddr)) łączymy się z hostem write (mysocket query strlen(query)) wysyłamy zapytanie len=read(mysocket buffer MAXRCVLEN) i pobieramy odpowiedź

buffer[len]=0

printf(Rcvd sbuffer)close(mysocket) zamykamy gniazdo return EXIT_SUCCESS

212 DODATEK E PRZYKŁADY Z KOMENTARZEM

Powyższy przykład może być odrobinę niezrozumiały dlatego przyda się kilka słoacutew wyjaśnieniaPliki nagłoacutewkowe ktoacutere dołączamy zawierają deklarację nowych dla Ciebie funkcji mdash socket() con-nect() write() oraz read() Oproacutecz tego spotkałeś się z nową strukturą mdash sockaddr in Wszystkie teobiekty są niezbędne do stworzenia połączenia

Dodatek F

Informacje o pliku i historia

F1 HistoriaTa książka została stworzona na polskojęzycznej wersji projektu Wikibooks przez autoroacutew wymie-nionych poniżej w sekcji Autorzy Najnowsza wersja podręcznika jest dostępna pod adresem httpplwikibooksorgwikiC

F2 Informacje o pliku i historia został utworzony przez Derbetha dnia listopada na podstawie wersji z listopada podręcznika na Wikibooks Wykorzystany został poprawiony program WikiLaTeX autorstwa użyt-kownika angielskichWikibooks Hagindaza Wynikowy kod po ręcznych poprawkach został przekształ-cony w książkę za pomocą systemu składu XeLaTeX Wykorzystano wolną dostępną na licencjach i czcionkę Linux Libertine oraz wolną czcionkę DejaVu Sans Mono

Najnowsza wersja tego -u jest postępna pod adresem httpplwikibooksorgwikiImageCpdf

F3 AutorzyAdam majewski Adiblol Akira Albmont Ananas Arfrever BartekChom Bercik Bla Bociex CathyRichards Cnr CzarnyInaczej CzarnyZajaczek DaniXTeam Derbeth Equadus Faw GDR GangGk Gynvael Incuś Karol Ossowski Kazet Kj Lethern MTM Marcin MastiBot MeaglinMerdis Michael Migol Mina MonteChristof Mt Myki Mythov Narf Noisy Norill PawelkgPawlosck Peter de Sowaro Piotr Pkierski Ponton Przykuta RedRad Sasek Sblive Silbarad T zielWarszk Webprog Wentuq ZiomekPL Zjem ci chleb i anonimowi autorzy

F4 GrafikiAutorzy i licencje grafik

grafika na okładce Saint-Elme Gautier rycina z książki Le Corset agrave travers les acircges Paryż źroacutedło Wikimedia Commons public domain

logoWikibooks zastrzeżony znak towarowycopy ampAll rights reserved Wikimedia FoundationInc

grafika a (strona ) autor Claudio Rocchini źroacutedło Wikimedia Commons licencja

grafika b (strona ) autor Adam majewski źroacutedło Wikimedia Commons licencja CreativeCommons Aribution Unported

213

214 DODATEK F INFORMACJE O PLIKU

grafia (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Daniel B źroacutedo Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Derrick Coetzee źroacutedło Wikimedia Commons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

Indeks

adres alternatywa

biblioteka standardowa big endian blok

Cjęzyk

dekrementacja dynamiczna alokacja pamięci

enum

funkcja definicja deklaracja rekurencyjna

inkrementacja

komentarz kompilacja

warunkowa kompilator

lista używanie

koniunkcja konwersja

libc lile endian Porownaj big endian

main makefile

napis poroacutewnywanie

negacja

operatordekrementacji inkrementacji modulo

pobrania adresu sizeof wyrażenia warunkowego wyłuskania

plikczytanie i pisanie nagłowkowy

porządek bajtoacutew prawda i fałsz preprocesor procedury prototyp funkcji przekazywanie argumentoacutew do funkcji

przez wartość przez wskaźnik

przepełnienie bufora przesunięcie bitowe

rzutowanie

sizeof stała struktura słowa kluczowe

tablica wielowymiarowa znakoacutew

typ definiowanie wyliczeniowy

unia

Valgrind void

jako typ zwracany na liście argumentoacutew void

volatile

wejściewyjście wskaźnik wyciek pamięci

215

216 INDEKS

wyroacutewnywanie

zmienna globalna lokalna statyczna

znaki specjalne

  • O podręczniku
    • O czym moacutewi ten podręcznik
    • Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika
    • Konwencje przyjęte w tym podręczniku
    • Czy mogę pomoacutec
    • Autorzy
    • Źroacutedła
      • O języku C
        • Historia C
        • Zastosowania języka C
        • Przyszłość C
          • Czego potrzebujesz
            • Czego potrzebujesz
            • Zintegrowane Środowiska Programistyczne
            • Dodatkowe narzędzia
              • Używanie kompilatora
                • GCC
                • Borland
                • Czytanie komunikatoacutew o błędach
                  • Pierwszy program
                    • Twoacutej pierwszy program
                    • Rozwiązywanie problemoacutew
                      • Podstawy
                        • Kompilacja Jak działa C
                        • Co może C
                        • Struktura blokowa
                        • Zasięg
                        • Funkcje
                        • Biblioteki standardowe
                        • Komentarze i styl
                        • Preprocesor
                        • Nazwy zmiennych stałych i funkcji
                          • Zmienne
                            • Czym są zmienne
                            • Typy zmiennych
                            • Specyfikatory
                            • Modyfikatory
                            • Uwagi
                              • Operatory
                                • Przypisanie
                                • Rzutowanie
                                • Operatory arytmetyczne
                                • Operacje bitowe
                                • Poroacutewnanie
                                • Operatory logiczne
                                • Operator wyrażenia warunkowego
                                • Operator przecinek
                                • Operator sizeof
                                • Inne operatory
                                • Priorytety i kolejność obliczeń
                                • Kolejność wyliczania argumentoacutew operatora
                                • Uwagi
                                • Zobacz też
                                  • Instrukcje sterujące
                                    • Instrukcje warunkowe
                                    • Pętle
                                    • Instrukcja goto
                                    • Natychmiastowe kończenie programu --- funkcja exit
                                    • Uwagi
                                      • Podstawowe procedury wejścia i wyjścia
                                        • Wejściewyjście
                                        • Funkcje wyjścia
                                        • Funkcja puts
                                        • Funkcja fputs
                                        • Funkcje wejścia
                                          • Funkcje
                                            • Tworzenie funkcji
                                            • Wywoływanie
                                            • Zwracanie wartości
                                            • Zwracana wartość
                                            • Funkcja main()
                                            • Dalsze informacje
                                            • Zobacz też
                                              • Preprocesor
                                                • Wstęp
                                                • Dyrektywy preprocesora
                                                • Predefiniowane makra
                                                  • Biblioteka standardowa
                                                    • Czym jest biblioteka
                                                    • Po co nam biblioteka standardowa
                                                    • Gdzie są funkcje z biblioteki standardowej
                                                    • Opis funkcji biblioteki standardowej
                                                    • Uwagi
                                                      • Czytanie i pisanie do plikoacutew
                                                        • Pojęcie pliku
                                                        • Identyfikacja pliku
                                                        • Podstawowa obsługa plikoacutew
                                                        • Rozmiar pliku
                                                        • Przykład --- pliki graficzny
                                                        • Co z katalogami
                                                          • Ćwiczenia dla początkujących
                                                            • Ćwiczenia
                                                              • Tablice
                                                                • Wstęp
                                                                • Odczytzapis wartości do tablicy
                                                                • Tablice znakoacutew
                                                                • Tablice wielowymiarowe
                                                                • Ograniczenia tablic
                                                                • Ciekawostki
                                                                  • Wskaźniki
                                                                    • Co to jest wskaźnik
                                                                    • Operowanie na wskaźnikach
                                                                    • Arytmetyka wskaźnikoacutew
                                                                    • Tablice a wskaźniki
                                                                    • Gdy argument jest wskaźnikiem
                                                                    • Pułapki wskaźnikoacutew
                                                                    • Na co wskazuje NULL
                                                                    • Stałe wskaźniki
                                                                    • Dynamiczna alokacja pamięci
                                                                    • Wskaźniki na funkcje
                                                                    • Możliwe deklaracje wskaźnikoacutew
                                                                    • Popularne błędy
                                                                    • Ciekawostki
                                                                      • Napisy
                                                                        • Łańcuchy znakoacutew w języku C
                                                                        • Operacje na łańcuchach
                                                                        • Bezpieczeństwo kodu a łańcuchy
                                                                        • Konwersje
                                                                        • Operacje na znakach
                                                                        • Częste błędy
                                                                        • Unicode
                                                                          • Typy złożone
                                                                            • typedef
                                                                            • Typ wyliczeniowy
                                                                            • Struktury
                                                                            • Unie
                                                                            • Inicjalizacja struktur i unii
                                                                            • Wspoacutelne własności typoacutew wyliczeniowych unii i struktur
                                                                            • Studium przypadku --- implementacja listy wskaźnikowej
                                                                              • Biblioteki
                                                                                • Czym jest biblioteka
                                                                                • Jak zbudowana jest biblioteka
                                                                                  • Więcej o kompilowaniu
                                                                                    • Ciekawe opcje kompilatora GCC
                                                                                    • Program make
                                                                                    • Optymalizacje
                                                                                    • Kompilacja krzyżowa
                                                                                    • Inne narzędzia
                                                                                      • Zaawansowane operacje matematyczne
                                                                                        • Biblioteka matematyczna
                                                                                        • Prezentacja liczb rzeczywistych w pamięci komputera
                                                                                        • Liczby zespolone
                                                                                          • Powszechne praktyki
                                                                                            • Konstruktory i destruktory
                                                                                            • Zerowanie zwolnionych wskaźnikoacutew
                                                                                            • Konwencje pisania makr
                                                                                            • Jak dostać się do konkretnego bitu
                                                                                            • Skroacutety notacji
                                                                                              • Przenośność programoacutew
                                                                                                • Niezdefiniowane zachowanie i zachowanie zależne od implementacji
                                                                                                • Rozmiar zmiennych
                                                                                                • Porządek bajtoacutew i bitoacutew
                                                                                                • Biblioteczne problemy
                                                                                                • Kompilacja warunkowa
                                                                                                  • Łączenie z innymi językami
                                                                                                    • Język C i Asembler
                                                                                                    • C++
                                                                                                      • Indeks alfabetyczny
                                                                                                      • Indeks tematyczny
                                                                                                        • asserth
                                                                                                        • ctypeh
                                                                                                        • errnoh
                                                                                                        • floath
                                                                                                        • limitsh
                                                                                                        • localeh
                                                                                                        • mathh
                                                                                                        • setjmph
                                                                                                        • signalh
                                                                                                        • stdargh
                                                                                                        • stddefh
                                                                                                        • stdioh
                                                                                                        • stdlibh
                                                                                                        • stringh
                                                                                                        • timeh
                                                                                                          • Wybrane funkcje biblioteki standardowej
                                                                                                            • assert
                                                                                                            • atoi
                                                                                                            • isalnum
                                                                                                            • malloc
                                                                                                            • printf
                                                                                                            • scanf
                                                                                                              • Składnia
                                                                                                                • Symbole i słowa kluczowe
                                                                                                                • Polskie znaki
                                                                                                                • Operatory
                                                                                                                • Typy danych
                                                                                                                  • Przykłady z komentarzem
                                                                                                                  • Informacje o pliku
                                                                                                                    • Historia
                                                                                                                    • Informacje o pliku PDF i historia
                                                                                                                    • Autorzy
                                                                                                                    • Grafiki
                                                                                                                      • Skorowidz
Page 3: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ

Spis tresci

O podręczniku O czym moacutewi ten podręcznik 11 Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika 11 Konwencje przyjęte w tym podręczniku 11 Czy mogę pomoacutec 12 Autorzy 12 Źroacutedła 12

O języku C Historia C 13 Zastosowania języka C 15 Przyszłość C 15

Czego potrzebujesz Czego potrzebujesz 17 Zintegrowane Środowiska Programistyczne 18 Dodatkowe narzędzia 18

Używanie kompilatora GCC 19 Borland 20 Czytanie komunikatoacutew o błędach 20

Pierwszy program Twoacutej pierwszy program 23 Rozwiązywanie problemoacutew 24

Podstawy Kompilacja Jak działa C 27 Co może C 27 Struktura blokowa 28 Zasięg 28 Funkcje 29 Biblioteki standardowe 29 Komentarze i styl 30 Preprocesor 32 Nazwy zmiennych stałych i funkcji 32

3

Zmienne Czym są zmienne 33 Typy zmiennych 36 Specyfikatory 39 Modyfikatory 40 Uwagi 41

Operatory Przypisanie 43 Rzutowanie 44 Operatory arytmetyczne 45 Operacje bitowe 46 Poroacutewnanie 48 Operatory logiczne 50 Operator wyrażenia warunkowego 51 Operator przecinek 51 Operator sizeof 51 Inne operatory 52 Priorytety i kolejność obliczeń 52 Kolejność wyliczania argumentoacutew operatora 53 Uwagi 54 Zobacz też 55

Instrukcje sterujące Instrukcje warunkowe 57 Pętle 60 Instrukcja goto 65 Natychmiastowe kończenie programu mdash funkcja exit 65 Uwagi 66

Podstawowe procedury wejścia i wyjścia Wejściewyjście 67 Funkcje wyjścia 68 Funkcja puts 69 Funkcja fputs 70 Funkcje wejścia 71

Funkcje Tworzenie funkcji 78 Wywoływanie 79 Zwracanie wartości 80 Zwracana wartość 81 Funkcja main() 81 Dalsze informacje 83 Zobacz też 87

Preprocesor Wstęp 89 Dyrektywy preprocesora 89 Predefiniowane makra 95

4

Biblioteka standardowa Czym jest biblioteka 97 Po co nam biblioteka standardowa 97 Gdzie są funkcje z biblioteki standardowej 97 Opis funkcji biblioteki standardowej 98 Uwagi 98

Czytanie i pisanie do plikoacutew Pojęcie pliku 99 Identyfikacja pliku 99 Podstawowa obsługa plikoacutew 99 Rozmiar pliku 102 Przykład mdash pliki graficzny 103 Co z katalogami 104

Ćwiczenia dla początkujący Ćwiczenia 105

Tablice Wstęp 107 Odczytzapis wartości do tablicy 109 Tablice znakoacutew 109 Tablice wielowymiarowe 110 Ograniczenia tablic 110 Ciekawostki 111

Wskaźniki Co to jest wskaźnik 113 Operowanie na wskaźnikach 114 Arytmetyka wskaźnikoacutew 117 Tablice a wskaźniki 118 Gdy argument jest wskaźnikiem 119 Pułapki wskaźnikoacutew 120 Na co wskazuje 120 Stałe wskaźniki 121 Dynamiczna alokacja pamięci 122 Wskaźniki na funkcje 125 Możliwe deklaracje wskaźnikoacutew 127 Popularne błędy 127 Ciekawostki 128

Napisy Łańcuchy znakoacutew w języku C 129 Operacje na łańcuchach 132 Bezpieczeństwo kodu a łańcuchy 134 Konwersje 137 Operacje na znakach 137 Częste błędy 137 Unicode 138

5

Typy złożone typedef 141 Typ wyliczeniowy 141 Struktury 142 Unie 142 Inicjalizacja struktur i unii 144 Wspoacutelne własności typoacutew wyliczeniowych unii i struktur 144 Studium przypadku mdash implementacja listy wskaźnikowej 146

Biblioteki Czym jest biblioteka 151 Jak zbudowana jest biblioteka 151

Więcej o kompilowaniu Ciekawe opcje kompilatora 155 Program make 155 Optymalizacje 157 Kompilacja krzyżowa 159 Inne narzędzia 159

Zaawansowane operacje matematyczne Biblioteka matematyczna 161 Prezentacja liczb rzeczywistych w pamięci komputera 162 Liczby zespolone 163

Powszene praktyki Konstruktory i destruktory 165 Zerowanie zwolnionych wskaźnikoacutew 166 Konwencje pisania makr 166 Jak dostać się do konkretnego bitu 167 Skroacutety notacji 168

Przenośność programoacutew Niezdefiniowane zachowanie i zachowanie zależne od implementacji 171 Rozmiar zmiennych 172 Porządek bajtoacutew i bitoacutew 172 Biblioteczne problemy 175 Kompilacja warunkowa 175

Łączenie z innymi językami Język C i Asembler 177 C++ 180

A Indeks alfabetyczny

B Indeks tematyczny B asserth 183B ctypeh 183B errnoh 183B floath 183B limitsh 183

6

B localeh 184B mathh 184B setjmph 185B signalh 185B stdargh 185B stddefh 185B stdioh 185B stdlibh 186B stringh 186B timeh 186

C Wybrane funkcje biblioteki standardowej C assert 189C atoi 190C isalnum 191C malloc 193C printf 195C scanf 199

D Składnia D Symbole i słowa kluczowe 203D Polskie znaki 205D Operatory 205D Typy danych 207

E Przykłady z komentarzem

F Informacje o pliku F Historia 213F Informacje o pliku i historia 213F Autorzy 213F Grafiki 213

Skorowidz

7

8

Spis tablic

Priorytety operatoroacutew 53

D Symbole i słowa kluczowe 204D Typy danych według roacuteżnych specyfikacji języka C 207

9

Rozdział 1

O podręczniku

11 O czym moacutewi ten podręcznikNiniejszy podręcznik stanowi przewodnik dla początkujących programistoacutew po języku pro-gramowania C

12 Co trzeba wiedzieć żeby skorzystać z niniejszego pod-ręcznika

Ten podręcznik ma nauczyć programowania w C od podstaw do poziomu zaawansowanegoDo zrozumienia rozdziału dla początkujących wymagana jest jedynie znajomość podstawo-wych pojęć z zakresu algebry oraz terminoacutew komputerowych Doświadczenie w programo-waniu w innych językach bardzo pomaga ale nie jest konieczne

13 Konwencje przyjęte w tym podręcznikuInformacje ważne oznaczamy w następujący sposoacuteb

Ważna informacja

Dodatkowe informacje ktoacutere odrobinę wykraczają poza zakres podręcznika a także wy-jaśniają kwestie niezwiązane bezpośrednio z językiem C oznaczamy tak

Wyjaśnienie

Ponadto kod w języku C będzie prezentowany w następujący sposoacuteb

include ltstdiohgt

int main (int argc char argv[])

return 0

11

12 ROZDZIAŁ 1 O PODRĘCZNIKU

Innego rodzaju przykłady dialog użytkownika z konsolą i programem wejście wyjścieprogramu informacje teoretyczne będą wyglądały tak

typ zmienna = wartość

14 Czy mogę pomoacutecOczywiście że możesz Mało tego będziemy zadowoleni z każdej pomocy ndash możesz pisaćrozdziały lub tłumaczyć je z angielskiej wersji tego podręcznika Nie musisz pytać się nikogoo zgodę mdash jeśli chcesz możesz zacząć już teraz Prosimy jedynie o zapoznanie się ze stylempodręcznika użytymi w nim szablonami i zachowanie układu rozdziałoacutew Propozycje zmianyspisu treści należy zgłaszać na stronie dyskusji

Jeśli znalazłeś jakiś błąd a nie umiesz go poprawić koniecznie powiadom o tym fakcieautoroacutew tego podręcznika za pomocą strony dyskusji danego modułu książki Dzięki temuprzyczyniasz się do rozwoju tego podręcznika

15 AutorzyIstotny wkład w powstanie podręcznika mają

CzarnyZajaczek

Derbeth

Kj

mina

Dodatkowo w rozwoju podręcznika pomagali między innymi

Lrds

Noisy

16 Źroacutedła podręcznik C Programming na anglojęzycznej wersji Wikibooks licencja GFDL

Brian W Kernighan Dennis M Ritchie Język ANSI C

ISO C Commiee Dra stycznia

Bruce Eckel inking in C++ Rozdział Język C w programie C++

Rozdział 2

O języku C

Zobacz w Wikipedii C (ję-zyk programowania)C jest językiem programowania wysokiego poziomu Jego nazwę interpretuje się jako na-

stępną literę po B (nazwa jego poprzednika) lub drugą literę języka BCPL (poprzednik językaB)

21 Historia CW roku trzej naukowcy z Bell Telephone Laboratories mdashWilliam Shockley Walter Brat-tain i John Bardeen mdash stworzyli pierwszy tranzystor w roku w MIT skonstruowanopierwszy komputer oparty wyłącznie na tranzystorach TX-O w roku Jack Kilby z Te-xas Instruments skonstruował układ scalony Ale zanim powstał pierwszy układ scalonypierwszy język wysokiego poziomu został już napisany

W powstał Fortran (Formula Translator) ktoacutery zapoczątkował napisanie języka For-tran I () Poacuteźniej powstały kolejno

Algol mdash Algorithmic Language w r

Algol ()

CPL mdash Combined Programming Language ()

BCPL mdash Basic CPL ()

B ()

i C w oparciu o BB został stworzony przez Kena ompsona z Bell Labs był to język interpretowany uży-

wany we wczesnych wewnętrznych wersjach systemu operacyjnego UNIX Inni pracownicyBell Labs ompson i Dennis Richie rozwinęli B nazywając go NB dalszy rozwoacutej NB dał Cmdash język kompilowany Większa część UNIX-a została ponownie napisana w NB a następniew C co dało w efekcie bardziej przenośny system operacyjny W roku wydana zostałaksiążka pt ldquoe C Programming Languagerdquo ktoacutera stała się pierwszym podręcznikiem donauki języka C

Możliwość uruchamiania UNIX-a na roacuteżnych komputerach była głoacutewną przyczyną po-czątkowej popularności zaroacutewno UNIX-a jak i C zamiast tworzyć nowy system operacyjnyprogramiści mogli po prostu napisać tylko te części systemu ktoacuterych wymagał inny sprzętoraz napisać kompilator C dla nowego systemu Odkąd większa część narzędzi systemowychbyła napisana w C logiczne było pisanie kolejnych w tym samym języku

13

14 ROZDZIAŁ 2 O JĘZYKU C

Kilka z obecnie powszechnie stosowanych systemoacutew operacyjnych takich jak Linux Mi-croso Windows zostały napisane w języku C

211 Standaryzacje

W roku Ritchie i Kerninghan opublikowali pierwszą książkę nt języka C mdash ldquoe CProgramming Languagerdquo Owa książka przez wiele lat była swoistym ldquowyznacznikiemrdquo jakprogramować w języku C Była więc to niejako pierwsza standaryzacja nazywana od na-zwisk twoacutercoacutew ldquoKampRrdquo Oto nowości wprowadzone przez nią do języka C w stosunku dojego pierwszych wersji (pochodzących z początku lat )

możliwość tworzenia struktur (słowo struct)

dłuższe typy danych (modyfikator long)

liczby całkowite bez znaku (modyfikator unsigned)

zmieniono operator ldquo=+rdquo na ldquo+=rdquo

Ponadto producenci kompilatoroacutew (zwłaszcza ATampT) wprowadzali swoje zmiany nieob-jęte standardem

funkcje nie zwracające wartości (void) oraz typ void

funkcje zwracające struktury i unie

przypisywanie wartości strukturom

wprowadzenie słowa kluczowego const

utworzenie biblioteki standardowej

wprowadzenie słowa kluczowego enum

Owe nieoficjalne rozszerzenia zagroziły spoacutejności języka dlatego też powstał standardregulujący wprowadzone nowinki Od roku trwały prace standaryzacyjne aby w roku wydać standard C (poprawna nazwa to ANSI X-) Niektoacutere zmiany wpro-wadzono z języka C++ jednak rewolucję miał dopiero przynieść standard C ktoacutery wpro-wadził min

funkcje inline

nowe typy danych (np long long int)

nowy sposoacuteb komentowania zapożyczony od C++ ()

przechowywanie liczb zmiennoprzecinkowych zostało zaadaptowane do norm IEEE

utworzono kilka nowych plikoacutew nagłoacutewkowych (stdboolh inypesh)

Na dzień dzisiejszy normą obowiązującą jest norma C

22 ZASTOSOWANIA JĘZYKA C 15

22 Zastosowania języka CJęzyk C został opracowany jako strukturalny język programowania do celoacutew ogoacutelnych Przezcałą swą historię (czyli ponad lat) służył do tworzenia przeroacuteżnych programoacutewmdash od syste-moacutew operacyjnych po programy nadzorujące pracę urządzeń przemysłowych C jako językdużo szybszy od językoacutew interpretowanych (Perl Python) oraz uruchamianych w maszy-nach wirtualnych (np C Java) może bez problemu wykonywać złożone operacje nawetwtedy gdy nałożone są dość duże limity czasu wykonywania pewnych operacji Jest on przytym bardzo przenośny mdash może działać praktycznie na każdej architekturze sprzętowej podwarunkiem opracowania odpowiedniego kompilatora Często wykorzystywany jest takżedo oprogramowywania mikrokontroleroacutew i systemoacutew wbudowanych Jednak w niektoacuterychsytuacjach język C okazuje się być mało przydatny zwłaszcza chodzi tu o obliczenia mate-matyczne wymagające dużej precyzji (w tej dziedzinie znakomicie spisuje się Fortran) lubteż dużej optymalizacji dla danego sprzętu (wtedy niezastąpiony jest język asemblera)

Kolejną zaletą C jest jego dostępność mdash właściwie każdy system typu UNIX posiada kom-pilator C w C pisane są funkcje systemowe

Problemem w przypadku C jest zarządzanie pamięcią ktoacutere nie wybacza programiściebłędoacutew niewygodne operowanie napisami i niestety pewna liczba ldquokruczkoacutewrdquo ktoacutere mogązaskakiwać nowicjuszy Na tle młodszych językoacutew programowania C jest językiem dosyćniskiego poziomu więc wiele rzeczy trzeba w nim robić ręcznie jednak zarazem umożliwia torobienie rzeczy nieprzewidzianych w samym języku (np implementację liczb bitowych)a także łatwe łączenie C z Asemblerem

23 Przyszłość CPomimo sędziwego już wieku (C ma ponad lat) nadal jest on jednym z najczęściej stosowa-nych językoacutew programowania Doczekał się już swoich następcoacutew z ktoacuterymi w niektoacuterychdziedzinach nadal udaje mu się wygrywać Widać zatem że pomimo pozornej prostoty iniewielkich możliwości język C nadal spełnia stawiane przed nim wymagania Warto zatemuczyć się języka C gdyż nadal jest on wykorzystywany (i nic nie wskazuje na to by miałosię to zmienić) a wiedza ktoacuterą zdobędziesz ucząc się C na pewno się nie zmarnuje Skład-nia języka C pomimo że przez wielu uważana za nieczytelną stała się podstawą dla takichjęzykoacutew jak C++ C czy też Java

16 ROZDZIAŁ 2 O JĘZYKU C

Rozdział 3

Czego potrzebujesz

31 Czego potrzebujeszWbrew powszechnej opinii nauczenie się ktoacuteregoś z językoacutew programowania (w tym językaC) nie jest takie trudne Do nauki wystarczą Ci

komputer z dowolnym systemem operacyjnym takim jak FreeBSD Linux Windows

Język C jest bardzo przenośny więc będzie działał właściwie na każdej platformiesprzętowej i w każdym nowoczesnym systemie operacyjnym

kompilator języka C

Kompilator języka C jest programem ktoacutery tłumaczy kod źroacutedłowy napisany przeznas do języka asembler a następnie do postaci zrozumiałej dla komputera (maszynycyfrowej) czyli do postaci ciągu zer i jedynek ktoacutere sterują pracą poszczegoacutelnych ele-mentoacutew komputera Kompilator języka C można dostać za darmo Przykładem sągcc pod systemy uniksowe DJGPP pod systemy DOS MinGW oraz lcc pod systemytypu Windows Jako kompilator C może dobrze służyć kompilator języka C++ (roacuteżnicemiędzy tymi językami przy pisaniu prostych programoacutew są nieistotne) Spokojnie mo-żesz więc użyć na przykład Microso Visual C++reg lub kompilatoroacutew firmy BorlandJeśli lubisz eksperymentować wyproacutebuj Tiny C Compiler bardzo szybki kompilatoro ciekawych funkcjach Możesz ponadto wyproacutebować interpreter języka C Więcejinformacji na Wikipedii

Linker (często jest razem z kompilatorem)

Linker jest to program ktoacutery uruchamiany jest po etapie kompilacji jednego lub kilkuplikoacutew źroacutedłowych (pliki z rozszerzeniem c cpp lub innym) skompilowanych do-wolnym kompilatorem Taki program łączy wszystkie nasze skompilowane pliki źroacute-dłowe i inne funkcje (np printf scan) ktoacutere były użyte (dołączone do naszego pro-gramu poprzez użycie dyrektywy include) w naszym programie a nie były zdefinio-wane(napisane przez nas) w naszych plikach źroacutedłowych lub nagłoacutewkowych Linkerjest to czasami jeden program połączony z kompilatorem Wywoływany jest on naogoacuteł automatycznie przez kompilator w wyniku czego dostajemy gotowy program douruchomienia

Debuger (opcjonalnie według potrzeb)

17

18 ROZDZIAŁ 3 CZEGO POTRZEBUJESZ

Debugger jest to program ktoacutery umożliwia prześledzenie(określenie wartości poszcze-goacutelnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijcewykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu Używasię go w celu określenia czemu nasz program nie działa po naszej myśli lub czemu pro-gram niespodziewanie kończy działanie bez powodu Aby użyć debuggera kompilatormusi dołączyć kod źroacutedłowy do gotowego skompilowanego programu Przykładowymidebuggerami są gdb pod Linuksem lub debugger firmy Borland pod Windowsa

edytora tekstowego

Systemy uniksowe oferują wiele edytoroacutew przydatnych dla programisty jak choćbyvim i Emacs w trybie tekstowym Kate w KDE czy gedit w GNOME Windows maedytor całkowicie wystarczający do pisania programoacutew w C mdash nieśmiertelny Notatnikmdash ale z łatwością znajdziesz w Internecie wiele wygodniejszych narzędzi takich jak npNotepad++ Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE

dużo chęci i dobrej motywacji

32 Zintegrowane Środowiska ProgramistyczneZamiast osobnego kompilatora i edytora możesz wybrać Zintegrowane Środowisko Progra-mistyczne (Integrated Development Environment IDE) IDE jest zestawem wszystkich pro-gramoacutew ktoacutere potrzebuje programista najczęściej z interfejsem graficznym IDE zawierakompilator linker i edytor z reguły roacutewnież debugger

Bardzo popularny IDE to płatny (istnieje także jego darmowa wersja) Microso VisualC++ (MS VC++) popularne darmowe IDE to np

CodeBlocks dla Windows jak i Linux dostępny na stronie wwwcodeblocksorg

KDevelop (Linux) dla KDE

NetBeans multiplatformowy darmowy do ściągnięcia na stronie wwwnetbeansorg

Eclipse z wtyczką CDT (wspoacutełpracuje z MinGW i GCC)

Borland C++ Builder dostępny za darmo do użytku prywatnego

Xcode dlaMac OS X i nowszy kompatybilny z procesorami PowerPC i Intel (moż-liwość stworzenia Universal Binary)

Geany dla systemoacutewWindows i Linux wspoacutełpracuje zMinGW iGCCwwwgeanyorg

Pelles C wwwsmorgasbordetcom

Dev-C++ dla Windows dostępny na stronie wwwbloodshednet

33 Dodatkowe narzędziaWśroacuted narzędzi ktoacutere nie są niezbędne ale zasługują na uwagę można wymienić Valgrindandash specjalnego rodzaju debugger Valgrind kontroluje wykonanie programu i wykrywa nie-prawidłowe operacje w pamięci oraz wycieki pamięci Użycie Valgrinda jest proste mdash kom-pilujemy program jak do debugowania następnie podajemy jako argument Valgrindowi

Rozdział 4

Używanie kompilatora

Język C jest językiem kompilowanym co oznacza że potrzebuje specjalnego programu mdashkompilatora mdash ktoacutery tłumaczy kod źroacutedłowy pisany przez człowieka na język rozkazoacutew da-nego komputera W skroacutecie działanie kompilatora sprowadza się do czytania tekstowegopliku z kodem programu raportowania ewentualnych błędoacutew i produkowania pliku wyniko-wego

Kompilator uruchamiamy ze Zintegrowanego Środowiska Programistycznego lub z kon-soli (linii poleceń) Przejść do konsoli można dla systemoacutew typu UNIX w trybie graficz-nym użyć programoacutew gnome-terminal konsole albo xterm w Windows ldquoWiersz poleceniardquo(można go znaleźćwmenuAkcesoria albo uruchomićwpisującw Start -gtUruchom ldquocmdrdquo)

41 GCCZobacz w Wikipedii GCC

GCC jest to darmowy zestaw kompilatoroacutew min języka C rozwijany w ramach projektuGNU Dostępny jest on na dużą ilość platform sprzętowych obsługiwanych przez takie sys-temy operacyjne jak AIX BSD Linux Mac OS X SunOS Windows Na niektoacuterych sys-temach (np Windows) nie jest on jednak dostępny automatycznie Należy zainstalowaćodpowiednie narzędza (poprzedni rozdział)

Aby skompilować kod języka C za pomocą kompilatora GCC napisany wcześniej w do-wolnym edytorze tekstu należy uruchomić program z odpowiednimi parametrami Podsta-wowym parametrem ktoacutery jest wymagany jest nazwa pliku zawierającego kod programuktoacutery chcemy skompilować

gcc kodc

Rezultatem kompilacji będzie plik wykonywalny z domyślną nazwą (w systemach Unixjest to ldquoaoutrdquo) Jest to metoda niezalecana ponieważ jeżeli skompilujemy w tym samymkatalogu kilka plikoacutew z kodem kolejne pliki wykonywalne zostaną nadpisane i w rezultacieotrzymamy tylko jeden (ten ostatni) skompilowany kod Aby wymusić na GCC nazwę plikuwykonywalnego musimy skorzystać z parametru ldquo-o ltnazwagtrdquo

gcc -o program kodc

W rezultacie otrzymujemy plik wykonywalny o nazwie programPracując nad złożonym programem składającym się z kilku plikoacutew źroacutedłowych (c) mo-

żemy skompilować je niezależnie od siebie tworząc tak zwane pliki typu obiekt z rozszerze-niem o (ang Object File) Następnie możemy stworzyć z nich jednolity program w procesie

19

20 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

konsolidacji (linkowaniu) Jest to bardzo wygodne i praktyczne rozwiązanie ze względu nato iż nie jesteśmy zmuszeni kompilować wszystkich plikoacutew tworzących program za każdymrazem na nowo a jedynie te w ktoacuterych wprowadziliśmy zmiany Aby skompilować plik bezlinkowania używamy parametru ldquo-c ltplikgtrdquo

gcc -o program1o -c kod1cgcc -o program2o -c kod2c

Otrzymujemy w ten sposoacuteb pliki typu obiekt programo i programo A następnie two-rzymy z nich program głoacutewny

gcc -o program program1o program2o

Możemy użyć roacutewnież flag min aby włączyć dokładne rygorystyczne sprawdzanie na-pisanego kodu (co może być przydatne jeśli chcemy dążyć do perfekcji) używamy przełącz-nikoacutew

gcc kodc -o program -Werror -Wall -W -pedantic -ansi

Więcej informacji na temat parametroacutew i działania kompilatora GCC można znaleźć na

Strona domowa projektu GNU GCC

Kroacutetki przekrojowy opis GCC po polsku

Strona podręcznika systemu UNIX (man)

42 BorlandZobacz podręcznik Borland C++ Compiler

43 Czytanie komunikatoacutew o błędaJedną z najbardziej podstawowych umiejętności ktoacutere musi posiąść początkujący progra-mista jest umiejętność rozumienia komunikatoacutew o roacuteżnego rodzaju błędach ktoacutere sygnali-zuje kompilator Wszystkie te informacje pomogą Ci szybko wychwycić ewentualne błędy(ktoacuterych na początku zawsze jest bardzo dużo) Nie martw się że na początku dość częstobędziesz oglądał wydruki błędoacutew zasygnalizowanych przez kompilator mdash nawet zaawanso-wanym programistom się to zdarza Kompilator ma za zadanie pomoacutec Ci w szybkiej popra-wie ewentualnych błędoacutew dlatego też umiejętność analizy komunikatoacutew o błędach jest takważna

431 GCC

Kompilator jest w stanie wychwycić błędy składniowe ktoacutere z pewnością będziesz popełniałKompilator GCC wyświetla je w następującej formie

nazwa_plikucnumer_linijkiopis błędu

Kompilator dość często podaje także nazwę funkcji w ktoacuterej wystąpił błąd Przykładowobłąd deklaracji zmiennej w pliku testc

43 CZYTANIE KOMUNIKATOacuteW O BŁĘDACH 21

include ltstdiohgt

int main ()

intr rprintf (dn r)

Spowoduje wygenerowanie następującego komunikatu o błędzie

testc In function lsquomainrsquotestc5 error lsquointrrsquo undeclared (first use in this function)testc5 error (Each undeclared identifier is reported only oncetestc5 error for each function it appears in)testc5 error syntax error before lsquorrsquotestc6 error lsquorrsquo undeclared (first use in this function)

Co widzimy w raporcie o błędach W linii użyliśmy nieznanego (undeclared) identy-fikatora intr mdash kompilator moacutewi że nie zna tego identyfikatora jest to pierwsze użycie wdanej funkcji i że więcej nie ostrzeże o użyciu tego identyfykatora w tej funkcji Ponieważintr nie został rozpoznany jako żaden znany typ linijka intr r nie została rozpoznana jakodeklaracja zmiennej i kompilator zgłasza błąd składniowy (syntax error) W konsekwencji rnie zostało rozpoznane jako zmienna i kompilator zgłosi to jeszcze w następnej linijce gdzieużywamy r

22 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

Rozdział 5

Pierwszy program

51 Twoacutej pierwszy program

Przyjęło się że pierwszy program napisany w dowolnym języku programowania powinienwyświetlić tekst ldquoHello Worldrdquo (Witaj Świecie) Zauważ że sam język C nie ma żadnychmechanizmoacutew przeznaczonych do wprowadzania i wypisywania danych mdash musimy zatemskorzystać z odpowiadających za to funkcji mdash w tym przypadku printf zawartej w standar-dowej bibliotece C (ang C Standard Library) (podobnie jak w Pascalu używa się do tegoprocedur Pascalowskim odpowiednikiem funkcji printf są procedury writewriteln)

W języku C deklaracje funkcji zawarte są w plika nagłoacutewkowy posiadających naj-częściej rozszerzenie h choć można także spotkać rozszerzenie hpp przy czym to drugiezwykło się stosować w języku C++ (rozszerzenie nie ma swych ldquotechnicznychrdquo korzeni mdash jestto tylko pewna konwencja) W celu umieszczenia w swoim kodzie pewnego pliku nagłoacutewko-wego używamy dyrektywy kompilacyjnej include Przed procesem kompilacji w miejscetej dyrektywy wstawiana jest treść podanego pliku nagłoacutewkowego dostarczając deklaracjifunkcji

Poniższy przykład obrazuje jak przy użyciu dyrektywy include umieścimyw kodzie plikstandardowej biblioteki C stdioh (Standard InputOutputHeaderfile) zawierającą definicjęfunkcji printf

include ltstdiohgt

W nawiasach troacutejkątnych lt gt umieszcza się nazwy standardowych plikoacutew nagłoacutewko-wych1 Żeby włączyć inny plik nagłoacutewkowy (np własny) znajdujący się w katalogu z kodemprogramu trzeba go wpisać w cudzysłoacutew

include moacutej_plik_nagłoacutewkowyh

Mamy więc funkcję printf jak i wiele innych do wprowadzania i wypisywania danychczas na pisanie programu

W programie definujemy głoacutewną funkcję main uruchamianą przy starcie programu za-wierającą właściwy kod Definicja funkcji zawiera oproacutecz nazwy i kodu także typ wartościzwracanej i argumentoacutew pobieranych Konstrukcja funkcji main

1Domyślne pliki nagłoacutewkowe znajdują się w katalogu z plikami nagłoacutewkowymi kompilatora W systemach zrodziny Unix będzie to katalog usrinclude natomiast w systemie Windows oacutew katalog będzie umieszczony wkatalogu z kompilatorem

23

24 ROZDZIAŁ 5 PIERWSZY PROGRAM

int main (void)

Typem zwracany przez funkcję jest int (Integer) czyli liczba całkowita (w przypadkumainbędzie to kod wyjściowy programu) W nawiasach umieszczane są argumenty funkcji tutajzapis void oznacza ich pominięcie Funkcja main jako argumenty może pobierać parametrylinii poleceń z jakimi program został uruchomiony i pełną ścieżkę do katalogu z programem

Kod funkcji umieszcza się w nawiasach klamrowych i Wewnątrz funkcji możemy wpisać poniższy kod

printf(Hello World)return 0

Wszystkie polecenia kończymy średnikiem return określa wartość jaką zwroacuteci funkcja(program) Liczba zero zwracana przez funkcję main() oznacza że program zakończył siębez błędoacutew błędne zakończenie często (choć nie zawsze) określane jest przez liczbę jeden2Funkcję main kończymy nawiasem klamrowym zamykającym

Twoacutej kod powinien wyglądać jak poniżej

include ltstdiohgtint main (void)

printf (Hello World)return 0

Teraz wystarczy go tylko skompilować i uruchomić

52 Rozwiązywanie problemoacutewJeśli nie możesz skompilować powyższego programu to najprawdopodobniej popełniłeś li-teroacutewkę przy ręcznym przepisywaniu go Zobacz też instrukcje na temat używania kompi-latora

Może też się zdarzyć że program skompiluje się uruchomi ale jego efektu działania niebędzie widać Dzieje się tak ponieważ nasz pierwszy program po prostu wypisuje komunikati od razu kończy działanie nie czekając na reakcję użytkownika Nie jest to problememgdy program jest uruchamiany z konsoli tekstowej ale w innych przypadkach nie widzimyefektoacutew jego działania

Jeśli korzystasz ze Zintegrowanego Środowiska Programistycznego (ang IDE) możeszzaznaczyć by nie zamykało ono programu po zakończeniu jego działania Innym sposobemjest dodanie instrukcji ktoacutere wstrzymywałyby zakończenie programu Można to zrobić do-dając przed linią z return funkcję pobierającą znak z wejścia

getchar()

2Jeżeli chcesz mieć pewność że twoacutej program będzie działał poprawnie roacutewnież na platformach gdzie 1 oznaczapoprawne zakończenie (lub nie oznacza nic) możesz skorzystać z makr EXIT SUCCESS i EXIT FAILURE zdefiniowanychw pliku nagłoacutewkowym stdlibh

52 ROZWIĄZYWANIE PROBLEMOacuteW 25

Jest też prostszy (choć nieprzenośny) sposoacuteb mianowicie wywołanie polecenia systemo-wego W zależności od używanego systemu operacyjnego mamy do dyspozycji roacuteżne po-lecenia powodujące roacuteżne efekty Do tego celu skorzystamy z funkcji system() ktoacutera jakoparametr przyjmuje polecenie systemowe ktoacutere chcemy wykonać np

Rodzina systemoacutew UnixLinux

system(sleep 10) oczekiwanie 10 s system(read discard) oczekiwanie na wpisanie tekstu

Rodzina systemoacutew oraz MS Windows

system(pause) oczekiwanie na wciśnięcie dowolnego klawisza

Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnychWindows w ktoacuterychto z reguły pracuje się w trybie okienkowym a z konsoli korzystamy tylko podczas urucha-mianiu programu Z kolei w systemach UnixLinux jest ona praktycznie w ogoacutele nie używanaw tym celu ze względu na uruchamianie programu bezpośrednio z konsoli

26 ROZDZIAŁ 5 PIERWSZY PROGRAM

Rozdział 6

Podstawy

Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogoacutel-nych informacji

61 Kompilacja Jak działa C

Jak każdy język programowania C sam w sobie jest niezrozumiały dla procesora Został onstworzony w celu umożliwienia ludziom łatwego pisania kodu ktoacutery może zostać przetwo-rzony na kod maszynowy Program ktoacutery zamienia kod C na wykonywalny kod binarnyto kompilator Jeśli pracujesz nad projektem ktoacutery wymaga kilku plikoacutew kodu źroacutedłowego(np pliki nagłoacutewkowe) wtedy jest uruchamiany kolejny program mdash linker Linker służy dopołączenia roacuteżnych plikoacutew i stworzenia jednej aplikacji lub biblioteki (library) Bibliotekajest zestawem procedur ktoacutery sam w sobie nie jest wykonywalny ale może być używanaprzez inne programy Kompilacja i łączenie plikoacutew są ze sobą bardzo ściśle powiązane stądsą przez wielu traktowane jako jeden proces Jedną rzecz warto sobie uświadomić mdash kompila-cja jest jednokierunkowa przekształcenie kodu źroacutedłowego C w kod maszynowy jest bardzoproste natomiast odwrotnie mdash nie Dekompilatory co prawda istnieją ale rzadko tworząużyteczny kod C

Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collec-tion dostępny na stronie gccgnuorg

62 Co może C

Pewnie zaskoczy Cię to że tak naprawdę ldquoczystyrdquo język C nie może zbyt wiele Język Cw grupie językoacutew programowania wysokiego poziomu jest stosunkowo nisko Dzięki temukod napisany w języku C można dość łatwo przetłumaczyć na kod asemblera Bardzo łatwojest też łączyć ze sobą kod napisany w języku asemblera z kodem napisanym w C Dla bar-dzo wielu ludzi przeszkodą jest także dość duża liczba i częsta dwuznaczność operatoroacutewPoczątkujący programista czytający kod programu w C może odnieść bardzo nieprzyjemnewrażenie ktoacutere można opisać cytatem ldquoja nigdy tego nie opanujęrdquo Wszystkie te elementyjęzyka C ktoacutere wydają Ci się dziwne i nielogiczne wmiarę jak będziesz nabierał doświadcze-nia nagle okażą się całkiem przemyślanie dobrane i takie a nie inne konstrukcje przypadnąCi do gustu Dalsza lektura tego podręcznika oraz zaznajamianie się z funkcjami z roacuteżnychbibliotek ukażą Ci całą gamę możliwości ktoacutere daje język C doświadczonemu programiście

27

28 ROZDZIAŁ 6 PODSTAWY

63 Struktura blokowaTeraz omoacutewimy podstawową strukturę programu napisanego w C Jeśli miałeś styczność zjęzykiem Pascal to pewnie słyszałeś o nim że jest to język programowania strukturalny WC nie ma tak ścisłej struktury blokowej mimo to jest bardzo ważne zrozumienie co oznaczastruktura blokowa Blok jest grupą instrukcji połączonych w ten sposoacuteb że są traktowanejak jedna całość W C blok zawiera się pomiędzy nawiasami klamrowymi Blok możetakże zawierać kolejne bloki

Zawartość bloku Generalnie blok zawiera ciąg kolejno wykonywanych poleceń Polece-nia zawsze (z nielicznymi wyjątkami) kończą się średnikiem () W jednej linii może znajdo-wać się wiele poleceń choć dla zwiększenia czytelności kodu najczęściej pisze się pojedyncząinstrukcję w każdej linii Jest kilka rodzajoacutew poleceń np instrukcje przypisania warunkoweczy pętli W dużej części tego podręcznika będziemy zajmować się właśnie instrukcjami

Pomiędzy poleceniami są roacutewnież odstępymdash spacje tabulacje oraz przejścia do następnejlinii przy czym dla kompilatora te trzy rodzaje odstępoacutew mają takie samo znaczenie Dlaprzykładu poniższe trzy fragmenty kodu źroacutedłowego dla kompilatora są takie same

printf(Hello world) return 0

printf(Hello world)return 0

printf(Hello world)

return 0

W tej regule istnieje jednak jeden wyjątek Dotyczy on stałych tekstowych W powyż-szych przykładach stałą tekstową jest ldquoHello worldrdquo Gdy jednak rozbijemy ten napis kom-pilator zasygnalizuje błąd

printf(Helloworld)return 0

Należy tylko zapamiętać że stałe tekstowe powinny zaczynać się i kończyć w tej samejlini (można ominąć to ograniczenie mdash więcej w rozdziale Napisy) Oproacutecz tego jednego przy-padku dla kompilatora ma znaczenie samo istnienie odstępu a nie jego wielkość czy rodzajJednak stosowanie odstępoacutew jest bardzo ważne dla zwiększenia czytelności kodu mdash dziękiczemu możemy zaoszczędzić sporo czasu i nerwoacutew ponieważ znalezienie błędu (ktoacutere sięzdarzają każdemu) w nieczytelnym kodzie może być bardzo trudne

64 ZasięgPojęcie to dotyczy zmiennych (ktoacutere przechowują dane przetwarzane przez program) Wkaż-dym programie (oproacutecz tych najprostszych) są zaroacutewno zmienne wykorzystywane przez całyczas działania programu oraz takie ktoacutere są używane przez pojedynczy blok programu (npfunkcję) Na przykład w pewnym programie w pewnym momencie jest wykonywane skom-plikowane obliczenie ktoacutere wymaga zadeklarowania wielu zmiennych do przechowywaniapośrednich wynikoacutew Ale przez większą część tego działania te zmienne są niepotrzebne

65 FUNKCJE 29

i zajmują tylko miejsce w pamięci mdash najlepiej gdyby to miejsce zostało zarezerwowane tużprzed wykonaniemwspomnianych obliczeń a zaraz po ich wykonaniu zwolnione Dlatego wC istnieją zmienne globalne oraz lokalne Zmienne globalne mogą być używane w każdymmiejscu programu natomiast lokalne mdash tylko w określonym bloku czy funkcji (oraz blokachw nim zawartych) Generalnie mdash zmienna zadeklarowanaw danym bloku jest dostępna tylkowewnątrz niego

65 Funkcje

Funkcje są ściśle związane ze strukturą blokową mdash funkcją jest po prostu blok instrukcjiktoacutery jest potem wywoływany w programie za pomocą pojedynczego polecenia Zazwyczajfunkcja wykonuje pewne określone zadanie np we wspomnianym programie wykonują-cym pewne skomplikowane obliczenie Każda funkcja ma swoją nazwę za pomocą ktoacuterejjest potem wywoływana w programie oraz blok wykonywanych poleceń Wiele funkcji po-biera pewne dane czyli argumenty funkcji wiele funkcji także zwraca pewną wartość pozakończeniu wykonywania Dobrym nawykiem jest dzielenie dużego programu na zestawmniejszych funkcji mdash dzięki temu będziesz moacutegł łatwiej odnaleźć błąd w programie

Jeśli chcesz użyć jakiejś funkcji to powinieneś wiedzieć

jakie zadanie wykonuje dana funkcja

rodzaj wczytywanych argumentoacutew i do czego są one potrzebne tej funkcji

rodzaj zwroacuteconych danych i co one oznaczają

W programach w języku C jedna funkcja ma szczegoacutelne znaczenie mdash jest tomain() Funk-cję tę zwaną funkcją głoacutewną musi zawierać każdy program W niej zawiera się głoacutewny kodprogramu przekazywane są do niej argumenty z ktoacuterymi wywoływany jest program (jakoparametry argc i argv) Więcej o funkcji main() dowiesz się poacuteźniej w rozdziale Funkcje

66 Biblioteki standardowe

Język C w przeciwieństwie do innych językoacutew programowania (np Fortranu czy Pascala)nie posiada absolutnie żadny słoacutew kluczowych ktoacutere odpowiedzialne by były za obsługęwejścia i wyjścia Może się to wydawać dziwne mdash język ktoacutery sam w sobie nie posiadapodstawowych funkcji musi być językiem o ograniczonym zastosowaniu Jednak brak pod-stawowych funkcji wejścia-wyjścia jest jedną z największych zalet tego języka Jego składniaopracowana jest tak by można było bardzo łatwo przełożyć ją na kod maszynowy To wła-śnie dzięki temu programy napisane w języku C są takie szybkie Pozostaje jednak pytaniemdash jak umożliwić programom komunikację z użytkownikiem

W roku kiedy zapoczątkowano prace nad standaryzacją C zdecydowano że po-winien być zestaw instrukcji identycznych w każdej implementacji C Nazwano je BibliotekąStandardową (czasemnazywaną ldquolibcrdquo) Zawiera ona podstawowe funkcje ktoacutere umożliwiająwykonywanie takich zadań jak wczytywanie i zwracanie danych modyfikowanie zmiennychłańcuchowych działania matematyczne operacje na plikach i wiele innych jednak nie za-wiera żadnych funkcji ktoacutere mogą być zależne od systemu operacyjnego czy sprzętu jakgrafika dźwięk czy obsługa sieci W programie ldquoHello Worldrdquo użyto funkcji z biblioteki stan-dardowej mdash printf ktoacutera wyświetla na ekranie sformatowany tekst

30 ROZDZIAŁ 6 PODSTAWY

67 Komentarze i stylKomentarze mdash to tekst włączony do kodu źroacutedłowego ktoacutery jest pomijany przez kompilatori służy jedynie dokumentacji W języku C komentarze zaczynają się od

a kończą

Dobre komentowanie ma duże znaczenie dla rozwijania oprogramowania nie tylko dlategoże inni będą kiedyś potrzebowali przeczytać napisany przez ciebie kod źroacutedłowy ale takżemożesz chcieć po dłuższym czasie powroacutecić do swojego programu i możesz zapomnieć doczego służy dany blok kodu albo dlaczego akurat użyłeś tego polecenia a nie innego Wchwili pisania programu to może być dla ciebie oczywiste ale po dłuższym czasie możeszmieć problemy ze zrozumieniem własnego kodu Jednak nie należy też wstawiać zbyt dużokomentarzy ponieważ wtedy kod może stać się jeszcze mniej czytelny mdash najlepiej komen-tować fragmenty ktoacutere nie są oczywiste dla programisty oraz te o szczegoacutelnym znaczeniuAle tego nauczysz się już w praktyce

Dobry styl pisania kodu jest o tyle ważny że powinien on być czytelny i zrozumiały po tow końcu wymyślono języki programowania wysokiego poziomu (w tym C) aby kod było ła-two zrozumieć ) I tak mdash należy stosować wcięcia dla odroacuteżnienia blokoacutew kolejnego poziomu(zawartych w innym bloku podrzędnych) nawiasy klamrowe otwierające i zamykające blokpowinny mieć takie same wcięcia staraj się aby nazwy funkcji i zmiennych kojarzyły się zzadaniem jakie dana funkcja czy zmienna pełni w programie W dalszej części podręcznikamożesz napotkać więcej zaleceń dotyczących stylu pisania kodu Staraj się stosować do tychzaleceń mdash dzięki temu kod pisanych przez ciebie programoacutew będzie łatwiejszy do czytania izrozumienia

Jeśli masz doświadczenia z językiem C++ pamiętaj że w C nie powinno się stosowaćkomentarzy zaczynających się od dwoacutech znakoacutew slash tak nie komentujemy w CJest to niezgodne ze standardem ANSI C i niektoacutere kompilatory mogą nie skompilować koduz komentarzami w stylu C++ (choć standard ISO C dopuszcza komentarze w stylu C++)

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentoacutew kodu Jeśli częśćprogramu źle działa i chcemy ją chwilowo wyłączyć albo fragment kodu jest nam już nie-potrzebny ale mamy wątpliwości czy w przyszłości nie będziemy chcieli go użyć mdash umiesz-czamy go po prostu wewnątrz komentarza

Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uważać najedną subtelność Otoacuteż komentarze w języku C nie mogą być zagnieżdżone Trzebana to uważać gdy chcemy objąć komentarzem obszar w ktoacuterym już istnieje komentarz (na-leży wtedy usunąć wewnętrzny komentarz) W nowszym standardzie C dopuszcza się abykomentarz typu zawierał w sobie komentarz

671 Po polsku czy angielsku

Jak jużwcześniej byłowspomniane zmiennym i funkcjom powinno się nadawać nazwy ktoacutereodpowiadają ich znaczeniu Zdecydowanie łatwiej jest czytać kod gdy średnią liczb przecho-wuje zmienna srednia niż a a znajdowaniemmaksimumw ciągu liczb zajmuje się funkcja maxalbo znajdz max niż nazwana f Często nazwy funkcji to właśnie czasowniki

67 KOMENTARZE I STYL 31

Powstaje pytanie w jakim języku należy pisać nazwy Jeśli chcemy by nasz kod mogłyczytać osoby nieznające polskiego mdash warto użyć języka angielskiego Jeśli nie mdash można bezproblemu użyć polskiego Bardzo istotne jest jednak by nie mieszać językoacutew Jeśli zdecy-dowaliśmy się używać polskiego używajmy go od początku do końca przeplatanie ze sobądwoacutech językoacutew robi złe wrażenie

Warto roacutewnież zdecydować się na sposoacuteb zapisywania nazw składających się z więcej niżjednego słowa Istnieje kilka możliwości najważniejsze z nich

oddzielanie podkreśleniem int to str

ldquokonwencja pascalowskardquo każde słowo dużą literą IntToStr

ldquokonwencja wielbłądziardquo pierwsze słowo małą kolejne dużą literą intToStr

Ponownie najlepiej stosować konsekwentnie jedną z konwencji i nie mieszać ze sobąkilku

672 Notacja węgierska

Czasem programista może zapomnieć jakiego typu była dana zmienna Wtedy musi znaleźćodpowiednią deklarację (co nie zawsze jest łatwe) Dlatego więc wymyślono sposoacuteb by temuzaradzić Pomyślano by w nazwie zmiennej (bądź wskaźnika na zmienną) napisać jakiegojest ona typu np

a liczba (liczba typu int)

w ll dlugaLiczba (wskaźnik na zmienną typu long long)

t5x5 ch tabliczka (tablica x elementoacutew typu char)

func i silnia (funkcja zwracająca int)

Jest to bardzo wygodne przy bardzo zagmatwanych zmiennych

w t4 w t2x2 s pomieszaniec (wskaźnik na tablicę czterech wskaźnikoacutew na tablice dwu-wymiarowe zmiennych typu short)

Lub gdy nie pamiętamy wymiaroacutew tablicy

t4x5x6 f powalonaKostkaRubika (od razu wiemy żet4x5x6 f powalonaKostkaRubika[5][4][6] jest niewłaściwe)

Taki zapis ma też swoje wady Gdy zdecydujemy się zmienić typ zmiennej zamiast poprostu przemienić w deklaracji int na long musimy zmieniać nazwy w całym programieCzęsto takie nazwy są po prostu długie i nie chce nam się ich pisać (no coacuteż programista teżczłowiek) więc wolimy wprowadzić pomieszaniec zamiast w t4 w t2x2 s pomieszaniec Naj-ważniejsze to jednak trzymać się rozwiązania ktoacutere wybraliśmy na początku bo mieszaniejest przerażające

32 ROZDZIAŁ 6 PODSTAWY

68 PreprocesorNie cały napisany przez ciebie kod będzie przekształcany przez kompilator bezpośrednio nakodwykonywalny programu Wwielu przypadkach będziesz używać poleceń ldquoskierowanychdo kompilatorardquo tzw dyrektyw kompilacyjnych Na początku procesu kompilacji specjalnypodprogram tzw preprocesor wyszukuje wszystkie dyrektywy kompilacyjne i wykonujeodpowiednie akcje mdash ktoacutere polegają notabene na edycji kodu źroacutedłowego (np wstawieniudeklaracji funkcji zamianie jednego ciągu znakoacutew na inny) Właściwy kompilator zamie-niający kod C na kod wykonywalny nie napotka już dyrektyw kompilacyjnych ponieważzostały one przez preprocesor usunięte po wykonaniu odpowiednich akcji

W C dyrektywy kompilacyjne zaczynają się od znaku hash () Przykładem najczęściejużywanej dyrektywy jest include ktoacutera jest użyta nawet w tak prostym programie jakldquoHello Worldrdquo include nakazuje preprocesorowi włączyć (ang include) w tym miejscuzawartość podanego pliku tzw pliku nagłoacutewkowego najczęściej to będzie plik zawierającyfunkcje z ktoacuterejś biblioteki standardowej (stdioh mdash STandard Input-Output rozszerzenie hoznacza plik nagłoacutewkowy C) Dzięki temu zamiast wklejać do kodu swojego programu dekla-racje kilkunastu a nawet kilkudziesięciu funkcji wystarczy wpisać jedną magiczną linijkę

69 Nazwy zmienny stały i funkcjiIdentyfikatory czyli nazwy zmiennych stałych i funkcji mogą składać się z liter (bez polskichznakoacutew) cyfr i znaku podkreślenia z tym że nazwa taka nie może zaczynać się od cyfry Niemożna używać nazw zarezerwowanych (patrz Składnia)

Przykłady błędnych nazw

2liczba (nie można zaczynać nazwy od cyfry)moja funkcja (nie można używać spacji)$i (nie można używać znaku $)if (if to słowo kluczowe)

Aby kod był bardziej czytelny przestrzegajmy poniższych (umownych) reguł

nazwy zmiennych piszemy małymi literami i file

nazwy stałych (zadeklarowanych przy pomocy define) piszemy wielkimi literamiSIZE

nazwy funkcji piszemy małymi literami print

wyrazy w nazwach oddzielamy znakiem podkreślenia open file close all files

Są to tylko konwencje mdash żaden kompilator nie zgłosi błędu jeśli wprowadzimy swoacutej wła-sny system nazewnictwa Jednak warto pamiętać że być może nad naszym kodem będą pra-cowali także inni programiści ktoacuterzy mogą mieć trudności z analizą kodu niespełniającegopewnych zasad

Rozdział 7

Zmienne

Procesor komputera stworzony jest tak aby przetwarzał dane znajdujące się w pamięci kom-putera Z punktu widzenia programu napisanego w języku C (ktoacutery jak wiadomo jest języ-kiem wysokiego poziomu) dane umieszczane są w postaci tzw zmienny Zmienne uła-twiają programiście pisanie programu Dzięki nim programista nie musi się przejmowaćgdzie w pamięci owe zmienne się znajdują tzn nie operuje fizycznymi adresami pamięcijak np 0x14613467 tylko prostą do zapamiętania nazwą zmiennej

71 Czym są zmienneZmienna jest to pewien fragment pamięci o ustalonym rozmiarze ktoacutery posiada własny iden-tyfikator (nazwę) oraz może przechowywać pewną wartość zależną od typu zmiennej

711 Deklaracja zmienny

Aby moacutec skorzystać ze zmiennej należy ją przed użyciem zadeklarować to znaczy poinfor-mować kompilator jak zmienna będzie się nazywać i jaki typ ma mieć Zmienne deklarujesię w sposoacuteb następujący

typ nazwa_zmiennej

Oto deklaracja zmiennej o nazwie ldquowiekrdquo typu ldquointrdquo czyli liczby całkowitej

int wiek

Zmiennej w momencie zadeklarowania można od razu przypisać wartość

int wiek = 17

W języku C zmienne deklaruje się na samym początku bloku (czyli przed pierwszą in-strukcją)

int wiek = 17printf(dn wiek)int kopia_wieku tu stary kompilator C zgłosi błąd

deklaracja występuje po instrukcji (printf) kopia_wieku = wiek

33

34 ROZDZIAŁ 7 ZMIENNE

Według nowszych standardoacutewmożliwe jest deklarowanie zmiennej w dowolnymmiejscuprogramu ale wtedy musimy pamiętać aby zadeklarować zmienną przed jej użyciem Toznaczy że taki kod jest niepoprawny

printf (Mam d latn wiek)int wiek = 17

Należy go zapisać tak

int wiek = 17printf (Mam d latn wiek)

Język C nie inicjalizuje zmiennych lokalnych Oznacza to że w nowo zadeklarowanejzmiennej znajdują się śmieci - to co wcześniej zawierał przydzielony zmiennej fragmentpamięci Aby uniknąć ciężkich do wykrycia błędoacutew dobrze jest inicjalizować (przypisywaćwartość) wszystkie zmienne w momencie zadeklarowania

712 Zasięg zmiennej

Zmienne mogą być dostępne dla wszystkich funkcji programu mdash nazywamy je wtedy zmien-nymi globalnymi Deklaruje się je przed wszystkimi funkcjami programu

include ltstdiohgt

int ab nasze zmienne globalne

void func1 ()

instrukcje a=3 dalsze instrukcje

int main ()

b=3a=2return 0

Zmienne globalne jeśli programista nie przypisze im innej wartości podczas definiowa-nia są inicjalizowane wartością

Zmienne ktoacutere funkcja deklaruje do ldquowłasnych potrzebrdquo nazywamy zmiennymi lokal-nymi Nasuwa się pytanie ldquoczy będzie błędem nazwanie tą samą nazwą zmiennej globalneji lokalnejrdquo Otoacuteż odpowiedź może być zaskakująca nie Natomiast w danej funkcji da sięużywać tylko jej zmiennej lokalnej Tej konstrukcji należy z wiadomych względoacutew unikać

int a=1 zmienna globalna

int main()

71 CZYM SĄ ZMIENNE 35

int a=2 to już zmienna lokalna printf(d a) wypisze 2

713 Czas życia

Czas życia to czas od momentu przydzielenia dla zmiennej miejsca w pamięci (stworzenieobiektu) do momentu zwolnienia miejsca w pamięci (likwidacja obiektu)

Zakres ważności to część programu w ktoacuterej nazwa znana jest kompilatorowi

main()

int a = 10 otwarcie lokalnego bloku

int b = 10printf(d d a b)

zamknięcie lokalnego bloku zmienna b jest usuwana

printf(d d a b) BŁĄD b juz nie istnieje tu usuwana jest zmienna a

Zdefiniowaliśmy dwie zmienne typu int Zaroacutewno a i b istnieją przez cały program (czasżycia) Nazwa zmiennej a jest znana kompilatorowi przez cały program Nazwa zmiennej bjest znana tylko w lokalnym bloku dlatego nastąpi błąd w ostatniej instrukcji

Niektoacutere kompilatory (prawdopodobniemożna tu zaliczyćMicrosoVisual C++ dowersji) uznają powyższy kod za poprawny W dodatku można ustawić w opcjach niektoacuterychkompilatoroacutew zachowanie w takiej sytuacji włącznie z zachowaniami niezgodnymi ze stan-dardem języka

Możemy świadomie ograniczyć ważność zmiennej do kilku linijek programu (tak jak ro-biliśmy wyżej) tworząc blok Nazwa zmiennej jest znana tylko w tym bloku

714 Stałe

Stała roacuteżni się od zmiennej tylko tym że nie można jej przypisać innej wartości w trak-cie działania programu Wartość stałej ustala się w kodzie programu i nigdy ona nie ulegazmianie Stałą deklaruje się z użyciem słowa kluczowego const w sposoacuteb następujący

const typ nazwa_stałej=wartość

Dobrze jest używać stałych w programie ponieważ unikniemy wtedy przypadkowychpomyłek a kompilator może często zoptymalizować ich użycie (np od razu podstawiając ichwartość do kodu)

36 ROZDZIAŁ 7 ZMIENNE

const int WARTOSC_POCZATKOWA=5int i=WARTOSC_POCZATKOWAWARTOSC_POCZATKOWA=4 tu kompilator zaprotestuje int j=WARTOSC_POCZATKOWA

Przykład pokazuje dobry zwyczaj programistyczny jakim jest zastępowanie umieszczo-nych na stałe w kodzie liczb stałymi W ten sposoacuteb będziemy mieli większą kontrolę nadkodem mdash stałe umieszczone w jednym miejscu można łatwo modyfikować zamiast szukaćpo całym kodzie liczb ktoacutere chcemy zmienić

Nie mamy jednak pełnej gwarancji że stała będzie miała tę samą wartość przez cały czaswykonania programu możliwe jest bowiem dostanie się do wartości stałej (miejsca jej prze-chowywania w pamięci) pośrednio mdash za pomocą wskaźnikoacutew Można zatem dojść do wnio-sku że słowo kluczowe const służy tylko do poinformowania kompilatora aby ten nie zezwa-lał na jawną zmianę wartości stałej Z drugiej strony zgodnie ze standardem proacuteba mody-fikacji wartości stałej ma niezdefiniowane działanie (tzw undefined behaviour) i w związkuz tym może się powieść lub nie ale może też spowodować jakieś subtelne zmiany ktoacutere wefekcie spowodują że program będzie źle działał

Podobnie do zdefiniowania stałej możemy użyć dyrektywy preprocesora define (opi-sanej w dalszej części podręcznika) Tak zdefiniowaną stałą nazywamy stałą symbolicznąW przeciwieństwie do stałej zadeklarowanej z użyciem słowa const stała zdefiniowana przyużyciu define jest zastępowana daną wartością w każdym miejscu gdzie występuje dlategoteż może być używana w miejscach gdzie ldquonormalnardquo stała nie mogłaby dobrze spełnić swejroli

W przeciwieństwie do języka C++ w C stała to cały czas zmienna ktoacuterej kompilatorpilnuje by nie zmieniła się

72 Typy zmienny

Każdy program w C operuje na zmiennych mdash wydzielonych w pamięci komputera obsza-rach ktoacutere mogą reprezentować obiekty nam znane takie jak liczby znaki czy też bardziejzłożone obiekty Jednak dla komputera każdy obszar w pamięci jest taki sam mdash to ciąg zeri jedynek w takiej postaci zupełnie nieprzydatny dla programisty i użytkownika Podczaspisania programu musimy wskazać w jaki sposoacuteb ten ciąg ma być interpretowany

Typ zmiennej wskazuje właśnie sposoacuteb w jaki pamięć w ktoacuterej znajduje się zmiennabędzie wykorzystywana Określając go przekazuje się kompilatorowi informację ile pamięcitrzeba zarezerwować dla zmiennej a także w jaki sposoacuteb wykonywać na nim operacje

Każda zmienna musi mieć określony swoacutej typ w miejscu deklaracji i tego typu nie możejuż zmienić Lecz co jeśli mamy zmienną jednego typu ale potrzebujemy w pewnymmiejscuprogramu innego typu danych W takimwypadku stosujemy konwersję (rzutowanie) jednejzmiennej na inną zmienną Rzutowanie zostanie opisane poacuteźniej w rozdziale Operatory

Istnieją wbudowane i zdefiniowane przez użytkownika typy danych Wbudowane typydanych to te ktoacutere zna kompilator są one w nim bezpośrednio ldquozaszyterdquo Można też tworzyćwłasne typy danych ale należy je kompilatorowi opisać Więcej informacji znajduje się wrozdziale Typy złożone

W języku C wyroacuteżniamy podstawowe typy zmiennych Są to

char mdash jednobajtowe liczby całkowite służy do przechowywania znakoacutew

int mdash typ całkowity o długości domyślnej dla danej architektury komputera

72 TYPY ZMIENNYCH 37

float mdash typ zmiennopozycyjny (zwany roacutewnież zmiennoprzecinkowym) reprezentującyliczby rzeczywiste ( bajty)

double mdash typ zmiennopozycyjny podwoacutejnej precyzji ( bajtoacutew)

Typy zmiennoprzecinkowe zostały dokładnie opisane w IEEE

W języku C nie istnieje specjalny typ zmiennych przeznaczony na zmienne typu logicz-nego (albo ldquoprawda albo ldquofałszrdquo) Jest to inne podejście niż na przykład w językach Pascalalbo Java - definiujących osobny typ ldquobooleanrdquo ktoacuterego nie można ldquomieszaćz innymi typamizmiennych W C do przechowywania wartości logicznych zazwyczaj używa się typu ldquointrdquoWięcej na temat tego jak język C rozumie prawdę i fałsz znajduje się w rozdziale Operatory

721 int

Ten typ przeznaczony jest do liczb całkowitych Liczby temożemy zapisać na kilka sposoboacutew

System dziesiętny

12 13 45 35 itd

System oacutesemkowy (oktalny)

010 czyli 8016 czyli 8 + 6 = 14018 BŁĄD

System ten operuje na cyfrach od do Tak wiec jest niedozwolona Jeżeli chcemyużyć takiego zapisu musimy zacząć liczbę od

System szesnastkowy (heksadecymalny)

0x10 czyli 116 + 0 = 160x12 czyli 116 + 2 = 180xff czyli 1516 + 15 = 255

W tym systemie możliwe cyfry to hellip i dodatkowo a b c d e f ktoacutere oznaczają Aby użyć takiego systemu musimy poprzedzić liczbę ciągiem 0x Wielkośćznakoacutew w takich literałach nie ma znaczenia

Ponadto w niektoacuterych kompilatorach przeznaczonych głoacutewnie domikrokontroleroacutew spo-tyka się jeszcze użycie systemu binarnego Zazwyczaj dodaje się przedrostek 0b przed liczbą(analogicznie do zapisu spotykanego w języku Python) W tym systemie możemy oczywiścieużywać tylko i wyłącznie cyfr i Tego typu rozszerzenie bardzo ułatwia programowanieniskopoziomowe układoacutew Należy jednak pamiętać że jest to tylko i wyłącznie rozszerzenie

38 ROZDZIAŁ 7 ZMIENNE

722 float

Ten typ oznacza liczby zmiennoprzecinkowe czyli ułamki Istnieją dwa sposoby zapisu

System dziesiętny

314 45644 2354 321 itd

System ldquonaukowyrdquo mdash wykładniczy

6e2 czyli 6 102 czyli 60015e3 czyli 15 103 czyli 150034e-3 czyli 34 10minus3 czyli 00034

Należy wziąć pod uwagę że reprezentacja liczb rzeczywistych w komputerze jest niedo-skonała i możemy otrzymywać wyniki o zauważalnej niedokładności

723 double

Doublemdash czyli ldquopodwoacutejnyrdquomdash oznacza liczby zmiennoprzecinkowe podwoacutejnej precyzji Ozna-cza to że liczba taka zajmuje zazwyczaj w pamięci dwa razy więcej miejsca niż float (np bity wobec dla float) ale ma też dwa razy lepszą dokładność

Domyślnie ułamki wpisane w kodzie są typu double Możemy to zmienić dodając nakońcu literę ldquordquo

15f (float)15 (double)

724 ar

Jest to typ znakowy umożliwiający zapis znakoacutew ASCII Może też być traktowany jako liczbaz zakresu Znaki zapisujemywpojedynczych cudzysłowach (czasami nazywanymi apo-strofami) by odroacuteżnić je od łańcuchoacutew tekstowych (pisanych w podwoacutejnych cudzysłowach)

a 7 $

Pojedynczy cudzysłoacutew rsquo zapisujemy tak a null (czyli zero ktoacutere między innymikończy napisy) tak 0 Więcej znakoacutew specjalnych

Warto zauważyć że typ char to zwykły typ liczbowy i można go używać tak samo jaktypu int (zazwyczaj ma jednak mniejszy zakres) Co więcej literały znakowe (np rsquoarsquo) sątraktowane jako liczby i w języku C są typu int (w języku C++ są typu char)

725 void

Słowa kluczowego void można w określonych sytuacjach użyć tam gdzie oczekiwana jestnazwa typu void nie jest właściwym typem bo nie można utworzyć zmiennej takiego typujest to ldquopustyrdquo typ (ang void znaczy ldquopustyrdquo) Typ void przydaje się do zaznaczania żefunkcja nie zwraca żadnej wartości lub że nie przyjmuje żadnych parametroacutew (więcej o tymw rozdziale Funkcje) Można też tworzyć zmienne będące typu ldquowskaźnik na voidrdquo

73 SPECYFIKATORY 39

73 Specyfikatory

Specyfikatory to słowa kluczowe ktoacutere postawione przy typie danych zmieniają jego zna-czenie

731 signed i unsigned

Na początku zastanoacutewmy się jak komputer może przechować liczbę ujemną Otoacuteż w przy-padku przechowywania liczb ujemnych musimyw zmiennej przechować jeszcze jej znak Jakwiadomo zmienna składa się z szeregu bitoacutew W przypadku użycia zmiennej pierwszy bit zlewej strony (nazywany także bitem najbardziej znaczącym) przechowuje znak liczby Efek-tem tego jest spadek ldquopojemnościrdquo zmiennej czyli zmniejszenie największej wartości ktoacuterąmożemy przechować w zmiennej

Signed oznacza liczbę ze znakiem unsigned mdash bez znaku (nieujemną) Mogą być zasto-sowane do typoacutew char i int i łączone ze specyfikatorami short i long (gdy ma to sens)

Jeśli przy signed lub unsigned nie napiszemy o jaki typ nam chodzi kompilator przyjmiewartość domyślną czyli int

Przykładowo dla zmiennej char(zajmującej bitoacutew zapisanej w formacie uzupełnień dodwoacutech) wygląda to tak

signed char a zmienna a przyjmuje wartości od -128 do 127 unsigned char b zmienna b przyjmuje wartości od 0 do 255 unsigned short cunsigned long int d

Jeżeli nie podamy żadnego ze specyfikatora wtedy liczba jest domyślnie przyjmowanajako signed (nie dotyczy to typu char dla ktoacuterego jest to zależne od kompilatora)

signed int i = 0 jest roacutewnoznaczne zint i = 0

Liczby bez znaku pozwalają nam zapisać większe liczby przy tej samej wielkości zmiennejmdash ale trzeba uważać by nie zejść z nimi poniżej zera mdash wtedy ldquoprzewijająrdquo się na sam konieczakresu co może powodować trudne do wykrycia błędy w programach

732 short i long

Short i long są wskazoacutewkami dla kompilatora by zarezerwował dla danego typu mniej (od-powiednio mdash więcej) pamięci Mogą być zastosowane do dwoacutech typoacutew int i double (tylkolong) mając roacuteżne znaczenie

Jeśli przy short lub long nie napiszemy o jaki typ nam chodzi kompilator przyjmie war-tość domyślną czyli int

Należy pamiętać że to jedynie życzenie wobec kompilatora mdash w wielu kompilatorachtypy int i long int mają ten sam rozmiar Standard języka C nakłada jedynie na kompilatorynastępujące ograniczenia int mdash nie może być kroacutetszy niż bitoacutew int mdash musi byćdłuższy lub roacutewny short a nie może być dłuższy niż long short int mdash nie może byćkroacutetszy niż bitoacutew long int mdash nie może być kroacutetszy niż bity

Zazwyczaj typ int jest typem danych o długości odpowiadającej wielkości rejestroacutew pro-cesora czyli na procesorze szesnastobitowym ma bitoacutew na trzydziestodwubitowym mdash

40 ROZDZIAŁ 7 ZMIENNE

itd1 Z tego powodu jeśli to tylko możliwe do reprezentacji liczb całkowitych preferowanejest użycie typu int bez żadnych specyfikatoroacutew rozmiaru

74 Modyfikatory

741 volatile

volatile znaczy ulotny Oznacza to że kompilator wyłączy dla takiej zmiennej optymaliza-cje typu zastąpienia przez stałą lub zawartość rejestru za to wygeneruje kod ktoacutery będzieodwoływał się zawsze do komoacuterek pamięci danego obiektu Zapobiegnie to błędowi gdyobiekt zostaje zmieniony przez część programu ktoacutera nie ma zauważalnego dla kompilatorazwiązku z danym fragmentem kodu lub nawet przez zupełnie inny proces

volatile float liczba1float liczba2

printf (fnfn liczba1 liczba2) instrukcje nie związane ze zmiennymi printf (fnf liczba1 liczba2)

Jeżeli zmienne liczba i liczba zmienią się niezauważalnie dla kompilatora to odczytując

liczba mdash nastąpi odwołanie do komoacuterek pamięci Kompilator pobierze nową wartośćzmiennej

liczba mdash kompilator może wypisać poprzednią wartość ktoacuterą przechowywał w reje-strze

Modyfikator volatile jest rzadko stosowany i przydaje się w wąskich zastosowaniachjak wspoacutełbieżność i wspoacutełdzielenie zasoboacutew oraz przerwania systemowe

742 register

Jeżeli utworzymy zmienną ktoacuterej będziemy używać w swoim programie bardzo często mo-żemy wykorzystać modyfikator register Kompilator może wtedy umieścić zmienną w re-jestrze do ktoacuterego ma szybki dostęp co przyśpieszy odwołania do tej zmiennej

register int liczba

W nowoczesnych kompilatorach ten modyfikator praktycznie nie ma wpływu na pro-gram Optymalizator sam decyduje czy i co należy umieścić w rejestrze Nie mamy żadnejgwarancji że zmienna tak zadeklarowana rzeczywiście się tam znajdzie chociaż dostęp doniej może zostać przyspieszony w inny sposoacuteb Raczej powinno się unikać tego typu kon-strukcji w programie

1Wiąże się to z pewnymi uwarunkowaniami historycznymi Podręcznik do języka C duetu KampR zakładał żetyp int miał się odnosić do typowej dla danego procesora długości liczby całkowitej Natomiast jeśli procesor moacutegłobsługiwać typy dłuższe lub kroacutetsze stosownego znaczenia nabierałymodyfikatory short i long Dobrymprzykłademmoże być architektura i386 ktoacutera umożliwia obliczenia na liczbach 16-bitowych Dlatego też modyfikator shortpowoduje skroacutecenie zmiennej do 16 bitoacutew

75 UWAGI 41

743 static

Pozwala na zdefiniowanie zmiennej statycznej ldquoStatycznośćrdquo polega na zachowaniu warto-ści pomiędzy kolejnymi definicjami tej samej zmiennej Jest to przede wszystkim przydatnew funkcjach Gdy zdefiniujemy zmienną w ciele funkcji to zmienna ta będzie od nowa defi-niowana wraz z domyślną wartością (jeżeli taką podano) W wypadku zmiennej określonejjako statyczna jej wartość się nie zmieni przy ponownym wywołaniu funkcji Na przykład

void dodaj(int liczba)

int zmienna = 0 bez staticzmienna = zmienna + liczbaprintf (Wartosc zmiennej dn zmienna)

Gdy wywołamy tę funkcję np razy w ten sposoacuteb

dodaj(3)dodaj(5)dodaj(4)

to ujrzymy na ekranie

Wartosc zmiennej 3Wartosc zmiennej 5Wartosc zmiennej 4

jeżeli jednak deklarację zmiennej zmienimy na static int zmienna = 0 to wartość zmiennejzostanie zachowana i po podobnym wykonaniu funkcji powinnyśmy ujrzeć

Wartosc zmiennej 3Wartosc zmiennej 8Wartosc zmiennej 12

Zupełnie co innego oznacza static zastosowane dla zmiennej globalnej Jest ona wtedywidoczna tylko w jednym pliku Zobacz też rozdział Biblioteki

744 extern

Przez extern oznacza się zmienne globalne zadeklarowane w innych plikach mdash informujemyw ten sposoacuteb kompilator żeby nie szukał jej w aktualnym pliku Zobacz też rozdział Biblio-teki

745 auto

Zupełnym archaizmem jest modyfikator auto ktoacutery oznacza tyle że zmienna jest lokalnaPonieważ zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna modyfikator tennie ma obecnie żadnego zastosowania praktycznego auto jest spadkiem po wcześniejszychjęzykach programowania na ktoacuterych oparty jest C (np B)

75 Uwagi Język C++ pozwala na mieszanie deklaracji zmiennych z kodem Więcej informacji w

C++Zmienne

42 ROZDZIAŁ 7 ZMIENNE

Rozdział 8

Operatory

81 Przypisanie

Operator przypisania (=rdquo) jak sama nazwa wskazuje przypisuje wartość prawego argu-mentu lewemu np

int a = 5 bb = aprintf(dn b) wypisze 5

Operator ten ma łączność prawostronną tzn obliczanie przypisań następuje z prawa nalewo i zwraca on przypisaną wartość dzięki czemu może być użyty kaskadowo

int a b ca = b = c = 3printf(d d dn a b c) wypisze 3 3 3

811 Skroacutecony zapis

C umożliwia też skroacutecony zapis postaci a = b gdzie jest jednym z operatoroacutew + - amp | ˆ ltlt lub gtgt (opisanych niżej) Ogoacutelnie rzecz ujmując zapis a = b jest roacutewnoważnyzapisowi a = a (b) np

int a = 1a += 5 to samo co a = a + 5 a = a + 2 to samo co a = a (a + 2) a = 2 to samo co a = a 2

Początkowo skroacutecona notacja miała następującą składnię a = b co często prowadziło doniejasności np i =- (i = - czy też i = i-) Dlatego też zdecydowano się zmienić kolejnośćoperatoroacutew

43

44 ROZDZIAŁ 8 OPERATORY

82 Rzutowanie

Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu Konwer-sja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podanaexplicite przez programistę) Oto kilka przykładoacutew konwersji niejawnej

int i = 427 konwersja z double do int float f = i konwersja z int do float double d = f konwersja z float do double unsigned u = i konwersja z int do unsigned int f = 42 konwersja z double do float i = d konwersja z double do int char str = foo konwersja z const char do char [1] const char cstr = str konwersja z char do const char void ptr = str konwersja z char do void

Podczas konwersji zmiennych zawierających większe ilości danych do typoacutew prostszych(np double do int) musimy liczyć się z utratą informacji jak to miało miejsce w pierwszejlinijce mdash zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta iw rezultacie zmiennej została przypisana wartość

Zaskakująca może się wydać linijka oznaczona przez 1 Niejawna konwersja z typu constchar do typu char nie jest dopuszczana przez standard C Jednak literały napisowe (ktoacutere sątypu const char) stanowią tutaj wyjątek Wynika on z faktu że były one używane na długoprzed wprowadzeniem słoacutewka const do języka i brak wspomnianegowyjątku spowodowałbyże duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod

Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania np

double d = 314int pi = (int)d 1 pi = (unsigned)pi gtgt 4 2

W pierwszym przypadku operator został użyty by zwroacutecić uwagę na utratę precyzji Wdrugim dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej

Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne kon-wersje (tj konwersja z double do int oraz z int do unsigned int) jednak niektoacutere konwersjesą błędne np

const char cstr = foochar str = cstr

W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję

const char cstr = foochar str = (char)cstr

Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompi-lator Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzieon robił i czy nie ma innego sposobu wykonania danej operacji ktoacutery nie wymagałby podej-mowania tak drastycznych krokoacutew

83 OPERATORY ARYTMETYCZNE 45

83 Operatory arytmetyczne

W arytmetyce komputerowej nie działa prawo łączności oraz rozdzielności Wynika toz ograniczonego rozmiaru zmiennych ktoacutere przechowują wartości Przykład dla zmiennycho długości bitoacutew (bez znaku) Maksymalna wartość ktoacuterą może przechowywać typ to216minus1 = 65535 Zatem operacja typu 65530+10minus20 zapisana jako (65530+10)minus20 możezaowocować czymś zupełnie innym niż 65530+(10minus20) W pierwszym przypadku zapewnedojdzie do tzw przepełnienia - procesor nie będzie miał miejsca aby zapisać dodatkowybit Zachowanie programu będzie w takim przypadku zależało od architektury procesoraAnalogiczny przykład możemy podać dla rozdzielności mnożenia względem dodawania

Język C definiuje następujące dwuargumentowe operatory arytmetyczne

dodawanie (+rdquo)

odejmowanie (-rdquo)

mnożenie (rdquo)

dzielenie (rdquo)

reszta z dzielenia (rdquo) określona tylko dla liczb całkowitych (tzw dzielenie modulo)

int a=7 b=2 cc = a bprintf (dnc) wypisze 1

Należy pamiętać że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak naj-większy z argumentoacutew Oznacza to że operacja wykonana na dwoacutech liczbach całkowitychnadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej Dla przy-kładu poniższy kod

float a = 7 2printf(fn a)

wypisze (wbrew oczekiwaniu początkujących programistoacutew) 30 a nie 35 Odnosi sięto nie tylko do dzielenia ale także mnożenia np

float a = 1000 1000 1000 1000 1000 1000printf(fn a)

prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali Aby wymusićobliczenia rzeczywiste należy zmienić typ jednego z argumentoacutew na liczbę rzeczywistą poprostu zmieniając literał lub korzystając z rzutowania np

float a = 70 2float b = (float)1000 1000 1000 1000 1000 1000printf(fn a)printf(fn b)

Operatory dodawania i odejmowania są określone roacutewnież gdy jednym z argumentoacutewjest wskaźnik a drugim liczba całkowita Ten drugi jest także określony gdy oba argumentysą wskaźnikami O takim użyciu tych operatoroacutew dowiesz się więcej CWskaźniki|w dalszejczęści książki

46 ROZDZIAŁ 8 OPERATORY

831 Inkrementacja i dekrementacja

Aby skroacutecić zapis wprowadzono dodatkowe operatory inkrementacji (++rdquo) i dekrementa-cji (ndashrdquo) ktoacutere dodatkowo mogą być pre- lub postfiksowe W rezultacie mamy więc czteryoperatory

pre-inkrementacja (++irdquo)

post-inkrementacja (i++rdquo)

pre-dekrementacja (ndashirdquo) i

post-dekrementacja (indashrdquo)

Operatory inkrementacji zwiększa a dekrementacji zmniejsza argument o jeden Ponadtooperatory pre- zwracają nową wartość argumentu natomiast post- starą wartość argumentu

int a b ca = 3b = a-- po operacji b=3 a=2 c = --b po operacji b=2 c=2

Czasami (szczegoacutelnie w C++) użycie operatoroacutew stawianych za argumentem jest niecomniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tym-czasową)

Bardzo ważne jest abyśmy poprawnie stosowali operatory dekrementacji i inkrementa-cji Chodzi o to aby w jednej instrukcji nie umieszczać kilku operatoroacutew ktoacutere modyfikująten sam obiekt (zmienną) Jeżeli taka sytuacja zaistnieje to efekt działania instrukcji jestnieokreślony Prostym przykładem mogą być następujące instrukcje

int a = 1a = a++a = ++aa = a++ + ++aprintf(d dn ++a ++a)printf(d dn a++ a++)

Kompilator potrafi ostrzegać przed takimi błędami - aby to czynił należy podać mujako argument opcję -Wsequence-point

84 Operacje bitoweOproacutecz operacji znanych z lekcji matematyki w podstawoacutewce język C został wyposażonytakże w operatory bitowe zdefiniowane dla liczb całkowitych Są to

negacja bitowa (˜rdquo)

koniunkcja bitowa (amprdquo)

alternatywa bitowa (|rdquo) i

84 OPERACJE BITOWE 47

alternatywa rozłączna () (ˆrdquo)

Działają one na poszczegoacutelnych bitach przez co mogą być szybsze od innych operacjiDziałanie tych operatoroacutew można zdefiniować za pomocą poniższych tabel

~ | 0 1 amp | 0 1 | | 0 1 ^ | 0 1-----+----- -----+----- -----+----- -----+-----

| 1 0 0 | 0 0 0 | 0 1 0 | 0 11 | 0 1 1 | 1 1 1 | 1 0

a | 0101 = 5b | 0011 = 3

-------+------~a | 1010 = 10~b | 1100 = 12

a amp b | 0001 = 1a | b | 0111 = 7a ^ b | 0110 = 6

Lub bardziej opisowo

negacja bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych argument miał bity roacutewne zero

koniunkcja bitowa daje wwyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych oba argumenty miały bity roacutewne jeden (mnemonik gdy wszystkie)

alternatywa bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden na wszystkichtych pozycjach na ktoacuterych jeden z argumentoacutew miał bit roacutewny jeden (mnemonik jeśli jest )

alternatywa rozłączna daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tychpozycjach na ktoacuterych tylko jeden z argumentoacutew miał bit roacutewny jeden (mnemonik gdy roacuteżne)

Przy okazji warto zauważyć że aˆbˆb to po prostu a Właściwość ta została wykorzystanaw roacuteżnych algorytmach szyfrowania oraz funkcjach haszujących Alternatywę wyłączną sto-suje się np do szyfrowania kodu wirusoacutew polimorficznych

841 Przesunięcie bitowe

Dodatkowo język C wyposażony jest w operatory przesunięcia bitowego w lewo (ltltrdquo) iprawo (gtgtrdquo) Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycjipodaną jako prawy argument Brzmi to może strasznie ale wcale takie nie jest Rozważmy-bitowe liczby bez znaku (taki hipotetyczny unsigned int) woacutewczas

a | altlt1 | altlt2 | agtgt1 | agtgt2------+------+------+------+------0001 | 0010 | 0100 | 0000 | 00000011 | 0110 | 1100 | 0001 | 00000101 | 1010 | 0100 | 0010 | 0001

48 ROZDZIAŁ 8 OPERATORY

1000 | 0000 | 0000 | 0100 | 00101111 | 1110 | 1100 | 0111 | 00111001 | 0010 | 0100 | 0100 | 0010

Nie jest to zatem takie straszne na jakie wygląda Widać że bity będące na skraju sątracone a w pusterdquo miejsca wpisywane są zera

Inaczej rzecz się ma jeżeli lewy argument jest liczbą ze znakiem Dla przesunięcia bito-wego w lewo a ltlt b jeżeli a jest nieujemna i wartość a middot 2b mieści się w zakresie liczby tojest to wynikiem operacji W przeciwnym wypadku działanie jest niezdefiniowane1

Dla przesunięcia bitowego w lewo jeżeli lewy argument jest nieujemny to operacja za-chowuje się tak jak w przypadku liczb bez znaku Jeżeli jest on ujemny to zachowanie jestzależne od implementacji

Zazwyczaj operacja przesunięcia w lewo zachowuje się tak samo jak dla liczb bez znakunatomiast przy przesuwaniu w prawo bit znaku nie zmienia się2

a | agtgt1 | agtgt2------+------+------0001 | 0000 | 00000011 | 0001 | 00000101 | 0010 | 00011000 | 1100 | 11101111 | 1111 | 11111001 | 1100 | 1110

Przesunięcie bitowe w lewo odpowiada pomnożeniu natomiast przesunięcie bitowe wprawo podzieleniu liczby przez dwa do potęgi jaką wyznacza prawy argument Jeżeli prawyargument jest ujemny lub większy lub roacutewny liczbie bitoacutew w typie działanie jest niezdefi-niowane

include ltstdiohgt

int main ()

int a = 6printf (6 ltlt 2 = dn altlt2) wypisze 24 printf (6 gtgt 2 = dn agtgt2) wypisze 1 return 0

85 PoroacutewnanieW języku C występują następujące operatory poroacutewnania

roacutewne (==rdquo)

roacuteżne (=rdquo)

mniejsze (ltrdquo)

1Niezdefiniowane w takim samym sensie jak niezdefiniowane jest zachowanie programu gdy proacutebujemy odwo-łać się do wartości wskazywanej przez wartość czy do zmiennych poza tablicą

2ale jeżeli zależy Ci na przenośności kodu nie możesz na tym polegać

85 POROacuteWNANIE 49

większe (gtrdquo)

mniejsze lub roacutewne (lt=rdquo) i

większe lub roacutewne (gt=rdquo)

Wykonują one odpowiednie poroacutewnanie swoich argumentoacutew i zwracają jedynkę jeżeliwarunek jest spełniony lub zero jeżeli nie jest

851 Częste błędy

Osoby ktoacutere poprzednio uczyły się innych językoacutew programowania często mają nawykużywania w instrukcjach logicznych zamiast operatora poroacutewnania == operatora przypi-sania = Ma to często zgubne efekty gdyż przypisanie zwraca wartość przypisaną lewemuargumentowi

Poroacutewnajmy ze sobą dwa warunki

(a = 1)(a == 1)

Pierwszy z nich zawsze będzie prawdziwy niezależnie od wartości zmiennej a Dziejesię tak ponieważ zostaje wykonane przypisanie do a wartości a następnie jako wartość jestzwracane to co zostało przypisane mdash czyli jeden Drugi natomiast będzie prawdziwy tylkogdy a jest roacutewne

W celu uniknięcia takich błędoacutew niektoacuterzy programiści zamiast pisać a == 1 piszą 1 == adzięki czemu pomyłka spowoduje że kompilator zgłosi błąd

Warto zauważyć że kompilator potrafi w pewnych sytuacjach wychwycić taki błądAby zaczął to robić należy podać mu argument -Wparentheses

Innym błędem jest użycie zwykłych operatoroacutew poroacutewnania do sprawdzania relacji po-między liczbami rzeczywistymi Ponieważ operacje zmiennoprzecinkowe wykonywane są zpewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie roacutewne Dlaprzykładu

include ltstdiohgtint main ()

float a b ca = 1e10 tj 10 do potęgi 10 b = 1e-10 tj 10 do potęgi -10 c = b c = b c = c + a c = b + a (teoretycznie) c = c - a c = b + a - a = b (teoretycznie) printf(dn c == b) wypisze 0

Obejściem jest poroacutewnywanie modułu roacuteżnicy liczb Roacutewnież i takie błędy kompilator potrafi wykrywać mdash aby to robił należy podać mu argument -Wfloat-equal

50 ROZDZIAŁ 8 OPERATORY

86 Operatory logiczneAnalogicznie do części operatoroacutew bitowych w C definiuje się operatory logiczne miano-wicie

negację (zaprzeczenie)

koniunkcję (ldquoirdquo) ampamp

alternatywę (ldquolubrdquo) ||

Działają one bardzo podobnie do operatoroacutew bitowych jednak zamiast operować na po-szczegoacutelnych bitach biorą pod uwagę wartość logiczną argumentoacutew

861 ldquoPrawdardquo i ldquofałszrdquo w języku C

Język C nie przewiduje specjalnego typu danych do operacji logicznych mdash operatory logicznemożna stosować do liczb (np typu int) tak samo jak operatory bitowe albo arytmetyczne

Wyrażenie ma wartość logiczną wtedy i tylko wtedy gdy jest roacutewne (jest ldquofałszywerdquo)W przeciwnym wypadku ma wartość (jest ldquoprawdziwerdquo) Operatory logiczne w wynikudają zawsze albo albo

Żeby w pełni uzmysłowić sobie co to to oznacza spoacutejrzmy na wynik wykonania poniż-szych trzech linijek

printf(koniunkcja dn 18 ampamp 19)printf(alternatywa dn a || b)printf(negacja dn 20)

koniunkcja 1alternatywa 1negacja 0

Liczba nie jest roacutewna więc ma wartość logiczną Podobnie ma wartość logiczną Dlatego ich koniunkcja jest roacutewna Znaki a i b zostaną w wyrażeniu logicznympotraktowane jako liczby o wartości odpowiadającej kodowi znaku mdash czyli oba będąmiały wartość logiczną

862 Skroacutecone obliczanie wyrażeń logiczny

Język C wykonuje skroacutecone obliczanie wyrażeń logicznych mdash to znaczy oblicza wyrażenietylko tak długo jak nie wie jaka będzie jego ostateczna wartość To znaczy idzie od lewejdo prawej obliczając kolejne wyrażenia (dodatkowo na kolejność wpływ mają nawiasy) i gdybędzie miał na tyle informacji by obliczyć wartość całości nie liczy reszty Może to wydawaćsię niejasne ale przyjrzyjmy się wyrażeniom logicznym

A ampamp BA || B

Jeśli A jest fałszywe to nie trzeba liczyć B w pierwszym wyrażeniu bo fałsz i dowolne wyra-żenie zawsze da fałsz Analogicznie jeśli A jest prawdziwe to wyrażenie jest prawdziwe iwartość B nie ma znaczenia

Poza zwiększoną szybkością zysk z takiego rozwiązania polega na możliwości stosowaniaefektoacutew ubocznych Idea efektu ubocznego opiera się na tym że w wyrażeniu można wywo-łać funkcje ktoacutere będą robiły poza zwracaniemwyniku inne rzeczy oraz używać podstawieńPopatrzmy na poniższy przykład

87 OPERATOR WYRAŻENIA WARUNKOWEGO 51

( (a gt 0) || (a lt 0) || (a = 1) )

Jeśli a będzie większe od to obliczona zostanie tylko wartość wyrażenia (a gt 0) mdash da onoprawdę czyli reszta obliczeń nie będzie potrzebna Jeśli a będzie mniejsze od zera najpierwzostanie obliczone pierwsze podwyrażenie a następnie drugie ktoacutere da prawdę Ciekawy bę-dzie jednak przypadek gdy a będzie roacutewne zero mdash do a zostanie wtedy podstawiona jedynkai całość wyrażenia zwroacuteci prawdę (bo jest traktowane jak prawda)

Efekty uboczne pozwalają na roacuteżne szaleństwa i wykonywanie złożonych operacji w sa-mych warunkach logicznych jednak przesadne używanie tego typu konstrukcji powodujeże kod staje się nieczytelny i jest uważane za zły styl programistyczny

87 Operator wyrażenia warunkowegoC posiada szczegoacutelny rodzaj operatora mdash to operator zwany też operatorem wyrażeniawarunkowego Jest to jedyny operator w tym języku przyjmujący trzy argumenty

a b c

Jego działanie wygląda następująco najpierw oceniana jest wartość logiczna wyrażenia ajeśli jest ono prawdziwe to zwracana jest wartość b jeśli natomiast wyrażenie a jest nie-prawdziwe zwracana jest wartość c

Praktyczne zastosowanie mdash znajdowanie większej z dwoacutech liczb

a = (bgt=c) b c Jeśli b jest większe bądź roacutewne c to zwroacuteć bW przeciwnym wypadku zwroacuteć c

lub zwracanie modułu liczby

a = a lt 0 -a a

Wartości wyrażeń są przy tym operatorze obliczane tylko jeżeli zachodzi taka potrzebanp w wyrażeniu 1 1 foo() funkcja foo() nie zostanie wywołana

88 Operator przecinekOperator przecinek jest dość dziwnym operatorem Powoduje on obliczanie wartości wyra-żeń od lewej do prawej po czym zwroacutecenie wartości ostatniego wyrażenia W zasadzie wnormalnym kodzie programu ma on niewielkie zastosowanie gdyż zamiast niego lepiej roz-dzielać instrukcje zwykłymi średnikami Ma on jednak zastosowanie w instrukcji sterującejfor

89 Operator sizeofOperator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanegotypu lub typu podanego wyrażenia Ma on dwa rodzaje sizeof(typ) lub sizeof wyrażeniePrzykładowo

include ltstdiohgt

int main()

52 ROZDZIAŁ 8 OPERATORY

printf(sizeof(short ) = dn sizeof(short ))printf(sizeof(int ) = dn sizeof(int ))printf(sizeof(long ) = dn sizeof(long ))printf(sizeof(float ) = dn sizeof(float ))printf(sizeof(double) = dn sizeof(double))return 0

Operator ten jest często wykorzystywany przy dynamicznej alokacji pamięci co zostanieopisane w rozdziale poświęconym wskaźnikom

Pomimo że w swej budowie operator sizeof bardzo przypomina funkcję to jednak niąnie jest Wynika to z trudności w implementacji takowej funkcji mdash jej specyfika musiałabyodnosić się bezpośrednio do kompilatora Ponadto jej argumentem musiałyby być typy anie zmienne W języku C nie jest możliwe przekazywanie typu jako argumentu Ponadtoczęsto zdarza się że rozmiar zmiennej musi być wiadomy jeszcze w czasie kompilacji mdash toewidentnie wyklucza implementację sizeof() jako funkcji

810 Inne operatory

Poza wyżej opisanymi operatorami istnieją jeszcze

operator []rdquo opisany przy okazji opisywania tablic

jednoargumentowe operatory rdquo i amprdquo opisane przy okazji opisywania wskaźnikoacutew

operatory rdquo i -gtrdquo opisywane przy okazji opisywania struktur i unii

operator ()rdquo będący operatorem wywołania funkcji

operator ()rdquo grupujący wyrażenia (np w celu zmiany kolejności obliczania

811 Priorytety i kolejność obliczeń

Jak w matematyce roacutewnież i w języku C obowiązuje pewna ustalona kolejność działań Abymoacutec ją określić należy ustalić dwa parametry danego operatora jego priorytet oraz łącz-ność Przykładowo operator mnożenia ma wyższy priorytet niż operator dodawania i z tegopowodu wwyrażeniu 2+2 middot2 najpierw wykonuje się mnożenie a dopiero potem dodawanie

Drugim parametrem jest łączność mdash określa ona od ktoacuterej stronywykonywane są działaniaw przypadku połączenia operatoroacutew o tym samym priorytecie Na przykład odejmowaniema łączność lewostronną i 2 minus 2 minus 2 da w wyniku - Gdyby miało łączność prawostronnąw wynikiem byłoby Przykładem matematycznego operatora ktoacutery ma łączność prawo-stronną jest potęgowanie np 322

jest roacutewne W języku C występuje dużo poziomoacutew operatoroacutew Poniżej przedstawiamy tabelkę ze

wszystkimi operatorami poczynając od tych z najwyższym priorytetem (wykonywanych napoczątku)

Duża liczba poziomoacutew pozwala czasami zaoszczędzić trochę milisekund w trakcie pisaniaprogramu i bajtoacutew na dysku gdyż często nawiasy nie są potrzebne nie należy jednak z tymprzesadzać gdyż kod programu może stać się mylący nie tylko dla innych ale po latach (czynawet i dniach) roacutewnież dla nas

812 KOLEJNOŚĆ WYLICZANIA ARGUMENTOacuteW OPERATORA 53

Tablica 81 Priorytety operatoroacutewOperator Łącznośćnawiasy nie dotyczyjednoargumentowe przyrostkowe [] -gt wywołanie funkcji postinkre-mentacja postdekrementacja

lewostronna

jednoargumentowe przedrostkowe ˜ + - amp sizeof preinkrementacjapredekrementacja rzutowanie

prawostronna

lewostronna+ - lewostronnaltlt gtgt lewostronnaltlt= gtgt= lewostronna== = lewostronnaamp lewostronnaˆ lewostronna| lewostronnaampamp lewostronna|| lewostronna prawostronnaoperatory przypisania prawostronna lewostronna

Warto także podkreślić że operator koniunkcji ma niższy priorytet niż operator poroacutew-nania3 Oznacza to że kod

if (flags amp FL_MASK == FL_FOO)

zazwyczaj da rezultat inny od oczekiwanego Najpierw bowiem wykona się poroacutewna-nie wartości FL MASK z wartością FL FOO a dopiero potem koniunkcja bitowa W takichsytuacjach należy pamiętać o użyciu nawiasoacutew

if ((flags amp FL_MASK) == FL_FOO)

Kompilator potrafi wykrywać takie błędy i aby to robił należy podać mu argument-Wparentheses

812 Kolejność wyliczania argumentoacutew operatoraW przypadku większości operatoroacutew (wyjątkami są tu ampamp || i przecinek) nie da się określićktoacutera wartość argumentu zostanie obliczona najpierw W większości przypadkoacutew nie mato większego znaczenia lecz w przypadku wyrażeń ktoacutere mają efekty uboczne wymuszeniekonkretnej kolejności może być potrzebne Weźmy dla przykładu program

include ltstdiohgt

int foo(int a) printf(dn a)

3Jest to zaszłość historyczna z czasoacutew gdy nie było logicznych operatoroacutew ampamp oraz || i zamiast nich stosowanooperatory bitowe amp oraz |

54 ROZDZIAŁ 8 OPERATORY

return 0

int main(void) return foo(1) + foo(2)

Otoacuteż nie wiemy czy najpierw zostanie wywołana funkcja foo z parametrem jeden czydwa Jeżeli ma to znaczenie należy użyć zmiennych pomocniczych zmieniając definicję funk-cji main na

int main(void) int tmp = foo(1)return tmp + foo(2)

Teraz już na pewno najpierw zostanie wypisana jedynka a potem dopiero dwoacutejka Sy-tuacja jeszcze bardziej się komplikuje gdy używamy wyrażeń z efektami ubocznymi jakoargumentoacutew funkcji np

include ltstdiohgt

int foo(int a) printf(dn a)return 0

int bar(int a int b int c int d) return a + b + c + d

int main(void) return foo(1) + foo(2) + foo(3) + foo(4)

Teraz też nie wiemy ktoacutera z permutacji liczb i zostanie wypisana i ponownienależy pomoacutec sobie zmiennymi tymczasowymi jeżeli zależy nam na konkretnej kolejności

int main(void) int tmp = foo(1)tmp += foo(2)tmp += foo(3)return tmp + foo(4)

813 Uwagi

W języku C++ wprowadzony został dodatkowo inny sposoacuteb zapisu rzutowania ktoacuterypozwala na łatwiejsze znalezienie w kodzie miejsc w ktoacuterych dokonujemy rzutowaniaWięcej na stronie C++Zmienne

814 ZOBACZ TEŻ 55

814 Zobacz też CSkładniaOperatory

56 ROZDZIAŁ 8 OPERATORY

Rozdział 9

Instrukcje sterujące

C jest językiem imperatywnym mdash oznacza to że instrukcje wykonują się jedna po drugiej wtakiej kolejności w jakiej są napisane Aby moacutec zmienić kolejność wykonywania instrukcjipotrzebne są instrukcje sterujące

Na wstępie przypomnijmy jeszcze informację z rozdziału Operatory że wyrażenie jestprawdziwe wtedy i tylko wtedy gdy jest roacuteżne od zera a fałszywe wtedy i tylko wtedy gdyjest roacutewne zeru

91 Instrukcje warunkowe

911 Instrukcja if

Użycie instrukcji if wygląda tak

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

dalsze instrukcje

Istnieje także możliwość reakcji na nieprawdziwość wyrażenia mdash wtedy należy zastosowaćsłowo kluczowe else

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

else blok wykonany jeśli wyrażenie jest nieprawdziwe

dalsze instrukcje

Przypatrzmy się bardziej ldquożyciowemurdquo programowi ktoacutery poroacutewnuje ze sobą dwie liczby

include ltstdiohgt

int main ()

int a ba = 4

57

58 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

b = 6if (a==b)

printf (a jest roacutewne bn) else

printf (a nie jest roacutewne bn)return 0

Stosowany jest też kroacutetszy zapis warunkoacutew logicznych korzystający z tego jak C rozumieprawdę i fałsz Jeśli zmienna a jest typu integer zamiast

if (a = 0) b = 1a

można napisać

if (a) b = 1a

a zamiast

if (a == 0) b = 1a

można napisać

if (a) b = 1a

Czasami zamiast pisać instrukcję if możemy użyć operatora wyrażenia warunkowego(patrz Operatory)

if (a = 0)b = 1a

elseb = 0

ma dokładnie taki sam efekt jak

b = (a =0) 1a 0

912 Instrukcja swit

Aby ograniczyćwielokrotne stosowanie instrukcji if możemy użyć swit Jej użyciewyglądatak

switch (wyrażenie) case wartość1 instrukcje jeśli wyrażenie == wartość1

breakcase wartość2 instrukcje jeśli wyrażenie == wartość2

break default instrukcje jeśli żaden z wcześniejszych warunkoacutew

break nie został spełniony

Należy pamiętać o użyciu break po zakończeniu listy instrukcji następujących po case Je-śli tego nie zrobimy program przejdzie do wykonywania instrukcji z następnego case Możemieć to fatalne skutki

91 INSTRUKCJE WARUNKOWE 59

include ltstdiohgt

int main ()

int a bprintf (Podaj a )scanf (d ampa)printf (Podaj b )scanf (d ampb)switch (b)

case 0 printf (Nie można dzielić przez 0n) tutaj zabrakło break default printf (ab=dn ab)

return 0

A czasami może być celowym zabiegiem (tzw ldquofall-throughrdquo) mdash woacutewczas warto zazna-czyć to w komentarzu Oto przykład

include ltstdiohgt

int main ()

int a = 4switch ((a3))

case 0printf (Liczba d dzieli się przez 3n a)break

case -2case -1case 1case 2printf (Liczba d nie dzieli się przez 3n a)break

return 0

Przeanalizujmy teraz działający przykład

include ltstdiohgt

int main ()

unsigned int dzieci = 3 podatek=1000switch (dzieci)

case 0 break brak dzieci - czyli brak ulgi case 1 ulga 2

podatek = podatek - (podatek100 2)break

case 2 ulga 5

60 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

podatek = podatek - (podatek100 5)break

default ulga 10 podatek = podatek - (podatek10010)break

printf (Do zapłaty dn podatek)

92 Pętle

921 Instrukcja while

Często zdarza się że nasz programmusi wielokrotnie powtarzać ten sam ciąg instrukcji Abynie przepisywać wiele razy tego samego kodu można skorzystać z tzw pętli Pętla wykonujesię dotąd dopoacuteki prawdziwy jest warunek

while (warunek) instrukcje do wykonania w pętli

dalsze instrukcje

Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie Załoacuteżmy żemamy obliczyć kwadraty liczb od do Piszemy zatem program

include ltstdiohgt

int main ()

int a = 1while (a lt= 10) dopoacuteki a nie przekracza 10

printf (dn aa) wypisz aa na ekran++a zwiększamy a o jeden

return 0

Po analizie kodu mogą nasunąć się dwa pytania

Po co zwiększać wartość a o jeden Otoacuteż gdybyśmy nie dodali instrukcji zwiększająceja to warunek zawsze byłby spełniony a pętla ldquokręciłabyrdquo się w nieskończoność

Dlaczego warunek to ldquoa lt= rdquo a nie ldquoa=rdquo Odpowiedź jest dość prosta Pętlasprawdza warunek przed wykonaniem kolejnego ldquoobroturdquo Dlatego też gdyby waru-nek brzmiał ldquoa=rdquo to dla a= jest on nieprawdziwy i pętla nie wykonałaby ostatniejiteracji przez co program generowałby kwadraty liczb od do a nie do

922 Instrukcja for

Od instrukcji while czasami wygodniejsza jest instrukcja for Umożliwia ona wpisanie usta-wiania zmiennej sprawdzania warunku i inkrementowania zmiennej w jednej linijce co czę-sto zwiększa czytelność kodu Instrukcję for stosuje się w następujący sposoacuteb

92 PĘTLE 61

for (wyrażenie1 wyrażenie2 wyrażenie3) instrukcje do wykonania w pętli

dalsze instrukcje

Jak widać pętla for znacznie roacuteżni się od tego typu pętli znanych w innych językachprogramowania Opiszemy więc co oznaczają poszczegoacutelne wyrażenia

wyrażenie mdash jest to instrukcja ktoacutera będzie wykonana przed pierwszym przebiegiempętli Zwykle jest to inicjalizacja zmiennej ktoacutera będzie służyła jako ldquolicznikrdquo przebie-goacutew pętli

wyrażenie mdash jest warunkiem zakończenia pętli Pętla wykonuje się tak długo jakprawdziwy jest ten warunek

wyrażenie mdash jest to instrukcja ktoacutera wykonywana będzie po każdym przejściu pętliZamieszczone są tu instrukcje ktoacutere zwiększają licznik o odpowiednią wartość

Jeżeli wewnątrz pętli nie ma żadnych instrukcji continue (opisanych niżej) to jest onaroacutewnoważna z

wyrażenie1while (wyrażenie2)

instrukcje do wykonania w pętli wyrażenie3

dalsze instrukcje

Ważną rzeczą jest tutaj to żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla forPoczątkującym programistom nieznajomość tego faktu sprawia wiele problemoacutew

W pierwszej kolejności w pętli for wykonuje się wyrażenie1 Wykonuje się ono zawszenawet jeżeli warunek przebiegu pętli jest od samego początku fałszywy Po wykonaniuwyrażenie1 pętla for sprawdza warunek zawarty w wyrażenie2 jeżeli jest on prawdziwyto wykonywana jest treść pętli for czyli najczęściej to co znajduje się między klamrami lubgdy ich nie ma następna pojedyncza instrukcja W szczegoacutelności musimy pamiętać że samśrednik też jest instrukcją mdash instrukcją pustą Gdy już zostanie wykonana treść pętli for na-stępuje wykonanie wyrażenie3 Należy zapamiętać że wyrażenie zostanie wykonane nawetjeżeli był to już ostatni obieg pętli Poniższe przykłady pętli for w rezultacie dadzą ten samwynik Wypiszą na ekran liczby od do

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 printf(d i++ ) )

Dwa pierwsze przykłady korzystają z własności struktury blokowej kolejny przykład jestjuż bardziej wyrafinowany i korzysta z tego że jako wyrażenie3może zostać podane dowolnebardziej skomplikowane wyrażenie zawierające w sobie inne podwyrażenia A oto kolejnyprogram ktoacutery najpierw wyświetla liczby w kolejności rosnącej a następnie wraca

62 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

include ltstdiohgtint main()int ifor(i=1 ilt=5 ++i)

printf(d i)

for( igt=1 i--)printf(d i)

return 0

Po analizie powyższego kodu początkujący programista może stwierdzić że pętla wy-pisze 123454321 Stanie się natomiast inaczej Wynikiem działania powyższego programubędzie ciąg cyfr 12345654321 Pierwsza pętla wypisze cyfry ldquordquo lecz po ostatnim swoimobiegu pętla for (tak jak zwykle) zinkrementuje zmienną i Gdy druga pętla przystąpi dopracy zacznie ona odliczać począwszy od liczby i= a nie By spowodować wyświetlanieliczb od do i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli fora pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej i o

Niech podsumowaniem będzie jakiś działający fragment kodu ktoacutery może obliczać war-tości kwadratoacutew liczb od do

include ltstdiohgt

int main ()

int afor (a=1 alt=10 ++a)

printf (dn aa)return 0

W kodzie źroacutedłowym spotyka się często inkrementację i++ Jest to zły zwyczaj biorącysię z wzorowania się na nazwie języka C++ Post-inkrementacja i++ powoduje że tworzonyjest obiekt tymczasowy ktoacutery jest zwracany jako wynik operacji (choć wynik ten nie jestnigdzie czytany) Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie ale wpętli ldquoforrdquo takie kopiowanie odbywa się po każdym przebiegu pętli Dodatkowo w C++ po-dobną konstrukcję stosuje się do obiektoacutew mdash kopiowanie obiektu może być już czasochłonnączynnością Dlatego w pętli ldquoforrdquo należy stosować wyłącznie ++i

923 Instrukcja dowhile

Pętle while i for mają jeden zasadniczy mankament mdash może się zdarzyć że nie wykonają sięani razu Aby mieć pewność że nasza pętla będzie miała co najmniej jeden przebieg musimyzastosować pętlę do while Wygląda ona następująco

92 PĘTLE 63

do instrukcje do wykonania w pętli

while (warunek) dalsze instrukcje

Zasadniczą roacuteżnicą pętli do while jest fakt iż sprawdza ona warunek pod koniec swojegoprzebiegu To właśnie ta cecha decyduje o tym że pętla wykona się co najmniej raz A terazprzykład działającego kodu ktoacutery tym razem będzie obliczał trzecią potęgę liczb od do

include ltstdiohgt

int main ()

int a = 1do

printf (dn aaa)++a

while (a lt= 10)return 0

Może się to wydać zaskakujące ale roacutewnież przy tej pętli zamiast bloku instrukcji możnazastosować pojedynczą instrukcję np

include ltstdiohgt

int main ()

int a = 1do printf (dn aaa) while (++a lt= 10)return 0

924 Instrukcja break

Instrukcja break pozwala na opuszczenie wykonywania pętli w dowolnym momencie Przy-kład użycia

int afor (a=1 a = 9 ++a)

if (a == 5) breakprintf (dn a)

Program wykona tylko przebiegi pętli gdyż przy przebiegu instrukcja break spowo-duje wyjście z pętli

Break i pętle nieskończone

W przypadku pętli for nie trzeba podawać warunku W takim przypadku kompilator przyj-mie że warunek jest stale spełniony Oznacza to że poniższe pętle są roacutewnoważne

64 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

for () for (1) for (aaa) gdzie a jest dowolną liczba rzeczywistą roacuteżną od 0while (1) do while (1)

Takie pętle nazywamy pętlami nieskończonymi ktoacutere przerwać może jedynie instrukcjabreak1(z racji tego że warunek pętli zawsze jest prawdziwy) 2

Wszystkie fragmenty kodu działają identycznie

int i = 0for (i=5++i)

kod

int i = 0for (++i)

if (i == 5) break

int i = 0for ()

if (i == 5) break++i

925 Instrukcja continue

W przeciwieństwie do break ktoacutera przerywa wykonywanie pętli instrukcja continue powo-duje przejście do następnej iteracji o ile tylko warunek pętli jest spełniony Przykład

int ifor (i = 0 i lt 100 ++i)

printf (Poczatekn)if (i gt 40) continue printf (Koniecn)

Dla wartości i większej od nie będzie wyświetlany komunikat ldquoKoniecrdquo Pętla wykonapełne przejść

Oto praktyczny przykład użycia tej instrukcji

include ltstdiohgtint main()

int i

1Tak naprawdę podobną operacje możemy wykonać za pomocą polecenia goto W praktyce jednak stosujesię zasadę że break stosuje się do przerwania działania pętli i wyjścia z niej goto stosuje się natomiast wtedykiedy chce się wydostać się z kilku zagnieżdżonych pętli za jednym zamachem Do przerwania pracy pętli mogąnam jeszcze posłużyć polecenia exit() lub return ale woacutewczas zakończymy nie tylko działanie pętli ale i całegoprogramufunkcji

2Żartobliwie można powiedzieć że stosując pętlę nieskończoną to najlepiej korzystać z pętli for() gdyżwymaga ona napisania najmniejszej liczby znakoacutew w poroacutewnaniu do innych konstrukcji

93 INSTRUKCJA GOTO 65

for (i = 1 i lt= 50 ++i) if (i4==0) continue printf (d i)

return 0

Powyższy program generuje liczby z zakresu od do ktoacutere nie są podzielne przez

93 Instrukcja goto

Istnieje także instrukcja ktoacutera dokonuje skoku do dowolnegomiejsca programu oznaczonegotzw etykietą

etykieta instrukcje goto etykieta

Uwaga kompilator w wersji i wyższych jest bardzo uczulony na etykiety za-mieszczone przed nawiasem klamrowym zamykającym blok instrukcji Innymi słowy nie-dopuszczalne jest umieszczanie etykiety zaraz przed klamrą ktoacutera kończy blok instrukcjizawartych np w pętli for Można natomiast stosować etykietę przed klamrą kończącą danąfunkcję

Instrukcja goto łamie sekwencję instrukcji i powoduje skok do dowolnie odległego miej-sca w programie - co może mieć nieprzewidziane skutki Zbyt częste używanie goto możeprowadzić do trudnych do zlokalizowania błędoacutew Oproacutecz tego kompilatory mają kłopotyz optymalizacją kodu w ktoacuterym występują skoki Z tego powodu zaleca się ograniczeniezastosowania tej instrukcji wyłącznie do opuszczania wielokrotnie zagnieżdżonych pętli

Przykład uzasadnionego użycia

int ijfor (i = 0 i lt 10 ++i)

for (j = i j lt i+10 ++j) if (i + j 21 == 0) goto koniec

koniec dalsza czesc programu

94 Natymiastowe kończenie programu mdash funkcja exit

Program może zostać w każdej chwili zakończony mdash do tego właśnie celu służy funkcja exitUżywamy jej następująco

exit (kod_wyjścia)

66 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

Liczba całkowita kod wyjścia jest przekazywana do procesu macierzystego dzięki czemudostaje on informację czy programw ktoacuterymwywołaliśmy tą funkcję zakończył się popraw-nie lub czy się tak nie stało Kody wyjścia są nieustandaryzowane i żeby program był w pełniprzenośny należy stosować makra EXIT SUCCESS i EXIT FAILURE choć na wielu systemach kod oznacza poprawne zakończenie a kod roacuteżny od błędne W każdym przypadku jeżeli naszprogram potrafi generować wiele roacuteżnych kodoacutew warto je wszystkie udokumentować w ewdokumentacji Są one też czasem pomocne przy wykrywaniu błędoacutew

95 Uwagi W języku C++ można deklarować zmienne w nagłoacutewku pętli ldquoforrdquo w następujący spo-

soacuteb for(int i=0 ilt10 ++i) (więcej informacji w C++Zmienne)

Rozdział 10

Podstawowe procedury wejścia iwyjścia

101 Wejściewyjście

Komputer byłby całkowicie bezużyteczny gdyby użytkownik nie moacutegł się z nim porozumieć(tj wprowadzić danych lub otrzymać wynikoacutew pracy programu) Programy komputerowesłużą w największym uproszczeniu do obroacutebki danych mdash więc muszą te dane jakoś od nasotrzymać przetworzyć i przekazać nam wynik

Takiewczytywanie i ldquowyrzucanierdquo danychw terminologii komputerowej nazywamywej-ściem (input) iwyjściem (output) Bardzo często moacutewi się o wejściu i wyjściu danych łączniemdash inputoutput albo po prostu IO

W C do komunikacji z użytkownikiem służą odpowiednie funkcje Zresztą do wielu za-dań w C służą funkcje Używając funkcji nie musimy wiedzieć w jaki sposoacuteb komputerwykonuje jakieś zadanie interesuje nas tylko to co ta funkcja robi Funkcje niejako ldquowyko-nują za nas część pracyrdquo ponieważ nie musimy pisać być może dziesiątek linijek kodu żebynp wypisać tekst na ekranie (wbrew pozorom mdash kod funkcji wyświetlającej tekst na ekraniejest dość skomplikowany) Jeszcze taka uwaga mdash gdy piszemy o jakiejś funkcji zazwyczajpodając jej nazwę dopisujemy na końcu nawias

printf()scanf()

żeby było jasne że chodzi o funkcję a nie o coś innego

Wyżej wymienione funkcje to jedne z najczęściej używanych funkcji w C mdash pierwszasłuży do wypisywania danych na ekran natomiast druga do wczytywania danych z klawia-tury1

1W zasadzie standard C nie definiuje czegoś takiego jak ekran i klawiatura mdash mowa w nim o standardowymwyjściu i standardowym wejściu Zazwyczaj jest to właśnie ekran i klawiatura ale nie zawsze W szczegoacutelności użyt-kownicy Linuksa lub innych systemoacutew uniksowych mogą być przyzwyczajeniu do przekierowania wejściawyjściazdo pliku czy łączenie komend w potoki (ang pipe) W takich sytuacjach dane nie są wyświetlane na ekranie aniodczytywane z klawiatury

67

68 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

102 Funkcje wyjścia

1021 Funkcja printf

W przykładzie ldquoHello Worldrdquo użyliśmy już jednej z dostępnych funkcji wyjścia a miano-wicie funkcji printf() Z punktu widzenia swoich możliwości jest to jedna z bardziej skom-plikowanych funkcji a jednocześnie jest jedną z najczęściej używanych Przyjrzyjmy sięponownie kodowi programu ldquoHello Worldrdquo

include ltstdiohgt

int main(void)

printf(Hello worldn)return 0

Po skompilowaniu i uruchomieniu program wypisze na ekranie

Hello world

W naszym przykładowym programie chcąc by funkcja printf() wypisała tekst na ekra-nie umieściliśmy go w cudzysłowach wewnątrz nawiasoacutew Ogoacutelnie wywołanie funkcjiprintf() wygląda następująco

printf(format argument1 argument2 )

Przykładowo

int i = 500printf(Liczbami całkowitymi są na przykład i oraz in 1 i)

wypisze

Liczbami całkowitymi są na przykład 1 oraz 500

Format to napis ujęty w cudzysłowy ktoacutery określa ogoacutelny kształt schemat tego co ma byćwyświetlone Format jest drukowany tak jak go napiszemy jednak niektoacutere znaki specjalnezostaną w nim podmienione na co innego Przykładowo znak specjalny n jest zamienianyna znak nowej linii 2 Natomiast procent jest podmieniany na jeden z argumentoacutew Po pro-cencie następuje specyfikacja jak wyświetlić dany argument W tym przykładzie i (od int)oznacza że argument ma być wyświetlony jak liczba całkowita W związku z tym że i mają specjalne znaczenie aby wydrukować je należy użyć ich podwoacutejnie

printf(Procent Backslash )

drukuje

Procent Backslash

(bez przejścia do nowej linii) Na liście argumentoacutew możemy mieszać ze sobą zmienne roacuteż-nych typoacutew liczby napisy itp w dowolnej liczbie Funkcja printf przyjmie ich tyle ile tylkonapiszemy Należy uważać by nie pomylić się w formatowaniu

2Zmiana ta następuje w momencie kompilacji programu i dotyczy wszystkich literałoacutew napisowych Nie jestto jakaś szczegoacutelna własność funkcji printf() Więcej o tego typu sekwencjach i ciągach znakoacutew w szczegoacutelnościopisane jest w rozdziale Napisy

103 FUNKCJA PUTS 69

int i = 5printf(i s i 5 4 napis) powinno być i i s

Przywłączeniu ostrzeżeń (opcja -Wall lub -WformatwGCC) kompilator powinien nas ostrzecgdy format nie odpowiada podanym elementom

Najczęstsze użycie printf()

printf(i i) gdy i jest typu int zamiast i można użyć d

printf(f i) gdy i jest typu float lub double

printf(c i) gdy i jest typu char (i chcemy wydrukować znak)

printf(s i) gdy i jest napisem (typu char)

Funkcja printf() nie jest żadną specjalną konstrukcją języka i łańcuch formatujący możebyć podany jako zmienna W związku z tym możliwa jest np taka konstrukcja

include ltstdiohgt

int main(void)

char buf[100]scanf(99s buf) funkcja wczytuje tekst do tablicy buf printf(buf)return 0

Program wczytuje tekst a następnie wypisuje go Jednak ponieważ znak procentu jesttraktowany w specjalny sposoacuteb toteż jeżeli na wejściu pojawi się ciąg znakoacutew zawierającyten znak mogą się stać roacuteżne dziwne rzeczy Między innymi z tego powodu w takich sytu-acjach lepiej używać funkcji puts() lub fputs() opisanych niżej lub wywołania printf(szmienna)

Więcej o funkcji printf()

103 Funkcja putsFunkcja puts() przyjmuje jako swoacutej argument ciąg znakoacutew ktoacutery następnie bezmyślnie wy-pisuje na ekran kończąc go znakiem przejścia do nowej linii W ten sposoacuteb nasz pierwszyprogram moglibyśmy napisać w ten sposoacuteb

include ltstdiohgt

int main(void)

puts(Hello world)return 0

W swoim działaniu funkcja ta jest w zasadzie identyczna do wywołania printf(snargument) jednak prawdopodobnie będzie działać szybciej Jedynym jejmankamentemmożebyć fakt że zawsze na końcu podawany jest znak przejścia do nowej linii Jeżeli jest to efektniepożądany (nie zawsze tak jest) należy skorzystać z funkcji fputs() opisanej niżej lub wy-wołania printf(s argument)

Więcej o funkcji puts()

70 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

104 Funkcja fputs

Opisując funkcję fputs() wybiegamy już trochę w przyszłość (a konkretnie do opisu operacjina plikach) ale warto o niej wspomnieć już teraz gdyż umożliwia ona wypisanie swojegoargumentu bez wypisania na końcu znaku przejścia do nowej linii

include ltstdiohgt

int main(void)

fputs(Hello worldn stdout)return 0

W chwili obecnej możesz się nie przejmować tym zagadkowym stdout wpisanym jakodrugi argument funkcji Jest to określenie strumienia wyjściowego (w naszymwypadku stan-dardowe wyjście mdash standard output)

Więcej o funkcji fputs()

1041 Funkcja putar

Funkcja putchar() służy do wypisywania pojedynczych znakoacutew Przykładowo jeżeli chcieli-byśmy napisać programwypisującyw prostej tabelce wszystkie liczby od do moglibyśmyto zrobić tak

include ltstdiohgt

int main(void)

int i = 0for ( ilt100 ++i) Nie jest to pierwsza liczba w wierszu if (i 10)

putchar( )printf(2d i) Jest to ostatnia liczba w wierszu if ((i 10)==9)

putchar(n)

return 0

Więcej o funkcji putchar()

105 FUNKCJE WEJŚCIA 71

105 Funkcje wejścia

1051 Funkcja scanf()

Teraz pomyślmy o sytuacji odwrotnej Tym razem to użytkownik musi powiedzieć coś pro-gramowi W poniższym przykładzie program podaje kwadrat liczby podanej przez użytkow-nika

include ltstdiohgt

int main ()

int liczba = 0printf (Podaj liczbę )scanf (d ampliczba)printf (dd=dn liczba liczba liczbaliczba)return 0

Zauważyłeś że w tej funkcji przy zmiennej pojawił się nowy operator mdash amp (etka) Jeston ważny gdyż bez niego funkcja scanf() nie skopiuje odczytanej wartości liczby do odpo-wiedniej zmiennej Właściwie oznacza przekazanie do funkcji adresu zmiennej by funkcjamogła zmienić jej wartość Nie musisz teraz rozumieć jak to się odbywa wszystko zostaniewyjaśnione w rozdziale Wskaźniki

Oznaczenia są podobne takie jak przy printf() czyli scanf(i ampliczba) wczytuje liczbętypu int scanf(f ampliczba) ndash liczbę typu float a scanf(s tablica znakoacutew) ciąg zna-koacutew Ale czemu w tym ostatnim przypadku nie ma etki Otoacuteż gdy podajemy jako argumentdo funkcji wyrażenie typu tablicowego zamieniane jest ono automatycznie na adres pierw-szego elementu tablicy Będzie to dokładniej opisane w rozdziale poświęconym wskaźnikom

Brak etki jest częstym błędem szczegoacutelnie wśroacuted początkujących programistoacutew Ponie-waż funkcja scanf() akceptuje zmienną liczbę argumentoacutew to nawet kompilator może miećkłopoty z wychwyceniem takich błędoacutew (konkretnie chodzi o to że standard nie wymagaod kompilatora wykrywania takich pomyłek) choć kompilator GCC radzi sobie z tym jeżelipodamy mu argument -Wformat

Należy jednak uważać na to ostatnie użycie Rozważmy na przykład poniższy kod

include ltstdiohgt

int main(void)

char tablica[100] 1 scanf(s tablica) 2 return 0

Robi on niewiele W linijce deklarujemy tablicę znakoacutew czyli mogącą przechowaćnapis długości znakoacutew Nie przejmuj się jeżeli nie do końca to wszystko rozumiesz mdash po-jęcia takie jak tablica czy ciąg znakoacutew staną się dla Ciebie jasne w miarę czytania kolejnych

72 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

rozdziałoacutew W linijce wywołujemy funkcję scanf() ktoacutera odczytuje tekst ze standardowegowejścia Nie zna ona jednak rozmiaru tablicy i nie wie ile znakoacutewmoże ona przechować przezco będzie czytać tyle znakoacutew aż napotka biały znak (format s nakazuje czytanie pojedyn-czego słowa) co może doprowadzić do przepełnienia bufora Niebezpieczne skutki czegośtakiego opisane są w rozdziale poświęconym napisom Na chwilę obecną musisz zapamiętaćżeby zaraz po znaku procentu podawać maksymalną liczbę znakoacutew ktoacutere może przechowaćbufor czyli liczbę o jeden mniejszą niż rozmiar tablicy Bezpieczna wersją powyższego kodujest

include ltstdiohgt

int main(void)

char tablica[100]scanf(99s tablica)return 0

Funkcja scanf() zwraca liczbę poprawnie wczytanych zmiennych lub EOF jeżeli nie majuż danych w strumieniu lub nastąpił błąd Załoacuteżmy dla przykładu że chcemy stworzyćprogram ktoacutery odczytuje po kolei liczby i wypisuje ich potęgi W pewnym momenciedane się kończą lub jest wprowadzana niepoprawna dana i woacutewczas nasz program powinienzakończyć działanie Aby to zrobić należy sprawdzać wartość zwracaną przez funkcję scanf()w warunku pętli

include ltstdiohgt

int main(void)

int nwhile (scanf(d ampn)==1)printf(dn nnn)

return 0

Podobnie możemy napisać program ktoacutery wczytuje po dwie liczby i je sumuje

include ltstdiohgt

int main(void)

int a bwhile (scanf(d d ampa ampb)==2)printf(dn a+b)

return 0

105 FUNKCJE WEJŚCIA 73

Rozpatrzmy teraz trochę bardziej skomplikowany przykład Otoacuteż ponownie jak poprzed-nio nasz program będzie wypisywał potęgę podanej liczby ale tym razem musi ignorowaćbłędne dane (tzn pomijać ciągi znakoacutew ktoacutere nie są liczbami) i kończyć działanie tylko wmomencie gdy nastąpi błąd odczytu lub koniec pliku3

include ltstdiohgt

int main(void)

int result ndoresult = scanf(d ampn)if (result==1)

printf(dn nnn)else if (result) result to to samo co result==0

result = scanf(s)

while (result=EOF)return 0

Zastanoacutewmy się przez chwilę co się dzieje w programie Najpierw wywoływana jestfunkcja scanf() i następuje proacuteba odczytu liczby typu int Jeżeli funkcja zwroacuteciła to liczbazostała poprawnie odczytana i następuje wypisanie jej trzeciej potęgi Jeżeli funkcja zwroacuteciła to na wejściu były jakieś dane ktoacutere nie wyglądały jak liczba W tej sytuacji wywołujemyfunkcję scanf() z formatem odczytującym dowolny ciąg znakoacutew nie będący białymi znakamiz jednoczesnym określeniem żeby nie zapisywała nigdzie wyniku W ten sposoacuteb niepopraw-nie wpisana dana jest omijana Pętla głoacutewna wykonuje się tak długo jak długo funkcja scanf()nie zwroacuteci wartości EOF

Więcej o funkcji scanf()

1052 Funkcja gets

Funkcja gets służy do wczytania pojedynczej linii Może Ci się to wydać dziwne ale funkcjitej nie należy używać pod żadnym pozorem Przyjmuje ona jeden argument mdash adres pierw-szego elementu tablicy do ktoacuterego należy zapisać odczytaną linię mdash i nic poza tym Z tegopowodu nie ma żadnej możliwości przekazania do tej funkcji rozmiaru bufora podanego jakoargument Podobnie jak w przypadku scanf() może to doprowadzić do przepełnienia buforaco może mieć tragiczne skutki Zamiast tej funkcji należy używać funkcji fgets()

Więcej o funkcji gets()

1053 Funkcja fgets

Funkcja fgets() jest bezpieczną wersją funkcji gets() ktoacutera dodatkowo może operować nadowolnych strumieniach wejściowych Jej użycie jest następujące

3Jak rozroacuteżniać te dwa zdarzenia dowiesz się w rozdziale Czytanie i pisanie do plikoacutew

74 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

fgets(tablica_znakoacutew rozmiar_tablicy_znakoacutew stdin)

Na chwilę obecną nie musisz się przejmować ostatnim argumentem (jest to określeniestrumienia w naszym przypadku standardowewejście mdash standard input) Funkcja czyta tekstaż do napotkania znaku przejścia do nowej linii ktoacutery także zapisuje w wynikowej tablicy(funkcja gets() tego nie robi) Jeżeli brakuje miejsca w tablicy to funkcja przerywa czytanie wten sposoacuteb aby sprawdzić czy została wczytana cała linia czy tylko jej część należy sprawdzićczy ostatnim znakiem nie jest znak przejścia do nowej linii Jeżeli nastąpił jakiś błąd lub nawejściu nie ma już danych funkcja zwraca wartość NULL

include ltstdiohgt

int main(void) char buffer[128] whole_line = 1 chwhile (fgets(buffer sizeof buffer stdin)) 1

if (whole_line) 2 putchar(gt)if (buffer[0]=gt)

putchar( )

fputs(buffer stdout) 3 for (ch = buffer ch ampamp ch=n ++ch) 4 whole_line = ch == n

if (whole_line)

putchar(n)return 0

Powyższy kod wczytuje dane ze standardowego wejścia mdash linia po linii mdash i dodaje napoczątku każdej linii znak większości po ktoacuterym dodaje spację jeżeli pierwszym znakiem nalinii nie jest znak większości W linijce następuje odczytywanie linii Jeżeli nie ma już wię-cej danych lub nastąpił błąd wejścia funkcja zwraca wartość NULL ktoacutera ma logiczną war-tość i woacutewczas pętla kończy działanie W przeciwnym wypadku funkcja zwraca po prostupierwszy argument ktoacutery ma wartość logiczną W linijce sprawdzamy czy poprzedniewywołanie funkcji wczytało całą linię czy tylko jej część mdash jeżeli całą to teraz jesteśmy napoczątku linii i należy dodać znakwiększości W linii najzwyczajniej w świecie wypisujemylinię W linii przeszukujemy tablicę znak po znaku aż do momentu gdy znajdziemy znako kodzie kończącym ciąg znakoacutew albo znak przejścia do nowej linii Ten drugi przypadekoznacza że funkcja fgets() wczytała całą linię

Więcej o funkcji fgets()

1054 Funkcja getar()

Jest to bardzo prosta funkcja wczytująca znak z klawiatury W wielu przypadkach danemogą być buforowane przez co wysyłane są do programu dopiero gdy bufor zostaje przepeł-niony lub na wejściu jest znak przejścia do nowej linii Z tego powodu po wpisaniu danegoznaku należy nacisnąć klawisz enter aczkolwiek trzeba pamiętać że w następnym wywoła-niu zostanie zwroacutecony znak przejścia do nowej linii Gdy nastąpił błąd lub nie ma już więcej

105 FUNKCJE WEJŚCIA 75

danych funkcja zwraca wartość EOF (ktoacutera ma jednak wartość logiczną toteż zwykła pętlawhile (getchar()) nie da oczekiwanego rezultatu)

include ltstdiohgt

int main(void)

int cwhile ((c = getchar())=EOF)

if (c== ) c = _

putchar(c)

return 0

Ten prosty program wczytuje dane znak po znaku i zamienia wszystkie spacje na znakipodkreślenia Może wydać się dziwne że zmienną c zdefiniowaliśmy jako trzymającą typ inta nie char Właśnie taki typ (tj int) zwraca funkcja getchar() i jest to konieczne ponieważwartość EOF wykracza poza zakres wartości typu char (gdyby tak nie było to nie byłobymożliwości rozroacuteżnienia wartości EOF od poprawnie wczytanego znaku) Więcej o funkcjigetchar()

76 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

Rozdział 11

Funkcje

W matematyce pod pojęciem funkcji rozumiemy twoacuter ktoacutery pobiera pewną liczbę argumen-toacutew i zwraca wynik1 Jeśli dla przykładu weźmiemy funkcję sin(x) to x będzie zmiennąrzeczywistą ktoacutera określa kąt a w rezultacie otrzymamy inną liczbę rzeczywistą mdash sinustego kąta

W C funkcja (czasami nazywana podprogramem rzadziej procedurą) to wydzielona częśćprogramu ktoacutera przetwarza argumenty i ewentualnie zwraca wartość ktoacutera następnie możebyć wykorzystana jako argument w innych działaniach lub funkcjach Funkcja może posia-dać własne zmienne lokalne W odroacuteżnieniu od funkcji matematycznych funkcje w C mogązwracać dla tych samych argumentoacutew roacuteżne wartości

Po lekturze poprzednich części podręcznika zapewne moacutegłbyś podać kilka przykładoacutewfunkcji z ktoacuterych korzystałeś Były to np

funkcja printf() drukująca tekst na ekranie czy

funkcja main() czyli głoacutewna funkcja programu

Głoacutewną motywacją tworzenia funkcji jest unikanie powtarzania kilka razy tego samegokodu W poniższym fragmencie

for(i=1 i lt= 5 ++i) printf(d ii)

for(i=1 i lt= 5 ++i)

printf(d iii)for(i=1 i lt= 5 ++i)

printf(d ii)

widzimy że pierwsza i trzecia pętla for są takie same Zamiast kopiować fragment kodukilka razy (co jest mało wygodne i może powodować błędy) lepszym rozwiązaniem mogłobybyć wydzielenie tego fragmentu tak by można go było wywoływać kilka razy Tak właśniedziałają funkcje

Innym niemniej ważnym powodem używania funkcji jest rozbicie programu na frag-menty wg ich funkcjonalności Oznacza to że z jeden duży program dzieli się na mniejsze

1Aby nie urażać matematykoacutew sprecyzujmy że chodzi o relację między zbiorami X i Y (X jest dziedziną Y jestprzeciwdziedziną) takie że każdy element zbioru X jest w relacji z dokładnie jednym elementem ze zbioru Y

77

78 ROZDZIAŁ 11 FUNKCJE

funkcje ktoacutere są ldquowyspecjalizowanerdquo w wykonywaniu określonych czynności Dzięki temułatwiej jest zlokalizować błąd Ponadto takie funkcje można potem przenieść do innych pro-gramoacutew

111 Tworzenie funkcji

Dobrze jest uczyć się na przykładach Rozważmy następujący kod

int iloczyn (int x int y)

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

int iloczyn (int x int y) to nagłoacutewek funkcji ktoacutery opisuje jakie argumenty przyj-muje funkcja i jaką wartość zwraca (funkcja może przyjmować wiele argumentoacutew lecz możezwracać tylko jedną wartość)2 Na początku podajemy typ zwracanej wartości mdash u nas intNastępnie mamy nazwę funkcji i w nawiasach listę argumentoacutew

Ciało funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasachklamrowych Pierwszą instrukcją jest deklaracja zmiennej mdash jest to zmienna lokalna czyliniewidoczna poza funkcją Dalej przeprowadzamy odpowiednie działania i zwracamy rezul-tat za pomocą instrukcji return

1111 Ogoacutelnie

Funkcję w języku C tworzy się następująco

typ identyfikator (typ1 argument1 typ2 argument2 typ_n argument_n)

instrukcje

Oczywiście istnieje możliwość utworzenia funkcji ktoacutera nie posiada żadnych argumen-toacutew Definiuje się ją tak samo jak funkcję z argumentami z tą tylko roacuteżnicą że międzyokrągłymi nawiasami nie znajduje się żaden argument lub pojedyncze słoacutewko void mdash w de-finicji funkcji nie ma to znaczenia jednak w deklaracji puste nawiasy oznaczają że prototypnie informuje jakie argumenty przyjmuje funkcja dlatego bezpieczniej jest stosować słoacutewkovoid

Funkcje definiuje się poza głoacutewną funkcją programu (main) W języku C nie można two-rzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji)

1112 Procedury

Przyjęło się że procedura od funkcji roacuteżni się tym że ta pierwsza nie zwraca żadnej wartościZatem aby stworzyć procedurę należy napisać

2Bardziej precyzyjnie można powiedzieć że funkcja może zwroacutecić tylko jedną wartość typu prostego lub jedenadres do jakiegoś obiektu w pamięci

112 WYWOŁYWANIE 79

void identyfikator (argument1 argument2 argumentn)

instrukcje

void (z ang pusty proacuteżny) jest słowem kluczowym mającym kilka znaczeń w tym przy-padku oznacza ldquobrak wartościrdquo

Generalnie w terminologii C pojęcie ldquoprocedurardquo nie jest używane moacutewi się raczej ldquofunk-cja zwracająca voidrdquo

Jeśli nie podamy typu danych zwracanych przez funkcję kompilator domyślnie przyjmietyp int choć już w standardzie C nieokreślenie wartości zwracanej jest błędem

1113 Stary sposoacuteb definiowania funkcji

Zanim powstał standard ANSI C w liście parametroacutew nie podawało się typoacutew argumentoacutewa jedynie ich nazwy Roacutewnież z tamtych czasoacutew wywodzi się oznaczenie iż puste nawiasy(w prototypie funkcji nie w definicji) oznaczają że funkcja przyjmuje nieokreśloną liczbęargumentoacutew Tego archaicznego sposobu definiowania funkcji nie należy już stosować aleponieważ w swojej przygodzie z językiem C Czytelnik może się na nią natknąć a co więcejstandard nadal (z powodu zgodności z wcześniejszymi wersjami) dopuszcza taką deklaracjęto należy tutaj o niej wspomnieć Otoacuteż wygląda ona następująco

typ_zwracany nazwa_funkcji(argument1 argument2 argumentn)typ1 argumenty typ2 argumenty

instrukcje

Na przykład wcześniejsza funkcja iloczyn wyglądałaby następująco

int iloczyn(x y)int x y

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

Najpoważniejszą wadą takiego sposobu jest fakt że w prototypie funkcji niema podanychtypoacutew argumentoacutew przez co kompilator nie jest w stanie sprawdzić poprawności wywołaniafunkcji Naprawiono to (wprowadzając definicje takie jak je znamy obecnie) najpierw wjęzyku C++ a potem rozwiązanie zapożyczono w standardzie ANSI C z roku

112 WywoływanieFunkcje wywołuje się następująco

80 ROZDZIAŁ 11 FUNKCJE

identyfikator (argument1 argument2 argumentn)

Jeśli chcemy aby przypisać zmiennej wartość ktoacuterą zwraca funkcja należy napisać tak

zmienna = funkcja (argument1 argument2 argumentn)

Programiści mający doświadczenia np z językiem Pascal mogą popełniać błąd polegającyna wywoływaniu funkcji bez nawiasoacutew okrągłych gdy nie przyjmuje ona żadnych argumen-toacutew

Przykładowo mamy funkcję

void pisz_komunikat()

printf(To jest komunikatn)

Jeśli teraz ją wywołamy

pisz_komunikat ŹLE pisz_komunikat() dobrze

to pierwsze polecenie nie spowoduje wywołania funkcji Dlaczego Aby kompilator Czrozumiał że chodzi nam o wywołanie funkcji musimy po jej nazwie dodać nawiasy okrą-głe nawet gdy funkcja nie ma argumentoacutew Użycie samej nazwy funkcji ma zupełnie inneznaczenie mdash oznacza pobranie jej adresu W jakim celu O tym będzie mowa w rozdzialeWskaźniki

PrzykładA oto działający przykład ktoacutery demonstruje wiadomości podane powyżej

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

int m = suma (4 5)printf (4+5=dn m)return 0

113 Zwracanie wartościreturn to słowo kluczowe języka C

W przypadku funkcji służy ono do

przerwania funkcji (i przejścia do następnej instrukcji w funkcji wywołującej)

114 ZWRACANA WARTOŚĆ 81

zwroacutecenia wartości

W przypadku procedur powoduje przerwania procedury bez zwracania wartościUżycie tej instrukcji jest bardzo proste i wygląda tak

return zwracana_wartość

lub dla procedur

return

Możliwe jest użycie kilku instrukcji returnw obrębie jednej funkcji Wielu programistoacutewuważa jednak że lepsze jest użycie jednej instrukcji return na końcu funkcji gdyż ułatwia tośledzenie przebiegu programu

114 Zwracana wartość

W C zwykle przyjmuje się że oznacza poprawne zakończenie funkcji

return 0 funkcja zakończona sukcesem

a inne wartości oznaczają niepoprawne zakończenie

return 1 funkcja zakończona niepowodzeniem

Ta wartość może być wykorzystana przez inne instrukcje np if

115 Funkcja main()

Do tej pory we wszystkich programach istniała funkcja main() Po co tak właściwie onajest Otoacuteż jest to funkcja ktoacutera zostaje wywołana przez fragment kodu inicjującego pracęprogramu Kod ten tworzony jest przez kompilator i nie mamy na niego wpływu Istotnejest że każdy program w języku C musi zawierać funkcję main()

Istnieją dwa możliwe prototypy (nagłoacutewki) omawianej funkcji

int main(void)

int main(int argc char argv) 3

Argument argc jest liczbą nieujemną określającą ile ciągoacutew znakoacutew przechowywanychjest w tablicy argv Wyrażenie argv[argc] ma zawsze wartość Pierwszym elementemtablicy argv (o ile istnieje4) jest nazwa programu czy komenda ktoacuterą program został urucho-miony Pozostałe przechowują argumenty podane przy uruchamianiu programu

Zazwyczaj jeśli program uruchomimy poleceniem

program argument1 argument2

3Czasami można się spotkać z prototypem int main(int argc char argv char env) ktoacutery jest definio-wany w standardzie ale wykracza już poza standard C

4Inne standardy mogą wymuszać istnienie tego elementu jednak jeśli chodzi o standard języka C to nic nie stoina przeszkodzie aby argument argc miał wartość zero

82 ROZDZIAŁ 11 FUNKCJE

to argc będzie roacutewne ( argumenty + nazwa programu) a argv będzie zawierać napisy pro-gram argument argument umieszczone w tablicy indeksowanej od do

Weźmy dla przykładu program ktoacutery wypisuje to co otrzymuje w argumentach argc iargv

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) while (argv)

puts(argv++) Ewentualnie można użycint ifor (i = 0 iltargc ++i)

puts(argv[i])return EXIT_SUCCESS

Uruchomiony w systemie typu UNIX poleceniem test foo bar baz powinien wypisać

testfoobarbaz

Na razie nie musisz rozumieć powyższych kodoacutew i opisoacutew gdyż odwołują się do pojęćtakich jak tablica oraz wskaźnik ktoacutere opisane zostaną w dalszej części podręcznika

Co ciekawe funkcja main nie roacuteżni się zanadto od innych funkcji i tak jak inne możewołać sama siebie (patrz rekurencja niżej) przykładowo powyższy program można zapisaćtak5

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) if (argv)

puts(argv)return main(argc-1 argv+1)

else return EXIT_SUCCESS

Ostatnią rzeczą dotyczącą funkcji main jest zwracana przez nią wartość Już przy oma-wianiu pierwszego programu wspomniane zostało że jedynymi wartościami ktoacutere znaczą

5Jeżeli ktoś lubi ekstrawagancki kod ciało funkcji main można zapisać jako return argv puts(argv)main(argc-1 argv+1) EXIT SUCCESS ale nie radzimy stosować tak skomplikowanych i bądź co bądź małoczytelnych konstrukcji

116 DALSZE INFORMACJE 83

zawsze to samowewszystkich implementacjach języka są EXIT SUCCESS i EXIT FAILURE6

zdefiniowane w pliku nagłoacutewkowym stdlibh Wartość i EXIT SUCCESS oznaczają po-prawne zakończenie programu (co wcale nie oznacza że makro EXIT SUCCESS ma wartośćzero) natomiast EXIT FAILURE zakończenie błędne Wszystkie inne wartości są zależne odimplementacji

116 Dalsze informacjePoniżej przekażemy ci parę bardziej zaawansowanych informacji o funkcjach w C jeśli niemasz ochoty wgłębiać się w szczegoacuteły możesz spokojnie pominąć tę część i wroacutecić tu poacuteźniej

1161 Jak zwroacutecić kilka wartości

Jeśli chcesz zwroacutecić z funkcji kilka wartości musisz zrobić to w trochę inny sposoacuteb Ge-neralnie możliwe są dwa podejścia jedno to ldquoupakowanierdquo zwracanych wartości ndash możnastworzyć tak zwaną strukturę ktoacutera będzie przechowywała kilka zmiennych (jest to opisanew rozdziale Typy złożone) Prostszym sposobem jest zwracanie jednej z wartości w normalnysposoacuteb a pozostałych jako parametroacutew Za chwilę dowiesz się jak to zrobić jeśli chcesz zo-baczyć przykład możesz przyjrzeć się funkcji scanf() z biblioteki standardowej

1162 Przekazywanie parametroacutew

Gdy wywołujemy funkcję wartość argumentoacutew z ktoacuterymi ją wywołujemy jest kopiowanado funkcji Kopiowana mdash to znaczy że nie możemy normalnie zmienić wartości zewnętrz-nych dla funkcji zmiennych Formalnie moacutewi się że w C argumenty są przekazywane przezwartość czyli wewnątrz funkcji operujemy tylko na ich kopiach

Możliwe jest modyfikowanie zmiennych przekazywanych do funkcji jako parametry mdashale do tego w C potrzebne są wskaźniki

1163 Funkcje rekurencyjne

Język Cmamożliwość tworzenia tzw funkcji rekurencyjny Jest to funkcja ktoacuteraw swojejwłasnej definicji (ciele) wywołuje samą siebie Najbardziej klasycznym przykładem może tubyć silnia Napiszmy sobie zatem naszą funkcję rekurencyjną ktoacutera oblicza silnię

int silnia (int liczba)

int silif (liczbalt0) return 0 wywołanie jest bezsensowne

zwracamy 0 jako kod błędu if (liczba==0 || liczba==1) return 1sil = liczbasilnia(liczba-1)return sil

Musimy być ostrożni przy funkcjach rekurencyjnych gdyż łatwo za ich pomocą utworzyćfunkcję ktoacutera będzie sama siebie wywoływała w nieskończoność a co za tym idzie będzie za-wieszała program Tutaj pierwszymi instrukcjami if ustalamy ldquowarunki stopurdquo gdzie kończy

6Uwaga Makra EXIT SUCCESS i EXIT FAILURE te służą tylko i wyłącznie jako wartości do zwracania przezfunkcję main() Nigdzie indziej nie mają one zastosowania

84 ROZDZIAŁ 11 FUNKCJE

się wywoływanie funkcji przez samą siebie a następnie określamy jak funkcja będzie wy-woływać samą siebie (odjęcie jedynki od argumentu co do ktoacuterego wiemy że jest dodatnigwarantuje że dojdziemy do warunku stopu w skończonej liczbie krokoacutew)

Warto też zauważyć że funkcje rekurencyjne czasami mogą być znacznie wolniejsze niżpodejście nierekurencyjne (iteracyjne przy użyciu pętli) Flagowym przykłademmoże tu byćfunkcja obliczająca wyrazy ciągu Fibonacciego

include ltstdiohgt

unsigned count

unsigned fib_rec(unsigned n) ++countreturn nlt2 n (fib_rec(n-2) + fib_rec(n-1))

unsigned fib_it (unsigned n) unsigned a = 0 b = 0 c = 1++countif (n) return 0while (--n)

++counta = bb = cc = a + b

return c

int main(void) unsigned n resultprintf(Ktory element ciagu Fibonacciego obliczyc )while (scanf(d ampn)==1)

count = 0result = fib_rec(n)printf(fib_ret(3u) = 6u (wywolan 5u)n n result count)

count = 0result = fib_it (n)printf(fib_it (3u) = 6u (wywolan 5u)n n result count)

return 0

W tym przypadku funkcja rekurencyjna choć łatwiejsza w napisaniu jest bardzo nie-efektywna

116 DALSZE INFORMACJE 85

1164 Deklarowanie funkcji

Czasami możemy chcieć przed napisaniem funkcji poinformować kompilator że dana funkcjaistnieje Niekiedy kompilator może zaprotestować jeśli użyjemy funkcji przed określeniemjaka to funkcja na przykład

int a()

return b(0)

int b(int p)

if( p == 0 )return 1

elsereturn a()

int main()

return b(1)

W tym przypadku nie jesteśmy w stanie zamienić a i b miejscami bo obie funkcje korzy-stają z siebie nawzajem Rozwiązaniem jest wcześniejsze zadeklarowanie funkcji Deklara-cja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji(przed otwierającym nawiasem klamrowym) z dodatkowo dodanym średnikiem na końcu Wnaszym przykładzie wystarczy na samym początku wstawić

int b(int p)

W deklaracji można pominąć nazwy parametroacutew funkcji

int b(int)

Bardzo częstym zwyczajem jest wypisanie przed funkcją main samych prototypoacutew funk-cji by ich definicje umieścić po definicji funkcji main np

int a(void)int b(int p)

int main()

return b(1)

int a()

return b(0)

86 ROZDZIAŁ 11 FUNKCJE

int b(int p)

if( p == 0 )return 1

elsereturn a()

Z poprzednich rozdziałoacutew pamiętasz że na początku programu dołączaliśmy tzw plikinagłoacutewkowe Zawierają one właśnie prototypy funkcji i ułatwiają pisanie dużych progra-moacutew Dalsze informacje o plikach nagłoacutewkowych zawarte są w rozdziale Tworzenie biblio-tek

1165 Zmienna liczba parametroacutew

Zauważyłeś zapewne że używając funkcji printf() lub scanf() po argumencie zawierającymtekst z odpowiednimi modyfikatorami mogłeś podać praktycznie nieograniczoną liczbę ar-gumentoacutew Zapewne deklaracja obu funkcji zadziwi Cię jeszcze bardziej

int printf(const char format )int scanf(const char format )

Jak widzisz w deklaracji zostały użyte kropki Otoacuteż język C ma możliwość przekazywa-nia nieograniczonej liczby argumentoacutew do funkcji (tzn jedynym ograniczeniem jest rozmiarstosu programu) Cała zabawa polega na tym aby umieć dostać się do odpowiedniego ar-gumentu oraz poznać jego typ (używając funkcji printf mogliśmy wpisać jako argument do-wolny typ danych) Do tego celu możemy użyć wszystkich ciekawostek zawartych w plikunagłoacutewkowym stdargh

Załoacuteżmy że chcemy napisać prostą funkcję ktoacutera dajmy na to mnoży wszystkie swojeargumenty (zakładamy że argumenty są typu int) Przyjmujemy przy tym że ostatni argu-ment będzie Będzie ona wyglądała tak

include ltstdarghgt

int mnoz (int pierwszy )

va_list argint iloczyn = 1 tva_start (arg pierwszy)for (t = pierwszy t t = va_arg(arg int))

iloczyn = tva_end (arg)return iloczyn

va list oznacza specjalny typ danych w ktoacuterym przechowywane będą argumenty prze-kazane do funkcji ldquova startrdquo inicjuje arg do dalszego użytku Jako drugi parametr musimypodać nazwę ostatniego znanego argumentu funkcji Makropolecenie va arg odczytuje ko-lejne argumenty i przekształca je do odpowiedniego typu danych Na zakończenie używanejest makro va end mdash jest ono obowiązkowe

117 ZOBACZ TEŻ 87

Oczywiście tak samo jak w przypadku funkcji printf() czy scanf() argumenty nie musząbyć takich samych typoacutew Rozważmy dla przykładu funkcję podobną do printf() ale znacznieuproszczoną

include ltstdarghgt

void wypisz(const char format ) va_list argva_start (arg format)for ( format ++format)

switch (format) case i printf(d va_arg(arg int)) breakcase I printf(u va_arg(arg unsigned)) breakcase l printf(ld va_arg(arg int)) breakcase L printf(lu va_arg(arg unsigned long)) breakcase f printf(f va_arg(arg double)) breakcase x printf(x va_arg(arg unsigned)) breakcase X printf(X va_arg(arg unsigned)) breakcase s printf(s va_arg(arg const char )) breakdefault putc(format)

va_end (arg)

Przyjmuje ona jako argument ciąg znakoacutew w ktoacuterych niektoacutere instruują funkcję bypobrała argument i go wypisała Nie przejmuj się jeżeli nie rozumiesz wyrażeń format i++format Istotne jest to że pętla sprawdza po kolei wszystkie znaki formatu

1166 Ezoteryka C

C ma wiele niuansoacutew o ktoacuterych wielu programistoacutew nie wie lub łatwo o nich zapomina

jeśli nie podamy typu wartości zwracanej w funkcji zostanie przyjęty typ int (wedługnajnowszego standardu C nie podanie typu wartości jest zwracane jako błąd)

jeśli nie podamy żadnych parametroacutew funkcji to funkcja będzie używała zmiennej ilo-ści parametroacutew (inaczej niż wC++ gdzie przyjęte zostanie że funkcja nie przyjmuje ar-gumentoacutew) Aby wymusić pustą listę argumentoacutew należy napisać int funkcja(void)(dotyczy to jedynie prototypoacutew czy deklaracji funkcji)

jeśli nie użyjemy w funkcji instrukcji return wartość zwracana będzie przypadkowa(dostaniemy śmieci z pamięci)

Kompilator C++ użyty do kompilacji kodu C najczęściej zaprotestuje i ostrzeże nas jeśliużyjemy powyższych konstrukcji Natomiast czysty kompilator C z domyślnymi ustawie-niami nie napisze nic i bez mrugnięcia okiem skompiluje taki kod

117 Zobacz też C++Funkcje inline mdash funkcje rozwijane wmiejscu wywoływania (dostępne też w stan-

dardzie C)

88 ROZDZIAŁ 11 FUNKCJE

C++Przeciążanie funkcji

Rozdział 12

Preprocesor

121 Wstęp

W języku C wszystkie linijki zaczynające się od symbolu rdquo nie podlegają bezpośrednio pro-cesowi kompilacji Są to natomiast instrukcje preprocesora mdash elementu kompilatora ktoacuteryanalizuje plik źroacutedłowy w poszukiwaniu wszystkich wyrażeń zaczynających się od rdquo Napodstawie tych instrukcji generuje on kod w czystymrdquo języku C ktoacutery następnie jest kom-pilowany przez kompilator Ponieważ za pomocą preprocesora można niemal sterowaćrdquokompilatorem daje on niezwykłe możliwości ktoacutere nie były dotąd znane w innych językachprogramowania Aby przekonać się jak wygląda kod przetworzony przez preprocesor użyj(w kompilatorze gcc) przełącznika -Erdquo

gcc testc -E -o testtxt

W pliku testtxt zostanie umieszczony cały kod w postaci ktoacutera zdatna jest do przetwo-rzenia przez kompilator

122 Dyrektywy preprocesora

Dyrektywy preprocesora są towyrażenia ktoacutere występują zaraz za symbolem rdquo i to właśnieza ich pomocą możemy używać preprocesora Dyrektywa zaczyna się od znaku i kończysię wraz z końcem linii Aby przenieść dalszą część dyrektywy do następnej linii należyzakończyć linię znakiem rdquo

define add(ab) a+b

Omoacutewimy teraz kilka ważniejszych dyrektyw

1221 include

Najpopularniejsza dyrektywa wstawiająca w swoje miejsce treść pliku podanego w nawia-sach ostrych lub cudzysłowie Składnia

89

90 ROZDZIAŁ 12 PREPROCESOR

Przykład 1

include ltplik_naglowkowy_do_dolaczeniagt

Za pomocą include możemy dołączyć dowolny plik mdash niekoniecznie plik nagłoacutewkowy

Przykład 2

include plik_naglowkowy_do_dolaczenia

Jeżeli nazwa pliku nagłoacutewkowego będzie ujęta w nawiasy ostre (przykład ) to kompi-lator poszuka go wśroacuted własnych plikoacutew nagłoacutewkowych (ktoacutere najczęściej się znajdują wpodkatalogu includesrdquo w katalogu kompilatora) Jeśli jednak nazwa ta będzie ujęta w po-dwoacutejne cudzysłowy(przykład ) to kompilator poszuka jej w katalogu w ktoacuterym znajdujesię kompilowany plik (można zmienić to zachowanie w opcjach niektoacuterych kompilatoroacutew)Przy użyciu tej dyrektywy można także wskazać dokładne położenie plikoacutew nagłoacutewkowychpoprzez wpisanie bezwzględnej lub względnej ścieżki dostępu do tego pliku nagłoacutewkowego

Przykład 3 mdash ścieżka bezwzględna do pliku nagłoacutewkowego w Linuksie i w Windowsie

Opis W miejsce jednej i drugiej linijki zostanie wczytany plik umieszczony w danej lokali-zacji

include usrincludeplik_nagłoacutewkowyhinclude Cborlandincludesplik_nagłoacutewkowyh

Przykład 4 mdash ścieżka względna do pliku nagłoacutewkowego

Opis W miejsce linijki zostanie wczytany plik umieszczony w katalogu katalogrdquo a tenkatalog jest w katalogu z plikiem źroacutedłowym Inaczej moacutewiąc jeśli plik źroacutedłowy jest wkatalogu homeuserdokumentyzrodlardquo to plik nagłoacutewkowy jest umieszczony w kataloguhomeuserdokumentyzrodlakatalogrdquo

include katalog1plik_naglowkowyh

Przykład 5 mdash ścieżka względna do pliku nagłoacutewkowego

Opis Jeśli plik źroacutedłowy jest umieszczony w katalogu homeuserdokumentyzrodlardquo toplik nagłoacutewkowy znajduje się w katalogu homeuserdokumentykatalogkatalogrdquo

include katalog1katalog2plik_naglowkowyh

Więcej informacji możesz uzyskać w rozdziale Biblioteki

1222 define

Linia pozwalająca zdefiniować stałą funkcję lub słowo kluczowe ktoacutere będzie potem pod-mienione w kodzie programu na odpowiednią wartość lub może zostać użyte w instrukcjachwarunkowych dla preprocesora Składnia

define NAZWA_STALEJ WARTOSC

lub

define NAZWA_STALEJ

122 DYREKTYWY PREPROCESORA 91

Przykład

define LICZBA mdash spowoduje że każde wystąpienie słowa LICZBA w kodzie zostanie za-stąpione oacutesemkądefine SUMA(ab) (a+b) mdash spowoduje ze każde wystąpienie wywołania funkcjirdquo SUMA zo-stanie zastąpione przez sumę argumentoacutew

1223 undef

Ta instrukcja odwołuje definicję wykonaną instrukcją define

undef STALA

1224 instrukcje warunkowe

Preprocesor zawiera roacutewnież instrukcje warunkowe pozwalające nawyboacuter tego coma zostaćskompilowane w zależności od tego czy stała jest zdefiniowana lub jaką ma wartość

if elif else endif

Te instrukcje uzależniają kompilacje od warunkoacutew Ich działanie jest podobne do instrukcjiwarunkowych w samym języku C I tak

if wprowadza warunek ktoacutery jeśli nie jest prawdziwy powoduje pominięcie kompilowaniakodu aż do napotkania jednej z poniższych instrukcji

else spowoduje skompilowanie kodu jeżeli warunek za if jest nieprawdziwy aż do napo-tkania ktoacuteregoś z poniższych instrukcji

elif wprowadza nowy warunek ktoacutery będzie sprawdzony jeżeli poprzedni był niepraw-dziwy Stanowi połączenie instrukcji if i else

endif zamyka blok ostatniej instrukcji warunkowej

Przykład

if INSTRUKCJE == 2printf (Podaj liczbę z przedziału 10 do 0n) 1

elif INSTRUKCJE == 1printf (Podaj liczbę ) 2

elseprintf (Podaj parametr ) 3

endifscanf (dn ampliczba)4

wiersz nr zostanie skompilowany jeżeli stała INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany gdy INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany w pozostałych wypadkach

wiersz nr będzie kompilowany zawsze

92 ROZDZIAŁ 12 PREPROCESOR

ifdef ifndef else endif

Te instrukcje warunkują kompilację od tego czy odpowiednia stała została zdefiniowana

ifdef spowoduje że kompilator skompiluje poniższy kod tylko gdy została zdefiniowanaodpowiednia stała

ifndef ma odwrotne działanie do ifdef a mianowicie brak definicji odpowiedniej stałejumożliwia kompilacje poniższego kodu

elseendif mają identyczne zastosowanie jak te z powyższej grupy

Przykład

define INFO definicja stałej INFOifdef INFO

printf (Twoacutercą tego programu jest Jan Kowalskin)1endififndef INFO

printf (Twoacutercą tego programu jest znany programistan)2endif

To czy dowiemy się kto jest twoacutercą tego programu zależy czy instrukcja definiująca stałąINFO będzie istnieć W powyższym przypadku na ekranie powinno się wyświetlić

Twoacutercą tego programu jest Jan Kowalski

1225 error

Powoduje przerwanie kompilacji i wyświetlenie tekstu ktoacutery znajduje się za tą instrukcjąPrzydatne gdy chcemy zabezpieczyć się przed zdefiniowaniem nieodpowiednich stałych

Przykład

if BLAD == 1error Poważny błąd kompilacjiendif

Co jeżeli zdefiniujemy stałą BLAD z wartością Spowoduje to wyświetlenie w trakcie kom-pilacji komunikatu podobnego do poniższego

Fatal error programc 6 Error directive Poważny błąd kompilacjiin function main() 1 errors in Compile

wraz z przerwaniem kompilacji

1226 warning

Wyświetla tekst zawarty w cudzysłowach jako ostrzeżenie Jest często używany do sygna-lizacji programiście że dana część programu jest przestarzała lub może sprawiać problemy

122 DYREKTYWY PREPROCESORA 93

Przykład

warning To jest bardzo prosty program

Spowoduje to takie oto zachowanie kompilatora

testc32 warning warning To jest bardzo prosty program

Użycie dyrektywy warning nie przerywa procesu kompilacji i służy tylko do wyświetlaniakomunikatoacutew dla programisty w czasie kompilacji programu

1227 line

Powoduje wyzerowanie licznika linii kompilatora ktoacutery jest używany przy wyświetlaniuopisu błędoacutew kompilacji Pozwala to na szybkie znalezienie możliwej przyczyny błędu wrozbudowanym programie

Przykład

printf (Podaj wartość funkcji)lineprintf (W przedziale od 10 do 0n) tutaj jest błąd - brak cudzysłowu zamykającego

Jeżeli teraz nastąpi proacuteba skompilowania tego kodu to kompilator poinformuje że wystąpiłbłąd składni w linii a nie np

1228 Makra

Preprocesor języka C umożliwia też tworzenie makr czyli automatycznie wykonywanychczynności Makra deklaruje się za pomocą dyrektywy define

define MAKRO(arg1 arg2 ) (wyrażenie)

Wmomencie wystąpienia MAKRA w tekście preprocesor automatycznie zamieni makrona wyrażenie Makra mogą być pewnego rodzaju alternatywami dla funkcji ale powinnosię ich używać tylko w specjalnych przypadkach Ponieważ makro sprowadza się do pro-stego zastąpienia przez preprocesor wywołania makra przez jego tekst jest bardzo podatnena trudne do zlokalizowania błędy (kompilator będzie podawał błędy wmiejscach w ktoacuterychnic nie widzimy mdash bo preprocesor wstawił tam tekst) Makra są szybsze (nie następuje wy-wołanie funkcji ktoacutere zawsze zajmuje trochę czasu1) ale też mniej bezpieczne i elastyczneniż funkcje

Przeanalizujmy teraz fragment kodu

include ltstdiohgtdefine KWADRAT(x) ((x)(x))

int main ()

printf (2 do kwadratu wynosi dn KWADRAT(2))return 0

1Tak naprawdę wg standardu C99 istnieje możliwość napisania funkcji ktoacuterej kod także będzie wstawiany wmiejscu wywołania Odbywa się to dzięki inline

94 ROZDZIAŁ 12 PREPROCESOR

Preprocesor w miejsce wyrażenia KWADRAT(2) wstawił ((2)(2)) Zastanoacutewmy się costałoby się gdybyśmy napisali KWADRAT(2) Preprocesor po prostu wstawi napis do koduco da wyrażenie ((2)(2)) ktoacutere jest nieprawidłowe Kompilator zgłosi błąd ale pro-gramista widzi tylko w kodzie użycie makra a nie prawdziwą przyczynę błędu Widać tu żebezpieczniejsze jest użycie funkcji ktoacutere dają możliwość wyspecyfikowania typoacutew argumen-toacutew

Nawet jeżeli program się skompiluje to makro może dawać nieoczekiwany wynik Jesttak w przypadku poniższego kodu

int x = 1int y = KWADRAT(++x)

Dzieje się tak dlatego że makra rozwijane są przez preprocesor i kompilator widzi kod

int x = 1int y = ((++x)(++x))

Roacutewnież poniższe makra są błędne pomimo że opisany problem w nich nie występuje

define SUMA(a b) a + bdefine ILOCZYN(a b) a b

Dają one nieoczekiwane wyniki dla wywołań

SUMA(2 2) 2 6 zamiast 8 ILOCZYN(2 + 2 2 + 2) 8 zamiast 16

Z tego powodu istotne jest użycie nawiasoacutew

define SUMA(a b) ((a) + (b))define ILOCZYN(a b) ((a) (b))

1229 oraz

Dość ciekawe możliwości ma w makrach znak rdquo Zamienia on stojący za nim identyfikatorna napis

include ltstdiohgtdefine wypisz(x) printf(s=in x x)

int main()

int i=1char a=5wypisz(i)wypisz(a)return 0

Program wypisze

i=1a=5

123 PREDEFINIOWANE MAKRA 95

Czyli wypisz(a) jest rozwijane w printf(s=in a a)Natomiast znaki rdquo łączą dwie nazwy w jedną Przykład

include ltstdiohgtdefine abc(x) int zmienna x

int main()

abc(nasza) dzięki temu zadeklarujemy zmienną o nazwie zmiennanasza zmiennanasza = 2return 0

Więcej o dobrych zwyczajach w tworzeniu makr można się dowiedzieć w rozdziale Po-wszechne praktyki

123 Predefiniowane makraW języku wprowadzono roacutewnież serię predefiniowanych makr ktoacutere mają ułatwić życie pro-gramiście Oto one

DATE mdash data w momencie kompilacji

TIME mdash godzina w momencie kompilacji

FILE mdash łańcuch ktoacutery zawiera nazwę pliku ktoacutery aktualnie jest kompilowany przezkompilator

LINE mdash definiuje numer linijki

STDC mdash w kompilatorach zgodnych ze standardem ANSI lub nowszym makro toprzyjmuje wartość

STDC VERSION mdash zależnie od poziomu zgodności kompilatora makro przyjmuje roacuteżnewartości

ndash jeżeli kompilator jest zgodny z ANSI (rok ) makro nie jest zdefiniowane

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199409L

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199901L

Warto roacutewnież wspomnieć o identyfikatorze func zdefiniowanym w standardzie Cktoacuterego wartość to nazwa funkcji

Sproacutebujmy użyć tych makr w praktyce

include ltstdiohgt

if __STDC_VERSION__ gt= 199901L Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go define BUG(message) fprintf(stderr sd s (w funkcji s)n

__FILE__ __LINE__ message __func__)else Jezeli __func__ nie ma to go nie używamy

96 ROZDZIAŁ 12 PREPROCESOR

define BUG(message) fprintf(stderr sd sn __FILE__ __LINE__ message)

endif

int main(void) printf(Program ABC data kompilacji s sn __DATE__ __TIME__)

BUG(Przykladowy komunikat bledu)return 0

Efekt działania programu gdy kompilowany jest kompilatorem C

Program ABC data kompilacji Sep 1 2008 191213testc17 Przykladowy komunikat bledu (w funkcji main)

Gdy kompilowany jest kompilatorem ANSI C

Program ABC data kompilacji Sep 1 2008 191316testc17 Przykladowy komunikat bledu

Rozdział 13

Biblioteka standardowa

131 Czym jest biblioteka

Bibliotekę w języku C stanowi zbioacuter skompilowanych wcześniej funkcji ktoacutery można łączyćz programem Biblioteki tworzy się aby udostępnić zbioacuter pewnych ldquowyspecjalizowanychrdquofunkcji do dyspozycji innych programoacutew Tworzenie bibliotek jest o tyle istotne że takiepodejście znacznie ułatwia tworzenie nowych programoacutew Łatwiej jest utworzyć program woparciu o istniejące biblioteki niż pisać programwraz zewszystkimi potrzebnymi funkcjami1

132 Po co nam biblioteka standardowa

W ktoacuterymś z początkowych rozdziałoacutew tego podręcznika napisane jest że czysty język C niemoże zbyt wiele Tak naprawdę to język C sam w sobie praktycznie nie ma mechanizmoacutewdo obsługi np wejścia-wyjścia Dlatego też większość systemoacutew operacyjnych posiada tzwbibliotekę standardową zwaną też biblioteką języka C To właśnie w niej zawarte są pod-stawowe funkcjonalności dzięki ktoacuterym twoacutej program może np napisać coś na ekranie

1321 Jak skonstruowana jest biblioteka standardowa

Zapytacie zapewne jak biblioteka standardowa realizuje te funkcje skoro sam język C tegonie potrafi Odpowiedź jest prosta mdash biblioteka standardowa nie jest napisana w samym ję-zyku C Ponieważ C jest językiem tłumaczonym do kodu maszynowego to w praktyce niema żadnych przeszkoacuted żeby np połączyć go z językiem niskiego poziomu jakim jest npasembler Dlatego biblioteka C z jednej strony udostępnia gotowe funkcje w języku C a zdrugiej za pomocą niskopoziomowych mechanizmoacutew2 komunikuje się z systemem operacyj-nym ktoacutery wykonuje odpowiednie czynności

133 Gdzie są funkcje z biblioteki standardowej

Pisząc program w języku C używamy roacuteżnego rodzaju funkcji takich jak np printf Niejesteśmy jednak ich autorami mało tego nie widzimy nawet deklaracji tych funkcji w naszymprogramie Pamiętacie program ldquoHello worldrdquo Zaczynał on się od takiej oto linijki

1Początkujący programista zapewne nie byłby w stanie napisać nawet funkcji printf2Takich jak np wywoływanie przerwań programowych

97

98 ROZDZIAŁ 13 BIBLIOTEKA STANDARDOWA

include ltstdiohgt

linijka ta oznacza ldquow tym miejscu wstaw zawartość pliku stdiohrdquo Nawiasy ldquoltrdquo i ldquogtrdquooznaczają że plik stdioh znajduje się w standardowym katalogu z plikami nagłoacutewkowymiWszystkie pliki z rozszerzeniem h są właśnie plikami nagłoacutewkowymi Wroacutećmy teraz do te-matu biblioteki standardowej Każdy system operacyjny ma za zadanie wykonywać pewnefunkcje na rzecz programoacutew Wszystkie te funkcje zawarte są właśnie w bibliotece standar-dowej W systemach z rodziny UNIX nazywa się ją LibC (biblioteka języka C) To tamwłaśnieznajduje się funkcja printf scanf puts i inne

Oproacutecz podstawowych funkcji wejścia-wyjścia biblioteka standardowa udostępnia teżmożliwość wykonywania funkcji matematycznych komunikacji przez sieć oraz wykonywa-nia wielu innych rzeczy

1331 Jeśli biblioteka nie jest potrzebna

Czasami korzystanie z funkcji bibliotecznych oraz standardowych plikoacutew nagłoacutewkowych jestniepożądane np wtedy gdy programista pisze swoacutej własny system operacyjny oraz biblio-tekę do niego Aby wyłączyć używanie biblioteki C w opcjach kompilatora GCC możemydodać następujące argumenty

-nostdinc -fno-builtin

134 Opis funkcji biblioteki standardowejPodręcznik C na Wikibooks zawiera opis dużej części biblioteki standardowej C

Indeks alfabetyczny

Indeks tematyczny

W systemach uniksowych możesz uzyskać pomoc dzięki narzędziu man przykładowopisząc

man printf

135 UwagiProgramy w języku C++ mogą dokładnie w ten sam sposoacuteb korzystać z biblioteki standar-dowej ale zalecane jest by robić to raczej w trochę odmienny sposoacuteb właściwy dla C++Szczegoacuteły w podręczniku C++

Rozdział 14

Czytanie i pisanie do plikoacutew

141 Pojęcie plikuNa początku dobrze by było abyś dowiedział się czym jest plik Odpowiedni artykuł do-stępny jest w Wikipedii Najprościej moacutewiąc plik to pewne dane zapisane na dysku

142 Identyfikacja plikuKażdy z nas korzystając na co dzień z komputera przyzwyczaił się do tego że plik ma okre-śloną nazwę Jednak w pisaniu programu posługiwanie się całą nazwą niosło by ze sobą conajmniej dwa problemy

pamięciożerność mdash przechowywanie całego (czasami nawet -bajtowego łańcucha)zajmuje niepotrzebnie pamięć

ryzyko błędoacutew (owe błędy szerzej omoacutewione zostały w rozdziale Napisy)

Aby uprościć korzystanie z plikoacutew programiści wpadli na pomysł aby identyfikatorempliku stała się liczba Dzięki temu kod programu stał się czytelniejszy oraz wyeliminowanokonieczność ciągłego korzystania z łańcuchoacutew Jednak sam plik nadal jest identyfikowany poswojej nazwie Aby ldquoprzetworzyćrdquo nazwę pliku na odpowiednią liczbę korzystamy z funkcjiopen lub fopen Roacuteżnica wyjaśniona jest poniżej

143 Podstawowa obsługa plikoacutewIstnieją dwie metody obsługi czytania i pisania do plikoacutew

wysokopoziomowa

niskopoziomowa

Nazwy funkcji z pierwszej grupy zaczynają się od litery ldquordquo (np fopen() fread() fclose())a identyfikatorem pliku jest wskaźnik na strukturę typu FILE Owa struktura to pewna grupazmiennych ktoacutera przechowuje dane o danym pliku mdash jak na przykład aktualną pozycję wnim Szczegoacutełami nie musisz się przejmować funkcje biblioteki standardowej same zajmująsię wykorzystaniem struktury FILE programista może więc zapomnieć czym tak naprawdęjest struktura FILE i traktować taką zmienną jako ldquouchwytrdquo identyfikator pliku

99

100 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

Druga grupa to funkcje typu read() open() write() i close()Podstawowym identyfikatorem pliku jest liczba całkowita ktoacutera jednoznacznie identy-

fikuje dany plik w systemie operacyjnym Liczba ta w systemach typu jest nazywanadeskryptorem pliku

Należy pamiętać że nie wolno nam używać funkcji z obu tych grup jednocześnie w sto-sunku do jednego otwartego pliku tzn nie można najpierw otworzyć pliku za pomocą fo-pen() a następnie odczytywać danych z tego samego pliku za pomocą read()

Czym roacuteżnią się oba podejścia do obsługi plikoacutew Otoacuteż metoda wysokopoziomowa maswoacutej własny bufor w ktoacuterym znajdują się dane po odczytaniu z dysku a przed wysłaniemich do programu użytkownika W przypadku funkcji niskopoziomowych dane kopiowane sąbezpośrednio z pliku do pamięci programu W praktyce używanie funkcji wysokopoziomo-wych jest prostsze a przy czytaniu danych małymi porcjami roacutewnież często szybsze i właśnieten model zostanie tutaj zaprezentowany

1431 Dane znakowe

Skupimy się teraz na najprostszym zmożliwych zagadnień mdash zapisie i odczycie pojedynczychznakoacutew oraz całych łańcuchoacutew

Napiszmy zatem nasz pierwszy program ktoacutery stworzy plik ldquotesttxtrdquo i umieści w nimtekst ldquoHello worldrdquo

include ltstdiohgtinclude ltstdlibhgt

int main ()

FILE fp używamy metody wysokopoziomowej musimy mieć zatem identyfikator pliku uwaga na gwiazdkę

char tekst[] = Hello worldif ((fp=fopen(testtxt w))==NULL)

printf (Nie mogę otworzyć pliku testtxt do zapisun)exit(1)

fprintf (fp s tekst) zapisz nasz łańcuch w pliku fclose (fp) zamknij plik return 0

Teraz omoacutewimy najważniejsze elementy programu Jak już było wspomniane wyżej doidentyfikacji pliku używa się wskaźnika na strukturę FILE (czyli FILE ) Funkcja fopenzwraca oacutew wskaźnik w przypadku poprawnego otwarcia pliku bądź też NULL gdy plik niemoże zostać otwarty Pierwszy argument funkcji to nazwa pliku natomiast drugi to trybdostępu mdash w oznacza ldquowriterdquo (pisanie) zwroacutecony ldquouchwytrdquo do pliku będzie moacutegł być wyko-rzystany jedynie w funkcjach zapisujących dane I odwrotnie gdy otworzymy plik podająctryb r (ldquoreadrdquo czytanie) będzie można z niego jedynie czytać dane Funkcja fopen zostaładokładniej opisana w odpowiedniej części rozdziału o bibliotece standardowej

Po zakończeniu korzystania z pliku należy plik zamknąć Robi się to za pomocą funk-cji fclose Jeśli zapomnimy o zamknięciu pliku wszystkie dokonane w nim zmiany zostanąutracone

143 PODSTAWOWA OBSŁUGA PLIKOacuteW 101

1432 Pliki a strumienie

Można zauważyć że do zapisu do pliku używamy funkcji fprintf ktoacutera wygląda bardzopodobnie do printf mdash jedyną roacuteżnicą jest to że w fprintf musimy jako pierwszy argu-ment podać identyfikator pliku Nie jest to przypadek mdash obie funkcje tak naprawdę robiątak samo Używana do wczytywania danych z klawiatury funkcja scanf też ma swoacutej od-powiednik wśroacuted funkcji operujących na plikach mdash jak nietrudno zgadnąć nosi ona nazwęfscanf

W rzeczywistości język C traktuje tak samo klawiaturę i plik mdash są to źroacutedła danych po-dobnie jak ekran i plik do ktoacuterych można dane kierować Jest to myślenie typowe dla sys-temoacutew typu UNIX jednak dla użytkownikoacutew przyzwyczajonych do systemu Windows albojęzykoacutew typu Pascal może być to co najmniej dziwne Nie da się ukryć że między klawia-turą i plikiem na dysku zachodzą podstawowe roacuteżnice i dostęp do nich odbywa się inaczejmdash jednak funkcje języka C pozwalają nam o tym zapomnieć i same zajmują się szczegoacutełamitechnicznymi Z punktu widzenia programisty urządzenia te sprowadzają się do nadanegoim identyfikatora Uogoacutelnione pliki nazywa się w C strumieniami

Każdy program w momencie uruchomienia ldquootrzymujerdquo od razu trzy otwarte strumienie

stdin (wejście)

stdout (wyjście)

stderr (wyjście błędoacutew)

(aby z nich korzystać należy dołączyć plik nagłoacutewkowy stdioh)Pierwszy z tych plikoacutew umożliwia odczytywanie danych wpisywanych przez użytkow-

nika natomiast pozostałe dwa służą do wyprowadzania informacji dla użytkownika oraz po-wiadamiania o błędach

Warto tutaj zauważyć że konstrukcja

fprintf (stdout Hej ja działam)

jest roacutewnoważna konstrukcji

printf (Hej ja działam)

Podobnie jest z funkcją scanf()

fscanf (stdin d ampzmienna)

działa tak samo jak

scanf(d ampzmienna)

1433 Obsługa błędoacutew

Jeśli nastąpił błąd możemy się dowiedzieć o jego przyczynie na podstawie zmiennej errnozadeklarowanej w pliku nagłoacutewkowym errnoh Możliwe jest też wydrukowanie komunikatuo błedzie za pomocą funkcji perror Na przykład używając

fp = fopen (tego pliku nie ma r)if( fp == NULL )

perror(błąd otwarcia pliku)exit(-10)

102 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

dostaniemy komunikat

błąd otwarcia pliku No such file or directory

1434 Zaawansowane operacje

Pora na kolejny tym razem bardziej złożony przykład Oto kroacutetki program ktoacutery swojewejście zapisuje do pliku o nazwie podanej w linii poleceń

include ltstdiohgtinclude ltstdlibhgt program udający bardzo prymitywną wersję programu tee(1)

int main (int argc char argv[])

FILE fpint cif (argc lt 2)

fprintf (stderr Uzycie s nazwa_plikun argv[0])exit (-1)

fp = fopen (argv[1] w)if (fp)

fprintf (stderr Nie moge otworzyc pliku sn argv[1])exit (-1)

printf(Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczycn)while ( (c = fgetc(stdin)) = EOF)

fputc (c stdout)fputc (c fp)

fclose(fp)return 0

Tym razem skorzystaliśmy już z dużo większego repertuaru funkcji Między innymimożna zauważyć tutaj funkcję fputc() ktoacutera umieszcza pojedynczy znak w pliku Ponadto wwyżej zaprezentowanym programie została użyta stała EOF ktoacutera reprezentuje koniec pliku(ang End Of File) Powyższy program otwiera plik ktoacuterego nazwa przekazywana jest jakopierwszy argument programu a następnie kopiuje dane z wejścia programu (stdin) na wyj-ście (stdout) oraz do utworzonego pliku (identyfikowanego za pomocą fp) Program robi todotąd aż naciśniemy kombinację klawiszy Ctrl+D(w systemach Unixowych) lub Ctrl+Z(wWindows) ktoacutera wyśle do programu informację że skończyliśmy wpisywać dane Programwyjdzie wtedy z pętli i zamknie utworzony plik

144 Rozmiar plikuDzięki standardowym funkcjom języka C możemy min określić długość pliku Do tego celusłużą funkcje fsetpos fgetpos oraz fseek Ponieważ przy każdym odczyciezapisie zdo plikuwskaźnik niejako ldquoprzesuwardquo się o liczbę przeczytanychzapisanych bajtoacutew Możemy jednak

145 PRZYKŁAD mdash PLIKI GRAFICZNY 103

ustawić wskaźnik w dowolnie wybranym miejscu Do tego właśnie służą wyżej wymienionefunkcje Aby odczytać rozmiar pliku powinniśmy ustawić nasz wskaźnik na koniec plikupo czym odczytać ile bajtoacutew od początku pliku się znajdujemy Wiem brzmi to strasznieale działa wyjątkowo prosto i skutecznie Użyjemy do tego tylko dwoacutech funkcji fseek orazfgetpos Pierwsza służy do ustawiania wskaźnika na odpowiedniej pozycji w pliku a drugado odczytywania na ktoacuterym bajcie pliku znajduje się wskaźnik Kod ktoacutery określa rozmiarpliku znajduje się tutaj

include ltstdiohgt

int main (int argc char argv)

FILE fp = NULLfpos_t dlugoscif (argc = 2)

printf (Użycie s ltnazwa plikugtn argv[0])return 1

if ((fp=fopen(argv[1] rb))==NULL) printf (Błąd otwarcia pliku sn argv[1])return 1

fseek (fp 0 SEEK_END) ustawiamy wskaźnik na koniec pliku fgetpos (fp ampdlugosc)printf (Rozmiar pliku dn dlugosc)fclose (fp)return 0

Znajomość rozmiaru pliku przydaje się w wielu roacuteżnych sytuacjach więc dobrze prze-analizuj przykład

145 Przykład mdash pliki graficznyNajprostszym przykładem rastrowego pliku graficznego jest plik Poniższy program po-kazuje jak utworzyć plik w katalogu roboczym programu Do zapisu

nagłoacutewka pliku używana jest funkcja fprintf

tablicy do pliku używana jest funkcja fwrite

include ltstdiohgtint main()

const int dimx = 800const int dimy = 800int i jFILE fp = fopen(firstppm wb) b - tryb binarny fprintf(fp P6nd dn255n dimx dimy)for(j=0 jltdimy ++j)

for(i=0 iltdimx ++i)static unsigned char color[3]

104 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

color[0]=i 255 red color[1]=j 255 green color[2]=(ij) 255 blue fwrite(color13fp)

fclose(fp)return 0

W powyższym przykładzie dostęp do danych jest sekwencyjny Jeśli chcemy mieć swo-bodny dostęp do danych to

korzystać z funkcji fsetpos fgetpos oraz fseek

utworzyć tablicę (dla dużych plikoacutew dynamiczną) zapisać do niej wszystkie dane anastępnie zapisać całą tablicę do pliku Ten sposoacuteb jest prostszy i szybszy Należyzwroacutecić uwagę że do obliczania rozmiaru całej tablicy nie możemy użyć funkcji sizeof

(a) Przykład użycia tej techniki sekwencyjnydostęp do danych (kod źroacutedłowy)

(b) Przykład użycia tej techniki swobodny do-stęp do danych (kod źroacutedłowy)

146 Co z katalogamiFaktycznie zapomnieliśmy o nich Jednak wynika to z tego że specyfikacja ANSI C nieuwzględnia obsługi katalogoacutew

Rozdział 15

Ćwiczenia dla początkujący

151 ĆwiczeniaWszystkie zamieszczone tutaj ćwiczenia mają na celu pomoacutec Ci w sprawdzeniu Twojej wie-dzy oraz umożliwieniu Tobie wykorzystania nowo nabytych wiadomości w praktyce Pa-miętaj także że ten podręcznik ma służyć także innym więc nie zamieszczaj tutaj Twoichrozwiązań Zachowaj je dla siebie

1511 Ćwiczenie 1

Napisz program ktoacutery wyświetli na ekranie twoje imię i nazwisko

1512 Ćwiczenie 2

Napisz program ktoacutery poprosi o podanie dwoacutech liczb rzeczywistych i wyświetli wynik mno-żenia obu zmiennych

1513 Ćwiczenie 3

Napisz program ktoacutery pobierze jako argumenty z linii komend nazwy dwoacutech plikoacutew i prze-kopiuje zawartość pierwszego pliku do drugiego (tworząc lub zamazując drugi)

1514 Ćwiczenie 4

Napisz program ktoacutery utworzy nowy plik (o dowolnie wybranej przez Ciebie nazwie) i za-pisze tam

Twoje imię

wiek

miasto w ktoacuterym mieszkasz

Przykładowy plik powinien wyglądać tak

Stanisław30Krakoacutew

105

106 ROZDZIAŁ 15 ĆWICZENIA DLA POCZĄTKUJĄCYCH

1515 Ćwiczenie 5

Napisz program generujący tabliczkę mnożenia x i wyświetlający ją na ekranie

1516 Ćwiczenie 6 mdash dla ętny

Napisz program znajdujący pierwiastki troacutejmianu kwadratowego ax2+bx+c= dla zadanychparametroacutew a b c

Rozdział 16

Tablice

W rozdziale Zmienne w C dowiedziałeś się jak przechowywać pojedyncze liczby oraz znakiCzasami zdarza się jednak że potrzebujemy przechować kilka kilkanaście albo iwięcej zmien-nych jednego typu Nie tworzymy wtedy np dwudziestu osobnych zmiennych W takichprzypadkach z pomocą przychodzi nam tablica

Rysunek 161 tablica 10-elementowa

Tablica to ciąg zmiennych jednego typu Ciąg taki posiada jedną nazwę a do jego po-szczegoacutelnych elementoacutew odnosimy się przez numer (indeks)

161 Wstęp

1611 Sposoby deklaracji tablic

Tablicę deklaruje się w następujący sposoacuteb

typ nazwa_tablicy[rozmiar]

gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy Zatemaby np zadeklarować tablicę mieszczącą liczb całkowitych możemy napisać tak

int tablica[20]

Podobnie jak przy deklaracji zmiennych także tablicy możemy nadać wartości począt-kowe przy jej deklaracji Odbywa się to przez umieszczenie wartości kolejnych elementoacutewoddzielonych przecinkami wewnątrz nawiasoacutew klamrowych

int tablica[3] = 123

Może to się wydać dziwne ale po ostatnim elemencie tablicy może występować przeci-nek Ponadto jeżeli poda się tylko część wartości w pozostałe wpisywane są zera

107

108 ROZDZIAŁ 16 TABLICE

int tablica[20] = 1

Niekoniecznie trzeba podawać rozmiar tablicy np

int tablica[] = 1 2 3 4 5

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku mdash elemen-toacutew)

Rozpatrzmy następujący kod

include ltstdiohgtdefine ROZMIAR 3int main()

int tab[ROZMIAR] = 368int iprintf (Druk tablicy tabn)

for (i=0 iltROZMIAR ++i) printf (Element numer d = dn i tab[i])

return 0

Wynik

Druk tablicy tabElement numer 0 = 3Element numer 1 = 6Element numer 2 = 8

Jak widać wszystko się zgadza W powyżej zamieszczonym przykładzie użyliśmy stałejdo podania rozmiaru tablicy Jest to o tyle pożądany zwyczaj że w razie konieczności zmianyrozmiaru tablicy zmieniana jest tylko jedna linijka kodu przy stałej a nie kilkadziesiąt innychlinijek rozsianych po kodzie całego programu

W pierwotnym standardzie języka C rozmiar tablicy nie moacutegł być określany przezzmienną lub nawet stałą zadeklarowaną przy użyciu słowa kluczowego const Dopiero wpoacuteźniejszej wersji standardu (tzw C) dopuszczono taką możliwość Dlatego do dekla-rowania rozmiaru tablic często używa się dyrektywy preprocesora define Powinni na tozwroacutecić uwagę zwłaszcza programiści C++ gdyż tam zawsze możliwe były oba sposoby

Innym sposobem jest użycie operatora sizeof do poznania wielkości tablicy Poniższy kodrobi to samo co przedstawiony

include ltstdiohgtint main()

int tab[3] = 368int i

162 ODCZYTZAPIS WARTOŚCI DO TABLICY 109

printf (Druk tablicy tabn)

for (i=0 ilt(sizeof tab sizeof tab) ++i) printf (Element numer d = dn i tab[i])

return 0

Należy pamiętać że działa on tylko dla tablic a nie wskaźnikoacutew (jak poacuteźniej się dowieszwskaźnik też można w pewnym stopniu traktować jak tablicę)

162 Odczytzapis wartości do tablicyTablicami posługujemy się tak samo jak zwykłymi zmiennymi Roacuteżnica polega jedynie napodaniu indeksu tablicy Określa on jednoznacznie z ktoacuterego elementu (wartości) chcemyskorzystać Indeksem jest liczba naturalna począwszy od zera To oznacza że pierwszy ele-ment tablicy ma indeks roacutewny drugi trzeci itd

Osoby ktoacutere wcześniej programowały w językach takich jak Pascal Basic czy Fortranmuszą przyzwyczaić się do tego że w języku C indeks numeruje się od Ponadto indeksempowinna być liczba - istnieje możliwość indeksowania za pomocą np pojedynczych znakoacutew(rsquoarsquo rsquobrsquo itp) jednak Cwewnętrznie konwertuje takie znaki na liczby im odpowiadające zatemtablica indeksowana znakami byłaby niepraktyczna i musiałaby mieć odpowiednio większyrozmiar

Sproacutebujmy przedstawić to na działającym przykładzie Przeanalizuj następujący kod

int tablica[5] = 0int i = 0tablica[2] = 3tablica[3] = 7for (i=0i=5++i)

printf (tablica[d]=dn i tablica[i])

Jak widać na początku deklarujemy -elementową tablicę ktoacuterą od razu zerujemy Na-stępnie pod trzeci i czwarty element podstawiamy liczby i Pętla ma za zadanie wypro-wadzić wynik naszych działań

163 Tablice znakoacutewTablice znakoacutew tj typu char oraz unsigned char posiadają dwie ogoacutelnie przyjęte nazwyzależnie od ich przeznaczenia

bufory mdash gdy wykorzystujemy je do przechowywania ogoacutelnie pojętych danych gdytraktujemy je jako po prostu ldquociągi bajtoacutewrdquo (typ char ma rozmiar bajta więc jestelastyczny do przechowywania np danych wczytanych z pliku przed ich przetworze-niem)

napisy mdash gdy zawarte w nich dane traktujemy jako ciągi liter jest im poświęconyosobny rozdział Napisy

110 ROZDZIAŁ 16 TABLICE

164 Tablice wielowymiarowe

Rysunek tablica dwuwymia-rowa (x)

Rozważmy teraz konieczność przechowania w pa-mięci komputera całej macierzy o wymiarach x Można by tego dokonać tworząc osobnych ta-blic jednowymiarowych reprezentujących poszcze-goacutelne wiersze macierzy Jednak język C dostarczanam dużo wygodniejszej metody ktoacutera w dodatkujest bardzo łatwa w użyciu Są to tablice wielowy-miarowe lub inaczej ldquotablice tablicrdquo Tablice wielo-wymiarowe definiujemy podając przy zmiennej kilkawymiaroacutew np

float macierz[10][10]

Tak samo wygląda dostęp do poszczegoacutelnych ele-mentoacutew tablicy

macierz[2][3] = 12

Jakwidać ten sposoacuteb jest dużowygodniejszy (i za-pewne dużo bardziej ldquonaturalnyrdquo) niż deklarowanie osobnych tablic jednowymiarowych Aby zaini-cjować tablicę wielowymiarową należy zastosowaćzagłębianie klamer np

float macierz[3][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz

Dodatkowo pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowy-miarowych) i woacutewczas kompilator sam ustali odpowiednią wielkość np

float macierz[][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz 63 27 57 27 czwarty wiersz

Innym bardziej elastycznym sposobem deklarowania tablic wielowymiarowych jest uży-cie wskaźnikoacutew Opisane to zostało w następnym rozdziale

165 Ograniczenia tablicPomimo swej wygody tablice mają ograniczony z goacutery zdefiniowany rozmiar ktoacuterego niemożna zmienić w trakcie działania programu Dlatego też w niektoacuterych zastosowaniach ta-blice zostały wyparte przez dynamiczną alokację pamięci Opisane to zostało w następnymrozdziale

166 CIEKAWOSTKI 111

Przy używaniu tablic trzeba być szczegoacutelnie ostrożnym przy konstruowaniu pętli ponie-waż ani kompilator ani skompilowany program nie będą w stanie wychwycić przekroczeniaprzez indeks rozmiaru tablicy 1 Efektem będzie odczyt lub zapis pamięci znajdującej się pozatablicą

Wystarczy pomylić się o jednomiejsce (tzw błąd off by one) by spowodować że działanieprogramu zostanie nagle przerwane przez system operacyjny

int foo[100]int i

for (i=0 ilt=100 ++i) powinno być ilt100 foo[i] = 0

166 CiekawostkiW pierwszej edycji konkursu IOCCC zwyciężył program napisany w C ktoacutery wyglądał dośćnietypowo

short main[] = 277 04735 -4129 25 0 477 1019 0xbef 0 12800-113 21119 0x52d7 -1006 -7151 0 0x4bc 02000414880 10541 2056 04010 4548 3044 -6716 0x94407 6 5568 1 -30460 0 0x9 5570 512 -304190x7e82 0760 6 0 4 02400 15 0 4 1280 4 04 0 0 0 0x8 0 4 0 0 12 0 4 0 0 020 0 4 0 30 0 026 0 0x6176 120 25712p 072163 r 29303 29801 e

Co ciekawe mdash program ten bez przeszkoacuted wykonywał się na komputerach VAX- orazPDP- Cały program to po prostu tablica z zawartym wewnątrz kodem maszynowym Taknaprawdę jest to wykorzystanie pewnych właściwości programu ktoacutery ostatecznie produ-kuje kod maszynowy Linker (to o nim mowa) nie rozroacuteżnia na dobrą sprawę nazw funkcjiod nazw zmiennych więc bez problemu ustawił punkt wejścia programu na tablicę wartościw ktoacuterych zapisany był kod maszynowy Tak przygotowany program został bez problemuwykonany przez komputer

1W zasadzie kompilatory mają możliwość dodania takiego sprawdzania ale nie robi się tego gdyż znaczniespowolniłoby to działanie programu Takie postępowanie jest jednak pożądane w okresie testowania programu

112 ROZDZIAŁ 16 TABLICE

Rozdział 17

Wskaźniki

Zobacz w WikipediiZmienna wskaźnikowaZmienne w komputerze są przechowywane w pamięci To wie każdy programista a dobry

programista potrafi kontrolować zachowanie komputera w przydzielaniu i obsługi pamięcidla zmiennych W tym celu pomocne są wskaźniki

171 Co to jest wskaźnik

Dla ułatwienia przyjęto poniżej że bajt ma bitoacutew typ int składa się z dwoacutech bajtoacutew(bitoacutew) typ long składa się z czterech bajtoacutew ( bitoacutew) oraz liczby zapisane są w formaciebig endian (tzn bardziej znaczący bajt na początku) co niekoniecznie musi być prawdą naTwoim komputerze

Rysunek Wskaźnik awskazu-jący na zmienną b Zauważmy żeb przechowuje liczbę podczas gdya przechowuje adres b w pamięci()

Wskaźnik (ang pointer) to specjalny rodzaj zmiennejw ktoacuterej zapisany jest adres w pamięci komputera tznwskaźnik wskazuje miejsce gdzie zapisana jest jakaśinformacja Oczywiście nic nie stoi na przeszkodzie abywskazywaną daną był innywskaźnik do kolejnegomiej-sca w pamięci

Obrazowo możemy wyobrazić sobie pamięć kom-putera jako bibliotekę a zmienne jako książki Zamiastbrać książkę z poacutełki samemu (analogicznie do korzy-stania wprost ze zwykłych zmiennych) możemy podaćbibliotekarzowi wypisany rewers z numerem katalogo-wym książki a on znajdzie ją za nas Analogia ta niejest doskonała ale pozwalawyobrazić sobie niektoacutere ce-chy wskaźnikoacutew kilka rewersoacutew może dotyczyć tej sa-mej książki numer w rewersie możemy skreślić i użyćgo do zamoacutewienia innej książki jeśli wpiszemy niepra-widłowy numer katalogowy to możemy dostać nie tąksiążkę ktoacuterą chcemy albo też nie dostać nic

Warto też poznać w tym miejscu definicję adresupamięci Możemy powiedzieć że adres to pewna liczba całkowita jednoznacznie definiującapołożenie pewnego obiektu (czyli np znaku czy liczby) w pamięci komputera Dokładniejsządefinicję możesz znaleźć w Wikipedii

113

114 ROZDZIAŁ 17 WSKAŹNIKI

172 Operowanie na wskaźnikaBy stworzyć wskaźnik do zmiennej i moacutec się nim posługiwać należy przypisać mu odpo-wiednią wartość (adres obiektu na jaki ma wskazywać) Skąd mamy znać ten adres Wy-starczy zapytać nasz komputer jaki adres przydzielił zmiennej ktoacuterą np wcześniej gdzieśstworzyliśmy Robi się to za pomocą operatora amp (operatora pobrania adresu) Przeanalizujnastępujący kod1

include ltstdiohgt

int main (void)

int liczba = 80printf(Zmienna znajduje sie pod adresem p i przechowuje wartosc dn

(void)ampliczba liczba)return 0

Program ten wypisuje adres pamięci pod ktoacuterym znajduje się zmienna oraz wartość jakąkryje zmienna przechowywana pod owym adresem

Aby moacutec zapisać gdzieś taki adres należy zadeklarować zmienną wskaźnikową Robi sięto poprzez dodanie (gwiazdki) po typie na jaki zmienna ma wskazywać np

int wskaznik1char wskaznik2floatwskaznik3

Niektoacuterzy programiści mogą nieco błędnie interpretować wskaźnik do typu jako nowytyp i uważać że jeśli napiszą

int abc

to otrzymają trzy wskaźniki do liczby całkowitej Tymczasem wskaźnikiem będzie tylkozmienna a natomiast b i c będą po prostu liczbami Powodem jest to że rdquogwiazdkaodnosi siędo zmiennej a nie do typu W tym przypadku trzy wskaźniki otrzymamy pisząc

int abc

Aby uniknąć pomyłek lepiej jest pisać gwiazdkę tuż przy zmiennej

int abc

albo jeszcze lepiej nie mieszać deklaracji wskaźnikoacutew i zmiennych

int aint bc

Aby dobrać się dowartości wskazywanej przez wskaźnik należy użyć unarnego operatora (gwiazdka) zwanego operatorem wyłuskania

1Warto zwroacutecić uwagę na rzutowanie do typu wskaźnik na void Rzutowanie to jest wymagane przez funkcjęprintf gdyż ta oczekuje że argumentem dla formatu p będzie właśnie wskaźnik na void gdy tymczasem w naszymprzykładzie wyrażenie ampliczba jest typu wskaźnik na int

172 OPEROWANIE NA WSKAŹNIKACH 115

include ltstdiohgt

int main (void)

int liczba = 80int wskaznik = ampliczbaprintf(Wartosc zmiennej d jej adres pn liczba (void)ampliczba)printf(Adres zapisany we wskazniku p wskazywana wartosc dn

(void)wskaznik wskaznik)

wskaznik = 42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

liczba = 0x42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

return 0

1721 O coodzi z tym typem na ktoacutery ma wskazywać Czemu to takieważne

Jest to ważne z kilku powodoacutewRoacuteżne typy zajmują w pamięci roacuteżną wielkość Przykładowo jeżeli w zmiennej typu

unsigned int zapiszemy liczbę to w pamięci będzie istnieć jako

+--------+--------+|komoacuterka1|komoacuterka2|+--------+--------+|11111111|11111010| = (unsigned int) 65530+--------+--------+

Wskaźnik do takiej zmiennej (jak i do dowolnej innej) będzie wskazywać na pierwsząkomoacuterkę w ktoacuterej ta zmienna ma swoją wartość

Jeżeli teraz stworzymy drugi wskaźnik do tego adresu tym razem typu unsigned arto wskaźnik przejmie ten adres prawidłowo2 lecz gdy sproacutebujemy odczytać wartość na jakąwskazuje ten wskaźnik to zostanie odczytana tylko pierwsza komoacuterka i wynik będzie roacutewny

+--------+|komoacuterka1|+--------+|11111111| = (unsigned char) 255+--------+

2Tak naprawdę nie zawsze można przypisywać wartości jednych wskaźnikoacutew do innych Standard C gwaran-tuje jedynie że można przypisać wskaźnikowi typu void wartość dowolnego wskaźnika a następnie przypisać tąwartość do wskaźnika pierwotnego typu oraz że dowolny wskaźnik można przypisać do wskaźnika typu char

116 ROZDZIAŁ 17 WSKAŹNIKI

Gdybyśmy natomiast stworzyli inny wskaźnik do tego adresu tym razem typu unsignedlong to przy proacutebie odczytu odczytane zostaną dwa bajty z wartością zapisaną w zmiennejunsigned int oraz dodatkowe dwa bajty z niewiadomą zawartością i woacutewczas wynik będzieroacutewny + przypadkowa wartość

+--------+--------+--------+--------+|komoacuterka1|komoacuterka2|komoacuterka3|komoacuterka4|+--------+--------+--------+--------+|11111111|11111010|||+--------+--------+--------+--------+

Ponadto zapis czy odczyt poza przydzielonym obszarem pamięci może prowadzić donieprzyjemnych skutkoacutew takich jak zmiana wartości innych zmiennych czy wręcz natych-miastowe przerwanie programu Jako przykład można podać ten (błędny) program3

include ltstdiohgt

int main(void)

unsigned char tab[10] = 100 101 102 103 104 105 106 107 108 109 unsigned short ptr = (unsigned short)amptab[2]unsigned i

ptr = 0xfffffor (i = 0 i lt 10 ++i)

printf(dn tab[i])tab[i] = tab[i] - 100

printf(poza tablica dn tab[10])tab[10] = -1return 0

Nie można roacutewnież zapominać że na niektoacuterych architekturach dane wielobajtowe mu-szą być odpowiednio wyroacutewnane w pamięci Np zmienna dwubajtowa może się znajdowaćjedynie pod parzystymi adresami Woacutewczas gdybyśmy chcieli adres zmiennej jednobajto-wej przypisać wskaźnikowi na zmienną dwubajtową mogłoby dojść do nieprzewidzianychbłędoacutew wynikających z proacuteby odczytu niewyroacutewnanej danej

Zaskakującemoże się okazać że roacuteżnewskaźniki mogąmieć roacuteżny rozmiar Np wskaźnikna ar może być większy od wskaźnika na int ale roacutewnież na odwroacutet Co więcej wskaźnikiroacuteżnych typoacutewmogą się roacuteżnić reprezentacją adresoacutew Dla przykładuwskaźnik naar możeprzechowywać adres do bajtu natomiast wskaźnik na int ten adres podzielony przez

Podsumowując roacuteżne wskaźniki to roacuteżne typy i nie należy beztrosko rzutować wyrażeńpomiędzy roacuteżnymi typami wskaźnikowymi bo grozi to nieprzewidywalnymi błędami

1722 Do czego służy typ void

Czasami zdarza się że nie wiemy na jaki typwskazuje danywskaźnik W takich przypadkachstosujemy typ void Sam void nie znaczy nic natomiast void oznacza ldquowskaźnik na obiekt

3Może się okazać że błąd nie będzie widoczny na Twoim komputerze

173 ARYTMETYKA WSKAŹNIKOacuteW 117

w pamięci niewiadomego typurdquo Taki wskaźnik możemy potem odnieść do konkretnego typudanych (w języku C++ wymagana jest do tego operacja rzutowania) Na przykład funkcjamalloc zwraca właśnie wskaźnik za pomocą void

173 Arytmetyka wskaźnikoacutew

W języku C do wskaźnikoacutew można dodawać lub odejmować liczby całkowite Istotne jestjednak że dodanie do wskaźnika liczby nie spowoduje przesunięcia się w pamięci kom-putera o dwa bajty Tak naprawdę przesuniemy się o rozmiar zmiennej Jest to bardzoważna informacja Początkujący programiści popełniają często dużo błędoacutew związanych znieprawidłową arytmetyką wskaźnikoacutew

Zobaczmy na przykład

int ptrint a[] = 1 2 3 5 7ptr = ampa[0]

Rysunek 172 Wskaźnik wskazuje na pierwszą komoacuterkę pamięci

Otrzymujemy następującą sytuacjęGdy wykonamy

ptr += 2

Rysunek 173 Przesunięcie wskaźnika na kolejne komoacuterki

wskaźnik ustawi się na trzecim elemencie tablicyWskaźniki można roacutewnież od siebie odejmować czego wynikiem jest odległość dwoacutech

wskazywanych wartości Odległość zwracana jest jako liczba obiektoacutew danego typu a nieliczba bajtoacutew Np

int a[] = 1 2 3 5 7int ptr = ampa[2]int diff = ptr - a diff ma wartość 2 (a nie 2sizeof(int))

118 ROZDZIAŁ 17 WSKAŹNIKI

Wynikiem może być oczywiście liczba ujemna Operacja jest przydatna do obliczaniawielkości tablicy (długości łańcucha znakoacutew) jeżeli mamy wskaźnik na jej pierwszy i ostatnielement

Operacje arytmetyczne na wskaźnikach mają pewne ograniczenia Przede wszystkim niemożna (tzn standard tego nie definiuje) skonstruować wskaźnika wskazującego gdzieś pozazadeklarowaną tablicę chyba że jest to obiekt zaraz za ostatnim (one past last) np

int a[] = 1 2 3 5 7int ptrptr = a + 10 niezdefiniowane ptr = a - 10 niezdefiniowane ptr = a + 5 zdefiniowane (element za ostatnim) ptr = 10 to już nie

Nie można4 roacutewnież odejmować od siebie wskaźnikoacutew wskazujących na obiekty znajdu-jące się w roacuteżnych tablicach np

int a[] = 1 2 3 b[] = 5 7int ptr1 = a ptr2 = bint diff = a - b niezdefiniowane

174 Tablice a wskaźniki

Trzeba wiedzieć że tablice to też rodzaj zmiennej wskaźnikowej Taki wskaźnik wskazuje namiejsce w pamięci gdzie przechowywany jest jej pierwszy element Następne elementy znaj-dują się bezpośrednio w następnych komoacuterkach pamięci w odstępie zgodnym z wielkościąodpowiedniego typu zmiennej

Na przykład tablica

int tab[] = 100200300

występuje w pamięci w sześciu komoacuterkach5

+--------+--------+--------+--------+--------+--------+|wartosc1| |wartosc2| |wartosc3| |+--------+--------+--------+--------+--------+--------+|00000000|01100100|00000000|11001000|00000001|00101100|+--------+--------+--------+--------+--------+--------+

Stąd do trzeciej wartości można się dostać tak (komoacuterki w tablicy numeruje się od zera)

zmienna = tab[2]

albo wykorzystując metodę wskaźnikową

zmienna = (tab + 2)

4To znaczy standard nie definiuje co się wtedy stanie aczkolwiek na większości architektur odejmowanie do-wolnych dwoacutech wskaźnikoacutew ma zdefiniowane zachowanie Pisząc przenośne programy nie można jednak na tympolegać zwłaszcza że odejmowanie wskaźnikoacutew wskazujących na elementy roacuteżnych tablic zazwyczaj nie ma sensu

5Ponownie przyjmując że bajt ma 8 bitoacutew int dwa bajty i liczby zapisywane są w formacie lile endian

175 GDY ARGUMENT JEST WSKAŹNIKIEM 119

Z definicji obie te metody są roacutewnoważneZ definicji (z wyjątkiem użycia operatora sizeo) wartością zmiennej lub wyrażenia typu tablico-

wego jest wskaźnik na jej pierwszy element (tab == amptab[0])Co więcej można poacutejść w drugą stronę i potraktować wskaźnik jak tablicę

int wskaznikwskaznik = amptab[1] lub wskaznik = tab + 1 zmienna = wskaznik[1] przypisze 300

Jako ciekawostkę podamy iż w języku C można odnosić się do elementoacutew tablicy jeszcze w innysposoacuteb

printf (dn 1[tab])

Skąd ta dziwna notacja Uzasadnienie jest proste

tab[1] = (tab + 1) = (1 + tab) = 1[tab]

Podobną składnię stosuje min asembler GNU

175 Gdy argument jest wskaźnikiem Czasami zdarza się że argumentem (lub argumentami) funkcji są wskaźniki W przypadku ldquonormal-nychrdquo zmiennych nasza funkcja działa tylko na lokalnych kopiach tychże argumentoacutew natomiast niezmienia zmiennych ktoacutere zostały podane jako argument Natomiast w przypadku wskaźnika każdaoperacja na wartości wskazywanej powoduje zmianę wartości zmiennej zewnętrznej Sproacutebujmy roz-patrzeć poniższy przykład

include ltstdiohgt

void func (int zmienna)

zmienna = 5

int main ()

int z=3printf (z=dn z) wypisze 3 func(ampz)printf (z=dn z) wypisze 5

Widzimy że funkcje w języku C nie tylko potrafią zwracać określoną wartość lecz także zmieniaćdane podane im jako argumenty Ten sposoacuteb przekazywania argumentoacutew do funkcji jest nazywanyprzekazywaniem przez wskaźnik (w przeciwieństwie do normalnego przekazywania przez wartość)

Zwroacutećmy uwagę na wywołanie func(ampz) Należy pamiętać by do funkcji przekazać adres zmien-nej a nie samą zmienną Jeśli byśmy napisali func(z) to funkcja starałaby się zmienić komoacuterkę pamięcio numerze Kompilator powinien ostrzec w takim przypadku o konwersji z typu int do wskaźnikaale często kompiluje taki program pozostając na ostrzeżeniu

Nie gra roli czy przy deklaracji funkcji jako argument funkcji podamy wskaźnik czy tablicę (z po-danym rozmiarem lub nie) np poniższe deklaracje są identyczne

120 ROZDZIAŁ 17 WSKAŹNIKI

void func(int ptr[])void func(int ptr)

Można przyjąć konwencję że deklaracja określa czy funkcji przekazujemy wskaźnik do pojedyn-czego argumentu czy do sekwencji ale roacutewnie dobrze można za każdym razem stosować gwiazdkę

176 Pułapki wskaźnikoacutewWażne jest aby przy posługiwaniu się wskaźnikami nigdy nie proacutebować odwoływać się do komoacuterkiwskazywanej przez wskaźnik o wartości lub niezainicjowany wskaźnik Przykładem nieprawi-dłowego kodu może być np

int wskprintf (zawartosc komorki dn (wsk)) Błąd wsk = 0 0 w kontekście wskaźnikoacutew oznacza wskaźnik NULL printf (zawartosc komorki dn (wsk)) Błąd

Należy roacutewnież uważać aby nie odwoływać się do komoacuterek poza przydzieloną pamięcią np

int tab[] = 0 1 2 tab[3] = 3 Błąd

Pamiętaj też że możesz być rozczarowany używając operatora sizeof podając zmienną wskaźni-kową Uzyskana wielkość będzie wielkością wskaźnika a nie wielkością typu użytego podczas deklaro-wania naszego wskaźnika Wielkość ta będzie zawsze miała taki sam rozmiar dla każdego wskaźnikaw zależności od kompilatora a także docelowej platformy Zamiast tego używaj sizeof(wskaźnik)Przykład

char zmiennaint a = sizeof zmienna a wynosi np 4 tj sizeof(char) a = sizeof(char) robimy to samo co wyżej a = sizeof zmienna zmienna a ma teraz przypisany rozmiar

pojedynczego znaku tj 1 a = sizeof(char) robimy to samo co wyżej

177 Na co wskazuje Analizując kody źroacutedłowe programoacutew często można spotkać taki oto zapis

void wskaznik = NULL lub = 0

Wiesz już że nie możemy odwołać się pod komoacuterkę pamięci wskazywaną przez wskaźnik Poco zatem przypisywać wskaźnikowi Odpowiedź może być zaskakująca właśnie po to aby uniknąćbłędoacutew Wydaje się to zabawne ale większość (jeśli nie wszystkie) funkcji ktoacutere zwracają wskaźnikw przypadku błędu zwroacuteci właśnie czyli zero Tutaj rodzi się kolejna wskazoacutewka jeśli w danejzmiennej przechowujemy wskaźnik zwroacutecony wcześniej przez jakąś funkcję zawsze sprawdzajmy czynie jest on roacutewny () Wtedy mamy pewność że funkcja zadziałała poprawnie

Dokładniej nie jest słowem kluczowym lecz stałą (makrem) zadeklarowaną przez dyrektywypreprocesora Deklaracja taka może być albo wartością albo też wartością zrzutowaną na void(((void )0)) ale też jakimś słowem kluczowym deklarowanym przez kompilator

Warto zauważyć że pomimo przypisywania wskaźnikowi zera nie oznacza to że wskaźnik jest reprezentowany przez same zerowe bity Co więcej wskaźniki roacuteżnych typoacutew mogą miećroacuteżną wartość Z tego powodu poniższy kod jest niepoprawny

int tablica_wskaznikow = calloc(100 sizeof tablica_wskaznikow)

178 STAŁE WSKAŹNIKI 121

Zakłada on że w reprezentacji wskaźnika występują same zera Poprawnym zainicjowaniemdynamicznej tablicy wskaźnikoacutew wartościami jest (pomijamy sprawzdanie wartości zwroacuteconejprzez malloc())

int tablica_wskaznikow = malloc(100 sizeof tablica_wskaznikow)int i = 0while (ilt100)

tablica_wskaznikow[i++] = 0

178 Stałe wskaźnikiTak jak istnieją zwykłe stałe tak samo możemy mieć stałe wskaźniki mdash jednak są ich dwa rodzajeWskaźniki na stałą wartość

const int a lub roacutewnoważnie int const a

oraz stałe wskaźniki

int const b

Pierwszy to wskaźnik ktoacuterym nie można zmienić wskazywanej wartości Drugi to wskaźnik ktoacute-rego nie można przestawić na inny adres Dodatkowo można zadeklarować stały wskaźnik ktoacuterym niemożna zmienić wartości wskazywanej zmiennej i roacutewnież można zrobić to na dwa sposoby

const int const c alternatywnie int const const c

int i=0const int a=ampiint const b=ampiint const const c=ampia = 1 kompilator zaprotestuje b = 2 ok c = 3 kompilator zaprotestuje a = b ok b = a kompilator zaprotestuje c = a kompilator zaprotestuje

Wskaźniki na stałą wartość są przydatne między innymi w sytuacji gdy mamy duży obiekt (naprzykład strukturę z kilkoma polami) Jeśli przypiszemy taką zmienną do innej zmiennej kopiowaniemoże potrwać dużo czasu a oproacutecz tego zostanie zajęte dużo pamięci Przekazanie takiej struktury dofunkcji albo zwroacutecenie jej jako wartość funkcji wiąże się z takim samym narzutem W takim wypadkudobrze jest użyć wskaźnika na stałą wartość

void funkcja(const duza_struktura ds)

czytamy z ds i wykonujemy obliczenia

funkcja(ampdane) mamy pewność że zmienna dane nie zostanie zmieniona

122 ROZDZIAŁ 17 WSKAŹNIKI

179 Dynamiczna alokacja pamięciMając styczność z tablicami można się zastanowić czy nie dałoby się mieć tablic ktoacuterych rozmiardostosowuje się do naszych potrzeb a nie jest na stałe zaszyty w kodzie programu Chcąc pomieścićwięcej danych możemy po prostu zwiększyć rozmiar tablicy mdash ale gdy do przechowania będzie mniejelementoacutew okaże się że marnujemy pamięć Język C umożliwia dzięki wskaźnikom i dynamicznejalokacji pamięci tworzenie tablic takiej wielkości jakiej akurat potrzebujemy

1791 O co odziCzym jest dynamiczna alokacja pamięci Normalnie zmienne programu przechowywane są na tzwstosie (ang sta) mdash powstają gdy program wchodzi do bloku w ktoacuterym zmienne są zadeklarowane azwalniane w momencie kiedy program opuszcza ten blok Jeśli deklarujemy tak tablice to ich rozmiarmusi być znanywmomencie kompilacji mdash żeby kompilator wygenerował kod rezerwujący odpowiedniąilość pamięci Dostępny jest jednak drugi rodzaj rezerwacji (czyli alokacji) pamięci Jest to alokacja nastercie (ang heap) Sterta to obszar pamięci wspoacutelny dla całego programu przechowywane są w nimzmienne ktoacuterych czas życia nie jest związany z poszczegoacutelnymi blokami Musimy sami rezerwować dlanich miejsce i to miejsce zwalniać ale dzięki temu możemy to zrobić w dowolnym momencie działaniaprogramu

Należy pamiętać że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analo-giczne działania na stosie Dodatkowo zmienna zajmuje na stercie więcej miejsca niż na stosie mdash stertautrzymuje specjalną strukturę w ktoacuterej trzymane są wolne partie (może to być np lista) Tak więcużywajmy dynamicznej alokacji tam gdzie jest potrzebna mdash dla danych ktoacuterych rozmiaru nie jesteśmyw stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem w ktoacuterymzostały zaalokowane

1792 Obsługa pamięciPodstawową funkcją do rezerwacji pamięci jest funkcja malloc Jest to niezbyt skomplikowana funkcjamdash podając jej rozmiar (w bajtach) potrzebnej pamięci dostajemy wskaźnik do zaalokowanego obszaru

Załoacuteżmy że chcemy stworzyć tablicę liczb typu float

int rozmiarfloat tablica

rozmiar = 3tablica = (float) malloc(rozmiar sizeof tablica)tablica[0] = 01

Przeanalizujmy teraz po kolei co dzieje się w powyższym fragmencie Najpierw deklarujemyzmiennemdash rozmiar tablicy i wskaźnik ktoacutery będzie wskazywał obszarw pamięci gdzie będzie trzymanatablica Do zmiennej rozmiar możemy w trakcie działania programu przypisać cokolwiek mdash wczytaćją z pliku z klawiatury obliczyć wylosować mdash nie jest to istotne rozmiar sizeof tablica obliczapotrzebną wielkość tablicy Dla każdej zmiennej float potrzebujemy tyle bajtoacutew ile zajmuje ten typdanych Ponieważ może się to roacuteżnić na rozmaitych maszynach istnieje operator sizeof zwracającydla danego wyrażenia rozmiar jego typu w bajtach

W wielu książkach (roacutewnież KampRv) i w Internecie stosuje się inny schemat użycia funkcji malloca mianowicie tablica = (float)malloc(rozmiar sizeof(float)) Takie użycie należy traktowaćjako błędne gdyż nie sprzyja ono poprawnemu wykrywaniu błędoacutew

Rozważmy sytuację gdy programista zapomni dodać plik nagłoacutewkowy stdlibh woacutewczas kompila-tor (z braku deklaracji funkcji malloc) przyjmie że zwraca ona typ int zatem do zmiennej tablica (ktoacuterajest wskaźnikiem) będzie przypisywana liczba całkowita co od razu spowoduje błąd kompilacji (a przy-najmniej ostrzeżenie) dzięki czemu będzie można szybko poprawić kod programu Rzutowanie jestkonieczne tylko w języku C++ gdzie konwersja z void na inne typy wskaźnikowe nie jest domyślnaale język ten oferuje nowe sposoby alokacji pamięci

179 DYNAMICZNA ALOKACJA PAMIĘCI 123

Teraz rozważmy sytuację gdy zdecydujemy się zwiększyć dokładność obliczeń i zamiast typu floatużyć typu double Będziemy musieli wyszukać wszystkie wywołania funkcji malloc calloc i reallocodnoszące się do naszej tablicy i zmieniać wszędzie sizeof(float) na sizeof(double) Aby temu zapo-biec lepiej od razu użyć sizeof tablica (lub jeśli ktoś woli z nawiasami sizeof(tablica)) woacutewczaszmiana typu zmiennej tablica na double zostanie od razu uwzględniona przy alokacji pamięci

Dodatkowo należy sprawdzić czy funkcja malloc nie zwroacuteciła wartości mdash dzieje się tak gdyzabrakło pamięci Ale uwaga może się tak stać roacutewnież jeżeli jako argument funkcji podano zero

Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić aby sys-tem operacyjny moacutegł go przydzielić innym potrzebującym procesom Do zwolnienia obszaru pamięciużywamy funkcji free() ktoacutera przyjmuje tylko jeden argument mdash wskaźnik ktoacutery otrzymaliśmy wwyniku działania funkcji malloc()

free (addr)

Należy pamiętać o zwalnianiu pamięci mdash inaczej dojdzie do tzw wycieku pamięci mdash program będzierezerwował nową pamięć ale nie zwracał jej z powrotem i w końcu pamięci może mu zabraknąć

Należy też uważać by nie zwalniać dwa razy tego samegomiejsca Po wywołaniu free wskaźnik niezmienia wartości pamięć wskazywana przez niego może też nie od razu ulec zmianie Czasemmożemywięc korzystać ze wskaźnika (zwłaszcza czytać) po wywołaniu free nie orientując się że robimy coś źlemdash i w pewnym momencie dostać komunikat o nieprawidłowym dostępie do pamięci Z tego powoduzaraz po wywołaniu funkcji free można przypisać wskaźnikowi wartość

Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci Tu z pomocąprzychodzi funkcja realloc

tablica = realloc(tablica 2rozmiarsizeof tablica)

Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub gdy zabrakło pa-mięci) Uwaga mdash może to być inny wskaźnik Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanymaktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca funkcja znajdzie nowe miejscei przekopiuje tam starą zawartość Jak widać wywołanie tej funkcji może być więc kosztowne podwzględem czasu

Ostatnią funkcją jest funkcja calloc() Przyjmuje ona dwa argumenty liczbę elementoacutew tablicyoraz wielkość pojedynczego elementu Podstawową roacuteżnicą pomiędzy funkcjami malloc() i calloc() jestto że ta druga zeruje wartość przydzielonej pamięci (do wszystkich bajtoacutew wpisuje wartość )

1793 Tablice wielowymiarowe

Rysunek 174 tablica dwuwymiarowa mdash w rzeczywistości tablica ze wskaźnikami do tablic

W rozdziale Tablice pokazaliśmy jak tworzyć tablice wielowymiarowe gdy ich rozmiar jest znanyw czasie kompilacji Teraz zaprezentujemy jak to wykonać za pomocą wskaźnikoacutew i to w sytuacji gdyrozmiar może się zmieniać Załoacuteżmy że chcemy stworzyć tabliczkę mnożenia

124 ROZDZIAŁ 17 WSKAŹNIKI

int rozmiarint iint tabliczka

printf(Podaj rozmiar tabliczki mnozenia )scanf(i amprozmiar) dla prostoty nie będziemy sprawdzali

czy użytkownik wpisał sensowną wartość

tabliczka = malloc(rozmiar sizeof tabliczka) 1 for (i = 0 iltrozmiar ++i) 2

tabliczka[i] = malloc(rozmiar sizeof tabliczka) 3 4

for (i = 0 iltrozmiar ++i) int jfor (j = 0 jltrozmiar ++j)

tabliczka[i][j] = (i+1)(j+1)

Najpierw musimy przydzielić pamięć mdash najpierw dla ldquotablicy tablicrdquo () a potem dla każdej z pod-tablic osobno (-) Ponieważ tablica jest typu int to nasza tablica tablic będzie wskaźnikiem na intczyli int Podobnie osobno ale w odwrotnej kolejności będziemy zwalniać tablicę wielowymiarową

for (i = 0 iltrozmiar ++i) free(tabliczka[i])

free(tabliczka)

Należy nie pomylić kolejności po wykonaniu free(tabliczka) nie będziemy mieli prawa odwoły-wać się do tabliczka[i] (bo wcześniej dokonaliśmy zwolnienia tego obszaru pamięci)

Można także zastosować bardziej oszczędny sposoacuteb alokowania tablicy wielowymiarowej a mia-nowicie

define ROZMIAR 10int iint tabliczka = malloc(ROZMIAR sizeof tabliczka)tabliczka = malloc(ROZMIAR ROZMIAR sizeof tabliczka)for (i = 1 iltROZMIAR ++i)

tabliczka[i] = tabliczka[0] + (i ROZMIAR)

for (i = 0 iltROZMIAR ++i) int jfor (j = 0 jltROZMIAR ++j)

tabliczka[i][j] = (i+1)(j+1)

free(tabliczka)free(tabliczka)

Powyższy kod działa w ten sposoacuteb że zamiast dla poszczegoacutelnych wierszy alokować osobno pamięćalokuje pamięć dla wszystkich elementoacutew tablicy i dopiero poacuteźniej przypisuje wskazania poszczegoacutel-nych wskaźnikoacutew-wierszy na kolejne bloki po elementoacutew

1710 WSKAŹNIKI NA FUNKCJE 125

Sposoacuteb ten jest bardziej oszczędny z dwoacutech powodoacutew Po pierwsze wykonywanych jest mniej ope-racji przydzielania pamięci (bo tylko dwie) Po drugie za każdym razem gdy alokuje się pamięć trochęmiejsca się marnuje gdyż funkcja malloc musi w stogu przechowywać roacuteżne dodatkowe informacje natemat każdej zaalokowanej przestrzeni Ponadto czasami alokacja odbywa się blokami i gdy zażąda sięniepełny blok to reszta bloku jest tracona

Zauważmy że w ten sposoacuteb możemy uzyskać nie tylko normalną ldquokwadratowąrdquo tablicę (dla dwoacutechwymiaroacutew) Możliwe jest np uzyskanie tablicy troacutejkątnej

0123012010

lub tablicy o dowolnym innym rozkładzie długości wierszy np

const size_t wymiary[] = 2 4 6 8 1 3 5 7 9 int iint tablica = malloc((sizeof wymiary sizeof wymiary) sizeof tablica)for (i = 0 ilt10 ++i)

tablica[i] = malloc(wymiary[i] sizeof tablica)

Gdy nabierzesz wprawy w używaniu wskaźnikoacutew oraz innych funkcji malloc i realloc nauczyszsię wykonywać roacuteżne inne operacje takie jak dodawanie kolejnych wierszy usuwanie wierszy zmianarozmiaru wierszy zamiana wierszy miejscami itp

1710 Wskaźniki na funkcjeDotychczas zajmowaliśmy się sytuacją gdy wskaźnik wskazywał na jakąś zmienną Jednak nie tylkozmienna ma swoacutej adres w pamięci Oproacutecz zmiennej także i funkcja musi mieć swoje określone miejscew pamięci A ponieważ funkcja ma swoacutej adres6 to nie ma przeszkoacuted aby i na nią wskazywał jakiśwskaźnik

17101 Deklaracja wskaźnika na funkcjęTak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresufunkcji Wskaźnik na funkcję roacuteżni się od innych rodzajoacutew wskaźnikoacutew Jedną z głoacutewnych roacuteżnic jestjego deklaracja Zwykle wygląda ona tak

typ_zwracanej_wartości (nazwa_wskaźnika)(typ1 parametr1 typ2 parametr2)

Oczywiście parametroacutew może być więcej (albo też w ogoacutele może ich nie być) Oto przykład wyko-rzystania wskaźnika na funkcję

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

6Tak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresu funkcji

126 ROZDZIAŁ 17 WSKAŹNIKI

int (wsk_suma)(int a int b)wsk_suma = sumaprintf(4+5=dn wsk_suma(45))return 0

Zwroacutećmy uwagę na dwie rzeczy

przypisując nazwę funkcji bez nawiasoacutew do wskaźnika automatycznie informujemy kompilatorże chodzi nam o adres funkcji

wskaźnika używamy tak jak normalnej funkcji na ktoacuterą on wskazuje

17102 Do czego można użyć wskaźnikoacutew na funkcjeJęzyk C jest językiem strukturalnym jednak dzięki wskaźnikom istnieje w nim możliwość ldquozaszczepie-niardquo pewnych obiektowych właściwości Wskaźnik na funkcję może być np elementem struktury mdashwtedy mamy bardzo prymitywną namiastkę klasy ktoacuterą dobrze znają programiści piszący w językuC++ Ponadto dzięki wskaźnikom możemy tworzyć mechanizmy działające na zasadzie funkcji zwrot-nej7 Dobrym przykładem może być np tworzenie sterownikoacutew gdzie musimy poinformować roacuteżnepodsystemy jakie funkcje w naszym kodzie służą do wykonywania określonych czynności Przykład

struct urzadzenie int (otworz)(void)void (zamknij)(void)

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

int rejestruj_urzadzenie(struct urzadzenie u) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzenieotworz = moje_urzadzenie_otworzmoje_urzadzeniezamknij = moje_urzadzenie_zamknijrejestruj_urzadzenie(ampmoje_urzadzenie)

Wten sposoacutebwpamięci każda klasamusi przechowywaćwszystkiewskaźniki dowszystkichmetodInnym rozwiązaniem może być stworzenie statycznej struktury ze wskaźnikami do funkcji i woacutewczasw strukturze będzie przechowywany jedynie wskaźnik do tej struktury np

struct urzadzenie_metody

7Funkcje zwrotne znalazły zastosowanie głoacutewnie w programowaniu

1711 MOŻLIWE DEKLARACJE WSKAŹNIKOacuteW 127

int (otworz)(void)void (zamknij)(void)

struct urzadzenie const struct urzadzenie_metody m

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

static const struct urzadzenie_metodymoje_urzadzenie_metody = moje_urzadzenie_otworzmoje_urzadzenie_zamknij

int rejestruj_urzadzenie(struct urzadzenie ampu) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzeniem = ampmoje_urzadzenie_metodyrejestruj_urzadzenie(ampmoje_urzadzenie)

1711 Możliwe deklaracje wskaźnikoacutew

Tutaj znajduje się kroacutetkie kompendium jak definiować wskaźniki oraz co oznaczają poszczegoacutelne defi-nicje

1712 Popularne błędy

Jednym z najczęstszych błędoacutew oproacutecz proacuteb wykonania operacji na wskaźniku są odwołania siędo obszaru pamięci po jego zwolnieniu Po wykonaniu funkcji free() nie możemy już wykonywaćżadnych odwołań do zwolnionego obszaru Innym rodzajem błędoacutew są

odwołania do adresoacutew pamięci ktoacutere są poza obszarem przydzielonym funkcją malloc()

brak sprawdzania czy dany wskaźnik nie ma wartości

wycieki pamięci czyli niezwalnianie całej przydzielonej wcześniej pamięci

128 ROZDZIAŁ 17 WSKAŹNIKI

i zmienna całkowita (typu int) ip wskaźnik p wskazujący na zmienną całkowitąa[] tablica a liczb całkowitych typu intf() funkcja f zwracająca liczbę całkowitą typu intpp wskaźnik pp na wskaźnik wskazujący na liczbę całkowitą typu int

(pa)[] wskaźnik pa wskazujący na tablicę liczb całkowitych typu int(pf)() wskaźnik pf na funkcję zwracającą liczbę całkowitą typu intap[] tablica ap wskaźnikoacutew na liczby całkowite typu intfp() funkcja fp ktoacutera zwraca wskaźnik na zmienną typu intppp wskaźnik ppp wskazujący na wskaźnik wskazujący na wskaźnik wskazu-

jący na liczbę typu int(ppa)[] wskaźnik ppa na wskaźnik wskazujący na tablicę liczb całkowitych typu

int(ppf)() wskaźnik ppf wskazujący na wskaźnik funkcji zwracającej dane typu int(pap)[] wskaźnik pap wskazujący na tablicę wskaźnikoacutew na typ int(pfp)() wskaźnik pfp na funkcję zwracającą wskaźnik na typ intapp[] tablica wskaźnikoacutew app wskazujących na typ int

(apa[])[] tablica wskaźnikoacutew apa wskazujących wskaźniki na typ int(apf[])() tablica wskaźnikoacutew apf na funkcję ktoacutere zwracają wskaźniki na typ intfpp() funkcja fpp ktoacutera zwraca wskaźnik na wskaźnik na wskaźnik ktoacutery wska-

zuje typ int(fpa())[] funkcja fpa ktoacutera zwraca wskaźnik na tablicę liczb typu int(fpf())() funkcja fpf ktoacutera zwraca wskaźnik na funkcję ktoacutera zwraca dane typu int

1713 Ciekawostki w rozdziale Zmienne pisaliśmy o stałych Normalnie nie mamy możliwości zmiany ich wartości

ale z użyciem wskaźnikoacutew staje się to możliwe

const int CONST=0int c=ampCONSTc = 1printf(inCONST) wypisuje 1

Konstrukcja taka może jednak wywołać ostrzeżenie kompilatora bądź nawet jego błąd mdash wtedymoże pomoacutec jawne rzutowanie z const int na int

język C++ oferuje mechanizm podobny do wskaźnikoacutew ale nieco wygodniejszy ndash referencje

język C++ dostarcza też innego sposobu dynamicznej alokacji i zwalniania pamięci mdash przez ope-ratory new i delete

w rozdziale Typy złożone znajduje się opis implementacji listy za pomocą wskaźnikoacutew Przy-kład ten może być bardzo przydatny przy zrozumieniu po co istnieją wskaźniki jak się nimiposługiwać oraz jak dobrze zarządzać pamięcią

Rozdział 18

Napisy

W dzisiejszych czasach komputer przestał być narzędziem tylko i wyłącznie do przetwarzania danychOd programoacutew komputerowych zaczęto wymagać czegoś nowego mdash program w wyniku swojego dzia-łania nie ma zwracać danych rozumianych tylko przez autora programu lecz powinien być na tylekomunikatywny aby przeciętny użytkownik komputera moacutegł bez problemu tenże komputer obsłużyćDo przechowywania tychże komunikatoacutew służą tzw ldquołańcuchyrdquo (ang string) czyli ciągi znakoacutew

Język C nie jest wygodnym narzędziem do manipulacji napisami Jak się wkroacutetce przekonamyzestaw funkcji umożliwiających operacje na napisach w bibliotece standardowej C jest raczej skromnyDodatkowo problemem jest sposoacuteb w jaki łańcuchy przechowywane są w pamięci

Napisy w języku Cmogą być przyczyną wielu trudnych do wykrycia błędoacuteww programach Wartodobrze zrozumieć jak należy operować na łańcuchach znakoacutew i zachować szczegoacutelną ostrożność w tychmiejscach gdzie napisoacutew używamy

181 Łańcuy znakoacutew w języku CNapis jest zapisywany w kodzie programu jako ciąg znakoacutew zawarty pomiędzy dwoma cudzysłowami

printf (Napis w języku C)

Wpamięci taki łańcuch jest następującympo sobie ciągiem znakoacutew (char) ktoacutery kończy się znakiemldquonullrdquo (czyli po prostu liczbą zero) zapisywanym jako rsquorsquo

Jeśli mamy napis do poszczegoacutelnych znakoacutew odwołujemy się jak w tablicy

char tekst = Jakiś tam tekstprintf(cn przykład[0]) wypisze p - znaki w napisach są numerowane od zera printf(cn tekst[2]) wypisze k

Ponieważ napis w pamięci kończy się zerem umieszczonym tuż za jego zawartością odwołanie się doznaku o indeksie roacutewnym długości napisu zwroacuteci zero

printf(d test[4]) wypisze 0

Napisy możemywczytywać z klawiatury i wypisywać na ekran przy pomocy dobrze znanych funk-cji scanf printf i pokrewnych Formatem używanym dla napisoacutew jest s

printf(s tekst)

129

130 ROZDZIAŁ 18 NAPISY

Większość funkcji działających na napisach znajduje się w pliku nagłoacutewkowym stringhJeśli łańcuch jest zbyt długi można zapisać gow kilku linijkach ale wtedy przechodząc do następnej

linii musimy na końcu postawić znak ldquordquo

printf(Ten napis zajmuje więcej niż jedną linię)

Instrukcja taka wydrukuje

Ten napis zajmuje więcej niż jedną linię

Możemy zauważyć że napis ktoacutery w programie zajął więcej niż jedną linię na ekranie zajął tylkojedną Jest tak ponieważ ldquordquo informuje kompilator że łańcuch będzie kontynuowany w następnej liniikodumdash niemawpływu na prezentację łańcucha Abywydrukować napis w kilku liniach należywstawićdo niego n (ldquonrdquo pochodzi tu od ldquonew linerdquo czyli ldquonowa liniardquo)

printf(Ten napisnna ekranienzajmie więcej niż jedną linię)

W wyniku otrzymamy

Ten napisna ekraniezajmie więcej niż jedną linię

1811 Jak komputer przeowuje w pamięci łańcu

Rysunek 181 Napis ldquoMerkkijonordquo przechowywany w pamięci

Zmienna ktoacutera przechowuje łańcuch znakoacutew jest tak naprawdę wskaźnikiem do ciągu znakoacutew(bajtoacutew) w pamięci Możemy też myśleć o napisie jako o tablicy znakoacutew (jak wyjaśnialiśmy wcześniejtablice to też wskaźniki)

Możemy wygodnie zadeklarować napis

char tekst = Jakiś tam tekst Umieszcza napis w obszarze danych programu i przypisuje adres

char tekst[] = Jakiś tam tekst Umieszcza napis w tablicy char tekst[] = Jakis tam tekst0

Tekst to taka tablica jak każda inna

Kompilator automatycznie przydziela wtedy odpowiednią ilość pamięci (tyle bajtoacutew ile jest literplus jeden dla kończącego nulla) Jeśli natomiast wiemy że dany łańcuch powinien przechowywaćokreśloną ilość znakoacutew (nawet jeśli w deklaracji tego łańcucha podajemy mniej znakoacutew) deklarujemygo w taki sam sposoacuteb jak tablicę jednowymiarową

char tekst[80] = Ten tekst musi być kroacutetszy niż 80 znakoacutew

Należy cały czas pamiętać że napis jest tak naprawdę tablicą Jeśli zarezerwowaliśmy dla napisu znakoacutew to przypisanie do niego dłuższego napisu spowoduje pisanie po pamięci

Uwaga Deklaracja char tekst = cokolwiek oraz char tekst = cokolwiek pomimo że wyglądająbardzo podobnie bardzo się od siebie roacuteżnią W przypadku pierwszej deklaracji proacuteba zmodyfikowania

181 ŁAŃCUCHY ZNAKOacuteW W JĘZYKU C 131

napisu (np tekst[0] = C) może mieć nieprzyjemne skutki Dzieje się tak dlatego że char tekst =cokolwiek deklaruje wskaźnik na stały obszar pamięci1

Pisanie po pamięci może czasami skończyć się błędem dostępu do pamięci (ldquosegmentation faultrdquow systemach ) i zamknięciem programu jednak może zdarzyć się jeszcze gorsza ewentualność mdashmożemy zmienić w ten sposoacuteb przypadkowo wartość innych zmiennych Program zacznie wtedy za-chowywać się nieprzewidywalnie mdash zmienne a nawet stałe co do ktoacuterych zakładaliśmy że ich wartośćbędzie ściśle ustalona mogą przyjąć taką wartość jaka absolutnie nie powinna mieć miejsca Wartowięc stosować zabezpieczenia typu makra assert

Kluczowy jest też kończący napis znak null W zasadzie wszystkie funkcje operujące na napisachopierają właśnie na nim Na przykład strlen szuka rozmiaru napisu idąc od początku i zliczając znaki ażnie natrafi na znak o kodzie zero Jeśli nasz napis nie kończy się znakiem null funkcja będzie szła dalejpo pamięci Na szczęście wszystkie operacje podstawienia typu tekst = ldquoTekstrdquo powodują zakończenienapisu nullem (o ile jest na niego miejsce) 2

1812 Znaki specjalneJak zapewne zauważyłeś w poprzednim przykładzie w łańcuchu ostatnim znakiem jest znak o wartościzero (rsquorsquo) Jednak łańcuchy mogą zawierać inne znaki specjalne(sekwencje sterujące) np

rsquoarsquo - alarm (sygnał akustyczny terminala)

rsquobrsquo - backspace (usuwa poprzedzający znak)

rsquorsquo - wysuniecie strony (np w drukarce)

rsquorrsquo - powroacutet kursora (karetki) do początku wiersza

rsquonrsquo - znak nowego wiersza

rsquordquo - cudzysłoacutew

rsquordquo - apostrof rsquorsquo - ukośnik wsteczny (backslash)

rsquotrsquo - tabulacja pozioma

rsquovrsquo - tabulacja pionowa

rsquorsquo - znak zapytania (pytajnik)

rsquoooorsquo - liczba zapisana w systemie oktalnym (oacutesemkowym) gdzie rsquoooorsquo należy zastąpić trzycy-frową liczbą w tym systemie

rsquoxhhrsquo - liczba zapisana w systemie heksadecymalnym (szesnastkowym) gdzie rsquohhrsquo należy za-stąpić dwucyfrową liczbą w tym systemie

rsquounnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnrsquo należy zastąpić czterocyfrowym identyfika-torem znaku w systemie szesnatkowym rsquonnnnrsquo odpowiada dłuższej formie w postaci rsquonnnnrsquo

rsquounnnnnnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnnnnnrsquo należy zastąpić ośmiocyfrowymidentyfikatorem znaku w systemie szesnatkowym

Warto zaznaczyć że znak nowej linii (rsquonrsquo) jest w roacuteżny sposoacuteb przechowywany w roacuteżnych sys-temach operacyjnych Wiąże się to z pewnymi historycznymi uwarunkowaniami W niektoacuterych sys-temach używa się do tego jednego znaku o kodzie xA (Line Feed mdash nowa linia) Do tej rodzinyzaliczamy systemy z rodziny Unix Linux BSD Mac OS X inne Drugą konwencją jest zapisywaniersquonrsquo za pomocą dwoacutech znakoacutew LF (Line Feed) + CR (Carriage return mdash powroacutet karetki) Znak CRreprezentowany jest przez wartość xD Kombinacji tych dwoacutech znakoacutew używają min CPM DOSOS Microso Windows Trzecia grupa systemoacutew używa do tego celu samego znaku CR Są to sys-temy działające na komputerach Commodore Apple II oraz Mac OS do wersji W związku z tym plikutworzony w systemie Linux może wyglądać dziwnie pod systemem Windows

1Można się zatem zastanawiać czemu kompilator dopuszcza przypisanie do zwykłego wskaźnika wskazania nastały obszar skoro kod const int foo int bar = foo generuje ostrzeżenie lub wręcz się nie kompiluje Jest topewna zaszłość historyczna wynikająca z faktu że słoacutewko const zostało wprowadzone do języka gdy już był on wpowszechnym użyciu

2Nie należy mylić znaku null (czyli znaku o kodzie zero) ze wskaźnikiem null (czy też )

132 ROZDZIAŁ 18 NAPISY

182 Operacje na łańcua

1821 Poroacutewnywanie łańcuoacutewNapisy to tak naprawdęwskaźniki Tak więc używając zwykłego operatora poroacutewnania == otrzymamywynik poroacutewnania adresoacutew a nie tekstoacutew

Do poroacutewnywania dwoacutech ciągoacutew znakoacutew należy użyć funkcji strcmp zadeklarowanej w pliku na-głoacutewkowym stringh Jako argument przyjmuje ona dwa napisy i zwraca wartość ujemną jeżeli napispierwszy jestmniejszy od drugiego jeżeli napisy są roacutewne lub wartość dodatnią jeżeli napis pierwszyjest większy od drugiego Ciągi znakoacutew poroacutewnywalne są leksykalnie kody znakoacutew czyli np (przyj-mując kodowanie ASCII) a jest mniejsze od b ale jest większe od B Np

include ltstdiohgtinclude ltstringhgt

int main(void) char str1[100] str2[100]int cmp

puts(Podaj dwa ciagi znakow )fgets(str1 sizeof str1 stdin)fgets(str2 sizeof str2 stdin)

cmp = strcmp(str1 str2)if (cmplt0)

puts(Pierwszy napis jest mniejszy) else if (cmpgt0)

puts(Pierwszy napis jest wiekszy) else

puts(Napisy sa takie same)

return 0

Czasami możemy chcieć poroacutewnać tylko fragment napisu np sprawdzić czy zaczyna się od jakie-goś ciągu W takich sytuacjach pomocna jest funkcja strncmp W poroacutewnaniu do strcmp() przyjmujeona jeszcze jeden argument oznaczający maksymalną liczbę znakoacutew do poroacutewnania

include ltstdiohgtinclude ltstringhgt

int main(void) char str[100]int cmp

fputs(Podaj ciag znakow stdout)fgets(str sizeof str stdin)

if (strncmp(str foo 3)) puts(Podany ciag zaczyna sie od foo)

return 0

182 OPERACJE NA ŁAŃCUCHACH 133

1822 Kopiowanie napisoacutewDo kopiowania ciągoacutew znakoacutew służy funkcja strcpy ktoacutera kopiuje drugi napis w miejsce pierwszegoMusimy pamiętać by w pierwszym łańcuchu było wystarczająco dużo miejsca

char napis[100]strcpy(napis Ala ma kota)

Znacznie bezpieczniej jest używać funkcji strncpy ktoacutera kopiuje co najwyżej tyle bajtoacutew ile podanojako trzeci parametr Uwaga Jeżeli drugi napis jest za długi funkcja nie kopiuje znaku null na koniecpierwszego napisu dlatego zawsze trzeba to robić ręcznie

char napis[100]strncpy(napis Ala ma kota sizeof napis - 1)napis[sizeof napis - 1] = 0

1823 Łączenie napisoacutewDo łączenia napisoacutew służy funkcja strcat ktoacutera kopiuje drugi napis do pierwszego Ponownie jak wprzypadku strcpymusimy zagwarantować by w pierwszym łańcuchu było wystarczająco dużo miejsca

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrcat(napis1 napis2)puts(napis1)return 0

I ponownie jak w przypadku strcpy istnieje funkcja strncat ktoacutera skopiuje co najwyżej tyle bajtoacutewile podano jako trzeci argument i dodatkowo dopisze znak null Przykładowo powyższy kod bezpieczniejzapisać jako

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrncat(napis1 napis2 sizeof napis1 - 1)puts(napis1)return 0

Osoby ktoacutere programowały w językach skryptowych muszą bardzo uważać na łączenie i kopiowa-nie napisoacutew Kompilator języka C nie wykryje nadpisania pamięci za zmienną łańcuchową i nie przy-dzieli dodatkowego obszaru pamięci Może się zdarzyć że program pomimo nadpisywania pamięci załańcuchem będzie nadal działał co bardzo utrudni wykrywanie tego typu błędoacutew

134 ROZDZIAŁ 18 NAPISY

183 Bezpieczeństwo kodu a łańcuy

1831 Przepełnienie buforaO co właściwie chodzi z tymi funkcjami strncpy i strncat Otoacuteż niewinnie wyglądające łańcuchy mogąokazać się zaboacutejcze dla bezpieczeństwa programu a przez to nawet dla systemu w ktoacuterym ten programdziała Może brzmi to strasznie lecz jest to prawda Może pojawić się tutaj pytanie ldquow jaki sposoacutebłańcuch może zaszkodzić programowirdquo Otoacuteż może i to całkiem łatwo Przeanalizujmy następującykod

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strcpy(haslo argv[1]) tutaj następuje przepełnienie bufora if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Jest to bardzo prosty program ktoacutery wykonuje jakąś akcję jeżeli podane jako pierwszy argumenthasło jest poprawne Sprawdźmy czy działa

$ aout niepoprawnePodales bledne haslo$ aout poprawneWitaj wprowadziles poprawne haslo

Jednak okazuje się że z powodu użycia funkcji strcpy włamywacz nie musi znać hasła aby programuznał że zna hasło np

$ aout 11111111111111111111111111111111Witaj wprowadziles poprawne haslo

Co się stało Podaliśmy ciąg jedynek dłuższy niż miejsce przewidziane na hasło Funkcja strcpy()kopiując znaki z argv[1] do tablicy (bufora) haslo przekroczyła przewidziane dla niego miejsce i szładalej mdash gdzie znajdowała się zmienna haslo poprawne strcpy() kopiowała znaki już tam gdzie znajdo-wały się inne dane mdash między innymi wpisała jedynkę do haslo poprawne

Podany przykład może się roacuteżnie zachowywać w zależności od kompilatora jakim został skompi-lowany i systemu na jakim działa ale ogoacutelnie mamy do czynienia z poważnym niebezpieczeństwem

183 BEZPIECZEŃSTWO KODU A ŁAŃCUCHY 135

Taką sytuację nazywamy przepełnieniem bufora Może umożliwić dostęp do komputera osobomnieuprzywilejowanym Należy wystrzegać się tego typu konstrukcji a wmiejsce niebezpiecznej funkcjistrcpy stosować bardziej bezpieczną strncpy

Oto bezpieczna wersja poprzedniego programu

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strncpy(haslo argv[1] sizeof haslo - 1)haslo[sizeof haslo - 1] = 0if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Bezpiecznymi alternatywami do strcpy i strcat są też funkcje strlcpy oraz strlcat opracowane przezprojekt OpenBSD i dostępne do ściągnięcia na wolnej licencji strlcpy strlcat strlcpy() działa podobniedo strncpy strlcpy (buf argv[1] sizeof buf) jednak jest szybsza (nie wypełnia pustego miejscazerami) i zawsze kończy napis nullem (czego nie gwarantuje strncpy) strlcat(dst src size) działanatomiast jak strncat(dst src size-1)

Do innych niebezpiecznych funkcji należy np gets zamiast ktoacuterej należy używać fgetsZawsze możemy też alokować napisy dynamicznie

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

136 ROZDZIAŁ 18 NAPISY

haslo = malloc(strlen(argv[1]) + 1) +1 dla znaku null if (haslo)

fputs(Za malo pamiecin stderr)return EXIT_FAILURE

strcpy(haslo argv[1])if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)free(haslo)return EXIT_SUCCESS

1832 Nadużycia z udziałem ciągoacutew formatującyJednak to nie koniec kłopotoacutew z napisami Wielu programistoacutew nieświadomych zagrożenia częstoużywa tego typu konstrukcji

include ltstdiohgtint main (int argc char argv[])

printf (argv[1])

Z punktu widzenia bezpieczeństwa jest to bardzo poważny błąd programu ktoacutery może nieść zesobą katastrofalne skutki Prawidłowo napisany kod powinien wyglądać następująco

include ltstdiohgtint main (int argc char argv[])

printf (s argv[1])

lub

include ltstdiohgtint main (int argc char argv[])

fputs (argv[1] stdout)

Źroacutedło problemu leży w konstrukcji funkcji printf Przyjmuje ona bowiem za pierwszy parametrłańcuch ktoacutery następnie przetwarza Jeśli w pierwszym parametrze wstawimy jakąś zmienną to funk-cja printf potraktuje ją jako ciąg znakoacutew razem ze znakami formatującymi Zatem ważne aby wcześniewyrobić sobie nawyk stosowania funkcji printf z co najmniej dwoma parametrami nawet w przypadkuwyświetlenia samego tekstu

184 KONWERSJE 137

184 KonwersjeCzasami zdarza się że łańcuch można interpretować nie tylko jako ciąg znakoacutew lecz np jako liczbęJednak aby dało się taką liczbę przetworzyć musimy skopiować ją do pewnej zmiennej Aby ułatwićprogramistom tego typu zamiany powstał zestaw funkcji bibliotecznych Należą do nich

atol strtol mdash zamienia łańcuch na liczbę całkowitą typu long

atoi mdash zamienia łańcuch na liczbę całkowitą typu int

atoll strtoll mdash zamienia łańcuch na liczbę całkowitą typu long long ( bity) dodatkowo istniejeprzestarzała funkcja atoq będąca rozszerzeniem

atof strtod mdash przekształca łańcuch na liczbę typu double

Ogoacutelnie rzecz ujmując funkcje z serii ato nie pozwalają na wykrycie błędoacutew przy konwersji i dla-tego gdy jest to potrzebne należy stosować funkcje strto

Czasami przydaje się też konwersja w drugą stronę tzn z liczby na łańcuch Do tego celu możeposłużyć funkcja sprintf lub snprintf sprintf jest bardzo podobna do printf tyle że wyniki jej praczwracane są do pewnego łańcucha a nie wyświetlane np na ekranie monitora Należy jednak uwa-żać przy jej użyciu (patrz mdash Bezpieczeństwo kodu a łańcuchy) snprintf (zdefiniowana w nowszymstandardzie) dodatkowo przyjmuje jako argument wielkość bufora docelowego

185 Operacje na znakaWarto też powiedzieć w tymmiejscu o operacjach na samych znakach Spoacutejrzmy na poniższy program

include ltstdiohgtinclude ltctypehgtinclude ltstringhgt

int main()

int znakwhile ((znak = getchar())=EOF)

if( islower(znak) ) znak = toupper(znak)

else if( isupper](znak) ) znak = tolower(znak)

putchar(znak)

return 0

Program ten zmieniawewczytywanym tekściewielkie litery namałe i odwrotnie Wykorzystujemyfunkcje operujące na znakach z pliku nagłoacutewkowego ctypeh isupper sprawdza czy znak jest wielkąliterą natomiast toupper zmienia znak (o ile jest literą) na wielką literę Analogicznie jest dla funkcjiislower i tolower

Jako ćwiczenie możesz tak zmodyfikować program żeby odczytywał dane z pliku podanego jakoargument lub wprowadzonego z klawiatury

186 Częste błędy pisanie do niezaalokowanego miejsca

138 ROZDZIAŁ 18 NAPISY

char tekstscanf(s tekst)

zapominanie o kończącym napis nullu

char test[4] = test nie zmieścił się null kończący napis

nieprawidłowe poroacutewnywanie łańcuchoacutew

char tekst1[] = jakis tekstchar tekst2[] = jakis tekstif( tekst1 == tekst2 ) tu zawsze będzie fałsz bo == poroacutewnuje adresy należy użyć strcmp()

187 UnicodeZobacz w Wikipedii Uni-code W dzisiejszych czasach brak obsługi wielu językoacutew praktycznie marginalizowałoby język Dlatego też

C wprowadza możliwość zapisu znakoacutew wg norm Unicode

1871 Jaki typDo przechowywania znakoacutew zakodowanych w Unicode powinno się korzystać z typu war t Jegodomyślny rozmiar jest zależny od użytego kompilatora lecz w większości zaktualizowanych kompila-toroacutew powinny to być bajty Typ ten jest częścią języka C++ natomiast w C znajduje się w plikunagłoacutewkowym stddefh

Alternatywą jest wykorzystanie gotowych bibliotek dla Unicode (większość jest dostępnych jedyniedla C++ nie wspoacutełpracuje z C) ktoacutere często mają zdefiniowane własne typy jednak zmuszeni jesteśmywtedy do przejścia ze znanych nam już funkcji jak np strcpy strcmp na funkcje dostarczane przezbibliotekę co jest dość niewygodne My zajmiemy się pierwszym wyjściem

1872 Jaki rozmiar i jakie kodowanieUnicode określa jedynie jakiej liczbie odpowiada jaki znak nie moacutewi zaś nic o sposobie dekodowania(tzn jaka sekwencja znakoacutew odpowiada jakiemu znakuznakom) Jako że Unicode obejmuje tysznakoacutew zmienna zdolna pomieścić go w całości musi mieć przynajmniej bajty Niestety procesory niefunkcjonują na zmiennych o tym rozmiarze pracują jedynie na zmiennych o wielkościach oraz bajtoacutew (kolejne potęgi liczby ) Dlatego też jeśli wciąż uparcie chcemy być dokładni i zastosowaćprzejrzyste kodowanie musimy skorzystać ze zmiennej -bajtowej ( bity) Tak do sprawy podeszlitwoacutercy kodowania Unicode nazwanego -UCS- Ten typ kodowania po prostu przydziela każ-Zobacz w Wikipedii -32demu znakowi Unicode kolejne liczby Jest to najbardziej intuicyjny i wygodny typ kodowania ale jakwidać ciągi znakoacutew zakodowane w nim są bardzo obszerne co zajmuje dostępną pamięć spowalniadziałanie programu oraz drastycznie pogarsza wydajność podczas transferu przez sieć Poza -istnieje jeszcze wiele innych kodowań Najpopularniejsze z nich to

- mdash od do bajtoacutew (dla znakoacutew poniżej do bajtoacutew) na znak przez co jest skraj-nie niewygodny gdy chcemy przeprowadzać jakiekolwiek operacje na tekście bez korzystania zgotowych funkcji

- mdash lub bajty na znak ręczne modyfikacje łańcucha są bardziej skomplikowane niż przy-

UCS- mdash bajty na znak przez co znaki z numerami powyżej nie są uwzględnione roacutewniewygodny w użytkowaniu co -

187 UNICODE 139

Ręczne operacje na ciągach zakodowanych w - i - są utrudnione ponieważ w przeci-wieństwie do - gdzie można określić iż powiedzmy znak ciągu zajmuje bajty od do (gdyżz goacutery wiemy że znak zajął bajty od do ) w tych kodowaniach musimy najpierw określić rozmiar znaku Ponadto gdy korzystamy z nich nie działają wtedy funkcje udostępniane przez biblioteki Cdo operowania na ciągach znakoacutew

Priorytet Proponowane kodowaniamały rozmiar -8

łatwa i wydajna edycja -32 lub -2przenośność -83

ogoacutelna szybkość -2 lub -8

Co należy zrobić by zacząć korzystać z kodowania - (domyślne kodowanie dla C)

powinniśmy korzystać z typu wchar t (ang ldquowide characterrdquo) jednak jeśli chcemy udostępniaćkod źroacutedłowy programu do kompilacji na innych platformach powinniśmy ustawić odpowiednieparametry dla kompilatoroacutew by rozmiar był identyczny niezależnie od platformy

korzystamy z odpowiednikoacutew funkcji operujących na typie char pracujących na wchar t (z re-guły składnia jest identyczna z tą roacuteżnicą że w nazwach funkcji zastępujemy ldquostrrdquo na ldquowcsrdquo npstrcpy mdash wcscpy strcmp mdash wcscmp)

jeśli przyzwyczajeni jesteśmy do korzystania z klasy string powinniśmy zamiast niej korzystaćz wstring ktoacutera posiada zbliżoną składnię ale pracuje na typie wchar t

Co należy zrobić by zacząć korzystać z Unicode

gdy korzystamy z kodowań innych niż - i - powinniśmy zdefiniować własny typ

w wykorzystywanych przez nas bibliotekach podajemy typ wykorzystanego kodowania

gdy chcemy ręcznie modyfikować ciąg musimy przeczytać specyfikację danego kodowania sąone wyczerpująco opisane na siostrzanym projekcie Wikibooks mdash Wikipedii

Przykład użycia kodowania -

include ltstddefhgt jeśli używamy C++ możemy opuścić tę linijkę include ltstdiohgtinclude ltstringhgt

int main() wchar_t wcs1 = LAla ma kotawchar_t wcs2 = LKot ma Alewchar_t calosc[25]

wcscpy(calosc wcs1)(calosc + wcslen(wcs1)) = L wcscpy(calosc + wcslen(wcs1) + 1 wcs2)

printf(lancuch wyjsciowy lsn calosc)return 0

140 ROZDZIAŁ 18 NAPISY

Rozdział 19

Typy złożone

191 typedefJest to słowo kluczowe ktoacutere służy do definiowania typoacutew pochodnych np

typedef stara_nazwa nowa_nazwatypedef int mojInttypedef int WskNaInt

od tej pory mozna używać typoacutew mojInt i WskNaInt

192 Typ wyliczeniowySłuży do tworzenia zmiennych ktoacutere powinny przechowywać tylko pewne z goacutery ustalone wartości

enum Nazwa WARTOSC_1 WARTOSC_2 WARTOSC_N

Na przykład można w ten sposoacuteb stworzyć zmienną przechowującą kierunek

enum Kierunek W_GORE W_DOL W_LEWO W_PRAWO

enum Kierunek kierunek = W_GORE

ktoacuterą można na przykład wykorzystać w instrukcji switch

switch(kierunek)

case W_GOREprintf(w goacuteręn)break

case W_DOLprintf(w doacutełn)break

defaultprintf(gdzieś w bokn)

Tradycyjnie przechowywane wielkości zapisuje się wielkimi literami (W GORE W DOL)Tak naprawdę C przechowuje wartości typu wyliczeniowego jako liczby całkowite o czym można

się łatwo przekonać

141

142 ROZDZIAŁ 19 TYPY ZŁOŻONE

kierunek = W_DOLprintf(in kierunek) wypisze 1

Kolejne wartości to po prostu liczby naturalne domyślnie pierwsza to zero druga jeden itp Mo-żemy przy deklarowaniu typu wyliczeniowego zmienić domyślne przyporządkowanie

enum Kierunek W_GORE W_DOL = 8 W_LEWO W_PRAWO printf(i in W_DOL W_LEWO) wypisze 8 9

Co więcej liczby mogą się powtarzać i wcale nie muszą być ustawione w kolejności rosnącej

enum Kierunek W_GORE = 5 W_DOL = 5 W_LEWO = 2 W_PRAWO = 1 printf(i in W_DOL W_LEWO) wypisze 5 2

Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajną ich obsługęale stwarza niebezpieczeństwa mdash można przypisywać pod typ wyliczeniowy liczby nawet nie mająceodpowiednika w wartościach a kompilator może o tym nawet nie ostrzec

kierunek = 40

193 StrukturyStruktury to specjalny typ danych mogący przechowywać wiele wartości w jednej zmiennej Od tablicjednakże roacuteżni się tym iż te wartości mogą być roacuteżnych typoacutew

Struktury definiuje się w następujący sposoacuteb

struct Struktura int pole1int pole2char pole3

gdzie ldquoStrukturardquo to nazwa tworzonej strukturyNazewnictwo ilość i typ poacutel definiuje programista według własnego uznaniaZmienną posiadającą strukturę tworzy się podając jako jej typ nazwę struktury

struct Struktura zmienna

Dostęp do poszczegoacutelnych poacutel uzyskuje się przy pomocy operatora wyboru składnika kropki (rsquorsquo)

zmiennaSpole1 = 60 przypisanie liczb do poacutel zmiennaSpole2 = 2zmiennaSpole3 = a a teraz znaku

194 UnieUnie to kolejny sposoacuteb prezentacji danychw pamięci Na pierwszy rzut oka wyglądają bardzo podobniedo struktur

union Nazwa typ1 nazwa1typ2 nazwa2

Na przykład

194 UNIE 143

union LiczbaLubZnak int calkowitachar znakdouble rzeczywista

Pola w unii nakładają się na siebie w ten sposoacuteb że w danej chwili można w niej przechowywaćwartość tylko jednego typu Unia zajmuje w pamięci tyle miejsca ile zajmuje największa z jej składo-wych W powyższym przypadku unia będzie miała prawdopodobnie rozmiar typu double czyli często bity a całkowita i znak będą wskazywały odpowiednio na pierwsze cztery bajty lub na pierwszy bajtunii (choć nie musi tak być zawsze) Dlaczego tak Taka forma często przydaje się np do konwersji po-między roacuteżnymi typami danych Możemy dzięki unii podzielić zmienną -bitową na cztery składowezmienne o długości bitoacutew każda

Do konkretnych wartości poacutel unii odwołujemy się podobnie jak w przypadku struktur za pomocąkropki

union LiczbaLubZnak liczbaliczbacalkowita = 10printf(dn liczbacalkowita)

Zazwyczaj użycie unii ma na celu zmniejszenie zapotrzebowania na pamięć gdy naraz będzie wy-korzystywane tylko jedno pole i jest często łączone z użyciem struktur

Przyjrzyjmy się teraz przykładowi ktoacutery powinien dobitnie zademonstrować działanie unii

include ltstdiohgt

struct adres_bajtowy __uint8_t a__uint8_t b__uint8_t c__uint8_t d

union adres __uint32_t ipstruct adres_bajtowy badres

int main ()

union adres addraddrbadresa = 192addrbadresb = 168addrbadresc = 1addrbadresd = 1printf (Adres IP w postaci 32-bitowej zmiennej 08xnaddrip)return 0

Zauważyłeś pewien ciekawy efekt Jeśli uruchomiłeś ten program na typowym komputerze domo-wym (rodzina i) na ekranie zapewne pojawił Ci się taki oto napis

Adres IP w postaci 32-bitowej zmiennej 0101a8c0

Dlaczego jedynki są na początku zmiennej skoro w programie były to dwa ostatnie bajty (pola c id struktury) Jest to problem kolejności bajtoacutew Aby dowiedzieć się o nim więcej przeczytaj rozdział

144 ROZDZIAŁ 19 TYPY ZŁOŻONE

przenośność programoacutew Zauważyłeś zatem że za pomocą tego programu w prosty sposoacuteb zamienili-śmy cztery zmienne jednobajtowe w jedną czterobajtową Jest to tylko jedno z możliwych zastosowańunii

195 Inicjalizacja struktur i uniiJeśli tworzymy nową strukturę lub unię możemy zaraz po jej deklaracji wypełnić ją określonymi da-nymi Rozważmy tutaj przykład

struct moja_struct int achar b moja = 1c

Wzasadzie taka deklaracja nie roacuteżni się niczym odwypełnienia np tablicy danymi Jednak standardC wprowadza pewne udogodnienie zaroacutewno przy deklaracji struktur jak i unii Polega ono na tymże w nawiasie klamrowym możemy podać nazwy poacutel struktury lub unii ktoacuterym przypisujemy wartośćnp

struct moja_struct int achar b moja = b = c pozostawiamy pole a niewypełnione żadną konkretną wartością

196 Wspoacutelnewłasności typoacutewwyliczeniowy unii i struk-tur

Warto w zwroacutecić uwagę że język C++ przy deklaracji zmiennych typoacutew wyliczeniowych unii lubstruktur nie wymaga przed nazwą typu odpowiedniego słowa kluczowego Na przykład poniższy kodjest poprawnym programem C++

enum Enum A B C union Union int a float b struct Struct int a float b int main()

Enum eUnion uStruct se = Aua = 0sa = 0return e + ua + sa

Nie jest to jednak poprawny kod C i należy o tym pamiętać szczegoacutelnie jeżeli uczysz się języka Ckorzystając z kompilatora C++

Należy roacutewnież pamiętać że po klamrze zamykającej definicje musi następować średnik Brak tegośrednika jest częstym błędem powodującym czasami niezrozumiałe komunikaty błędoacutew Jedynym wy-jątkiem jest natychmiastowa definicja zmiennych danego typu na przykład

struct Struktura int pole

s1 s2 s3

196 WSPOacuteLNE WŁASNOŚCI TYPOacuteW WYLICZENIOWYCH UNII I STRUKTUR 145

Definicja typoacutew wyliczeniowych unii i struktur jest lokalna do bloku To znaczy możemy zdefi-niować strukturę wewnątrz jednej z funkcji (czy wręcz wewnątrz jakiegoś bloku funkcji) i tylko tambędzie można używać tego typu

Częstym idiomem w C jest użycie typedef od razu z definicją typu by uniknąć pisania enum unionczy struct przy deklaracji zmiennych danego typu

typedef struct struktura int pole

StrukturaStruktura s1struct struktura s2

W tym przypadku zmienne s i s są tego samego typu Możemy też zrezygnować z nazywaniasamej struktury

typedef struct int pole

StrukturaStruktura s1

1961 Wskaźnik na unię i strukturęPodobnie jak na każdą inną zmienna wskaźnik może wskazywać także na unię lub strukturę Otoprzykład

typedef struct int p1 p2

Struktura

int main ()

Struktura s = 0 0 Struktura wsk = ampswsk-gtp1 = 2wsk-gtp2 = 3return 0

Zapis wsk-gtp1 jest (z definicji) roacutewnoważny (wsk)p1 ale bardziej przejrzysty i powszechnie sto-sowany Wyrażenie wskp1 spowoduje błąd kompilacji (strukturą jest wsk a nie wsk)

1962 Zobacz też Powszechne praktyki mdash konstruktory i destruktory

1963 Pola bitoweStruktury mają pewne dodatkowe możliwości w stosunku do zmiennych Mowa tutaj o rozmiarzeelementu struktury W przeciwieństwie do zmiennej może on mieć nawet bit Aby moacutec zdefiniowaćtaką zmienną musimy użyć tzw pola bitowego Wygląda ono tak

struct moja unsigned int a14 4 bity

a28 8 bitoacutew (często 1 bajt) a31 1 bit a43 3 bity

146 ROZDZIAŁ 19 TYPY ZŁOŻONE

Wszystkie pola tej struktury mają w sumie rozmiar bitoacutew jednak możemy odwoływać się donichw taki sam sposoacuteb jak do innych elementoacutew struktury W ten sposoacuteb efektywniej wykorzystujemypamięć jednak istnieją pewne zjawiska ktoacuterych musimy być świadomi przy stosowaniu poacutel bitowychWięcej na ten temat w rozdziale przenośność programoacutew

Pola bitowe znalazły zastosowanie głoacutewnie w implementacjach protokołoacutew sieciowych

197 Studium przypadku mdash implementacja listy wskaźniko-wej

Zobacz w Wikipedii ListaRozważmy teraz coś co każdy z nas może spotkać w codziennym życiu Każdy z nas widział kiedyśjakiś przykład listy (czy to zakupoacutew czy też listę wierzycieli) Język C też oferuje listy jednak w progra-mowaniu listy będą służyły do czegoś innego Wyobraźmy sobie sytuację w ktoacuterej jesteśmy autoramigenialnego programu ktoacutery znajduje kolejne liczby pierwsze Oczywiście każdą kolejną liczbę pierw-szą może wyświetlać na ekran jednak z matematyki wiemy że dana liczba jest liczbą pierwszą jeśli niedzieli się przez żadną liczbę pierwszą ją poprzedzającą mniejszą od pierwiastka z badanej liczby Uffmniej więcej chodzi o to że moglibyśmy wykorzystać znalezione wcześniej liczby do przyspieszeniadziałania naszego programu Jednak nasze liczby trzeba jakoś mądrze przechować w pamięci Tablicemają ograniczenie mdash musimy z goacutery znać ich rozmiar Jeśli zapełnilibyśmy tablicę to przy znalezieniukażdej kolejnej liczby musielibyśmy

przydzielać nowy obszar pamięci o rozmiarze poprzedniego rozmiaru + rozmiar zmiennej prze-chowującej nowo znalezioną liczbę

kopiować zawartość starego obszaru do nowego

zwalniać stary nieużywany obszar pamięci

w ostatnim elemencie nowej tablicy zapisać znalezioną liczbę

Coacuteż trochę tutaj roboty jest a kopiowanie całej zawartości jednego obszaru w drugi jest czaso-chłonne W takim przypadku możemy użyć listy Tworząc listę możemy w prosty sposoacuteb przechowaćnowo znalezione liczby Przy użyciu listy nasze postępowanie ograniczy się do

przydzielenia obszaru pamięci aby przechować wartość obliczeń

dodać do listy nowy element

Prawda że proste Dodatkowo lista zajmuje w pamięci tylko tyle pamięci ile potrzeba na aktualnąliczbę elementoacutew Pusta tablica zajmuje natomiast tyle samo miejsca co pełna tablica

1971 Implementacja listyW języku C aby stworzyć listę musimy użyć struktur Dlaczego Ponieważ musimy przechować conajmniej dwie wartości

pewną zmienną (np liczbę pierwszą z przykładu)

wskaźnik na kolejny element listy

Przyjmijmy że szukając liczb pierwszych nie przekroczymy możliwości typu unsigned long

typedef struct element struct element next wskaźnik na kolejny element listy unsigned long val przechowywana wartość

el_listy

Zacznijmy zatem pisać nasz eksperymentalny program do wyszukiwania liczb pierwszych Pierw-szą liczbą pierwszą jest liczba Pierwszym elementem naszej listy będzie zatem struktura ktoacutera będzieprzechowywała liczbę Na co będzie wskazywało pole next Ponieważ na początku działania pro-gramu będziemy mieć tylko jeden element listy pole next powinno wskazywać na Umoacutewmy sięzatem że pole next ostatniego elementu listy będzie wskazywało mdash po tym poznamy że lista sięskończyła

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 147

include ltstdiohgtinclude ltstdlibhgttypedef struct element

struct element nextunsigned long val

el_listy

el_listy first pierwszy element listy

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (ilt=END++i) tutaj powinien znajdować się kod ktoacutery sprawdza podzielność sprawdzanej liczby przezpoprzednio znalezione liczby pierwsze oraz dodaje liczbę do listy w przypadku stwierdzenia

że jest ona liczbą pierwszą

wypisz_liste(first)return 0

Na początek zajmiemy się wypisywaniem listy W tym celu będziemy musieli ldquoodwiedzićrdquo każdyelement listy Elementy listy są połączone polem next aby przeglądnąć listę użyjemy następującegoalgorytmu

Ustaw wskaźnik roboczy na pierwszym elemencie listy

Jeśli wskaźnik ma wartość przerwij

Wypisz element wskazywany przez wskaźnik

Przesuń wskaźnik na element ktoacutery jest wskazywany przez pole next

Wroacuteć do punktu

void wypisz_liste(el_listy lista)

el_listy wsk=lista 1 while( wsk = NULL ) 2

printf (lun wsk-gtval) 3 wsk = wsk-gtnext 4 5

Zastanoacutewmy się teraz jak powinien wyglądać kod ktoacutery dodaje do listy następny element Takafunkcja powinna

znaleźć ostatni element (tj element ktoacuterego pole next == )

przydzielić odpowiedni obszar pamięci

skopiować w pole val w nowo przydzielonym obszarze znalezioną liczbę pierwszą

nadać polu next ostatniego elementu listy wartość

w pole next ostatniego elementu listy wpisać adres nowo przydzielonego obszaru

148 ROZDZIAŁ 19 TYPY ZŁOŻONE

Napiszmy zatem odpowiednią funkcję

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL) 1

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy)) 2 nowy-gtval = liczba 3 nowy-gtnext = NULL 4 wsk-gtnext = nowy 5

Ihellip to już właściwie koniec naszej funkcji (warto zwroacutecić uwagę że funkcja w tej wersji zakłada żena liście jest już przynajmniej jeden element) Wstaw ją do kodu przed funkcją main Został namjeszcze jeden problem w pętli for musimy dodać kod ktoacutery odpowiednio będzie ldquobadałrdquo liczby oraz wprzypadku stwierdzenia pierwszeństwa liczby będzie dodawał ją do listy Ten kod powinien wyglądaćmniej więcej tak

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczba wsk-gtval)==0) return 0 jeśli reszta z dzielenialiczby przez ktoacuterąkolwiek z poprzednio znalezionychliczb pierwszych jest roacutewna zero to znaczy że liczba tanie jest liczbą pierwszą

wsk = wsk-gtnext

natomiast jeśli sprawdzimy wszystkie poprzednio znalezione liczbyi żadna z nich nie będzie dzieliła liczby imożemy liczbę i dodać do listy liczb pierwszych

return 1for (ilt=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (firsti)

Podsumujmy teraz efekty naszej pracy Oto cały kod naszego programu

include ltstdiohgtinclude ltstdlibhgt

typedef struct element struct element nextunsigned long val

el_listy

el_listy first

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 149

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL)

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy))nowy-gtval = liczbanowy-gtnext = NULLwsk-gtnext = nowy podczepiamy nowy element do ostatniego z listy

void wypisz_liste(el_listy lista)

el_listy wsk=listawhile( wsk = NULL )

printf (lun wsk-gtval)wsk = wsk-gtnext

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczbawsk-gtval)==0) return 0wsk = wsk-gtnext

return 1

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (i=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (first i)

wypisz_liste(first)return 0

Możemy jeszcze pomyśleć jak można by wykonać usuwanie elementu z listy Najprościej byłobyzrobić

wsk-gtnext = wsk-gtnext-gtnext

150 ROZDZIAŁ 19 TYPY ZŁOŻONE

ale wtedy element na ktoacutery wskazywał wcześniej wsk-gtnext przestaje być dostępny i zaśmieca pa-mięć Trzeba go usunąć Zauważmy że aby usunąć element potrzebujemy wskaźnika do elementu gopoprzedzającego (po to by nie rozerwać listy) Popatrzmy na poniższą funkcję

void usun_z_listy(el_listy lista int element)

el_listy wsk=listawhile (wsk-gtnext = NULL)

if (wsk-gtnext-gtval == element) musimy mieć wskaźnik do elementu poprzedzającego el_listy usuwany=wsk-gtnext zapamiętujemy usuwany element wsk-gtnext = usuwany-gtnext przestawiamy wskaźnik next by omijał usuwany element free(usuwany) usuwamy z pamięci else

wsk = wsk-gtnext idziemy dalej tylko wtedy kiedy nie usuwaliśmy bo nie chcemy zostawić duplikatoacutew

Funkcja ta jest tak napisana by usuwała z listy wszystkie wystąpienia danego elementu (w naszymprogramie nie ma to miejsca ale lista jest zrobiona tak że może trzymać dowolne liczby) Zauważmyże wskaźnik wsk jest przesuwany tylko wtedy gdy nie kasowaliśmy Gdybyśmy zawsze go przesuwaliprzegapilibyśmy element gdyby występował kilka razy pod rząd

Funkcja ta działa poprawnie tylko wtedy gdy nie chcemy usuwać pierwszego elementu Można topoprawić mdash dodając instrukcję warunkową do funkcji lub dodając do listy ldquogłowęrdquo mdash pierwszy elementnie przechowujący niczego ale upraszczający operacje na liście Zostawiamy to do samodzielnej pracy

Cały powyższy przykład omawiał tylko jeden przypadek listy mdash listę jednokierunkową Jednakistnieją jeszcze inne typy list np lista jednokierunkowa cykliczna lista dwukierunkowa oraz dwukie-runkowa cykliczna Roacuteżnią się one od siebie tylko tym że

w przypadku list dwukierunkowych mdashw strukturze el listy znajduje się jeszcze pole ktoacutere wska-zuje na element poprzedni

w przypadku list cyklicznych mdash ostatni element wskazuje na pierwszy (nie rozroacuteżnia się wtedyelementu pierwszego ani ostatniego)

Rozdział 20

Biblioteki

201 Czym jest bibliotekaBiblioteka jest to zbioacuter funkcji ktoacutere zostały wydzielone po to aby dało się z nich korzystać wwielu pro-gramach Ułatwia to programowanie mdash nie musimy np sami tworzyć funkcji printf Każda bibliotekaposiada swoje pliki nagłoacutewkowe ktoacutere zawierają deklaracje funkcji bibliotecznych oraz często zawartesą w nich komentarze jak używać danej funkcji W tej części podręcznika nauczymy się tworzyć naszewłasne biblioteki

202 Jak zbudowana jest bibliotekaKażda biblioteka składa się z co najmniej dwoacutech części

pliku nagłoacutewkowego z deklaracjami funkcji (plik z rozszerzeniem h)

pliku źroacutedłowego zawierającego ciała funkcji (plik z rozszerzeniem c)

2021 Budowa pliku nagłoacutewkowegoOto najprostszy możliwy plik nagłoacutewkowy

ifndef PLIK_Hdefine PLIK_H tutaj są wpisane deklaracje funkcji endif PLIK_H

Zapewne zapytasz się na co komu instrukcje ifndef define oraz endif Otoacuteż często się zdarzaże w programie korzystamy z plikoacutew nagłoacutewkowych ktoacutere dołączają się wzajemnie Oznaczałoby to żew kodzie programu kilka razy pojawiła by się zawartość tego samego pliku nagłoacutewkowego Instrukcjaifndef i define temu zapobiega Dzięki temu kompilator nie musi kilkakrotnie kompilować tegosamego kodu

W plikach nagłoacutewkowych często umieszcza się też definicje typoacutew z ktoacuterych korzysta bibliotekaalbo np makr

2022 Budowa najprostszej bibliotekiZałoacuteżmy że nasza biblioteka będzie zawierała jedną funkcję ktoacuterawypisuje na ekran tekst ldquoplWikibooksrdquoUtwoacuterzmy zatem nasz plik nagłoacutewkowy

151

152 ROZDZIAŁ 20 BIBLIOTEKI

ifndef WIKI_Hdefine WIKI_Hvoid wiki (void)endif

Należy pamiętać o podaniu void w liście argumentoacutew funkcji nie przyjmujących argumentoacutew Oile przy definicji funkcji nie trzeba tego robić (jak to często czyniliśmy w przypadku funkcji main) otyle w prototypie brak słoacutewka void oznacza że w prototypie nie ma informacji na temat tego jakieargumenty funkcja przyjmuje

Plik nagłoacutewkowy zapisujemy jako ldquowikihrdquo Teraz napiszmy ciało tej funkcji

include wikihinclude ltstdiohgt

void wiki (void)

printf (plWikibooksn)

Ważne jest dołączenie na początku pliku nagłoacutewkowego Dlaczego Plik nagłoacutewkowy zawieradeklaracje naszych funkcji mdash jeśli popełniliśmy błąd i deklaracja nie zgadza się z definicją kompilatorod razu nas o tym powiadomi Oproacutecz tego plik nagłoacutewkowy może zawierać definicje istotnych typoacutewlub makr

Zapiszmy naszą bibliotekę jako plik ldquowikicrdquo Teraz należy ją skompilować Robi się to trochę ina-czej niż normalny program Należy po prostu do opcji kompilatora gcc dodać opcję ldquo-crdquo

gcc wikic -c -o wikio

Rozszerzenie ldquoordquo jest domyślnym rozszerzeniem dla bibliotek statycznych (typowych bibliotek łą-czonych z resztą programu na etapie kompilacji) Teraz możemy spokojnie skorzystać z naszej nowejbiblioteki Napiszmy nasz program

include wikih

int main ()

wiki()return 0

Zapiszmy program jako ldquomaincrdquo Teraz musimy odpowiednio skompilować nasz program

gcc mainc wikio -o main

Uruchamiamy nasz program

mainplWikibooks

Jak widać nasza pierwsza biblioteka działaZauważmy że kompilatorowi podajemy i pliki z kodem źroacutedłowym (mainc) i pliki ze skompilo-

wanymi bibliotekami (wikio) by uzyskać plik wykonywalny (main) Jeśli nie podalibyśmy plikoacutew zbibliotekami mainc co prawda skompilowałby się ale błąd zostałby zgłoszony przez linker mdash częśćkompilatora odpowiedzialna za wstawienie w miejsce wywołań funkcji ich adresoacutew (takiego adresulinker nie moacutegłby znaleźć)

202 JAK ZBUDOWANA JEST BIBLIOTEKA 153

2023 Zmiana dostępu do funkcji i zmienny (static i extern)Język C w przeciwieństwie do swego młodszego krewnego mdash C++ nie posiada praktycznie żadnychmechanizmoacutew ochrony kodu biblioteki przed modyfikacjami C++ ma w swoim asortymencie minsterowanie uprawnieniami roacuteżnych elementoacutew klasy Jednak programista piszący program w C niejest tak do końca bezradny Autorzy C dali mu do ręki dwa narzędzia extern oraz static Pierwsze ztych słoacutew kluczowych informuje kompilator że dana funkcja lub zmienna istnieje ale w innymmiejscui zostanie dołączona do kodu programu w czasie łączenia go z biblioteką

extern przydaje się gdy zmienna lub funkcja jest zadeklarowana w bibliotece ale nie jest udostęp-niona na zewnątrz (nie pojawia się w pliku nagłoacutewkowym) Przykładowo

bibliotekah extern char zmienna_dzielona[]

bibliotekac include bibliotekah

char zmienna_dzielona[] = Zawartosc

mainc include ltstdiohgtinclude bibliotekah

int main()

printf(sn zmienna_dzielona)return 0

Gdybyśmy tu nie zastosowali extern kompilator (nie linker) zaprotestowałby że nie zna zmiennejzmienna dzielona Proacuteba dopisania deklaracji char zmienna dzielona stworzyłaby nową zmienną iutracilibyśmy dostęp do interesującej nas zawartości

Odwrotne działanie ma słowo kluczowe static użyte w tym kontekście (użyte wewnątrz bloku two-rzy zmienną statyczną więcej informacji w rozdziale Zmienne) Może ono odnosić się zaroacutewno dozmiennych jak i do funkcji globalnych Powoduje że dana zmienna lub funkcja jest niedostępna nazewnątrz biblioteki1 Możemy dzięki temu ukryć np funkcje ktoacutere używane są przez samą bibliotekęby nie dało się ich wykorzystać przez extern

1Tak naprawdę całe ldquoukrycierdquo funkcji polega na zmianie niektoacuterych danych w pliku z kodem binarnym danejbiblioteki (pliku o) przez co linker powoduje wygenerowanie komunikatu o błędzie w czasie łączenia biblioteki zprogramem

154 ROZDZIAŁ 20 BIBLIOTEKI

Rozdział 21

Więcej o kompilowaniu

211 Ciekawe opcje kompilatora Emdash powoduje wygenerowanie kodu programu ze zmianami wprowadzonymi przez preprocesor

S mdash zamiana kodu w języku C na kod asemblera (komenda gcc -S plikc spowoduje utworzeniepliku o nazwie pliks w ktoacuterym znajdzie się kod asemblera)

c mdash kompilacja bez łączenia z bibliotekami

Ikatalog mdash ustawienie domyślnego katalogu z plikami nagłoacutewkowymi na katalog

lbiblioteka mdash wymusza łączenie programu z podaną biblioteką (np -lGL)

212 Program makeDość często może się zdarzyć że nasz program składa się z kilku plikoacutew źroacutedłowych Jeśli tych plikoacutewjest mało (np -) możemy jeszcze proacutebować ręcznie kompilować każdy z nich Jednak jeśli tych plikoacutewjest dużo lub chcemy pokazać nasz program innym użytkownikom musimy stworzyć elegancki sposoacutebkompilacji naszego programu Właśnie po to aby zautomatyzować proces kompilacji powstał programmake Program make analizuje pliki Makefile i na ich podstawie wykonuje określone czynności

2121 Budowa pliku MakefileUwaga poniżej został omoacutewiony Makefile dla Make Istnieją inne programy make i mogą używaćinnej składni Na Wikibooks został też obszernie opisany program make firmy Borland

Najważniejszym elementem pliku Makefile są zależności oraz reguły przetwarzania Zależnościpolegają na tym że np jeśli nasz program ma być zbudowany z plikoacutew to najpierw należy skom-pilować każdy z tych plikoacutew a dopiero poacuteźniej połączyć je w jeden cały program Zatem zależnościokreślają kolejność wykonywanych czynności Natomiast reguły określają jak skompilować dany plikZależności tworzy się tak

co od_czegoreguły

Dzięki temu program make zna już kolejność wykonywanych działań oraz czynności jakie ma wy-konać Aby zbudować ldquocordquo należy wykonać polecenie make co Pierwsza reguła w pliku Makefile jestregułą domyślną Jeśli wydamy polecenie make bez parametroacutew zostanie zbudowana właśnie reguładomyślna Tak więc dobrze jest jako pierwszą regułę wstawić regułę budującą końcowy plik wykony-walny zwyczajowo regułę tą nazywa się all

155

156 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Należy pamiętać by sekcji ldquocordquo niewcinać natomiast ldquoregułyrdquowcinać tabulatorem Część ldquood czegordquomoże być pusta

Plik Makefile umożliwia też definiowanie pewnych zmiennych Nie trzeba tutaj się już troszczyć otyp zmiennej wystarczy napisać

nazwa_zmiennej = wartość

Wten sposoacutebmożemy zadeklarować dowolnie dużo zmiennych Zmiennemogą być roacuteżnemdash nazwakompilatora jego parametry iwiele innych Zmiennej używamywnastępujący sposoacuteb $(nazwa zmiennej)

Komentarze w pliku Makefile tworzymy zaczynając linię od znaku hash ()

2122 Przykładowy plik MakefileDość tej teorii teraz zajmiemy się działającym przykładem Załoacuteżmy że nasz przykładowy programnazywa się test oraz składa się z czterech plikoacutew pierwszyc drugic trzecic czwartyc

Odpowiedni plik Makefile powinien wyglądać mniej więcej tak

Moacutej plik makefile - wpisz make all aby skompilować cały program (właściwie wystarczy wpisać make - all jest domyślny jako pierwszy cel)CC = gcc

all pierwszyo drugio trzecio czwartyo$(CC) pierwszyo drugio trzecio czwartyo -o test

pierwszyo pierwszyc$(CC) pierwszyc -c -o pierwszyo

drugio drugic$(CC) drugic -c -o drugio

trzecio trzecic$(CC) trzecic -c -o trzecio

czwartyo czwartyc$(CC) czwartyc -c -o czwartyo

Widzimy że nasz program zależy od plikoacutew z rozszerzeniem o (pierwszyo itd) potem każdy ztych plikoacutew zależy od plikoacutew c ktoacutere program make skompiluje w pierwszej kolejności a następniepołączy w jeden program (test) Nazwę kompilatora zapisaliśmy jako zmienną ponieważ powtarza sięi zmienna jest sposobem by zmienić ją wszędzie za jednym zamachem

Zatem jak widać używanie pliku Makefile jest bardzo proste Warto na koniec naszego przykładudodać regułę ktoacutera wyczyści katalog z plikoacutew o

cleanrm -f o test

Ta reguła spowoduje usunięcie wszystkich plikoacutew o oraz naszego programu jeśli napiszemy makeclean

Możemy też ukryć wykonywane komendy albo dopisać własny opis czynności

cleanecho Usuwam gotowe plikirm -f o test

Ten sam plik Makefile moacutegłby wyglądać inaczej

213 OPTYMALIZACJE 157

CFLAGS = -g -O tutaj można dodawać inne flagi kompilatoraLIBS = -lm tutaj można dodawać biblioteki

OBJ =pierwszyo drugio trzecio czwartyo

all main

cleanrm -f o test

co$(CC) -c $(INCLUDES) $(CFLAGS) $lt

main $(OBJ)$(CC) $(OBJ) $(LIBS) -o test

Tak naprawdę jest to dopiero bardzo podstawowe wprowadzenie do używania programu makejednak jest ono wystarczające byś zaczął z niego korzystać Wyczerpujące omoacutewienie całego programuniestety przekracza zakres tego podręcznika

213 OptymalizacjeKompilator umożliwia generację kodu zoptymalizowanego dla konkretnej architektury Służą dotego opcje -mar= i -mtune= Stopień optymalizacji ustalamy za pomocą opcji -Ox gdzie x jest nume-rem stopnia optymalizacji (od do ) Możliwe jest też użycie opcji -Os ktoacutera powoduje generowaniekodu o jak najmniejszym rozmiarze Aby skompilować dany plik z optymalizacjami dla procesora Ath-lon należy napisać tak

gcc programc -o program -march=athlon-xp -O3

Z optymalizacjami należy uważać gdyż często zdarza się że kod skompilowany bez optymalizacjidziała zupełnie inaczej niż ten ktoacutery został skompilowany z optymalizacjami

2131 WyroacutewnywanieWyroacutewnywanie jest pewnym zjawiskiem na ktoacutere w bardzo wielu podręcznikach moacutewiących o Cw ogoacutele się nie wspomina Ten rozdział ma za zadanie wyjaśnienie tego zjawiska oraz uprzedzenieprogramisty o pewnych faktach ktoacutere w poacuteźniejszej jego ldquotwoacuterczościrdquo mogą zminimalizować czas naznalezienie pewnych informacji ktoacutere mogą wpływać na to że jego program nie będzie działał popraw-nie

Często zdarza się że kompilator w ramach optymalizacji ldquowyroacutewnujerdquo elementy struktury tak abyprocesor moacutegł łatwiej odczytać i przetworzyć dane Przyjrzyjmy się bliżej następującemu fragmentowikodu

typedef struct unsigned char wiek 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

158 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Aby procesor moacutegł łatwiej przetworzyć dane kompilator może dodać do tej struktury jedno ośmio-bitowe pole Wtedy struktura będzie wyglądała tak

typedef struct unsigned char wiek 8 bitoacutew unsigned char fill[1] 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

Wtedy rozmiar zmiennych przechowujących wiek płeć oraz dochoacuted będzie wynosił bity mdashbędzie zatem potęgą liczby dwa i procesorowi dużo łatwiej będzie tak ułożoną strukturę przechowywaćw pamięci cache Jednak taka sytuacja nie zawsze jest pożądana Może się okazać że nasza strukturamusi odzwierciedlać np pojedynczy pakiet danych przesyłanych przez sieć Nie może być w niejzatem żadnych innych poacutel poza tymi ktoacutere są istotne do transmisji Aby wymusić na kompilatorzewyroacutewnanie -bajtowe (co w praktyce wyłącza je) należy przed definicją struktury dodać dwie linijkiTen kod działa pod Visual C++

pragma pack(push)pragma pack(1)

struct struktura

pragma pack(pop)

W kompilatorze należy po deklaracji struktury dodajemy przed średnikiem kończącym jednąlinijkę

__attribute__ ((packed))

Działa ona dokładnie tak samo jak makra pragma jednak jest ona obecna tylko w kompilatorze

Dzięki użyciu tego atrybutu kompilator zostanie ldquozmuszonyrdquo do braku ingerencji w naszą strukturęJest jednak jeszcze jeden być może bardziej elegancki sposoacuteb na obejście dopełniania Zauważyłeś żedopełnienie dodane przez kompilator pojawiło się między polem o długości bitoacutew (plec) oraz polem odługości bitoacutew (dochod) Wyroacutewnywanie polega na tym że dana zmienna powinna być umieszczonapod adresem będącym wielokrotnością jej rozmiaru Oznacza to że jeśli np mamy w strukturze napoczątku dwie zmienne o rozmiarze jednego bajta a potem jedną zmienną o rozmiarze bajtoacutew topomiędzy polami o rozmiarze bajtoacutew a polem czterobajtowym pojawi się dwubajtowe dopełnienieMoże Ci się wydawać że jest to tylko niepotrzebne mącenie w głowie jednak niektoacutere architektury(zwłaszcza typu ) mogą nie wykonać kodu ktoacutery nie został wyroacutewnany Dlatego naszą strukturępowinniśmy zapisać mniej więcej tak

typedef struct unsigned short dochod 16 bitoacutew unsigned char wiek 8 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

W ten sposoacuteb wyroacutewnana struktura nie będzie podlegała modyfikacjom przez kompilator oraz bę-dzie przenośna pomiędzy roacuteżnymi kompilatorami

Wyroacutewnywanie działa także na pojedynczych zmiennych w programie jednak ten problem nie po-woduje tyle zamieszania co ingerencja kompilatora w układ poacutel struktury Wyroacutewnywanie zmiennychpolega tylko na tym że kompilator umieszcza je pod adresami ktoacutere są wielokrotnością ich rozmiaru

214 KOMPILACJA KRZYŻOWA 159

214 Kompilacja krzyżowaMając w domu dwa komputery o odmiennych architekturach (np i oraz Sparc) możemy potrze-bować stworzyć program dla jednej maszyny mając do dyspozycji tylko drugi komputer Nie musimywtedy latać do znajomego posiadającego odpowiedni sprzęt Możemy skorzystać z tzw kompilacjikrzyżowej (ang cross-compile) Polega ona na tym że program nie jest kompilowany pod procesorna ktoacuterym działa kompilator lecz na inną zdefiniowaną wcześniej maszynę Efekt będzie taki sam askompilowany program możemy bez problemu uruchomić na drugim komputerze

215 Inne narzędziaWśroacuted przydatnych narzędzi warto wymienić roacutewnież program objdump (zaroacutewno pod Unix jak ipod Windows) oraz readelf (tylko Unix) Objdump służy do deasemblacji i analizy skompilowanychprogramoacutew Readelf służy do analizy pliku wykonywalnego w formacie (używanego w większościsystemoacutew z rodziny Unix) Więcej informacji możesz uzyskać pisząc (w systemach Unix)

man 1 objdumpman 1 readelf

160 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Rozdział 22

Zaawansowane operacjematematyczne

221 Biblioteka matematycznaAby moacutec korzystać z wszystkich dobrodziejstw funkcji matematycznych musimy na początku dołączyćplik mathh

include ltmathhgt

A w procesie kompilacji (dotyczy kompilatora GCC) musimy niekiedy dodać flagę ldquo-lmrdquo

gcc plikc -o plik -lm

Funkcje matematyczne ktoacutere znajdują się w bibliotece standardowej możesz znaleźć tutaj Przykorzystaniu z nich musisz wziąć pod uwagę min to że biblioteka matematyczna prowadzi kalkulacjęw oparciu o radiany a nie stopnie

2211 Stałe matematyczneW pliku mathh zdefiniowane są pewne stałe ktoacutere mogą być przydatne do obliczeń Są to min

M E mdash podstawa logarytmu naturalnego (e liczba Eulera)

M LOG2E mdash logarytm o podstawie z liczby e

M LOG10E mdash logarytm o podstawie z liczby e

M LN2 mdash logarytm naturalny z liczby

M LN10 mdash logarytm naturalny z liczby

M PI mdash liczba π

M PI 2 mdash liczba π

M PI 4 mdash liczba π

M 1 PI mdash liczba π

M 2 PI mdash liczba π

161

162 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

222 Prezentacja liczb rzeczywisty w pamięci komputeraByć może ten temat może wydać Ci się niepotrzebnym lecz w wielu książkach nie ma w ogoacutele tegotematu Dzięki niemu zrozumiesz jak komputer radzi sobie z przecinkiem oraz dlaczego niektoacutere ob-liczenia dają niezbyt dokładne wyniki Na początek trochę teorii do przechowywania liczb rzeczywi-stych przeznaczone są typy float double oraz long double Zajmują one odpowiednio oraz bitoacutew Wiemy też że komputer nie ma fizycznej możliwości zapisania przecinka Sproacutebujmy terazzapisać jakąś liczbę wymierną w formie liczb binarnych Nasza liczba to powiedzmy 425 Sproacutebujmy jąrozbić na sumę potęg dwoacutejki 4 = 1 middot22 +0 middot21 +0 middot20 Dobra mdash rozpisaliśmy liczbę 4 ale co z częściądziesiętną Skorzystajmy z zasad matematyki 025 = 2minus2 Zatem nasza liczba powinna wyglądaćtak

10001Ponieważ komputer nie jest w stanie przechować pozycji przecinka ktoś wpadł na prosty ale

sprytny pomysł ustawienia przecinka jak najbliżej początku liczby i tylko mnożenia jej przez odpowied-nią potęgę dwoacutejki Taki sposoacuteb przechowywania liczb nazywamy zmiennoprzecinkowym a procesprzekształcania naszej liczby z postaci czytelnej przez człowieka na format zmiennoprzecinkowy na-zywamy normalizacją Wroacutećmy do naszej liczby mdash 425 W postaci binarnej wygląda ona tak 10001natomiast po normalizacji będzie wyglądała tak 10001 middot 22 W ten sposoacuteb w pamięci komputeraznajdą się dwie informacje liczba zakodowana w pamięci z ldquowirtualnymrdquo przecinkiem oraz numerpotęgi dwoacutejki Te dwie informacje wystarczają do przechowania wartości liczby Jednak pojawia sięinny problem mdash co się stanie jeśli np będziemy chcieli przełożyć liczbę typu 1

3 Otoacuteż tutaj wychodzą

na wierzch pewne niedociągnięcia komputera w dziedzinie samej matematyki daje w rozwinięciudziesiętnym 0(3) Jak zatem zapisać taką liczbę Otoacuteż nie możemy przechować całego jej rozwinięcia(wynika to z ograniczeń typu danych mdash ma on niestety skończoną liczbę bitoacutew) Dlatego przechowujesię tylko pewne przybliżenie liczby Jest ono tym bardziej dokładne im dany typ ma więcej bitoacutew Za-tem do obliczeń wymagających dokładnych danych powinniśmy użyć typu double lub long double Naszczęście w większości przeciętnych programoacutew tego typu problemy zwykle nie występują A ponie-waż początkujący programista nie odpowiada za tworzenie programoacutew sterujących np lotem statkukosmicznego więc drobne przekłamania na odległych miejscach po przecinku nie stanowią większegoproblemu

Należy brać pod uwagę że w komputerze liczby rzeczywiste nie są tym samym czym w mate-matyce Komputery nie potrafią przechować każdej liczby zmiennoprzecinkowej w związku z tymobliczenia prowadzone przy użyciu komputera mogą być niedokładne i odbiegać od prawidłowych wy-nikoacutew Szczegoacutelnie ważne jest to przy programowaniu aplikacji inżynieryjnych oraz w medycyniegdzie takie błędy mogą skutkować katastrofą ilub narażeniem ludzkiego życia oraz zdrowia

Na ile poważny jest to problem Sproacutebujmy przyjrzeć się działaniu polegającym na -krotnymdodawaniu do liczby wartości Oto kod

include ltstdiohgt

int main ()

float a = 0int i = 0for (ilt1000i++)

a += 1030printf (fn a)

Z matematyki wynika że 1000 middot 13

= 333(3) podczas gdy komputer wypisze wynik nieco roacuteżniącysię od oczekiwanego (w moim przypadku)

223 LICZBY ZESPOLONE 163

333334106

Błąd pojawił się na cyfrze części tysięcznej liczby Nie jest to może poważny błąd jednak zastanoacutewmysię czy ten błąd nie będzie się powiększał Zamieniamy w kodzie ilość iteracji z na Tymrazem moacutej komputer wskazał już nieco inny wynik

33356554688

Błąd przesunął się na cyfrę dziesiątek w liczbie Tak więc nie należy do końca polegać na prezentacjiliczb zmiennoprzecinkowych w komputerze

223 Liczby zespoloneOperacje na liczba zespolony są częścią uaktualnionego standardu języka C o nazwie C ktoacuteryjest obsługiwany jedynie przez część kompilatoroacutew

Podane tutaj informacje zostały sprawdzone na systemie Gentoo Linux z biblioteką GNU libc wwersji i kompilatorem GCC w wersji

Dotychczas korzystaliśmy tylko z liczb rzeczywistych lecz najnowsze standardy języka C umożli-wiają korzystanie także z innych liczb mdash np z liczb zespolonych

Abymoacutec korzystać z liczb zespolonychwnaszymprogramie należywnagłoacutewku programu umieścićnastępującą linijkę

include ltcomplexhgt

Wiemy że liczba zespolona zdeklarowana jest następująco

z = a+bi gdzie a b są liczbami rzeczywistymi a ii=(-1)

W pliku complexh liczba i zdefiniowana jest jako I Zatem wyproacutebujmy możliwości liczb zespolo-nych

include ltmathhgtinclude ltcomplexhgtinclude ltstdiohgt

int main ()

float _Complex z = 4+25Iprintf (Liczba z f+fin creal(z) cimag (z))return 0

następnie kompilujemy nasz program

gcc plik1c -o plik1 -lm

Po wykonaniu naszego programu powinniśmy otrzymać

Liczba z 400+250i

W programie zamieszczonym powyżej użyliśmy dwoacutech funkcji mdash creal i cimag

creal mdash zwraca część rzeczywistą liczby zespolonej

cimag mdash zwraca część urojoną liczby zespolonej

164 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

Rozdział 23

Powszene praktyki

Rozdział tenma za zadanie pokazać powszechnie stosowanemetody programowania wC Nie będziemytu uczyć jak należy stawiać nawiasy klamrowe ani ktoacutery sposoacuteb nazewnictwa zmiennych jest najlep-szy mdash prowadzone są o to spory z ktoacuterych niewiele wynika Zaprezentowane tu rozwiązania mająkonkretny wpływ na jakość tworzonych programoacutew

231 Konstruktory i destruktoryW większości obiektowych językoacutew programowania obiekty nie mogą być tworzone bezpośrednio mdashobiekty otrzymuje się wywołując specjalną metodę danej klasy zwaną konstruktorem Konstruktorysą ważne ponieważ pozwalają zapewnić obiektowi odpowiedni stan początkowy Destruktory wywo-ływane na końcu czasu życia obiektu są istotne gdy obiekt ma wyłączny dostęp do pewnych zasoboacutewi konieczne jest upewnienie się czy te zasoby zostaną zwolnione

Ponieważ C nie jest językiem obiektowym nie ma wbudowanego wsparcia dla konstruktoroacutew idestruktoroacutew Często programiści bezpośrednio modyfikują tworzone obiekty i struktury Jednakżeprowadzi to do potencjalnych błędoacutew ponieważ operacje na obiekcie mogą się nie powieść lub zacho-wać się nieprzewidywalnie jeśli obiekt nie został prawidłowo zainicjalizowany Lepszym podejściemjest stworzenie funkcji ktoacutera tworzy instancję obiektu ewentualnie przyjmując pewne parametry

struct string size_t sizechar data

struct string create_string(const char initial) assert (initial = NULL)struct string new_string = malloc(sizeof(new_string))if (new_string = NULL)

new_string-gtsize = strlen(initial)new_string-gtdata = strdup(initial)

return new_string

Podobnie bezpośrednie usuwanie obiektoacutew może nie do końca się udać prowadząc do wyciekuzasoboacutew Lepiej jest użyć destruktora

void free_string(struct string s)

165

166 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

assert (s = NULL)free(s-gtdata) zwalniamy pamięć zajmowaną przez strukturę free(s) usuwamy samą strukturę

Często łączy się destruktory z zerowaniem zwolnionych wskaźnikoacutewCzasami dobrze jest ukryć definicję obiektu żeby mieć pewność że użytkownicy nie utworzą go

ręcznie Aby to zapewnić struktura jest definiowanaw pliku źroacutedłowym (lub prywatnymnagłoacutewku nie-dostępnym dla użytkownikoacutew) zamiast w pliku nagłoacutewkowym a deklaracja wyprzedzająca jest umiesz-czona w pliku nagłoacutewkowym

struct stringstruct string create_string(const char initial)void free_string(struct string s)

232 Zerowanie zwolniony wskaźnikoacutewJak powiedziano już wcześniej po wywołaniu free() dla wskaźnika staje się on ldquowiszącym wskaź-nikiemrdquo Co gorsze większość nowoczesnych platform nie potrafi wykryć kiedy taki wskaźnik jestużywany zanim zostanie ponownie przypisany

Jednym z prostych rozwiązań tego problemu jest zapewnienie że każdy wskaźnik jest zerowanynatychmiast po zwolnieniu

free(p)p = NULL

Inaczej niż w przypadku ldquowiszących wskaźnikoacutewrdquo na wielu nowoczesnych architekturach przyproacutebie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek Dodatkowo programy mogązawierać sprawdzanie błędoacutew dla zerowych wartości ale nie dla ldquowiszących wskaźnikoacutewrdquo Aby zapew-nić że jest to wykonywane dla każdego wskaźnika możemy użyć makra

define FREE(p) do free(p) (p) = NULL while(0)

(aby zobaczyć dlaczego makro jest napisane w ten sposoacuteb zobacz Konwencje pisania makr)Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik ktoacutery przekazuje się do

nich więc argument musi być do nich przekazywany przez referencję Na przykład oto zaktualizowanydestruktor z sekcji Konstruktory i destruktory

void free_string(struct string s)

assert(s = NULL ampamp s = NULL)FREE((s)-gtdata) zwalniamy pamięć zajmowaną przez strukturę FREE(s) usuwamy strukturę

Niestety ten idiom nie jest wstanie pomoacutec wwypadkuwskazywania przez inne wskaźniki zwolnio-nej pamięci Z tego powodu niektoacuterzy eksperci C uważają go za niebezpieczny jako kreujący fałszywepoczucie bezpieczeństwa

233 Konwencje pisania makrPonieważ makra preprocesora działają na zasadzie zwykłego zastępowania napisoacutew są podatne nawiele kłopotliwych błędoacutew z ktoacuterych części można uniknąć przez stosowanie się do poniższych reguł

234 JAK DOSTAĆ SIĘ DO KONKRETNEGO BITU 167

Umieszczaj nawiasy dookoła argumentoacutew makra kiedy to tylko możliwe Zapewnia to że gdysą wyrażeniami kolejność działań nie zostanie zmieniona Na przykład

Źle define kwadrat(x) (xx)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Załoacuteżmy że w programie makro kwadrat() zdefiniowane bez nawiasoacutew zostałowywołane następująco kwadrat(a+b) Wtedy zostanie ono zamienione przez preprocesorna (a+ba+b) Z kolejności działań wiemy że najpierw zostanie wykonane mnożeniewięc wartość wyrażenia kwadrat(a+b) będzie roacuteżna od kwadratu wyrażenia a+b

Umieszczaj nawiasy dookoła całegomakra jeśli jest pojedynczymwyrażeniem Ponownie chronito przed zaburzeniem kolejności działań

Źle define kwadrat(x) (x)(x)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Definiujemy makro define suma(a b) (a)+(b) i wywołujemy je w kodziewynik = suma(3 4) 5 Makro zostanie rozwinięte jako wynik = (3)+(4)5 co mdash zpowodu kolejności działań mdash da wynik inny niż pożądany

Jeśli makro składa się z wielu instrukcji lub deklaruje zmienne powinno być umieszczone w pętlido while(0) bez kończącego średnika Pozwala to na użycie makra jak pojedynczejinstrukcji w każdym miejscu jak ciało innego wyrażenia pozwalając jednocześnie na umiesz-czenie średnika po makrze bez tworzenia zerowego wyrażenia Należy uważać by zmienne wmakrze potencjalnie nie kolidowały z argumentami makra

Źle define FREE(p) free(p) p = NULL

Dobrze define FREE(p) do free(p) p = NULL while(0)

Unikaj używania argumentoacutew makra więcej niż raz wewnątrz makra Może to spowodowaćkłopoty gdy argument makra ma efekty uboczne (np zawiera operator inkrementacji)

Przykład define kwadrat(x) ((x)(x)) nie powinno być wywoływane z operatoreminkrementacji kwadrat(a++) ponieważ zostanie to rozwinięte jako ((a++) (a++)) co jestniezgodne ze specyfikacją języka i zachowanie takiego wyrażenia jest niezdefiniowane(dwukrotna inkrementacja w tym samym wyrażeniu)

Jeśli makro może być w przyszłości zastąpione przez funkcję rozważ użycie w nazwie małychliter jak w funkcji

234 Jak dostać się do konkretnego bituWiemy że komputer to maszyna ktoacuterej najmniejszą jednostką pamięci jest bit jednak w C najmniejszazmienna ma rozmiar bitoacutew (czyli jednego bajtu) Jak zatem można odczytać wartość pojedynczychbitoacutew W bardzo prosty sposoacuteb mdashw zestawie operatoroacutew języka C znajdują się tzw operatory bitoweSą to m in

amp mdash logiczne ldquoirdquo

| mdash logiczne ldquolubrdquo

˜ mdash logiczne ldquonierdquo

Oproacutecz tego są także przesunięcia (ltlt oraz gtgt) Zastanoacutewmy się teraz jak je wykorzystać w prak-tyce Załoacuteżmy że zajmujemy się jednobajtową zmienną

unsigned char i = 2

Zmatematyki wiemy że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej) Jeśli teraz np chcielibyśmy ldquozapalićrdquo drugi bit od lewej (tj bit ktoacuterego zapalenie niejako ldquododardquo doliczby wartość 6) powinniśmy użyć logicznego lub

168 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

unsigned char i = 2i |= 64

Gdzie =6 Odczytywanie wykonuje się za pomocą tzw maski bitowej Polega to na

wyzerowaniu bitoacutew ktoacutere są nam w danej chwili niepotrzebne

odpowiedniemu przesunięciu bitoacutew dzięki czemu szukany bit znajdzie się na pozycji pierwszegobitu od prawej

Do ldquowyłuskaniardquo odpowiedniego bitu możemy posłużyć się operacją ldquoirdquo mdash czyli operatorem ampWygląda to analogicznie do posługiwania się operatorem ldquolubrdquo

unsigned char i = 3 bitowo 00000011 unsigned char temp = 0temp = i amp 1 sprawdzamy najmniej znaczący bit - czyli pierwszy z prawej if (temp)

printf (bit zapalony)else

printf (bit zgaszony)

Jeśli nie władasz biegle kodem binarnym tworzenie masek bitowych ułatwią ci przesunięcia bitoweAby uzyskać liczbę ktoacutera ma zapalony bit o numerze n (bity są liczone od zera) przesuwamy bitowo wlewo jedynkę o n pozycji

1 ltlt n

Jeśli chcemy uzyskać liczbę w ktoacuterej zapalone są bity na pozycjach l m n mdash używamy sumylogicznej (ldquolubrdquo)

(1 ltlt l) | (1 ltlt m) | (1 ltlt n)

Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone sąwszystkie bity poza n odwracamy ją za pomocąoperatora logicznej negacji

~(1 ltlt n)

Warto władać biegle operacjami na bitach ale początkujący mogą (po uprzednim przeanalizowa-niu) zdefiniować następujące makra i ich używać

Sprawdzenie czy w liczbie k jest zapalony bit n define IS_BIT_SET(k n) ((k) amp (1 ltlt (n)))

Zapalenie bitu n w zmiennej k define SET_BIT(k n) (k |= (1 ltlt (n)))

Zgaszenie bitu n w zmiennej k define RESET_BIT(k n) (k amp= ~(1 ltlt (n)))

235 Skroacutety notacjiIstnieją pewne sposoby ograniczenia ilości niepotrzebnego kodu Przykładem może być wykonywaniejednej operacji w razie wystąpienia jakiegoś warunku np zamiast pisać

if (warunek) printf (Warunek prawdziwyn)

235 SKROacuteTY NOTACJI 169

możesz skroacutecić notację do

if (warunek)printf (Warunek prawdziwyn)

Podobnie jest w przypadku pętli for

for (warunek)printf (Wyświetlam się w pętlin)

Niestety ograniczeniemw tymwypadku jest to że można w ten sposoacuteb zapisać tylko jedną instruk-cję

170 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

Rozdział 24

Przenośność programoacutew

Jak dowiedziałeś się z poprzednich rozdziałoacutew tego podręcznika język C umożliwia tworzenie progra-moacutew ktoacutere mogą być uruchamiane na roacuteżnych platformach sprzętowych pod warunkiem ich powtoacuter-nej kompilacji Język C należy do grupy językoacutew wysokiego poziomu ktoacutere tłumaczone są do poziomukodumaszynowego (tzn kod źroacutedłowy jest kompilowany) Z jednej strony jest to korzystne posunięciegdyż programy są szybsze i mniejsze niż programy napisane w językach interpretowanych (takich wktoacuterych kod źroacutedłowy nie jest kompilowany do kodu maszynowego tylko na bieżąco interpretowanyprzez tzw interpreter) Jednak istnieje także druga strona medalu mdash pewne zawiłości sprzętu ktoacutereograniczają przenośność programoacutew Ten rozdział ma wyjaśnić Ci mechanizmy działania sprzętu wtaki sposoacuteb abyś bez problemu moacutegł tworzyć poprawne i całkowicie przenośne programy

241 Niezdefiniowane zaowanie i zaowanie zależne odimplementacji

Wtrakcie czytania kolejnych rozdziałoacutewmożna było się natknąć na zwroty takie jak zachowanie niezde-finiowane (ang undefined behaviour) czy zachowanie zależne od implementacji (ang implementation-defined behaviour) Coacuteż one tak właściwie oznaczają

Zacznijmy od tego drugiego Autorzy standardu języka C czuli że wymuszanie jakiegoś konkret-nego działania danego wyrażenia byłoby zbytnim obciążeniem dla osoacuteb piszących kompilatory gdyżdany wymoacuteg moacutegłby być bardzo trudny do zrealizowania na konkretnej architekturze Dla przykładugdyby standard wymagał że typ unsigned char ma dokładnie bitoacutew to napisanie kompilatora dla ar-chitektury na ktoacuterej bajt ma bitoacutew byłoby cokolwiek kłopotliwe a z pewnością wynikowy programdziałałby o wiele wolniej niżby to było możliwe

Z tego właśnie powodu niektoacutere aspekty języka nie są określone bezpośrednio w standardzie i sąpozostawione do decyzji zespołu (osoby) piszącego konkretną implementację W ten sposoacuteb nie mażadnych przeciwwskazań (ze strony standardu) aby na architekturze gdzie bajty mają bitoacutew typchar roacutewnież miał tyle bitoacutew Dokonany wyboacuter musi być jednak opisany w dokumentacji kompilatoratak żeby osoba pisząca program w C mogła sprawdzić jak dana konstrukcja zadziała

Należy zatem pamiętać że poleganie na jakimś konkretnym działaniu programu w przypadkachzachowania zależnego od implementacji drastycznie zmniejsza przenośność kodu źroacutedłowego

Zachowania niezdefiniowane są o wiele groźniejsze gdyż zaistnienie takowego może spowodo-wać dowolny efekt ktoacutery nie musi być nigdzie udokumentowany Przykładem może tutaj być proacutebaodwołania się do wartości wskazywanej przez wskaźnik o wartości

Jeżeli gdzieś w naszym programie zaistnieje sytuacja niezdefiniowanego zachowania to nie jest jużto kwestia przenośności kodu ale po prostu błędu w kodzie chyba że świadomie korzystamy z roz-szerzenia naszego kompilatora Rozważmy odwoływanie się do wartości wskazywanej przez wskaźniko wartości Ponieważ według standardu operacja taka ma niezdefiniowany skutek to w szcze-goacutelności może wywołać jakąś z goacutery określoną funkcję mdash kompilator może coś takiego zrealizować

171

172 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

sprawdzając wartość wskaźnika przed każdą dereferencją w ten sposoacuteb niezdefiniowane zachowaniedla konkretnego kompilatora stanie się jak najbardziej zdefiniowane

Sytuacją wziętą z życia są operatory przesunięć bitowych gdy działają na liczbach ze znakiemKonkretnie przesuwanie w lewo liczb jest dla wielu przypadkoacutew niezdefiniowane Bardzo często jed-nak w dokumentacji kompilatora działanie przesunięć bitowych jest dokładnie opisane Jest to o tyleinteresujący fakt iż wielu programistoacutew nie zdaje sobie z niego sprawy i nieświadomie korzysta z roz-szerzeń kompilatora

Istnieje jeszcze trzecia klasa zachowań Zachowania nieokreślone (ang unspecified behaviour)Są to sytuacje gdy standard określa kilka możliwych sposoboacutew w jaki dane wyrażenie może działaći pozostawia kompilatorowi decyzję co z tym dalej zrobić Coś takiego nie musi być nigdzie opisanew dokumentacji i znowu poleganie na konkretnym zachowaniu jest błędem Klasycznym przykłademmoże być kolejność obliczania argumentoacutew wywołania funkcji

242 Rozmiar zmiennyRozmiar poszczegoacutelnych typoacutew danych (np char int czy long) jest roacuteżna na roacuteżnych platformachgdyż nie jest definiowany w sztywny sposoacuteb jak np ldquolong int zawsze powinien mieć bityrdquo (takieokreślenie wiązałoby się z wyżej opisanymi trudnościami) lecz w na zasadzie zależności typu ldquolongpowinien być nie kroacutetszy niż intrdquo ldquoshort nie powinien być dłuższy od intrdquo Pierwsza standaryzacjajęzyka C zakładała że typ int będzie miał taki rozmiar jak domyślna długość liczb całkowitych nadanym komputerze natomiast modyfikatory short oraz long zmieniały długość tego typu tylko wtedygdy dana maszyna obsługiwała typy o mniejszej lub większej długości1

Z tego powodu nigdy nie zakładaj że dany typ będzie miał określony rozmiar Jeżeli potrzebujesztypu o konkretnym rozmiarze (a dokładnej konkretnej liczbie bitoacutew wartości) możesz skorzystać z plikunagłoacutewkowego stdinth wprowadzonego do języka przez standard ISO C z roku Definiuje on typyint t int t int t int t uint t uint t uint t i uint t (o ile w danej architekturze występujątypy o konkretnej liczbie bitoacutew)

Jednak możemy posiadać implementację ktoacutera nie posiada tego pliku nagłoacutewkowego W takiej sy-tuacji nie pozostaje nam nic innego jak tworzyć własny plik nagłoacutewkowy w ktoacuterym za pomocą słoacutewkatypedef sami zdefiniujemy potrzebne nam typy Np

typedef unsigned char u8typedef signed char s8typedef unsigned short u16typedef signed short s16typedef unsigned long u32typedef signed long s32typedef unsigned long long u64typedef signed long long s64

Aczkolwiek należy pamiętać że taki plik będzie trzeba pisać od nowa dla każdej architektury najakiej chcemy kompilować nasz program

243 Porządek bajtoacutew i bitoacutew

2431 Bajty i słowaWiesz zapewne że podstawową jednostką danych jest bit ktoacutery może mieć wartość lub Kilkakolejnych bitoacutew2 stanowi bajt (dla skupienia uwagi przyjmijmy że bajt składa się z bitoacutew) Częstotyp short ma wielkość dwoacutech bajtoacutew i woacutewczas pojawia się pytanie w jaki sposoacuteb są one zapisane

1Dokładniejszy opis rozmiaroacutew dostępny jest w rozdziale Składnia2Standard wymaga aby było ich co najmniej 8 i liczba bitoacutew w bajcie w konkretnej implementacji jest określona

przez makro CHAR BIT zdefiniowane w pliku nagłoacutewkowym limitsh

243 PORZĄDEK BAJTOacuteW I BITOacuteW 173

w pamięci mdash czy najpierw ten bardziej znaczący mdash big-endian czy najpierw ten mniej znaczący mdashlittle-endian

Skąd takie nazwy Otoacuteż pochodzą one z książki Podroacuteże Guliwera w ktoacuterej liliputy kłoacuteciły się ostronę od ktoacuterej należy rozbijać jajko na twardo Jedni uważali że trzeba je rozbijać od grubszegokońca (big-endian) a drudzy że od cieńszego (lile-endian) Nazwy te są o tyle trafne że w wypadkuprocesoroacutew wyboacuter kolejności bajtoacutew jest sprawą czysto polityczną ktoacutera jest technicznie neutralna

Sprawa się jeszcze bardziej komplikuje w przypadku typoacutew ktoacutere składają się np z bajtoacutew Woacutew-czas są aż ( silnia) sposoby zapisania kolejnych fragmentoacutew takiego typu W praktyce zapewne spo-tkasz się jedynie z kolejnościami big-endian lub lile-endian co nie zmienia faktu że inne możliwościtakże istnieją i przy pisaniu programoacutew ktoacutere mają być przenośne należy to brać pod uwagę

Poniższy przykład dobrze obrazuje oba sposoby przechowywania zawartości zmiennych w pamięcikomputera (przyjmujemy CHAR BIT == oraz sizeof(long) == bez bitoacutew wypełnienia (ang paddingbits)) unsigned long zmienna = 0x01020304 w pamięci komputera będzie przechowywana tak

adres | 0 | 1 | 2 | 3 |big-endian |0x01|0x02|0x03|0x04|little-endian |0x04|0x03|0x02|0x01|

2432 Konwersja z jednego porządku do innegoCzasami zdarza się że napisany przez nas program musi się komunikować z innym programem (możeteż przez nas napisanym) ktoacutery działa na komputerze o (potencjalnie) innym porządku bajtoacutew Częstonajprościej jest przesyłać liczby jako tekst gdyż jest on niezależny od innych czynnikoacutew jednak takiformat zajmuje więcej miejsca a nie zawsze możemy sobie pozwolić na taką rozrzutność

Przykładem może być komunikacja sieciowa w ktoacuterej przyjęło się że dane przesyłane są w po-rządku big-endian Aby moacutec łatwo operować na takich danych w standardzie zdefiniowanonastępujące funkcje (w zasadzie zazwyczaj są to makra)

include ltarpainethgtuint32_t htonl(uint32_t hostlong)uint16_t htons(uint16_t hostshort)uint32_t ntohl(uint32_t netlong)uint16_t ntohs(uint16_t netshort)

Pierwsze dwie konwertują liczbę z reprezentacji lokalnej na reprezentację big-endian (host to ne-twork) natomiast kolejne dwie dokonują konwersji w drugą stronę (network to host)

Można roacutewnież skorzystać z pliku nagłoacutewkowego endianh w ktoacuterym definiowane są makra po-zwalające określić porządek bajtoacutew

include ltendianhgtinclude ltstdiohgt

int main() if __BYTE_ORDER == __BIG_ENDIAN

printf(Porządek big-endian (4321)n)elif __BYTE_ORDER == __LITTLE_ENDIAN

printf(Porządek little-endian (1234)n)elif defined __PDP_ENDIAN ampamp __BYTE_ORDER == __PDP_ENDIAN

printf(Porządek PDP (3412)n)else

printf(Inny porządek (d)n __BYTE_ORDER)endif

return 0

174 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

Na podstawie makra BYTE ORDER można skonstruować funkcję ktoacutera będzie konwertowaćliczby pomiędzy porządkiem roacuteżnymi porządkami

include ltendianhgtinclude ltstdiohgtinclude ltstdinthgt

uint32_t convert_order32(uint32_t val unsigned from unsigned to) if (from==to)

return val else

uint32_t ret = 0unsigned char tmp[5] = 0 0 0 0 0 unsigned char ptr = (unsigned char)ampvalunsigned div = 1000do tmp[from div 10] = ptr++ while ((div = 10))ptr = (unsigned char)ampretdiv = 1000do ptr++ = tmp[to div 10] while ((div = 10))return ret

define LE_TO_H(val) convert_order32((val) 1234 __BYTE_ORDER)define H_TO_LE(val) convert_order32((val) __BYTE_ORDER 1234)define BE_TO_H(val) convert_order32((val) 4321 __BYTE_ORDER)define H_TO_BE(val) convert_order32((val) __BYTE_ORDER 4321)define PDP_TO_H(val) convert_order32((val) 3412 __BYTE_ORDER)define H_TO_PDP(val) convert_order32((val) __BYTE_ORDER 3412)

int main ()

printf(08xn LE_TO_H(0x01020304))printf(08xn H_TO_LE(0x01020304))printf(08xn BE_TO_H(0x01020304))printf(08xn H_TO_BE(0x01020304))printf(08xn PDP_TO_H(0x01020304))printf(08xn H_TO_PDP(0x01020304))return 0

Ciągle jednak polegamy na niestandardowym pliku nagłoacutewkowym endianh Można go wyelimi-nować sprawdzając porządek bajtoacutew w czasie wykonywania programu

include ltstdiohgtinclude ltstdinthgt

int main() uint32_t val = 0x04030201unsigned char v = (unsigned char )ampvalint byte_order = v[0] 1000 + v[1] 100 + v[2] 10 + v[3]

if (byte_order == 4321) printf(Porządek big-endian (4321)n)

else if (byte_order == 1234)

244 BIBLIOTECZNE PROBLEMY 175

printf(Porządek little-endian (1234)n) else if (byte_order == 3412)

printf(Porządek PDP (3412)n) else

printf(Inny porządek (d)n byte_order)return 0

Powyższe przykłady opisują jedynie część problemoacutew jakie mogą wynikać z proacuteby przenoszeniabinarnych danych pomiędzy wieloma platformami Wszystkie co więcej zakładają że bajt ma bitoacutewco wcale nie musi być prawdą dla konkretnej architektury na ktoacuterą piszemy aplikację Co więcej liczbymogą posiadać w swojej reprezentacje bity wypełnienia (ang padding bits) ktoacutere nie biorą udziaływ przechowywaniu wartości liczby Te wszystkie roacuteżnice mogą dodatkowo skomplikować kod Toteżnależy być świadomym iż przenosząc dane binarnie musimy uważać na roacuteżne reprezentacje liczb

244 Biblioteczne problemy

2441 Dostępność bibliotekPisząc programy nieraz będziemy musieli korzystać z roacuteżnych bibliotek Problem polega na tym żenie zawsze będą one dostępne na komputerze na ktoacuterym inny użytkownik naszego programu będzieproacutebował go kompilować Dlatego też ważne jest abyśmy korzystali z łatwo dostępnych bibliotek ktoacuteredostępne są na wiele roacuteżnych systemoacutew i platform sprzętowych Zapamiętaj Twoacutej program jest natyle przenośny na ile przenośne są biblioteki z ktoacuterych korzysta

2442 Odmiany bibliotekPod Windows funkcje atan floor i fabs są w tej samej bibliotece co standardowe funkcje C

Pod Uniksami są w osobnej bibliotece matematycznej libm w wersji

statycznej (zwykle usrliblibma) i pliku nagłoacutewkowym mathh (zwykle usrincludemathh)3

ladowanej dynamicznie ( usrliblibmso )

Aby korzystać z tych funkcji potrzebujemy

dodać include ltmathhgt

przy kompilacji dołączyć bibliotekę libm gcc mainc -lm

Opcja -lm używa libmso albo libma w zależności od tego ktoacutere są znalezione i w zależności odobecności opcji -static45

245 Kompilacja warunkowaPrzy zwiększaniu przenośności kodu może pomoacutec preprocessor Przyjmijmy np że chcemy korzy-stać ze słoacutewka kluczowego inline wprowadzonego w standardzie C ale roacutewnocześnie chcemy abynasz program był rozumiany przez kompilatory ANSI CWoacutewczas możemy skorzystać z następującegokodu

ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline

3An Introduction to mdashfor the compilers gcc and g++ 27 Linking with external libraries4man ld5Dyskusja na grupie plcomposlinuxprogramowanie na temat c gc atan2 floor fabs

176 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

else define __inline__ endifendif

a w kodzie programu zamiast słoacutewka inline stosować inline Co więcej kompilator rozumiesłoacutewka kluczowe tak tworzone i w jego przypadku warto nie redefiniować ich wartości

ifndef __GNUC__ ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline else define __inline__ endif endifendif

Korzystając z kompilacji warunkowej można także korzystać z roacuteżnego kodu zależnie od (np) sys-temu operacyjnego Przykładowo przed kompilacją na konkretnej platformie tworzymy odpowiedniplik configh ktoacutery następnie dołączamy do wszystkich plikoacutew źroacutedłowych w ktoacuterych podejmujemydecyzje na podstawie zdefiniowanych makr Dla przykładu plik configh

ifndef CONFIG_Hdefine CONFIG_H

Uncomment if using Windows define USE_WINDOWS

Uncomment if using Linux define USE_LINUX

error You must edit configh fileerror Edit it and remove those error lines

endif

Jakiś plik źroacutedłowy

include configh

ifdef USE_WINDOWSrob_cos_wersja_dla_windows()

elserob_cos_wersja_dla_linux()

endif

Istnieją roacuteżne narzędzia ktoacutere pozwalają na automatyczne tworzenie takich plikoacutew configh dziękiczemu użytkownik przed skompilowaniem programu nie musi się trudzić i edytować ich ręcznie ajedynie uruchomić odpowiednie polecenie Przykładem jest zestaw autoconf i automake

Rozdział 25

Łączenie z innymi językami

Programista pisząc jakiś program ma problem z wyborem najbardziej odpowiedniego języka do utwo-rzenia tego programu Niekiedy zdarza się że najlepiej byłoby pisać program korzystając z roacuteżnychjęzykoacutew Język C może być z łatwością łączony z innymi językami programowania ktoacutere podlegająkompilacji bezpośrednio do kodu maszynowego (Asembler Fortran czy też C++) Ponadto dzięki spe-cjalnym bibliotekom można go łączyć z językami bardzo wysokiego poziomu (takimi jak np Pythonczy też Ruby) Ten rozdział ma za zadanie wytłumaczyć Ci w jaki sposoacuteb można mieszać roacuteżne językiprogramowania w jednym programie

251 Język C i Asembler

Informacje zawarte w tym rozdziale odnoszą się do komputeroacutew z procesorem i i pokrewnych

Łączenie języka C i języka asemblera jest dość powszechnym zjawiskiem Dzięki możliwości połączeniaobu tych językoacutew programowania można było utworzyć bibliotekę dla języka C ktoacutera niskopoziomowokomunikuje się z jądrem systemu operacyjnego komputera Ponieważ zaroacutewno asembler jak i C sąjęzykami tłumaczonymi do poziomu kodu maszynowego za ich łączenie odpowiada program zwanylinkerem (popularny ld) Ponadto niektoacuterzy producenci kompilatoroacutew umożliwiają stosowanie tzwwstawek asemblerowy ktoacutere umieszcza się bezpośrednio w kodzie programu napisanego w językuC Kompilator kompilując taki kod wstawi w miejsce tychże wstawek odpowiedni kod maszynowyktoacutery jest efektem przetłumaczenia kodu asemblera zawartegow takiej wstawce Opiszę tu oba sposobyłączenia obydwu językoacutew

2511 Łączenie na poziomie kodu maszynowegoW naszym przykładzie założymy że w pliku fS zawarty będzie kod napisany w asemblerze a fcto kod z programem w języku C Program w języku C będzie wykorzystywał jedną funkcję napisanąw języku asemblera ktoacutera wyświetli prosty napis ldquoHello worldrdquo Z powodu ograniczeń technicznychzakładamy że program uruchomiony zostanie w środowisku POSIX na platformie i i skompilowanykompilatorem gcc Używaną składnią asemblera będzie ATampT (domyślna dla asemblera ) Oto plikfS

text

globl _f1_f1

pushl ebpmovl esp ebp

177

178 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

movl $4 eax 4 to funkcja systemowa write movl $1 ebx 1 to stdout movl $tekst ecx adres naszego napisu movl $len edx długość napisu w bajtach int $0x80 wywołanie przerwania systemowego popl ebpret

datatekst

string Hello worldnlen = - tekst

W systemach z rodziny UNIX należy pominąć znak rdquo rdquoprzed nazwą funkcji f

Teraz kolej na fc

extern void f1 (void) musimy użyć słowa extern int main ()

f1()return 0

Teraz możemy skompilować oba programy

as f1S -o f1ogcc f2c -c -o f2ogcc f2o f1o -o program

W ten sposoacuteb uzyskujemy plik wykonywalny o nazwie ldquoprogramrdquo Efekt działania programu powinienbyć następujący

Hello world

Na razie utworzyliśmy bardzo prostą funkcję ktoacutera w zasadzie nie komunikuje się z językiem Cczyli nie zwraca żadnej wartości ani nie pobiera argumentoacutew Jednak aby zacząć pisać obsługę funk-cji ktoacutera będzie pobierała argumenty i zwracała wyniki musimy poznać działanie języka C od trochęniższego poziomu

Argumenty

Do komunikacji z funkcją język C korzysta ze stosu Argumenty odkładane sąw kolejności od ostatniegodo pierwszego Ponadto na końcu odkładany jest tzw adres powrotu dzięki czemu po wykonaniufunkcji program ldquowierdquo w ktoacuterym miejscu ma kontynuować działanie Ponadto początek funkcji wasemblerze wygląda tak

pushl ebpmovl esp ebp

Zatem na stosie znajdują się kolejno zawartość rejestru EBP adres powrotu a następnie argumenty odpierwszego do n-tego

251 JĘZYK C I ASEMBLER 179

Zwracanie wartości

Na architekturze i do zwracaniawynikoacutew pracy programu używa się rejestru EAX bądź jego ldquomniej-szychrdquo odpowiednikoacutew tj AX i AHAL Zatem aby funkcja napisana w asemblerze zwroacuteciła ldquordquo przedrozkazem ret należy napisać

movl $1 eax

Nazewnictwo

Kompilatory języka CC++ dodają podkreślnik ldquo rdquo na początku każdej nazwy Dla przykładu funkcja

void funkcja()

W pliku wyjściowym będzie posiadać nazwę funkcja Dlatego aby korzystać z poziomu języka C zfunkcji zakodowanych w asemblerze muszą one mieć przy definicji w pliku asemblera wspomnianydodatkowy podkreślnik na początku

Łączymy wszystko w całość

Pora abyśmy napisali jakąś funkcję ktoacutera pobierze argumenty i zwroacuteci jakiś konkretny wynik Otokod fS

text

globl _funkcja_funkcja

pushl ebpmovl esp ebpmovl 8(esp) eax kopiujemy pierwszy argument do eax addl 12(esp) eax do pierwszego argumentu w eax dodajemy drugi argument popl ebpret i zwracamy wynik dodawania

oraz fc

include ltstdiohgtextern int funkcja (int a int b)int main ()printf (2+3=dn funkcja(23))return 0

Po skompilowaniu i uruchomieniu programu powinniśmy otrzymać wydruk +=

2512 Wstawki asembleroweOproacutecz możliwości wstępnie skompilowanych modułoacutew możesz posłużyć się także tzw wstawkamiasemblerowymi Ich użycie powoduje wstawienie w miejsce wystąpienia wstawki odpowiedniegokodumaszynowego ktoacutery powstanie po przetłumaczeniu kodu asemblerowego Ponieważ jednakwstawkiasemblerowe nie są standardowym elementem języka C każdy kompilator ma całkowicie odmiennąfilozofię ich stosowania (lub nie ma ich w ogoacutele) Ponieważ w tym podręczniku używamy głoacutewniekompilatora więc w tym rozdziale zostanie omoacutewiona filozofia stosowania wstawek asemblerawedług programistoacutew

Ze wstawek asemblerowych korzysta się tak

180 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

int main ()

asm (nop)

W tym wypadku wstawiona zostanie instrukcja ldquonoprdquo (no operation) ktoacutera tak naprawdę służytylko i wyłącznie do konstruowania pętli opoacuteźniających

252 C++Język C++ z racji swojego podobieństwa do C będzie wyjątkowo łatwy do łączenia Pewnym utrudnie-niem może być obiektowość języka C++ oraz występowanie w nim przestrzeni nazw oraz możliwośćprzeciążania funkcji Oczywiście nadal zakładamy że głoacutewny program piszemy w C natomiast korzy-stamy tylko z pojedynczych funkcji napisanych w C++ Ponieważ język C nie oferuje tego wszystkiegoco daje programiście język C++ to musimy ldquozmusićrdquo C++ do wyłączenia pewnych swoich możliwościaby można było połączyć ze sobą elementy programu napisane w dwoacutech roacuteżnych językach Używa siędo tego następującej konstrukcji

extern C funkcje zmienne i wszystko to co będziemy łączyć z programem w C

W zrozumieniu teorii pomoże Ci prosty przykład plik fc

include ltstdiohgtextern int f2(int a)

int main ()

printf (dn f2(2))return 0

oraz plik fcpp

include ltiostreamgtusing namespace stdextern C

int f2 (int a)

cout ltlt a= ltlt a ltlt endlreturn a2

Teraz oba pliki kompilujemy

gcc f1c -c -o f1og++ f2cpp -c -o f2o

Przy łączeniu obu tych plikoacutew musimy pamiętać że język C++ także korzysta ze swojej bibliotekiZatem poprawna postać polecenia kompilacji powinna wyglądać

gcc f1o f2o -o program -lstdc++

(stdc++ mdash biblioteka standardowa języka C++) Bardzo istotne jest tutaj to abyśmy zawsze pamiętalio extern ldquoCrdquo gdyż w przeciwnym razie funkcje napisane w C++ będą dla programu w C całkowicieniewidoczne

Dodatek A

Indeks alfabetyczny

Alfabetyczny spis funkcji biblioteki standardowej ANSI C (tzw libc) w wersji C

A01 A abort()

abs()

acos()

asctime()

asin()

assert()

atan()

atan()

atexit()

atof()

atoi()

atol()

A02 B bsearch()

A03 C calloc()

ceil()

clearerr()

clock()

cos()

cosh()

ctime()

A04 D diime()

div()

A05 E errno (zmienna)

exit()

exp()

A06 F fabs()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

floor()

fmod()

fopen()

fprintf()

fputc()

fputs()

fread()

free()

freopen()

frexp()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

A07 G getc()

getchar()

getenv()

gets()

gmtime()

A08 I isalnum()

isalpha()

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

181

182 DODATEK A INDEKS ALFABETYCZNY

A09 L labs()

ldexp()

ldiv()

localeconv()

localtime()

log()

log()

longjmp()

A010 M malloc()

mblen()

mbstowcs()

mbtowc()

memchr()

memcmp()

memcpy()

memmove()

memset()

mktime()

modf()

A011 O offsetof()

A012 P perror()

pow()

printf()

putc()

putchar()

puts()

A013 Q qsort()

A014 R raise()

rand()

realloc()

remove()

rename()

rewind()

A015 S scanf()

setbuf()

setjmp()

setlocale()

setvbuf()

signal()

sin()

sinh()

sprintf()

sqrt()

srand()

sscanf()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strime()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtod()

strtok()

strtol()

strtoul()

strxfrm()

system()

A016 T tan()

tanh()

time()

tm (struktura)

tmpfile()

tmpnam()

tolower()

toupper()

A017 U ungetc()

A018 V va arg()

va end()

va start()

vfprintf()

vprintf()

vsprintf()

A019 W wcstombs()

wctomb()

Dodatek B

Indeks tematyczny

Spis plikoacutew nagłoacutewkowych oraz zawartych w nich funkcji i makr biblioteki standardowej C Funkcjemakra i typy wprowadzone dopiero w standardzie C zostały oznaczone poprzez ldquo[C]rdquo po nazwie

B1 asserthMakro asercji

assert()

B2 ctypehKlasyfikowanie znakoacutew

isalnum()

isalpha()

isblank() [C]

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

tolower()

toupper()

B3 errnohDeklaracje kodoacutew błędoacutew

EDOM (makro)

EILSEQ (makro) [C]

ERANGE (makro)

errno (zmienna)

B4 floathWłaściwości typoacutew zmiennoprzecinkowych zależne od implementacji

B5 limitshWłaściwości typoacutew całkowitych zależne od implementacji

183

184 DODATEK B INDEKS TEMATYCZNY

B6 localehUstawienia międzynarodowe

localeconv()

setlocale()

B7 mathhFunkcje matematyczne

FP FAST FMAF (makro) [C]

FP FAST FMAL (makro) [C]

FP FAST FMA (makro) [C]

FP ILOGB (makro) [C]

FP ILOGBNAN (makro) [C]

FP INFINITE (makro) [C]

FP NAN (makro) [C]

FP NORMAL (makro) [C]

FP SUBNORMAL (makro) [C]

FP ZERO (makro) [C]

HUGE VALF (makro) [C]

HUGE VALL (makro) [C]

HUGE VAL (makro)

INFINITY (makro) [C]

MATH ERREXCEPT (makro) [C]

MATH ERRNO (makro) [C]

NAN (makro) [C]

acosh()

acos()

asinh()

asin()

atan()

atanh()

atan()

cbrt() [C]

ceil()

copysign() [C]

cosh()

cos()

double t (typ) [C]

erfc() [C]

erf() [C]

exp() [C]

expm() [C]

exp()

fabs()

fdim() [C]

flaot t (typ) [C]

floor()

fmax() [C]

fma() [C]

fmin() [C]

fmod()

fpclassify() [C]

frexp()

hypot() [C]

ilogb() [C]

isfinite() [C]

isgreaterequal() [C]

isgreater() [C]

isinf() [C]

islessequal() [C]

islessgreater() [C]

isless() [C]

isnan() [C]

isnormal() [C]

isunordered() [C]

ldexp()

lgamma() [C]

llrint() [C]

llround() [C]

log()

logp() [C]

log() [C]

logb() [C]

log()

B8 SETJMPH 185

lrint() [C]

lround() [C]

math errhandling (makro) [C]

modf()

nan() [C]

nearbyint() [C]

nextaer() [C]

nexoward() [C]

pow()

remainder() [C]

remquo() [C]

rint() [C]

round() [C]

scalbln() [C]

scalbn() [C]

signbit() [C]

sinh()

sin()

sqrt()

tanh()

tan()

tgamma() [C]

trunc() [C]

B8 setjmphObsługa nielokalnych skokoacutew

longjmp()

setjmp()

B9 signalhObsługa sygnałoacutew

raise()

signal()

B10 stdarghNarzędzia dla funkcji ze zmienną liczbą argumentoacutew

va arg()

va end()

va start()

B11 stddefhStandardowe definicje

offsetof()

B12 stdiohStandard InputOutput czyli standardowe wejście-wyjście

clearerr()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

fopen()

186 DODATEK B INDEKS TEMATYCZNY

fprintf()

fputc()

fputs()

fread()

freopen()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

getc()

getchar()

gets()

perror()

printf()

putc()

putchar()

puts()

remove()

rename()

rewind()

scanf()

setbuf()

setvbuf()

sprintf()

sscanf()

tmpfile()

tmpnam()

ungetc()

vfprintf()

vprintf()

vsprintf()

B13 stdlibhNajbardziej podstawowe funkcje

abort()

abs()

atexit()

atof()

atoi()

atol()

bsearch()

calloc()

div()

exit()

free()

getenv()

labs()

ldiv()

malloc()

mblen()

mbstowcs()

mbtowc()

qsort()

rand()

realloc()

srand()

strtod()

strtol()

strtoul()

system()

wctomb()

wcstombs()

B14 stringhOperacje na łańcuchach znakoacutew

memchr()

memcmp()

memcpy()

memmove()

memset()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtok()

strxfrm()

strdup()

B15 timehFunkcje obsługi czasu

B15 TIMEH 187

asctime()

clock()

ctime()

diime()

gmtime()

localtime()

mktime()

strime()

time()

tm (struktura)

188 DODATEK B INDEKS TEMATYCZNY

Dodatek C

Wybrane funkcje bibliotekistandardowej

C1 assert

C11 Deklaracjadefine assert(expr)

C12 Plik nagłoacutewkowyasserth

C13 OpisMakro przypominające w użyciu funkcję służy do debuggowania programoacutew Gdy testowany waruneklogiczny expr przyjmuje wartość fałsz na standardowe wyjście błędoacutew wypisywany jest komunikat obłędzie (zawierające min argument wywołania makra nazwę funkcji w ktoacuterej zostało wywołanenazwę pliku źroacutedłowego oraz numer linii w formacie zależnym od implementacji) i program jest prze-rywany poprzez wywołanie funkcji abort

W ten sposoacuteb możemy oznaczyć w programie niezmienniki czyli warunki ktoacutere niezależnie odwartości zmiennych muszą pozostać prawdziwe Jeśli asercja zawiedzie oznacza to że popełniliśmybłąd w algorytmie piszemy sobie po pamięci (nadając zmiennym wartości ktoacuterych nigdy nie powinnymieć) albo nastąpiła po drodze sytuacja wyjątkowa na przykład związana z obsługą operacji wejścia-wyjścia

Można łatwo pozbyć się asercji uwalniając kod od spowalniających obciążeń a jednocześnie niemusząc kasować wystąpień assert i zachowując je na przyszłość Aby to zrobić należy przed dołą-czeniem pliku nagłoacutewkowego asserth zdefiniować makro NDEBUG woacutewczas makro assert przyjmujepostać

define assert(ignore) ((void)0)

Makro assert jest redefiniowane za każdym dołączeniem pliku nagłoacutewkowego asserth

C14 Wartość zwracanaMakro nie zwraca żadnej wartości

189

190 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C15 Przykładinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

Program wypisze komunikat podobny do

Assertion failed err==0 file testc line 6

Natomiast jeśli uruchomimy

define NDEBUGinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

nie pojawi się żaden komunikat o błędach

C2 atoi

C21 Deklaracjaint atoi (const char string)

C22 Plik nagłoacutewkowystdlibh

C23 OpisFunkcja jako argument pobiera liczbę w postaci ciągu znakoacutew ASCII a następnie zwraca jej wartość wformacie int Liczbę może poprzedzać dowolona ilość białych znakoacutew (spacje tabulatory itp) oraz jejznak (plus (+) lub minus (-)) Funkcja atoi() kończy wczytywać znaki w momencie napotkania jakiego-kowiek znaku ktoacutery nie jest cyfrą

C24 Wartość zwracanaW przypadku gdy ciąg nie zawiera cyfr zwracana jest wartość

C25 UwagiZnak musi bezpośrednio poprzedzać liczbę czyli możliwy jest zapis ldquo-rdquo natomiast proacuteba potraktowa-nia funkcją atoi ciągu ldquo- rdquo skutkuje zwracaną wartością

C3 ISALNUM 191

C26 Przykładinclude ltstdiohgtinclude ltstdlibhgtint main(void)

char c_Numer = nt 2004uint i_Numeri_Numer = atoi(c_Numer)printf(n Liczba typu int d oraz jako ciąg znakoacutew s n i_Numer c_Numer)return 0

C3 isalnum

C31 Deklaracjainclude ltctypehgt

int isalnum(int c)int isalpha(int c)int isblank(int c)int iscntrl(int c)int isdigit(int c)int isgraph(int c)int islower(int c)int isprint(int c)int ispuntc(int c)int isspace(int c)int isupper(int c)int isxdigit(int c)

C32 Argumentyc wartość znaku reprezentowana w jako typ unsigned char lub wartość makra EOF Z tego powodu

przed przekazaniem funkcji argumentu typu char lub signed char należy go zrzutować na typunsigned char lub unsigned int

C33 OpisFunkcje sprawdzają czy podany znak spełnia jakiś konkretny warunek Biorą pod uwagę ustawieniajęzyka i dla roacuteżnych znakoacutew w roacuteżnych localersquoach mogą zwracać roacuteżne wartości

isalnum sprawdza czy znak jest liczbą lub literą

isalpha sprawdza czy znak jest literą

isblank sprawdza czy znak jest znakiem odstępu służącym do oddzielania wyrazoacutew (standardowymiznakami odstępu są spacja i znak tabulacji)

iscntrl sprawdza czy znak jest znakiem sterującym

isdigit sprawdza czy znak jest cyfrą dziesiętna

isgraph sprawdza czy znak jest znakiem drukowalnym roacuteżnym od spacji

islower sprawdza czy znak jest małą literą

isprint sprawdza czy znak jest znakiem drukowalnym (włączając w to spację)

192 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

ispunct sprawdza czy znak jest znakiem przestankowym dla ktoacuterego ani isspace ani isalnum nie sąprawdziwe (standardowo są to wszystkie znaki drukowalne dla ktoacuterych te funkcje zwracajązero)

isspace sprawdza czy znak jest tzw białym znakiem (standardowymi białymi znakami są spacjawysunięcie strony rsquorsquo znak przejścia do nowej linii rsquonrsquo znak powrotu karetki rsquorrsquo tabulacjapozioma rsquotrsquo i tabulacja pionowa rsquovrsquo)

isupper sprawdza czy znak jest dużą literą

isxdigit sprawdza czy znak jest cyfrą szesnastkową tj cyfrą dziesiętną lub literą od rsquoarsquo do rsquorsquo niezależnieod wielkości

Funkcja isblank niewystępowaław oryginalnym standardzie ANSI C z roku (tzw C) i zostaładodana dopiero w nowszym standardzie z roku (tzw C)

C34 Wartość zwracanaLiczba niezerowa gdy podany argument spełnia konkretny warunek w przeciwnym wypadku mdash zero

C35 Przykład użyciainclude ltctypehgt funkcje is include ltlocalehgt setlocale include ltstdiohgt printf i scanf

void identify_char(int c) printf( Litera lub cyfra sn isalnum (c) tak nie)

if __STDC_VERSION__ gt= 199901Lprintf( Odstęp sn isblank (c) tak nie)

endifprintf( Znak sterujący sn iscntrl (c) tak nie)printf( Cyfra dziesiętna sn isdigit (c) tak nie)printf( Graficzny sn isgraph (c) tak nie)printf( Mała litera sn islower (c) tak nie)printf( Drukowalny sn isprint (c) tak nie)printf( Przestankowy sn ispunct (c) tak nie)printf( Biały znak sn isspace (c) tak nie)printf( Wielka litera sn isupper (c) tak nie)printf( Cyfra szesnastkowa sn isxdigit(c) tak nie)

int main() unsigned char cprintf(Naciśnij jakiś klawiszn)if (scanf(c ampc)==1)

identify_char(c)setlocale(LC_ALL pl_PL) przystosowanie do warunkoacutew polskich puts(Po zmianie ustawień języka)identify_char(c)

return 0

C36 Zobacz też tolower toupper

C4 MALLOC 193

C4 malloc

C41 Deklaracjainclude ltstdlibhgt

void calloc(size_t nmeb size_t size)void malloc(size_t size)void free(void ptr)void realloc(void ptr size_t size)

C42 Argumentynmeb liczba elementoacutew dla ktoacuterych ma być przydzielona pamięć

size rozmiar (w bajtach) pamięci do zarezerwowania bądź rozmiar pojedynczego elementu

ptr wskaźnik zwroacutecony przez poprzednie wywołanie jednej z funkcji lub

C43 OpisFunkcja calloc przydziela pamięć dla nmeb elementoacutew o rozmiarze size każdy i zeruje przydzielonąpamięć

Funkcja malloc przydziela pamięć o wielkości size bajtoacutewFunkcja free zwalnia blok pamięci wskazywany przez ptr wcześniej przydzielony przez jedną z

funkcji malloc calloc lub realloc Jeżeli ptr ma wartość funkcja nie robi nicFunkcja realloc zmienia rozmiar przydzielonego wcześniej bloku pamięci wskazywanego przez ptr

do size bajtoacutew Pierwsze n bajtoacutew bloku nie ulegnie zmianie gdzie n jest minimum z rozmiaru staregobloku i size Jeżeli ptr jest roacutewny zero (tj ) funkcja zachowuje się tak samo jako malloc

C44 Wartość zwracanaJeżeli przydzielanie pamięci się powiodło funkcje calloc malloc i realloc zwracają wskaźnik do nowoprzydzielonego bloku pamięci W przypadku funkcji realloc może to być wartość inna niż ptr

Jeśli jako size nmeb podano zero zwracany jest albo wskaźnik albo prawidłowy wskaźnikktoacutery można podać do funkcji free (zauważmy że jest też prawidłowym argumentem free)

Jeśli działanie funkcji nie powiedzie się zwracany jest i odpowiedni kod błędu jest wpisywanydo zmiennej errno Dzieje się tak zazwyczaj gdy nie ma wystarczająco dużo miejsca w pamięci

C45 Przykładinclude ltstdiohgtinclude ltstdlibhgt

int main(void)

size_t size num = 0float tab tmp

Przydzielenie początkowego bloku pamięci size = 64tab = malloc(size sizeof tab)if (tab)

perror(malloc)return EXIT_FAILURE

194 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Odczyt liczb while (scanf(f amptmp)==1)

Jeżeli zapełniono całą tablicę trzeba ją zwiększyć if (num==size)float ptr = realloc(tab (size = 2) sizeof ptr)if (ptr)

free(tab)perror(realloc)return EXIT_FAILURE

tab = ptr

tab[num++] = tmp

Wypisanie w odwrotnej kolejnosci while (num)

printf(fn tab[--num])

Zwolnienie pamieci i zakonczenie programu free(tab)return EXIT_SUCCESS

C46 Uwagi

Użycie rzutowania przy wywołaniach funkcji malloc realloc oraz calloc w języku C jest zbędne i szko-dliwe W przypadku braku deklaracji tych funkcji (np gdy programista zapomni dodać plik nagłoacutew-kowy stdlibh) kompilator przyjmuje domyślną deklaracje w ktoacuterej funkcja zwraca int Przy brakurzutowania spowoduje to błąd kompilacji (z powodu niemożności skonwertowania liczby na wskaźnik)co pozwoli na szybkie wychwycenie błędu w programie Rzutowanie powoduje że kompilator zostajezmuszony do przeprowadzenia konwersji typoacutew i nie wyświetla żadnych błędoacutew W przypadku językaC++ rzutowanie jest konieczne

Zastosowanie operatora sizeof z wyrażeniem (np sizeof tablica) a nie typem (np sizeof float)ułatwia poacuteźniejszą modyfikację programoacutew Gdyby w pewnym momencie programista zdecydował sięzmienić tablicę z tablicy floatoacutew na tablice doublersquoi musiałby wyszukiwać wszystkie wywołania funkcjimalloc realloc i calloc co nie jest konieczne przy użyciu operatora sizeof z wyrażeniem

Ponieważ dla parametru size roacutewnego zero funkcja może zwroacutecić albo wskaźnik roacuteżny od wartości albo jej roacutewny zwykłe sprawdzanie poprawności wywołania poprzez przyroacutewnanie zwroacuteconejwartości do zera może nie dać prawidłowego wyniku

C47 Zobacz też

Wskaźniki (dokładne omoacutewienie zastosowania)

C5 PRINTF 195

C5 printf

C51 Deklaracjainclude ltstdiohgt

int printf(const char format )int fprintf(FILE stream const char format )int sprintf(char str const char format )int snprintf(char str size_t size const char format )

include ltstdarghgt

int vprintf(const char format va_list ap)int vfprintf(FILE stream const char format va_list ap)int vsprintf(char str const char format va_list ap)int vsnprintf(char str size_t size const char format va_list ap)

C52 OpisFunkcje formatują tekst zgodnie z podanym formatem opisanym poniżej Funkcje printf i vprintf wy-pisują tekst na standardowe wyjście (tj do stdout) fprintf i vfprintf do strumienia podanego jakoargument a sprintf vsprintf snprintf i vsnprintf zapisują go w podanej jako argument tablicy znakoacutew

Funkcje vprintf vfprintf vsprintf i vsnprintf roacuteżnią się od odpowiadających im funkcjom printffprintf sprintf i snprintf tym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

Funkcje snprintf i vsnprintf roacuteżnią się od sprintf i vsprintf tym że nie zapisuje do tablicy nie wię-cej niż size znakoacutew (wliczając kończący znak rsquorsquo) Oznacza to że można je używać bez obawy owystąpienie przepełnienia bufora

C53 Argumentyformat format w jakim zostaną wypisane następne argumenty

stream strumień wyjściowy do ktoacuterego mają być zapisane dane

str tablica znakoacutew do ktoacuterej ma być zapisany sformatowany tekst

size rozmiar tablicy znakoacutew

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

C54 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) ktoacutere są kopiowane bez zmian na wyjścieoraz sekwencji sterujących zaczynających się od symbolu procenta po ktoacuterym następuje

dowolna liczba flag

opcjonalne określenie minimalnej szerokości pola

opcjonalne określenie precyzji

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

196 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Flagi

W sekwencji możliwe są następujące flagi

- (minus) oznacza że pole ma być wyroacutewnane do lewej a nie do prawej

+ (plus) oznacza że dane liczbowe zawsze poprzedzone są znakiem (plusem dla liczb nieujem-nych lub minusem dla ujemnych)

spacja oznacza że liczby nieujemne poprzedzone są dodatkową spacją jeżeli flaga plus i spacjasą użyte jednocześnie to spacja jest ignorowana

(hash) powoduje że wynik jest przedstawiony w alternatywnej postaci

ndash dla formatu o powoduje to zwiększenie precyzji jeżeli jest to konieczne aby na początkuwyniku było zero

ndash dla formatoacutew x i X niezerowa liczba poprzedzona jest ciągiem x lub X

ndash dla formatoacutew a A e E f F g i G wynik zawsze zawiera kropkę nawet jeżeli nie ma za niążadnych cyfr

ndash dla formatoacutew g i G końcowe zera nie są usuwane

(zero) dla formatoacutew d i o u xX aA e E f F g iG do wyroacutewnania pola wykorzystywane sązera zamiast spacji za wyjątkiem wypisywania wartości nieskończoność i NaN Jeżeli obie flagi i mdash są obecne to flaga zero jest ignorowana Dla formatoacutew d i o u x i X jeżeli określona jestprecyzja flaga ta jest ignorowana

Szerokość pola i precyzja

Minimalna szerokość pola oznacza ile najmniej znakoacutew ma zająć dane pole Jeżeli wartość po formato-waniu zajmuje mniej miejsca jest ona wyroacutewnywana spacjami z lewej strony (chyba że podano flagiktoacutere modyfikują to zachowanie) Domyślna wartość tego pola to

Precyzja dla formatoacutew

d i o u x iX określa minimalną liczbę cyfr ktoacutere mają być wyświetlone i ma domyślną wartość

a A e E f i F mdash liczbę cyfr ktoacutere mają być wyświetlone po kropce i ma domyślną wartość

g i G określa liczbę cyfr znaczących i ma domyślną wartość

dla formatu s mdash maksymalną liczbę znakoacutew ktoacutere mają być wypisane

Szerokość pola może być albo dodatnią liczbą zaczynającą się od cyfry roacuteżnej od zera albo gwiazdkąPodobnie precyzja z tą roacuteżnicą że jest jeszcze poprzedzona kropką Gwiazdka oznacza że brany jestkolejny z argumentoacutew ktoacutery musi być typu int Wartość ujemna przy określeniu szerokości jest trak-towana tak jakby podano flagę - (minus)

Rozmiar argumentu

Dla formatoacutew d i i można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu signed char

h mdash oznacza że format odnosi się do argumentu typu short

l (el) mdash oznacza że format odnosi się do argumentu typu long

ll (el el) mdash oznacza że format odnosi się do argumentu typu long long

j mdash oznacza że format odnosi się do argumentu typu intmax t

z mdash oznacza że że format odnosi się do argumentu typu będącego odpowiednikiem typu size tze znakiem

t mdash oznacza że że format odnosi się do argumentu typu ptrdiff t

C5 PRINTF 197

Dla formatoacutew o u x i X można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d ioznaczają one że format odnosi się do argumentu odpowiedniego typu bez znaku

Dla formatu n można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d i oznaczająone że format odnosi się do argumentu będącego wskaźnikiem na dany typ

Dla formatoacutew a A e E f F g iGmożna użyć modyfikatoroacutew rozmiaru L ktoacutery oznacza że formatodnosi się do argumentu typu long double

Dodatkowo modyfikator l (el) dla formatu c oznacza że odnosi się on do argumentu typu wint ta dla formatu s że odnosi się on do argumentu typu wskaźnik na wchar t

Format

Funkcje z rodziny printf obsługują następujące formaty

d i mdash argument typu int jest przedstawiany jako liczba całkowita ze znakiem w postaci [-]ddd

o u x X mdash argument typu unsigned int jest przedstawiany jako nieujemna liczba całkowitazapisana w systemie oktalnym (o) dziesiętnym (u) lub heksadecymalnym (x i X)

f F mdash argument typu double jest przedstawiany w postaci [-]dddddd

e E mdash argument typu double jest reprezentowany w postaci [i]dddde+dd gdzie liczba przedkropką dziesiętną jest roacuteżna od zera jeżeli liczba jest roacuteżna od zera a + oznacza znak wykładnikaFormat E używa wielkiej litery E zamiast małej

g G mdash argument typu double jest reprezentowany w formacie takim jak f lub e (odpowiednio Flub E) zależnie od liczby znaczących cyfr w liczbie oraz określonej precyzji

a A mdash argument typu double przedstawiany jest w formacie [-]xhhhhp+d czyli analogiczniejak dla e i E tyle że liczba zapisana jest w systemie heksadecymalnym

c mdash argument typu int jest konwertowany do unsigned char i wynikowy znak jest wypisywanyJeżeli podanomodyfikator rozmiaru l argument typuwint t konwertowany jest dowielobajtowejsekwencji i wypisywany

s mdash argument powinien być typu wskaźnik na char (lub wchar t) Wszystkie znaki z podanejtablicy aż do i z wyłączeniem znaku null są wypisywane

pmdashargument powinien być typuwskaźnik na void Jest to konwertowany na serię drukowalnychznakoacutew w sposoacuteb zależny od implementacji

n mdash argument powinien być wskaźnikiem na liczbę całkowitą ze znakiem do ktoacuterego zapisanajest liczba zapisanych znakoacutew

W przypadku formatoacutew f F e E gG a iAwartość nieskończoność jest przedstawiana w formacie[-]inf lub [-]infinity zależnie od implementacji Wartość NaN jest przedstawiana w postaci [-]nan lub[i]nan(sekwencja) gdzie sekwencja jest zależna od implementacji W przypadku formatoacutew określo-nych wielką literą roacutewnież wynikowy ciąg znakoacutew jest wypisywany wielką literą

C55 Wartość zwracana

Jeżeli funkcje zakończą się sukcesem zwracają liczbę znakoacutew w tekście (wypisanym na standardowewyjście do podanego strumienia lub tablicy znakoacutew) nie wliczając kończącego rsquorsquo W przeciwnymwypadku zwracana jest liczba ujemna

Wyjątkami są funkcje snprintf i vsnprintf ktoacutere zwracają liczbę znakoacutew ktoacutere zostałyby zapisanedo tablicy znakoacutew gdyby była wystarczająco duża

198 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C56 Przykład użyciainclude ltstdiohgtint main()

int i = 4float f = 31415char s = Monty Pythonprintf(i = inf = 1fnWskaźnik s wskazuje na napis sn i f s)return 0

Wyświetli

i = 4f = 31Wskaźnik s wskazuje na napis Monty Python

Funkcja formatująca ciąg znakoacutew i alokująca odpowiednią ilość pamięci

include ltstdarghgtinclude ltstdlibhgt

char sprintfalloc(const char format ) int retsize_t size = 100char str = malloc(size)if (str)

return 0

for()va_list apchar tmp

va_start(ap format)ret = vsnprintf(str size format ap)va_end(ap)

if (retltsize) break

tmp = realloc(str (size_t)ret + 1)if (tmp) ret = -1break

else str = tmpsize = (size_t)ret + 1

if (retlt0) free(str)str = 0

C6 SCANF 199

else if (size-1gtret) char tmp = realloc(str (size_t)ret + 1)if (tmp) str = tmp

return str

C57 Uwagi

Funkcje snprintf i vsnprintf nie były zdefiniowane w standardzie C Zostały one dodane dopiero wstandardzie C

Biblioteka glibc do wersji włącznie posiadała implementacje funkcji snprintf oraz vsnprintfktoacutere były niezgodne ze standardem gdyż zwracały - w przypadku gdy wynikowy tekst nie mieściłsię w podanej tablicy znakoacutew

C6 scanf

C61 Deklaracja

W pliku nagłoacutewkowym stdioh

int scanf(const char format )int fscanf(FILE stream const char format )int sscanf(const char str const char format )

W pliku nagłoacutewkowym stdargh

int vscanf(const char format va_list ap)int vsscanf(const char str const char format va_list ap)int vfscanf(FILE stream const char format va_list ap)

C62 Opis

Funkcje odczytują dane zgodnie z podanym formatem opisanym niżej Funkcje scanf i vscanf odczytujądane ze standardowego wejścia (tj stdin) fscanf i vfscanf ze strumienia podanego jako argument asscanf i vsscanf z podanego ciągu znakoacutew

Funkcje vscanf vfscanf i vsscanf roacuteżnią się od odpowiadających im funkcjom scanf fscanf i sscanftym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

C63 Argumenty

format format odczytu danych

stream strumień wejściowy z ktoacuterego mają być odczytane dane

str tablica znakoacutew z ktoacuterej mają być odczytane dane

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

200 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C64 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) oraz sekwencji sterujących zaczynającychsię od symbolu procenta po ktoacuterym następuje

opcjonalna gwiazdka

opcjonalne maksymalna szerokość pola

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

Wystąpienie w formacie białego znaku powoduje że funkcje z rodziny scanf będą odczytywać iodrzucać znaki aż do napotkania pierwszego znaku nie będącego białym znakiem

Wszystkie inne znaki (tj nie białe znaki oraz nie sekwencje sterujące) muszą dokładnie pasowaćdo danych wejściowych

Wszystkie białe znaki z wejścia są ignorowane chyba że sekwencja sterująca określa format [ c lubn

Jeżeli w sekwencji sterującej występuje gwiazdka to dane z wejścia zostaną pobrane zgodnie zformatem ale wynik konwersji nie zostanie nigdzie zapisany W ten sposoacuteb można pomijać częśćdanych

Maksymalna szerokość pola przyjmuje postać dodatniej liczby całkowitej zaczynającej się od cyfryroacuteżnej od zera Określa ona ile maksymalnie znakoacutew dany format może odczytać Jest to szczegoacutelnieprzydatne przy odczytywaniu ciągu znakoacutew gdyż dzięki temu można podać wielkość tablicy (minusjeden) i tym samym uniknąć błędoacutew przepełnienia bufora

Rozmiar argumentu

Dla formatoacutew d i o u x i n można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu wskaźnik na signed char lub unsignedchar

h mdash oznacza że format odnosi się do argumentu typu wskaźnik na short lub wskaźnik na unsi-gned short

l (el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long lub wskaźnik naunsigned long

ll (el el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long long lub wskaźnikna unsigned long long

j mdash oznacza że format odnosi się do argumentu typu wskaźnik na intmax t lub wskaźnik nauintmax t

z mdash oznacza że że format odnosi się do argumentu typu wskaźnik na size t lub odpowiedni typze znakiem

t mdash oznacza że że format odnosi się do argumentu typu wskaźnik na ptrdiff t lub odpowiednityp bez znaku

Dla formatoacutew a e f i g można użyć modyfikatoroacutew rozmiaru

l ktoacutery oznacza że format odnosi się do argumenty typu wskaźnik na double lub

L ktoacutery oznacza że format odnosi się do argumentu typu wskaźnik na long double

Dla formatoacutew c s i [ modyfikator l oznacza że format odnosi się do argumentu typu wskaźnik nawchar t

C6 SCANF 201

Format

Funkcje z rodziny scanf obsługują następujące formaty

d i odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przywywołaniufunkcji strtol z argumentem base roacutewnym odpowiednio dla d lub dla i argument powinienbyć wskaźnikiem na int

o u x odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przy wy-wołaniu funkcji strtoul z argumentem base roacutewnym odpowiednio dla o dla u lub dla xargument powinien być wskaźnikiem na unsigned int

a e f g odczytuje liczbę rzeczywistą nieskończoność lub NaN ktoacuterych format jest taki sam jakoczekiwany przy wywołaniu funkcji strtod argument powinien być wskaźnikiem na float

c odczytuje dokładnie tyle znakoacutew ile określono w maksymalnym rozmiarze pola (domyślnie )argument powinien być wskaźnikiem na char

s odczytuje sekwencje znakoacutew nie będących białymi znakami argument powinien być wskaźni-kiem na char

[ odczytuje niepusty ciąg znakoacutew z ktoacuterych każdymusi należeć do określonego zbioru argumentpowinien być wskaźnikiem na char

p odczytuje sekwencje znakoacutew zależną od implementacji odpowiadającą ciągowi wypisywa-nemu przez funkcję printf gdy podano sekwencję p argument powinien być typu wskaźnikna wskaźnik na void

n nie odczytuje żadnych znakoacutew ale zamiast tego zapisuje do podanej zmiennej liczbę odczyta-nych do tej pory znakoacutew argument powinien być typu wskaźnik na int

Słoacutewko więcej o formacie [ Po otwierającym nawiasie następuje ciąg określający znaki jakie mogąwystępować w odczytanym napisie i kończy się on nawiasem zamykającym tj ] Znaki pomiędzynawiasami (tzw scanlist) określają możliwe znaki chyba że pierwszym znakiem jest ˆ mdash woacutewczasw odczytanym ciągu znakoacutew mogą występować znaki nie występujące w scanlist Jeżeli sekwencjazaczyna się od [] lub [ˆ] to ten pierwszy nawias zamykający nie jest traktowany jako koniec sekwencjitylko jak zwykły znak Jeżeli wewnątrz sekwencji występuje znak - (minus) ktoacutery nie jest pierwszymlub drugim jeżeli pierwszym jest ˆ ani ostatnim znakiem zachowanie jest zależne od implementacji

Formaty A E F G i X są roacutewnież dopuszczalne i mają takie same działanie jak a e f g i x

C65 Wartość zwracanaFunkcja zwraca EOF jeżeli nastąpi koniec danych lub błąd odczytu zanim jakiekolwiek konwersje zo-staną dokonane lub liczbę poprawnie wczytanych poacutel (ktoacutera może być roacutewna zero)

202 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Dodatek D

Składnia

D1 Symbole i słowa kluczoweJęzyk C definiuje pewną ilość słoacutew za pomocą ktoacuterych tworzy się np pętle itp Są to tzw słowakluczowe tzn nie można użyć ich jako nazwy zmiennej czy też stałej (o nich poniżej) Oto lista słoacutewkluczowych języka C (według norm ANSI C z roku oraz ISO C z roku )

203

204 DODATEK D SKŁADNIA

Tablica D1 Symbole i słowa kluczoweSłowo Opis w tym podręcznikuauto Zmiennebreak Instrukcje sterującecase Instrukcje sterującear Zmienneconst Zmienne

continue Instrukcje sterującedefault Instrukcje sterujące

do Instrukcje sterującedouble Zmienneelse Instrukcje sterująceenum Typy złożoneextern Bibliotekifloat Zmiennefor Instrukcje sterującegoto Instrukcje sterująceif Instrukcje sterująceint Zmiennelong Zmienne

register Zmiennereturn Procedury i funkcjeshort Zmiennesigned Zmiennesizeof Zmiennestatic Biblioteki Zmiennestruct Typy złożoneswit Instrukcje sterującetypedef Typy złożoneunion Typy złożone

unsigned Zmiennevoid Wskaźniki

volatile Zmiennewhile Instrukcje sterujące

D2 POLSKIE ZNAKI 205

Specyfikacja ISO C z roku dodaje następujące słowa

Bool

Complex

Imaginary

inline

restrict

D2 Polskie znakiPisząc program możemy stosować polskie litery (tj ldquoąćęłńoacuteśźżrdquo) tylko w

komentarzach

ciągach znakoacutew (łańcuchach)

Niedopuszczalne jest stosowanie polskich znakoacutew w innych miejscach

D3 Operatory

D31 Operatory arytmetyczneSą to operatory wykonujące znane wszystkim dodawanie odejmowanie itp

operator znaczenie+ dodawanie- odejmowanie mnożenie dzielenie dzielenie modulo mdash daje w wyniku samą resztę z dzielenia= operator przypisania mdash wykonuje działanie po prawej stronie i wynik

przypisuje obiektowi po lewej

D32 Operatory logiczneSłużą poroacutewnaniu zawartości dwoacutech zmiennych według określonych kryterioacutew

Operator Rodzaj poroacutewnania== czy roacutewnegt większygt= większy bądź roacutewnylt mniejszylt= mniejszy bądź roacutewny= czy roacuteżny(nieroacutewny)

Są jeszcze operatory służące do grupowania poroacutewnań (patrz też logika w Wikipedii)

|| lub(OR)ampamp ioraz(AND) negacja(NOT)

206 DODATEK D SKŁADNIA

D33 Operatory binarne

Są to operatory ktoacutere działają na bitach

operator funkcja przykład| suma bitowa(OR) 5 | 2 da w wyniku 7 ( 00000101 OR 00000010 =

00000111)amp iloczyn bitowy 7 amp 2 da w wyniku 2 ( 00000111 AND 00000010

= 00000010)~ negacja bitowa 2 da wwyniku 253 (NOT 00000010 = 11111101

)gtgt przesunięcie bitoacutew o X w prawo 7 gtgt 2 da w wyniku 1 ( 00000111 gtgt 2 =

00000001)ltlt przesunięcie bitoacutew o X w lewo 7 ltlt 2 da w wyniku 28 ( 00000111 ltlt 2 =

00011100)^ alternatywa wyłączna 7 ˆ 2 da w wyniku 5 ( 00000111 ˆ 00000010 =

00000101)

D34 Operatory inkrementacjidekrementacji

Służą do dodawaniaodejmowania od liczby wartości jeden

Przykłady

Operacja Opis operacji Wartość wyrażeniax++ zwiększy wartość w x o jeden wartość zmiennej x przed zmianą++x zwiększy wartość w x o jeden wartość zmiennej x powiększona o jedenxndash zmniejszy wartość w x o jeden wartość zmiennej x przed zmianąndashx zmniejszy wartość w x o jeden wartość zmiennej x pomniejszona o jeden

Parę przykładoacutew dla zrozumienia

int a=7if ((a++)==7) najpierw poroacutewnuje potem dodaje

printf (dna) wypisze 8 if ((++a)==9) najpierw dodaje potem poroacutewnuje

printf (dn a) wypisze 9

Analogicznie ma się sytuacja z operatorami dekrementacji

D4 TYPY DANYCH 207

D35 PozostałeOperacja Opis operacji Wartość wyrażenia

x operator wyłuskania dla wskaźnika wartość trzymana w pamięci pod adre-sem przechowywanym we wskaźniku

ampx operator pobrania adresu zwraca adres zmiennejx[a] operator wybrania elementu tablicy zwraca element tablicy o indeksie a

(numerowanym od zera)xa operator wyboru składnika a ze zmien-

nej xwybiera składnik ze struktury lub unii

x-gta operator wyboru składnika a przezwskaźnik do zmiennej x

wybiera składnik ze struktury gdy uży-wamy wskaźnika do struktury zamiastzwykłej zmiennej

sizeof (typ) operator pobrania rozmiaru typu zwraca rozmiar typu w bajtachsizeof wyrażenie operator pobrania rozmiaru typu zwraca rozmiar typu rezultatu wyraże-

nia

D36 Operator ternarnyIstnieje jeden operator przyjmujący trzy argumenty mdash jest to operator wyrażenia warunkowego a b c Zwraca on b gdy a jest prawdą lub c w przeciwnym wypadku

D4 Typy dany

Tablica D Typy danych według roacuteżnych specyfikacji języka C

Typ Opis Inne nazwyTypy dany wg norm C i C

ar Służy głoacutewnie do przechowywania znakoacutew Od kom-pilatora zależy czy jest to liczba ze znakiem czy bez wwiększości kompilatoroacutew jest liczbą ze znakiem

signed ar Typ char ze znakiemunsigned ar Typ char bez znakushort Występuje gdy docelowa maszyna wyszczegoacutelnia

kroacutetki typ danych całkowitych w przeciwnym wy-padku jest tożsamy z typem int Często ma rozmiarjednego słowa maszynowego

short int signed shortsigned short int

unsigned short Liczba typu short bez znaku Podobnie jak short uży-wana do zredukowania zużycia pamięci przez program

unsigned short int

int Liczba całkowita odpowiadająca podstawowemu roz-miarowi liczby całkowitej w danym komputerze Pod-stawowy typ dla liczb całkowitych

signed int signed

unsigned Liczba całkowita bez znaku unsigned intlong Długa liczba całkowita long int signed long

signed long intunsigned long Długa liczba całkowita bez znaku unsigned long intfloat Podstawowy typ do przechowywania liczb zmienno-

przecinkowych W nowszym standardzie zgodny jest znormą IEEE Nie można stosować go z modyfika-torem signed ani unsigned

double Liczba zmiennoprzecinkowa podwoacutejnej precyzji Po-dobnie jak float nie łączy się z modyfikatorem signedani unsigned

208 DODATEK D SKŁADNIA

long double Największa możliwa dokładność liczb zmiennoprzecin-kowych Nie łączy się z modyfikatorem signed aniunsigned

Typy dany według normy CBool Przechowuje wartości lub long long Nowy typ umożliwiający obliczeniach na bardzo du-

żych liczbach całkowitych bez użycia typu floatlong long int signedlong long signed longlong int

unsigned long long Długie liczby całkowite bez znaku unsigned long long intfloat Complex Słuzy do przechowywania liczb zespolonychdouble Complex Słuzy do przechowywania liczb zespolonychlong double Complex Słuzy do przechowywania liczb zespolonych

Typy dany definiowane przez użytkownikastruct Więcej o kompilowaniuunion Rozmiar typu jest taki jak rozmiar największego polatypedef Nowo zdefiniowany typ przyjmuje taki sam rozmiar

jak typ macierzystyenum Zwykle elementy mają taką samą długość jak typ int

Zależności rozmiaru typoacutew danych są następujące

sizeof(cokolwiek) = sizeof(signed cokolwiek) = sizeof(unsigned cokolwiek)

= sizeof(ar) le sizeof(short) le sizeof(int) le sizeof(long) le sizeof(long long)

sizeof(float) le sizeof(double) le sizeof(long double)

sizeof(cokolwiek Complex) = sizeof(cokolwiek)

sizeof(void ) = sizeof(ar ) ge sizeof(cokolwiek )

sizeof(cokolwiek ) = sizeof(signed cokolwiek ) = sizeof(unsigned cokolwiek )

sizeof(cokolwiek ) = sizeof(const cokolwiek )

Dodatkowo jeżeli przez V(typ) oznaczymy liczbę bitoacutew wykorzystywanych w typie to zachodzi

le V(ar) = V(signed ar) = V(unsigned ar)

le V(short) = V(unsigned short)

le V(int) = V(unsigned int)

le V(long) = V(unsigned long)

le V(long long) = V(unsigned long long)

V(ar) le V(short) le V(int) le V(long) le V(long long)

Dodatek E

Przykłady z komentarzem

E01 Liczby losowePoniższy program generuje wiersz po wierszu macierz o określonych przez użytkownika wymiarachzawierającą losowo wybrane liczby Każdy wygenerowany wiersz macierzy zapisywany jest w plikutekstowym o wprowadzonej przez użytkownika nazwie W pierwszym wierszu pliku wynikowego za-pisano wymiary utworzonej macierzy Program napisany i skompilowany został w środowisku GNU-Linux

include ltstdiohgtinclude ltstdlibhgt dla funkcji rand() oraz srand() include lttimehgt dla funkcji [time()

main()

int i j n mfloat reFILE fpchar fileName[128]

printf(Wprowadz nazwe pliku wynikowegon)scanf(sampfileName)

printf(Wprowadz po sobie liczbe wierszy i kolumn macierzy oddzielone spacjąn)scanf(d d ampn ampm)

jeżeli wystąpił błąd w otwieraniu pliku i go nie otwartowoacutewczas funkcja fclose(fp) wywołana na końcu programu zgłosi błądwykonania i wysypie nam program z działania stąd musimy umieścićwarunek ktoacutery w kontrolowany sposoacuteb zatrzyma program (funkcja exit)

if ( (fp = fopen(fileName w)) == NULL )

puts(Otwarcie pliku nie jest mozliwe)exit jeśli w procedurze glownej

to piszemy bez nawiasow

else puts(Plik otwarty prawidłowo)

209

210 DODATEK E PRZYKŁADY Z KOMENTARZEM

fprintf(fp d dn n m) w pierwszym wierszu umieszczono wymiary macierzy

srand( (unsigned int) time(0) )for (i=1 ilt=n ++i)

for (j=1 jlt=m ++j)re = ((rand() 200)-100) 100fprintf(fp1f re )if (j=m) fprintf(fp )

fprintf(fpn)fclose(fp)return 0

E02 Zamiana liczb dziesiętny na liczby w systemie dwoacutejkowym

Zajmijmy się teraz innym zagadnieniem Wiemy że komputer zapisuje wszystkie liczby w postacibinarnej (czyli za pomocą jedynek i zer) Sproacutebujmy zatem zamienić liczbę zapisaną w ldquonaszymrdquo dzie-siątkowym systemie na zapis binarny Uwaga Program działa jedynie dla liczb od do maksymalnejwartości ktoacuterą może przyjąć typ unsigned short int w twoim kompilatorze

include ltstdiohgtinclude ltlimitshgt

void dectobin (unsigned short a)

int licznik

CHAR_BIT to liczba bitoacutew w bajcie licznik = CHAR_BIT sizeof(a)while (--licznik gt= 0)

putchar(((a gtgt licznik) amp 1)) 1 0)

int main ()

unsigned short a

printf (Podaj liczbę od 0 do hd USHRT_MAX)scanf (hd ampa)printf (hd(10) = a)dectobin(a)printf ((2)n)

return 0

211

E03 Zalążek przeglądarki

Zajmiemy się tym razem inną kwestią a mianowicie programowaniem sieci Jest to zagadnienie bar-dzo ostatnio popularne Nasz program będzie miał za zadanie połączyć się z serwerem ktoacuterego adresużytkownik będzie podawał jako pierwszy parametr programu wysłać zapytanie HTTP i odebrać treśćktoacuterą wyśle do nas serwer Zacznijmy może od tego że obsługa sieci jest niemal identyczna w roacuteżnychsystemach operacyjnych Na przykład między systemami z rodziny Unix oraz Windowsem roacuteżnica po-lega tylko na dołączeniu innych plikoacutew nagłoacutewkowych (dla Windowsa mdash winsockh) Przeanalizujmyzatem poniższy kod

include ltstdiohgtinclude ltstdlibhgtinclude ltstringhgtinclude ltunistdhgtinclude ltarpainethgtinclude ltsystypeshgtinclude ltnetinetinhgtinclude ltsyssockethgt

define MAXRCVLEN 512define PORTNUM 80

char query = GET HTTP11nn

int main(int argc char argv[])

char buffer[MAXRCVLEN+1]int len mysocketstruct sockaddr_in destchar host_ip = NULLif (argc = 2)

printf (Podaj adres serweran)exit (1)

host_ip = argv[1]mysocket = socket(AF_INET SOCK_STREAM 0)

destsin_family = AF_INETdestsin_addrs_addr = inet_addr(host_ip) ustawiamy adres hosta destsin_port = htons (PORTNUM) numer portu przechowuje dwubajtowa zmienna -

musimy ustalić porządek sieciowy - Big Endian memset(amp(destsin_zero) 0 8) zerowanie reszty struktury

connect(mysocket (struct sockaddr )ampdestsizeof(struct sockaddr)) łączymy się z hostem write (mysocket query strlen(query)) wysyłamy zapytanie len=read(mysocket buffer MAXRCVLEN) i pobieramy odpowiedź

buffer[len]=0

printf(Rcvd sbuffer)close(mysocket) zamykamy gniazdo return EXIT_SUCCESS

212 DODATEK E PRZYKŁADY Z KOMENTARZEM

Powyższy przykład może być odrobinę niezrozumiały dlatego przyda się kilka słoacutew wyjaśnieniaPliki nagłoacutewkowe ktoacutere dołączamy zawierają deklarację nowych dla Ciebie funkcji mdash socket() con-nect() write() oraz read() Oproacutecz tego spotkałeś się z nową strukturą mdash sockaddr in Wszystkie teobiekty są niezbędne do stworzenia połączenia

Dodatek F

Informacje o pliku i historia

F1 HistoriaTa książka została stworzona na polskojęzycznej wersji projektu Wikibooks przez autoroacutew wymie-nionych poniżej w sekcji Autorzy Najnowsza wersja podręcznika jest dostępna pod adresem httpplwikibooksorgwikiC

F2 Informacje o pliku i historia został utworzony przez Derbetha dnia listopada na podstawie wersji z listopada podręcznika na Wikibooks Wykorzystany został poprawiony program WikiLaTeX autorstwa użyt-kownika angielskichWikibooks Hagindaza Wynikowy kod po ręcznych poprawkach został przekształ-cony w książkę za pomocą systemu składu XeLaTeX Wykorzystano wolną dostępną na licencjach i czcionkę Linux Libertine oraz wolną czcionkę DejaVu Sans Mono

Najnowsza wersja tego -u jest postępna pod adresem httpplwikibooksorgwikiImageCpdf

F3 AutorzyAdam majewski Adiblol Akira Albmont Ananas Arfrever BartekChom Bercik Bla Bociex CathyRichards Cnr CzarnyInaczej CzarnyZajaczek DaniXTeam Derbeth Equadus Faw GDR GangGk Gynvael Incuś Karol Ossowski Kazet Kj Lethern MTM Marcin MastiBot MeaglinMerdis Michael Migol Mina MonteChristof Mt Myki Mythov Narf Noisy Norill PawelkgPawlosck Peter de Sowaro Piotr Pkierski Ponton Przykuta RedRad Sasek Sblive Silbarad T zielWarszk Webprog Wentuq ZiomekPL Zjem ci chleb i anonimowi autorzy

F4 GrafikiAutorzy i licencje grafik

grafika na okładce Saint-Elme Gautier rycina z książki Le Corset agrave travers les acircges Paryż źroacutedło Wikimedia Commons public domain

logoWikibooks zastrzeżony znak towarowycopy ampAll rights reserved Wikimedia FoundationInc

grafika a (strona ) autor Claudio Rocchini źroacutedło Wikimedia Commons licencja

grafika b (strona ) autor Adam majewski źroacutedło Wikimedia Commons licencja CreativeCommons Aribution Unported

213

214 DODATEK F INFORMACJE O PLIKU

grafia (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Daniel B źroacutedo Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Derrick Coetzee źroacutedło Wikimedia Commons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

Indeks

adres alternatywa

biblioteka standardowa big endian blok

Cjęzyk

dekrementacja dynamiczna alokacja pamięci

enum

funkcja definicja deklaracja rekurencyjna

inkrementacja

komentarz kompilacja

warunkowa kompilator

lista używanie

koniunkcja konwersja

libc lile endian Porownaj big endian

main makefile

napis poroacutewnywanie

negacja

operatordekrementacji inkrementacji modulo

pobrania adresu sizeof wyrażenia warunkowego wyłuskania

plikczytanie i pisanie nagłowkowy

porządek bajtoacutew prawda i fałsz preprocesor procedury prototyp funkcji przekazywanie argumentoacutew do funkcji

przez wartość przez wskaźnik

przepełnienie bufora przesunięcie bitowe

rzutowanie

sizeof stała struktura słowa kluczowe

tablica wielowymiarowa znakoacutew

typ definiowanie wyliczeniowy

unia

Valgrind void

jako typ zwracany na liście argumentoacutew void

volatile

wejściewyjście wskaźnik wyciek pamięci

215

216 INDEKS

wyroacutewnywanie

zmienna globalna lokalna statyczna

znaki specjalne

  • O podręczniku
    • O czym moacutewi ten podręcznik
    • Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika
    • Konwencje przyjęte w tym podręczniku
    • Czy mogę pomoacutec
    • Autorzy
    • Źroacutedła
      • O języku C
        • Historia C
        • Zastosowania języka C
        • Przyszłość C
          • Czego potrzebujesz
            • Czego potrzebujesz
            • Zintegrowane Środowiska Programistyczne
            • Dodatkowe narzędzia
              • Używanie kompilatora
                • GCC
                • Borland
                • Czytanie komunikatoacutew o błędach
                  • Pierwszy program
                    • Twoacutej pierwszy program
                    • Rozwiązywanie problemoacutew
                      • Podstawy
                        • Kompilacja Jak działa C
                        • Co może C
                        • Struktura blokowa
                        • Zasięg
                        • Funkcje
                        • Biblioteki standardowe
                        • Komentarze i styl
                        • Preprocesor
                        • Nazwy zmiennych stałych i funkcji
                          • Zmienne
                            • Czym są zmienne
                            • Typy zmiennych
                            • Specyfikatory
                            • Modyfikatory
                            • Uwagi
                              • Operatory
                                • Przypisanie
                                • Rzutowanie
                                • Operatory arytmetyczne
                                • Operacje bitowe
                                • Poroacutewnanie
                                • Operatory logiczne
                                • Operator wyrażenia warunkowego
                                • Operator przecinek
                                • Operator sizeof
                                • Inne operatory
                                • Priorytety i kolejność obliczeń
                                • Kolejność wyliczania argumentoacutew operatora
                                • Uwagi
                                • Zobacz też
                                  • Instrukcje sterujące
                                    • Instrukcje warunkowe
                                    • Pętle
                                    • Instrukcja goto
                                    • Natychmiastowe kończenie programu --- funkcja exit
                                    • Uwagi
                                      • Podstawowe procedury wejścia i wyjścia
                                        • Wejściewyjście
                                        • Funkcje wyjścia
                                        • Funkcja puts
                                        • Funkcja fputs
                                        • Funkcje wejścia
                                          • Funkcje
                                            • Tworzenie funkcji
                                            • Wywoływanie
                                            • Zwracanie wartości
                                            • Zwracana wartość
                                            • Funkcja main()
                                            • Dalsze informacje
                                            • Zobacz też
                                              • Preprocesor
                                                • Wstęp
                                                • Dyrektywy preprocesora
                                                • Predefiniowane makra
                                                  • Biblioteka standardowa
                                                    • Czym jest biblioteka
                                                    • Po co nam biblioteka standardowa
                                                    • Gdzie są funkcje z biblioteki standardowej
                                                    • Opis funkcji biblioteki standardowej
                                                    • Uwagi
                                                      • Czytanie i pisanie do plikoacutew
                                                        • Pojęcie pliku
                                                        • Identyfikacja pliku
                                                        • Podstawowa obsługa plikoacutew
                                                        • Rozmiar pliku
                                                        • Przykład --- pliki graficzny
                                                        • Co z katalogami
                                                          • Ćwiczenia dla początkujących
                                                            • Ćwiczenia
                                                              • Tablice
                                                                • Wstęp
                                                                • Odczytzapis wartości do tablicy
                                                                • Tablice znakoacutew
                                                                • Tablice wielowymiarowe
                                                                • Ograniczenia tablic
                                                                • Ciekawostki
                                                                  • Wskaźniki
                                                                    • Co to jest wskaźnik
                                                                    • Operowanie na wskaźnikach
                                                                    • Arytmetyka wskaźnikoacutew
                                                                    • Tablice a wskaźniki
                                                                    • Gdy argument jest wskaźnikiem
                                                                    • Pułapki wskaźnikoacutew
                                                                    • Na co wskazuje NULL
                                                                    • Stałe wskaźniki
                                                                    • Dynamiczna alokacja pamięci
                                                                    • Wskaźniki na funkcje
                                                                    • Możliwe deklaracje wskaźnikoacutew
                                                                    • Popularne błędy
                                                                    • Ciekawostki
                                                                      • Napisy
                                                                        • Łańcuchy znakoacutew w języku C
                                                                        • Operacje na łańcuchach
                                                                        • Bezpieczeństwo kodu a łańcuchy
                                                                        • Konwersje
                                                                        • Operacje na znakach
                                                                        • Częste błędy
                                                                        • Unicode
                                                                          • Typy złożone
                                                                            • typedef
                                                                            • Typ wyliczeniowy
                                                                            • Struktury
                                                                            • Unie
                                                                            • Inicjalizacja struktur i unii
                                                                            • Wspoacutelne własności typoacutew wyliczeniowych unii i struktur
                                                                            • Studium przypadku --- implementacja listy wskaźnikowej
                                                                              • Biblioteki
                                                                                • Czym jest biblioteka
                                                                                • Jak zbudowana jest biblioteka
                                                                                  • Więcej o kompilowaniu
                                                                                    • Ciekawe opcje kompilatora GCC
                                                                                    • Program make
                                                                                    • Optymalizacje
                                                                                    • Kompilacja krzyżowa
                                                                                    • Inne narzędzia
                                                                                      • Zaawansowane operacje matematyczne
                                                                                        • Biblioteka matematyczna
                                                                                        • Prezentacja liczb rzeczywistych w pamięci komputera
                                                                                        • Liczby zespolone
                                                                                          • Powszechne praktyki
                                                                                            • Konstruktory i destruktory
                                                                                            • Zerowanie zwolnionych wskaźnikoacutew
                                                                                            • Konwencje pisania makr
                                                                                            • Jak dostać się do konkretnego bitu
                                                                                            • Skroacutety notacji
                                                                                              • Przenośność programoacutew
                                                                                                • Niezdefiniowane zachowanie i zachowanie zależne od implementacji
                                                                                                • Rozmiar zmiennych
                                                                                                • Porządek bajtoacutew i bitoacutew
                                                                                                • Biblioteczne problemy
                                                                                                • Kompilacja warunkowa
                                                                                                  • Łączenie z innymi językami
                                                                                                    • Język C i Asembler
                                                                                                    • C++
                                                                                                      • Indeks alfabetyczny
                                                                                                      • Indeks tematyczny
                                                                                                        • asserth
                                                                                                        • ctypeh
                                                                                                        • errnoh
                                                                                                        • floath
                                                                                                        • limitsh
                                                                                                        • localeh
                                                                                                        • mathh
                                                                                                        • setjmph
                                                                                                        • signalh
                                                                                                        • stdargh
                                                                                                        • stddefh
                                                                                                        • stdioh
                                                                                                        • stdlibh
                                                                                                        • stringh
                                                                                                        • timeh
                                                                                                          • Wybrane funkcje biblioteki standardowej
                                                                                                            • assert
                                                                                                            • atoi
                                                                                                            • isalnum
                                                                                                            • malloc
                                                                                                            • printf
                                                                                                            • scanf
                                                                                                              • Składnia
                                                                                                                • Symbole i słowa kluczowe
                                                                                                                • Polskie znaki
                                                                                                                • Operatory
                                                                                                                • Typy danych
                                                                                                                  • Przykłady z komentarzem
                                                                                                                  • Informacje o pliku
                                                                                                                    • Historia
                                                                                                                    • Informacje o pliku PDF i historia
                                                                                                                    • Autorzy
                                                                                                                    • Grafiki
                                                                                                                      • Skorowidz
Page 4: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ

Zmienne Czym są zmienne 33 Typy zmiennych 36 Specyfikatory 39 Modyfikatory 40 Uwagi 41

Operatory Przypisanie 43 Rzutowanie 44 Operatory arytmetyczne 45 Operacje bitowe 46 Poroacutewnanie 48 Operatory logiczne 50 Operator wyrażenia warunkowego 51 Operator przecinek 51 Operator sizeof 51 Inne operatory 52 Priorytety i kolejność obliczeń 52 Kolejność wyliczania argumentoacutew operatora 53 Uwagi 54 Zobacz też 55

Instrukcje sterujące Instrukcje warunkowe 57 Pętle 60 Instrukcja goto 65 Natychmiastowe kończenie programu mdash funkcja exit 65 Uwagi 66

Podstawowe procedury wejścia i wyjścia Wejściewyjście 67 Funkcje wyjścia 68 Funkcja puts 69 Funkcja fputs 70 Funkcje wejścia 71

Funkcje Tworzenie funkcji 78 Wywoływanie 79 Zwracanie wartości 80 Zwracana wartość 81 Funkcja main() 81 Dalsze informacje 83 Zobacz też 87

Preprocesor Wstęp 89 Dyrektywy preprocesora 89 Predefiniowane makra 95

4

Biblioteka standardowa Czym jest biblioteka 97 Po co nam biblioteka standardowa 97 Gdzie są funkcje z biblioteki standardowej 97 Opis funkcji biblioteki standardowej 98 Uwagi 98

Czytanie i pisanie do plikoacutew Pojęcie pliku 99 Identyfikacja pliku 99 Podstawowa obsługa plikoacutew 99 Rozmiar pliku 102 Przykład mdash pliki graficzny 103 Co z katalogami 104

Ćwiczenia dla początkujący Ćwiczenia 105

Tablice Wstęp 107 Odczytzapis wartości do tablicy 109 Tablice znakoacutew 109 Tablice wielowymiarowe 110 Ograniczenia tablic 110 Ciekawostki 111

Wskaźniki Co to jest wskaźnik 113 Operowanie na wskaźnikach 114 Arytmetyka wskaźnikoacutew 117 Tablice a wskaźniki 118 Gdy argument jest wskaźnikiem 119 Pułapki wskaźnikoacutew 120 Na co wskazuje 120 Stałe wskaźniki 121 Dynamiczna alokacja pamięci 122 Wskaźniki na funkcje 125 Możliwe deklaracje wskaźnikoacutew 127 Popularne błędy 127 Ciekawostki 128

Napisy Łańcuchy znakoacutew w języku C 129 Operacje na łańcuchach 132 Bezpieczeństwo kodu a łańcuchy 134 Konwersje 137 Operacje na znakach 137 Częste błędy 137 Unicode 138

5

Typy złożone typedef 141 Typ wyliczeniowy 141 Struktury 142 Unie 142 Inicjalizacja struktur i unii 144 Wspoacutelne własności typoacutew wyliczeniowych unii i struktur 144 Studium przypadku mdash implementacja listy wskaźnikowej 146

Biblioteki Czym jest biblioteka 151 Jak zbudowana jest biblioteka 151

Więcej o kompilowaniu Ciekawe opcje kompilatora 155 Program make 155 Optymalizacje 157 Kompilacja krzyżowa 159 Inne narzędzia 159

Zaawansowane operacje matematyczne Biblioteka matematyczna 161 Prezentacja liczb rzeczywistych w pamięci komputera 162 Liczby zespolone 163

Powszene praktyki Konstruktory i destruktory 165 Zerowanie zwolnionych wskaźnikoacutew 166 Konwencje pisania makr 166 Jak dostać się do konkretnego bitu 167 Skroacutety notacji 168

Przenośność programoacutew Niezdefiniowane zachowanie i zachowanie zależne od implementacji 171 Rozmiar zmiennych 172 Porządek bajtoacutew i bitoacutew 172 Biblioteczne problemy 175 Kompilacja warunkowa 175

Łączenie z innymi językami Język C i Asembler 177 C++ 180

A Indeks alfabetyczny

B Indeks tematyczny B asserth 183B ctypeh 183B errnoh 183B floath 183B limitsh 183

6

B localeh 184B mathh 184B setjmph 185B signalh 185B stdargh 185B stddefh 185B stdioh 185B stdlibh 186B stringh 186B timeh 186

C Wybrane funkcje biblioteki standardowej C assert 189C atoi 190C isalnum 191C malloc 193C printf 195C scanf 199

D Składnia D Symbole i słowa kluczowe 203D Polskie znaki 205D Operatory 205D Typy danych 207

E Przykłady z komentarzem

F Informacje o pliku F Historia 213F Informacje o pliku i historia 213F Autorzy 213F Grafiki 213

Skorowidz

7

8

Spis tablic

Priorytety operatoroacutew 53

D Symbole i słowa kluczowe 204D Typy danych według roacuteżnych specyfikacji języka C 207

9

Rozdział 1

O podręczniku

11 O czym moacutewi ten podręcznikNiniejszy podręcznik stanowi przewodnik dla początkujących programistoacutew po języku pro-gramowania C

12 Co trzeba wiedzieć żeby skorzystać z niniejszego pod-ręcznika

Ten podręcznik ma nauczyć programowania w C od podstaw do poziomu zaawansowanegoDo zrozumienia rozdziału dla początkujących wymagana jest jedynie znajomość podstawo-wych pojęć z zakresu algebry oraz terminoacutew komputerowych Doświadczenie w programo-waniu w innych językach bardzo pomaga ale nie jest konieczne

13 Konwencje przyjęte w tym podręcznikuInformacje ważne oznaczamy w następujący sposoacuteb

Ważna informacja

Dodatkowe informacje ktoacutere odrobinę wykraczają poza zakres podręcznika a także wy-jaśniają kwestie niezwiązane bezpośrednio z językiem C oznaczamy tak

Wyjaśnienie

Ponadto kod w języku C będzie prezentowany w następujący sposoacuteb

include ltstdiohgt

int main (int argc char argv[])

return 0

11

12 ROZDZIAŁ 1 O PODRĘCZNIKU

Innego rodzaju przykłady dialog użytkownika z konsolą i programem wejście wyjścieprogramu informacje teoretyczne będą wyglądały tak

typ zmienna = wartość

14 Czy mogę pomoacutecOczywiście że możesz Mało tego będziemy zadowoleni z każdej pomocy ndash możesz pisaćrozdziały lub tłumaczyć je z angielskiej wersji tego podręcznika Nie musisz pytać się nikogoo zgodę mdash jeśli chcesz możesz zacząć już teraz Prosimy jedynie o zapoznanie się ze stylempodręcznika użytymi w nim szablonami i zachowanie układu rozdziałoacutew Propozycje zmianyspisu treści należy zgłaszać na stronie dyskusji

Jeśli znalazłeś jakiś błąd a nie umiesz go poprawić koniecznie powiadom o tym fakcieautoroacutew tego podręcznika za pomocą strony dyskusji danego modułu książki Dzięki temuprzyczyniasz się do rozwoju tego podręcznika

15 AutorzyIstotny wkład w powstanie podręcznika mają

CzarnyZajaczek

Derbeth

Kj

mina

Dodatkowo w rozwoju podręcznika pomagali między innymi

Lrds

Noisy

16 Źroacutedła podręcznik C Programming na anglojęzycznej wersji Wikibooks licencja GFDL

Brian W Kernighan Dennis M Ritchie Język ANSI C

ISO C Commiee Dra stycznia

Bruce Eckel inking in C++ Rozdział Język C w programie C++

Rozdział 2

O języku C

Zobacz w Wikipedii C (ję-zyk programowania)C jest językiem programowania wysokiego poziomu Jego nazwę interpretuje się jako na-

stępną literę po B (nazwa jego poprzednika) lub drugą literę języka BCPL (poprzednik językaB)

21 Historia CW roku trzej naukowcy z Bell Telephone Laboratories mdashWilliam Shockley Walter Brat-tain i John Bardeen mdash stworzyli pierwszy tranzystor w roku w MIT skonstruowanopierwszy komputer oparty wyłącznie na tranzystorach TX-O w roku Jack Kilby z Te-xas Instruments skonstruował układ scalony Ale zanim powstał pierwszy układ scalonypierwszy język wysokiego poziomu został już napisany

W powstał Fortran (Formula Translator) ktoacutery zapoczątkował napisanie języka For-tran I () Poacuteźniej powstały kolejno

Algol mdash Algorithmic Language w r

Algol ()

CPL mdash Combined Programming Language ()

BCPL mdash Basic CPL ()

B ()

i C w oparciu o BB został stworzony przez Kena ompsona z Bell Labs był to język interpretowany uży-

wany we wczesnych wewnętrznych wersjach systemu operacyjnego UNIX Inni pracownicyBell Labs ompson i Dennis Richie rozwinęli B nazywając go NB dalszy rozwoacutej NB dał Cmdash język kompilowany Większa część UNIX-a została ponownie napisana w NB a następniew C co dało w efekcie bardziej przenośny system operacyjny W roku wydana zostałaksiążka pt ldquoe C Programming Languagerdquo ktoacutera stała się pierwszym podręcznikiem donauki języka C

Możliwość uruchamiania UNIX-a na roacuteżnych komputerach była głoacutewną przyczyną po-czątkowej popularności zaroacutewno UNIX-a jak i C zamiast tworzyć nowy system operacyjnyprogramiści mogli po prostu napisać tylko te części systemu ktoacuterych wymagał inny sprzętoraz napisać kompilator C dla nowego systemu Odkąd większa część narzędzi systemowychbyła napisana w C logiczne było pisanie kolejnych w tym samym języku

13

14 ROZDZIAŁ 2 O JĘZYKU C

Kilka z obecnie powszechnie stosowanych systemoacutew operacyjnych takich jak Linux Mi-croso Windows zostały napisane w języku C

211 Standaryzacje

W roku Ritchie i Kerninghan opublikowali pierwszą książkę nt języka C mdash ldquoe CProgramming Languagerdquo Owa książka przez wiele lat była swoistym ldquowyznacznikiemrdquo jakprogramować w języku C Była więc to niejako pierwsza standaryzacja nazywana od na-zwisk twoacutercoacutew ldquoKampRrdquo Oto nowości wprowadzone przez nią do języka C w stosunku dojego pierwszych wersji (pochodzących z początku lat )

możliwość tworzenia struktur (słowo struct)

dłuższe typy danych (modyfikator long)

liczby całkowite bez znaku (modyfikator unsigned)

zmieniono operator ldquo=+rdquo na ldquo+=rdquo

Ponadto producenci kompilatoroacutew (zwłaszcza ATampT) wprowadzali swoje zmiany nieob-jęte standardem

funkcje nie zwracające wartości (void) oraz typ void

funkcje zwracające struktury i unie

przypisywanie wartości strukturom

wprowadzenie słowa kluczowego const

utworzenie biblioteki standardowej

wprowadzenie słowa kluczowego enum

Owe nieoficjalne rozszerzenia zagroziły spoacutejności języka dlatego też powstał standardregulujący wprowadzone nowinki Od roku trwały prace standaryzacyjne aby w roku wydać standard C (poprawna nazwa to ANSI X-) Niektoacutere zmiany wpro-wadzono z języka C++ jednak rewolucję miał dopiero przynieść standard C ktoacutery wpro-wadził min

funkcje inline

nowe typy danych (np long long int)

nowy sposoacuteb komentowania zapożyczony od C++ ()

przechowywanie liczb zmiennoprzecinkowych zostało zaadaptowane do norm IEEE

utworzono kilka nowych plikoacutew nagłoacutewkowych (stdboolh inypesh)

Na dzień dzisiejszy normą obowiązującą jest norma C

22 ZASTOSOWANIA JĘZYKA C 15

22 Zastosowania języka CJęzyk C został opracowany jako strukturalny język programowania do celoacutew ogoacutelnych Przezcałą swą historię (czyli ponad lat) służył do tworzenia przeroacuteżnych programoacutewmdash od syste-moacutew operacyjnych po programy nadzorujące pracę urządzeń przemysłowych C jako językdużo szybszy od językoacutew interpretowanych (Perl Python) oraz uruchamianych w maszy-nach wirtualnych (np C Java) może bez problemu wykonywać złożone operacje nawetwtedy gdy nałożone są dość duże limity czasu wykonywania pewnych operacji Jest on przytym bardzo przenośny mdash może działać praktycznie na każdej architekturze sprzętowej podwarunkiem opracowania odpowiedniego kompilatora Często wykorzystywany jest takżedo oprogramowywania mikrokontroleroacutew i systemoacutew wbudowanych Jednak w niektoacuterychsytuacjach język C okazuje się być mało przydatny zwłaszcza chodzi tu o obliczenia mate-matyczne wymagające dużej precyzji (w tej dziedzinie znakomicie spisuje się Fortran) lubteż dużej optymalizacji dla danego sprzętu (wtedy niezastąpiony jest język asemblera)

Kolejną zaletą C jest jego dostępność mdash właściwie każdy system typu UNIX posiada kom-pilator C w C pisane są funkcje systemowe

Problemem w przypadku C jest zarządzanie pamięcią ktoacutere nie wybacza programiściebłędoacutew niewygodne operowanie napisami i niestety pewna liczba ldquokruczkoacutewrdquo ktoacutere mogązaskakiwać nowicjuszy Na tle młodszych językoacutew programowania C jest językiem dosyćniskiego poziomu więc wiele rzeczy trzeba w nim robić ręcznie jednak zarazem umożliwia torobienie rzeczy nieprzewidzianych w samym języku (np implementację liczb bitowych)a także łatwe łączenie C z Asemblerem

23 Przyszłość CPomimo sędziwego już wieku (C ma ponad lat) nadal jest on jednym z najczęściej stosowa-nych językoacutew programowania Doczekał się już swoich następcoacutew z ktoacuterymi w niektoacuterychdziedzinach nadal udaje mu się wygrywać Widać zatem że pomimo pozornej prostoty iniewielkich możliwości język C nadal spełnia stawiane przed nim wymagania Warto zatemuczyć się języka C gdyż nadal jest on wykorzystywany (i nic nie wskazuje na to by miałosię to zmienić) a wiedza ktoacuterą zdobędziesz ucząc się C na pewno się nie zmarnuje Skład-nia języka C pomimo że przez wielu uważana za nieczytelną stała się podstawą dla takichjęzykoacutew jak C++ C czy też Java

16 ROZDZIAŁ 2 O JĘZYKU C

Rozdział 3

Czego potrzebujesz

31 Czego potrzebujeszWbrew powszechnej opinii nauczenie się ktoacuteregoś z językoacutew programowania (w tym językaC) nie jest takie trudne Do nauki wystarczą Ci

komputer z dowolnym systemem operacyjnym takim jak FreeBSD Linux Windows

Język C jest bardzo przenośny więc będzie działał właściwie na każdej platformiesprzętowej i w każdym nowoczesnym systemie operacyjnym

kompilator języka C

Kompilator języka C jest programem ktoacutery tłumaczy kod źroacutedłowy napisany przeznas do języka asembler a następnie do postaci zrozumiałej dla komputera (maszynycyfrowej) czyli do postaci ciągu zer i jedynek ktoacutere sterują pracą poszczegoacutelnych ele-mentoacutew komputera Kompilator języka C można dostać za darmo Przykładem sągcc pod systemy uniksowe DJGPP pod systemy DOS MinGW oraz lcc pod systemytypu Windows Jako kompilator C może dobrze służyć kompilator języka C++ (roacuteżnicemiędzy tymi językami przy pisaniu prostych programoacutew są nieistotne) Spokojnie mo-żesz więc użyć na przykład Microso Visual C++reg lub kompilatoroacutew firmy BorlandJeśli lubisz eksperymentować wyproacutebuj Tiny C Compiler bardzo szybki kompilatoro ciekawych funkcjach Możesz ponadto wyproacutebować interpreter języka C Więcejinformacji na Wikipedii

Linker (często jest razem z kompilatorem)

Linker jest to program ktoacutery uruchamiany jest po etapie kompilacji jednego lub kilkuplikoacutew źroacutedłowych (pliki z rozszerzeniem c cpp lub innym) skompilowanych do-wolnym kompilatorem Taki program łączy wszystkie nasze skompilowane pliki źroacute-dłowe i inne funkcje (np printf scan) ktoacutere były użyte (dołączone do naszego pro-gramu poprzez użycie dyrektywy include) w naszym programie a nie były zdefinio-wane(napisane przez nas) w naszych plikach źroacutedłowych lub nagłoacutewkowych Linkerjest to czasami jeden program połączony z kompilatorem Wywoływany jest on naogoacuteł automatycznie przez kompilator w wyniku czego dostajemy gotowy program douruchomienia

Debuger (opcjonalnie według potrzeb)

17

18 ROZDZIAŁ 3 CZEGO POTRZEBUJESZ

Debugger jest to program ktoacutery umożliwia prześledzenie(określenie wartości poszcze-goacutelnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijcewykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu Używasię go w celu określenia czemu nasz program nie działa po naszej myśli lub czemu pro-gram niespodziewanie kończy działanie bez powodu Aby użyć debuggera kompilatormusi dołączyć kod źroacutedłowy do gotowego skompilowanego programu Przykładowymidebuggerami są gdb pod Linuksem lub debugger firmy Borland pod Windowsa

edytora tekstowego

Systemy uniksowe oferują wiele edytoroacutew przydatnych dla programisty jak choćbyvim i Emacs w trybie tekstowym Kate w KDE czy gedit w GNOME Windows maedytor całkowicie wystarczający do pisania programoacutew w C mdash nieśmiertelny Notatnikmdash ale z łatwością znajdziesz w Internecie wiele wygodniejszych narzędzi takich jak npNotepad++ Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE

dużo chęci i dobrej motywacji

32 Zintegrowane Środowiska ProgramistyczneZamiast osobnego kompilatora i edytora możesz wybrać Zintegrowane Środowisko Progra-mistyczne (Integrated Development Environment IDE) IDE jest zestawem wszystkich pro-gramoacutew ktoacutere potrzebuje programista najczęściej z interfejsem graficznym IDE zawierakompilator linker i edytor z reguły roacutewnież debugger

Bardzo popularny IDE to płatny (istnieje także jego darmowa wersja) Microso VisualC++ (MS VC++) popularne darmowe IDE to np

CodeBlocks dla Windows jak i Linux dostępny na stronie wwwcodeblocksorg

KDevelop (Linux) dla KDE

NetBeans multiplatformowy darmowy do ściągnięcia na stronie wwwnetbeansorg

Eclipse z wtyczką CDT (wspoacutełpracuje z MinGW i GCC)

Borland C++ Builder dostępny za darmo do użytku prywatnego

Xcode dlaMac OS X i nowszy kompatybilny z procesorami PowerPC i Intel (moż-liwość stworzenia Universal Binary)

Geany dla systemoacutewWindows i Linux wspoacutełpracuje zMinGW iGCCwwwgeanyorg

Pelles C wwwsmorgasbordetcom

Dev-C++ dla Windows dostępny na stronie wwwbloodshednet

33 Dodatkowe narzędziaWśroacuted narzędzi ktoacutere nie są niezbędne ale zasługują na uwagę można wymienić Valgrindandash specjalnego rodzaju debugger Valgrind kontroluje wykonanie programu i wykrywa nie-prawidłowe operacje w pamięci oraz wycieki pamięci Użycie Valgrinda jest proste mdash kom-pilujemy program jak do debugowania następnie podajemy jako argument Valgrindowi

Rozdział 4

Używanie kompilatora

Język C jest językiem kompilowanym co oznacza że potrzebuje specjalnego programu mdashkompilatora mdash ktoacutery tłumaczy kod źroacutedłowy pisany przez człowieka na język rozkazoacutew da-nego komputera W skroacutecie działanie kompilatora sprowadza się do czytania tekstowegopliku z kodem programu raportowania ewentualnych błędoacutew i produkowania pliku wyniko-wego

Kompilator uruchamiamy ze Zintegrowanego Środowiska Programistycznego lub z kon-soli (linii poleceń) Przejść do konsoli można dla systemoacutew typu UNIX w trybie graficz-nym użyć programoacutew gnome-terminal konsole albo xterm w Windows ldquoWiersz poleceniardquo(można go znaleźćwmenuAkcesoria albo uruchomićwpisującw Start -gtUruchom ldquocmdrdquo)

41 GCCZobacz w Wikipedii GCC

GCC jest to darmowy zestaw kompilatoroacutew min języka C rozwijany w ramach projektuGNU Dostępny jest on na dużą ilość platform sprzętowych obsługiwanych przez takie sys-temy operacyjne jak AIX BSD Linux Mac OS X SunOS Windows Na niektoacuterych sys-temach (np Windows) nie jest on jednak dostępny automatycznie Należy zainstalowaćodpowiednie narzędza (poprzedni rozdział)

Aby skompilować kod języka C za pomocą kompilatora GCC napisany wcześniej w do-wolnym edytorze tekstu należy uruchomić program z odpowiednimi parametrami Podsta-wowym parametrem ktoacutery jest wymagany jest nazwa pliku zawierającego kod programuktoacutery chcemy skompilować

gcc kodc

Rezultatem kompilacji będzie plik wykonywalny z domyślną nazwą (w systemach Unixjest to ldquoaoutrdquo) Jest to metoda niezalecana ponieważ jeżeli skompilujemy w tym samymkatalogu kilka plikoacutew z kodem kolejne pliki wykonywalne zostaną nadpisane i w rezultacieotrzymamy tylko jeden (ten ostatni) skompilowany kod Aby wymusić na GCC nazwę plikuwykonywalnego musimy skorzystać z parametru ldquo-o ltnazwagtrdquo

gcc -o program kodc

W rezultacie otrzymujemy plik wykonywalny o nazwie programPracując nad złożonym programem składającym się z kilku plikoacutew źroacutedłowych (c) mo-

żemy skompilować je niezależnie od siebie tworząc tak zwane pliki typu obiekt z rozszerze-niem o (ang Object File) Następnie możemy stworzyć z nich jednolity program w procesie

19

20 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

konsolidacji (linkowaniu) Jest to bardzo wygodne i praktyczne rozwiązanie ze względu nato iż nie jesteśmy zmuszeni kompilować wszystkich plikoacutew tworzących program za każdymrazem na nowo a jedynie te w ktoacuterych wprowadziliśmy zmiany Aby skompilować plik bezlinkowania używamy parametru ldquo-c ltplikgtrdquo

gcc -o program1o -c kod1cgcc -o program2o -c kod2c

Otrzymujemy w ten sposoacuteb pliki typu obiekt programo i programo A następnie two-rzymy z nich program głoacutewny

gcc -o program program1o program2o

Możemy użyć roacutewnież flag min aby włączyć dokładne rygorystyczne sprawdzanie na-pisanego kodu (co może być przydatne jeśli chcemy dążyć do perfekcji) używamy przełącz-nikoacutew

gcc kodc -o program -Werror -Wall -W -pedantic -ansi

Więcej informacji na temat parametroacutew i działania kompilatora GCC można znaleźć na

Strona domowa projektu GNU GCC

Kroacutetki przekrojowy opis GCC po polsku

Strona podręcznika systemu UNIX (man)

42 BorlandZobacz podręcznik Borland C++ Compiler

43 Czytanie komunikatoacutew o błędaJedną z najbardziej podstawowych umiejętności ktoacutere musi posiąść początkujący progra-mista jest umiejętność rozumienia komunikatoacutew o roacuteżnego rodzaju błędach ktoacutere sygnali-zuje kompilator Wszystkie te informacje pomogą Ci szybko wychwycić ewentualne błędy(ktoacuterych na początku zawsze jest bardzo dużo) Nie martw się że na początku dość częstobędziesz oglądał wydruki błędoacutew zasygnalizowanych przez kompilator mdash nawet zaawanso-wanym programistom się to zdarza Kompilator ma za zadanie pomoacutec Ci w szybkiej popra-wie ewentualnych błędoacutew dlatego też umiejętność analizy komunikatoacutew o błędach jest takważna

431 GCC

Kompilator jest w stanie wychwycić błędy składniowe ktoacutere z pewnością będziesz popełniałKompilator GCC wyświetla je w następującej formie

nazwa_plikucnumer_linijkiopis błędu

Kompilator dość często podaje także nazwę funkcji w ktoacuterej wystąpił błąd Przykładowobłąd deklaracji zmiennej w pliku testc

43 CZYTANIE KOMUNIKATOacuteW O BŁĘDACH 21

include ltstdiohgt

int main ()

intr rprintf (dn r)

Spowoduje wygenerowanie następującego komunikatu o błędzie

testc In function lsquomainrsquotestc5 error lsquointrrsquo undeclared (first use in this function)testc5 error (Each undeclared identifier is reported only oncetestc5 error for each function it appears in)testc5 error syntax error before lsquorrsquotestc6 error lsquorrsquo undeclared (first use in this function)

Co widzimy w raporcie o błędach W linii użyliśmy nieznanego (undeclared) identy-fikatora intr mdash kompilator moacutewi że nie zna tego identyfikatora jest to pierwsze użycie wdanej funkcji i że więcej nie ostrzeże o użyciu tego identyfykatora w tej funkcji Ponieważintr nie został rozpoznany jako żaden znany typ linijka intr r nie została rozpoznana jakodeklaracja zmiennej i kompilator zgłasza błąd składniowy (syntax error) W konsekwencji rnie zostało rozpoznane jako zmienna i kompilator zgłosi to jeszcze w następnej linijce gdzieużywamy r

22 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

Rozdział 5

Pierwszy program

51 Twoacutej pierwszy program

Przyjęło się że pierwszy program napisany w dowolnym języku programowania powinienwyświetlić tekst ldquoHello Worldrdquo (Witaj Świecie) Zauważ że sam język C nie ma żadnychmechanizmoacutew przeznaczonych do wprowadzania i wypisywania danych mdash musimy zatemskorzystać z odpowiadających za to funkcji mdash w tym przypadku printf zawartej w standar-dowej bibliotece C (ang C Standard Library) (podobnie jak w Pascalu używa się do tegoprocedur Pascalowskim odpowiednikiem funkcji printf są procedury writewriteln)

W języku C deklaracje funkcji zawarte są w plika nagłoacutewkowy posiadających naj-częściej rozszerzenie h choć można także spotkać rozszerzenie hpp przy czym to drugiezwykło się stosować w języku C++ (rozszerzenie nie ma swych ldquotechnicznychrdquo korzeni mdash jestto tylko pewna konwencja) W celu umieszczenia w swoim kodzie pewnego pliku nagłoacutewko-wego używamy dyrektywy kompilacyjnej include Przed procesem kompilacji w miejscetej dyrektywy wstawiana jest treść podanego pliku nagłoacutewkowego dostarczając deklaracjifunkcji

Poniższy przykład obrazuje jak przy użyciu dyrektywy include umieścimyw kodzie plikstandardowej biblioteki C stdioh (Standard InputOutputHeaderfile) zawierającą definicjęfunkcji printf

include ltstdiohgt

W nawiasach troacutejkątnych lt gt umieszcza się nazwy standardowych plikoacutew nagłoacutewko-wych1 Żeby włączyć inny plik nagłoacutewkowy (np własny) znajdujący się w katalogu z kodemprogramu trzeba go wpisać w cudzysłoacutew

include moacutej_plik_nagłoacutewkowyh

Mamy więc funkcję printf jak i wiele innych do wprowadzania i wypisywania danychczas na pisanie programu

W programie definujemy głoacutewną funkcję main uruchamianą przy starcie programu za-wierającą właściwy kod Definicja funkcji zawiera oproacutecz nazwy i kodu także typ wartościzwracanej i argumentoacutew pobieranych Konstrukcja funkcji main

1Domyślne pliki nagłoacutewkowe znajdują się w katalogu z plikami nagłoacutewkowymi kompilatora W systemach zrodziny Unix będzie to katalog usrinclude natomiast w systemie Windows oacutew katalog będzie umieszczony wkatalogu z kompilatorem

23

24 ROZDZIAŁ 5 PIERWSZY PROGRAM

int main (void)

Typem zwracany przez funkcję jest int (Integer) czyli liczba całkowita (w przypadkumainbędzie to kod wyjściowy programu) W nawiasach umieszczane są argumenty funkcji tutajzapis void oznacza ich pominięcie Funkcja main jako argumenty może pobierać parametrylinii poleceń z jakimi program został uruchomiony i pełną ścieżkę do katalogu z programem

Kod funkcji umieszcza się w nawiasach klamrowych i Wewnątrz funkcji możemy wpisać poniższy kod

printf(Hello World)return 0

Wszystkie polecenia kończymy średnikiem return określa wartość jaką zwroacuteci funkcja(program) Liczba zero zwracana przez funkcję main() oznacza że program zakończył siębez błędoacutew błędne zakończenie często (choć nie zawsze) określane jest przez liczbę jeden2Funkcję main kończymy nawiasem klamrowym zamykającym

Twoacutej kod powinien wyglądać jak poniżej

include ltstdiohgtint main (void)

printf (Hello World)return 0

Teraz wystarczy go tylko skompilować i uruchomić

52 Rozwiązywanie problemoacutewJeśli nie możesz skompilować powyższego programu to najprawdopodobniej popełniłeś li-teroacutewkę przy ręcznym przepisywaniu go Zobacz też instrukcje na temat używania kompi-latora

Może też się zdarzyć że program skompiluje się uruchomi ale jego efektu działania niebędzie widać Dzieje się tak ponieważ nasz pierwszy program po prostu wypisuje komunikati od razu kończy działanie nie czekając na reakcję użytkownika Nie jest to problememgdy program jest uruchamiany z konsoli tekstowej ale w innych przypadkach nie widzimyefektoacutew jego działania

Jeśli korzystasz ze Zintegrowanego Środowiska Programistycznego (ang IDE) możeszzaznaczyć by nie zamykało ono programu po zakończeniu jego działania Innym sposobemjest dodanie instrukcji ktoacutere wstrzymywałyby zakończenie programu Można to zrobić do-dając przed linią z return funkcję pobierającą znak z wejścia

getchar()

2Jeżeli chcesz mieć pewność że twoacutej program będzie działał poprawnie roacutewnież na platformach gdzie 1 oznaczapoprawne zakończenie (lub nie oznacza nic) możesz skorzystać z makr EXIT SUCCESS i EXIT FAILURE zdefiniowanychw pliku nagłoacutewkowym stdlibh

52 ROZWIĄZYWANIE PROBLEMOacuteW 25

Jest też prostszy (choć nieprzenośny) sposoacuteb mianowicie wywołanie polecenia systemo-wego W zależności od używanego systemu operacyjnego mamy do dyspozycji roacuteżne po-lecenia powodujące roacuteżne efekty Do tego celu skorzystamy z funkcji system() ktoacutera jakoparametr przyjmuje polecenie systemowe ktoacutere chcemy wykonać np

Rodzina systemoacutew UnixLinux

system(sleep 10) oczekiwanie 10 s system(read discard) oczekiwanie na wpisanie tekstu

Rodzina systemoacutew oraz MS Windows

system(pause) oczekiwanie na wciśnięcie dowolnego klawisza

Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnychWindows w ktoacuterychto z reguły pracuje się w trybie okienkowym a z konsoli korzystamy tylko podczas urucha-mianiu programu Z kolei w systemach UnixLinux jest ona praktycznie w ogoacutele nie używanaw tym celu ze względu na uruchamianie programu bezpośrednio z konsoli

26 ROZDZIAŁ 5 PIERWSZY PROGRAM

Rozdział 6

Podstawy

Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogoacutel-nych informacji

61 Kompilacja Jak działa C

Jak każdy język programowania C sam w sobie jest niezrozumiały dla procesora Został onstworzony w celu umożliwienia ludziom łatwego pisania kodu ktoacutery może zostać przetwo-rzony na kod maszynowy Program ktoacutery zamienia kod C na wykonywalny kod binarnyto kompilator Jeśli pracujesz nad projektem ktoacutery wymaga kilku plikoacutew kodu źroacutedłowego(np pliki nagłoacutewkowe) wtedy jest uruchamiany kolejny program mdash linker Linker służy dopołączenia roacuteżnych plikoacutew i stworzenia jednej aplikacji lub biblioteki (library) Bibliotekajest zestawem procedur ktoacutery sam w sobie nie jest wykonywalny ale może być używanaprzez inne programy Kompilacja i łączenie plikoacutew są ze sobą bardzo ściśle powiązane stądsą przez wielu traktowane jako jeden proces Jedną rzecz warto sobie uświadomić mdash kompila-cja jest jednokierunkowa przekształcenie kodu źroacutedłowego C w kod maszynowy jest bardzoproste natomiast odwrotnie mdash nie Dekompilatory co prawda istnieją ale rzadko tworząużyteczny kod C

Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collec-tion dostępny na stronie gccgnuorg

62 Co może C

Pewnie zaskoczy Cię to że tak naprawdę ldquoczystyrdquo język C nie może zbyt wiele Język Cw grupie językoacutew programowania wysokiego poziomu jest stosunkowo nisko Dzięki temukod napisany w języku C można dość łatwo przetłumaczyć na kod asemblera Bardzo łatwojest też łączyć ze sobą kod napisany w języku asemblera z kodem napisanym w C Dla bar-dzo wielu ludzi przeszkodą jest także dość duża liczba i częsta dwuznaczność operatoroacutewPoczątkujący programista czytający kod programu w C może odnieść bardzo nieprzyjemnewrażenie ktoacutere można opisać cytatem ldquoja nigdy tego nie opanujęrdquo Wszystkie te elementyjęzyka C ktoacutere wydają Ci się dziwne i nielogiczne wmiarę jak będziesz nabierał doświadcze-nia nagle okażą się całkiem przemyślanie dobrane i takie a nie inne konstrukcje przypadnąCi do gustu Dalsza lektura tego podręcznika oraz zaznajamianie się z funkcjami z roacuteżnychbibliotek ukażą Ci całą gamę możliwości ktoacutere daje język C doświadczonemu programiście

27

28 ROZDZIAŁ 6 PODSTAWY

63 Struktura blokowaTeraz omoacutewimy podstawową strukturę programu napisanego w C Jeśli miałeś styczność zjęzykiem Pascal to pewnie słyszałeś o nim że jest to język programowania strukturalny WC nie ma tak ścisłej struktury blokowej mimo to jest bardzo ważne zrozumienie co oznaczastruktura blokowa Blok jest grupą instrukcji połączonych w ten sposoacuteb że są traktowanejak jedna całość W C blok zawiera się pomiędzy nawiasami klamrowymi Blok możetakże zawierać kolejne bloki

Zawartość bloku Generalnie blok zawiera ciąg kolejno wykonywanych poleceń Polece-nia zawsze (z nielicznymi wyjątkami) kończą się średnikiem () W jednej linii może znajdo-wać się wiele poleceń choć dla zwiększenia czytelności kodu najczęściej pisze się pojedyncząinstrukcję w każdej linii Jest kilka rodzajoacutew poleceń np instrukcje przypisania warunkoweczy pętli W dużej części tego podręcznika będziemy zajmować się właśnie instrukcjami

Pomiędzy poleceniami są roacutewnież odstępymdash spacje tabulacje oraz przejścia do następnejlinii przy czym dla kompilatora te trzy rodzaje odstępoacutew mają takie samo znaczenie Dlaprzykładu poniższe trzy fragmenty kodu źroacutedłowego dla kompilatora są takie same

printf(Hello world) return 0

printf(Hello world)return 0

printf(Hello world)

return 0

W tej regule istnieje jednak jeden wyjątek Dotyczy on stałych tekstowych W powyż-szych przykładach stałą tekstową jest ldquoHello worldrdquo Gdy jednak rozbijemy ten napis kom-pilator zasygnalizuje błąd

printf(Helloworld)return 0

Należy tylko zapamiętać że stałe tekstowe powinny zaczynać się i kończyć w tej samejlini (można ominąć to ograniczenie mdash więcej w rozdziale Napisy) Oproacutecz tego jednego przy-padku dla kompilatora ma znaczenie samo istnienie odstępu a nie jego wielkość czy rodzajJednak stosowanie odstępoacutew jest bardzo ważne dla zwiększenia czytelności kodu mdash dziękiczemu możemy zaoszczędzić sporo czasu i nerwoacutew ponieważ znalezienie błędu (ktoacutere sięzdarzają każdemu) w nieczytelnym kodzie może być bardzo trudne

64 ZasięgPojęcie to dotyczy zmiennych (ktoacutere przechowują dane przetwarzane przez program) Wkaż-dym programie (oproacutecz tych najprostszych) są zaroacutewno zmienne wykorzystywane przez całyczas działania programu oraz takie ktoacutere są używane przez pojedynczy blok programu (npfunkcję) Na przykład w pewnym programie w pewnym momencie jest wykonywane skom-plikowane obliczenie ktoacutere wymaga zadeklarowania wielu zmiennych do przechowywaniapośrednich wynikoacutew Ale przez większą część tego działania te zmienne są niepotrzebne

65 FUNKCJE 29

i zajmują tylko miejsce w pamięci mdash najlepiej gdyby to miejsce zostało zarezerwowane tużprzed wykonaniemwspomnianych obliczeń a zaraz po ich wykonaniu zwolnione Dlatego wC istnieją zmienne globalne oraz lokalne Zmienne globalne mogą być używane w każdymmiejscu programu natomiast lokalne mdash tylko w określonym bloku czy funkcji (oraz blokachw nim zawartych) Generalnie mdash zmienna zadeklarowanaw danym bloku jest dostępna tylkowewnątrz niego

65 Funkcje

Funkcje są ściśle związane ze strukturą blokową mdash funkcją jest po prostu blok instrukcjiktoacutery jest potem wywoływany w programie za pomocą pojedynczego polecenia Zazwyczajfunkcja wykonuje pewne określone zadanie np we wspomnianym programie wykonują-cym pewne skomplikowane obliczenie Każda funkcja ma swoją nazwę za pomocą ktoacuterejjest potem wywoływana w programie oraz blok wykonywanych poleceń Wiele funkcji po-biera pewne dane czyli argumenty funkcji wiele funkcji także zwraca pewną wartość pozakończeniu wykonywania Dobrym nawykiem jest dzielenie dużego programu na zestawmniejszych funkcji mdash dzięki temu będziesz moacutegł łatwiej odnaleźć błąd w programie

Jeśli chcesz użyć jakiejś funkcji to powinieneś wiedzieć

jakie zadanie wykonuje dana funkcja

rodzaj wczytywanych argumentoacutew i do czego są one potrzebne tej funkcji

rodzaj zwroacuteconych danych i co one oznaczają

W programach w języku C jedna funkcja ma szczegoacutelne znaczenie mdash jest tomain() Funk-cję tę zwaną funkcją głoacutewną musi zawierać każdy program W niej zawiera się głoacutewny kodprogramu przekazywane są do niej argumenty z ktoacuterymi wywoływany jest program (jakoparametry argc i argv) Więcej o funkcji main() dowiesz się poacuteźniej w rozdziale Funkcje

66 Biblioteki standardowe

Język C w przeciwieństwie do innych językoacutew programowania (np Fortranu czy Pascala)nie posiada absolutnie żadny słoacutew kluczowych ktoacutere odpowiedzialne by były za obsługęwejścia i wyjścia Może się to wydawać dziwne mdash język ktoacutery sam w sobie nie posiadapodstawowych funkcji musi być językiem o ograniczonym zastosowaniu Jednak brak pod-stawowych funkcji wejścia-wyjścia jest jedną z największych zalet tego języka Jego składniaopracowana jest tak by można było bardzo łatwo przełożyć ją na kod maszynowy To wła-śnie dzięki temu programy napisane w języku C są takie szybkie Pozostaje jednak pytaniemdash jak umożliwić programom komunikację z użytkownikiem

W roku kiedy zapoczątkowano prace nad standaryzacją C zdecydowano że po-winien być zestaw instrukcji identycznych w każdej implementacji C Nazwano je BibliotekąStandardową (czasemnazywaną ldquolibcrdquo) Zawiera ona podstawowe funkcje ktoacutere umożliwiająwykonywanie takich zadań jak wczytywanie i zwracanie danych modyfikowanie zmiennychłańcuchowych działania matematyczne operacje na plikach i wiele innych jednak nie za-wiera żadnych funkcji ktoacutere mogą być zależne od systemu operacyjnego czy sprzętu jakgrafika dźwięk czy obsługa sieci W programie ldquoHello Worldrdquo użyto funkcji z biblioteki stan-dardowej mdash printf ktoacutera wyświetla na ekranie sformatowany tekst

30 ROZDZIAŁ 6 PODSTAWY

67 Komentarze i stylKomentarze mdash to tekst włączony do kodu źroacutedłowego ktoacutery jest pomijany przez kompilatori służy jedynie dokumentacji W języku C komentarze zaczynają się od

a kończą

Dobre komentowanie ma duże znaczenie dla rozwijania oprogramowania nie tylko dlategoże inni będą kiedyś potrzebowali przeczytać napisany przez ciebie kod źroacutedłowy ale takżemożesz chcieć po dłuższym czasie powroacutecić do swojego programu i możesz zapomnieć doczego służy dany blok kodu albo dlaczego akurat użyłeś tego polecenia a nie innego Wchwili pisania programu to może być dla ciebie oczywiste ale po dłuższym czasie możeszmieć problemy ze zrozumieniem własnego kodu Jednak nie należy też wstawiać zbyt dużokomentarzy ponieważ wtedy kod może stać się jeszcze mniej czytelny mdash najlepiej komen-tować fragmenty ktoacutere nie są oczywiste dla programisty oraz te o szczegoacutelnym znaczeniuAle tego nauczysz się już w praktyce

Dobry styl pisania kodu jest o tyle ważny że powinien on być czytelny i zrozumiały po tow końcu wymyślono języki programowania wysokiego poziomu (w tym C) aby kod było ła-two zrozumieć ) I tak mdash należy stosować wcięcia dla odroacuteżnienia blokoacutew kolejnego poziomu(zawartych w innym bloku podrzędnych) nawiasy klamrowe otwierające i zamykające blokpowinny mieć takie same wcięcia staraj się aby nazwy funkcji i zmiennych kojarzyły się zzadaniem jakie dana funkcja czy zmienna pełni w programie W dalszej części podręcznikamożesz napotkać więcej zaleceń dotyczących stylu pisania kodu Staraj się stosować do tychzaleceń mdash dzięki temu kod pisanych przez ciebie programoacutew będzie łatwiejszy do czytania izrozumienia

Jeśli masz doświadczenia z językiem C++ pamiętaj że w C nie powinno się stosowaćkomentarzy zaczynających się od dwoacutech znakoacutew slash tak nie komentujemy w CJest to niezgodne ze standardem ANSI C i niektoacutere kompilatory mogą nie skompilować koduz komentarzami w stylu C++ (choć standard ISO C dopuszcza komentarze w stylu C++)

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentoacutew kodu Jeśli częśćprogramu źle działa i chcemy ją chwilowo wyłączyć albo fragment kodu jest nam już nie-potrzebny ale mamy wątpliwości czy w przyszłości nie będziemy chcieli go użyć mdash umiesz-czamy go po prostu wewnątrz komentarza

Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uważać najedną subtelność Otoacuteż komentarze w języku C nie mogą być zagnieżdżone Trzebana to uważać gdy chcemy objąć komentarzem obszar w ktoacuterym już istnieje komentarz (na-leży wtedy usunąć wewnętrzny komentarz) W nowszym standardzie C dopuszcza się abykomentarz typu zawierał w sobie komentarz

671 Po polsku czy angielsku

Jak jużwcześniej byłowspomniane zmiennym i funkcjom powinno się nadawać nazwy ktoacutereodpowiadają ich znaczeniu Zdecydowanie łatwiej jest czytać kod gdy średnią liczb przecho-wuje zmienna srednia niż a a znajdowaniemmaksimumw ciągu liczb zajmuje się funkcja maxalbo znajdz max niż nazwana f Często nazwy funkcji to właśnie czasowniki

67 KOMENTARZE I STYL 31

Powstaje pytanie w jakim języku należy pisać nazwy Jeśli chcemy by nasz kod mogłyczytać osoby nieznające polskiego mdash warto użyć języka angielskiego Jeśli nie mdash można bezproblemu użyć polskiego Bardzo istotne jest jednak by nie mieszać językoacutew Jeśli zdecy-dowaliśmy się używać polskiego używajmy go od początku do końca przeplatanie ze sobądwoacutech językoacutew robi złe wrażenie

Warto roacutewnież zdecydować się na sposoacuteb zapisywania nazw składających się z więcej niżjednego słowa Istnieje kilka możliwości najważniejsze z nich

oddzielanie podkreśleniem int to str

ldquokonwencja pascalowskardquo każde słowo dużą literą IntToStr

ldquokonwencja wielbłądziardquo pierwsze słowo małą kolejne dużą literą intToStr

Ponownie najlepiej stosować konsekwentnie jedną z konwencji i nie mieszać ze sobąkilku

672 Notacja węgierska

Czasem programista może zapomnieć jakiego typu była dana zmienna Wtedy musi znaleźćodpowiednią deklarację (co nie zawsze jest łatwe) Dlatego więc wymyślono sposoacuteb by temuzaradzić Pomyślano by w nazwie zmiennej (bądź wskaźnika na zmienną) napisać jakiegojest ona typu np

a liczba (liczba typu int)

w ll dlugaLiczba (wskaźnik na zmienną typu long long)

t5x5 ch tabliczka (tablica x elementoacutew typu char)

func i silnia (funkcja zwracająca int)

Jest to bardzo wygodne przy bardzo zagmatwanych zmiennych

w t4 w t2x2 s pomieszaniec (wskaźnik na tablicę czterech wskaźnikoacutew na tablice dwu-wymiarowe zmiennych typu short)

Lub gdy nie pamiętamy wymiaroacutew tablicy

t4x5x6 f powalonaKostkaRubika (od razu wiemy żet4x5x6 f powalonaKostkaRubika[5][4][6] jest niewłaściwe)

Taki zapis ma też swoje wady Gdy zdecydujemy się zmienić typ zmiennej zamiast poprostu przemienić w deklaracji int na long musimy zmieniać nazwy w całym programieCzęsto takie nazwy są po prostu długie i nie chce nam się ich pisać (no coacuteż programista teżczłowiek) więc wolimy wprowadzić pomieszaniec zamiast w t4 w t2x2 s pomieszaniec Naj-ważniejsze to jednak trzymać się rozwiązania ktoacutere wybraliśmy na początku bo mieszaniejest przerażające

32 ROZDZIAŁ 6 PODSTAWY

68 PreprocesorNie cały napisany przez ciebie kod będzie przekształcany przez kompilator bezpośrednio nakodwykonywalny programu Wwielu przypadkach będziesz używać poleceń ldquoskierowanychdo kompilatorardquo tzw dyrektyw kompilacyjnych Na początku procesu kompilacji specjalnypodprogram tzw preprocesor wyszukuje wszystkie dyrektywy kompilacyjne i wykonujeodpowiednie akcje mdash ktoacutere polegają notabene na edycji kodu źroacutedłowego (np wstawieniudeklaracji funkcji zamianie jednego ciągu znakoacutew na inny) Właściwy kompilator zamie-niający kod C na kod wykonywalny nie napotka już dyrektyw kompilacyjnych ponieważzostały one przez preprocesor usunięte po wykonaniu odpowiednich akcji

W C dyrektywy kompilacyjne zaczynają się od znaku hash () Przykładem najczęściejużywanej dyrektywy jest include ktoacutera jest użyta nawet w tak prostym programie jakldquoHello Worldrdquo include nakazuje preprocesorowi włączyć (ang include) w tym miejscuzawartość podanego pliku tzw pliku nagłoacutewkowego najczęściej to będzie plik zawierającyfunkcje z ktoacuterejś biblioteki standardowej (stdioh mdash STandard Input-Output rozszerzenie hoznacza plik nagłoacutewkowy C) Dzięki temu zamiast wklejać do kodu swojego programu dekla-racje kilkunastu a nawet kilkudziesięciu funkcji wystarczy wpisać jedną magiczną linijkę

69 Nazwy zmienny stały i funkcjiIdentyfikatory czyli nazwy zmiennych stałych i funkcji mogą składać się z liter (bez polskichznakoacutew) cyfr i znaku podkreślenia z tym że nazwa taka nie może zaczynać się od cyfry Niemożna używać nazw zarezerwowanych (patrz Składnia)

Przykłady błędnych nazw

2liczba (nie można zaczynać nazwy od cyfry)moja funkcja (nie można używać spacji)$i (nie można używać znaku $)if (if to słowo kluczowe)

Aby kod był bardziej czytelny przestrzegajmy poniższych (umownych) reguł

nazwy zmiennych piszemy małymi literami i file

nazwy stałych (zadeklarowanych przy pomocy define) piszemy wielkimi literamiSIZE

nazwy funkcji piszemy małymi literami print

wyrazy w nazwach oddzielamy znakiem podkreślenia open file close all files

Są to tylko konwencje mdash żaden kompilator nie zgłosi błędu jeśli wprowadzimy swoacutej wła-sny system nazewnictwa Jednak warto pamiętać że być może nad naszym kodem będą pra-cowali także inni programiści ktoacuterzy mogą mieć trudności z analizą kodu niespełniającegopewnych zasad

Rozdział 7

Zmienne

Procesor komputera stworzony jest tak aby przetwarzał dane znajdujące się w pamięci kom-putera Z punktu widzenia programu napisanego w języku C (ktoacutery jak wiadomo jest języ-kiem wysokiego poziomu) dane umieszczane są w postaci tzw zmienny Zmienne uła-twiają programiście pisanie programu Dzięki nim programista nie musi się przejmowaćgdzie w pamięci owe zmienne się znajdują tzn nie operuje fizycznymi adresami pamięcijak np 0x14613467 tylko prostą do zapamiętania nazwą zmiennej

71 Czym są zmienneZmienna jest to pewien fragment pamięci o ustalonym rozmiarze ktoacutery posiada własny iden-tyfikator (nazwę) oraz może przechowywać pewną wartość zależną od typu zmiennej

711 Deklaracja zmienny

Aby moacutec skorzystać ze zmiennej należy ją przed użyciem zadeklarować to znaczy poinfor-mować kompilator jak zmienna będzie się nazywać i jaki typ ma mieć Zmienne deklarujesię w sposoacuteb następujący

typ nazwa_zmiennej

Oto deklaracja zmiennej o nazwie ldquowiekrdquo typu ldquointrdquo czyli liczby całkowitej

int wiek

Zmiennej w momencie zadeklarowania można od razu przypisać wartość

int wiek = 17

W języku C zmienne deklaruje się na samym początku bloku (czyli przed pierwszą in-strukcją)

int wiek = 17printf(dn wiek)int kopia_wieku tu stary kompilator C zgłosi błąd

deklaracja występuje po instrukcji (printf) kopia_wieku = wiek

33

34 ROZDZIAŁ 7 ZMIENNE

Według nowszych standardoacutewmożliwe jest deklarowanie zmiennej w dowolnymmiejscuprogramu ale wtedy musimy pamiętać aby zadeklarować zmienną przed jej użyciem Toznaczy że taki kod jest niepoprawny

printf (Mam d latn wiek)int wiek = 17

Należy go zapisać tak

int wiek = 17printf (Mam d latn wiek)

Język C nie inicjalizuje zmiennych lokalnych Oznacza to że w nowo zadeklarowanejzmiennej znajdują się śmieci - to co wcześniej zawierał przydzielony zmiennej fragmentpamięci Aby uniknąć ciężkich do wykrycia błędoacutew dobrze jest inicjalizować (przypisywaćwartość) wszystkie zmienne w momencie zadeklarowania

712 Zasięg zmiennej

Zmienne mogą być dostępne dla wszystkich funkcji programu mdash nazywamy je wtedy zmien-nymi globalnymi Deklaruje się je przed wszystkimi funkcjami programu

include ltstdiohgt

int ab nasze zmienne globalne

void func1 ()

instrukcje a=3 dalsze instrukcje

int main ()

b=3a=2return 0

Zmienne globalne jeśli programista nie przypisze im innej wartości podczas definiowa-nia są inicjalizowane wartością

Zmienne ktoacutere funkcja deklaruje do ldquowłasnych potrzebrdquo nazywamy zmiennymi lokal-nymi Nasuwa się pytanie ldquoczy będzie błędem nazwanie tą samą nazwą zmiennej globalneji lokalnejrdquo Otoacuteż odpowiedź może być zaskakująca nie Natomiast w danej funkcji da sięużywać tylko jej zmiennej lokalnej Tej konstrukcji należy z wiadomych względoacutew unikać

int a=1 zmienna globalna

int main()

71 CZYM SĄ ZMIENNE 35

int a=2 to już zmienna lokalna printf(d a) wypisze 2

713 Czas życia

Czas życia to czas od momentu przydzielenia dla zmiennej miejsca w pamięci (stworzenieobiektu) do momentu zwolnienia miejsca w pamięci (likwidacja obiektu)

Zakres ważności to część programu w ktoacuterej nazwa znana jest kompilatorowi

main()

int a = 10 otwarcie lokalnego bloku

int b = 10printf(d d a b)

zamknięcie lokalnego bloku zmienna b jest usuwana

printf(d d a b) BŁĄD b juz nie istnieje tu usuwana jest zmienna a

Zdefiniowaliśmy dwie zmienne typu int Zaroacutewno a i b istnieją przez cały program (czasżycia) Nazwa zmiennej a jest znana kompilatorowi przez cały program Nazwa zmiennej bjest znana tylko w lokalnym bloku dlatego nastąpi błąd w ostatniej instrukcji

Niektoacutere kompilatory (prawdopodobniemożna tu zaliczyćMicrosoVisual C++ dowersji) uznają powyższy kod za poprawny W dodatku można ustawić w opcjach niektoacuterychkompilatoroacutew zachowanie w takiej sytuacji włącznie z zachowaniami niezgodnymi ze stan-dardem języka

Możemy świadomie ograniczyć ważność zmiennej do kilku linijek programu (tak jak ro-biliśmy wyżej) tworząc blok Nazwa zmiennej jest znana tylko w tym bloku

714 Stałe

Stała roacuteżni się od zmiennej tylko tym że nie można jej przypisać innej wartości w trak-cie działania programu Wartość stałej ustala się w kodzie programu i nigdy ona nie ulegazmianie Stałą deklaruje się z użyciem słowa kluczowego const w sposoacuteb następujący

const typ nazwa_stałej=wartość

Dobrze jest używać stałych w programie ponieważ unikniemy wtedy przypadkowychpomyłek a kompilator może często zoptymalizować ich użycie (np od razu podstawiając ichwartość do kodu)

36 ROZDZIAŁ 7 ZMIENNE

const int WARTOSC_POCZATKOWA=5int i=WARTOSC_POCZATKOWAWARTOSC_POCZATKOWA=4 tu kompilator zaprotestuje int j=WARTOSC_POCZATKOWA

Przykład pokazuje dobry zwyczaj programistyczny jakim jest zastępowanie umieszczo-nych na stałe w kodzie liczb stałymi W ten sposoacuteb będziemy mieli większą kontrolę nadkodem mdash stałe umieszczone w jednym miejscu można łatwo modyfikować zamiast szukaćpo całym kodzie liczb ktoacutere chcemy zmienić

Nie mamy jednak pełnej gwarancji że stała będzie miała tę samą wartość przez cały czaswykonania programu możliwe jest bowiem dostanie się do wartości stałej (miejsca jej prze-chowywania w pamięci) pośrednio mdash za pomocą wskaźnikoacutew Można zatem dojść do wnio-sku że słowo kluczowe const służy tylko do poinformowania kompilatora aby ten nie zezwa-lał na jawną zmianę wartości stałej Z drugiej strony zgodnie ze standardem proacuteba mody-fikacji wartości stałej ma niezdefiniowane działanie (tzw undefined behaviour) i w związkuz tym może się powieść lub nie ale może też spowodować jakieś subtelne zmiany ktoacutere wefekcie spowodują że program będzie źle działał

Podobnie do zdefiniowania stałej możemy użyć dyrektywy preprocesora define (opi-sanej w dalszej części podręcznika) Tak zdefiniowaną stałą nazywamy stałą symbolicznąW przeciwieństwie do stałej zadeklarowanej z użyciem słowa const stała zdefiniowana przyużyciu define jest zastępowana daną wartością w każdym miejscu gdzie występuje dlategoteż może być używana w miejscach gdzie ldquonormalnardquo stała nie mogłaby dobrze spełnić swejroli

W przeciwieństwie do języka C++ w C stała to cały czas zmienna ktoacuterej kompilatorpilnuje by nie zmieniła się

72 Typy zmienny

Każdy program w C operuje na zmiennych mdash wydzielonych w pamięci komputera obsza-rach ktoacutere mogą reprezentować obiekty nam znane takie jak liczby znaki czy też bardziejzłożone obiekty Jednak dla komputera każdy obszar w pamięci jest taki sam mdash to ciąg zeri jedynek w takiej postaci zupełnie nieprzydatny dla programisty i użytkownika Podczaspisania programu musimy wskazać w jaki sposoacuteb ten ciąg ma być interpretowany

Typ zmiennej wskazuje właśnie sposoacuteb w jaki pamięć w ktoacuterej znajduje się zmiennabędzie wykorzystywana Określając go przekazuje się kompilatorowi informację ile pamięcitrzeba zarezerwować dla zmiennej a także w jaki sposoacuteb wykonywać na nim operacje

Każda zmienna musi mieć określony swoacutej typ w miejscu deklaracji i tego typu nie możejuż zmienić Lecz co jeśli mamy zmienną jednego typu ale potrzebujemy w pewnymmiejscuprogramu innego typu danych W takimwypadku stosujemy konwersję (rzutowanie) jednejzmiennej na inną zmienną Rzutowanie zostanie opisane poacuteźniej w rozdziale Operatory

Istnieją wbudowane i zdefiniowane przez użytkownika typy danych Wbudowane typydanych to te ktoacutere zna kompilator są one w nim bezpośrednio ldquozaszyterdquo Można też tworzyćwłasne typy danych ale należy je kompilatorowi opisać Więcej informacji znajduje się wrozdziale Typy złożone

W języku C wyroacuteżniamy podstawowe typy zmiennych Są to

char mdash jednobajtowe liczby całkowite służy do przechowywania znakoacutew

int mdash typ całkowity o długości domyślnej dla danej architektury komputera

72 TYPY ZMIENNYCH 37

float mdash typ zmiennopozycyjny (zwany roacutewnież zmiennoprzecinkowym) reprezentującyliczby rzeczywiste ( bajty)

double mdash typ zmiennopozycyjny podwoacutejnej precyzji ( bajtoacutew)

Typy zmiennoprzecinkowe zostały dokładnie opisane w IEEE

W języku C nie istnieje specjalny typ zmiennych przeznaczony na zmienne typu logicz-nego (albo ldquoprawda albo ldquofałszrdquo) Jest to inne podejście niż na przykład w językach Pascalalbo Java - definiujących osobny typ ldquobooleanrdquo ktoacuterego nie można ldquomieszaćz innymi typamizmiennych W C do przechowywania wartości logicznych zazwyczaj używa się typu ldquointrdquoWięcej na temat tego jak język C rozumie prawdę i fałsz znajduje się w rozdziale Operatory

721 int

Ten typ przeznaczony jest do liczb całkowitych Liczby temożemy zapisać na kilka sposoboacutew

System dziesiętny

12 13 45 35 itd

System oacutesemkowy (oktalny)

010 czyli 8016 czyli 8 + 6 = 14018 BŁĄD

System ten operuje na cyfrach od do Tak wiec jest niedozwolona Jeżeli chcemyużyć takiego zapisu musimy zacząć liczbę od

System szesnastkowy (heksadecymalny)

0x10 czyli 116 + 0 = 160x12 czyli 116 + 2 = 180xff czyli 1516 + 15 = 255

W tym systemie możliwe cyfry to hellip i dodatkowo a b c d e f ktoacutere oznaczają Aby użyć takiego systemu musimy poprzedzić liczbę ciągiem 0x Wielkośćznakoacutew w takich literałach nie ma znaczenia

Ponadto w niektoacuterych kompilatorach przeznaczonych głoacutewnie domikrokontroleroacutew spo-tyka się jeszcze użycie systemu binarnego Zazwyczaj dodaje się przedrostek 0b przed liczbą(analogicznie do zapisu spotykanego w języku Python) W tym systemie możemy oczywiścieużywać tylko i wyłącznie cyfr i Tego typu rozszerzenie bardzo ułatwia programowanieniskopoziomowe układoacutew Należy jednak pamiętać że jest to tylko i wyłącznie rozszerzenie

38 ROZDZIAŁ 7 ZMIENNE

722 float

Ten typ oznacza liczby zmiennoprzecinkowe czyli ułamki Istnieją dwa sposoby zapisu

System dziesiętny

314 45644 2354 321 itd

System ldquonaukowyrdquo mdash wykładniczy

6e2 czyli 6 102 czyli 60015e3 czyli 15 103 czyli 150034e-3 czyli 34 10minus3 czyli 00034

Należy wziąć pod uwagę że reprezentacja liczb rzeczywistych w komputerze jest niedo-skonała i możemy otrzymywać wyniki o zauważalnej niedokładności

723 double

Doublemdash czyli ldquopodwoacutejnyrdquomdash oznacza liczby zmiennoprzecinkowe podwoacutejnej precyzji Ozna-cza to że liczba taka zajmuje zazwyczaj w pamięci dwa razy więcej miejsca niż float (np bity wobec dla float) ale ma też dwa razy lepszą dokładność

Domyślnie ułamki wpisane w kodzie są typu double Możemy to zmienić dodając nakońcu literę ldquordquo

15f (float)15 (double)

724 ar

Jest to typ znakowy umożliwiający zapis znakoacutew ASCII Może też być traktowany jako liczbaz zakresu Znaki zapisujemywpojedynczych cudzysłowach (czasami nazywanymi apo-strofami) by odroacuteżnić je od łańcuchoacutew tekstowych (pisanych w podwoacutejnych cudzysłowach)

a 7 $

Pojedynczy cudzysłoacutew rsquo zapisujemy tak a null (czyli zero ktoacutere między innymikończy napisy) tak 0 Więcej znakoacutew specjalnych

Warto zauważyć że typ char to zwykły typ liczbowy i można go używać tak samo jaktypu int (zazwyczaj ma jednak mniejszy zakres) Co więcej literały znakowe (np rsquoarsquo) sątraktowane jako liczby i w języku C są typu int (w języku C++ są typu char)

725 void

Słowa kluczowego void można w określonych sytuacjach użyć tam gdzie oczekiwana jestnazwa typu void nie jest właściwym typem bo nie można utworzyć zmiennej takiego typujest to ldquopustyrdquo typ (ang void znaczy ldquopustyrdquo) Typ void przydaje się do zaznaczania żefunkcja nie zwraca żadnej wartości lub że nie przyjmuje żadnych parametroacutew (więcej o tymw rozdziale Funkcje) Można też tworzyć zmienne będące typu ldquowskaźnik na voidrdquo

73 SPECYFIKATORY 39

73 Specyfikatory

Specyfikatory to słowa kluczowe ktoacutere postawione przy typie danych zmieniają jego zna-czenie

731 signed i unsigned

Na początku zastanoacutewmy się jak komputer może przechować liczbę ujemną Otoacuteż w przy-padku przechowywania liczb ujemnych musimyw zmiennej przechować jeszcze jej znak Jakwiadomo zmienna składa się z szeregu bitoacutew W przypadku użycia zmiennej pierwszy bit zlewej strony (nazywany także bitem najbardziej znaczącym) przechowuje znak liczby Efek-tem tego jest spadek ldquopojemnościrdquo zmiennej czyli zmniejszenie największej wartości ktoacuterąmożemy przechować w zmiennej

Signed oznacza liczbę ze znakiem unsigned mdash bez znaku (nieujemną) Mogą być zasto-sowane do typoacutew char i int i łączone ze specyfikatorami short i long (gdy ma to sens)

Jeśli przy signed lub unsigned nie napiszemy o jaki typ nam chodzi kompilator przyjmiewartość domyślną czyli int

Przykładowo dla zmiennej char(zajmującej bitoacutew zapisanej w formacie uzupełnień dodwoacutech) wygląda to tak

signed char a zmienna a przyjmuje wartości od -128 do 127 unsigned char b zmienna b przyjmuje wartości od 0 do 255 unsigned short cunsigned long int d

Jeżeli nie podamy żadnego ze specyfikatora wtedy liczba jest domyślnie przyjmowanajako signed (nie dotyczy to typu char dla ktoacuterego jest to zależne od kompilatora)

signed int i = 0 jest roacutewnoznaczne zint i = 0

Liczby bez znaku pozwalają nam zapisać większe liczby przy tej samej wielkości zmiennejmdash ale trzeba uważać by nie zejść z nimi poniżej zera mdash wtedy ldquoprzewijająrdquo się na sam konieczakresu co może powodować trudne do wykrycia błędy w programach

732 short i long

Short i long są wskazoacutewkami dla kompilatora by zarezerwował dla danego typu mniej (od-powiednio mdash więcej) pamięci Mogą być zastosowane do dwoacutech typoacutew int i double (tylkolong) mając roacuteżne znaczenie

Jeśli przy short lub long nie napiszemy o jaki typ nam chodzi kompilator przyjmie war-tość domyślną czyli int

Należy pamiętać że to jedynie życzenie wobec kompilatora mdash w wielu kompilatorachtypy int i long int mają ten sam rozmiar Standard języka C nakłada jedynie na kompilatorynastępujące ograniczenia int mdash nie może być kroacutetszy niż bitoacutew int mdash musi byćdłuższy lub roacutewny short a nie może być dłuższy niż long short int mdash nie może byćkroacutetszy niż bitoacutew long int mdash nie może być kroacutetszy niż bity

Zazwyczaj typ int jest typem danych o długości odpowiadającej wielkości rejestroacutew pro-cesora czyli na procesorze szesnastobitowym ma bitoacutew na trzydziestodwubitowym mdash

40 ROZDZIAŁ 7 ZMIENNE

itd1 Z tego powodu jeśli to tylko możliwe do reprezentacji liczb całkowitych preferowanejest użycie typu int bez żadnych specyfikatoroacutew rozmiaru

74 Modyfikatory

741 volatile

volatile znaczy ulotny Oznacza to że kompilator wyłączy dla takiej zmiennej optymaliza-cje typu zastąpienia przez stałą lub zawartość rejestru za to wygeneruje kod ktoacutery będzieodwoływał się zawsze do komoacuterek pamięci danego obiektu Zapobiegnie to błędowi gdyobiekt zostaje zmieniony przez część programu ktoacutera nie ma zauważalnego dla kompilatorazwiązku z danym fragmentem kodu lub nawet przez zupełnie inny proces

volatile float liczba1float liczba2

printf (fnfn liczba1 liczba2) instrukcje nie związane ze zmiennymi printf (fnf liczba1 liczba2)

Jeżeli zmienne liczba i liczba zmienią się niezauważalnie dla kompilatora to odczytując

liczba mdash nastąpi odwołanie do komoacuterek pamięci Kompilator pobierze nową wartośćzmiennej

liczba mdash kompilator może wypisać poprzednią wartość ktoacuterą przechowywał w reje-strze

Modyfikator volatile jest rzadko stosowany i przydaje się w wąskich zastosowaniachjak wspoacutełbieżność i wspoacutełdzielenie zasoboacutew oraz przerwania systemowe

742 register

Jeżeli utworzymy zmienną ktoacuterej będziemy używać w swoim programie bardzo często mo-żemy wykorzystać modyfikator register Kompilator może wtedy umieścić zmienną w re-jestrze do ktoacuterego ma szybki dostęp co przyśpieszy odwołania do tej zmiennej

register int liczba

W nowoczesnych kompilatorach ten modyfikator praktycznie nie ma wpływu na pro-gram Optymalizator sam decyduje czy i co należy umieścić w rejestrze Nie mamy żadnejgwarancji że zmienna tak zadeklarowana rzeczywiście się tam znajdzie chociaż dostęp doniej może zostać przyspieszony w inny sposoacuteb Raczej powinno się unikać tego typu kon-strukcji w programie

1Wiąże się to z pewnymi uwarunkowaniami historycznymi Podręcznik do języka C duetu KampR zakładał żetyp int miał się odnosić do typowej dla danego procesora długości liczby całkowitej Natomiast jeśli procesor moacutegłobsługiwać typy dłuższe lub kroacutetsze stosownego znaczenia nabierałymodyfikatory short i long Dobrymprzykłademmoże być architektura i386 ktoacutera umożliwia obliczenia na liczbach 16-bitowych Dlatego też modyfikator shortpowoduje skroacutecenie zmiennej do 16 bitoacutew

75 UWAGI 41

743 static

Pozwala na zdefiniowanie zmiennej statycznej ldquoStatycznośćrdquo polega na zachowaniu warto-ści pomiędzy kolejnymi definicjami tej samej zmiennej Jest to przede wszystkim przydatnew funkcjach Gdy zdefiniujemy zmienną w ciele funkcji to zmienna ta będzie od nowa defi-niowana wraz z domyślną wartością (jeżeli taką podano) W wypadku zmiennej określonejjako statyczna jej wartość się nie zmieni przy ponownym wywołaniu funkcji Na przykład

void dodaj(int liczba)

int zmienna = 0 bez staticzmienna = zmienna + liczbaprintf (Wartosc zmiennej dn zmienna)

Gdy wywołamy tę funkcję np razy w ten sposoacuteb

dodaj(3)dodaj(5)dodaj(4)

to ujrzymy na ekranie

Wartosc zmiennej 3Wartosc zmiennej 5Wartosc zmiennej 4

jeżeli jednak deklarację zmiennej zmienimy na static int zmienna = 0 to wartość zmiennejzostanie zachowana i po podobnym wykonaniu funkcji powinnyśmy ujrzeć

Wartosc zmiennej 3Wartosc zmiennej 8Wartosc zmiennej 12

Zupełnie co innego oznacza static zastosowane dla zmiennej globalnej Jest ona wtedywidoczna tylko w jednym pliku Zobacz też rozdział Biblioteki

744 extern

Przez extern oznacza się zmienne globalne zadeklarowane w innych plikach mdash informujemyw ten sposoacuteb kompilator żeby nie szukał jej w aktualnym pliku Zobacz też rozdział Biblio-teki

745 auto

Zupełnym archaizmem jest modyfikator auto ktoacutery oznacza tyle że zmienna jest lokalnaPonieważ zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna modyfikator tennie ma obecnie żadnego zastosowania praktycznego auto jest spadkiem po wcześniejszychjęzykach programowania na ktoacuterych oparty jest C (np B)

75 Uwagi Język C++ pozwala na mieszanie deklaracji zmiennych z kodem Więcej informacji w

C++Zmienne

42 ROZDZIAŁ 7 ZMIENNE

Rozdział 8

Operatory

81 Przypisanie

Operator przypisania (=rdquo) jak sama nazwa wskazuje przypisuje wartość prawego argu-mentu lewemu np

int a = 5 bb = aprintf(dn b) wypisze 5

Operator ten ma łączność prawostronną tzn obliczanie przypisań następuje z prawa nalewo i zwraca on przypisaną wartość dzięki czemu może być użyty kaskadowo

int a b ca = b = c = 3printf(d d dn a b c) wypisze 3 3 3

811 Skroacutecony zapis

C umożliwia też skroacutecony zapis postaci a = b gdzie jest jednym z operatoroacutew + - amp | ˆ ltlt lub gtgt (opisanych niżej) Ogoacutelnie rzecz ujmując zapis a = b jest roacutewnoważnyzapisowi a = a (b) np

int a = 1a += 5 to samo co a = a + 5 a = a + 2 to samo co a = a (a + 2) a = 2 to samo co a = a 2

Początkowo skroacutecona notacja miała następującą składnię a = b co często prowadziło doniejasności np i =- (i = - czy też i = i-) Dlatego też zdecydowano się zmienić kolejnośćoperatoroacutew

43

44 ROZDZIAŁ 8 OPERATORY

82 Rzutowanie

Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu Konwer-sja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podanaexplicite przez programistę) Oto kilka przykładoacutew konwersji niejawnej

int i = 427 konwersja z double do int float f = i konwersja z int do float double d = f konwersja z float do double unsigned u = i konwersja z int do unsigned int f = 42 konwersja z double do float i = d konwersja z double do int char str = foo konwersja z const char do char [1] const char cstr = str konwersja z char do const char void ptr = str konwersja z char do void

Podczas konwersji zmiennych zawierających większe ilości danych do typoacutew prostszych(np double do int) musimy liczyć się z utratą informacji jak to miało miejsce w pierwszejlinijce mdash zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta iw rezultacie zmiennej została przypisana wartość

Zaskakująca może się wydać linijka oznaczona przez 1 Niejawna konwersja z typu constchar do typu char nie jest dopuszczana przez standard C Jednak literały napisowe (ktoacutere sątypu const char) stanowią tutaj wyjątek Wynika on z faktu że były one używane na długoprzed wprowadzeniem słoacutewka const do języka i brak wspomnianegowyjątku spowodowałbyże duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod

Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania np

double d = 314int pi = (int)d 1 pi = (unsigned)pi gtgt 4 2

W pierwszym przypadku operator został użyty by zwroacutecić uwagę na utratę precyzji Wdrugim dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej

Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne kon-wersje (tj konwersja z double do int oraz z int do unsigned int) jednak niektoacutere konwersjesą błędne np

const char cstr = foochar str = cstr

W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję

const char cstr = foochar str = (char)cstr

Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompi-lator Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzieon robił i czy nie ma innego sposobu wykonania danej operacji ktoacutery nie wymagałby podej-mowania tak drastycznych krokoacutew

83 OPERATORY ARYTMETYCZNE 45

83 Operatory arytmetyczne

W arytmetyce komputerowej nie działa prawo łączności oraz rozdzielności Wynika toz ograniczonego rozmiaru zmiennych ktoacutere przechowują wartości Przykład dla zmiennycho długości bitoacutew (bez znaku) Maksymalna wartość ktoacuterą może przechowywać typ to216minus1 = 65535 Zatem operacja typu 65530+10minus20 zapisana jako (65530+10)minus20 możezaowocować czymś zupełnie innym niż 65530+(10minus20) W pierwszym przypadku zapewnedojdzie do tzw przepełnienia - procesor nie będzie miał miejsca aby zapisać dodatkowybit Zachowanie programu będzie w takim przypadku zależało od architektury procesoraAnalogiczny przykład możemy podać dla rozdzielności mnożenia względem dodawania

Język C definiuje następujące dwuargumentowe operatory arytmetyczne

dodawanie (+rdquo)

odejmowanie (-rdquo)

mnożenie (rdquo)

dzielenie (rdquo)

reszta z dzielenia (rdquo) określona tylko dla liczb całkowitych (tzw dzielenie modulo)

int a=7 b=2 cc = a bprintf (dnc) wypisze 1

Należy pamiętać że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak naj-większy z argumentoacutew Oznacza to że operacja wykonana na dwoacutech liczbach całkowitychnadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej Dla przy-kładu poniższy kod

float a = 7 2printf(fn a)

wypisze (wbrew oczekiwaniu początkujących programistoacutew) 30 a nie 35 Odnosi sięto nie tylko do dzielenia ale także mnożenia np

float a = 1000 1000 1000 1000 1000 1000printf(fn a)

prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali Aby wymusićobliczenia rzeczywiste należy zmienić typ jednego z argumentoacutew na liczbę rzeczywistą poprostu zmieniając literał lub korzystając z rzutowania np

float a = 70 2float b = (float)1000 1000 1000 1000 1000 1000printf(fn a)printf(fn b)

Operatory dodawania i odejmowania są określone roacutewnież gdy jednym z argumentoacutewjest wskaźnik a drugim liczba całkowita Ten drugi jest także określony gdy oba argumentysą wskaźnikami O takim użyciu tych operatoroacutew dowiesz się więcej CWskaźniki|w dalszejczęści książki

46 ROZDZIAŁ 8 OPERATORY

831 Inkrementacja i dekrementacja

Aby skroacutecić zapis wprowadzono dodatkowe operatory inkrementacji (++rdquo) i dekrementa-cji (ndashrdquo) ktoacutere dodatkowo mogą być pre- lub postfiksowe W rezultacie mamy więc czteryoperatory

pre-inkrementacja (++irdquo)

post-inkrementacja (i++rdquo)

pre-dekrementacja (ndashirdquo) i

post-dekrementacja (indashrdquo)

Operatory inkrementacji zwiększa a dekrementacji zmniejsza argument o jeden Ponadtooperatory pre- zwracają nową wartość argumentu natomiast post- starą wartość argumentu

int a b ca = 3b = a-- po operacji b=3 a=2 c = --b po operacji b=2 c=2

Czasami (szczegoacutelnie w C++) użycie operatoroacutew stawianych za argumentem jest niecomniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tym-czasową)

Bardzo ważne jest abyśmy poprawnie stosowali operatory dekrementacji i inkrementa-cji Chodzi o to aby w jednej instrukcji nie umieszczać kilku operatoroacutew ktoacutere modyfikująten sam obiekt (zmienną) Jeżeli taka sytuacja zaistnieje to efekt działania instrukcji jestnieokreślony Prostym przykładem mogą być następujące instrukcje

int a = 1a = a++a = ++aa = a++ + ++aprintf(d dn ++a ++a)printf(d dn a++ a++)

Kompilator potrafi ostrzegać przed takimi błędami - aby to czynił należy podać mujako argument opcję -Wsequence-point

84 Operacje bitoweOproacutecz operacji znanych z lekcji matematyki w podstawoacutewce język C został wyposażonytakże w operatory bitowe zdefiniowane dla liczb całkowitych Są to

negacja bitowa (˜rdquo)

koniunkcja bitowa (amprdquo)

alternatywa bitowa (|rdquo) i

84 OPERACJE BITOWE 47

alternatywa rozłączna () (ˆrdquo)

Działają one na poszczegoacutelnych bitach przez co mogą być szybsze od innych operacjiDziałanie tych operatoroacutew można zdefiniować za pomocą poniższych tabel

~ | 0 1 amp | 0 1 | | 0 1 ^ | 0 1-----+----- -----+----- -----+----- -----+-----

| 1 0 0 | 0 0 0 | 0 1 0 | 0 11 | 0 1 1 | 1 1 1 | 1 0

a | 0101 = 5b | 0011 = 3

-------+------~a | 1010 = 10~b | 1100 = 12

a amp b | 0001 = 1a | b | 0111 = 7a ^ b | 0110 = 6

Lub bardziej opisowo

negacja bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych argument miał bity roacutewne zero

koniunkcja bitowa daje wwyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych oba argumenty miały bity roacutewne jeden (mnemonik gdy wszystkie)

alternatywa bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden na wszystkichtych pozycjach na ktoacuterych jeden z argumentoacutew miał bit roacutewny jeden (mnemonik jeśli jest )

alternatywa rozłączna daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tychpozycjach na ktoacuterych tylko jeden z argumentoacutew miał bit roacutewny jeden (mnemonik gdy roacuteżne)

Przy okazji warto zauważyć że aˆbˆb to po prostu a Właściwość ta została wykorzystanaw roacuteżnych algorytmach szyfrowania oraz funkcjach haszujących Alternatywę wyłączną sto-suje się np do szyfrowania kodu wirusoacutew polimorficznych

841 Przesunięcie bitowe

Dodatkowo język C wyposażony jest w operatory przesunięcia bitowego w lewo (ltltrdquo) iprawo (gtgtrdquo) Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycjipodaną jako prawy argument Brzmi to może strasznie ale wcale takie nie jest Rozważmy-bitowe liczby bez znaku (taki hipotetyczny unsigned int) woacutewczas

a | altlt1 | altlt2 | agtgt1 | agtgt2------+------+------+------+------0001 | 0010 | 0100 | 0000 | 00000011 | 0110 | 1100 | 0001 | 00000101 | 1010 | 0100 | 0010 | 0001

48 ROZDZIAŁ 8 OPERATORY

1000 | 0000 | 0000 | 0100 | 00101111 | 1110 | 1100 | 0111 | 00111001 | 0010 | 0100 | 0100 | 0010

Nie jest to zatem takie straszne na jakie wygląda Widać że bity będące na skraju sątracone a w pusterdquo miejsca wpisywane są zera

Inaczej rzecz się ma jeżeli lewy argument jest liczbą ze znakiem Dla przesunięcia bito-wego w lewo a ltlt b jeżeli a jest nieujemna i wartość a middot 2b mieści się w zakresie liczby tojest to wynikiem operacji W przeciwnym wypadku działanie jest niezdefiniowane1

Dla przesunięcia bitowego w lewo jeżeli lewy argument jest nieujemny to operacja za-chowuje się tak jak w przypadku liczb bez znaku Jeżeli jest on ujemny to zachowanie jestzależne od implementacji

Zazwyczaj operacja przesunięcia w lewo zachowuje się tak samo jak dla liczb bez znakunatomiast przy przesuwaniu w prawo bit znaku nie zmienia się2

a | agtgt1 | agtgt2------+------+------0001 | 0000 | 00000011 | 0001 | 00000101 | 0010 | 00011000 | 1100 | 11101111 | 1111 | 11111001 | 1100 | 1110

Przesunięcie bitowe w lewo odpowiada pomnożeniu natomiast przesunięcie bitowe wprawo podzieleniu liczby przez dwa do potęgi jaką wyznacza prawy argument Jeżeli prawyargument jest ujemny lub większy lub roacutewny liczbie bitoacutew w typie działanie jest niezdefi-niowane

include ltstdiohgt

int main ()

int a = 6printf (6 ltlt 2 = dn altlt2) wypisze 24 printf (6 gtgt 2 = dn agtgt2) wypisze 1 return 0

85 PoroacutewnanieW języku C występują następujące operatory poroacutewnania

roacutewne (==rdquo)

roacuteżne (=rdquo)

mniejsze (ltrdquo)

1Niezdefiniowane w takim samym sensie jak niezdefiniowane jest zachowanie programu gdy proacutebujemy odwo-łać się do wartości wskazywanej przez wartość czy do zmiennych poza tablicą

2ale jeżeli zależy Ci na przenośności kodu nie możesz na tym polegać

85 POROacuteWNANIE 49

większe (gtrdquo)

mniejsze lub roacutewne (lt=rdquo) i

większe lub roacutewne (gt=rdquo)

Wykonują one odpowiednie poroacutewnanie swoich argumentoacutew i zwracają jedynkę jeżeliwarunek jest spełniony lub zero jeżeli nie jest

851 Częste błędy

Osoby ktoacutere poprzednio uczyły się innych językoacutew programowania często mają nawykużywania w instrukcjach logicznych zamiast operatora poroacutewnania == operatora przypi-sania = Ma to często zgubne efekty gdyż przypisanie zwraca wartość przypisaną lewemuargumentowi

Poroacutewnajmy ze sobą dwa warunki

(a = 1)(a == 1)

Pierwszy z nich zawsze będzie prawdziwy niezależnie od wartości zmiennej a Dziejesię tak ponieważ zostaje wykonane przypisanie do a wartości a następnie jako wartość jestzwracane to co zostało przypisane mdash czyli jeden Drugi natomiast będzie prawdziwy tylkogdy a jest roacutewne

W celu uniknięcia takich błędoacutew niektoacuterzy programiści zamiast pisać a == 1 piszą 1 == adzięki czemu pomyłka spowoduje że kompilator zgłosi błąd

Warto zauważyć że kompilator potrafi w pewnych sytuacjach wychwycić taki błądAby zaczął to robić należy podać mu argument -Wparentheses

Innym błędem jest użycie zwykłych operatoroacutew poroacutewnania do sprawdzania relacji po-między liczbami rzeczywistymi Ponieważ operacje zmiennoprzecinkowe wykonywane są zpewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie roacutewne Dlaprzykładu

include ltstdiohgtint main ()

float a b ca = 1e10 tj 10 do potęgi 10 b = 1e-10 tj 10 do potęgi -10 c = b c = b c = c + a c = b + a (teoretycznie) c = c - a c = b + a - a = b (teoretycznie) printf(dn c == b) wypisze 0

Obejściem jest poroacutewnywanie modułu roacuteżnicy liczb Roacutewnież i takie błędy kompilator potrafi wykrywać mdash aby to robił należy podać mu argument -Wfloat-equal

50 ROZDZIAŁ 8 OPERATORY

86 Operatory logiczneAnalogicznie do części operatoroacutew bitowych w C definiuje się operatory logiczne miano-wicie

negację (zaprzeczenie)

koniunkcję (ldquoirdquo) ampamp

alternatywę (ldquolubrdquo) ||

Działają one bardzo podobnie do operatoroacutew bitowych jednak zamiast operować na po-szczegoacutelnych bitach biorą pod uwagę wartość logiczną argumentoacutew

861 ldquoPrawdardquo i ldquofałszrdquo w języku C

Język C nie przewiduje specjalnego typu danych do operacji logicznych mdash operatory logicznemożna stosować do liczb (np typu int) tak samo jak operatory bitowe albo arytmetyczne

Wyrażenie ma wartość logiczną wtedy i tylko wtedy gdy jest roacutewne (jest ldquofałszywerdquo)W przeciwnym wypadku ma wartość (jest ldquoprawdziwerdquo) Operatory logiczne w wynikudają zawsze albo albo

Żeby w pełni uzmysłowić sobie co to to oznacza spoacutejrzmy na wynik wykonania poniż-szych trzech linijek

printf(koniunkcja dn 18 ampamp 19)printf(alternatywa dn a || b)printf(negacja dn 20)

koniunkcja 1alternatywa 1negacja 0

Liczba nie jest roacutewna więc ma wartość logiczną Podobnie ma wartość logiczną Dlatego ich koniunkcja jest roacutewna Znaki a i b zostaną w wyrażeniu logicznympotraktowane jako liczby o wartości odpowiadającej kodowi znaku mdash czyli oba będąmiały wartość logiczną

862 Skroacutecone obliczanie wyrażeń logiczny

Język C wykonuje skroacutecone obliczanie wyrażeń logicznych mdash to znaczy oblicza wyrażenietylko tak długo jak nie wie jaka będzie jego ostateczna wartość To znaczy idzie od lewejdo prawej obliczając kolejne wyrażenia (dodatkowo na kolejność wpływ mają nawiasy) i gdybędzie miał na tyle informacji by obliczyć wartość całości nie liczy reszty Może to wydawaćsię niejasne ale przyjrzyjmy się wyrażeniom logicznym

A ampamp BA || B

Jeśli A jest fałszywe to nie trzeba liczyć B w pierwszym wyrażeniu bo fałsz i dowolne wyra-żenie zawsze da fałsz Analogicznie jeśli A jest prawdziwe to wyrażenie jest prawdziwe iwartość B nie ma znaczenia

Poza zwiększoną szybkością zysk z takiego rozwiązania polega na możliwości stosowaniaefektoacutew ubocznych Idea efektu ubocznego opiera się na tym że w wyrażeniu można wywo-łać funkcje ktoacutere będą robiły poza zwracaniemwyniku inne rzeczy oraz używać podstawieńPopatrzmy na poniższy przykład

87 OPERATOR WYRAŻENIA WARUNKOWEGO 51

( (a gt 0) || (a lt 0) || (a = 1) )

Jeśli a będzie większe od to obliczona zostanie tylko wartość wyrażenia (a gt 0) mdash da onoprawdę czyli reszta obliczeń nie będzie potrzebna Jeśli a będzie mniejsze od zera najpierwzostanie obliczone pierwsze podwyrażenie a następnie drugie ktoacutere da prawdę Ciekawy bę-dzie jednak przypadek gdy a będzie roacutewne zero mdash do a zostanie wtedy podstawiona jedynkai całość wyrażenia zwroacuteci prawdę (bo jest traktowane jak prawda)

Efekty uboczne pozwalają na roacuteżne szaleństwa i wykonywanie złożonych operacji w sa-mych warunkach logicznych jednak przesadne używanie tego typu konstrukcji powodujeże kod staje się nieczytelny i jest uważane za zły styl programistyczny

87 Operator wyrażenia warunkowegoC posiada szczegoacutelny rodzaj operatora mdash to operator zwany też operatorem wyrażeniawarunkowego Jest to jedyny operator w tym języku przyjmujący trzy argumenty

a b c

Jego działanie wygląda następująco najpierw oceniana jest wartość logiczna wyrażenia ajeśli jest ono prawdziwe to zwracana jest wartość b jeśli natomiast wyrażenie a jest nie-prawdziwe zwracana jest wartość c

Praktyczne zastosowanie mdash znajdowanie większej z dwoacutech liczb

a = (bgt=c) b c Jeśli b jest większe bądź roacutewne c to zwroacuteć bW przeciwnym wypadku zwroacuteć c

lub zwracanie modułu liczby

a = a lt 0 -a a

Wartości wyrażeń są przy tym operatorze obliczane tylko jeżeli zachodzi taka potrzebanp w wyrażeniu 1 1 foo() funkcja foo() nie zostanie wywołana

88 Operator przecinekOperator przecinek jest dość dziwnym operatorem Powoduje on obliczanie wartości wyra-żeń od lewej do prawej po czym zwroacutecenie wartości ostatniego wyrażenia W zasadzie wnormalnym kodzie programu ma on niewielkie zastosowanie gdyż zamiast niego lepiej roz-dzielać instrukcje zwykłymi średnikami Ma on jednak zastosowanie w instrukcji sterującejfor

89 Operator sizeofOperator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanegotypu lub typu podanego wyrażenia Ma on dwa rodzaje sizeof(typ) lub sizeof wyrażeniePrzykładowo

include ltstdiohgt

int main()

52 ROZDZIAŁ 8 OPERATORY

printf(sizeof(short ) = dn sizeof(short ))printf(sizeof(int ) = dn sizeof(int ))printf(sizeof(long ) = dn sizeof(long ))printf(sizeof(float ) = dn sizeof(float ))printf(sizeof(double) = dn sizeof(double))return 0

Operator ten jest często wykorzystywany przy dynamicznej alokacji pamięci co zostanieopisane w rozdziale poświęconym wskaźnikom

Pomimo że w swej budowie operator sizeof bardzo przypomina funkcję to jednak niąnie jest Wynika to z trudności w implementacji takowej funkcji mdash jej specyfika musiałabyodnosić się bezpośrednio do kompilatora Ponadto jej argumentem musiałyby być typy anie zmienne W języku C nie jest możliwe przekazywanie typu jako argumentu Ponadtoczęsto zdarza się że rozmiar zmiennej musi być wiadomy jeszcze w czasie kompilacji mdash toewidentnie wyklucza implementację sizeof() jako funkcji

810 Inne operatory

Poza wyżej opisanymi operatorami istnieją jeszcze

operator []rdquo opisany przy okazji opisywania tablic

jednoargumentowe operatory rdquo i amprdquo opisane przy okazji opisywania wskaźnikoacutew

operatory rdquo i -gtrdquo opisywane przy okazji opisywania struktur i unii

operator ()rdquo będący operatorem wywołania funkcji

operator ()rdquo grupujący wyrażenia (np w celu zmiany kolejności obliczania

811 Priorytety i kolejność obliczeń

Jak w matematyce roacutewnież i w języku C obowiązuje pewna ustalona kolejność działań Abymoacutec ją określić należy ustalić dwa parametry danego operatora jego priorytet oraz łącz-ność Przykładowo operator mnożenia ma wyższy priorytet niż operator dodawania i z tegopowodu wwyrażeniu 2+2 middot2 najpierw wykonuje się mnożenie a dopiero potem dodawanie

Drugim parametrem jest łączność mdash określa ona od ktoacuterej stronywykonywane są działaniaw przypadku połączenia operatoroacutew o tym samym priorytecie Na przykład odejmowaniema łączność lewostronną i 2 minus 2 minus 2 da w wyniku - Gdyby miało łączność prawostronnąw wynikiem byłoby Przykładem matematycznego operatora ktoacutery ma łączność prawo-stronną jest potęgowanie np 322

jest roacutewne W języku C występuje dużo poziomoacutew operatoroacutew Poniżej przedstawiamy tabelkę ze

wszystkimi operatorami poczynając od tych z najwyższym priorytetem (wykonywanych napoczątku)

Duża liczba poziomoacutew pozwala czasami zaoszczędzić trochę milisekund w trakcie pisaniaprogramu i bajtoacutew na dysku gdyż często nawiasy nie są potrzebne nie należy jednak z tymprzesadzać gdyż kod programu może stać się mylący nie tylko dla innych ale po latach (czynawet i dniach) roacutewnież dla nas

812 KOLEJNOŚĆ WYLICZANIA ARGUMENTOacuteW OPERATORA 53

Tablica 81 Priorytety operatoroacutewOperator Łącznośćnawiasy nie dotyczyjednoargumentowe przyrostkowe [] -gt wywołanie funkcji postinkre-mentacja postdekrementacja

lewostronna

jednoargumentowe przedrostkowe ˜ + - amp sizeof preinkrementacjapredekrementacja rzutowanie

prawostronna

lewostronna+ - lewostronnaltlt gtgt lewostronnaltlt= gtgt= lewostronna== = lewostronnaamp lewostronnaˆ lewostronna| lewostronnaampamp lewostronna|| lewostronna prawostronnaoperatory przypisania prawostronna lewostronna

Warto także podkreślić że operator koniunkcji ma niższy priorytet niż operator poroacutew-nania3 Oznacza to że kod

if (flags amp FL_MASK == FL_FOO)

zazwyczaj da rezultat inny od oczekiwanego Najpierw bowiem wykona się poroacutewna-nie wartości FL MASK z wartością FL FOO a dopiero potem koniunkcja bitowa W takichsytuacjach należy pamiętać o użyciu nawiasoacutew

if ((flags amp FL_MASK) == FL_FOO)

Kompilator potrafi wykrywać takie błędy i aby to robił należy podać mu argument-Wparentheses

812 Kolejność wyliczania argumentoacutew operatoraW przypadku większości operatoroacutew (wyjątkami są tu ampamp || i przecinek) nie da się określićktoacutera wartość argumentu zostanie obliczona najpierw W większości przypadkoacutew nie mato większego znaczenia lecz w przypadku wyrażeń ktoacutere mają efekty uboczne wymuszeniekonkretnej kolejności może być potrzebne Weźmy dla przykładu program

include ltstdiohgt

int foo(int a) printf(dn a)

3Jest to zaszłość historyczna z czasoacutew gdy nie było logicznych operatoroacutew ampamp oraz || i zamiast nich stosowanooperatory bitowe amp oraz |

54 ROZDZIAŁ 8 OPERATORY

return 0

int main(void) return foo(1) + foo(2)

Otoacuteż nie wiemy czy najpierw zostanie wywołana funkcja foo z parametrem jeden czydwa Jeżeli ma to znaczenie należy użyć zmiennych pomocniczych zmieniając definicję funk-cji main na

int main(void) int tmp = foo(1)return tmp + foo(2)

Teraz już na pewno najpierw zostanie wypisana jedynka a potem dopiero dwoacutejka Sy-tuacja jeszcze bardziej się komplikuje gdy używamy wyrażeń z efektami ubocznymi jakoargumentoacutew funkcji np

include ltstdiohgt

int foo(int a) printf(dn a)return 0

int bar(int a int b int c int d) return a + b + c + d

int main(void) return foo(1) + foo(2) + foo(3) + foo(4)

Teraz też nie wiemy ktoacutera z permutacji liczb i zostanie wypisana i ponownienależy pomoacutec sobie zmiennymi tymczasowymi jeżeli zależy nam na konkretnej kolejności

int main(void) int tmp = foo(1)tmp += foo(2)tmp += foo(3)return tmp + foo(4)

813 Uwagi

W języku C++ wprowadzony został dodatkowo inny sposoacuteb zapisu rzutowania ktoacuterypozwala na łatwiejsze znalezienie w kodzie miejsc w ktoacuterych dokonujemy rzutowaniaWięcej na stronie C++Zmienne

814 ZOBACZ TEŻ 55

814 Zobacz też CSkładniaOperatory

56 ROZDZIAŁ 8 OPERATORY

Rozdział 9

Instrukcje sterujące

C jest językiem imperatywnym mdash oznacza to że instrukcje wykonują się jedna po drugiej wtakiej kolejności w jakiej są napisane Aby moacutec zmienić kolejność wykonywania instrukcjipotrzebne są instrukcje sterujące

Na wstępie przypomnijmy jeszcze informację z rozdziału Operatory że wyrażenie jestprawdziwe wtedy i tylko wtedy gdy jest roacuteżne od zera a fałszywe wtedy i tylko wtedy gdyjest roacutewne zeru

91 Instrukcje warunkowe

911 Instrukcja if

Użycie instrukcji if wygląda tak

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

dalsze instrukcje

Istnieje także możliwość reakcji na nieprawdziwość wyrażenia mdash wtedy należy zastosowaćsłowo kluczowe else

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

else blok wykonany jeśli wyrażenie jest nieprawdziwe

dalsze instrukcje

Przypatrzmy się bardziej ldquożyciowemurdquo programowi ktoacutery poroacutewnuje ze sobą dwie liczby

include ltstdiohgt

int main ()

int a ba = 4

57

58 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

b = 6if (a==b)

printf (a jest roacutewne bn) else

printf (a nie jest roacutewne bn)return 0

Stosowany jest też kroacutetszy zapis warunkoacutew logicznych korzystający z tego jak C rozumieprawdę i fałsz Jeśli zmienna a jest typu integer zamiast

if (a = 0) b = 1a

można napisać

if (a) b = 1a

a zamiast

if (a == 0) b = 1a

można napisać

if (a) b = 1a

Czasami zamiast pisać instrukcję if możemy użyć operatora wyrażenia warunkowego(patrz Operatory)

if (a = 0)b = 1a

elseb = 0

ma dokładnie taki sam efekt jak

b = (a =0) 1a 0

912 Instrukcja swit

Aby ograniczyćwielokrotne stosowanie instrukcji if możemy użyć swit Jej użyciewyglądatak

switch (wyrażenie) case wartość1 instrukcje jeśli wyrażenie == wartość1

breakcase wartość2 instrukcje jeśli wyrażenie == wartość2

break default instrukcje jeśli żaden z wcześniejszych warunkoacutew

break nie został spełniony

Należy pamiętać o użyciu break po zakończeniu listy instrukcji następujących po case Je-śli tego nie zrobimy program przejdzie do wykonywania instrukcji z następnego case Możemieć to fatalne skutki

91 INSTRUKCJE WARUNKOWE 59

include ltstdiohgt

int main ()

int a bprintf (Podaj a )scanf (d ampa)printf (Podaj b )scanf (d ampb)switch (b)

case 0 printf (Nie można dzielić przez 0n) tutaj zabrakło break default printf (ab=dn ab)

return 0

A czasami może być celowym zabiegiem (tzw ldquofall-throughrdquo) mdash woacutewczas warto zazna-czyć to w komentarzu Oto przykład

include ltstdiohgt

int main ()

int a = 4switch ((a3))

case 0printf (Liczba d dzieli się przez 3n a)break

case -2case -1case 1case 2printf (Liczba d nie dzieli się przez 3n a)break

return 0

Przeanalizujmy teraz działający przykład

include ltstdiohgt

int main ()

unsigned int dzieci = 3 podatek=1000switch (dzieci)

case 0 break brak dzieci - czyli brak ulgi case 1 ulga 2

podatek = podatek - (podatek100 2)break

case 2 ulga 5

60 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

podatek = podatek - (podatek100 5)break

default ulga 10 podatek = podatek - (podatek10010)break

printf (Do zapłaty dn podatek)

92 Pętle

921 Instrukcja while

Często zdarza się że nasz programmusi wielokrotnie powtarzać ten sam ciąg instrukcji Abynie przepisywać wiele razy tego samego kodu można skorzystać z tzw pętli Pętla wykonujesię dotąd dopoacuteki prawdziwy jest warunek

while (warunek) instrukcje do wykonania w pętli

dalsze instrukcje

Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie Załoacuteżmy żemamy obliczyć kwadraty liczb od do Piszemy zatem program

include ltstdiohgt

int main ()

int a = 1while (a lt= 10) dopoacuteki a nie przekracza 10

printf (dn aa) wypisz aa na ekran++a zwiększamy a o jeden

return 0

Po analizie kodu mogą nasunąć się dwa pytania

Po co zwiększać wartość a o jeden Otoacuteż gdybyśmy nie dodali instrukcji zwiększająceja to warunek zawsze byłby spełniony a pętla ldquokręciłabyrdquo się w nieskończoność

Dlaczego warunek to ldquoa lt= rdquo a nie ldquoa=rdquo Odpowiedź jest dość prosta Pętlasprawdza warunek przed wykonaniem kolejnego ldquoobroturdquo Dlatego też gdyby waru-nek brzmiał ldquoa=rdquo to dla a= jest on nieprawdziwy i pętla nie wykonałaby ostatniejiteracji przez co program generowałby kwadraty liczb od do a nie do

922 Instrukcja for

Od instrukcji while czasami wygodniejsza jest instrukcja for Umożliwia ona wpisanie usta-wiania zmiennej sprawdzania warunku i inkrementowania zmiennej w jednej linijce co czę-sto zwiększa czytelność kodu Instrukcję for stosuje się w następujący sposoacuteb

92 PĘTLE 61

for (wyrażenie1 wyrażenie2 wyrażenie3) instrukcje do wykonania w pętli

dalsze instrukcje

Jak widać pętla for znacznie roacuteżni się od tego typu pętli znanych w innych językachprogramowania Opiszemy więc co oznaczają poszczegoacutelne wyrażenia

wyrażenie mdash jest to instrukcja ktoacutera będzie wykonana przed pierwszym przebiegiempętli Zwykle jest to inicjalizacja zmiennej ktoacutera będzie służyła jako ldquolicznikrdquo przebie-goacutew pętli

wyrażenie mdash jest warunkiem zakończenia pętli Pętla wykonuje się tak długo jakprawdziwy jest ten warunek

wyrażenie mdash jest to instrukcja ktoacutera wykonywana będzie po każdym przejściu pętliZamieszczone są tu instrukcje ktoacutere zwiększają licznik o odpowiednią wartość

Jeżeli wewnątrz pętli nie ma żadnych instrukcji continue (opisanych niżej) to jest onaroacutewnoważna z

wyrażenie1while (wyrażenie2)

instrukcje do wykonania w pętli wyrażenie3

dalsze instrukcje

Ważną rzeczą jest tutaj to żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla forPoczątkującym programistom nieznajomość tego faktu sprawia wiele problemoacutew

W pierwszej kolejności w pętli for wykonuje się wyrażenie1 Wykonuje się ono zawszenawet jeżeli warunek przebiegu pętli jest od samego początku fałszywy Po wykonaniuwyrażenie1 pętla for sprawdza warunek zawarty w wyrażenie2 jeżeli jest on prawdziwyto wykonywana jest treść pętli for czyli najczęściej to co znajduje się między klamrami lubgdy ich nie ma następna pojedyncza instrukcja W szczegoacutelności musimy pamiętać że samśrednik też jest instrukcją mdash instrukcją pustą Gdy już zostanie wykonana treść pętli for na-stępuje wykonanie wyrażenie3 Należy zapamiętać że wyrażenie zostanie wykonane nawetjeżeli był to już ostatni obieg pętli Poniższe przykłady pętli for w rezultacie dadzą ten samwynik Wypiszą na ekran liczby od do

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 printf(d i++ ) )

Dwa pierwsze przykłady korzystają z własności struktury blokowej kolejny przykład jestjuż bardziej wyrafinowany i korzysta z tego że jako wyrażenie3może zostać podane dowolnebardziej skomplikowane wyrażenie zawierające w sobie inne podwyrażenia A oto kolejnyprogram ktoacutery najpierw wyświetla liczby w kolejności rosnącej a następnie wraca

62 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

include ltstdiohgtint main()int ifor(i=1 ilt=5 ++i)

printf(d i)

for( igt=1 i--)printf(d i)

return 0

Po analizie powyższego kodu początkujący programista może stwierdzić że pętla wy-pisze 123454321 Stanie się natomiast inaczej Wynikiem działania powyższego programubędzie ciąg cyfr 12345654321 Pierwsza pętla wypisze cyfry ldquordquo lecz po ostatnim swoimobiegu pętla for (tak jak zwykle) zinkrementuje zmienną i Gdy druga pętla przystąpi dopracy zacznie ona odliczać począwszy od liczby i= a nie By spowodować wyświetlanieliczb od do i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli fora pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej i o

Niech podsumowaniem będzie jakiś działający fragment kodu ktoacutery może obliczać war-tości kwadratoacutew liczb od do

include ltstdiohgt

int main ()

int afor (a=1 alt=10 ++a)

printf (dn aa)return 0

W kodzie źroacutedłowym spotyka się często inkrementację i++ Jest to zły zwyczaj biorącysię z wzorowania się na nazwie języka C++ Post-inkrementacja i++ powoduje że tworzonyjest obiekt tymczasowy ktoacutery jest zwracany jako wynik operacji (choć wynik ten nie jestnigdzie czytany) Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie ale wpętli ldquoforrdquo takie kopiowanie odbywa się po każdym przebiegu pętli Dodatkowo w C++ po-dobną konstrukcję stosuje się do obiektoacutew mdash kopiowanie obiektu może być już czasochłonnączynnością Dlatego w pętli ldquoforrdquo należy stosować wyłącznie ++i

923 Instrukcja dowhile

Pętle while i for mają jeden zasadniczy mankament mdash może się zdarzyć że nie wykonają sięani razu Aby mieć pewność że nasza pętla będzie miała co najmniej jeden przebieg musimyzastosować pętlę do while Wygląda ona następująco

92 PĘTLE 63

do instrukcje do wykonania w pętli

while (warunek) dalsze instrukcje

Zasadniczą roacuteżnicą pętli do while jest fakt iż sprawdza ona warunek pod koniec swojegoprzebiegu To właśnie ta cecha decyduje o tym że pętla wykona się co najmniej raz A terazprzykład działającego kodu ktoacutery tym razem będzie obliczał trzecią potęgę liczb od do

include ltstdiohgt

int main ()

int a = 1do

printf (dn aaa)++a

while (a lt= 10)return 0

Może się to wydać zaskakujące ale roacutewnież przy tej pętli zamiast bloku instrukcji możnazastosować pojedynczą instrukcję np

include ltstdiohgt

int main ()

int a = 1do printf (dn aaa) while (++a lt= 10)return 0

924 Instrukcja break

Instrukcja break pozwala na opuszczenie wykonywania pętli w dowolnym momencie Przy-kład użycia

int afor (a=1 a = 9 ++a)

if (a == 5) breakprintf (dn a)

Program wykona tylko przebiegi pętli gdyż przy przebiegu instrukcja break spowo-duje wyjście z pętli

Break i pętle nieskończone

W przypadku pętli for nie trzeba podawać warunku W takim przypadku kompilator przyj-mie że warunek jest stale spełniony Oznacza to że poniższe pętle są roacutewnoważne

64 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

for () for (1) for (aaa) gdzie a jest dowolną liczba rzeczywistą roacuteżną od 0while (1) do while (1)

Takie pętle nazywamy pętlami nieskończonymi ktoacutere przerwać może jedynie instrukcjabreak1(z racji tego że warunek pętli zawsze jest prawdziwy) 2

Wszystkie fragmenty kodu działają identycznie

int i = 0for (i=5++i)

kod

int i = 0for (++i)

if (i == 5) break

int i = 0for ()

if (i == 5) break++i

925 Instrukcja continue

W przeciwieństwie do break ktoacutera przerywa wykonywanie pętli instrukcja continue powo-duje przejście do następnej iteracji o ile tylko warunek pętli jest spełniony Przykład

int ifor (i = 0 i lt 100 ++i)

printf (Poczatekn)if (i gt 40) continue printf (Koniecn)

Dla wartości i większej od nie będzie wyświetlany komunikat ldquoKoniecrdquo Pętla wykonapełne przejść

Oto praktyczny przykład użycia tej instrukcji

include ltstdiohgtint main()

int i

1Tak naprawdę podobną operacje możemy wykonać za pomocą polecenia goto W praktyce jednak stosujesię zasadę że break stosuje się do przerwania działania pętli i wyjścia z niej goto stosuje się natomiast wtedykiedy chce się wydostać się z kilku zagnieżdżonych pętli za jednym zamachem Do przerwania pracy pętli mogąnam jeszcze posłużyć polecenia exit() lub return ale woacutewczas zakończymy nie tylko działanie pętli ale i całegoprogramufunkcji

2Żartobliwie można powiedzieć że stosując pętlę nieskończoną to najlepiej korzystać z pętli for() gdyżwymaga ona napisania najmniejszej liczby znakoacutew w poroacutewnaniu do innych konstrukcji

93 INSTRUKCJA GOTO 65

for (i = 1 i lt= 50 ++i) if (i4==0) continue printf (d i)

return 0

Powyższy program generuje liczby z zakresu od do ktoacutere nie są podzielne przez

93 Instrukcja goto

Istnieje także instrukcja ktoacutera dokonuje skoku do dowolnegomiejsca programu oznaczonegotzw etykietą

etykieta instrukcje goto etykieta

Uwaga kompilator w wersji i wyższych jest bardzo uczulony na etykiety za-mieszczone przed nawiasem klamrowym zamykającym blok instrukcji Innymi słowy nie-dopuszczalne jest umieszczanie etykiety zaraz przed klamrą ktoacutera kończy blok instrukcjizawartych np w pętli for Można natomiast stosować etykietę przed klamrą kończącą danąfunkcję

Instrukcja goto łamie sekwencję instrukcji i powoduje skok do dowolnie odległego miej-sca w programie - co może mieć nieprzewidziane skutki Zbyt częste używanie goto możeprowadzić do trudnych do zlokalizowania błędoacutew Oproacutecz tego kompilatory mają kłopotyz optymalizacją kodu w ktoacuterym występują skoki Z tego powodu zaleca się ograniczeniezastosowania tej instrukcji wyłącznie do opuszczania wielokrotnie zagnieżdżonych pętli

Przykład uzasadnionego użycia

int ijfor (i = 0 i lt 10 ++i)

for (j = i j lt i+10 ++j) if (i + j 21 == 0) goto koniec

koniec dalsza czesc programu

94 Natymiastowe kończenie programu mdash funkcja exit

Program może zostać w każdej chwili zakończony mdash do tego właśnie celu służy funkcja exitUżywamy jej następująco

exit (kod_wyjścia)

66 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

Liczba całkowita kod wyjścia jest przekazywana do procesu macierzystego dzięki czemudostaje on informację czy programw ktoacuterymwywołaliśmy tą funkcję zakończył się popraw-nie lub czy się tak nie stało Kody wyjścia są nieustandaryzowane i żeby program był w pełniprzenośny należy stosować makra EXIT SUCCESS i EXIT FAILURE choć na wielu systemach kod oznacza poprawne zakończenie a kod roacuteżny od błędne W każdym przypadku jeżeli naszprogram potrafi generować wiele roacuteżnych kodoacutew warto je wszystkie udokumentować w ewdokumentacji Są one też czasem pomocne przy wykrywaniu błędoacutew

95 Uwagi W języku C++ można deklarować zmienne w nagłoacutewku pętli ldquoforrdquo w następujący spo-

soacuteb for(int i=0 ilt10 ++i) (więcej informacji w C++Zmienne)

Rozdział 10

Podstawowe procedury wejścia iwyjścia

101 Wejściewyjście

Komputer byłby całkowicie bezużyteczny gdyby użytkownik nie moacutegł się z nim porozumieć(tj wprowadzić danych lub otrzymać wynikoacutew pracy programu) Programy komputerowesłużą w największym uproszczeniu do obroacutebki danych mdash więc muszą te dane jakoś od nasotrzymać przetworzyć i przekazać nam wynik

Takiewczytywanie i ldquowyrzucanierdquo danychw terminologii komputerowej nazywamywej-ściem (input) iwyjściem (output) Bardzo często moacutewi się o wejściu i wyjściu danych łączniemdash inputoutput albo po prostu IO

W C do komunikacji z użytkownikiem służą odpowiednie funkcje Zresztą do wielu za-dań w C służą funkcje Używając funkcji nie musimy wiedzieć w jaki sposoacuteb komputerwykonuje jakieś zadanie interesuje nas tylko to co ta funkcja robi Funkcje niejako ldquowyko-nują za nas część pracyrdquo ponieważ nie musimy pisać być może dziesiątek linijek kodu żebynp wypisać tekst na ekranie (wbrew pozorom mdash kod funkcji wyświetlającej tekst na ekraniejest dość skomplikowany) Jeszcze taka uwaga mdash gdy piszemy o jakiejś funkcji zazwyczajpodając jej nazwę dopisujemy na końcu nawias

printf()scanf()

żeby było jasne że chodzi o funkcję a nie o coś innego

Wyżej wymienione funkcje to jedne z najczęściej używanych funkcji w C mdash pierwszasłuży do wypisywania danych na ekran natomiast druga do wczytywania danych z klawia-tury1

1W zasadzie standard C nie definiuje czegoś takiego jak ekran i klawiatura mdash mowa w nim o standardowymwyjściu i standardowym wejściu Zazwyczaj jest to właśnie ekran i klawiatura ale nie zawsze W szczegoacutelności użyt-kownicy Linuksa lub innych systemoacutew uniksowych mogą być przyzwyczajeniu do przekierowania wejściawyjściazdo pliku czy łączenie komend w potoki (ang pipe) W takich sytuacjach dane nie są wyświetlane na ekranie aniodczytywane z klawiatury

67

68 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

102 Funkcje wyjścia

1021 Funkcja printf

W przykładzie ldquoHello Worldrdquo użyliśmy już jednej z dostępnych funkcji wyjścia a miano-wicie funkcji printf() Z punktu widzenia swoich możliwości jest to jedna z bardziej skom-plikowanych funkcji a jednocześnie jest jedną z najczęściej używanych Przyjrzyjmy sięponownie kodowi programu ldquoHello Worldrdquo

include ltstdiohgt

int main(void)

printf(Hello worldn)return 0

Po skompilowaniu i uruchomieniu program wypisze na ekranie

Hello world

W naszym przykładowym programie chcąc by funkcja printf() wypisała tekst na ekra-nie umieściliśmy go w cudzysłowach wewnątrz nawiasoacutew Ogoacutelnie wywołanie funkcjiprintf() wygląda następująco

printf(format argument1 argument2 )

Przykładowo

int i = 500printf(Liczbami całkowitymi są na przykład i oraz in 1 i)

wypisze

Liczbami całkowitymi są na przykład 1 oraz 500

Format to napis ujęty w cudzysłowy ktoacutery określa ogoacutelny kształt schemat tego co ma byćwyświetlone Format jest drukowany tak jak go napiszemy jednak niektoacutere znaki specjalnezostaną w nim podmienione na co innego Przykładowo znak specjalny n jest zamienianyna znak nowej linii 2 Natomiast procent jest podmieniany na jeden z argumentoacutew Po pro-cencie następuje specyfikacja jak wyświetlić dany argument W tym przykładzie i (od int)oznacza że argument ma być wyświetlony jak liczba całkowita W związku z tym że i mają specjalne znaczenie aby wydrukować je należy użyć ich podwoacutejnie

printf(Procent Backslash )

drukuje

Procent Backslash

(bez przejścia do nowej linii) Na liście argumentoacutew możemy mieszać ze sobą zmienne roacuteż-nych typoacutew liczby napisy itp w dowolnej liczbie Funkcja printf przyjmie ich tyle ile tylkonapiszemy Należy uważać by nie pomylić się w formatowaniu

2Zmiana ta następuje w momencie kompilacji programu i dotyczy wszystkich literałoacutew napisowych Nie jestto jakaś szczegoacutelna własność funkcji printf() Więcej o tego typu sekwencjach i ciągach znakoacutew w szczegoacutelnościopisane jest w rozdziale Napisy

103 FUNKCJA PUTS 69

int i = 5printf(i s i 5 4 napis) powinno być i i s

Przywłączeniu ostrzeżeń (opcja -Wall lub -WformatwGCC) kompilator powinien nas ostrzecgdy format nie odpowiada podanym elementom

Najczęstsze użycie printf()

printf(i i) gdy i jest typu int zamiast i można użyć d

printf(f i) gdy i jest typu float lub double

printf(c i) gdy i jest typu char (i chcemy wydrukować znak)

printf(s i) gdy i jest napisem (typu char)

Funkcja printf() nie jest żadną specjalną konstrukcją języka i łańcuch formatujący możebyć podany jako zmienna W związku z tym możliwa jest np taka konstrukcja

include ltstdiohgt

int main(void)

char buf[100]scanf(99s buf) funkcja wczytuje tekst do tablicy buf printf(buf)return 0

Program wczytuje tekst a następnie wypisuje go Jednak ponieważ znak procentu jesttraktowany w specjalny sposoacuteb toteż jeżeli na wejściu pojawi się ciąg znakoacutew zawierającyten znak mogą się stać roacuteżne dziwne rzeczy Między innymi z tego powodu w takich sytu-acjach lepiej używać funkcji puts() lub fputs() opisanych niżej lub wywołania printf(szmienna)

Więcej o funkcji printf()

103 Funkcja putsFunkcja puts() przyjmuje jako swoacutej argument ciąg znakoacutew ktoacutery następnie bezmyślnie wy-pisuje na ekran kończąc go znakiem przejścia do nowej linii W ten sposoacuteb nasz pierwszyprogram moglibyśmy napisać w ten sposoacuteb

include ltstdiohgt

int main(void)

puts(Hello world)return 0

W swoim działaniu funkcja ta jest w zasadzie identyczna do wywołania printf(snargument) jednak prawdopodobnie będzie działać szybciej Jedynym jejmankamentemmożebyć fakt że zawsze na końcu podawany jest znak przejścia do nowej linii Jeżeli jest to efektniepożądany (nie zawsze tak jest) należy skorzystać z funkcji fputs() opisanej niżej lub wy-wołania printf(s argument)

Więcej o funkcji puts()

70 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

104 Funkcja fputs

Opisując funkcję fputs() wybiegamy już trochę w przyszłość (a konkretnie do opisu operacjina plikach) ale warto o niej wspomnieć już teraz gdyż umożliwia ona wypisanie swojegoargumentu bez wypisania na końcu znaku przejścia do nowej linii

include ltstdiohgt

int main(void)

fputs(Hello worldn stdout)return 0

W chwili obecnej możesz się nie przejmować tym zagadkowym stdout wpisanym jakodrugi argument funkcji Jest to określenie strumienia wyjściowego (w naszymwypadku stan-dardowe wyjście mdash standard output)

Więcej o funkcji fputs()

1041 Funkcja putar

Funkcja putchar() służy do wypisywania pojedynczych znakoacutew Przykładowo jeżeli chcieli-byśmy napisać programwypisującyw prostej tabelce wszystkie liczby od do moglibyśmyto zrobić tak

include ltstdiohgt

int main(void)

int i = 0for ( ilt100 ++i) Nie jest to pierwsza liczba w wierszu if (i 10)

putchar( )printf(2d i) Jest to ostatnia liczba w wierszu if ((i 10)==9)

putchar(n)

return 0

Więcej o funkcji putchar()

105 FUNKCJE WEJŚCIA 71

105 Funkcje wejścia

1051 Funkcja scanf()

Teraz pomyślmy o sytuacji odwrotnej Tym razem to użytkownik musi powiedzieć coś pro-gramowi W poniższym przykładzie program podaje kwadrat liczby podanej przez użytkow-nika

include ltstdiohgt

int main ()

int liczba = 0printf (Podaj liczbę )scanf (d ampliczba)printf (dd=dn liczba liczba liczbaliczba)return 0

Zauważyłeś że w tej funkcji przy zmiennej pojawił się nowy operator mdash amp (etka) Jeston ważny gdyż bez niego funkcja scanf() nie skopiuje odczytanej wartości liczby do odpo-wiedniej zmiennej Właściwie oznacza przekazanie do funkcji adresu zmiennej by funkcjamogła zmienić jej wartość Nie musisz teraz rozumieć jak to się odbywa wszystko zostaniewyjaśnione w rozdziale Wskaźniki

Oznaczenia są podobne takie jak przy printf() czyli scanf(i ampliczba) wczytuje liczbętypu int scanf(f ampliczba) ndash liczbę typu float a scanf(s tablica znakoacutew) ciąg zna-koacutew Ale czemu w tym ostatnim przypadku nie ma etki Otoacuteż gdy podajemy jako argumentdo funkcji wyrażenie typu tablicowego zamieniane jest ono automatycznie na adres pierw-szego elementu tablicy Będzie to dokładniej opisane w rozdziale poświęconym wskaźnikom

Brak etki jest częstym błędem szczegoacutelnie wśroacuted początkujących programistoacutew Ponie-waż funkcja scanf() akceptuje zmienną liczbę argumentoacutew to nawet kompilator może miećkłopoty z wychwyceniem takich błędoacutew (konkretnie chodzi o to że standard nie wymagaod kompilatora wykrywania takich pomyłek) choć kompilator GCC radzi sobie z tym jeżelipodamy mu argument -Wformat

Należy jednak uważać na to ostatnie użycie Rozważmy na przykład poniższy kod

include ltstdiohgt

int main(void)

char tablica[100] 1 scanf(s tablica) 2 return 0

Robi on niewiele W linijce deklarujemy tablicę znakoacutew czyli mogącą przechowaćnapis długości znakoacutew Nie przejmuj się jeżeli nie do końca to wszystko rozumiesz mdash po-jęcia takie jak tablica czy ciąg znakoacutew staną się dla Ciebie jasne w miarę czytania kolejnych

72 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

rozdziałoacutew W linijce wywołujemy funkcję scanf() ktoacutera odczytuje tekst ze standardowegowejścia Nie zna ona jednak rozmiaru tablicy i nie wie ile znakoacutewmoże ona przechować przezco będzie czytać tyle znakoacutew aż napotka biały znak (format s nakazuje czytanie pojedyn-czego słowa) co może doprowadzić do przepełnienia bufora Niebezpieczne skutki czegośtakiego opisane są w rozdziale poświęconym napisom Na chwilę obecną musisz zapamiętaćżeby zaraz po znaku procentu podawać maksymalną liczbę znakoacutew ktoacutere może przechowaćbufor czyli liczbę o jeden mniejszą niż rozmiar tablicy Bezpieczna wersją powyższego kodujest

include ltstdiohgt

int main(void)

char tablica[100]scanf(99s tablica)return 0

Funkcja scanf() zwraca liczbę poprawnie wczytanych zmiennych lub EOF jeżeli nie majuż danych w strumieniu lub nastąpił błąd Załoacuteżmy dla przykładu że chcemy stworzyćprogram ktoacutery odczytuje po kolei liczby i wypisuje ich potęgi W pewnym momenciedane się kończą lub jest wprowadzana niepoprawna dana i woacutewczas nasz program powinienzakończyć działanie Aby to zrobić należy sprawdzać wartość zwracaną przez funkcję scanf()w warunku pętli

include ltstdiohgt

int main(void)

int nwhile (scanf(d ampn)==1)printf(dn nnn)

return 0

Podobnie możemy napisać program ktoacutery wczytuje po dwie liczby i je sumuje

include ltstdiohgt

int main(void)

int a bwhile (scanf(d d ampa ampb)==2)printf(dn a+b)

return 0

105 FUNKCJE WEJŚCIA 73

Rozpatrzmy teraz trochę bardziej skomplikowany przykład Otoacuteż ponownie jak poprzed-nio nasz program będzie wypisywał potęgę podanej liczby ale tym razem musi ignorowaćbłędne dane (tzn pomijać ciągi znakoacutew ktoacutere nie są liczbami) i kończyć działanie tylko wmomencie gdy nastąpi błąd odczytu lub koniec pliku3

include ltstdiohgt

int main(void)

int result ndoresult = scanf(d ampn)if (result==1)

printf(dn nnn)else if (result) result to to samo co result==0

result = scanf(s)

while (result=EOF)return 0

Zastanoacutewmy się przez chwilę co się dzieje w programie Najpierw wywoływana jestfunkcja scanf() i następuje proacuteba odczytu liczby typu int Jeżeli funkcja zwroacuteciła to liczbazostała poprawnie odczytana i następuje wypisanie jej trzeciej potęgi Jeżeli funkcja zwroacuteciła to na wejściu były jakieś dane ktoacutere nie wyglądały jak liczba W tej sytuacji wywołujemyfunkcję scanf() z formatem odczytującym dowolny ciąg znakoacutew nie będący białymi znakamiz jednoczesnym określeniem żeby nie zapisywała nigdzie wyniku W ten sposoacuteb niepopraw-nie wpisana dana jest omijana Pętla głoacutewna wykonuje się tak długo jak długo funkcja scanf()nie zwroacuteci wartości EOF

Więcej o funkcji scanf()

1052 Funkcja gets

Funkcja gets służy do wczytania pojedynczej linii Może Ci się to wydać dziwne ale funkcjitej nie należy używać pod żadnym pozorem Przyjmuje ona jeden argument mdash adres pierw-szego elementu tablicy do ktoacuterego należy zapisać odczytaną linię mdash i nic poza tym Z tegopowodu nie ma żadnej możliwości przekazania do tej funkcji rozmiaru bufora podanego jakoargument Podobnie jak w przypadku scanf() może to doprowadzić do przepełnienia buforaco może mieć tragiczne skutki Zamiast tej funkcji należy używać funkcji fgets()

Więcej o funkcji gets()

1053 Funkcja fgets

Funkcja fgets() jest bezpieczną wersją funkcji gets() ktoacutera dodatkowo może operować nadowolnych strumieniach wejściowych Jej użycie jest następujące

3Jak rozroacuteżniać te dwa zdarzenia dowiesz się w rozdziale Czytanie i pisanie do plikoacutew

74 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

fgets(tablica_znakoacutew rozmiar_tablicy_znakoacutew stdin)

Na chwilę obecną nie musisz się przejmować ostatnim argumentem (jest to określeniestrumienia w naszym przypadku standardowewejście mdash standard input) Funkcja czyta tekstaż do napotkania znaku przejścia do nowej linii ktoacutery także zapisuje w wynikowej tablicy(funkcja gets() tego nie robi) Jeżeli brakuje miejsca w tablicy to funkcja przerywa czytanie wten sposoacuteb aby sprawdzić czy została wczytana cała linia czy tylko jej część należy sprawdzićczy ostatnim znakiem nie jest znak przejścia do nowej linii Jeżeli nastąpił jakiś błąd lub nawejściu nie ma już danych funkcja zwraca wartość NULL

include ltstdiohgt

int main(void) char buffer[128] whole_line = 1 chwhile (fgets(buffer sizeof buffer stdin)) 1

if (whole_line) 2 putchar(gt)if (buffer[0]=gt)

putchar( )

fputs(buffer stdout) 3 for (ch = buffer ch ampamp ch=n ++ch) 4 whole_line = ch == n

if (whole_line)

putchar(n)return 0

Powyższy kod wczytuje dane ze standardowego wejścia mdash linia po linii mdash i dodaje napoczątku każdej linii znak większości po ktoacuterym dodaje spację jeżeli pierwszym znakiem nalinii nie jest znak większości W linijce następuje odczytywanie linii Jeżeli nie ma już wię-cej danych lub nastąpił błąd wejścia funkcja zwraca wartość NULL ktoacutera ma logiczną war-tość i woacutewczas pętla kończy działanie W przeciwnym wypadku funkcja zwraca po prostupierwszy argument ktoacutery ma wartość logiczną W linijce sprawdzamy czy poprzedniewywołanie funkcji wczytało całą linię czy tylko jej część mdash jeżeli całą to teraz jesteśmy napoczątku linii i należy dodać znakwiększości W linii najzwyczajniej w świecie wypisujemylinię W linii przeszukujemy tablicę znak po znaku aż do momentu gdy znajdziemy znako kodzie kończącym ciąg znakoacutew albo znak przejścia do nowej linii Ten drugi przypadekoznacza że funkcja fgets() wczytała całą linię

Więcej o funkcji fgets()

1054 Funkcja getar()

Jest to bardzo prosta funkcja wczytująca znak z klawiatury W wielu przypadkach danemogą być buforowane przez co wysyłane są do programu dopiero gdy bufor zostaje przepeł-niony lub na wejściu jest znak przejścia do nowej linii Z tego powodu po wpisaniu danegoznaku należy nacisnąć klawisz enter aczkolwiek trzeba pamiętać że w następnym wywoła-niu zostanie zwroacutecony znak przejścia do nowej linii Gdy nastąpił błąd lub nie ma już więcej

105 FUNKCJE WEJŚCIA 75

danych funkcja zwraca wartość EOF (ktoacutera ma jednak wartość logiczną toteż zwykła pętlawhile (getchar()) nie da oczekiwanego rezultatu)

include ltstdiohgt

int main(void)

int cwhile ((c = getchar())=EOF)

if (c== ) c = _

putchar(c)

return 0

Ten prosty program wczytuje dane znak po znaku i zamienia wszystkie spacje na znakipodkreślenia Może wydać się dziwne że zmienną c zdefiniowaliśmy jako trzymającą typ inta nie char Właśnie taki typ (tj int) zwraca funkcja getchar() i jest to konieczne ponieważwartość EOF wykracza poza zakres wartości typu char (gdyby tak nie było to nie byłobymożliwości rozroacuteżnienia wartości EOF od poprawnie wczytanego znaku) Więcej o funkcjigetchar()

76 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

Rozdział 11

Funkcje

W matematyce pod pojęciem funkcji rozumiemy twoacuter ktoacutery pobiera pewną liczbę argumen-toacutew i zwraca wynik1 Jeśli dla przykładu weźmiemy funkcję sin(x) to x będzie zmiennąrzeczywistą ktoacutera określa kąt a w rezultacie otrzymamy inną liczbę rzeczywistą mdash sinustego kąta

W C funkcja (czasami nazywana podprogramem rzadziej procedurą) to wydzielona częśćprogramu ktoacutera przetwarza argumenty i ewentualnie zwraca wartość ktoacutera następnie możebyć wykorzystana jako argument w innych działaniach lub funkcjach Funkcja może posia-dać własne zmienne lokalne W odroacuteżnieniu od funkcji matematycznych funkcje w C mogązwracać dla tych samych argumentoacutew roacuteżne wartości

Po lekturze poprzednich części podręcznika zapewne moacutegłbyś podać kilka przykładoacutewfunkcji z ktoacuterych korzystałeś Były to np

funkcja printf() drukująca tekst na ekranie czy

funkcja main() czyli głoacutewna funkcja programu

Głoacutewną motywacją tworzenia funkcji jest unikanie powtarzania kilka razy tego samegokodu W poniższym fragmencie

for(i=1 i lt= 5 ++i) printf(d ii)

for(i=1 i lt= 5 ++i)

printf(d iii)for(i=1 i lt= 5 ++i)

printf(d ii)

widzimy że pierwsza i trzecia pętla for są takie same Zamiast kopiować fragment kodukilka razy (co jest mało wygodne i może powodować błędy) lepszym rozwiązaniem mogłobybyć wydzielenie tego fragmentu tak by można go było wywoływać kilka razy Tak właśniedziałają funkcje

Innym niemniej ważnym powodem używania funkcji jest rozbicie programu na frag-menty wg ich funkcjonalności Oznacza to że z jeden duży program dzieli się na mniejsze

1Aby nie urażać matematykoacutew sprecyzujmy że chodzi o relację między zbiorami X i Y (X jest dziedziną Y jestprzeciwdziedziną) takie że każdy element zbioru X jest w relacji z dokładnie jednym elementem ze zbioru Y

77

78 ROZDZIAŁ 11 FUNKCJE

funkcje ktoacutere są ldquowyspecjalizowanerdquo w wykonywaniu określonych czynności Dzięki temułatwiej jest zlokalizować błąd Ponadto takie funkcje można potem przenieść do innych pro-gramoacutew

111 Tworzenie funkcji

Dobrze jest uczyć się na przykładach Rozważmy następujący kod

int iloczyn (int x int y)

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

int iloczyn (int x int y) to nagłoacutewek funkcji ktoacutery opisuje jakie argumenty przyj-muje funkcja i jaką wartość zwraca (funkcja może przyjmować wiele argumentoacutew lecz możezwracać tylko jedną wartość)2 Na początku podajemy typ zwracanej wartości mdash u nas intNastępnie mamy nazwę funkcji i w nawiasach listę argumentoacutew

Ciało funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasachklamrowych Pierwszą instrukcją jest deklaracja zmiennej mdash jest to zmienna lokalna czyliniewidoczna poza funkcją Dalej przeprowadzamy odpowiednie działania i zwracamy rezul-tat za pomocą instrukcji return

1111 Ogoacutelnie

Funkcję w języku C tworzy się następująco

typ identyfikator (typ1 argument1 typ2 argument2 typ_n argument_n)

instrukcje

Oczywiście istnieje możliwość utworzenia funkcji ktoacutera nie posiada żadnych argumen-toacutew Definiuje się ją tak samo jak funkcję z argumentami z tą tylko roacuteżnicą że międzyokrągłymi nawiasami nie znajduje się żaden argument lub pojedyncze słoacutewko void mdash w de-finicji funkcji nie ma to znaczenia jednak w deklaracji puste nawiasy oznaczają że prototypnie informuje jakie argumenty przyjmuje funkcja dlatego bezpieczniej jest stosować słoacutewkovoid

Funkcje definiuje się poza głoacutewną funkcją programu (main) W języku C nie można two-rzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji)

1112 Procedury

Przyjęło się że procedura od funkcji roacuteżni się tym że ta pierwsza nie zwraca żadnej wartościZatem aby stworzyć procedurę należy napisać

2Bardziej precyzyjnie można powiedzieć że funkcja może zwroacutecić tylko jedną wartość typu prostego lub jedenadres do jakiegoś obiektu w pamięci

112 WYWOŁYWANIE 79

void identyfikator (argument1 argument2 argumentn)

instrukcje

void (z ang pusty proacuteżny) jest słowem kluczowym mającym kilka znaczeń w tym przy-padku oznacza ldquobrak wartościrdquo

Generalnie w terminologii C pojęcie ldquoprocedurardquo nie jest używane moacutewi się raczej ldquofunk-cja zwracająca voidrdquo

Jeśli nie podamy typu danych zwracanych przez funkcję kompilator domyślnie przyjmietyp int choć już w standardzie C nieokreślenie wartości zwracanej jest błędem

1113 Stary sposoacuteb definiowania funkcji

Zanim powstał standard ANSI C w liście parametroacutew nie podawało się typoacutew argumentoacutewa jedynie ich nazwy Roacutewnież z tamtych czasoacutew wywodzi się oznaczenie iż puste nawiasy(w prototypie funkcji nie w definicji) oznaczają że funkcja przyjmuje nieokreśloną liczbęargumentoacutew Tego archaicznego sposobu definiowania funkcji nie należy już stosować aleponieważ w swojej przygodzie z językiem C Czytelnik może się na nią natknąć a co więcejstandard nadal (z powodu zgodności z wcześniejszymi wersjami) dopuszcza taką deklaracjęto należy tutaj o niej wspomnieć Otoacuteż wygląda ona następująco

typ_zwracany nazwa_funkcji(argument1 argument2 argumentn)typ1 argumenty typ2 argumenty

instrukcje

Na przykład wcześniejsza funkcja iloczyn wyglądałaby następująco

int iloczyn(x y)int x y

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

Najpoważniejszą wadą takiego sposobu jest fakt że w prototypie funkcji niema podanychtypoacutew argumentoacutew przez co kompilator nie jest w stanie sprawdzić poprawności wywołaniafunkcji Naprawiono to (wprowadzając definicje takie jak je znamy obecnie) najpierw wjęzyku C++ a potem rozwiązanie zapożyczono w standardzie ANSI C z roku

112 WywoływanieFunkcje wywołuje się następująco

80 ROZDZIAŁ 11 FUNKCJE

identyfikator (argument1 argument2 argumentn)

Jeśli chcemy aby przypisać zmiennej wartość ktoacuterą zwraca funkcja należy napisać tak

zmienna = funkcja (argument1 argument2 argumentn)

Programiści mający doświadczenia np z językiem Pascal mogą popełniać błąd polegającyna wywoływaniu funkcji bez nawiasoacutew okrągłych gdy nie przyjmuje ona żadnych argumen-toacutew

Przykładowo mamy funkcję

void pisz_komunikat()

printf(To jest komunikatn)

Jeśli teraz ją wywołamy

pisz_komunikat ŹLE pisz_komunikat() dobrze

to pierwsze polecenie nie spowoduje wywołania funkcji Dlaczego Aby kompilator Czrozumiał że chodzi nam o wywołanie funkcji musimy po jej nazwie dodać nawiasy okrą-głe nawet gdy funkcja nie ma argumentoacutew Użycie samej nazwy funkcji ma zupełnie inneznaczenie mdash oznacza pobranie jej adresu W jakim celu O tym będzie mowa w rozdzialeWskaźniki

PrzykładA oto działający przykład ktoacutery demonstruje wiadomości podane powyżej

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

int m = suma (4 5)printf (4+5=dn m)return 0

113 Zwracanie wartościreturn to słowo kluczowe języka C

W przypadku funkcji służy ono do

przerwania funkcji (i przejścia do następnej instrukcji w funkcji wywołującej)

114 ZWRACANA WARTOŚĆ 81

zwroacutecenia wartości

W przypadku procedur powoduje przerwania procedury bez zwracania wartościUżycie tej instrukcji jest bardzo proste i wygląda tak

return zwracana_wartość

lub dla procedur

return

Możliwe jest użycie kilku instrukcji returnw obrębie jednej funkcji Wielu programistoacutewuważa jednak że lepsze jest użycie jednej instrukcji return na końcu funkcji gdyż ułatwia tośledzenie przebiegu programu

114 Zwracana wartość

W C zwykle przyjmuje się że oznacza poprawne zakończenie funkcji

return 0 funkcja zakończona sukcesem

a inne wartości oznaczają niepoprawne zakończenie

return 1 funkcja zakończona niepowodzeniem

Ta wartość może być wykorzystana przez inne instrukcje np if

115 Funkcja main()

Do tej pory we wszystkich programach istniała funkcja main() Po co tak właściwie onajest Otoacuteż jest to funkcja ktoacutera zostaje wywołana przez fragment kodu inicjującego pracęprogramu Kod ten tworzony jest przez kompilator i nie mamy na niego wpływu Istotnejest że każdy program w języku C musi zawierać funkcję main()

Istnieją dwa możliwe prototypy (nagłoacutewki) omawianej funkcji

int main(void)

int main(int argc char argv) 3

Argument argc jest liczbą nieujemną określającą ile ciągoacutew znakoacutew przechowywanychjest w tablicy argv Wyrażenie argv[argc] ma zawsze wartość Pierwszym elementemtablicy argv (o ile istnieje4) jest nazwa programu czy komenda ktoacuterą program został urucho-miony Pozostałe przechowują argumenty podane przy uruchamianiu programu

Zazwyczaj jeśli program uruchomimy poleceniem

program argument1 argument2

3Czasami można się spotkać z prototypem int main(int argc char argv char env) ktoacutery jest definio-wany w standardzie ale wykracza już poza standard C

4Inne standardy mogą wymuszać istnienie tego elementu jednak jeśli chodzi o standard języka C to nic nie stoina przeszkodzie aby argument argc miał wartość zero

82 ROZDZIAŁ 11 FUNKCJE

to argc będzie roacutewne ( argumenty + nazwa programu) a argv będzie zawierać napisy pro-gram argument argument umieszczone w tablicy indeksowanej od do

Weźmy dla przykładu program ktoacutery wypisuje to co otrzymuje w argumentach argc iargv

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) while (argv)

puts(argv++) Ewentualnie można użycint ifor (i = 0 iltargc ++i)

puts(argv[i])return EXIT_SUCCESS

Uruchomiony w systemie typu UNIX poleceniem test foo bar baz powinien wypisać

testfoobarbaz

Na razie nie musisz rozumieć powyższych kodoacutew i opisoacutew gdyż odwołują się do pojęćtakich jak tablica oraz wskaźnik ktoacutere opisane zostaną w dalszej części podręcznika

Co ciekawe funkcja main nie roacuteżni się zanadto od innych funkcji i tak jak inne możewołać sama siebie (patrz rekurencja niżej) przykładowo powyższy program można zapisaćtak5

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) if (argv)

puts(argv)return main(argc-1 argv+1)

else return EXIT_SUCCESS

Ostatnią rzeczą dotyczącą funkcji main jest zwracana przez nią wartość Już przy oma-wianiu pierwszego programu wspomniane zostało że jedynymi wartościami ktoacutere znaczą

5Jeżeli ktoś lubi ekstrawagancki kod ciało funkcji main można zapisać jako return argv puts(argv)main(argc-1 argv+1) EXIT SUCCESS ale nie radzimy stosować tak skomplikowanych i bądź co bądź małoczytelnych konstrukcji

116 DALSZE INFORMACJE 83

zawsze to samowewszystkich implementacjach języka są EXIT SUCCESS i EXIT FAILURE6

zdefiniowane w pliku nagłoacutewkowym stdlibh Wartość i EXIT SUCCESS oznaczają po-prawne zakończenie programu (co wcale nie oznacza że makro EXIT SUCCESS ma wartośćzero) natomiast EXIT FAILURE zakończenie błędne Wszystkie inne wartości są zależne odimplementacji

116 Dalsze informacjePoniżej przekażemy ci parę bardziej zaawansowanych informacji o funkcjach w C jeśli niemasz ochoty wgłębiać się w szczegoacuteły możesz spokojnie pominąć tę część i wroacutecić tu poacuteźniej

1161 Jak zwroacutecić kilka wartości

Jeśli chcesz zwroacutecić z funkcji kilka wartości musisz zrobić to w trochę inny sposoacuteb Ge-neralnie możliwe są dwa podejścia jedno to ldquoupakowanierdquo zwracanych wartości ndash możnastworzyć tak zwaną strukturę ktoacutera będzie przechowywała kilka zmiennych (jest to opisanew rozdziale Typy złożone) Prostszym sposobem jest zwracanie jednej z wartości w normalnysposoacuteb a pozostałych jako parametroacutew Za chwilę dowiesz się jak to zrobić jeśli chcesz zo-baczyć przykład możesz przyjrzeć się funkcji scanf() z biblioteki standardowej

1162 Przekazywanie parametroacutew

Gdy wywołujemy funkcję wartość argumentoacutew z ktoacuterymi ją wywołujemy jest kopiowanado funkcji Kopiowana mdash to znaczy że nie możemy normalnie zmienić wartości zewnętrz-nych dla funkcji zmiennych Formalnie moacutewi się że w C argumenty są przekazywane przezwartość czyli wewnątrz funkcji operujemy tylko na ich kopiach

Możliwe jest modyfikowanie zmiennych przekazywanych do funkcji jako parametry mdashale do tego w C potrzebne są wskaźniki

1163 Funkcje rekurencyjne

Język Cmamożliwość tworzenia tzw funkcji rekurencyjny Jest to funkcja ktoacuteraw swojejwłasnej definicji (ciele) wywołuje samą siebie Najbardziej klasycznym przykładem może tubyć silnia Napiszmy sobie zatem naszą funkcję rekurencyjną ktoacutera oblicza silnię

int silnia (int liczba)

int silif (liczbalt0) return 0 wywołanie jest bezsensowne

zwracamy 0 jako kod błędu if (liczba==0 || liczba==1) return 1sil = liczbasilnia(liczba-1)return sil

Musimy być ostrożni przy funkcjach rekurencyjnych gdyż łatwo za ich pomocą utworzyćfunkcję ktoacutera będzie sama siebie wywoływała w nieskończoność a co za tym idzie będzie za-wieszała program Tutaj pierwszymi instrukcjami if ustalamy ldquowarunki stopurdquo gdzie kończy

6Uwaga Makra EXIT SUCCESS i EXIT FAILURE te służą tylko i wyłącznie jako wartości do zwracania przezfunkcję main() Nigdzie indziej nie mają one zastosowania

84 ROZDZIAŁ 11 FUNKCJE

się wywoływanie funkcji przez samą siebie a następnie określamy jak funkcja będzie wy-woływać samą siebie (odjęcie jedynki od argumentu co do ktoacuterego wiemy że jest dodatnigwarantuje że dojdziemy do warunku stopu w skończonej liczbie krokoacutew)

Warto też zauważyć że funkcje rekurencyjne czasami mogą być znacznie wolniejsze niżpodejście nierekurencyjne (iteracyjne przy użyciu pętli) Flagowym przykłademmoże tu byćfunkcja obliczająca wyrazy ciągu Fibonacciego

include ltstdiohgt

unsigned count

unsigned fib_rec(unsigned n) ++countreturn nlt2 n (fib_rec(n-2) + fib_rec(n-1))

unsigned fib_it (unsigned n) unsigned a = 0 b = 0 c = 1++countif (n) return 0while (--n)

++counta = bb = cc = a + b

return c

int main(void) unsigned n resultprintf(Ktory element ciagu Fibonacciego obliczyc )while (scanf(d ampn)==1)

count = 0result = fib_rec(n)printf(fib_ret(3u) = 6u (wywolan 5u)n n result count)

count = 0result = fib_it (n)printf(fib_it (3u) = 6u (wywolan 5u)n n result count)

return 0

W tym przypadku funkcja rekurencyjna choć łatwiejsza w napisaniu jest bardzo nie-efektywna

116 DALSZE INFORMACJE 85

1164 Deklarowanie funkcji

Czasami możemy chcieć przed napisaniem funkcji poinformować kompilator że dana funkcjaistnieje Niekiedy kompilator może zaprotestować jeśli użyjemy funkcji przed określeniemjaka to funkcja na przykład

int a()

return b(0)

int b(int p)

if( p == 0 )return 1

elsereturn a()

int main()

return b(1)

W tym przypadku nie jesteśmy w stanie zamienić a i b miejscami bo obie funkcje korzy-stają z siebie nawzajem Rozwiązaniem jest wcześniejsze zadeklarowanie funkcji Deklara-cja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji(przed otwierającym nawiasem klamrowym) z dodatkowo dodanym średnikiem na końcu Wnaszym przykładzie wystarczy na samym początku wstawić

int b(int p)

W deklaracji można pominąć nazwy parametroacutew funkcji

int b(int)

Bardzo częstym zwyczajem jest wypisanie przed funkcją main samych prototypoacutew funk-cji by ich definicje umieścić po definicji funkcji main np

int a(void)int b(int p)

int main()

return b(1)

int a()

return b(0)

86 ROZDZIAŁ 11 FUNKCJE

int b(int p)

if( p == 0 )return 1

elsereturn a()

Z poprzednich rozdziałoacutew pamiętasz że na początku programu dołączaliśmy tzw plikinagłoacutewkowe Zawierają one właśnie prototypy funkcji i ułatwiają pisanie dużych progra-moacutew Dalsze informacje o plikach nagłoacutewkowych zawarte są w rozdziale Tworzenie biblio-tek

1165 Zmienna liczba parametroacutew

Zauważyłeś zapewne że używając funkcji printf() lub scanf() po argumencie zawierającymtekst z odpowiednimi modyfikatorami mogłeś podać praktycznie nieograniczoną liczbę ar-gumentoacutew Zapewne deklaracja obu funkcji zadziwi Cię jeszcze bardziej

int printf(const char format )int scanf(const char format )

Jak widzisz w deklaracji zostały użyte kropki Otoacuteż język C ma możliwość przekazywa-nia nieograniczonej liczby argumentoacutew do funkcji (tzn jedynym ograniczeniem jest rozmiarstosu programu) Cała zabawa polega na tym aby umieć dostać się do odpowiedniego ar-gumentu oraz poznać jego typ (używając funkcji printf mogliśmy wpisać jako argument do-wolny typ danych) Do tego celu możemy użyć wszystkich ciekawostek zawartych w plikunagłoacutewkowym stdargh

Załoacuteżmy że chcemy napisać prostą funkcję ktoacutera dajmy na to mnoży wszystkie swojeargumenty (zakładamy że argumenty są typu int) Przyjmujemy przy tym że ostatni argu-ment będzie Będzie ona wyglądała tak

include ltstdarghgt

int mnoz (int pierwszy )

va_list argint iloczyn = 1 tva_start (arg pierwszy)for (t = pierwszy t t = va_arg(arg int))

iloczyn = tva_end (arg)return iloczyn

va list oznacza specjalny typ danych w ktoacuterym przechowywane będą argumenty prze-kazane do funkcji ldquova startrdquo inicjuje arg do dalszego użytku Jako drugi parametr musimypodać nazwę ostatniego znanego argumentu funkcji Makropolecenie va arg odczytuje ko-lejne argumenty i przekształca je do odpowiedniego typu danych Na zakończenie używanejest makro va end mdash jest ono obowiązkowe

117 ZOBACZ TEŻ 87

Oczywiście tak samo jak w przypadku funkcji printf() czy scanf() argumenty nie musząbyć takich samych typoacutew Rozważmy dla przykładu funkcję podobną do printf() ale znacznieuproszczoną

include ltstdarghgt

void wypisz(const char format ) va_list argva_start (arg format)for ( format ++format)

switch (format) case i printf(d va_arg(arg int)) breakcase I printf(u va_arg(arg unsigned)) breakcase l printf(ld va_arg(arg int)) breakcase L printf(lu va_arg(arg unsigned long)) breakcase f printf(f va_arg(arg double)) breakcase x printf(x va_arg(arg unsigned)) breakcase X printf(X va_arg(arg unsigned)) breakcase s printf(s va_arg(arg const char )) breakdefault putc(format)

va_end (arg)

Przyjmuje ona jako argument ciąg znakoacutew w ktoacuterych niektoacutere instruują funkcję bypobrała argument i go wypisała Nie przejmuj się jeżeli nie rozumiesz wyrażeń format i++format Istotne jest to że pętla sprawdza po kolei wszystkie znaki formatu

1166 Ezoteryka C

C ma wiele niuansoacutew o ktoacuterych wielu programistoacutew nie wie lub łatwo o nich zapomina

jeśli nie podamy typu wartości zwracanej w funkcji zostanie przyjęty typ int (wedługnajnowszego standardu C nie podanie typu wartości jest zwracane jako błąd)

jeśli nie podamy żadnych parametroacutew funkcji to funkcja będzie używała zmiennej ilo-ści parametroacutew (inaczej niż wC++ gdzie przyjęte zostanie że funkcja nie przyjmuje ar-gumentoacutew) Aby wymusić pustą listę argumentoacutew należy napisać int funkcja(void)(dotyczy to jedynie prototypoacutew czy deklaracji funkcji)

jeśli nie użyjemy w funkcji instrukcji return wartość zwracana będzie przypadkowa(dostaniemy śmieci z pamięci)

Kompilator C++ użyty do kompilacji kodu C najczęściej zaprotestuje i ostrzeże nas jeśliużyjemy powyższych konstrukcji Natomiast czysty kompilator C z domyślnymi ustawie-niami nie napisze nic i bez mrugnięcia okiem skompiluje taki kod

117 Zobacz też C++Funkcje inline mdash funkcje rozwijane wmiejscu wywoływania (dostępne też w stan-

dardzie C)

88 ROZDZIAŁ 11 FUNKCJE

C++Przeciążanie funkcji

Rozdział 12

Preprocesor

121 Wstęp

W języku C wszystkie linijki zaczynające się od symbolu rdquo nie podlegają bezpośrednio pro-cesowi kompilacji Są to natomiast instrukcje preprocesora mdash elementu kompilatora ktoacuteryanalizuje plik źroacutedłowy w poszukiwaniu wszystkich wyrażeń zaczynających się od rdquo Napodstawie tych instrukcji generuje on kod w czystymrdquo języku C ktoacutery następnie jest kom-pilowany przez kompilator Ponieważ za pomocą preprocesora można niemal sterowaćrdquokompilatorem daje on niezwykłe możliwości ktoacutere nie były dotąd znane w innych językachprogramowania Aby przekonać się jak wygląda kod przetworzony przez preprocesor użyj(w kompilatorze gcc) przełącznika -Erdquo

gcc testc -E -o testtxt

W pliku testtxt zostanie umieszczony cały kod w postaci ktoacutera zdatna jest do przetwo-rzenia przez kompilator

122 Dyrektywy preprocesora

Dyrektywy preprocesora są towyrażenia ktoacutere występują zaraz za symbolem rdquo i to właśnieza ich pomocą możemy używać preprocesora Dyrektywa zaczyna się od znaku i kończysię wraz z końcem linii Aby przenieść dalszą część dyrektywy do następnej linii należyzakończyć linię znakiem rdquo

define add(ab) a+b

Omoacutewimy teraz kilka ważniejszych dyrektyw

1221 include

Najpopularniejsza dyrektywa wstawiająca w swoje miejsce treść pliku podanego w nawia-sach ostrych lub cudzysłowie Składnia

89

90 ROZDZIAŁ 12 PREPROCESOR

Przykład 1

include ltplik_naglowkowy_do_dolaczeniagt

Za pomocą include możemy dołączyć dowolny plik mdash niekoniecznie plik nagłoacutewkowy

Przykład 2

include plik_naglowkowy_do_dolaczenia

Jeżeli nazwa pliku nagłoacutewkowego będzie ujęta w nawiasy ostre (przykład ) to kompi-lator poszuka go wśroacuted własnych plikoacutew nagłoacutewkowych (ktoacutere najczęściej się znajdują wpodkatalogu includesrdquo w katalogu kompilatora) Jeśli jednak nazwa ta będzie ujęta w po-dwoacutejne cudzysłowy(przykład ) to kompilator poszuka jej w katalogu w ktoacuterym znajdujesię kompilowany plik (można zmienić to zachowanie w opcjach niektoacuterych kompilatoroacutew)Przy użyciu tej dyrektywy można także wskazać dokładne położenie plikoacutew nagłoacutewkowychpoprzez wpisanie bezwzględnej lub względnej ścieżki dostępu do tego pliku nagłoacutewkowego

Przykład 3 mdash ścieżka bezwzględna do pliku nagłoacutewkowego w Linuksie i w Windowsie

Opis W miejsce jednej i drugiej linijki zostanie wczytany plik umieszczony w danej lokali-zacji

include usrincludeplik_nagłoacutewkowyhinclude Cborlandincludesplik_nagłoacutewkowyh

Przykład 4 mdash ścieżka względna do pliku nagłoacutewkowego

Opis W miejsce linijki zostanie wczytany plik umieszczony w katalogu katalogrdquo a tenkatalog jest w katalogu z plikiem źroacutedłowym Inaczej moacutewiąc jeśli plik źroacutedłowy jest wkatalogu homeuserdokumentyzrodlardquo to plik nagłoacutewkowy jest umieszczony w kataloguhomeuserdokumentyzrodlakatalogrdquo

include katalog1plik_naglowkowyh

Przykład 5 mdash ścieżka względna do pliku nagłoacutewkowego

Opis Jeśli plik źroacutedłowy jest umieszczony w katalogu homeuserdokumentyzrodlardquo toplik nagłoacutewkowy znajduje się w katalogu homeuserdokumentykatalogkatalogrdquo

include katalog1katalog2plik_naglowkowyh

Więcej informacji możesz uzyskać w rozdziale Biblioteki

1222 define

Linia pozwalająca zdefiniować stałą funkcję lub słowo kluczowe ktoacutere będzie potem pod-mienione w kodzie programu na odpowiednią wartość lub może zostać użyte w instrukcjachwarunkowych dla preprocesora Składnia

define NAZWA_STALEJ WARTOSC

lub

define NAZWA_STALEJ

122 DYREKTYWY PREPROCESORA 91

Przykład

define LICZBA mdash spowoduje że każde wystąpienie słowa LICZBA w kodzie zostanie za-stąpione oacutesemkądefine SUMA(ab) (a+b) mdash spowoduje ze każde wystąpienie wywołania funkcjirdquo SUMA zo-stanie zastąpione przez sumę argumentoacutew

1223 undef

Ta instrukcja odwołuje definicję wykonaną instrukcją define

undef STALA

1224 instrukcje warunkowe

Preprocesor zawiera roacutewnież instrukcje warunkowe pozwalające nawyboacuter tego coma zostaćskompilowane w zależności od tego czy stała jest zdefiniowana lub jaką ma wartość

if elif else endif

Te instrukcje uzależniają kompilacje od warunkoacutew Ich działanie jest podobne do instrukcjiwarunkowych w samym języku C I tak

if wprowadza warunek ktoacutery jeśli nie jest prawdziwy powoduje pominięcie kompilowaniakodu aż do napotkania jednej z poniższych instrukcji

else spowoduje skompilowanie kodu jeżeli warunek za if jest nieprawdziwy aż do napo-tkania ktoacuteregoś z poniższych instrukcji

elif wprowadza nowy warunek ktoacutery będzie sprawdzony jeżeli poprzedni był niepraw-dziwy Stanowi połączenie instrukcji if i else

endif zamyka blok ostatniej instrukcji warunkowej

Przykład

if INSTRUKCJE == 2printf (Podaj liczbę z przedziału 10 do 0n) 1

elif INSTRUKCJE == 1printf (Podaj liczbę ) 2

elseprintf (Podaj parametr ) 3

endifscanf (dn ampliczba)4

wiersz nr zostanie skompilowany jeżeli stała INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany gdy INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany w pozostałych wypadkach

wiersz nr będzie kompilowany zawsze

92 ROZDZIAŁ 12 PREPROCESOR

ifdef ifndef else endif

Te instrukcje warunkują kompilację od tego czy odpowiednia stała została zdefiniowana

ifdef spowoduje że kompilator skompiluje poniższy kod tylko gdy została zdefiniowanaodpowiednia stała

ifndef ma odwrotne działanie do ifdef a mianowicie brak definicji odpowiedniej stałejumożliwia kompilacje poniższego kodu

elseendif mają identyczne zastosowanie jak te z powyższej grupy

Przykład

define INFO definicja stałej INFOifdef INFO

printf (Twoacutercą tego programu jest Jan Kowalskin)1endififndef INFO

printf (Twoacutercą tego programu jest znany programistan)2endif

To czy dowiemy się kto jest twoacutercą tego programu zależy czy instrukcja definiująca stałąINFO będzie istnieć W powyższym przypadku na ekranie powinno się wyświetlić

Twoacutercą tego programu jest Jan Kowalski

1225 error

Powoduje przerwanie kompilacji i wyświetlenie tekstu ktoacutery znajduje się za tą instrukcjąPrzydatne gdy chcemy zabezpieczyć się przed zdefiniowaniem nieodpowiednich stałych

Przykład

if BLAD == 1error Poważny błąd kompilacjiendif

Co jeżeli zdefiniujemy stałą BLAD z wartością Spowoduje to wyświetlenie w trakcie kom-pilacji komunikatu podobnego do poniższego

Fatal error programc 6 Error directive Poważny błąd kompilacjiin function main() 1 errors in Compile

wraz z przerwaniem kompilacji

1226 warning

Wyświetla tekst zawarty w cudzysłowach jako ostrzeżenie Jest często używany do sygna-lizacji programiście że dana część programu jest przestarzała lub może sprawiać problemy

122 DYREKTYWY PREPROCESORA 93

Przykład

warning To jest bardzo prosty program

Spowoduje to takie oto zachowanie kompilatora

testc32 warning warning To jest bardzo prosty program

Użycie dyrektywy warning nie przerywa procesu kompilacji i służy tylko do wyświetlaniakomunikatoacutew dla programisty w czasie kompilacji programu

1227 line

Powoduje wyzerowanie licznika linii kompilatora ktoacutery jest używany przy wyświetlaniuopisu błędoacutew kompilacji Pozwala to na szybkie znalezienie możliwej przyczyny błędu wrozbudowanym programie

Przykład

printf (Podaj wartość funkcji)lineprintf (W przedziale od 10 do 0n) tutaj jest błąd - brak cudzysłowu zamykającego

Jeżeli teraz nastąpi proacuteba skompilowania tego kodu to kompilator poinformuje że wystąpiłbłąd składni w linii a nie np

1228 Makra

Preprocesor języka C umożliwia też tworzenie makr czyli automatycznie wykonywanychczynności Makra deklaruje się za pomocą dyrektywy define

define MAKRO(arg1 arg2 ) (wyrażenie)

Wmomencie wystąpienia MAKRA w tekście preprocesor automatycznie zamieni makrona wyrażenie Makra mogą być pewnego rodzaju alternatywami dla funkcji ale powinnosię ich używać tylko w specjalnych przypadkach Ponieważ makro sprowadza się do pro-stego zastąpienia przez preprocesor wywołania makra przez jego tekst jest bardzo podatnena trudne do zlokalizowania błędy (kompilator będzie podawał błędy wmiejscach w ktoacuterychnic nie widzimy mdash bo preprocesor wstawił tam tekst) Makra są szybsze (nie następuje wy-wołanie funkcji ktoacutere zawsze zajmuje trochę czasu1) ale też mniej bezpieczne i elastyczneniż funkcje

Przeanalizujmy teraz fragment kodu

include ltstdiohgtdefine KWADRAT(x) ((x)(x))

int main ()

printf (2 do kwadratu wynosi dn KWADRAT(2))return 0

1Tak naprawdę wg standardu C99 istnieje możliwość napisania funkcji ktoacuterej kod także będzie wstawiany wmiejscu wywołania Odbywa się to dzięki inline

94 ROZDZIAŁ 12 PREPROCESOR

Preprocesor w miejsce wyrażenia KWADRAT(2) wstawił ((2)(2)) Zastanoacutewmy się costałoby się gdybyśmy napisali KWADRAT(2) Preprocesor po prostu wstawi napis do koduco da wyrażenie ((2)(2)) ktoacutere jest nieprawidłowe Kompilator zgłosi błąd ale pro-gramista widzi tylko w kodzie użycie makra a nie prawdziwą przyczynę błędu Widać tu żebezpieczniejsze jest użycie funkcji ktoacutere dają możliwość wyspecyfikowania typoacutew argumen-toacutew

Nawet jeżeli program się skompiluje to makro może dawać nieoczekiwany wynik Jesttak w przypadku poniższego kodu

int x = 1int y = KWADRAT(++x)

Dzieje się tak dlatego że makra rozwijane są przez preprocesor i kompilator widzi kod

int x = 1int y = ((++x)(++x))

Roacutewnież poniższe makra są błędne pomimo że opisany problem w nich nie występuje

define SUMA(a b) a + bdefine ILOCZYN(a b) a b

Dają one nieoczekiwane wyniki dla wywołań

SUMA(2 2) 2 6 zamiast 8 ILOCZYN(2 + 2 2 + 2) 8 zamiast 16

Z tego powodu istotne jest użycie nawiasoacutew

define SUMA(a b) ((a) + (b))define ILOCZYN(a b) ((a) (b))

1229 oraz

Dość ciekawe możliwości ma w makrach znak rdquo Zamienia on stojący za nim identyfikatorna napis

include ltstdiohgtdefine wypisz(x) printf(s=in x x)

int main()

int i=1char a=5wypisz(i)wypisz(a)return 0

Program wypisze

i=1a=5

123 PREDEFINIOWANE MAKRA 95

Czyli wypisz(a) jest rozwijane w printf(s=in a a)Natomiast znaki rdquo łączą dwie nazwy w jedną Przykład

include ltstdiohgtdefine abc(x) int zmienna x

int main()

abc(nasza) dzięki temu zadeklarujemy zmienną o nazwie zmiennanasza zmiennanasza = 2return 0

Więcej o dobrych zwyczajach w tworzeniu makr można się dowiedzieć w rozdziale Po-wszechne praktyki

123 Predefiniowane makraW języku wprowadzono roacutewnież serię predefiniowanych makr ktoacutere mają ułatwić życie pro-gramiście Oto one

DATE mdash data w momencie kompilacji

TIME mdash godzina w momencie kompilacji

FILE mdash łańcuch ktoacutery zawiera nazwę pliku ktoacutery aktualnie jest kompilowany przezkompilator

LINE mdash definiuje numer linijki

STDC mdash w kompilatorach zgodnych ze standardem ANSI lub nowszym makro toprzyjmuje wartość

STDC VERSION mdash zależnie od poziomu zgodności kompilatora makro przyjmuje roacuteżnewartości

ndash jeżeli kompilator jest zgodny z ANSI (rok ) makro nie jest zdefiniowane

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199409L

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199901L

Warto roacutewnież wspomnieć o identyfikatorze func zdefiniowanym w standardzie Cktoacuterego wartość to nazwa funkcji

Sproacutebujmy użyć tych makr w praktyce

include ltstdiohgt

if __STDC_VERSION__ gt= 199901L Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go define BUG(message) fprintf(stderr sd s (w funkcji s)n

__FILE__ __LINE__ message __func__)else Jezeli __func__ nie ma to go nie używamy

96 ROZDZIAŁ 12 PREPROCESOR

define BUG(message) fprintf(stderr sd sn __FILE__ __LINE__ message)

endif

int main(void) printf(Program ABC data kompilacji s sn __DATE__ __TIME__)

BUG(Przykladowy komunikat bledu)return 0

Efekt działania programu gdy kompilowany jest kompilatorem C

Program ABC data kompilacji Sep 1 2008 191213testc17 Przykladowy komunikat bledu (w funkcji main)

Gdy kompilowany jest kompilatorem ANSI C

Program ABC data kompilacji Sep 1 2008 191316testc17 Przykladowy komunikat bledu

Rozdział 13

Biblioteka standardowa

131 Czym jest biblioteka

Bibliotekę w języku C stanowi zbioacuter skompilowanych wcześniej funkcji ktoacutery można łączyćz programem Biblioteki tworzy się aby udostępnić zbioacuter pewnych ldquowyspecjalizowanychrdquofunkcji do dyspozycji innych programoacutew Tworzenie bibliotek jest o tyle istotne że takiepodejście znacznie ułatwia tworzenie nowych programoacutew Łatwiej jest utworzyć program woparciu o istniejące biblioteki niż pisać programwraz zewszystkimi potrzebnymi funkcjami1

132 Po co nam biblioteka standardowa

W ktoacuterymś z początkowych rozdziałoacutew tego podręcznika napisane jest że czysty język C niemoże zbyt wiele Tak naprawdę to język C sam w sobie praktycznie nie ma mechanizmoacutewdo obsługi np wejścia-wyjścia Dlatego też większość systemoacutew operacyjnych posiada tzwbibliotekę standardową zwaną też biblioteką języka C To właśnie w niej zawarte są pod-stawowe funkcjonalności dzięki ktoacuterym twoacutej program może np napisać coś na ekranie

1321 Jak skonstruowana jest biblioteka standardowa

Zapytacie zapewne jak biblioteka standardowa realizuje te funkcje skoro sam język C tegonie potrafi Odpowiedź jest prosta mdash biblioteka standardowa nie jest napisana w samym ję-zyku C Ponieważ C jest językiem tłumaczonym do kodu maszynowego to w praktyce niema żadnych przeszkoacuted żeby np połączyć go z językiem niskiego poziomu jakim jest npasembler Dlatego biblioteka C z jednej strony udostępnia gotowe funkcje w języku C a zdrugiej za pomocą niskopoziomowych mechanizmoacutew2 komunikuje się z systemem operacyj-nym ktoacutery wykonuje odpowiednie czynności

133 Gdzie są funkcje z biblioteki standardowej

Pisząc program w języku C używamy roacuteżnego rodzaju funkcji takich jak np printf Niejesteśmy jednak ich autorami mało tego nie widzimy nawet deklaracji tych funkcji w naszymprogramie Pamiętacie program ldquoHello worldrdquo Zaczynał on się od takiej oto linijki

1Początkujący programista zapewne nie byłby w stanie napisać nawet funkcji printf2Takich jak np wywoływanie przerwań programowych

97

98 ROZDZIAŁ 13 BIBLIOTEKA STANDARDOWA

include ltstdiohgt

linijka ta oznacza ldquow tym miejscu wstaw zawartość pliku stdiohrdquo Nawiasy ldquoltrdquo i ldquogtrdquooznaczają że plik stdioh znajduje się w standardowym katalogu z plikami nagłoacutewkowymiWszystkie pliki z rozszerzeniem h są właśnie plikami nagłoacutewkowymi Wroacutećmy teraz do te-matu biblioteki standardowej Każdy system operacyjny ma za zadanie wykonywać pewnefunkcje na rzecz programoacutew Wszystkie te funkcje zawarte są właśnie w bibliotece standar-dowej W systemach z rodziny UNIX nazywa się ją LibC (biblioteka języka C) To tamwłaśnieznajduje się funkcja printf scanf puts i inne

Oproacutecz podstawowych funkcji wejścia-wyjścia biblioteka standardowa udostępnia teżmożliwość wykonywania funkcji matematycznych komunikacji przez sieć oraz wykonywa-nia wielu innych rzeczy

1331 Jeśli biblioteka nie jest potrzebna

Czasami korzystanie z funkcji bibliotecznych oraz standardowych plikoacutew nagłoacutewkowych jestniepożądane np wtedy gdy programista pisze swoacutej własny system operacyjny oraz biblio-tekę do niego Aby wyłączyć używanie biblioteki C w opcjach kompilatora GCC możemydodać następujące argumenty

-nostdinc -fno-builtin

134 Opis funkcji biblioteki standardowejPodręcznik C na Wikibooks zawiera opis dużej części biblioteki standardowej C

Indeks alfabetyczny

Indeks tematyczny

W systemach uniksowych możesz uzyskać pomoc dzięki narzędziu man przykładowopisząc

man printf

135 UwagiProgramy w języku C++ mogą dokładnie w ten sam sposoacuteb korzystać z biblioteki standar-dowej ale zalecane jest by robić to raczej w trochę odmienny sposoacuteb właściwy dla C++Szczegoacuteły w podręczniku C++

Rozdział 14

Czytanie i pisanie do plikoacutew

141 Pojęcie plikuNa początku dobrze by było abyś dowiedział się czym jest plik Odpowiedni artykuł do-stępny jest w Wikipedii Najprościej moacutewiąc plik to pewne dane zapisane na dysku

142 Identyfikacja plikuKażdy z nas korzystając na co dzień z komputera przyzwyczaił się do tego że plik ma okre-śloną nazwę Jednak w pisaniu programu posługiwanie się całą nazwą niosło by ze sobą conajmniej dwa problemy

pamięciożerność mdash przechowywanie całego (czasami nawet -bajtowego łańcucha)zajmuje niepotrzebnie pamięć

ryzyko błędoacutew (owe błędy szerzej omoacutewione zostały w rozdziale Napisy)

Aby uprościć korzystanie z plikoacutew programiści wpadli na pomysł aby identyfikatorempliku stała się liczba Dzięki temu kod programu stał się czytelniejszy oraz wyeliminowanokonieczność ciągłego korzystania z łańcuchoacutew Jednak sam plik nadal jest identyfikowany poswojej nazwie Aby ldquoprzetworzyćrdquo nazwę pliku na odpowiednią liczbę korzystamy z funkcjiopen lub fopen Roacuteżnica wyjaśniona jest poniżej

143 Podstawowa obsługa plikoacutewIstnieją dwie metody obsługi czytania i pisania do plikoacutew

wysokopoziomowa

niskopoziomowa

Nazwy funkcji z pierwszej grupy zaczynają się od litery ldquordquo (np fopen() fread() fclose())a identyfikatorem pliku jest wskaźnik na strukturę typu FILE Owa struktura to pewna grupazmiennych ktoacutera przechowuje dane o danym pliku mdash jak na przykład aktualną pozycję wnim Szczegoacutełami nie musisz się przejmować funkcje biblioteki standardowej same zajmująsię wykorzystaniem struktury FILE programista może więc zapomnieć czym tak naprawdęjest struktura FILE i traktować taką zmienną jako ldquouchwytrdquo identyfikator pliku

99

100 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

Druga grupa to funkcje typu read() open() write() i close()Podstawowym identyfikatorem pliku jest liczba całkowita ktoacutera jednoznacznie identy-

fikuje dany plik w systemie operacyjnym Liczba ta w systemach typu jest nazywanadeskryptorem pliku

Należy pamiętać że nie wolno nam używać funkcji z obu tych grup jednocześnie w sto-sunku do jednego otwartego pliku tzn nie można najpierw otworzyć pliku za pomocą fo-pen() a następnie odczytywać danych z tego samego pliku za pomocą read()

Czym roacuteżnią się oba podejścia do obsługi plikoacutew Otoacuteż metoda wysokopoziomowa maswoacutej własny bufor w ktoacuterym znajdują się dane po odczytaniu z dysku a przed wysłaniemich do programu użytkownika W przypadku funkcji niskopoziomowych dane kopiowane sąbezpośrednio z pliku do pamięci programu W praktyce używanie funkcji wysokopoziomo-wych jest prostsze a przy czytaniu danych małymi porcjami roacutewnież często szybsze i właśnieten model zostanie tutaj zaprezentowany

1431 Dane znakowe

Skupimy się teraz na najprostszym zmożliwych zagadnień mdash zapisie i odczycie pojedynczychznakoacutew oraz całych łańcuchoacutew

Napiszmy zatem nasz pierwszy program ktoacutery stworzy plik ldquotesttxtrdquo i umieści w nimtekst ldquoHello worldrdquo

include ltstdiohgtinclude ltstdlibhgt

int main ()

FILE fp używamy metody wysokopoziomowej musimy mieć zatem identyfikator pliku uwaga na gwiazdkę

char tekst[] = Hello worldif ((fp=fopen(testtxt w))==NULL)

printf (Nie mogę otworzyć pliku testtxt do zapisun)exit(1)

fprintf (fp s tekst) zapisz nasz łańcuch w pliku fclose (fp) zamknij plik return 0

Teraz omoacutewimy najważniejsze elementy programu Jak już było wspomniane wyżej doidentyfikacji pliku używa się wskaźnika na strukturę FILE (czyli FILE ) Funkcja fopenzwraca oacutew wskaźnik w przypadku poprawnego otwarcia pliku bądź też NULL gdy plik niemoże zostać otwarty Pierwszy argument funkcji to nazwa pliku natomiast drugi to trybdostępu mdash w oznacza ldquowriterdquo (pisanie) zwroacutecony ldquouchwytrdquo do pliku będzie moacutegł być wyko-rzystany jedynie w funkcjach zapisujących dane I odwrotnie gdy otworzymy plik podająctryb r (ldquoreadrdquo czytanie) będzie można z niego jedynie czytać dane Funkcja fopen zostaładokładniej opisana w odpowiedniej części rozdziału o bibliotece standardowej

Po zakończeniu korzystania z pliku należy plik zamknąć Robi się to za pomocą funk-cji fclose Jeśli zapomnimy o zamknięciu pliku wszystkie dokonane w nim zmiany zostanąutracone

143 PODSTAWOWA OBSŁUGA PLIKOacuteW 101

1432 Pliki a strumienie

Można zauważyć że do zapisu do pliku używamy funkcji fprintf ktoacutera wygląda bardzopodobnie do printf mdash jedyną roacuteżnicą jest to że w fprintf musimy jako pierwszy argu-ment podać identyfikator pliku Nie jest to przypadek mdash obie funkcje tak naprawdę robiątak samo Używana do wczytywania danych z klawiatury funkcja scanf też ma swoacutej od-powiednik wśroacuted funkcji operujących na plikach mdash jak nietrudno zgadnąć nosi ona nazwęfscanf

W rzeczywistości język C traktuje tak samo klawiaturę i plik mdash są to źroacutedła danych po-dobnie jak ekran i plik do ktoacuterych można dane kierować Jest to myślenie typowe dla sys-temoacutew typu UNIX jednak dla użytkownikoacutew przyzwyczajonych do systemu Windows albojęzykoacutew typu Pascal może być to co najmniej dziwne Nie da się ukryć że między klawia-turą i plikiem na dysku zachodzą podstawowe roacuteżnice i dostęp do nich odbywa się inaczejmdash jednak funkcje języka C pozwalają nam o tym zapomnieć i same zajmują się szczegoacutełamitechnicznymi Z punktu widzenia programisty urządzenia te sprowadzają się do nadanegoim identyfikatora Uogoacutelnione pliki nazywa się w C strumieniami

Każdy program w momencie uruchomienia ldquootrzymujerdquo od razu trzy otwarte strumienie

stdin (wejście)

stdout (wyjście)

stderr (wyjście błędoacutew)

(aby z nich korzystać należy dołączyć plik nagłoacutewkowy stdioh)Pierwszy z tych plikoacutew umożliwia odczytywanie danych wpisywanych przez użytkow-

nika natomiast pozostałe dwa służą do wyprowadzania informacji dla użytkownika oraz po-wiadamiania o błędach

Warto tutaj zauważyć że konstrukcja

fprintf (stdout Hej ja działam)

jest roacutewnoważna konstrukcji

printf (Hej ja działam)

Podobnie jest z funkcją scanf()

fscanf (stdin d ampzmienna)

działa tak samo jak

scanf(d ampzmienna)

1433 Obsługa błędoacutew

Jeśli nastąpił błąd możemy się dowiedzieć o jego przyczynie na podstawie zmiennej errnozadeklarowanej w pliku nagłoacutewkowym errnoh Możliwe jest też wydrukowanie komunikatuo błedzie za pomocą funkcji perror Na przykład używając

fp = fopen (tego pliku nie ma r)if( fp == NULL )

perror(błąd otwarcia pliku)exit(-10)

102 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

dostaniemy komunikat

błąd otwarcia pliku No such file or directory

1434 Zaawansowane operacje

Pora na kolejny tym razem bardziej złożony przykład Oto kroacutetki program ktoacutery swojewejście zapisuje do pliku o nazwie podanej w linii poleceń

include ltstdiohgtinclude ltstdlibhgt program udający bardzo prymitywną wersję programu tee(1)

int main (int argc char argv[])

FILE fpint cif (argc lt 2)

fprintf (stderr Uzycie s nazwa_plikun argv[0])exit (-1)

fp = fopen (argv[1] w)if (fp)

fprintf (stderr Nie moge otworzyc pliku sn argv[1])exit (-1)

printf(Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczycn)while ( (c = fgetc(stdin)) = EOF)

fputc (c stdout)fputc (c fp)

fclose(fp)return 0

Tym razem skorzystaliśmy już z dużo większego repertuaru funkcji Między innymimożna zauważyć tutaj funkcję fputc() ktoacutera umieszcza pojedynczy znak w pliku Ponadto wwyżej zaprezentowanym programie została użyta stała EOF ktoacutera reprezentuje koniec pliku(ang End Of File) Powyższy program otwiera plik ktoacuterego nazwa przekazywana jest jakopierwszy argument programu a następnie kopiuje dane z wejścia programu (stdin) na wyj-ście (stdout) oraz do utworzonego pliku (identyfikowanego za pomocą fp) Program robi todotąd aż naciśniemy kombinację klawiszy Ctrl+D(w systemach Unixowych) lub Ctrl+Z(wWindows) ktoacutera wyśle do programu informację że skończyliśmy wpisywać dane Programwyjdzie wtedy z pętli i zamknie utworzony plik

144 Rozmiar plikuDzięki standardowym funkcjom języka C możemy min określić długość pliku Do tego celusłużą funkcje fsetpos fgetpos oraz fseek Ponieważ przy każdym odczyciezapisie zdo plikuwskaźnik niejako ldquoprzesuwardquo się o liczbę przeczytanychzapisanych bajtoacutew Możemy jednak

145 PRZYKŁAD mdash PLIKI GRAFICZNY 103

ustawić wskaźnik w dowolnie wybranym miejscu Do tego właśnie służą wyżej wymienionefunkcje Aby odczytać rozmiar pliku powinniśmy ustawić nasz wskaźnik na koniec plikupo czym odczytać ile bajtoacutew od początku pliku się znajdujemy Wiem brzmi to strasznieale działa wyjątkowo prosto i skutecznie Użyjemy do tego tylko dwoacutech funkcji fseek orazfgetpos Pierwsza służy do ustawiania wskaźnika na odpowiedniej pozycji w pliku a drugado odczytywania na ktoacuterym bajcie pliku znajduje się wskaźnik Kod ktoacutery określa rozmiarpliku znajduje się tutaj

include ltstdiohgt

int main (int argc char argv)

FILE fp = NULLfpos_t dlugoscif (argc = 2)

printf (Użycie s ltnazwa plikugtn argv[0])return 1

if ((fp=fopen(argv[1] rb))==NULL) printf (Błąd otwarcia pliku sn argv[1])return 1

fseek (fp 0 SEEK_END) ustawiamy wskaźnik na koniec pliku fgetpos (fp ampdlugosc)printf (Rozmiar pliku dn dlugosc)fclose (fp)return 0

Znajomość rozmiaru pliku przydaje się w wielu roacuteżnych sytuacjach więc dobrze prze-analizuj przykład

145 Przykład mdash pliki graficznyNajprostszym przykładem rastrowego pliku graficznego jest plik Poniższy program po-kazuje jak utworzyć plik w katalogu roboczym programu Do zapisu

nagłoacutewka pliku używana jest funkcja fprintf

tablicy do pliku używana jest funkcja fwrite

include ltstdiohgtint main()

const int dimx = 800const int dimy = 800int i jFILE fp = fopen(firstppm wb) b - tryb binarny fprintf(fp P6nd dn255n dimx dimy)for(j=0 jltdimy ++j)

for(i=0 iltdimx ++i)static unsigned char color[3]

104 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

color[0]=i 255 red color[1]=j 255 green color[2]=(ij) 255 blue fwrite(color13fp)

fclose(fp)return 0

W powyższym przykładzie dostęp do danych jest sekwencyjny Jeśli chcemy mieć swo-bodny dostęp do danych to

korzystać z funkcji fsetpos fgetpos oraz fseek

utworzyć tablicę (dla dużych plikoacutew dynamiczną) zapisać do niej wszystkie dane anastępnie zapisać całą tablicę do pliku Ten sposoacuteb jest prostszy i szybszy Należyzwroacutecić uwagę że do obliczania rozmiaru całej tablicy nie możemy użyć funkcji sizeof

(a) Przykład użycia tej techniki sekwencyjnydostęp do danych (kod źroacutedłowy)

(b) Przykład użycia tej techniki swobodny do-stęp do danych (kod źroacutedłowy)

146 Co z katalogamiFaktycznie zapomnieliśmy o nich Jednak wynika to z tego że specyfikacja ANSI C nieuwzględnia obsługi katalogoacutew

Rozdział 15

Ćwiczenia dla początkujący

151 ĆwiczeniaWszystkie zamieszczone tutaj ćwiczenia mają na celu pomoacutec Ci w sprawdzeniu Twojej wie-dzy oraz umożliwieniu Tobie wykorzystania nowo nabytych wiadomości w praktyce Pa-miętaj także że ten podręcznik ma służyć także innym więc nie zamieszczaj tutaj Twoichrozwiązań Zachowaj je dla siebie

1511 Ćwiczenie 1

Napisz program ktoacutery wyświetli na ekranie twoje imię i nazwisko

1512 Ćwiczenie 2

Napisz program ktoacutery poprosi o podanie dwoacutech liczb rzeczywistych i wyświetli wynik mno-żenia obu zmiennych

1513 Ćwiczenie 3

Napisz program ktoacutery pobierze jako argumenty z linii komend nazwy dwoacutech plikoacutew i prze-kopiuje zawartość pierwszego pliku do drugiego (tworząc lub zamazując drugi)

1514 Ćwiczenie 4

Napisz program ktoacutery utworzy nowy plik (o dowolnie wybranej przez Ciebie nazwie) i za-pisze tam

Twoje imię

wiek

miasto w ktoacuterym mieszkasz

Przykładowy plik powinien wyglądać tak

Stanisław30Krakoacutew

105

106 ROZDZIAŁ 15 ĆWICZENIA DLA POCZĄTKUJĄCYCH

1515 Ćwiczenie 5

Napisz program generujący tabliczkę mnożenia x i wyświetlający ją na ekranie

1516 Ćwiczenie 6 mdash dla ętny

Napisz program znajdujący pierwiastki troacutejmianu kwadratowego ax2+bx+c= dla zadanychparametroacutew a b c

Rozdział 16

Tablice

W rozdziale Zmienne w C dowiedziałeś się jak przechowywać pojedyncze liczby oraz znakiCzasami zdarza się jednak że potrzebujemy przechować kilka kilkanaście albo iwięcej zmien-nych jednego typu Nie tworzymy wtedy np dwudziestu osobnych zmiennych W takichprzypadkach z pomocą przychodzi nam tablica

Rysunek 161 tablica 10-elementowa

Tablica to ciąg zmiennych jednego typu Ciąg taki posiada jedną nazwę a do jego po-szczegoacutelnych elementoacutew odnosimy się przez numer (indeks)

161 Wstęp

1611 Sposoby deklaracji tablic

Tablicę deklaruje się w następujący sposoacuteb

typ nazwa_tablicy[rozmiar]

gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy Zatemaby np zadeklarować tablicę mieszczącą liczb całkowitych możemy napisać tak

int tablica[20]

Podobnie jak przy deklaracji zmiennych także tablicy możemy nadać wartości począt-kowe przy jej deklaracji Odbywa się to przez umieszczenie wartości kolejnych elementoacutewoddzielonych przecinkami wewnątrz nawiasoacutew klamrowych

int tablica[3] = 123

Może to się wydać dziwne ale po ostatnim elemencie tablicy może występować przeci-nek Ponadto jeżeli poda się tylko część wartości w pozostałe wpisywane są zera

107

108 ROZDZIAŁ 16 TABLICE

int tablica[20] = 1

Niekoniecznie trzeba podawać rozmiar tablicy np

int tablica[] = 1 2 3 4 5

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku mdash elemen-toacutew)

Rozpatrzmy następujący kod

include ltstdiohgtdefine ROZMIAR 3int main()

int tab[ROZMIAR] = 368int iprintf (Druk tablicy tabn)

for (i=0 iltROZMIAR ++i) printf (Element numer d = dn i tab[i])

return 0

Wynik

Druk tablicy tabElement numer 0 = 3Element numer 1 = 6Element numer 2 = 8

Jak widać wszystko się zgadza W powyżej zamieszczonym przykładzie użyliśmy stałejdo podania rozmiaru tablicy Jest to o tyle pożądany zwyczaj że w razie konieczności zmianyrozmiaru tablicy zmieniana jest tylko jedna linijka kodu przy stałej a nie kilkadziesiąt innychlinijek rozsianych po kodzie całego programu

W pierwotnym standardzie języka C rozmiar tablicy nie moacutegł być określany przezzmienną lub nawet stałą zadeklarowaną przy użyciu słowa kluczowego const Dopiero wpoacuteźniejszej wersji standardu (tzw C) dopuszczono taką możliwość Dlatego do dekla-rowania rozmiaru tablic często używa się dyrektywy preprocesora define Powinni na tozwroacutecić uwagę zwłaszcza programiści C++ gdyż tam zawsze możliwe były oba sposoby

Innym sposobem jest użycie operatora sizeof do poznania wielkości tablicy Poniższy kodrobi to samo co przedstawiony

include ltstdiohgtint main()

int tab[3] = 368int i

162 ODCZYTZAPIS WARTOŚCI DO TABLICY 109

printf (Druk tablicy tabn)

for (i=0 ilt(sizeof tab sizeof tab) ++i) printf (Element numer d = dn i tab[i])

return 0

Należy pamiętać że działa on tylko dla tablic a nie wskaźnikoacutew (jak poacuteźniej się dowieszwskaźnik też można w pewnym stopniu traktować jak tablicę)

162 Odczytzapis wartości do tablicyTablicami posługujemy się tak samo jak zwykłymi zmiennymi Roacuteżnica polega jedynie napodaniu indeksu tablicy Określa on jednoznacznie z ktoacuterego elementu (wartości) chcemyskorzystać Indeksem jest liczba naturalna począwszy od zera To oznacza że pierwszy ele-ment tablicy ma indeks roacutewny drugi trzeci itd

Osoby ktoacutere wcześniej programowały w językach takich jak Pascal Basic czy Fortranmuszą przyzwyczaić się do tego że w języku C indeks numeruje się od Ponadto indeksempowinna być liczba - istnieje możliwość indeksowania za pomocą np pojedynczych znakoacutew(rsquoarsquo rsquobrsquo itp) jednak Cwewnętrznie konwertuje takie znaki na liczby im odpowiadające zatemtablica indeksowana znakami byłaby niepraktyczna i musiałaby mieć odpowiednio większyrozmiar

Sproacutebujmy przedstawić to na działającym przykładzie Przeanalizuj następujący kod

int tablica[5] = 0int i = 0tablica[2] = 3tablica[3] = 7for (i=0i=5++i)

printf (tablica[d]=dn i tablica[i])

Jak widać na początku deklarujemy -elementową tablicę ktoacuterą od razu zerujemy Na-stępnie pod trzeci i czwarty element podstawiamy liczby i Pętla ma za zadanie wypro-wadzić wynik naszych działań

163 Tablice znakoacutewTablice znakoacutew tj typu char oraz unsigned char posiadają dwie ogoacutelnie przyjęte nazwyzależnie od ich przeznaczenia

bufory mdash gdy wykorzystujemy je do przechowywania ogoacutelnie pojętych danych gdytraktujemy je jako po prostu ldquociągi bajtoacutewrdquo (typ char ma rozmiar bajta więc jestelastyczny do przechowywania np danych wczytanych z pliku przed ich przetworze-niem)

napisy mdash gdy zawarte w nich dane traktujemy jako ciągi liter jest im poświęconyosobny rozdział Napisy

110 ROZDZIAŁ 16 TABLICE

164 Tablice wielowymiarowe

Rysunek tablica dwuwymia-rowa (x)

Rozważmy teraz konieczność przechowania w pa-mięci komputera całej macierzy o wymiarach x Można by tego dokonać tworząc osobnych ta-blic jednowymiarowych reprezentujących poszcze-goacutelne wiersze macierzy Jednak język C dostarczanam dużo wygodniejszej metody ktoacutera w dodatkujest bardzo łatwa w użyciu Są to tablice wielowy-miarowe lub inaczej ldquotablice tablicrdquo Tablice wielo-wymiarowe definiujemy podając przy zmiennej kilkawymiaroacutew np

float macierz[10][10]

Tak samo wygląda dostęp do poszczegoacutelnych ele-mentoacutew tablicy

macierz[2][3] = 12

Jakwidać ten sposoacuteb jest dużowygodniejszy (i za-pewne dużo bardziej ldquonaturalnyrdquo) niż deklarowanie osobnych tablic jednowymiarowych Aby zaini-cjować tablicę wielowymiarową należy zastosowaćzagłębianie klamer np

float macierz[3][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz

Dodatkowo pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowy-miarowych) i woacutewczas kompilator sam ustali odpowiednią wielkość np

float macierz[][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz 63 27 57 27 czwarty wiersz

Innym bardziej elastycznym sposobem deklarowania tablic wielowymiarowych jest uży-cie wskaźnikoacutew Opisane to zostało w następnym rozdziale

165 Ograniczenia tablicPomimo swej wygody tablice mają ograniczony z goacutery zdefiniowany rozmiar ktoacuterego niemożna zmienić w trakcie działania programu Dlatego też w niektoacuterych zastosowaniach ta-blice zostały wyparte przez dynamiczną alokację pamięci Opisane to zostało w następnymrozdziale

166 CIEKAWOSTKI 111

Przy używaniu tablic trzeba być szczegoacutelnie ostrożnym przy konstruowaniu pętli ponie-waż ani kompilator ani skompilowany program nie będą w stanie wychwycić przekroczeniaprzez indeks rozmiaru tablicy 1 Efektem będzie odczyt lub zapis pamięci znajdującej się pozatablicą

Wystarczy pomylić się o jednomiejsce (tzw błąd off by one) by spowodować że działanieprogramu zostanie nagle przerwane przez system operacyjny

int foo[100]int i

for (i=0 ilt=100 ++i) powinno być ilt100 foo[i] = 0

166 CiekawostkiW pierwszej edycji konkursu IOCCC zwyciężył program napisany w C ktoacutery wyglądał dośćnietypowo

short main[] = 277 04735 -4129 25 0 477 1019 0xbef 0 12800-113 21119 0x52d7 -1006 -7151 0 0x4bc 02000414880 10541 2056 04010 4548 3044 -6716 0x94407 6 5568 1 -30460 0 0x9 5570 512 -304190x7e82 0760 6 0 4 02400 15 0 4 1280 4 04 0 0 0 0x8 0 4 0 0 12 0 4 0 0 020 0 4 0 30 0 026 0 0x6176 120 25712p 072163 r 29303 29801 e

Co ciekawe mdash program ten bez przeszkoacuted wykonywał się na komputerach VAX- orazPDP- Cały program to po prostu tablica z zawartym wewnątrz kodem maszynowym Taknaprawdę jest to wykorzystanie pewnych właściwości programu ktoacutery ostatecznie produ-kuje kod maszynowy Linker (to o nim mowa) nie rozroacuteżnia na dobrą sprawę nazw funkcjiod nazw zmiennych więc bez problemu ustawił punkt wejścia programu na tablicę wartościw ktoacuterych zapisany był kod maszynowy Tak przygotowany program został bez problemuwykonany przez komputer

1W zasadzie kompilatory mają możliwość dodania takiego sprawdzania ale nie robi się tego gdyż znaczniespowolniłoby to działanie programu Takie postępowanie jest jednak pożądane w okresie testowania programu

112 ROZDZIAŁ 16 TABLICE

Rozdział 17

Wskaźniki

Zobacz w WikipediiZmienna wskaźnikowaZmienne w komputerze są przechowywane w pamięci To wie każdy programista a dobry

programista potrafi kontrolować zachowanie komputera w przydzielaniu i obsługi pamięcidla zmiennych W tym celu pomocne są wskaźniki

171 Co to jest wskaźnik

Dla ułatwienia przyjęto poniżej że bajt ma bitoacutew typ int składa się z dwoacutech bajtoacutew(bitoacutew) typ long składa się z czterech bajtoacutew ( bitoacutew) oraz liczby zapisane są w formaciebig endian (tzn bardziej znaczący bajt na początku) co niekoniecznie musi być prawdą naTwoim komputerze

Rysunek Wskaźnik awskazu-jący na zmienną b Zauważmy żeb przechowuje liczbę podczas gdya przechowuje adres b w pamięci()

Wskaźnik (ang pointer) to specjalny rodzaj zmiennejw ktoacuterej zapisany jest adres w pamięci komputera tznwskaźnik wskazuje miejsce gdzie zapisana jest jakaśinformacja Oczywiście nic nie stoi na przeszkodzie abywskazywaną daną był innywskaźnik do kolejnegomiej-sca w pamięci

Obrazowo możemy wyobrazić sobie pamięć kom-putera jako bibliotekę a zmienne jako książki Zamiastbrać książkę z poacutełki samemu (analogicznie do korzy-stania wprost ze zwykłych zmiennych) możemy podaćbibliotekarzowi wypisany rewers z numerem katalogo-wym książki a on znajdzie ją za nas Analogia ta niejest doskonała ale pozwalawyobrazić sobie niektoacutere ce-chy wskaźnikoacutew kilka rewersoacutew może dotyczyć tej sa-mej książki numer w rewersie możemy skreślić i użyćgo do zamoacutewienia innej książki jeśli wpiszemy niepra-widłowy numer katalogowy to możemy dostać nie tąksiążkę ktoacuterą chcemy albo też nie dostać nic

Warto też poznać w tym miejscu definicję adresupamięci Możemy powiedzieć że adres to pewna liczba całkowita jednoznacznie definiującapołożenie pewnego obiektu (czyli np znaku czy liczby) w pamięci komputera Dokładniejsządefinicję możesz znaleźć w Wikipedii

113

114 ROZDZIAŁ 17 WSKAŹNIKI

172 Operowanie na wskaźnikaBy stworzyć wskaźnik do zmiennej i moacutec się nim posługiwać należy przypisać mu odpo-wiednią wartość (adres obiektu na jaki ma wskazywać) Skąd mamy znać ten adres Wy-starczy zapytać nasz komputer jaki adres przydzielił zmiennej ktoacuterą np wcześniej gdzieśstworzyliśmy Robi się to za pomocą operatora amp (operatora pobrania adresu) Przeanalizujnastępujący kod1

include ltstdiohgt

int main (void)

int liczba = 80printf(Zmienna znajduje sie pod adresem p i przechowuje wartosc dn

(void)ampliczba liczba)return 0

Program ten wypisuje adres pamięci pod ktoacuterym znajduje się zmienna oraz wartość jakąkryje zmienna przechowywana pod owym adresem

Aby moacutec zapisać gdzieś taki adres należy zadeklarować zmienną wskaźnikową Robi sięto poprzez dodanie (gwiazdki) po typie na jaki zmienna ma wskazywać np

int wskaznik1char wskaznik2floatwskaznik3

Niektoacuterzy programiści mogą nieco błędnie interpretować wskaźnik do typu jako nowytyp i uważać że jeśli napiszą

int abc

to otrzymają trzy wskaźniki do liczby całkowitej Tymczasem wskaźnikiem będzie tylkozmienna a natomiast b i c będą po prostu liczbami Powodem jest to że rdquogwiazdkaodnosi siędo zmiennej a nie do typu W tym przypadku trzy wskaźniki otrzymamy pisząc

int abc

Aby uniknąć pomyłek lepiej jest pisać gwiazdkę tuż przy zmiennej

int abc

albo jeszcze lepiej nie mieszać deklaracji wskaźnikoacutew i zmiennych

int aint bc

Aby dobrać się dowartości wskazywanej przez wskaźnik należy użyć unarnego operatora (gwiazdka) zwanego operatorem wyłuskania

1Warto zwroacutecić uwagę na rzutowanie do typu wskaźnik na void Rzutowanie to jest wymagane przez funkcjęprintf gdyż ta oczekuje że argumentem dla formatu p będzie właśnie wskaźnik na void gdy tymczasem w naszymprzykładzie wyrażenie ampliczba jest typu wskaźnik na int

172 OPEROWANIE NA WSKAŹNIKACH 115

include ltstdiohgt

int main (void)

int liczba = 80int wskaznik = ampliczbaprintf(Wartosc zmiennej d jej adres pn liczba (void)ampliczba)printf(Adres zapisany we wskazniku p wskazywana wartosc dn

(void)wskaznik wskaznik)

wskaznik = 42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

liczba = 0x42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

return 0

1721 O coodzi z tym typem na ktoacutery ma wskazywać Czemu to takieważne

Jest to ważne z kilku powodoacutewRoacuteżne typy zajmują w pamięci roacuteżną wielkość Przykładowo jeżeli w zmiennej typu

unsigned int zapiszemy liczbę to w pamięci będzie istnieć jako

+--------+--------+|komoacuterka1|komoacuterka2|+--------+--------+|11111111|11111010| = (unsigned int) 65530+--------+--------+

Wskaźnik do takiej zmiennej (jak i do dowolnej innej) będzie wskazywać na pierwsząkomoacuterkę w ktoacuterej ta zmienna ma swoją wartość

Jeżeli teraz stworzymy drugi wskaźnik do tego adresu tym razem typu unsigned arto wskaźnik przejmie ten adres prawidłowo2 lecz gdy sproacutebujemy odczytać wartość na jakąwskazuje ten wskaźnik to zostanie odczytana tylko pierwsza komoacuterka i wynik będzie roacutewny

+--------+|komoacuterka1|+--------+|11111111| = (unsigned char) 255+--------+

2Tak naprawdę nie zawsze można przypisywać wartości jednych wskaźnikoacutew do innych Standard C gwaran-tuje jedynie że można przypisać wskaźnikowi typu void wartość dowolnego wskaźnika a następnie przypisać tąwartość do wskaźnika pierwotnego typu oraz że dowolny wskaźnik można przypisać do wskaźnika typu char

116 ROZDZIAŁ 17 WSKAŹNIKI

Gdybyśmy natomiast stworzyli inny wskaźnik do tego adresu tym razem typu unsignedlong to przy proacutebie odczytu odczytane zostaną dwa bajty z wartością zapisaną w zmiennejunsigned int oraz dodatkowe dwa bajty z niewiadomą zawartością i woacutewczas wynik będzieroacutewny + przypadkowa wartość

+--------+--------+--------+--------+|komoacuterka1|komoacuterka2|komoacuterka3|komoacuterka4|+--------+--------+--------+--------+|11111111|11111010|||+--------+--------+--------+--------+

Ponadto zapis czy odczyt poza przydzielonym obszarem pamięci może prowadzić donieprzyjemnych skutkoacutew takich jak zmiana wartości innych zmiennych czy wręcz natych-miastowe przerwanie programu Jako przykład można podać ten (błędny) program3

include ltstdiohgt

int main(void)

unsigned char tab[10] = 100 101 102 103 104 105 106 107 108 109 unsigned short ptr = (unsigned short)amptab[2]unsigned i

ptr = 0xfffffor (i = 0 i lt 10 ++i)

printf(dn tab[i])tab[i] = tab[i] - 100

printf(poza tablica dn tab[10])tab[10] = -1return 0

Nie można roacutewnież zapominać że na niektoacuterych architekturach dane wielobajtowe mu-szą być odpowiednio wyroacutewnane w pamięci Np zmienna dwubajtowa może się znajdowaćjedynie pod parzystymi adresami Woacutewczas gdybyśmy chcieli adres zmiennej jednobajto-wej przypisać wskaźnikowi na zmienną dwubajtową mogłoby dojść do nieprzewidzianychbłędoacutew wynikających z proacuteby odczytu niewyroacutewnanej danej

Zaskakującemoże się okazać że roacuteżnewskaźniki mogąmieć roacuteżny rozmiar Np wskaźnikna ar może być większy od wskaźnika na int ale roacutewnież na odwroacutet Co więcej wskaźnikiroacuteżnych typoacutewmogą się roacuteżnić reprezentacją adresoacutew Dla przykładuwskaźnik naar możeprzechowywać adres do bajtu natomiast wskaźnik na int ten adres podzielony przez

Podsumowując roacuteżne wskaźniki to roacuteżne typy i nie należy beztrosko rzutować wyrażeńpomiędzy roacuteżnymi typami wskaźnikowymi bo grozi to nieprzewidywalnymi błędami

1722 Do czego służy typ void

Czasami zdarza się że nie wiemy na jaki typwskazuje danywskaźnik W takich przypadkachstosujemy typ void Sam void nie znaczy nic natomiast void oznacza ldquowskaźnik na obiekt

3Może się okazać że błąd nie będzie widoczny na Twoim komputerze

173 ARYTMETYKA WSKAŹNIKOacuteW 117

w pamięci niewiadomego typurdquo Taki wskaźnik możemy potem odnieść do konkretnego typudanych (w języku C++ wymagana jest do tego operacja rzutowania) Na przykład funkcjamalloc zwraca właśnie wskaźnik za pomocą void

173 Arytmetyka wskaźnikoacutew

W języku C do wskaźnikoacutew można dodawać lub odejmować liczby całkowite Istotne jestjednak że dodanie do wskaźnika liczby nie spowoduje przesunięcia się w pamięci kom-putera o dwa bajty Tak naprawdę przesuniemy się o rozmiar zmiennej Jest to bardzoważna informacja Początkujący programiści popełniają często dużo błędoacutew związanych znieprawidłową arytmetyką wskaźnikoacutew

Zobaczmy na przykład

int ptrint a[] = 1 2 3 5 7ptr = ampa[0]

Rysunek 172 Wskaźnik wskazuje na pierwszą komoacuterkę pamięci

Otrzymujemy następującą sytuacjęGdy wykonamy

ptr += 2

Rysunek 173 Przesunięcie wskaźnika na kolejne komoacuterki

wskaźnik ustawi się na trzecim elemencie tablicyWskaźniki można roacutewnież od siebie odejmować czego wynikiem jest odległość dwoacutech

wskazywanych wartości Odległość zwracana jest jako liczba obiektoacutew danego typu a nieliczba bajtoacutew Np

int a[] = 1 2 3 5 7int ptr = ampa[2]int diff = ptr - a diff ma wartość 2 (a nie 2sizeof(int))

118 ROZDZIAŁ 17 WSKAŹNIKI

Wynikiem może być oczywiście liczba ujemna Operacja jest przydatna do obliczaniawielkości tablicy (długości łańcucha znakoacutew) jeżeli mamy wskaźnik na jej pierwszy i ostatnielement

Operacje arytmetyczne na wskaźnikach mają pewne ograniczenia Przede wszystkim niemożna (tzn standard tego nie definiuje) skonstruować wskaźnika wskazującego gdzieś pozazadeklarowaną tablicę chyba że jest to obiekt zaraz za ostatnim (one past last) np

int a[] = 1 2 3 5 7int ptrptr = a + 10 niezdefiniowane ptr = a - 10 niezdefiniowane ptr = a + 5 zdefiniowane (element za ostatnim) ptr = 10 to już nie

Nie można4 roacutewnież odejmować od siebie wskaźnikoacutew wskazujących na obiekty znajdu-jące się w roacuteżnych tablicach np

int a[] = 1 2 3 b[] = 5 7int ptr1 = a ptr2 = bint diff = a - b niezdefiniowane

174 Tablice a wskaźniki

Trzeba wiedzieć że tablice to też rodzaj zmiennej wskaźnikowej Taki wskaźnik wskazuje namiejsce w pamięci gdzie przechowywany jest jej pierwszy element Następne elementy znaj-dują się bezpośrednio w następnych komoacuterkach pamięci w odstępie zgodnym z wielkościąodpowiedniego typu zmiennej

Na przykład tablica

int tab[] = 100200300

występuje w pamięci w sześciu komoacuterkach5

+--------+--------+--------+--------+--------+--------+|wartosc1| |wartosc2| |wartosc3| |+--------+--------+--------+--------+--------+--------+|00000000|01100100|00000000|11001000|00000001|00101100|+--------+--------+--------+--------+--------+--------+

Stąd do trzeciej wartości można się dostać tak (komoacuterki w tablicy numeruje się od zera)

zmienna = tab[2]

albo wykorzystując metodę wskaźnikową

zmienna = (tab + 2)

4To znaczy standard nie definiuje co się wtedy stanie aczkolwiek na większości architektur odejmowanie do-wolnych dwoacutech wskaźnikoacutew ma zdefiniowane zachowanie Pisząc przenośne programy nie można jednak na tympolegać zwłaszcza że odejmowanie wskaźnikoacutew wskazujących na elementy roacuteżnych tablic zazwyczaj nie ma sensu

5Ponownie przyjmując że bajt ma 8 bitoacutew int dwa bajty i liczby zapisywane są w formacie lile endian

175 GDY ARGUMENT JEST WSKAŹNIKIEM 119

Z definicji obie te metody są roacutewnoważneZ definicji (z wyjątkiem użycia operatora sizeo) wartością zmiennej lub wyrażenia typu tablico-

wego jest wskaźnik na jej pierwszy element (tab == amptab[0])Co więcej można poacutejść w drugą stronę i potraktować wskaźnik jak tablicę

int wskaznikwskaznik = amptab[1] lub wskaznik = tab + 1 zmienna = wskaznik[1] przypisze 300

Jako ciekawostkę podamy iż w języku C można odnosić się do elementoacutew tablicy jeszcze w innysposoacuteb

printf (dn 1[tab])

Skąd ta dziwna notacja Uzasadnienie jest proste

tab[1] = (tab + 1) = (1 + tab) = 1[tab]

Podobną składnię stosuje min asembler GNU

175 Gdy argument jest wskaźnikiem Czasami zdarza się że argumentem (lub argumentami) funkcji są wskaźniki W przypadku ldquonormal-nychrdquo zmiennych nasza funkcja działa tylko na lokalnych kopiach tychże argumentoacutew natomiast niezmienia zmiennych ktoacutere zostały podane jako argument Natomiast w przypadku wskaźnika każdaoperacja na wartości wskazywanej powoduje zmianę wartości zmiennej zewnętrznej Sproacutebujmy roz-patrzeć poniższy przykład

include ltstdiohgt

void func (int zmienna)

zmienna = 5

int main ()

int z=3printf (z=dn z) wypisze 3 func(ampz)printf (z=dn z) wypisze 5

Widzimy że funkcje w języku C nie tylko potrafią zwracać określoną wartość lecz także zmieniaćdane podane im jako argumenty Ten sposoacuteb przekazywania argumentoacutew do funkcji jest nazywanyprzekazywaniem przez wskaźnik (w przeciwieństwie do normalnego przekazywania przez wartość)

Zwroacutećmy uwagę na wywołanie func(ampz) Należy pamiętać by do funkcji przekazać adres zmien-nej a nie samą zmienną Jeśli byśmy napisali func(z) to funkcja starałaby się zmienić komoacuterkę pamięcio numerze Kompilator powinien ostrzec w takim przypadku o konwersji z typu int do wskaźnikaale często kompiluje taki program pozostając na ostrzeżeniu

Nie gra roli czy przy deklaracji funkcji jako argument funkcji podamy wskaźnik czy tablicę (z po-danym rozmiarem lub nie) np poniższe deklaracje są identyczne

120 ROZDZIAŁ 17 WSKAŹNIKI

void func(int ptr[])void func(int ptr)

Można przyjąć konwencję że deklaracja określa czy funkcji przekazujemy wskaźnik do pojedyn-czego argumentu czy do sekwencji ale roacutewnie dobrze można za każdym razem stosować gwiazdkę

176 Pułapki wskaźnikoacutewWażne jest aby przy posługiwaniu się wskaźnikami nigdy nie proacutebować odwoływać się do komoacuterkiwskazywanej przez wskaźnik o wartości lub niezainicjowany wskaźnik Przykładem nieprawi-dłowego kodu może być np

int wskprintf (zawartosc komorki dn (wsk)) Błąd wsk = 0 0 w kontekście wskaźnikoacutew oznacza wskaźnik NULL printf (zawartosc komorki dn (wsk)) Błąd

Należy roacutewnież uważać aby nie odwoływać się do komoacuterek poza przydzieloną pamięcią np

int tab[] = 0 1 2 tab[3] = 3 Błąd

Pamiętaj też że możesz być rozczarowany używając operatora sizeof podając zmienną wskaźni-kową Uzyskana wielkość będzie wielkością wskaźnika a nie wielkością typu użytego podczas deklaro-wania naszego wskaźnika Wielkość ta będzie zawsze miała taki sam rozmiar dla każdego wskaźnikaw zależności od kompilatora a także docelowej platformy Zamiast tego używaj sizeof(wskaźnik)Przykład

char zmiennaint a = sizeof zmienna a wynosi np 4 tj sizeof(char) a = sizeof(char) robimy to samo co wyżej a = sizeof zmienna zmienna a ma teraz przypisany rozmiar

pojedynczego znaku tj 1 a = sizeof(char) robimy to samo co wyżej

177 Na co wskazuje Analizując kody źroacutedłowe programoacutew często można spotkać taki oto zapis

void wskaznik = NULL lub = 0

Wiesz już że nie możemy odwołać się pod komoacuterkę pamięci wskazywaną przez wskaźnik Poco zatem przypisywać wskaźnikowi Odpowiedź może być zaskakująca właśnie po to aby uniknąćbłędoacutew Wydaje się to zabawne ale większość (jeśli nie wszystkie) funkcji ktoacutere zwracają wskaźnikw przypadku błędu zwroacuteci właśnie czyli zero Tutaj rodzi się kolejna wskazoacutewka jeśli w danejzmiennej przechowujemy wskaźnik zwroacutecony wcześniej przez jakąś funkcję zawsze sprawdzajmy czynie jest on roacutewny () Wtedy mamy pewność że funkcja zadziałała poprawnie

Dokładniej nie jest słowem kluczowym lecz stałą (makrem) zadeklarowaną przez dyrektywypreprocesora Deklaracja taka może być albo wartością albo też wartością zrzutowaną na void(((void )0)) ale też jakimś słowem kluczowym deklarowanym przez kompilator

Warto zauważyć że pomimo przypisywania wskaźnikowi zera nie oznacza to że wskaźnik jest reprezentowany przez same zerowe bity Co więcej wskaźniki roacuteżnych typoacutew mogą miećroacuteżną wartość Z tego powodu poniższy kod jest niepoprawny

int tablica_wskaznikow = calloc(100 sizeof tablica_wskaznikow)

178 STAŁE WSKAŹNIKI 121

Zakłada on że w reprezentacji wskaźnika występują same zera Poprawnym zainicjowaniemdynamicznej tablicy wskaźnikoacutew wartościami jest (pomijamy sprawzdanie wartości zwroacuteconejprzez malloc())

int tablica_wskaznikow = malloc(100 sizeof tablica_wskaznikow)int i = 0while (ilt100)

tablica_wskaznikow[i++] = 0

178 Stałe wskaźnikiTak jak istnieją zwykłe stałe tak samo możemy mieć stałe wskaźniki mdash jednak są ich dwa rodzajeWskaźniki na stałą wartość

const int a lub roacutewnoważnie int const a

oraz stałe wskaźniki

int const b

Pierwszy to wskaźnik ktoacuterym nie można zmienić wskazywanej wartości Drugi to wskaźnik ktoacute-rego nie można przestawić na inny adres Dodatkowo można zadeklarować stały wskaźnik ktoacuterym niemożna zmienić wartości wskazywanej zmiennej i roacutewnież można zrobić to na dwa sposoby

const int const c alternatywnie int const const c

int i=0const int a=ampiint const b=ampiint const const c=ampia = 1 kompilator zaprotestuje b = 2 ok c = 3 kompilator zaprotestuje a = b ok b = a kompilator zaprotestuje c = a kompilator zaprotestuje

Wskaźniki na stałą wartość są przydatne między innymi w sytuacji gdy mamy duży obiekt (naprzykład strukturę z kilkoma polami) Jeśli przypiszemy taką zmienną do innej zmiennej kopiowaniemoże potrwać dużo czasu a oproacutecz tego zostanie zajęte dużo pamięci Przekazanie takiej struktury dofunkcji albo zwroacutecenie jej jako wartość funkcji wiąże się z takim samym narzutem W takim wypadkudobrze jest użyć wskaźnika na stałą wartość

void funkcja(const duza_struktura ds)

czytamy z ds i wykonujemy obliczenia

funkcja(ampdane) mamy pewność że zmienna dane nie zostanie zmieniona

122 ROZDZIAŁ 17 WSKAŹNIKI

179 Dynamiczna alokacja pamięciMając styczność z tablicami można się zastanowić czy nie dałoby się mieć tablic ktoacuterych rozmiardostosowuje się do naszych potrzeb a nie jest na stałe zaszyty w kodzie programu Chcąc pomieścićwięcej danych możemy po prostu zwiększyć rozmiar tablicy mdash ale gdy do przechowania będzie mniejelementoacutew okaże się że marnujemy pamięć Język C umożliwia dzięki wskaźnikom i dynamicznejalokacji pamięci tworzenie tablic takiej wielkości jakiej akurat potrzebujemy

1791 O co odziCzym jest dynamiczna alokacja pamięci Normalnie zmienne programu przechowywane są na tzwstosie (ang sta) mdash powstają gdy program wchodzi do bloku w ktoacuterym zmienne są zadeklarowane azwalniane w momencie kiedy program opuszcza ten blok Jeśli deklarujemy tak tablice to ich rozmiarmusi być znanywmomencie kompilacji mdash żeby kompilator wygenerował kod rezerwujący odpowiedniąilość pamięci Dostępny jest jednak drugi rodzaj rezerwacji (czyli alokacji) pamięci Jest to alokacja nastercie (ang heap) Sterta to obszar pamięci wspoacutelny dla całego programu przechowywane są w nimzmienne ktoacuterych czas życia nie jest związany z poszczegoacutelnymi blokami Musimy sami rezerwować dlanich miejsce i to miejsce zwalniać ale dzięki temu możemy to zrobić w dowolnym momencie działaniaprogramu

Należy pamiętać że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analo-giczne działania na stosie Dodatkowo zmienna zajmuje na stercie więcej miejsca niż na stosie mdash stertautrzymuje specjalną strukturę w ktoacuterej trzymane są wolne partie (może to być np lista) Tak więcużywajmy dynamicznej alokacji tam gdzie jest potrzebna mdash dla danych ktoacuterych rozmiaru nie jesteśmyw stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem w ktoacuterymzostały zaalokowane

1792 Obsługa pamięciPodstawową funkcją do rezerwacji pamięci jest funkcja malloc Jest to niezbyt skomplikowana funkcjamdash podając jej rozmiar (w bajtach) potrzebnej pamięci dostajemy wskaźnik do zaalokowanego obszaru

Załoacuteżmy że chcemy stworzyć tablicę liczb typu float

int rozmiarfloat tablica

rozmiar = 3tablica = (float) malloc(rozmiar sizeof tablica)tablica[0] = 01

Przeanalizujmy teraz po kolei co dzieje się w powyższym fragmencie Najpierw deklarujemyzmiennemdash rozmiar tablicy i wskaźnik ktoacutery będzie wskazywał obszarw pamięci gdzie będzie trzymanatablica Do zmiennej rozmiar możemy w trakcie działania programu przypisać cokolwiek mdash wczytaćją z pliku z klawiatury obliczyć wylosować mdash nie jest to istotne rozmiar sizeof tablica obliczapotrzebną wielkość tablicy Dla każdej zmiennej float potrzebujemy tyle bajtoacutew ile zajmuje ten typdanych Ponieważ może się to roacuteżnić na rozmaitych maszynach istnieje operator sizeof zwracającydla danego wyrażenia rozmiar jego typu w bajtach

W wielu książkach (roacutewnież KampRv) i w Internecie stosuje się inny schemat użycia funkcji malloca mianowicie tablica = (float)malloc(rozmiar sizeof(float)) Takie użycie należy traktowaćjako błędne gdyż nie sprzyja ono poprawnemu wykrywaniu błędoacutew

Rozważmy sytuację gdy programista zapomni dodać plik nagłoacutewkowy stdlibh woacutewczas kompila-tor (z braku deklaracji funkcji malloc) przyjmie że zwraca ona typ int zatem do zmiennej tablica (ktoacuterajest wskaźnikiem) będzie przypisywana liczba całkowita co od razu spowoduje błąd kompilacji (a przy-najmniej ostrzeżenie) dzięki czemu będzie można szybko poprawić kod programu Rzutowanie jestkonieczne tylko w języku C++ gdzie konwersja z void na inne typy wskaźnikowe nie jest domyślnaale język ten oferuje nowe sposoby alokacji pamięci

179 DYNAMICZNA ALOKACJA PAMIĘCI 123

Teraz rozważmy sytuację gdy zdecydujemy się zwiększyć dokładność obliczeń i zamiast typu floatużyć typu double Będziemy musieli wyszukać wszystkie wywołania funkcji malloc calloc i reallocodnoszące się do naszej tablicy i zmieniać wszędzie sizeof(float) na sizeof(double) Aby temu zapo-biec lepiej od razu użyć sizeof tablica (lub jeśli ktoś woli z nawiasami sizeof(tablica)) woacutewczaszmiana typu zmiennej tablica na double zostanie od razu uwzględniona przy alokacji pamięci

Dodatkowo należy sprawdzić czy funkcja malloc nie zwroacuteciła wartości mdash dzieje się tak gdyzabrakło pamięci Ale uwaga może się tak stać roacutewnież jeżeli jako argument funkcji podano zero

Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić aby sys-tem operacyjny moacutegł go przydzielić innym potrzebującym procesom Do zwolnienia obszaru pamięciużywamy funkcji free() ktoacutera przyjmuje tylko jeden argument mdash wskaźnik ktoacutery otrzymaliśmy wwyniku działania funkcji malloc()

free (addr)

Należy pamiętać o zwalnianiu pamięci mdash inaczej dojdzie do tzw wycieku pamięci mdash program będzierezerwował nową pamięć ale nie zwracał jej z powrotem i w końcu pamięci może mu zabraknąć

Należy też uważać by nie zwalniać dwa razy tego samegomiejsca Po wywołaniu free wskaźnik niezmienia wartości pamięć wskazywana przez niego może też nie od razu ulec zmianie Czasemmożemywięc korzystać ze wskaźnika (zwłaszcza czytać) po wywołaniu free nie orientując się że robimy coś źlemdash i w pewnym momencie dostać komunikat o nieprawidłowym dostępie do pamięci Z tego powoduzaraz po wywołaniu funkcji free można przypisać wskaźnikowi wartość

Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci Tu z pomocąprzychodzi funkcja realloc

tablica = realloc(tablica 2rozmiarsizeof tablica)

Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub gdy zabrakło pa-mięci) Uwaga mdash może to być inny wskaźnik Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanymaktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca funkcja znajdzie nowe miejscei przekopiuje tam starą zawartość Jak widać wywołanie tej funkcji może być więc kosztowne podwzględem czasu

Ostatnią funkcją jest funkcja calloc() Przyjmuje ona dwa argumenty liczbę elementoacutew tablicyoraz wielkość pojedynczego elementu Podstawową roacuteżnicą pomiędzy funkcjami malloc() i calloc() jestto że ta druga zeruje wartość przydzielonej pamięci (do wszystkich bajtoacutew wpisuje wartość )

1793 Tablice wielowymiarowe

Rysunek 174 tablica dwuwymiarowa mdash w rzeczywistości tablica ze wskaźnikami do tablic

W rozdziale Tablice pokazaliśmy jak tworzyć tablice wielowymiarowe gdy ich rozmiar jest znanyw czasie kompilacji Teraz zaprezentujemy jak to wykonać za pomocą wskaźnikoacutew i to w sytuacji gdyrozmiar może się zmieniać Załoacuteżmy że chcemy stworzyć tabliczkę mnożenia

124 ROZDZIAŁ 17 WSKAŹNIKI

int rozmiarint iint tabliczka

printf(Podaj rozmiar tabliczki mnozenia )scanf(i amprozmiar) dla prostoty nie będziemy sprawdzali

czy użytkownik wpisał sensowną wartość

tabliczka = malloc(rozmiar sizeof tabliczka) 1 for (i = 0 iltrozmiar ++i) 2

tabliczka[i] = malloc(rozmiar sizeof tabliczka) 3 4

for (i = 0 iltrozmiar ++i) int jfor (j = 0 jltrozmiar ++j)

tabliczka[i][j] = (i+1)(j+1)

Najpierw musimy przydzielić pamięć mdash najpierw dla ldquotablicy tablicrdquo () a potem dla każdej z pod-tablic osobno (-) Ponieważ tablica jest typu int to nasza tablica tablic będzie wskaźnikiem na intczyli int Podobnie osobno ale w odwrotnej kolejności będziemy zwalniać tablicę wielowymiarową

for (i = 0 iltrozmiar ++i) free(tabliczka[i])

free(tabliczka)

Należy nie pomylić kolejności po wykonaniu free(tabliczka) nie będziemy mieli prawa odwoły-wać się do tabliczka[i] (bo wcześniej dokonaliśmy zwolnienia tego obszaru pamięci)

Można także zastosować bardziej oszczędny sposoacuteb alokowania tablicy wielowymiarowej a mia-nowicie

define ROZMIAR 10int iint tabliczka = malloc(ROZMIAR sizeof tabliczka)tabliczka = malloc(ROZMIAR ROZMIAR sizeof tabliczka)for (i = 1 iltROZMIAR ++i)

tabliczka[i] = tabliczka[0] + (i ROZMIAR)

for (i = 0 iltROZMIAR ++i) int jfor (j = 0 jltROZMIAR ++j)

tabliczka[i][j] = (i+1)(j+1)

free(tabliczka)free(tabliczka)

Powyższy kod działa w ten sposoacuteb że zamiast dla poszczegoacutelnych wierszy alokować osobno pamięćalokuje pamięć dla wszystkich elementoacutew tablicy i dopiero poacuteźniej przypisuje wskazania poszczegoacutel-nych wskaźnikoacutew-wierszy na kolejne bloki po elementoacutew

1710 WSKAŹNIKI NA FUNKCJE 125

Sposoacuteb ten jest bardziej oszczędny z dwoacutech powodoacutew Po pierwsze wykonywanych jest mniej ope-racji przydzielania pamięci (bo tylko dwie) Po drugie za każdym razem gdy alokuje się pamięć trochęmiejsca się marnuje gdyż funkcja malloc musi w stogu przechowywać roacuteżne dodatkowe informacje natemat każdej zaalokowanej przestrzeni Ponadto czasami alokacja odbywa się blokami i gdy zażąda sięniepełny blok to reszta bloku jest tracona

Zauważmy że w ten sposoacuteb możemy uzyskać nie tylko normalną ldquokwadratowąrdquo tablicę (dla dwoacutechwymiaroacutew) Możliwe jest np uzyskanie tablicy troacutejkątnej

0123012010

lub tablicy o dowolnym innym rozkładzie długości wierszy np

const size_t wymiary[] = 2 4 6 8 1 3 5 7 9 int iint tablica = malloc((sizeof wymiary sizeof wymiary) sizeof tablica)for (i = 0 ilt10 ++i)

tablica[i] = malloc(wymiary[i] sizeof tablica)

Gdy nabierzesz wprawy w używaniu wskaźnikoacutew oraz innych funkcji malloc i realloc nauczyszsię wykonywać roacuteżne inne operacje takie jak dodawanie kolejnych wierszy usuwanie wierszy zmianarozmiaru wierszy zamiana wierszy miejscami itp

1710 Wskaźniki na funkcjeDotychczas zajmowaliśmy się sytuacją gdy wskaźnik wskazywał na jakąś zmienną Jednak nie tylkozmienna ma swoacutej adres w pamięci Oproacutecz zmiennej także i funkcja musi mieć swoje określone miejscew pamięci A ponieważ funkcja ma swoacutej adres6 to nie ma przeszkoacuted aby i na nią wskazywał jakiśwskaźnik

17101 Deklaracja wskaźnika na funkcjęTak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresufunkcji Wskaźnik na funkcję roacuteżni się od innych rodzajoacutew wskaźnikoacutew Jedną z głoacutewnych roacuteżnic jestjego deklaracja Zwykle wygląda ona tak

typ_zwracanej_wartości (nazwa_wskaźnika)(typ1 parametr1 typ2 parametr2)

Oczywiście parametroacutew może być więcej (albo też w ogoacutele może ich nie być) Oto przykład wyko-rzystania wskaźnika na funkcję

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

6Tak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresu funkcji

126 ROZDZIAŁ 17 WSKAŹNIKI

int (wsk_suma)(int a int b)wsk_suma = sumaprintf(4+5=dn wsk_suma(45))return 0

Zwroacutećmy uwagę na dwie rzeczy

przypisując nazwę funkcji bez nawiasoacutew do wskaźnika automatycznie informujemy kompilatorże chodzi nam o adres funkcji

wskaźnika używamy tak jak normalnej funkcji na ktoacuterą on wskazuje

17102 Do czego można użyć wskaźnikoacutew na funkcjeJęzyk C jest językiem strukturalnym jednak dzięki wskaźnikom istnieje w nim możliwość ldquozaszczepie-niardquo pewnych obiektowych właściwości Wskaźnik na funkcję może być np elementem struktury mdashwtedy mamy bardzo prymitywną namiastkę klasy ktoacuterą dobrze znają programiści piszący w językuC++ Ponadto dzięki wskaźnikom możemy tworzyć mechanizmy działające na zasadzie funkcji zwrot-nej7 Dobrym przykładem może być np tworzenie sterownikoacutew gdzie musimy poinformować roacuteżnepodsystemy jakie funkcje w naszym kodzie służą do wykonywania określonych czynności Przykład

struct urzadzenie int (otworz)(void)void (zamknij)(void)

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

int rejestruj_urzadzenie(struct urzadzenie u) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzenieotworz = moje_urzadzenie_otworzmoje_urzadzeniezamknij = moje_urzadzenie_zamknijrejestruj_urzadzenie(ampmoje_urzadzenie)

Wten sposoacutebwpamięci każda klasamusi przechowywaćwszystkiewskaźniki dowszystkichmetodInnym rozwiązaniem może być stworzenie statycznej struktury ze wskaźnikami do funkcji i woacutewczasw strukturze będzie przechowywany jedynie wskaźnik do tej struktury np

struct urzadzenie_metody

7Funkcje zwrotne znalazły zastosowanie głoacutewnie w programowaniu

1711 MOŻLIWE DEKLARACJE WSKAŹNIKOacuteW 127

int (otworz)(void)void (zamknij)(void)

struct urzadzenie const struct urzadzenie_metody m

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

static const struct urzadzenie_metodymoje_urzadzenie_metody = moje_urzadzenie_otworzmoje_urzadzenie_zamknij

int rejestruj_urzadzenie(struct urzadzenie ampu) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzeniem = ampmoje_urzadzenie_metodyrejestruj_urzadzenie(ampmoje_urzadzenie)

1711 Możliwe deklaracje wskaźnikoacutew

Tutaj znajduje się kroacutetkie kompendium jak definiować wskaźniki oraz co oznaczają poszczegoacutelne defi-nicje

1712 Popularne błędy

Jednym z najczęstszych błędoacutew oproacutecz proacuteb wykonania operacji na wskaźniku są odwołania siędo obszaru pamięci po jego zwolnieniu Po wykonaniu funkcji free() nie możemy już wykonywaćżadnych odwołań do zwolnionego obszaru Innym rodzajem błędoacutew są

odwołania do adresoacutew pamięci ktoacutere są poza obszarem przydzielonym funkcją malloc()

brak sprawdzania czy dany wskaźnik nie ma wartości

wycieki pamięci czyli niezwalnianie całej przydzielonej wcześniej pamięci

128 ROZDZIAŁ 17 WSKAŹNIKI

i zmienna całkowita (typu int) ip wskaźnik p wskazujący na zmienną całkowitąa[] tablica a liczb całkowitych typu intf() funkcja f zwracająca liczbę całkowitą typu intpp wskaźnik pp na wskaźnik wskazujący na liczbę całkowitą typu int

(pa)[] wskaźnik pa wskazujący na tablicę liczb całkowitych typu int(pf)() wskaźnik pf na funkcję zwracającą liczbę całkowitą typu intap[] tablica ap wskaźnikoacutew na liczby całkowite typu intfp() funkcja fp ktoacutera zwraca wskaźnik na zmienną typu intppp wskaźnik ppp wskazujący na wskaźnik wskazujący na wskaźnik wskazu-

jący na liczbę typu int(ppa)[] wskaźnik ppa na wskaźnik wskazujący na tablicę liczb całkowitych typu

int(ppf)() wskaźnik ppf wskazujący na wskaźnik funkcji zwracającej dane typu int(pap)[] wskaźnik pap wskazujący na tablicę wskaźnikoacutew na typ int(pfp)() wskaźnik pfp na funkcję zwracającą wskaźnik na typ intapp[] tablica wskaźnikoacutew app wskazujących na typ int

(apa[])[] tablica wskaźnikoacutew apa wskazujących wskaźniki na typ int(apf[])() tablica wskaźnikoacutew apf na funkcję ktoacutere zwracają wskaźniki na typ intfpp() funkcja fpp ktoacutera zwraca wskaźnik na wskaźnik na wskaźnik ktoacutery wska-

zuje typ int(fpa())[] funkcja fpa ktoacutera zwraca wskaźnik na tablicę liczb typu int(fpf())() funkcja fpf ktoacutera zwraca wskaźnik na funkcję ktoacutera zwraca dane typu int

1713 Ciekawostki w rozdziale Zmienne pisaliśmy o stałych Normalnie nie mamy możliwości zmiany ich wartości

ale z użyciem wskaźnikoacutew staje się to możliwe

const int CONST=0int c=ampCONSTc = 1printf(inCONST) wypisuje 1

Konstrukcja taka może jednak wywołać ostrzeżenie kompilatora bądź nawet jego błąd mdash wtedymoże pomoacutec jawne rzutowanie z const int na int

język C++ oferuje mechanizm podobny do wskaźnikoacutew ale nieco wygodniejszy ndash referencje

język C++ dostarcza też innego sposobu dynamicznej alokacji i zwalniania pamięci mdash przez ope-ratory new i delete

w rozdziale Typy złożone znajduje się opis implementacji listy za pomocą wskaźnikoacutew Przy-kład ten może być bardzo przydatny przy zrozumieniu po co istnieją wskaźniki jak się nimiposługiwać oraz jak dobrze zarządzać pamięcią

Rozdział 18

Napisy

W dzisiejszych czasach komputer przestał być narzędziem tylko i wyłącznie do przetwarzania danychOd programoacutew komputerowych zaczęto wymagać czegoś nowego mdash program w wyniku swojego dzia-łania nie ma zwracać danych rozumianych tylko przez autora programu lecz powinien być na tylekomunikatywny aby przeciętny użytkownik komputera moacutegł bez problemu tenże komputer obsłużyćDo przechowywania tychże komunikatoacutew służą tzw ldquołańcuchyrdquo (ang string) czyli ciągi znakoacutew

Język C nie jest wygodnym narzędziem do manipulacji napisami Jak się wkroacutetce przekonamyzestaw funkcji umożliwiających operacje na napisach w bibliotece standardowej C jest raczej skromnyDodatkowo problemem jest sposoacuteb w jaki łańcuchy przechowywane są w pamięci

Napisy w języku Cmogą być przyczyną wielu trudnych do wykrycia błędoacuteww programach Wartodobrze zrozumieć jak należy operować na łańcuchach znakoacutew i zachować szczegoacutelną ostrożność w tychmiejscach gdzie napisoacutew używamy

181 Łańcuy znakoacutew w języku CNapis jest zapisywany w kodzie programu jako ciąg znakoacutew zawarty pomiędzy dwoma cudzysłowami

printf (Napis w języku C)

Wpamięci taki łańcuch jest następującympo sobie ciągiem znakoacutew (char) ktoacutery kończy się znakiemldquonullrdquo (czyli po prostu liczbą zero) zapisywanym jako rsquorsquo

Jeśli mamy napis do poszczegoacutelnych znakoacutew odwołujemy się jak w tablicy

char tekst = Jakiś tam tekstprintf(cn przykład[0]) wypisze p - znaki w napisach są numerowane od zera printf(cn tekst[2]) wypisze k

Ponieważ napis w pamięci kończy się zerem umieszczonym tuż za jego zawartością odwołanie się doznaku o indeksie roacutewnym długości napisu zwroacuteci zero

printf(d test[4]) wypisze 0

Napisy możemywczytywać z klawiatury i wypisywać na ekran przy pomocy dobrze znanych funk-cji scanf printf i pokrewnych Formatem używanym dla napisoacutew jest s

printf(s tekst)

129

130 ROZDZIAŁ 18 NAPISY

Większość funkcji działających na napisach znajduje się w pliku nagłoacutewkowym stringhJeśli łańcuch jest zbyt długi można zapisać gow kilku linijkach ale wtedy przechodząc do następnej

linii musimy na końcu postawić znak ldquordquo

printf(Ten napis zajmuje więcej niż jedną linię)

Instrukcja taka wydrukuje

Ten napis zajmuje więcej niż jedną linię

Możemy zauważyć że napis ktoacutery w programie zajął więcej niż jedną linię na ekranie zajął tylkojedną Jest tak ponieważ ldquordquo informuje kompilator że łańcuch będzie kontynuowany w następnej liniikodumdash niemawpływu na prezentację łańcucha Abywydrukować napis w kilku liniach należywstawićdo niego n (ldquonrdquo pochodzi tu od ldquonew linerdquo czyli ldquonowa liniardquo)

printf(Ten napisnna ekranienzajmie więcej niż jedną linię)

W wyniku otrzymamy

Ten napisna ekraniezajmie więcej niż jedną linię

1811 Jak komputer przeowuje w pamięci łańcu

Rysunek 181 Napis ldquoMerkkijonordquo przechowywany w pamięci

Zmienna ktoacutera przechowuje łańcuch znakoacutew jest tak naprawdę wskaźnikiem do ciągu znakoacutew(bajtoacutew) w pamięci Możemy też myśleć o napisie jako o tablicy znakoacutew (jak wyjaśnialiśmy wcześniejtablice to też wskaźniki)

Możemy wygodnie zadeklarować napis

char tekst = Jakiś tam tekst Umieszcza napis w obszarze danych programu i przypisuje adres

char tekst[] = Jakiś tam tekst Umieszcza napis w tablicy char tekst[] = Jakis tam tekst0

Tekst to taka tablica jak każda inna

Kompilator automatycznie przydziela wtedy odpowiednią ilość pamięci (tyle bajtoacutew ile jest literplus jeden dla kończącego nulla) Jeśli natomiast wiemy że dany łańcuch powinien przechowywaćokreśloną ilość znakoacutew (nawet jeśli w deklaracji tego łańcucha podajemy mniej znakoacutew) deklarujemygo w taki sam sposoacuteb jak tablicę jednowymiarową

char tekst[80] = Ten tekst musi być kroacutetszy niż 80 znakoacutew

Należy cały czas pamiętać że napis jest tak naprawdę tablicą Jeśli zarezerwowaliśmy dla napisu znakoacutew to przypisanie do niego dłuższego napisu spowoduje pisanie po pamięci

Uwaga Deklaracja char tekst = cokolwiek oraz char tekst = cokolwiek pomimo że wyglądająbardzo podobnie bardzo się od siebie roacuteżnią W przypadku pierwszej deklaracji proacuteba zmodyfikowania

181 ŁAŃCUCHY ZNAKOacuteW W JĘZYKU C 131

napisu (np tekst[0] = C) może mieć nieprzyjemne skutki Dzieje się tak dlatego że char tekst =cokolwiek deklaruje wskaźnik na stały obszar pamięci1

Pisanie po pamięci może czasami skończyć się błędem dostępu do pamięci (ldquosegmentation faultrdquow systemach ) i zamknięciem programu jednak może zdarzyć się jeszcze gorsza ewentualność mdashmożemy zmienić w ten sposoacuteb przypadkowo wartość innych zmiennych Program zacznie wtedy za-chowywać się nieprzewidywalnie mdash zmienne a nawet stałe co do ktoacuterych zakładaliśmy że ich wartośćbędzie ściśle ustalona mogą przyjąć taką wartość jaka absolutnie nie powinna mieć miejsca Wartowięc stosować zabezpieczenia typu makra assert

Kluczowy jest też kończący napis znak null W zasadzie wszystkie funkcje operujące na napisachopierają właśnie na nim Na przykład strlen szuka rozmiaru napisu idąc od początku i zliczając znaki ażnie natrafi na znak o kodzie zero Jeśli nasz napis nie kończy się znakiem null funkcja będzie szła dalejpo pamięci Na szczęście wszystkie operacje podstawienia typu tekst = ldquoTekstrdquo powodują zakończenienapisu nullem (o ile jest na niego miejsce) 2

1812 Znaki specjalneJak zapewne zauważyłeś w poprzednim przykładzie w łańcuchu ostatnim znakiem jest znak o wartościzero (rsquorsquo) Jednak łańcuchy mogą zawierać inne znaki specjalne(sekwencje sterujące) np

rsquoarsquo - alarm (sygnał akustyczny terminala)

rsquobrsquo - backspace (usuwa poprzedzający znak)

rsquorsquo - wysuniecie strony (np w drukarce)

rsquorrsquo - powroacutet kursora (karetki) do początku wiersza

rsquonrsquo - znak nowego wiersza

rsquordquo - cudzysłoacutew

rsquordquo - apostrof rsquorsquo - ukośnik wsteczny (backslash)

rsquotrsquo - tabulacja pozioma

rsquovrsquo - tabulacja pionowa

rsquorsquo - znak zapytania (pytajnik)

rsquoooorsquo - liczba zapisana w systemie oktalnym (oacutesemkowym) gdzie rsquoooorsquo należy zastąpić trzycy-frową liczbą w tym systemie

rsquoxhhrsquo - liczba zapisana w systemie heksadecymalnym (szesnastkowym) gdzie rsquohhrsquo należy za-stąpić dwucyfrową liczbą w tym systemie

rsquounnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnrsquo należy zastąpić czterocyfrowym identyfika-torem znaku w systemie szesnatkowym rsquonnnnrsquo odpowiada dłuższej formie w postaci rsquonnnnrsquo

rsquounnnnnnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnnnnnrsquo należy zastąpić ośmiocyfrowymidentyfikatorem znaku w systemie szesnatkowym

Warto zaznaczyć że znak nowej linii (rsquonrsquo) jest w roacuteżny sposoacuteb przechowywany w roacuteżnych sys-temach operacyjnych Wiąże się to z pewnymi historycznymi uwarunkowaniami W niektoacuterych sys-temach używa się do tego jednego znaku o kodzie xA (Line Feed mdash nowa linia) Do tej rodzinyzaliczamy systemy z rodziny Unix Linux BSD Mac OS X inne Drugą konwencją jest zapisywaniersquonrsquo za pomocą dwoacutech znakoacutew LF (Line Feed) + CR (Carriage return mdash powroacutet karetki) Znak CRreprezentowany jest przez wartość xD Kombinacji tych dwoacutech znakoacutew używają min CPM DOSOS Microso Windows Trzecia grupa systemoacutew używa do tego celu samego znaku CR Są to sys-temy działające na komputerach Commodore Apple II oraz Mac OS do wersji W związku z tym plikutworzony w systemie Linux może wyglądać dziwnie pod systemem Windows

1Można się zatem zastanawiać czemu kompilator dopuszcza przypisanie do zwykłego wskaźnika wskazania nastały obszar skoro kod const int foo int bar = foo generuje ostrzeżenie lub wręcz się nie kompiluje Jest topewna zaszłość historyczna wynikająca z faktu że słoacutewko const zostało wprowadzone do języka gdy już był on wpowszechnym użyciu

2Nie należy mylić znaku null (czyli znaku o kodzie zero) ze wskaźnikiem null (czy też )

132 ROZDZIAŁ 18 NAPISY

182 Operacje na łańcua

1821 Poroacutewnywanie łańcuoacutewNapisy to tak naprawdęwskaźniki Tak więc używając zwykłego operatora poroacutewnania == otrzymamywynik poroacutewnania adresoacutew a nie tekstoacutew

Do poroacutewnywania dwoacutech ciągoacutew znakoacutew należy użyć funkcji strcmp zadeklarowanej w pliku na-głoacutewkowym stringh Jako argument przyjmuje ona dwa napisy i zwraca wartość ujemną jeżeli napispierwszy jestmniejszy od drugiego jeżeli napisy są roacutewne lub wartość dodatnią jeżeli napis pierwszyjest większy od drugiego Ciągi znakoacutew poroacutewnywalne są leksykalnie kody znakoacutew czyli np (przyj-mując kodowanie ASCII) a jest mniejsze od b ale jest większe od B Np

include ltstdiohgtinclude ltstringhgt

int main(void) char str1[100] str2[100]int cmp

puts(Podaj dwa ciagi znakow )fgets(str1 sizeof str1 stdin)fgets(str2 sizeof str2 stdin)

cmp = strcmp(str1 str2)if (cmplt0)

puts(Pierwszy napis jest mniejszy) else if (cmpgt0)

puts(Pierwszy napis jest wiekszy) else

puts(Napisy sa takie same)

return 0

Czasami możemy chcieć poroacutewnać tylko fragment napisu np sprawdzić czy zaczyna się od jakie-goś ciągu W takich sytuacjach pomocna jest funkcja strncmp W poroacutewnaniu do strcmp() przyjmujeona jeszcze jeden argument oznaczający maksymalną liczbę znakoacutew do poroacutewnania

include ltstdiohgtinclude ltstringhgt

int main(void) char str[100]int cmp

fputs(Podaj ciag znakow stdout)fgets(str sizeof str stdin)

if (strncmp(str foo 3)) puts(Podany ciag zaczyna sie od foo)

return 0

182 OPERACJE NA ŁAŃCUCHACH 133

1822 Kopiowanie napisoacutewDo kopiowania ciągoacutew znakoacutew służy funkcja strcpy ktoacutera kopiuje drugi napis w miejsce pierwszegoMusimy pamiętać by w pierwszym łańcuchu było wystarczająco dużo miejsca

char napis[100]strcpy(napis Ala ma kota)

Znacznie bezpieczniej jest używać funkcji strncpy ktoacutera kopiuje co najwyżej tyle bajtoacutew ile podanojako trzeci parametr Uwaga Jeżeli drugi napis jest za długi funkcja nie kopiuje znaku null na koniecpierwszego napisu dlatego zawsze trzeba to robić ręcznie

char napis[100]strncpy(napis Ala ma kota sizeof napis - 1)napis[sizeof napis - 1] = 0

1823 Łączenie napisoacutewDo łączenia napisoacutew służy funkcja strcat ktoacutera kopiuje drugi napis do pierwszego Ponownie jak wprzypadku strcpymusimy zagwarantować by w pierwszym łańcuchu było wystarczająco dużo miejsca

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrcat(napis1 napis2)puts(napis1)return 0

I ponownie jak w przypadku strcpy istnieje funkcja strncat ktoacutera skopiuje co najwyżej tyle bajtoacutewile podano jako trzeci argument i dodatkowo dopisze znak null Przykładowo powyższy kod bezpieczniejzapisać jako

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrncat(napis1 napis2 sizeof napis1 - 1)puts(napis1)return 0

Osoby ktoacutere programowały w językach skryptowych muszą bardzo uważać na łączenie i kopiowa-nie napisoacutew Kompilator języka C nie wykryje nadpisania pamięci za zmienną łańcuchową i nie przy-dzieli dodatkowego obszaru pamięci Może się zdarzyć że program pomimo nadpisywania pamięci załańcuchem będzie nadal działał co bardzo utrudni wykrywanie tego typu błędoacutew

134 ROZDZIAŁ 18 NAPISY

183 Bezpieczeństwo kodu a łańcuy

1831 Przepełnienie buforaO co właściwie chodzi z tymi funkcjami strncpy i strncat Otoacuteż niewinnie wyglądające łańcuchy mogąokazać się zaboacutejcze dla bezpieczeństwa programu a przez to nawet dla systemu w ktoacuterym ten programdziała Może brzmi to strasznie lecz jest to prawda Może pojawić się tutaj pytanie ldquow jaki sposoacutebłańcuch może zaszkodzić programowirdquo Otoacuteż może i to całkiem łatwo Przeanalizujmy następującykod

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strcpy(haslo argv[1]) tutaj następuje przepełnienie bufora if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Jest to bardzo prosty program ktoacutery wykonuje jakąś akcję jeżeli podane jako pierwszy argumenthasło jest poprawne Sprawdźmy czy działa

$ aout niepoprawnePodales bledne haslo$ aout poprawneWitaj wprowadziles poprawne haslo

Jednak okazuje się że z powodu użycia funkcji strcpy włamywacz nie musi znać hasła aby programuznał że zna hasło np

$ aout 11111111111111111111111111111111Witaj wprowadziles poprawne haslo

Co się stało Podaliśmy ciąg jedynek dłuższy niż miejsce przewidziane na hasło Funkcja strcpy()kopiując znaki z argv[1] do tablicy (bufora) haslo przekroczyła przewidziane dla niego miejsce i szładalej mdash gdzie znajdowała się zmienna haslo poprawne strcpy() kopiowała znaki już tam gdzie znajdo-wały się inne dane mdash między innymi wpisała jedynkę do haslo poprawne

Podany przykład może się roacuteżnie zachowywać w zależności od kompilatora jakim został skompi-lowany i systemu na jakim działa ale ogoacutelnie mamy do czynienia z poważnym niebezpieczeństwem

183 BEZPIECZEŃSTWO KODU A ŁAŃCUCHY 135

Taką sytuację nazywamy przepełnieniem bufora Może umożliwić dostęp do komputera osobomnieuprzywilejowanym Należy wystrzegać się tego typu konstrukcji a wmiejsce niebezpiecznej funkcjistrcpy stosować bardziej bezpieczną strncpy

Oto bezpieczna wersja poprzedniego programu

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strncpy(haslo argv[1] sizeof haslo - 1)haslo[sizeof haslo - 1] = 0if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Bezpiecznymi alternatywami do strcpy i strcat są też funkcje strlcpy oraz strlcat opracowane przezprojekt OpenBSD i dostępne do ściągnięcia na wolnej licencji strlcpy strlcat strlcpy() działa podobniedo strncpy strlcpy (buf argv[1] sizeof buf) jednak jest szybsza (nie wypełnia pustego miejscazerami) i zawsze kończy napis nullem (czego nie gwarantuje strncpy) strlcat(dst src size) działanatomiast jak strncat(dst src size-1)

Do innych niebezpiecznych funkcji należy np gets zamiast ktoacuterej należy używać fgetsZawsze możemy też alokować napisy dynamicznie

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

136 ROZDZIAŁ 18 NAPISY

haslo = malloc(strlen(argv[1]) + 1) +1 dla znaku null if (haslo)

fputs(Za malo pamiecin stderr)return EXIT_FAILURE

strcpy(haslo argv[1])if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)free(haslo)return EXIT_SUCCESS

1832 Nadużycia z udziałem ciągoacutew formatującyJednak to nie koniec kłopotoacutew z napisami Wielu programistoacutew nieświadomych zagrożenia częstoużywa tego typu konstrukcji

include ltstdiohgtint main (int argc char argv[])

printf (argv[1])

Z punktu widzenia bezpieczeństwa jest to bardzo poważny błąd programu ktoacutery może nieść zesobą katastrofalne skutki Prawidłowo napisany kod powinien wyglądać następująco

include ltstdiohgtint main (int argc char argv[])

printf (s argv[1])

lub

include ltstdiohgtint main (int argc char argv[])

fputs (argv[1] stdout)

Źroacutedło problemu leży w konstrukcji funkcji printf Przyjmuje ona bowiem za pierwszy parametrłańcuch ktoacutery następnie przetwarza Jeśli w pierwszym parametrze wstawimy jakąś zmienną to funk-cja printf potraktuje ją jako ciąg znakoacutew razem ze znakami formatującymi Zatem ważne aby wcześniewyrobić sobie nawyk stosowania funkcji printf z co najmniej dwoma parametrami nawet w przypadkuwyświetlenia samego tekstu

184 KONWERSJE 137

184 KonwersjeCzasami zdarza się że łańcuch można interpretować nie tylko jako ciąg znakoacutew lecz np jako liczbęJednak aby dało się taką liczbę przetworzyć musimy skopiować ją do pewnej zmiennej Aby ułatwićprogramistom tego typu zamiany powstał zestaw funkcji bibliotecznych Należą do nich

atol strtol mdash zamienia łańcuch na liczbę całkowitą typu long

atoi mdash zamienia łańcuch na liczbę całkowitą typu int

atoll strtoll mdash zamienia łańcuch na liczbę całkowitą typu long long ( bity) dodatkowo istniejeprzestarzała funkcja atoq będąca rozszerzeniem

atof strtod mdash przekształca łańcuch na liczbę typu double

Ogoacutelnie rzecz ujmując funkcje z serii ato nie pozwalają na wykrycie błędoacutew przy konwersji i dla-tego gdy jest to potrzebne należy stosować funkcje strto

Czasami przydaje się też konwersja w drugą stronę tzn z liczby na łańcuch Do tego celu możeposłużyć funkcja sprintf lub snprintf sprintf jest bardzo podobna do printf tyle że wyniki jej praczwracane są do pewnego łańcucha a nie wyświetlane np na ekranie monitora Należy jednak uwa-żać przy jej użyciu (patrz mdash Bezpieczeństwo kodu a łańcuchy) snprintf (zdefiniowana w nowszymstandardzie) dodatkowo przyjmuje jako argument wielkość bufora docelowego

185 Operacje na znakaWarto też powiedzieć w tymmiejscu o operacjach na samych znakach Spoacutejrzmy na poniższy program

include ltstdiohgtinclude ltctypehgtinclude ltstringhgt

int main()

int znakwhile ((znak = getchar())=EOF)

if( islower(znak) ) znak = toupper(znak)

else if( isupper](znak) ) znak = tolower(znak)

putchar(znak)

return 0

Program ten zmieniawewczytywanym tekściewielkie litery namałe i odwrotnie Wykorzystujemyfunkcje operujące na znakach z pliku nagłoacutewkowego ctypeh isupper sprawdza czy znak jest wielkąliterą natomiast toupper zmienia znak (o ile jest literą) na wielką literę Analogicznie jest dla funkcjiislower i tolower

Jako ćwiczenie możesz tak zmodyfikować program żeby odczytywał dane z pliku podanego jakoargument lub wprowadzonego z klawiatury

186 Częste błędy pisanie do niezaalokowanego miejsca

138 ROZDZIAŁ 18 NAPISY

char tekstscanf(s tekst)

zapominanie o kończącym napis nullu

char test[4] = test nie zmieścił się null kończący napis

nieprawidłowe poroacutewnywanie łańcuchoacutew

char tekst1[] = jakis tekstchar tekst2[] = jakis tekstif( tekst1 == tekst2 ) tu zawsze będzie fałsz bo == poroacutewnuje adresy należy użyć strcmp()

187 UnicodeZobacz w Wikipedii Uni-code W dzisiejszych czasach brak obsługi wielu językoacutew praktycznie marginalizowałoby język Dlatego też

C wprowadza możliwość zapisu znakoacutew wg norm Unicode

1871 Jaki typDo przechowywania znakoacutew zakodowanych w Unicode powinno się korzystać z typu war t Jegodomyślny rozmiar jest zależny od użytego kompilatora lecz w większości zaktualizowanych kompila-toroacutew powinny to być bajty Typ ten jest częścią języka C++ natomiast w C znajduje się w plikunagłoacutewkowym stddefh

Alternatywą jest wykorzystanie gotowych bibliotek dla Unicode (większość jest dostępnych jedyniedla C++ nie wspoacutełpracuje z C) ktoacutere często mają zdefiniowane własne typy jednak zmuszeni jesteśmywtedy do przejścia ze znanych nam już funkcji jak np strcpy strcmp na funkcje dostarczane przezbibliotekę co jest dość niewygodne My zajmiemy się pierwszym wyjściem

1872 Jaki rozmiar i jakie kodowanieUnicode określa jedynie jakiej liczbie odpowiada jaki znak nie moacutewi zaś nic o sposobie dekodowania(tzn jaka sekwencja znakoacutew odpowiada jakiemu znakuznakom) Jako że Unicode obejmuje tysznakoacutew zmienna zdolna pomieścić go w całości musi mieć przynajmniej bajty Niestety procesory niefunkcjonują na zmiennych o tym rozmiarze pracują jedynie na zmiennych o wielkościach oraz bajtoacutew (kolejne potęgi liczby ) Dlatego też jeśli wciąż uparcie chcemy być dokładni i zastosowaćprzejrzyste kodowanie musimy skorzystać ze zmiennej -bajtowej ( bity) Tak do sprawy podeszlitwoacutercy kodowania Unicode nazwanego -UCS- Ten typ kodowania po prostu przydziela każ-Zobacz w Wikipedii -32demu znakowi Unicode kolejne liczby Jest to najbardziej intuicyjny i wygodny typ kodowania ale jakwidać ciągi znakoacutew zakodowane w nim są bardzo obszerne co zajmuje dostępną pamięć spowalniadziałanie programu oraz drastycznie pogarsza wydajność podczas transferu przez sieć Poza -istnieje jeszcze wiele innych kodowań Najpopularniejsze z nich to

- mdash od do bajtoacutew (dla znakoacutew poniżej do bajtoacutew) na znak przez co jest skraj-nie niewygodny gdy chcemy przeprowadzać jakiekolwiek operacje na tekście bez korzystania zgotowych funkcji

- mdash lub bajty na znak ręczne modyfikacje łańcucha są bardziej skomplikowane niż przy-

UCS- mdash bajty na znak przez co znaki z numerami powyżej nie są uwzględnione roacutewniewygodny w użytkowaniu co -

187 UNICODE 139

Ręczne operacje na ciągach zakodowanych w - i - są utrudnione ponieważ w przeci-wieństwie do - gdzie można określić iż powiedzmy znak ciągu zajmuje bajty od do (gdyżz goacutery wiemy że znak zajął bajty od do ) w tych kodowaniach musimy najpierw określić rozmiar znaku Ponadto gdy korzystamy z nich nie działają wtedy funkcje udostępniane przez biblioteki Cdo operowania na ciągach znakoacutew

Priorytet Proponowane kodowaniamały rozmiar -8

łatwa i wydajna edycja -32 lub -2przenośność -83

ogoacutelna szybkość -2 lub -8

Co należy zrobić by zacząć korzystać z kodowania - (domyślne kodowanie dla C)

powinniśmy korzystać z typu wchar t (ang ldquowide characterrdquo) jednak jeśli chcemy udostępniaćkod źroacutedłowy programu do kompilacji na innych platformach powinniśmy ustawić odpowiednieparametry dla kompilatoroacutew by rozmiar był identyczny niezależnie od platformy

korzystamy z odpowiednikoacutew funkcji operujących na typie char pracujących na wchar t (z re-guły składnia jest identyczna z tą roacuteżnicą że w nazwach funkcji zastępujemy ldquostrrdquo na ldquowcsrdquo npstrcpy mdash wcscpy strcmp mdash wcscmp)

jeśli przyzwyczajeni jesteśmy do korzystania z klasy string powinniśmy zamiast niej korzystaćz wstring ktoacutera posiada zbliżoną składnię ale pracuje na typie wchar t

Co należy zrobić by zacząć korzystać z Unicode

gdy korzystamy z kodowań innych niż - i - powinniśmy zdefiniować własny typ

w wykorzystywanych przez nas bibliotekach podajemy typ wykorzystanego kodowania

gdy chcemy ręcznie modyfikować ciąg musimy przeczytać specyfikację danego kodowania sąone wyczerpująco opisane na siostrzanym projekcie Wikibooks mdash Wikipedii

Przykład użycia kodowania -

include ltstddefhgt jeśli używamy C++ możemy opuścić tę linijkę include ltstdiohgtinclude ltstringhgt

int main() wchar_t wcs1 = LAla ma kotawchar_t wcs2 = LKot ma Alewchar_t calosc[25]

wcscpy(calosc wcs1)(calosc + wcslen(wcs1)) = L wcscpy(calosc + wcslen(wcs1) + 1 wcs2)

printf(lancuch wyjsciowy lsn calosc)return 0

140 ROZDZIAŁ 18 NAPISY

Rozdział 19

Typy złożone

191 typedefJest to słowo kluczowe ktoacutere służy do definiowania typoacutew pochodnych np

typedef stara_nazwa nowa_nazwatypedef int mojInttypedef int WskNaInt

od tej pory mozna używać typoacutew mojInt i WskNaInt

192 Typ wyliczeniowySłuży do tworzenia zmiennych ktoacutere powinny przechowywać tylko pewne z goacutery ustalone wartości

enum Nazwa WARTOSC_1 WARTOSC_2 WARTOSC_N

Na przykład można w ten sposoacuteb stworzyć zmienną przechowującą kierunek

enum Kierunek W_GORE W_DOL W_LEWO W_PRAWO

enum Kierunek kierunek = W_GORE

ktoacuterą można na przykład wykorzystać w instrukcji switch

switch(kierunek)

case W_GOREprintf(w goacuteręn)break

case W_DOLprintf(w doacutełn)break

defaultprintf(gdzieś w bokn)

Tradycyjnie przechowywane wielkości zapisuje się wielkimi literami (W GORE W DOL)Tak naprawdę C przechowuje wartości typu wyliczeniowego jako liczby całkowite o czym można

się łatwo przekonać

141

142 ROZDZIAŁ 19 TYPY ZŁOŻONE

kierunek = W_DOLprintf(in kierunek) wypisze 1

Kolejne wartości to po prostu liczby naturalne domyślnie pierwsza to zero druga jeden itp Mo-żemy przy deklarowaniu typu wyliczeniowego zmienić domyślne przyporządkowanie

enum Kierunek W_GORE W_DOL = 8 W_LEWO W_PRAWO printf(i in W_DOL W_LEWO) wypisze 8 9

Co więcej liczby mogą się powtarzać i wcale nie muszą być ustawione w kolejności rosnącej

enum Kierunek W_GORE = 5 W_DOL = 5 W_LEWO = 2 W_PRAWO = 1 printf(i in W_DOL W_LEWO) wypisze 5 2

Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajną ich obsługęale stwarza niebezpieczeństwa mdash można przypisywać pod typ wyliczeniowy liczby nawet nie mająceodpowiednika w wartościach a kompilator może o tym nawet nie ostrzec

kierunek = 40

193 StrukturyStruktury to specjalny typ danych mogący przechowywać wiele wartości w jednej zmiennej Od tablicjednakże roacuteżni się tym iż te wartości mogą być roacuteżnych typoacutew

Struktury definiuje się w następujący sposoacuteb

struct Struktura int pole1int pole2char pole3

gdzie ldquoStrukturardquo to nazwa tworzonej strukturyNazewnictwo ilość i typ poacutel definiuje programista według własnego uznaniaZmienną posiadającą strukturę tworzy się podając jako jej typ nazwę struktury

struct Struktura zmienna

Dostęp do poszczegoacutelnych poacutel uzyskuje się przy pomocy operatora wyboru składnika kropki (rsquorsquo)

zmiennaSpole1 = 60 przypisanie liczb do poacutel zmiennaSpole2 = 2zmiennaSpole3 = a a teraz znaku

194 UnieUnie to kolejny sposoacuteb prezentacji danychw pamięci Na pierwszy rzut oka wyglądają bardzo podobniedo struktur

union Nazwa typ1 nazwa1typ2 nazwa2

Na przykład

194 UNIE 143

union LiczbaLubZnak int calkowitachar znakdouble rzeczywista

Pola w unii nakładają się na siebie w ten sposoacuteb że w danej chwili można w niej przechowywaćwartość tylko jednego typu Unia zajmuje w pamięci tyle miejsca ile zajmuje największa z jej składo-wych W powyższym przypadku unia będzie miała prawdopodobnie rozmiar typu double czyli często bity a całkowita i znak będą wskazywały odpowiednio na pierwsze cztery bajty lub na pierwszy bajtunii (choć nie musi tak być zawsze) Dlaczego tak Taka forma często przydaje się np do konwersji po-między roacuteżnymi typami danych Możemy dzięki unii podzielić zmienną -bitową na cztery składowezmienne o długości bitoacutew każda

Do konkretnych wartości poacutel unii odwołujemy się podobnie jak w przypadku struktur za pomocąkropki

union LiczbaLubZnak liczbaliczbacalkowita = 10printf(dn liczbacalkowita)

Zazwyczaj użycie unii ma na celu zmniejszenie zapotrzebowania na pamięć gdy naraz będzie wy-korzystywane tylko jedno pole i jest często łączone z użyciem struktur

Przyjrzyjmy się teraz przykładowi ktoacutery powinien dobitnie zademonstrować działanie unii

include ltstdiohgt

struct adres_bajtowy __uint8_t a__uint8_t b__uint8_t c__uint8_t d

union adres __uint32_t ipstruct adres_bajtowy badres

int main ()

union adres addraddrbadresa = 192addrbadresb = 168addrbadresc = 1addrbadresd = 1printf (Adres IP w postaci 32-bitowej zmiennej 08xnaddrip)return 0

Zauważyłeś pewien ciekawy efekt Jeśli uruchomiłeś ten program na typowym komputerze domo-wym (rodzina i) na ekranie zapewne pojawił Ci się taki oto napis

Adres IP w postaci 32-bitowej zmiennej 0101a8c0

Dlaczego jedynki są na początku zmiennej skoro w programie były to dwa ostatnie bajty (pola c id struktury) Jest to problem kolejności bajtoacutew Aby dowiedzieć się o nim więcej przeczytaj rozdział

144 ROZDZIAŁ 19 TYPY ZŁOŻONE

przenośność programoacutew Zauważyłeś zatem że za pomocą tego programu w prosty sposoacuteb zamienili-śmy cztery zmienne jednobajtowe w jedną czterobajtową Jest to tylko jedno z możliwych zastosowańunii

195 Inicjalizacja struktur i uniiJeśli tworzymy nową strukturę lub unię możemy zaraz po jej deklaracji wypełnić ją określonymi da-nymi Rozważmy tutaj przykład

struct moja_struct int achar b moja = 1c

Wzasadzie taka deklaracja nie roacuteżni się niczym odwypełnienia np tablicy danymi Jednak standardC wprowadza pewne udogodnienie zaroacutewno przy deklaracji struktur jak i unii Polega ono na tymże w nawiasie klamrowym możemy podać nazwy poacutel struktury lub unii ktoacuterym przypisujemy wartośćnp

struct moja_struct int achar b moja = b = c pozostawiamy pole a niewypełnione żadną konkretną wartością

196 Wspoacutelnewłasności typoacutewwyliczeniowy unii i struk-tur

Warto w zwroacutecić uwagę że język C++ przy deklaracji zmiennych typoacutew wyliczeniowych unii lubstruktur nie wymaga przed nazwą typu odpowiedniego słowa kluczowego Na przykład poniższy kodjest poprawnym programem C++

enum Enum A B C union Union int a float b struct Struct int a float b int main()

Enum eUnion uStruct se = Aua = 0sa = 0return e + ua + sa

Nie jest to jednak poprawny kod C i należy o tym pamiętać szczegoacutelnie jeżeli uczysz się języka Ckorzystając z kompilatora C++

Należy roacutewnież pamiętać że po klamrze zamykającej definicje musi następować średnik Brak tegośrednika jest częstym błędem powodującym czasami niezrozumiałe komunikaty błędoacutew Jedynym wy-jątkiem jest natychmiastowa definicja zmiennych danego typu na przykład

struct Struktura int pole

s1 s2 s3

196 WSPOacuteLNE WŁASNOŚCI TYPOacuteW WYLICZENIOWYCH UNII I STRUKTUR 145

Definicja typoacutew wyliczeniowych unii i struktur jest lokalna do bloku To znaczy możemy zdefi-niować strukturę wewnątrz jednej z funkcji (czy wręcz wewnątrz jakiegoś bloku funkcji) i tylko tambędzie można używać tego typu

Częstym idiomem w C jest użycie typedef od razu z definicją typu by uniknąć pisania enum unionczy struct przy deklaracji zmiennych danego typu

typedef struct struktura int pole

StrukturaStruktura s1struct struktura s2

W tym przypadku zmienne s i s są tego samego typu Możemy też zrezygnować z nazywaniasamej struktury

typedef struct int pole

StrukturaStruktura s1

1961 Wskaźnik na unię i strukturęPodobnie jak na każdą inną zmienna wskaźnik może wskazywać także na unię lub strukturę Otoprzykład

typedef struct int p1 p2

Struktura

int main ()

Struktura s = 0 0 Struktura wsk = ampswsk-gtp1 = 2wsk-gtp2 = 3return 0

Zapis wsk-gtp1 jest (z definicji) roacutewnoważny (wsk)p1 ale bardziej przejrzysty i powszechnie sto-sowany Wyrażenie wskp1 spowoduje błąd kompilacji (strukturą jest wsk a nie wsk)

1962 Zobacz też Powszechne praktyki mdash konstruktory i destruktory

1963 Pola bitoweStruktury mają pewne dodatkowe możliwości w stosunku do zmiennych Mowa tutaj o rozmiarzeelementu struktury W przeciwieństwie do zmiennej może on mieć nawet bit Aby moacutec zdefiniowaćtaką zmienną musimy użyć tzw pola bitowego Wygląda ono tak

struct moja unsigned int a14 4 bity

a28 8 bitoacutew (często 1 bajt) a31 1 bit a43 3 bity

146 ROZDZIAŁ 19 TYPY ZŁOŻONE

Wszystkie pola tej struktury mają w sumie rozmiar bitoacutew jednak możemy odwoływać się donichw taki sam sposoacuteb jak do innych elementoacutew struktury W ten sposoacuteb efektywniej wykorzystujemypamięć jednak istnieją pewne zjawiska ktoacuterych musimy być świadomi przy stosowaniu poacutel bitowychWięcej na ten temat w rozdziale przenośność programoacutew

Pola bitowe znalazły zastosowanie głoacutewnie w implementacjach protokołoacutew sieciowych

197 Studium przypadku mdash implementacja listy wskaźniko-wej

Zobacz w Wikipedii ListaRozważmy teraz coś co każdy z nas może spotkać w codziennym życiu Każdy z nas widział kiedyśjakiś przykład listy (czy to zakupoacutew czy też listę wierzycieli) Język C też oferuje listy jednak w progra-mowaniu listy będą służyły do czegoś innego Wyobraźmy sobie sytuację w ktoacuterej jesteśmy autoramigenialnego programu ktoacutery znajduje kolejne liczby pierwsze Oczywiście każdą kolejną liczbę pierw-szą może wyświetlać na ekran jednak z matematyki wiemy że dana liczba jest liczbą pierwszą jeśli niedzieli się przez żadną liczbę pierwszą ją poprzedzającą mniejszą od pierwiastka z badanej liczby Uffmniej więcej chodzi o to że moglibyśmy wykorzystać znalezione wcześniej liczby do przyspieszeniadziałania naszego programu Jednak nasze liczby trzeba jakoś mądrze przechować w pamięci Tablicemają ograniczenie mdash musimy z goacutery znać ich rozmiar Jeśli zapełnilibyśmy tablicę to przy znalezieniukażdej kolejnej liczby musielibyśmy

przydzielać nowy obszar pamięci o rozmiarze poprzedniego rozmiaru + rozmiar zmiennej prze-chowującej nowo znalezioną liczbę

kopiować zawartość starego obszaru do nowego

zwalniać stary nieużywany obszar pamięci

w ostatnim elemencie nowej tablicy zapisać znalezioną liczbę

Coacuteż trochę tutaj roboty jest a kopiowanie całej zawartości jednego obszaru w drugi jest czaso-chłonne W takim przypadku możemy użyć listy Tworząc listę możemy w prosty sposoacuteb przechowaćnowo znalezione liczby Przy użyciu listy nasze postępowanie ograniczy się do

przydzielenia obszaru pamięci aby przechować wartość obliczeń

dodać do listy nowy element

Prawda że proste Dodatkowo lista zajmuje w pamięci tylko tyle pamięci ile potrzeba na aktualnąliczbę elementoacutew Pusta tablica zajmuje natomiast tyle samo miejsca co pełna tablica

1971 Implementacja listyW języku C aby stworzyć listę musimy użyć struktur Dlaczego Ponieważ musimy przechować conajmniej dwie wartości

pewną zmienną (np liczbę pierwszą z przykładu)

wskaźnik na kolejny element listy

Przyjmijmy że szukając liczb pierwszych nie przekroczymy możliwości typu unsigned long

typedef struct element struct element next wskaźnik na kolejny element listy unsigned long val przechowywana wartość

el_listy

Zacznijmy zatem pisać nasz eksperymentalny program do wyszukiwania liczb pierwszych Pierw-szą liczbą pierwszą jest liczba Pierwszym elementem naszej listy będzie zatem struktura ktoacutera będzieprzechowywała liczbę Na co będzie wskazywało pole next Ponieważ na początku działania pro-gramu będziemy mieć tylko jeden element listy pole next powinno wskazywać na Umoacutewmy sięzatem że pole next ostatniego elementu listy będzie wskazywało mdash po tym poznamy że lista sięskończyła

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 147

include ltstdiohgtinclude ltstdlibhgttypedef struct element

struct element nextunsigned long val

el_listy

el_listy first pierwszy element listy

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (ilt=END++i) tutaj powinien znajdować się kod ktoacutery sprawdza podzielność sprawdzanej liczby przezpoprzednio znalezione liczby pierwsze oraz dodaje liczbę do listy w przypadku stwierdzenia

że jest ona liczbą pierwszą

wypisz_liste(first)return 0

Na początek zajmiemy się wypisywaniem listy W tym celu będziemy musieli ldquoodwiedzićrdquo każdyelement listy Elementy listy są połączone polem next aby przeglądnąć listę użyjemy następującegoalgorytmu

Ustaw wskaźnik roboczy na pierwszym elemencie listy

Jeśli wskaźnik ma wartość przerwij

Wypisz element wskazywany przez wskaźnik

Przesuń wskaźnik na element ktoacutery jest wskazywany przez pole next

Wroacuteć do punktu

void wypisz_liste(el_listy lista)

el_listy wsk=lista 1 while( wsk = NULL ) 2

printf (lun wsk-gtval) 3 wsk = wsk-gtnext 4 5

Zastanoacutewmy się teraz jak powinien wyglądać kod ktoacutery dodaje do listy następny element Takafunkcja powinna

znaleźć ostatni element (tj element ktoacuterego pole next == )

przydzielić odpowiedni obszar pamięci

skopiować w pole val w nowo przydzielonym obszarze znalezioną liczbę pierwszą

nadać polu next ostatniego elementu listy wartość

w pole next ostatniego elementu listy wpisać adres nowo przydzielonego obszaru

148 ROZDZIAŁ 19 TYPY ZŁOŻONE

Napiszmy zatem odpowiednią funkcję

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL) 1

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy)) 2 nowy-gtval = liczba 3 nowy-gtnext = NULL 4 wsk-gtnext = nowy 5

Ihellip to już właściwie koniec naszej funkcji (warto zwroacutecić uwagę że funkcja w tej wersji zakłada żena liście jest już przynajmniej jeden element) Wstaw ją do kodu przed funkcją main Został namjeszcze jeden problem w pętli for musimy dodać kod ktoacutery odpowiednio będzie ldquobadałrdquo liczby oraz wprzypadku stwierdzenia pierwszeństwa liczby będzie dodawał ją do listy Ten kod powinien wyglądaćmniej więcej tak

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczba wsk-gtval)==0) return 0 jeśli reszta z dzielenialiczby przez ktoacuterąkolwiek z poprzednio znalezionychliczb pierwszych jest roacutewna zero to znaczy że liczba tanie jest liczbą pierwszą

wsk = wsk-gtnext

natomiast jeśli sprawdzimy wszystkie poprzednio znalezione liczbyi żadna z nich nie będzie dzieliła liczby imożemy liczbę i dodać do listy liczb pierwszych

return 1for (ilt=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (firsti)

Podsumujmy teraz efekty naszej pracy Oto cały kod naszego programu

include ltstdiohgtinclude ltstdlibhgt

typedef struct element struct element nextunsigned long val

el_listy

el_listy first

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 149

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL)

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy))nowy-gtval = liczbanowy-gtnext = NULLwsk-gtnext = nowy podczepiamy nowy element do ostatniego z listy

void wypisz_liste(el_listy lista)

el_listy wsk=listawhile( wsk = NULL )

printf (lun wsk-gtval)wsk = wsk-gtnext

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczbawsk-gtval)==0) return 0wsk = wsk-gtnext

return 1

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (i=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (first i)

wypisz_liste(first)return 0

Możemy jeszcze pomyśleć jak można by wykonać usuwanie elementu z listy Najprościej byłobyzrobić

wsk-gtnext = wsk-gtnext-gtnext

150 ROZDZIAŁ 19 TYPY ZŁOŻONE

ale wtedy element na ktoacutery wskazywał wcześniej wsk-gtnext przestaje być dostępny i zaśmieca pa-mięć Trzeba go usunąć Zauważmy że aby usunąć element potrzebujemy wskaźnika do elementu gopoprzedzającego (po to by nie rozerwać listy) Popatrzmy na poniższą funkcję

void usun_z_listy(el_listy lista int element)

el_listy wsk=listawhile (wsk-gtnext = NULL)

if (wsk-gtnext-gtval == element) musimy mieć wskaźnik do elementu poprzedzającego el_listy usuwany=wsk-gtnext zapamiętujemy usuwany element wsk-gtnext = usuwany-gtnext przestawiamy wskaźnik next by omijał usuwany element free(usuwany) usuwamy z pamięci else

wsk = wsk-gtnext idziemy dalej tylko wtedy kiedy nie usuwaliśmy bo nie chcemy zostawić duplikatoacutew

Funkcja ta jest tak napisana by usuwała z listy wszystkie wystąpienia danego elementu (w naszymprogramie nie ma to miejsca ale lista jest zrobiona tak że może trzymać dowolne liczby) Zauważmyże wskaźnik wsk jest przesuwany tylko wtedy gdy nie kasowaliśmy Gdybyśmy zawsze go przesuwaliprzegapilibyśmy element gdyby występował kilka razy pod rząd

Funkcja ta działa poprawnie tylko wtedy gdy nie chcemy usuwać pierwszego elementu Można topoprawić mdash dodając instrukcję warunkową do funkcji lub dodając do listy ldquogłowęrdquo mdash pierwszy elementnie przechowujący niczego ale upraszczający operacje na liście Zostawiamy to do samodzielnej pracy

Cały powyższy przykład omawiał tylko jeden przypadek listy mdash listę jednokierunkową Jednakistnieją jeszcze inne typy list np lista jednokierunkowa cykliczna lista dwukierunkowa oraz dwukie-runkowa cykliczna Roacuteżnią się one od siebie tylko tym że

w przypadku list dwukierunkowych mdashw strukturze el listy znajduje się jeszcze pole ktoacutere wska-zuje na element poprzedni

w przypadku list cyklicznych mdash ostatni element wskazuje na pierwszy (nie rozroacuteżnia się wtedyelementu pierwszego ani ostatniego)

Rozdział 20

Biblioteki

201 Czym jest bibliotekaBiblioteka jest to zbioacuter funkcji ktoacutere zostały wydzielone po to aby dało się z nich korzystać wwielu pro-gramach Ułatwia to programowanie mdash nie musimy np sami tworzyć funkcji printf Każda bibliotekaposiada swoje pliki nagłoacutewkowe ktoacutere zawierają deklaracje funkcji bibliotecznych oraz często zawartesą w nich komentarze jak używać danej funkcji W tej części podręcznika nauczymy się tworzyć naszewłasne biblioteki

202 Jak zbudowana jest bibliotekaKażda biblioteka składa się z co najmniej dwoacutech części

pliku nagłoacutewkowego z deklaracjami funkcji (plik z rozszerzeniem h)

pliku źroacutedłowego zawierającego ciała funkcji (plik z rozszerzeniem c)

2021 Budowa pliku nagłoacutewkowegoOto najprostszy możliwy plik nagłoacutewkowy

ifndef PLIK_Hdefine PLIK_H tutaj są wpisane deklaracje funkcji endif PLIK_H

Zapewne zapytasz się na co komu instrukcje ifndef define oraz endif Otoacuteż często się zdarzaże w programie korzystamy z plikoacutew nagłoacutewkowych ktoacutere dołączają się wzajemnie Oznaczałoby to żew kodzie programu kilka razy pojawiła by się zawartość tego samego pliku nagłoacutewkowego Instrukcjaifndef i define temu zapobiega Dzięki temu kompilator nie musi kilkakrotnie kompilować tegosamego kodu

W plikach nagłoacutewkowych często umieszcza się też definicje typoacutew z ktoacuterych korzysta bibliotekaalbo np makr

2022 Budowa najprostszej bibliotekiZałoacuteżmy że nasza biblioteka będzie zawierała jedną funkcję ktoacuterawypisuje na ekran tekst ldquoplWikibooksrdquoUtwoacuterzmy zatem nasz plik nagłoacutewkowy

151

152 ROZDZIAŁ 20 BIBLIOTEKI

ifndef WIKI_Hdefine WIKI_Hvoid wiki (void)endif

Należy pamiętać o podaniu void w liście argumentoacutew funkcji nie przyjmujących argumentoacutew Oile przy definicji funkcji nie trzeba tego robić (jak to często czyniliśmy w przypadku funkcji main) otyle w prototypie brak słoacutewka void oznacza że w prototypie nie ma informacji na temat tego jakieargumenty funkcja przyjmuje

Plik nagłoacutewkowy zapisujemy jako ldquowikihrdquo Teraz napiszmy ciało tej funkcji

include wikihinclude ltstdiohgt

void wiki (void)

printf (plWikibooksn)

Ważne jest dołączenie na początku pliku nagłoacutewkowego Dlaczego Plik nagłoacutewkowy zawieradeklaracje naszych funkcji mdash jeśli popełniliśmy błąd i deklaracja nie zgadza się z definicją kompilatorod razu nas o tym powiadomi Oproacutecz tego plik nagłoacutewkowy może zawierać definicje istotnych typoacutewlub makr

Zapiszmy naszą bibliotekę jako plik ldquowikicrdquo Teraz należy ją skompilować Robi się to trochę ina-czej niż normalny program Należy po prostu do opcji kompilatora gcc dodać opcję ldquo-crdquo

gcc wikic -c -o wikio

Rozszerzenie ldquoordquo jest domyślnym rozszerzeniem dla bibliotek statycznych (typowych bibliotek łą-czonych z resztą programu na etapie kompilacji) Teraz możemy spokojnie skorzystać z naszej nowejbiblioteki Napiszmy nasz program

include wikih

int main ()

wiki()return 0

Zapiszmy program jako ldquomaincrdquo Teraz musimy odpowiednio skompilować nasz program

gcc mainc wikio -o main

Uruchamiamy nasz program

mainplWikibooks

Jak widać nasza pierwsza biblioteka działaZauważmy że kompilatorowi podajemy i pliki z kodem źroacutedłowym (mainc) i pliki ze skompilo-

wanymi bibliotekami (wikio) by uzyskać plik wykonywalny (main) Jeśli nie podalibyśmy plikoacutew zbibliotekami mainc co prawda skompilowałby się ale błąd zostałby zgłoszony przez linker mdash częśćkompilatora odpowiedzialna za wstawienie w miejsce wywołań funkcji ich adresoacutew (takiego adresulinker nie moacutegłby znaleźć)

202 JAK ZBUDOWANA JEST BIBLIOTEKA 153

2023 Zmiana dostępu do funkcji i zmienny (static i extern)Język C w przeciwieństwie do swego młodszego krewnego mdash C++ nie posiada praktycznie żadnychmechanizmoacutew ochrony kodu biblioteki przed modyfikacjami C++ ma w swoim asortymencie minsterowanie uprawnieniami roacuteżnych elementoacutew klasy Jednak programista piszący program w C niejest tak do końca bezradny Autorzy C dali mu do ręki dwa narzędzia extern oraz static Pierwsze ztych słoacutew kluczowych informuje kompilator że dana funkcja lub zmienna istnieje ale w innymmiejscui zostanie dołączona do kodu programu w czasie łączenia go z biblioteką

extern przydaje się gdy zmienna lub funkcja jest zadeklarowana w bibliotece ale nie jest udostęp-niona na zewnątrz (nie pojawia się w pliku nagłoacutewkowym) Przykładowo

bibliotekah extern char zmienna_dzielona[]

bibliotekac include bibliotekah

char zmienna_dzielona[] = Zawartosc

mainc include ltstdiohgtinclude bibliotekah

int main()

printf(sn zmienna_dzielona)return 0

Gdybyśmy tu nie zastosowali extern kompilator (nie linker) zaprotestowałby że nie zna zmiennejzmienna dzielona Proacuteba dopisania deklaracji char zmienna dzielona stworzyłaby nową zmienną iutracilibyśmy dostęp do interesującej nas zawartości

Odwrotne działanie ma słowo kluczowe static użyte w tym kontekście (użyte wewnątrz bloku two-rzy zmienną statyczną więcej informacji w rozdziale Zmienne) Może ono odnosić się zaroacutewno dozmiennych jak i do funkcji globalnych Powoduje że dana zmienna lub funkcja jest niedostępna nazewnątrz biblioteki1 Możemy dzięki temu ukryć np funkcje ktoacutere używane są przez samą bibliotekęby nie dało się ich wykorzystać przez extern

1Tak naprawdę całe ldquoukrycierdquo funkcji polega na zmianie niektoacuterych danych w pliku z kodem binarnym danejbiblioteki (pliku o) przez co linker powoduje wygenerowanie komunikatu o błędzie w czasie łączenia biblioteki zprogramem

154 ROZDZIAŁ 20 BIBLIOTEKI

Rozdział 21

Więcej o kompilowaniu

211 Ciekawe opcje kompilatora Emdash powoduje wygenerowanie kodu programu ze zmianami wprowadzonymi przez preprocesor

S mdash zamiana kodu w języku C na kod asemblera (komenda gcc -S plikc spowoduje utworzeniepliku o nazwie pliks w ktoacuterym znajdzie się kod asemblera)

c mdash kompilacja bez łączenia z bibliotekami

Ikatalog mdash ustawienie domyślnego katalogu z plikami nagłoacutewkowymi na katalog

lbiblioteka mdash wymusza łączenie programu z podaną biblioteką (np -lGL)

212 Program makeDość często może się zdarzyć że nasz program składa się z kilku plikoacutew źroacutedłowych Jeśli tych plikoacutewjest mało (np -) możemy jeszcze proacutebować ręcznie kompilować każdy z nich Jednak jeśli tych plikoacutewjest dużo lub chcemy pokazać nasz program innym użytkownikom musimy stworzyć elegancki sposoacutebkompilacji naszego programu Właśnie po to aby zautomatyzować proces kompilacji powstał programmake Program make analizuje pliki Makefile i na ich podstawie wykonuje określone czynności

2121 Budowa pliku MakefileUwaga poniżej został omoacutewiony Makefile dla Make Istnieją inne programy make i mogą używaćinnej składni Na Wikibooks został też obszernie opisany program make firmy Borland

Najważniejszym elementem pliku Makefile są zależności oraz reguły przetwarzania Zależnościpolegają na tym że np jeśli nasz program ma być zbudowany z plikoacutew to najpierw należy skom-pilować każdy z tych plikoacutew a dopiero poacuteźniej połączyć je w jeden cały program Zatem zależnościokreślają kolejność wykonywanych czynności Natomiast reguły określają jak skompilować dany plikZależności tworzy się tak

co od_czegoreguły

Dzięki temu program make zna już kolejność wykonywanych działań oraz czynności jakie ma wy-konać Aby zbudować ldquocordquo należy wykonać polecenie make co Pierwsza reguła w pliku Makefile jestregułą domyślną Jeśli wydamy polecenie make bez parametroacutew zostanie zbudowana właśnie reguładomyślna Tak więc dobrze jest jako pierwszą regułę wstawić regułę budującą końcowy plik wykony-walny zwyczajowo regułę tą nazywa się all

155

156 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Należy pamiętać by sekcji ldquocordquo niewcinać natomiast ldquoregułyrdquowcinać tabulatorem Część ldquood czegordquomoże być pusta

Plik Makefile umożliwia też definiowanie pewnych zmiennych Nie trzeba tutaj się już troszczyć otyp zmiennej wystarczy napisać

nazwa_zmiennej = wartość

Wten sposoacutebmożemy zadeklarować dowolnie dużo zmiennych Zmiennemogą być roacuteżnemdash nazwakompilatora jego parametry iwiele innych Zmiennej używamywnastępujący sposoacuteb $(nazwa zmiennej)

Komentarze w pliku Makefile tworzymy zaczynając linię od znaku hash ()

2122 Przykładowy plik MakefileDość tej teorii teraz zajmiemy się działającym przykładem Załoacuteżmy że nasz przykładowy programnazywa się test oraz składa się z czterech plikoacutew pierwszyc drugic trzecic czwartyc

Odpowiedni plik Makefile powinien wyglądać mniej więcej tak

Moacutej plik makefile - wpisz make all aby skompilować cały program (właściwie wystarczy wpisać make - all jest domyślny jako pierwszy cel)CC = gcc

all pierwszyo drugio trzecio czwartyo$(CC) pierwszyo drugio trzecio czwartyo -o test

pierwszyo pierwszyc$(CC) pierwszyc -c -o pierwszyo

drugio drugic$(CC) drugic -c -o drugio

trzecio trzecic$(CC) trzecic -c -o trzecio

czwartyo czwartyc$(CC) czwartyc -c -o czwartyo

Widzimy że nasz program zależy od plikoacutew z rozszerzeniem o (pierwszyo itd) potem każdy ztych plikoacutew zależy od plikoacutew c ktoacutere program make skompiluje w pierwszej kolejności a następniepołączy w jeden program (test) Nazwę kompilatora zapisaliśmy jako zmienną ponieważ powtarza sięi zmienna jest sposobem by zmienić ją wszędzie za jednym zamachem

Zatem jak widać używanie pliku Makefile jest bardzo proste Warto na koniec naszego przykładudodać regułę ktoacutera wyczyści katalog z plikoacutew o

cleanrm -f o test

Ta reguła spowoduje usunięcie wszystkich plikoacutew o oraz naszego programu jeśli napiszemy makeclean

Możemy też ukryć wykonywane komendy albo dopisać własny opis czynności

cleanecho Usuwam gotowe plikirm -f o test

Ten sam plik Makefile moacutegłby wyglądać inaczej

213 OPTYMALIZACJE 157

CFLAGS = -g -O tutaj można dodawać inne flagi kompilatoraLIBS = -lm tutaj można dodawać biblioteki

OBJ =pierwszyo drugio trzecio czwartyo

all main

cleanrm -f o test

co$(CC) -c $(INCLUDES) $(CFLAGS) $lt

main $(OBJ)$(CC) $(OBJ) $(LIBS) -o test

Tak naprawdę jest to dopiero bardzo podstawowe wprowadzenie do używania programu makejednak jest ono wystarczające byś zaczął z niego korzystać Wyczerpujące omoacutewienie całego programuniestety przekracza zakres tego podręcznika

213 OptymalizacjeKompilator umożliwia generację kodu zoptymalizowanego dla konkretnej architektury Służą dotego opcje -mar= i -mtune= Stopień optymalizacji ustalamy za pomocą opcji -Ox gdzie x jest nume-rem stopnia optymalizacji (od do ) Możliwe jest też użycie opcji -Os ktoacutera powoduje generowaniekodu o jak najmniejszym rozmiarze Aby skompilować dany plik z optymalizacjami dla procesora Ath-lon należy napisać tak

gcc programc -o program -march=athlon-xp -O3

Z optymalizacjami należy uważać gdyż często zdarza się że kod skompilowany bez optymalizacjidziała zupełnie inaczej niż ten ktoacutery został skompilowany z optymalizacjami

2131 WyroacutewnywanieWyroacutewnywanie jest pewnym zjawiskiem na ktoacutere w bardzo wielu podręcznikach moacutewiących o Cw ogoacutele się nie wspomina Ten rozdział ma za zadanie wyjaśnienie tego zjawiska oraz uprzedzenieprogramisty o pewnych faktach ktoacutere w poacuteźniejszej jego ldquotwoacuterczościrdquo mogą zminimalizować czas naznalezienie pewnych informacji ktoacutere mogą wpływać na to że jego program nie będzie działał popraw-nie

Często zdarza się że kompilator w ramach optymalizacji ldquowyroacutewnujerdquo elementy struktury tak abyprocesor moacutegł łatwiej odczytać i przetworzyć dane Przyjrzyjmy się bliżej następującemu fragmentowikodu

typedef struct unsigned char wiek 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

158 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Aby procesor moacutegł łatwiej przetworzyć dane kompilator może dodać do tej struktury jedno ośmio-bitowe pole Wtedy struktura będzie wyglądała tak

typedef struct unsigned char wiek 8 bitoacutew unsigned char fill[1] 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

Wtedy rozmiar zmiennych przechowujących wiek płeć oraz dochoacuted będzie wynosił bity mdashbędzie zatem potęgą liczby dwa i procesorowi dużo łatwiej będzie tak ułożoną strukturę przechowywaćw pamięci cache Jednak taka sytuacja nie zawsze jest pożądana Może się okazać że nasza strukturamusi odzwierciedlać np pojedynczy pakiet danych przesyłanych przez sieć Nie może być w niejzatem żadnych innych poacutel poza tymi ktoacutere są istotne do transmisji Aby wymusić na kompilatorzewyroacutewnanie -bajtowe (co w praktyce wyłącza je) należy przed definicją struktury dodać dwie linijkiTen kod działa pod Visual C++

pragma pack(push)pragma pack(1)

struct struktura

pragma pack(pop)

W kompilatorze należy po deklaracji struktury dodajemy przed średnikiem kończącym jednąlinijkę

__attribute__ ((packed))

Działa ona dokładnie tak samo jak makra pragma jednak jest ona obecna tylko w kompilatorze

Dzięki użyciu tego atrybutu kompilator zostanie ldquozmuszonyrdquo do braku ingerencji w naszą strukturęJest jednak jeszcze jeden być może bardziej elegancki sposoacuteb na obejście dopełniania Zauważyłeś żedopełnienie dodane przez kompilator pojawiło się między polem o długości bitoacutew (plec) oraz polem odługości bitoacutew (dochod) Wyroacutewnywanie polega na tym że dana zmienna powinna być umieszczonapod adresem będącym wielokrotnością jej rozmiaru Oznacza to że jeśli np mamy w strukturze napoczątku dwie zmienne o rozmiarze jednego bajta a potem jedną zmienną o rozmiarze bajtoacutew topomiędzy polami o rozmiarze bajtoacutew a polem czterobajtowym pojawi się dwubajtowe dopełnienieMoże Ci się wydawać że jest to tylko niepotrzebne mącenie w głowie jednak niektoacutere architektury(zwłaszcza typu ) mogą nie wykonać kodu ktoacutery nie został wyroacutewnany Dlatego naszą strukturępowinniśmy zapisać mniej więcej tak

typedef struct unsigned short dochod 16 bitoacutew unsigned char wiek 8 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

W ten sposoacuteb wyroacutewnana struktura nie będzie podlegała modyfikacjom przez kompilator oraz bę-dzie przenośna pomiędzy roacuteżnymi kompilatorami

Wyroacutewnywanie działa także na pojedynczych zmiennych w programie jednak ten problem nie po-woduje tyle zamieszania co ingerencja kompilatora w układ poacutel struktury Wyroacutewnywanie zmiennychpolega tylko na tym że kompilator umieszcza je pod adresami ktoacutere są wielokrotnością ich rozmiaru

214 KOMPILACJA KRZYŻOWA 159

214 Kompilacja krzyżowaMając w domu dwa komputery o odmiennych architekturach (np i oraz Sparc) możemy potrze-bować stworzyć program dla jednej maszyny mając do dyspozycji tylko drugi komputer Nie musimywtedy latać do znajomego posiadającego odpowiedni sprzęt Możemy skorzystać z tzw kompilacjikrzyżowej (ang cross-compile) Polega ona na tym że program nie jest kompilowany pod procesorna ktoacuterym działa kompilator lecz na inną zdefiniowaną wcześniej maszynę Efekt będzie taki sam askompilowany program możemy bez problemu uruchomić na drugim komputerze

215 Inne narzędziaWśroacuted przydatnych narzędzi warto wymienić roacutewnież program objdump (zaroacutewno pod Unix jak ipod Windows) oraz readelf (tylko Unix) Objdump służy do deasemblacji i analizy skompilowanychprogramoacutew Readelf służy do analizy pliku wykonywalnego w formacie (używanego w większościsystemoacutew z rodziny Unix) Więcej informacji możesz uzyskać pisząc (w systemach Unix)

man 1 objdumpman 1 readelf

160 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Rozdział 22

Zaawansowane operacjematematyczne

221 Biblioteka matematycznaAby moacutec korzystać z wszystkich dobrodziejstw funkcji matematycznych musimy na początku dołączyćplik mathh

include ltmathhgt

A w procesie kompilacji (dotyczy kompilatora GCC) musimy niekiedy dodać flagę ldquo-lmrdquo

gcc plikc -o plik -lm

Funkcje matematyczne ktoacutere znajdują się w bibliotece standardowej możesz znaleźć tutaj Przykorzystaniu z nich musisz wziąć pod uwagę min to że biblioteka matematyczna prowadzi kalkulacjęw oparciu o radiany a nie stopnie

2211 Stałe matematyczneW pliku mathh zdefiniowane są pewne stałe ktoacutere mogą być przydatne do obliczeń Są to min

M E mdash podstawa logarytmu naturalnego (e liczba Eulera)

M LOG2E mdash logarytm o podstawie z liczby e

M LOG10E mdash logarytm o podstawie z liczby e

M LN2 mdash logarytm naturalny z liczby

M LN10 mdash logarytm naturalny z liczby

M PI mdash liczba π

M PI 2 mdash liczba π

M PI 4 mdash liczba π

M 1 PI mdash liczba π

M 2 PI mdash liczba π

161

162 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

222 Prezentacja liczb rzeczywisty w pamięci komputeraByć może ten temat może wydać Ci się niepotrzebnym lecz w wielu książkach nie ma w ogoacutele tegotematu Dzięki niemu zrozumiesz jak komputer radzi sobie z przecinkiem oraz dlaczego niektoacutere ob-liczenia dają niezbyt dokładne wyniki Na początek trochę teorii do przechowywania liczb rzeczywi-stych przeznaczone są typy float double oraz long double Zajmują one odpowiednio oraz bitoacutew Wiemy też że komputer nie ma fizycznej możliwości zapisania przecinka Sproacutebujmy terazzapisać jakąś liczbę wymierną w formie liczb binarnych Nasza liczba to powiedzmy 425 Sproacutebujmy jąrozbić na sumę potęg dwoacutejki 4 = 1 middot22 +0 middot21 +0 middot20 Dobra mdash rozpisaliśmy liczbę 4 ale co z częściądziesiętną Skorzystajmy z zasad matematyki 025 = 2minus2 Zatem nasza liczba powinna wyglądaćtak

10001Ponieważ komputer nie jest w stanie przechować pozycji przecinka ktoś wpadł na prosty ale

sprytny pomysł ustawienia przecinka jak najbliżej początku liczby i tylko mnożenia jej przez odpowied-nią potęgę dwoacutejki Taki sposoacuteb przechowywania liczb nazywamy zmiennoprzecinkowym a procesprzekształcania naszej liczby z postaci czytelnej przez człowieka na format zmiennoprzecinkowy na-zywamy normalizacją Wroacutećmy do naszej liczby mdash 425 W postaci binarnej wygląda ona tak 10001natomiast po normalizacji będzie wyglądała tak 10001 middot 22 W ten sposoacuteb w pamięci komputeraznajdą się dwie informacje liczba zakodowana w pamięci z ldquowirtualnymrdquo przecinkiem oraz numerpotęgi dwoacutejki Te dwie informacje wystarczają do przechowania wartości liczby Jednak pojawia sięinny problem mdash co się stanie jeśli np będziemy chcieli przełożyć liczbę typu 1

3 Otoacuteż tutaj wychodzą

na wierzch pewne niedociągnięcia komputera w dziedzinie samej matematyki daje w rozwinięciudziesiętnym 0(3) Jak zatem zapisać taką liczbę Otoacuteż nie możemy przechować całego jej rozwinięcia(wynika to z ograniczeń typu danych mdash ma on niestety skończoną liczbę bitoacutew) Dlatego przechowujesię tylko pewne przybliżenie liczby Jest ono tym bardziej dokładne im dany typ ma więcej bitoacutew Za-tem do obliczeń wymagających dokładnych danych powinniśmy użyć typu double lub long double Naszczęście w większości przeciętnych programoacutew tego typu problemy zwykle nie występują A ponie-waż początkujący programista nie odpowiada za tworzenie programoacutew sterujących np lotem statkukosmicznego więc drobne przekłamania na odległych miejscach po przecinku nie stanowią większegoproblemu

Należy brać pod uwagę że w komputerze liczby rzeczywiste nie są tym samym czym w mate-matyce Komputery nie potrafią przechować każdej liczby zmiennoprzecinkowej w związku z tymobliczenia prowadzone przy użyciu komputera mogą być niedokładne i odbiegać od prawidłowych wy-nikoacutew Szczegoacutelnie ważne jest to przy programowaniu aplikacji inżynieryjnych oraz w medycyniegdzie takie błędy mogą skutkować katastrofą ilub narażeniem ludzkiego życia oraz zdrowia

Na ile poważny jest to problem Sproacutebujmy przyjrzeć się działaniu polegającym na -krotnymdodawaniu do liczby wartości Oto kod

include ltstdiohgt

int main ()

float a = 0int i = 0for (ilt1000i++)

a += 1030printf (fn a)

Z matematyki wynika że 1000 middot 13

= 333(3) podczas gdy komputer wypisze wynik nieco roacuteżniącysię od oczekiwanego (w moim przypadku)

223 LICZBY ZESPOLONE 163

333334106

Błąd pojawił się na cyfrze części tysięcznej liczby Nie jest to może poważny błąd jednak zastanoacutewmysię czy ten błąd nie będzie się powiększał Zamieniamy w kodzie ilość iteracji z na Tymrazem moacutej komputer wskazał już nieco inny wynik

33356554688

Błąd przesunął się na cyfrę dziesiątek w liczbie Tak więc nie należy do końca polegać na prezentacjiliczb zmiennoprzecinkowych w komputerze

223 Liczby zespoloneOperacje na liczba zespolony są częścią uaktualnionego standardu języka C o nazwie C ktoacuteryjest obsługiwany jedynie przez część kompilatoroacutew

Podane tutaj informacje zostały sprawdzone na systemie Gentoo Linux z biblioteką GNU libc wwersji i kompilatorem GCC w wersji

Dotychczas korzystaliśmy tylko z liczb rzeczywistych lecz najnowsze standardy języka C umożli-wiają korzystanie także z innych liczb mdash np z liczb zespolonych

Abymoacutec korzystać z liczb zespolonychwnaszymprogramie należywnagłoacutewku programu umieścićnastępującą linijkę

include ltcomplexhgt

Wiemy że liczba zespolona zdeklarowana jest następująco

z = a+bi gdzie a b są liczbami rzeczywistymi a ii=(-1)

W pliku complexh liczba i zdefiniowana jest jako I Zatem wyproacutebujmy możliwości liczb zespolo-nych

include ltmathhgtinclude ltcomplexhgtinclude ltstdiohgt

int main ()

float _Complex z = 4+25Iprintf (Liczba z f+fin creal(z) cimag (z))return 0

następnie kompilujemy nasz program

gcc plik1c -o plik1 -lm

Po wykonaniu naszego programu powinniśmy otrzymać

Liczba z 400+250i

W programie zamieszczonym powyżej użyliśmy dwoacutech funkcji mdash creal i cimag

creal mdash zwraca część rzeczywistą liczby zespolonej

cimag mdash zwraca część urojoną liczby zespolonej

164 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

Rozdział 23

Powszene praktyki

Rozdział tenma za zadanie pokazać powszechnie stosowanemetody programowania wC Nie będziemytu uczyć jak należy stawiać nawiasy klamrowe ani ktoacutery sposoacuteb nazewnictwa zmiennych jest najlep-szy mdash prowadzone są o to spory z ktoacuterych niewiele wynika Zaprezentowane tu rozwiązania mająkonkretny wpływ na jakość tworzonych programoacutew

231 Konstruktory i destruktoryW większości obiektowych językoacutew programowania obiekty nie mogą być tworzone bezpośrednio mdashobiekty otrzymuje się wywołując specjalną metodę danej klasy zwaną konstruktorem Konstruktorysą ważne ponieważ pozwalają zapewnić obiektowi odpowiedni stan początkowy Destruktory wywo-ływane na końcu czasu życia obiektu są istotne gdy obiekt ma wyłączny dostęp do pewnych zasoboacutewi konieczne jest upewnienie się czy te zasoby zostaną zwolnione

Ponieważ C nie jest językiem obiektowym nie ma wbudowanego wsparcia dla konstruktoroacutew idestruktoroacutew Często programiści bezpośrednio modyfikują tworzone obiekty i struktury Jednakżeprowadzi to do potencjalnych błędoacutew ponieważ operacje na obiekcie mogą się nie powieść lub zacho-wać się nieprzewidywalnie jeśli obiekt nie został prawidłowo zainicjalizowany Lepszym podejściemjest stworzenie funkcji ktoacutera tworzy instancję obiektu ewentualnie przyjmując pewne parametry

struct string size_t sizechar data

struct string create_string(const char initial) assert (initial = NULL)struct string new_string = malloc(sizeof(new_string))if (new_string = NULL)

new_string-gtsize = strlen(initial)new_string-gtdata = strdup(initial)

return new_string

Podobnie bezpośrednie usuwanie obiektoacutew może nie do końca się udać prowadząc do wyciekuzasoboacutew Lepiej jest użyć destruktora

void free_string(struct string s)

165

166 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

assert (s = NULL)free(s-gtdata) zwalniamy pamięć zajmowaną przez strukturę free(s) usuwamy samą strukturę

Często łączy się destruktory z zerowaniem zwolnionych wskaźnikoacutewCzasami dobrze jest ukryć definicję obiektu żeby mieć pewność że użytkownicy nie utworzą go

ręcznie Aby to zapewnić struktura jest definiowanaw pliku źroacutedłowym (lub prywatnymnagłoacutewku nie-dostępnym dla użytkownikoacutew) zamiast w pliku nagłoacutewkowym a deklaracja wyprzedzająca jest umiesz-czona w pliku nagłoacutewkowym

struct stringstruct string create_string(const char initial)void free_string(struct string s)

232 Zerowanie zwolniony wskaźnikoacutewJak powiedziano już wcześniej po wywołaniu free() dla wskaźnika staje się on ldquowiszącym wskaź-nikiemrdquo Co gorsze większość nowoczesnych platform nie potrafi wykryć kiedy taki wskaźnik jestużywany zanim zostanie ponownie przypisany

Jednym z prostych rozwiązań tego problemu jest zapewnienie że każdy wskaźnik jest zerowanynatychmiast po zwolnieniu

free(p)p = NULL

Inaczej niż w przypadku ldquowiszących wskaźnikoacutewrdquo na wielu nowoczesnych architekturach przyproacutebie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek Dodatkowo programy mogązawierać sprawdzanie błędoacutew dla zerowych wartości ale nie dla ldquowiszących wskaźnikoacutewrdquo Aby zapew-nić że jest to wykonywane dla każdego wskaźnika możemy użyć makra

define FREE(p) do free(p) (p) = NULL while(0)

(aby zobaczyć dlaczego makro jest napisane w ten sposoacuteb zobacz Konwencje pisania makr)Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik ktoacutery przekazuje się do

nich więc argument musi być do nich przekazywany przez referencję Na przykład oto zaktualizowanydestruktor z sekcji Konstruktory i destruktory

void free_string(struct string s)

assert(s = NULL ampamp s = NULL)FREE((s)-gtdata) zwalniamy pamięć zajmowaną przez strukturę FREE(s) usuwamy strukturę

Niestety ten idiom nie jest wstanie pomoacutec wwypadkuwskazywania przez inne wskaźniki zwolnio-nej pamięci Z tego powodu niektoacuterzy eksperci C uważają go za niebezpieczny jako kreujący fałszywepoczucie bezpieczeństwa

233 Konwencje pisania makrPonieważ makra preprocesora działają na zasadzie zwykłego zastępowania napisoacutew są podatne nawiele kłopotliwych błędoacutew z ktoacuterych części można uniknąć przez stosowanie się do poniższych reguł

234 JAK DOSTAĆ SIĘ DO KONKRETNEGO BITU 167

Umieszczaj nawiasy dookoła argumentoacutew makra kiedy to tylko możliwe Zapewnia to że gdysą wyrażeniami kolejność działań nie zostanie zmieniona Na przykład

Źle define kwadrat(x) (xx)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Załoacuteżmy że w programie makro kwadrat() zdefiniowane bez nawiasoacutew zostałowywołane następująco kwadrat(a+b) Wtedy zostanie ono zamienione przez preprocesorna (a+ba+b) Z kolejności działań wiemy że najpierw zostanie wykonane mnożeniewięc wartość wyrażenia kwadrat(a+b) będzie roacuteżna od kwadratu wyrażenia a+b

Umieszczaj nawiasy dookoła całegomakra jeśli jest pojedynczymwyrażeniem Ponownie chronito przed zaburzeniem kolejności działań

Źle define kwadrat(x) (x)(x)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Definiujemy makro define suma(a b) (a)+(b) i wywołujemy je w kodziewynik = suma(3 4) 5 Makro zostanie rozwinięte jako wynik = (3)+(4)5 co mdash zpowodu kolejności działań mdash da wynik inny niż pożądany

Jeśli makro składa się z wielu instrukcji lub deklaruje zmienne powinno być umieszczone w pętlido while(0) bez kończącego średnika Pozwala to na użycie makra jak pojedynczejinstrukcji w każdym miejscu jak ciało innego wyrażenia pozwalając jednocześnie na umiesz-czenie średnika po makrze bez tworzenia zerowego wyrażenia Należy uważać by zmienne wmakrze potencjalnie nie kolidowały z argumentami makra

Źle define FREE(p) free(p) p = NULL

Dobrze define FREE(p) do free(p) p = NULL while(0)

Unikaj używania argumentoacutew makra więcej niż raz wewnątrz makra Może to spowodowaćkłopoty gdy argument makra ma efekty uboczne (np zawiera operator inkrementacji)

Przykład define kwadrat(x) ((x)(x)) nie powinno być wywoływane z operatoreminkrementacji kwadrat(a++) ponieważ zostanie to rozwinięte jako ((a++) (a++)) co jestniezgodne ze specyfikacją języka i zachowanie takiego wyrażenia jest niezdefiniowane(dwukrotna inkrementacja w tym samym wyrażeniu)

Jeśli makro może być w przyszłości zastąpione przez funkcję rozważ użycie w nazwie małychliter jak w funkcji

234 Jak dostać się do konkretnego bituWiemy że komputer to maszyna ktoacuterej najmniejszą jednostką pamięci jest bit jednak w C najmniejszazmienna ma rozmiar bitoacutew (czyli jednego bajtu) Jak zatem można odczytać wartość pojedynczychbitoacutew W bardzo prosty sposoacuteb mdashw zestawie operatoroacutew języka C znajdują się tzw operatory bitoweSą to m in

amp mdash logiczne ldquoirdquo

| mdash logiczne ldquolubrdquo

˜ mdash logiczne ldquonierdquo

Oproacutecz tego są także przesunięcia (ltlt oraz gtgt) Zastanoacutewmy się teraz jak je wykorzystać w prak-tyce Załoacuteżmy że zajmujemy się jednobajtową zmienną

unsigned char i = 2

Zmatematyki wiemy że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej) Jeśli teraz np chcielibyśmy ldquozapalićrdquo drugi bit od lewej (tj bit ktoacuterego zapalenie niejako ldquododardquo doliczby wartość 6) powinniśmy użyć logicznego lub

168 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

unsigned char i = 2i |= 64

Gdzie =6 Odczytywanie wykonuje się za pomocą tzw maski bitowej Polega to na

wyzerowaniu bitoacutew ktoacutere są nam w danej chwili niepotrzebne

odpowiedniemu przesunięciu bitoacutew dzięki czemu szukany bit znajdzie się na pozycji pierwszegobitu od prawej

Do ldquowyłuskaniardquo odpowiedniego bitu możemy posłużyć się operacją ldquoirdquo mdash czyli operatorem ampWygląda to analogicznie do posługiwania się operatorem ldquolubrdquo

unsigned char i = 3 bitowo 00000011 unsigned char temp = 0temp = i amp 1 sprawdzamy najmniej znaczący bit - czyli pierwszy z prawej if (temp)

printf (bit zapalony)else

printf (bit zgaszony)

Jeśli nie władasz biegle kodem binarnym tworzenie masek bitowych ułatwią ci przesunięcia bitoweAby uzyskać liczbę ktoacutera ma zapalony bit o numerze n (bity są liczone od zera) przesuwamy bitowo wlewo jedynkę o n pozycji

1 ltlt n

Jeśli chcemy uzyskać liczbę w ktoacuterej zapalone są bity na pozycjach l m n mdash używamy sumylogicznej (ldquolubrdquo)

(1 ltlt l) | (1 ltlt m) | (1 ltlt n)

Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone sąwszystkie bity poza n odwracamy ją za pomocąoperatora logicznej negacji

~(1 ltlt n)

Warto władać biegle operacjami na bitach ale początkujący mogą (po uprzednim przeanalizowa-niu) zdefiniować następujące makra i ich używać

Sprawdzenie czy w liczbie k jest zapalony bit n define IS_BIT_SET(k n) ((k) amp (1 ltlt (n)))

Zapalenie bitu n w zmiennej k define SET_BIT(k n) (k |= (1 ltlt (n)))

Zgaszenie bitu n w zmiennej k define RESET_BIT(k n) (k amp= ~(1 ltlt (n)))

235 Skroacutety notacjiIstnieją pewne sposoby ograniczenia ilości niepotrzebnego kodu Przykładem może być wykonywaniejednej operacji w razie wystąpienia jakiegoś warunku np zamiast pisać

if (warunek) printf (Warunek prawdziwyn)

235 SKROacuteTY NOTACJI 169

możesz skroacutecić notację do

if (warunek)printf (Warunek prawdziwyn)

Podobnie jest w przypadku pętli for

for (warunek)printf (Wyświetlam się w pętlin)

Niestety ograniczeniemw tymwypadku jest to że można w ten sposoacuteb zapisać tylko jedną instruk-cję

170 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

Rozdział 24

Przenośność programoacutew

Jak dowiedziałeś się z poprzednich rozdziałoacutew tego podręcznika język C umożliwia tworzenie progra-moacutew ktoacutere mogą być uruchamiane na roacuteżnych platformach sprzętowych pod warunkiem ich powtoacuter-nej kompilacji Język C należy do grupy językoacutew wysokiego poziomu ktoacutere tłumaczone są do poziomukodumaszynowego (tzn kod źroacutedłowy jest kompilowany) Z jednej strony jest to korzystne posunięciegdyż programy są szybsze i mniejsze niż programy napisane w językach interpretowanych (takich wktoacuterych kod źroacutedłowy nie jest kompilowany do kodu maszynowego tylko na bieżąco interpretowanyprzez tzw interpreter) Jednak istnieje także druga strona medalu mdash pewne zawiłości sprzętu ktoacutereograniczają przenośność programoacutew Ten rozdział ma wyjaśnić Ci mechanizmy działania sprzętu wtaki sposoacuteb abyś bez problemu moacutegł tworzyć poprawne i całkowicie przenośne programy

241 Niezdefiniowane zaowanie i zaowanie zależne odimplementacji

Wtrakcie czytania kolejnych rozdziałoacutewmożna było się natknąć na zwroty takie jak zachowanie niezde-finiowane (ang undefined behaviour) czy zachowanie zależne od implementacji (ang implementation-defined behaviour) Coacuteż one tak właściwie oznaczają

Zacznijmy od tego drugiego Autorzy standardu języka C czuli że wymuszanie jakiegoś konkret-nego działania danego wyrażenia byłoby zbytnim obciążeniem dla osoacuteb piszących kompilatory gdyżdany wymoacuteg moacutegłby być bardzo trudny do zrealizowania na konkretnej architekturze Dla przykładugdyby standard wymagał że typ unsigned char ma dokładnie bitoacutew to napisanie kompilatora dla ar-chitektury na ktoacuterej bajt ma bitoacutew byłoby cokolwiek kłopotliwe a z pewnością wynikowy programdziałałby o wiele wolniej niżby to było możliwe

Z tego właśnie powodu niektoacutere aspekty języka nie są określone bezpośrednio w standardzie i sąpozostawione do decyzji zespołu (osoby) piszącego konkretną implementację W ten sposoacuteb nie mażadnych przeciwwskazań (ze strony standardu) aby na architekturze gdzie bajty mają bitoacutew typchar roacutewnież miał tyle bitoacutew Dokonany wyboacuter musi być jednak opisany w dokumentacji kompilatoratak żeby osoba pisząca program w C mogła sprawdzić jak dana konstrukcja zadziała

Należy zatem pamiętać że poleganie na jakimś konkretnym działaniu programu w przypadkachzachowania zależnego od implementacji drastycznie zmniejsza przenośność kodu źroacutedłowego

Zachowania niezdefiniowane są o wiele groźniejsze gdyż zaistnienie takowego może spowodo-wać dowolny efekt ktoacutery nie musi być nigdzie udokumentowany Przykładem może tutaj być proacutebaodwołania się do wartości wskazywanej przez wskaźnik o wartości

Jeżeli gdzieś w naszym programie zaistnieje sytuacja niezdefiniowanego zachowania to nie jest jużto kwestia przenośności kodu ale po prostu błędu w kodzie chyba że świadomie korzystamy z roz-szerzenia naszego kompilatora Rozważmy odwoływanie się do wartości wskazywanej przez wskaźniko wartości Ponieważ według standardu operacja taka ma niezdefiniowany skutek to w szcze-goacutelności może wywołać jakąś z goacutery określoną funkcję mdash kompilator może coś takiego zrealizować

171

172 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

sprawdzając wartość wskaźnika przed każdą dereferencją w ten sposoacuteb niezdefiniowane zachowaniedla konkretnego kompilatora stanie się jak najbardziej zdefiniowane

Sytuacją wziętą z życia są operatory przesunięć bitowych gdy działają na liczbach ze znakiemKonkretnie przesuwanie w lewo liczb jest dla wielu przypadkoacutew niezdefiniowane Bardzo często jed-nak w dokumentacji kompilatora działanie przesunięć bitowych jest dokładnie opisane Jest to o tyleinteresujący fakt iż wielu programistoacutew nie zdaje sobie z niego sprawy i nieświadomie korzysta z roz-szerzeń kompilatora

Istnieje jeszcze trzecia klasa zachowań Zachowania nieokreślone (ang unspecified behaviour)Są to sytuacje gdy standard określa kilka możliwych sposoboacutew w jaki dane wyrażenie może działaći pozostawia kompilatorowi decyzję co z tym dalej zrobić Coś takiego nie musi być nigdzie opisanew dokumentacji i znowu poleganie na konkretnym zachowaniu jest błędem Klasycznym przykłademmoże być kolejność obliczania argumentoacutew wywołania funkcji

242 Rozmiar zmiennyRozmiar poszczegoacutelnych typoacutew danych (np char int czy long) jest roacuteżna na roacuteżnych platformachgdyż nie jest definiowany w sztywny sposoacuteb jak np ldquolong int zawsze powinien mieć bityrdquo (takieokreślenie wiązałoby się z wyżej opisanymi trudnościami) lecz w na zasadzie zależności typu ldquolongpowinien być nie kroacutetszy niż intrdquo ldquoshort nie powinien być dłuższy od intrdquo Pierwsza standaryzacjajęzyka C zakładała że typ int będzie miał taki rozmiar jak domyślna długość liczb całkowitych nadanym komputerze natomiast modyfikatory short oraz long zmieniały długość tego typu tylko wtedygdy dana maszyna obsługiwała typy o mniejszej lub większej długości1

Z tego powodu nigdy nie zakładaj że dany typ będzie miał określony rozmiar Jeżeli potrzebujesztypu o konkretnym rozmiarze (a dokładnej konkretnej liczbie bitoacutew wartości) możesz skorzystać z plikunagłoacutewkowego stdinth wprowadzonego do języka przez standard ISO C z roku Definiuje on typyint t int t int t int t uint t uint t uint t i uint t (o ile w danej architekturze występujątypy o konkretnej liczbie bitoacutew)

Jednak możemy posiadać implementację ktoacutera nie posiada tego pliku nagłoacutewkowego W takiej sy-tuacji nie pozostaje nam nic innego jak tworzyć własny plik nagłoacutewkowy w ktoacuterym za pomocą słoacutewkatypedef sami zdefiniujemy potrzebne nam typy Np

typedef unsigned char u8typedef signed char s8typedef unsigned short u16typedef signed short s16typedef unsigned long u32typedef signed long s32typedef unsigned long long u64typedef signed long long s64

Aczkolwiek należy pamiętać że taki plik będzie trzeba pisać od nowa dla każdej architektury najakiej chcemy kompilować nasz program

243 Porządek bajtoacutew i bitoacutew

2431 Bajty i słowaWiesz zapewne że podstawową jednostką danych jest bit ktoacutery może mieć wartość lub Kilkakolejnych bitoacutew2 stanowi bajt (dla skupienia uwagi przyjmijmy że bajt składa się z bitoacutew) Częstotyp short ma wielkość dwoacutech bajtoacutew i woacutewczas pojawia się pytanie w jaki sposoacuteb są one zapisane

1Dokładniejszy opis rozmiaroacutew dostępny jest w rozdziale Składnia2Standard wymaga aby było ich co najmniej 8 i liczba bitoacutew w bajcie w konkretnej implementacji jest określona

przez makro CHAR BIT zdefiniowane w pliku nagłoacutewkowym limitsh

243 PORZĄDEK BAJTOacuteW I BITOacuteW 173

w pamięci mdash czy najpierw ten bardziej znaczący mdash big-endian czy najpierw ten mniej znaczący mdashlittle-endian

Skąd takie nazwy Otoacuteż pochodzą one z książki Podroacuteże Guliwera w ktoacuterej liliputy kłoacuteciły się ostronę od ktoacuterej należy rozbijać jajko na twardo Jedni uważali że trzeba je rozbijać od grubszegokońca (big-endian) a drudzy że od cieńszego (lile-endian) Nazwy te są o tyle trafne że w wypadkuprocesoroacutew wyboacuter kolejności bajtoacutew jest sprawą czysto polityczną ktoacutera jest technicznie neutralna

Sprawa się jeszcze bardziej komplikuje w przypadku typoacutew ktoacutere składają się np z bajtoacutew Woacutew-czas są aż ( silnia) sposoby zapisania kolejnych fragmentoacutew takiego typu W praktyce zapewne spo-tkasz się jedynie z kolejnościami big-endian lub lile-endian co nie zmienia faktu że inne możliwościtakże istnieją i przy pisaniu programoacutew ktoacutere mają być przenośne należy to brać pod uwagę

Poniższy przykład dobrze obrazuje oba sposoby przechowywania zawartości zmiennych w pamięcikomputera (przyjmujemy CHAR BIT == oraz sizeof(long) == bez bitoacutew wypełnienia (ang paddingbits)) unsigned long zmienna = 0x01020304 w pamięci komputera będzie przechowywana tak

adres | 0 | 1 | 2 | 3 |big-endian |0x01|0x02|0x03|0x04|little-endian |0x04|0x03|0x02|0x01|

2432 Konwersja z jednego porządku do innegoCzasami zdarza się że napisany przez nas program musi się komunikować z innym programem (możeteż przez nas napisanym) ktoacutery działa na komputerze o (potencjalnie) innym porządku bajtoacutew Częstonajprościej jest przesyłać liczby jako tekst gdyż jest on niezależny od innych czynnikoacutew jednak takiformat zajmuje więcej miejsca a nie zawsze możemy sobie pozwolić na taką rozrzutność

Przykładem może być komunikacja sieciowa w ktoacuterej przyjęło się że dane przesyłane są w po-rządku big-endian Aby moacutec łatwo operować na takich danych w standardzie zdefiniowanonastępujące funkcje (w zasadzie zazwyczaj są to makra)

include ltarpainethgtuint32_t htonl(uint32_t hostlong)uint16_t htons(uint16_t hostshort)uint32_t ntohl(uint32_t netlong)uint16_t ntohs(uint16_t netshort)

Pierwsze dwie konwertują liczbę z reprezentacji lokalnej na reprezentację big-endian (host to ne-twork) natomiast kolejne dwie dokonują konwersji w drugą stronę (network to host)

Można roacutewnież skorzystać z pliku nagłoacutewkowego endianh w ktoacuterym definiowane są makra po-zwalające określić porządek bajtoacutew

include ltendianhgtinclude ltstdiohgt

int main() if __BYTE_ORDER == __BIG_ENDIAN

printf(Porządek big-endian (4321)n)elif __BYTE_ORDER == __LITTLE_ENDIAN

printf(Porządek little-endian (1234)n)elif defined __PDP_ENDIAN ampamp __BYTE_ORDER == __PDP_ENDIAN

printf(Porządek PDP (3412)n)else

printf(Inny porządek (d)n __BYTE_ORDER)endif

return 0

174 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

Na podstawie makra BYTE ORDER można skonstruować funkcję ktoacutera będzie konwertowaćliczby pomiędzy porządkiem roacuteżnymi porządkami

include ltendianhgtinclude ltstdiohgtinclude ltstdinthgt

uint32_t convert_order32(uint32_t val unsigned from unsigned to) if (from==to)

return val else

uint32_t ret = 0unsigned char tmp[5] = 0 0 0 0 0 unsigned char ptr = (unsigned char)ampvalunsigned div = 1000do tmp[from div 10] = ptr++ while ((div = 10))ptr = (unsigned char)ampretdiv = 1000do ptr++ = tmp[to div 10] while ((div = 10))return ret

define LE_TO_H(val) convert_order32((val) 1234 __BYTE_ORDER)define H_TO_LE(val) convert_order32((val) __BYTE_ORDER 1234)define BE_TO_H(val) convert_order32((val) 4321 __BYTE_ORDER)define H_TO_BE(val) convert_order32((val) __BYTE_ORDER 4321)define PDP_TO_H(val) convert_order32((val) 3412 __BYTE_ORDER)define H_TO_PDP(val) convert_order32((val) __BYTE_ORDER 3412)

int main ()

printf(08xn LE_TO_H(0x01020304))printf(08xn H_TO_LE(0x01020304))printf(08xn BE_TO_H(0x01020304))printf(08xn H_TO_BE(0x01020304))printf(08xn PDP_TO_H(0x01020304))printf(08xn H_TO_PDP(0x01020304))return 0

Ciągle jednak polegamy na niestandardowym pliku nagłoacutewkowym endianh Można go wyelimi-nować sprawdzając porządek bajtoacutew w czasie wykonywania programu

include ltstdiohgtinclude ltstdinthgt

int main() uint32_t val = 0x04030201unsigned char v = (unsigned char )ampvalint byte_order = v[0] 1000 + v[1] 100 + v[2] 10 + v[3]

if (byte_order == 4321) printf(Porządek big-endian (4321)n)

else if (byte_order == 1234)

244 BIBLIOTECZNE PROBLEMY 175

printf(Porządek little-endian (1234)n) else if (byte_order == 3412)

printf(Porządek PDP (3412)n) else

printf(Inny porządek (d)n byte_order)return 0

Powyższe przykłady opisują jedynie część problemoacutew jakie mogą wynikać z proacuteby przenoszeniabinarnych danych pomiędzy wieloma platformami Wszystkie co więcej zakładają że bajt ma bitoacutewco wcale nie musi być prawdą dla konkretnej architektury na ktoacuterą piszemy aplikację Co więcej liczbymogą posiadać w swojej reprezentacje bity wypełnienia (ang padding bits) ktoacutere nie biorą udziaływ przechowywaniu wartości liczby Te wszystkie roacuteżnice mogą dodatkowo skomplikować kod Toteżnależy być świadomym iż przenosząc dane binarnie musimy uważać na roacuteżne reprezentacje liczb

244 Biblioteczne problemy

2441 Dostępność bibliotekPisząc programy nieraz będziemy musieli korzystać z roacuteżnych bibliotek Problem polega na tym żenie zawsze będą one dostępne na komputerze na ktoacuterym inny użytkownik naszego programu będzieproacutebował go kompilować Dlatego też ważne jest abyśmy korzystali z łatwo dostępnych bibliotek ktoacuteredostępne są na wiele roacuteżnych systemoacutew i platform sprzętowych Zapamiętaj Twoacutej program jest natyle przenośny na ile przenośne są biblioteki z ktoacuterych korzysta

2442 Odmiany bibliotekPod Windows funkcje atan floor i fabs są w tej samej bibliotece co standardowe funkcje C

Pod Uniksami są w osobnej bibliotece matematycznej libm w wersji

statycznej (zwykle usrliblibma) i pliku nagłoacutewkowym mathh (zwykle usrincludemathh)3

ladowanej dynamicznie ( usrliblibmso )

Aby korzystać z tych funkcji potrzebujemy

dodać include ltmathhgt

przy kompilacji dołączyć bibliotekę libm gcc mainc -lm

Opcja -lm używa libmso albo libma w zależności od tego ktoacutere są znalezione i w zależności odobecności opcji -static45

245 Kompilacja warunkowaPrzy zwiększaniu przenośności kodu może pomoacutec preprocessor Przyjmijmy np że chcemy korzy-stać ze słoacutewka kluczowego inline wprowadzonego w standardzie C ale roacutewnocześnie chcemy abynasz program był rozumiany przez kompilatory ANSI CWoacutewczas możemy skorzystać z następującegokodu

ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline

3An Introduction to mdashfor the compilers gcc and g++ 27 Linking with external libraries4man ld5Dyskusja na grupie plcomposlinuxprogramowanie na temat c gc atan2 floor fabs

176 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

else define __inline__ endifendif

a w kodzie programu zamiast słoacutewka inline stosować inline Co więcej kompilator rozumiesłoacutewka kluczowe tak tworzone i w jego przypadku warto nie redefiniować ich wartości

ifndef __GNUC__ ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline else define __inline__ endif endifendif

Korzystając z kompilacji warunkowej można także korzystać z roacuteżnego kodu zależnie od (np) sys-temu operacyjnego Przykładowo przed kompilacją na konkretnej platformie tworzymy odpowiedniplik configh ktoacutery następnie dołączamy do wszystkich plikoacutew źroacutedłowych w ktoacuterych podejmujemydecyzje na podstawie zdefiniowanych makr Dla przykładu plik configh

ifndef CONFIG_Hdefine CONFIG_H

Uncomment if using Windows define USE_WINDOWS

Uncomment if using Linux define USE_LINUX

error You must edit configh fileerror Edit it and remove those error lines

endif

Jakiś plik źroacutedłowy

include configh

ifdef USE_WINDOWSrob_cos_wersja_dla_windows()

elserob_cos_wersja_dla_linux()

endif

Istnieją roacuteżne narzędzia ktoacutere pozwalają na automatyczne tworzenie takich plikoacutew configh dziękiczemu użytkownik przed skompilowaniem programu nie musi się trudzić i edytować ich ręcznie ajedynie uruchomić odpowiednie polecenie Przykładem jest zestaw autoconf i automake

Rozdział 25

Łączenie z innymi językami

Programista pisząc jakiś program ma problem z wyborem najbardziej odpowiedniego języka do utwo-rzenia tego programu Niekiedy zdarza się że najlepiej byłoby pisać program korzystając z roacuteżnychjęzykoacutew Język C może być z łatwością łączony z innymi językami programowania ktoacutere podlegająkompilacji bezpośrednio do kodu maszynowego (Asembler Fortran czy też C++) Ponadto dzięki spe-cjalnym bibliotekom można go łączyć z językami bardzo wysokiego poziomu (takimi jak np Pythonczy też Ruby) Ten rozdział ma za zadanie wytłumaczyć Ci w jaki sposoacuteb można mieszać roacuteżne językiprogramowania w jednym programie

251 Język C i Asembler

Informacje zawarte w tym rozdziale odnoszą się do komputeroacutew z procesorem i i pokrewnych

Łączenie języka C i języka asemblera jest dość powszechnym zjawiskiem Dzięki możliwości połączeniaobu tych językoacutew programowania można było utworzyć bibliotekę dla języka C ktoacutera niskopoziomowokomunikuje się z jądrem systemu operacyjnego komputera Ponieważ zaroacutewno asembler jak i C sąjęzykami tłumaczonymi do poziomu kodu maszynowego za ich łączenie odpowiada program zwanylinkerem (popularny ld) Ponadto niektoacuterzy producenci kompilatoroacutew umożliwiają stosowanie tzwwstawek asemblerowy ktoacutere umieszcza się bezpośrednio w kodzie programu napisanego w językuC Kompilator kompilując taki kod wstawi w miejsce tychże wstawek odpowiedni kod maszynowyktoacutery jest efektem przetłumaczenia kodu asemblera zawartegow takiej wstawce Opiszę tu oba sposobyłączenia obydwu językoacutew

2511 Łączenie na poziomie kodu maszynowegoW naszym przykładzie założymy że w pliku fS zawarty będzie kod napisany w asemblerze a fcto kod z programem w języku C Program w języku C będzie wykorzystywał jedną funkcję napisanąw języku asemblera ktoacutera wyświetli prosty napis ldquoHello worldrdquo Z powodu ograniczeń technicznychzakładamy że program uruchomiony zostanie w środowisku POSIX na platformie i i skompilowanykompilatorem gcc Używaną składnią asemblera będzie ATampT (domyślna dla asemblera ) Oto plikfS

text

globl _f1_f1

pushl ebpmovl esp ebp

177

178 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

movl $4 eax 4 to funkcja systemowa write movl $1 ebx 1 to stdout movl $tekst ecx adres naszego napisu movl $len edx długość napisu w bajtach int $0x80 wywołanie przerwania systemowego popl ebpret

datatekst

string Hello worldnlen = - tekst

W systemach z rodziny UNIX należy pominąć znak rdquo rdquoprzed nazwą funkcji f

Teraz kolej na fc

extern void f1 (void) musimy użyć słowa extern int main ()

f1()return 0

Teraz możemy skompilować oba programy

as f1S -o f1ogcc f2c -c -o f2ogcc f2o f1o -o program

W ten sposoacuteb uzyskujemy plik wykonywalny o nazwie ldquoprogramrdquo Efekt działania programu powinienbyć następujący

Hello world

Na razie utworzyliśmy bardzo prostą funkcję ktoacutera w zasadzie nie komunikuje się z językiem Cczyli nie zwraca żadnej wartości ani nie pobiera argumentoacutew Jednak aby zacząć pisać obsługę funk-cji ktoacutera będzie pobierała argumenty i zwracała wyniki musimy poznać działanie języka C od trochęniższego poziomu

Argumenty

Do komunikacji z funkcją język C korzysta ze stosu Argumenty odkładane sąw kolejności od ostatniegodo pierwszego Ponadto na końcu odkładany jest tzw adres powrotu dzięki czemu po wykonaniufunkcji program ldquowierdquo w ktoacuterym miejscu ma kontynuować działanie Ponadto początek funkcji wasemblerze wygląda tak

pushl ebpmovl esp ebp

Zatem na stosie znajdują się kolejno zawartość rejestru EBP adres powrotu a następnie argumenty odpierwszego do n-tego

251 JĘZYK C I ASEMBLER 179

Zwracanie wartości

Na architekturze i do zwracaniawynikoacutew pracy programu używa się rejestru EAX bądź jego ldquomniej-szychrdquo odpowiednikoacutew tj AX i AHAL Zatem aby funkcja napisana w asemblerze zwroacuteciła ldquordquo przedrozkazem ret należy napisać

movl $1 eax

Nazewnictwo

Kompilatory języka CC++ dodają podkreślnik ldquo rdquo na początku każdej nazwy Dla przykładu funkcja

void funkcja()

W pliku wyjściowym będzie posiadać nazwę funkcja Dlatego aby korzystać z poziomu języka C zfunkcji zakodowanych w asemblerze muszą one mieć przy definicji w pliku asemblera wspomnianydodatkowy podkreślnik na początku

Łączymy wszystko w całość

Pora abyśmy napisali jakąś funkcję ktoacutera pobierze argumenty i zwroacuteci jakiś konkretny wynik Otokod fS

text

globl _funkcja_funkcja

pushl ebpmovl esp ebpmovl 8(esp) eax kopiujemy pierwszy argument do eax addl 12(esp) eax do pierwszego argumentu w eax dodajemy drugi argument popl ebpret i zwracamy wynik dodawania

oraz fc

include ltstdiohgtextern int funkcja (int a int b)int main ()printf (2+3=dn funkcja(23))return 0

Po skompilowaniu i uruchomieniu programu powinniśmy otrzymać wydruk +=

2512 Wstawki asembleroweOproacutecz możliwości wstępnie skompilowanych modułoacutew możesz posłużyć się także tzw wstawkamiasemblerowymi Ich użycie powoduje wstawienie w miejsce wystąpienia wstawki odpowiedniegokodumaszynowego ktoacutery powstanie po przetłumaczeniu kodu asemblerowego Ponieważ jednakwstawkiasemblerowe nie są standardowym elementem języka C każdy kompilator ma całkowicie odmiennąfilozofię ich stosowania (lub nie ma ich w ogoacutele) Ponieważ w tym podręczniku używamy głoacutewniekompilatora więc w tym rozdziale zostanie omoacutewiona filozofia stosowania wstawek asemblerawedług programistoacutew

Ze wstawek asemblerowych korzysta się tak

180 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

int main ()

asm (nop)

W tym wypadku wstawiona zostanie instrukcja ldquonoprdquo (no operation) ktoacutera tak naprawdę służytylko i wyłącznie do konstruowania pętli opoacuteźniających

252 C++Język C++ z racji swojego podobieństwa do C będzie wyjątkowo łatwy do łączenia Pewnym utrudnie-niem może być obiektowość języka C++ oraz występowanie w nim przestrzeni nazw oraz możliwośćprzeciążania funkcji Oczywiście nadal zakładamy że głoacutewny program piszemy w C natomiast korzy-stamy tylko z pojedynczych funkcji napisanych w C++ Ponieważ język C nie oferuje tego wszystkiegoco daje programiście język C++ to musimy ldquozmusićrdquo C++ do wyłączenia pewnych swoich możliwościaby można było połączyć ze sobą elementy programu napisane w dwoacutech roacuteżnych językach Używa siędo tego następującej konstrukcji

extern C funkcje zmienne i wszystko to co będziemy łączyć z programem w C

W zrozumieniu teorii pomoże Ci prosty przykład plik fc

include ltstdiohgtextern int f2(int a)

int main ()

printf (dn f2(2))return 0

oraz plik fcpp

include ltiostreamgtusing namespace stdextern C

int f2 (int a)

cout ltlt a= ltlt a ltlt endlreturn a2

Teraz oba pliki kompilujemy

gcc f1c -c -o f1og++ f2cpp -c -o f2o

Przy łączeniu obu tych plikoacutew musimy pamiętać że język C++ także korzysta ze swojej bibliotekiZatem poprawna postać polecenia kompilacji powinna wyglądać

gcc f1o f2o -o program -lstdc++

(stdc++ mdash biblioteka standardowa języka C++) Bardzo istotne jest tutaj to abyśmy zawsze pamiętalio extern ldquoCrdquo gdyż w przeciwnym razie funkcje napisane w C++ będą dla programu w C całkowicieniewidoczne

Dodatek A

Indeks alfabetyczny

Alfabetyczny spis funkcji biblioteki standardowej ANSI C (tzw libc) w wersji C

A01 A abort()

abs()

acos()

asctime()

asin()

assert()

atan()

atan()

atexit()

atof()

atoi()

atol()

A02 B bsearch()

A03 C calloc()

ceil()

clearerr()

clock()

cos()

cosh()

ctime()

A04 D diime()

div()

A05 E errno (zmienna)

exit()

exp()

A06 F fabs()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

floor()

fmod()

fopen()

fprintf()

fputc()

fputs()

fread()

free()

freopen()

frexp()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

A07 G getc()

getchar()

getenv()

gets()

gmtime()

A08 I isalnum()

isalpha()

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

181

182 DODATEK A INDEKS ALFABETYCZNY

A09 L labs()

ldexp()

ldiv()

localeconv()

localtime()

log()

log()

longjmp()

A010 M malloc()

mblen()

mbstowcs()

mbtowc()

memchr()

memcmp()

memcpy()

memmove()

memset()

mktime()

modf()

A011 O offsetof()

A012 P perror()

pow()

printf()

putc()

putchar()

puts()

A013 Q qsort()

A014 R raise()

rand()

realloc()

remove()

rename()

rewind()

A015 S scanf()

setbuf()

setjmp()

setlocale()

setvbuf()

signal()

sin()

sinh()

sprintf()

sqrt()

srand()

sscanf()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strime()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtod()

strtok()

strtol()

strtoul()

strxfrm()

system()

A016 T tan()

tanh()

time()

tm (struktura)

tmpfile()

tmpnam()

tolower()

toupper()

A017 U ungetc()

A018 V va arg()

va end()

va start()

vfprintf()

vprintf()

vsprintf()

A019 W wcstombs()

wctomb()

Dodatek B

Indeks tematyczny

Spis plikoacutew nagłoacutewkowych oraz zawartych w nich funkcji i makr biblioteki standardowej C Funkcjemakra i typy wprowadzone dopiero w standardzie C zostały oznaczone poprzez ldquo[C]rdquo po nazwie

B1 asserthMakro asercji

assert()

B2 ctypehKlasyfikowanie znakoacutew

isalnum()

isalpha()

isblank() [C]

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

tolower()

toupper()

B3 errnohDeklaracje kodoacutew błędoacutew

EDOM (makro)

EILSEQ (makro) [C]

ERANGE (makro)

errno (zmienna)

B4 floathWłaściwości typoacutew zmiennoprzecinkowych zależne od implementacji

B5 limitshWłaściwości typoacutew całkowitych zależne od implementacji

183

184 DODATEK B INDEKS TEMATYCZNY

B6 localehUstawienia międzynarodowe

localeconv()

setlocale()

B7 mathhFunkcje matematyczne

FP FAST FMAF (makro) [C]

FP FAST FMAL (makro) [C]

FP FAST FMA (makro) [C]

FP ILOGB (makro) [C]

FP ILOGBNAN (makro) [C]

FP INFINITE (makro) [C]

FP NAN (makro) [C]

FP NORMAL (makro) [C]

FP SUBNORMAL (makro) [C]

FP ZERO (makro) [C]

HUGE VALF (makro) [C]

HUGE VALL (makro) [C]

HUGE VAL (makro)

INFINITY (makro) [C]

MATH ERREXCEPT (makro) [C]

MATH ERRNO (makro) [C]

NAN (makro) [C]

acosh()

acos()

asinh()

asin()

atan()

atanh()

atan()

cbrt() [C]

ceil()

copysign() [C]

cosh()

cos()

double t (typ) [C]

erfc() [C]

erf() [C]

exp() [C]

expm() [C]

exp()

fabs()

fdim() [C]

flaot t (typ) [C]

floor()

fmax() [C]

fma() [C]

fmin() [C]

fmod()

fpclassify() [C]

frexp()

hypot() [C]

ilogb() [C]

isfinite() [C]

isgreaterequal() [C]

isgreater() [C]

isinf() [C]

islessequal() [C]

islessgreater() [C]

isless() [C]

isnan() [C]

isnormal() [C]

isunordered() [C]

ldexp()

lgamma() [C]

llrint() [C]

llround() [C]

log()

logp() [C]

log() [C]

logb() [C]

log()

B8 SETJMPH 185

lrint() [C]

lround() [C]

math errhandling (makro) [C]

modf()

nan() [C]

nearbyint() [C]

nextaer() [C]

nexoward() [C]

pow()

remainder() [C]

remquo() [C]

rint() [C]

round() [C]

scalbln() [C]

scalbn() [C]

signbit() [C]

sinh()

sin()

sqrt()

tanh()

tan()

tgamma() [C]

trunc() [C]

B8 setjmphObsługa nielokalnych skokoacutew

longjmp()

setjmp()

B9 signalhObsługa sygnałoacutew

raise()

signal()

B10 stdarghNarzędzia dla funkcji ze zmienną liczbą argumentoacutew

va arg()

va end()

va start()

B11 stddefhStandardowe definicje

offsetof()

B12 stdiohStandard InputOutput czyli standardowe wejście-wyjście

clearerr()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

fopen()

186 DODATEK B INDEKS TEMATYCZNY

fprintf()

fputc()

fputs()

fread()

freopen()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

getc()

getchar()

gets()

perror()

printf()

putc()

putchar()

puts()

remove()

rename()

rewind()

scanf()

setbuf()

setvbuf()

sprintf()

sscanf()

tmpfile()

tmpnam()

ungetc()

vfprintf()

vprintf()

vsprintf()

B13 stdlibhNajbardziej podstawowe funkcje

abort()

abs()

atexit()

atof()

atoi()

atol()

bsearch()

calloc()

div()

exit()

free()

getenv()

labs()

ldiv()

malloc()

mblen()

mbstowcs()

mbtowc()

qsort()

rand()

realloc()

srand()

strtod()

strtol()

strtoul()

system()

wctomb()

wcstombs()

B14 stringhOperacje na łańcuchach znakoacutew

memchr()

memcmp()

memcpy()

memmove()

memset()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtok()

strxfrm()

strdup()

B15 timehFunkcje obsługi czasu

B15 TIMEH 187

asctime()

clock()

ctime()

diime()

gmtime()

localtime()

mktime()

strime()

time()

tm (struktura)

188 DODATEK B INDEKS TEMATYCZNY

Dodatek C

Wybrane funkcje bibliotekistandardowej

C1 assert

C11 Deklaracjadefine assert(expr)

C12 Plik nagłoacutewkowyasserth

C13 OpisMakro przypominające w użyciu funkcję służy do debuggowania programoacutew Gdy testowany waruneklogiczny expr przyjmuje wartość fałsz na standardowe wyjście błędoacutew wypisywany jest komunikat obłędzie (zawierające min argument wywołania makra nazwę funkcji w ktoacuterej zostało wywołanenazwę pliku źroacutedłowego oraz numer linii w formacie zależnym od implementacji) i program jest prze-rywany poprzez wywołanie funkcji abort

W ten sposoacuteb możemy oznaczyć w programie niezmienniki czyli warunki ktoacutere niezależnie odwartości zmiennych muszą pozostać prawdziwe Jeśli asercja zawiedzie oznacza to że popełniliśmybłąd w algorytmie piszemy sobie po pamięci (nadając zmiennym wartości ktoacuterych nigdy nie powinnymieć) albo nastąpiła po drodze sytuacja wyjątkowa na przykład związana z obsługą operacji wejścia-wyjścia

Można łatwo pozbyć się asercji uwalniając kod od spowalniających obciążeń a jednocześnie niemusząc kasować wystąpień assert i zachowując je na przyszłość Aby to zrobić należy przed dołą-czeniem pliku nagłoacutewkowego asserth zdefiniować makro NDEBUG woacutewczas makro assert przyjmujepostać

define assert(ignore) ((void)0)

Makro assert jest redefiniowane za każdym dołączeniem pliku nagłoacutewkowego asserth

C14 Wartość zwracanaMakro nie zwraca żadnej wartości

189

190 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C15 Przykładinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

Program wypisze komunikat podobny do

Assertion failed err==0 file testc line 6

Natomiast jeśli uruchomimy

define NDEBUGinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

nie pojawi się żaden komunikat o błędach

C2 atoi

C21 Deklaracjaint atoi (const char string)

C22 Plik nagłoacutewkowystdlibh

C23 OpisFunkcja jako argument pobiera liczbę w postaci ciągu znakoacutew ASCII a następnie zwraca jej wartość wformacie int Liczbę może poprzedzać dowolona ilość białych znakoacutew (spacje tabulatory itp) oraz jejznak (plus (+) lub minus (-)) Funkcja atoi() kończy wczytywać znaki w momencie napotkania jakiego-kowiek znaku ktoacutery nie jest cyfrą

C24 Wartość zwracanaW przypadku gdy ciąg nie zawiera cyfr zwracana jest wartość

C25 UwagiZnak musi bezpośrednio poprzedzać liczbę czyli możliwy jest zapis ldquo-rdquo natomiast proacuteba potraktowa-nia funkcją atoi ciągu ldquo- rdquo skutkuje zwracaną wartością

C3 ISALNUM 191

C26 Przykładinclude ltstdiohgtinclude ltstdlibhgtint main(void)

char c_Numer = nt 2004uint i_Numeri_Numer = atoi(c_Numer)printf(n Liczba typu int d oraz jako ciąg znakoacutew s n i_Numer c_Numer)return 0

C3 isalnum

C31 Deklaracjainclude ltctypehgt

int isalnum(int c)int isalpha(int c)int isblank(int c)int iscntrl(int c)int isdigit(int c)int isgraph(int c)int islower(int c)int isprint(int c)int ispuntc(int c)int isspace(int c)int isupper(int c)int isxdigit(int c)

C32 Argumentyc wartość znaku reprezentowana w jako typ unsigned char lub wartość makra EOF Z tego powodu

przed przekazaniem funkcji argumentu typu char lub signed char należy go zrzutować na typunsigned char lub unsigned int

C33 OpisFunkcje sprawdzają czy podany znak spełnia jakiś konkretny warunek Biorą pod uwagę ustawieniajęzyka i dla roacuteżnych znakoacutew w roacuteżnych localersquoach mogą zwracać roacuteżne wartości

isalnum sprawdza czy znak jest liczbą lub literą

isalpha sprawdza czy znak jest literą

isblank sprawdza czy znak jest znakiem odstępu służącym do oddzielania wyrazoacutew (standardowymiznakami odstępu są spacja i znak tabulacji)

iscntrl sprawdza czy znak jest znakiem sterującym

isdigit sprawdza czy znak jest cyfrą dziesiętna

isgraph sprawdza czy znak jest znakiem drukowalnym roacuteżnym od spacji

islower sprawdza czy znak jest małą literą

isprint sprawdza czy znak jest znakiem drukowalnym (włączając w to spację)

192 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

ispunct sprawdza czy znak jest znakiem przestankowym dla ktoacuterego ani isspace ani isalnum nie sąprawdziwe (standardowo są to wszystkie znaki drukowalne dla ktoacuterych te funkcje zwracajązero)

isspace sprawdza czy znak jest tzw białym znakiem (standardowymi białymi znakami są spacjawysunięcie strony rsquorsquo znak przejścia do nowej linii rsquonrsquo znak powrotu karetki rsquorrsquo tabulacjapozioma rsquotrsquo i tabulacja pionowa rsquovrsquo)

isupper sprawdza czy znak jest dużą literą

isxdigit sprawdza czy znak jest cyfrą szesnastkową tj cyfrą dziesiętną lub literą od rsquoarsquo do rsquorsquo niezależnieod wielkości

Funkcja isblank niewystępowaław oryginalnym standardzie ANSI C z roku (tzw C) i zostaładodana dopiero w nowszym standardzie z roku (tzw C)

C34 Wartość zwracanaLiczba niezerowa gdy podany argument spełnia konkretny warunek w przeciwnym wypadku mdash zero

C35 Przykład użyciainclude ltctypehgt funkcje is include ltlocalehgt setlocale include ltstdiohgt printf i scanf

void identify_char(int c) printf( Litera lub cyfra sn isalnum (c) tak nie)

if __STDC_VERSION__ gt= 199901Lprintf( Odstęp sn isblank (c) tak nie)

endifprintf( Znak sterujący sn iscntrl (c) tak nie)printf( Cyfra dziesiętna sn isdigit (c) tak nie)printf( Graficzny sn isgraph (c) tak nie)printf( Mała litera sn islower (c) tak nie)printf( Drukowalny sn isprint (c) tak nie)printf( Przestankowy sn ispunct (c) tak nie)printf( Biały znak sn isspace (c) tak nie)printf( Wielka litera sn isupper (c) tak nie)printf( Cyfra szesnastkowa sn isxdigit(c) tak nie)

int main() unsigned char cprintf(Naciśnij jakiś klawiszn)if (scanf(c ampc)==1)

identify_char(c)setlocale(LC_ALL pl_PL) przystosowanie do warunkoacutew polskich puts(Po zmianie ustawień języka)identify_char(c)

return 0

C36 Zobacz też tolower toupper

C4 MALLOC 193

C4 malloc

C41 Deklaracjainclude ltstdlibhgt

void calloc(size_t nmeb size_t size)void malloc(size_t size)void free(void ptr)void realloc(void ptr size_t size)

C42 Argumentynmeb liczba elementoacutew dla ktoacuterych ma być przydzielona pamięć

size rozmiar (w bajtach) pamięci do zarezerwowania bądź rozmiar pojedynczego elementu

ptr wskaźnik zwroacutecony przez poprzednie wywołanie jednej z funkcji lub

C43 OpisFunkcja calloc przydziela pamięć dla nmeb elementoacutew o rozmiarze size każdy i zeruje przydzielonąpamięć

Funkcja malloc przydziela pamięć o wielkości size bajtoacutewFunkcja free zwalnia blok pamięci wskazywany przez ptr wcześniej przydzielony przez jedną z

funkcji malloc calloc lub realloc Jeżeli ptr ma wartość funkcja nie robi nicFunkcja realloc zmienia rozmiar przydzielonego wcześniej bloku pamięci wskazywanego przez ptr

do size bajtoacutew Pierwsze n bajtoacutew bloku nie ulegnie zmianie gdzie n jest minimum z rozmiaru staregobloku i size Jeżeli ptr jest roacutewny zero (tj ) funkcja zachowuje się tak samo jako malloc

C44 Wartość zwracanaJeżeli przydzielanie pamięci się powiodło funkcje calloc malloc i realloc zwracają wskaźnik do nowoprzydzielonego bloku pamięci W przypadku funkcji realloc może to być wartość inna niż ptr

Jeśli jako size nmeb podano zero zwracany jest albo wskaźnik albo prawidłowy wskaźnikktoacutery można podać do funkcji free (zauważmy że jest też prawidłowym argumentem free)

Jeśli działanie funkcji nie powiedzie się zwracany jest i odpowiedni kod błędu jest wpisywanydo zmiennej errno Dzieje się tak zazwyczaj gdy nie ma wystarczająco dużo miejsca w pamięci

C45 Przykładinclude ltstdiohgtinclude ltstdlibhgt

int main(void)

size_t size num = 0float tab tmp

Przydzielenie początkowego bloku pamięci size = 64tab = malloc(size sizeof tab)if (tab)

perror(malloc)return EXIT_FAILURE

194 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Odczyt liczb while (scanf(f amptmp)==1)

Jeżeli zapełniono całą tablicę trzeba ją zwiększyć if (num==size)float ptr = realloc(tab (size = 2) sizeof ptr)if (ptr)

free(tab)perror(realloc)return EXIT_FAILURE

tab = ptr

tab[num++] = tmp

Wypisanie w odwrotnej kolejnosci while (num)

printf(fn tab[--num])

Zwolnienie pamieci i zakonczenie programu free(tab)return EXIT_SUCCESS

C46 Uwagi

Użycie rzutowania przy wywołaniach funkcji malloc realloc oraz calloc w języku C jest zbędne i szko-dliwe W przypadku braku deklaracji tych funkcji (np gdy programista zapomni dodać plik nagłoacutew-kowy stdlibh) kompilator przyjmuje domyślną deklaracje w ktoacuterej funkcja zwraca int Przy brakurzutowania spowoduje to błąd kompilacji (z powodu niemożności skonwertowania liczby na wskaźnik)co pozwoli na szybkie wychwycenie błędu w programie Rzutowanie powoduje że kompilator zostajezmuszony do przeprowadzenia konwersji typoacutew i nie wyświetla żadnych błędoacutew W przypadku językaC++ rzutowanie jest konieczne

Zastosowanie operatora sizeof z wyrażeniem (np sizeof tablica) a nie typem (np sizeof float)ułatwia poacuteźniejszą modyfikację programoacutew Gdyby w pewnym momencie programista zdecydował sięzmienić tablicę z tablicy floatoacutew na tablice doublersquoi musiałby wyszukiwać wszystkie wywołania funkcjimalloc realloc i calloc co nie jest konieczne przy użyciu operatora sizeof z wyrażeniem

Ponieważ dla parametru size roacutewnego zero funkcja może zwroacutecić albo wskaźnik roacuteżny od wartości albo jej roacutewny zwykłe sprawdzanie poprawności wywołania poprzez przyroacutewnanie zwroacuteconejwartości do zera może nie dać prawidłowego wyniku

C47 Zobacz też

Wskaźniki (dokładne omoacutewienie zastosowania)

C5 PRINTF 195

C5 printf

C51 Deklaracjainclude ltstdiohgt

int printf(const char format )int fprintf(FILE stream const char format )int sprintf(char str const char format )int snprintf(char str size_t size const char format )

include ltstdarghgt

int vprintf(const char format va_list ap)int vfprintf(FILE stream const char format va_list ap)int vsprintf(char str const char format va_list ap)int vsnprintf(char str size_t size const char format va_list ap)

C52 OpisFunkcje formatują tekst zgodnie z podanym formatem opisanym poniżej Funkcje printf i vprintf wy-pisują tekst na standardowe wyjście (tj do stdout) fprintf i vfprintf do strumienia podanego jakoargument a sprintf vsprintf snprintf i vsnprintf zapisują go w podanej jako argument tablicy znakoacutew

Funkcje vprintf vfprintf vsprintf i vsnprintf roacuteżnią się od odpowiadających im funkcjom printffprintf sprintf i snprintf tym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

Funkcje snprintf i vsnprintf roacuteżnią się od sprintf i vsprintf tym że nie zapisuje do tablicy nie wię-cej niż size znakoacutew (wliczając kończący znak rsquorsquo) Oznacza to że można je używać bez obawy owystąpienie przepełnienia bufora

C53 Argumentyformat format w jakim zostaną wypisane następne argumenty

stream strumień wyjściowy do ktoacuterego mają być zapisane dane

str tablica znakoacutew do ktoacuterej ma być zapisany sformatowany tekst

size rozmiar tablicy znakoacutew

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

C54 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) ktoacutere są kopiowane bez zmian na wyjścieoraz sekwencji sterujących zaczynających się od symbolu procenta po ktoacuterym następuje

dowolna liczba flag

opcjonalne określenie minimalnej szerokości pola

opcjonalne określenie precyzji

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

196 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Flagi

W sekwencji możliwe są następujące flagi

- (minus) oznacza że pole ma być wyroacutewnane do lewej a nie do prawej

+ (plus) oznacza że dane liczbowe zawsze poprzedzone są znakiem (plusem dla liczb nieujem-nych lub minusem dla ujemnych)

spacja oznacza że liczby nieujemne poprzedzone są dodatkową spacją jeżeli flaga plus i spacjasą użyte jednocześnie to spacja jest ignorowana

(hash) powoduje że wynik jest przedstawiony w alternatywnej postaci

ndash dla formatu o powoduje to zwiększenie precyzji jeżeli jest to konieczne aby na początkuwyniku było zero

ndash dla formatoacutew x i X niezerowa liczba poprzedzona jest ciągiem x lub X

ndash dla formatoacutew a A e E f F g i G wynik zawsze zawiera kropkę nawet jeżeli nie ma za niążadnych cyfr

ndash dla formatoacutew g i G końcowe zera nie są usuwane

(zero) dla formatoacutew d i o u xX aA e E f F g iG do wyroacutewnania pola wykorzystywane sązera zamiast spacji za wyjątkiem wypisywania wartości nieskończoność i NaN Jeżeli obie flagi i mdash są obecne to flaga zero jest ignorowana Dla formatoacutew d i o u x i X jeżeli określona jestprecyzja flaga ta jest ignorowana

Szerokość pola i precyzja

Minimalna szerokość pola oznacza ile najmniej znakoacutew ma zająć dane pole Jeżeli wartość po formato-waniu zajmuje mniej miejsca jest ona wyroacutewnywana spacjami z lewej strony (chyba że podano flagiktoacutere modyfikują to zachowanie) Domyślna wartość tego pola to

Precyzja dla formatoacutew

d i o u x iX określa minimalną liczbę cyfr ktoacutere mają być wyświetlone i ma domyślną wartość

a A e E f i F mdash liczbę cyfr ktoacutere mają być wyświetlone po kropce i ma domyślną wartość

g i G określa liczbę cyfr znaczących i ma domyślną wartość

dla formatu s mdash maksymalną liczbę znakoacutew ktoacutere mają być wypisane

Szerokość pola może być albo dodatnią liczbą zaczynającą się od cyfry roacuteżnej od zera albo gwiazdkąPodobnie precyzja z tą roacuteżnicą że jest jeszcze poprzedzona kropką Gwiazdka oznacza że brany jestkolejny z argumentoacutew ktoacutery musi być typu int Wartość ujemna przy określeniu szerokości jest trak-towana tak jakby podano flagę - (minus)

Rozmiar argumentu

Dla formatoacutew d i i można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu signed char

h mdash oznacza że format odnosi się do argumentu typu short

l (el) mdash oznacza że format odnosi się do argumentu typu long

ll (el el) mdash oznacza że format odnosi się do argumentu typu long long

j mdash oznacza że format odnosi się do argumentu typu intmax t

z mdash oznacza że że format odnosi się do argumentu typu będącego odpowiednikiem typu size tze znakiem

t mdash oznacza że że format odnosi się do argumentu typu ptrdiff t

C5 PRINTF 197

Dla formatoacutew o u x i X można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d ioznaczają one że format odnosi się do argumentu odpowiedniego typu bez znaku

Dla formatu n można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d i oznaczająone że format odnosi się do argumentu będącego wskaźnikiem na dany typ

Dla formatoacutew a A e E f F g iGmożna użyć modyfikatoroacutew rozmiaru L ktoacutery oznacza że formatodnosi się do argumentu typu long double

Dodatkowo modyfikator l (el) dla formatu c oznacza że odnosi się on do argumentu typu wint ta dla formatu s że odnosi się on do argumentu typu wskaźnik na wchar t

Format

Funkcje z rodziny printf obsługują następujące formaty

d i mdash argument typu int jest przedstawiany jako liczba całkowita ze znakiem w postaci [-]ddd

o u x X mdash argument typu unsigned int jest przedstawiany jako nieujemna liczba całkowitazapisana w systemie oktalnym (o) dziesiętnym (u) lub heksadecymalnym (x i X)

f F mdash argument typu double jest przedstawiany w postaci [-]dddddd

e E mdash argument typu double jest reprezentowany w postaci [i]dddde+dd gdzie liczba przedkropką dziesiętną jest roacuteżna od zera jeżeli liczba jest roacuteżna od zera a + oznacza znak wykładnikaFormat E używa wielkiej litery E zamiast małej

g G mdash argument typu double jest reprezentowany w formacie takim jak f lub e (odpowiednio Flub E) zależnie od liczby znaczących cyfr w liczbie oraz określonej precyzji

a A mdash argument typu double przedstawiany jest w formacie [-]xhhhhp+d czyli analogiczniejak dla e i E tyle że liczba zapisana jest w systemie heksadecymalnym

c mdash argument typu int jest konwertowany do unsigned char i wynikowy znak jest wypisywanyJeżeli podanomodyfikator rozmiaru l argument typuwint t konwertowany jest dowielobajtowejsekwencji i wypisywany

s mdash argument powinien być typu wskaźnik na char (lub wchar t) Wszystkie znaki z podanejtablicy aż do i z wyłączeniem znaku null są wypisywane

pmdashargument powinien być typuwskaźnik na void Jest to konwertowany na serię drukowalnychznakoacutew w sposoacuteb zależny od implementacji

n mdash argument powinien być wskaźnikiem na liczbę całkowitą ze znakiem do ktoacuterego zapisanajest liczba zapisanych znakoacutew

W przypadku formatoacutew f F e E gG a iAwartość nieskończoność jest przedstawiana w formacie[-]inf lub [-]infinity zależnie od implementacji Wartość NaN jest przedstawiana w postaci [-]nan lub[i]nan(sekwencja) gdzie sekwencja jest zależna od implementacji W przypadku formatoacutew określo-nych wielką literą roacutewnież wynikowy ciąg znakoacutew jest wypisywany wielką literą

C55 Wartość zwracana

Jeżeli funkcje zakończą się sukcesem zwracają liczbę znakoacutew w tekście (wypisanym na standardowewyjście do podanego strumienia lub tablicy znakoacutew) nie wliczając kończącego rsquorsquo W przeciwnymwypadku zwracana jest liczba ujemna

Wyjątkami są funkcje snprintf i vsnprintf ktoacutere zwracają liczbę znakoacutew ktoacutere zostałyby zapisanedo tablicy znakoacutew gdyby była wystarczająco duża

198 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C56 Przykład użyciainclude ltstdiohgtint main()

int i = 4float f = 31415char s = Monty Pythonprintf(i = inf = 1fnWskaźnik s wskazuje na napis sn i f s)return 0

Wyświetli

i = 4f = 31Wskaźnik s wskazuje na napis Monty Python

Funkcja formatująca ciąg znakoacutew i alokująca odpowiednią ilość pamięci

include ltstdarghgtinclude ltstdlibhgt

char sprintfalloc(const char format ) int retsize_t size = 100char str = malloc(size)if (str)

return 0

for()va_list apchar tmp

va_start(ap format)ret = vsnprintf(str size format ap)va_end(ap)

if (retltsize) break

tmp = realloc(str (size_t)ret + 1)if (tmp) ret = -1break

else str = tmpsize = (size_t)ret + 1

if (retlt0) free(str)str = 0

C6 SCANF 199

else if (size-1gtret) char tmp = realloc(str (size_t)ret + 1)if (tmp) str = tmp

return str

C57 Uwagi

Funkcje snprintf i vsnprintf nie były zdefiniowane w standardzie C Zostały one dodane dopiero wstandardzie C

Biblioteka glibc do wersji włącznie posiadała implementacje funkcji snprintf oraz vsnprintfktoacutere były niezgodne ze standardem gdyż zwracały - w przypadku gdy wynikowy tekst nie mieściłsię w podanej tablicy znakoacutew

C6 scanf

C61 Deklaracja

W pliku nagłoacutewkowym stdioh

int scanf(const char format )int fscanf(FILE stream const char format )int sscanf(const char str const char format )

W pliku nagłoacutewkowym stdargh

int vscanf(const char format va_list ap)int vsscanf(const char str const char format va_list ap)int vfscanf(FILE stream const char format va_list ap)

C62 Opis

Funkcje odczytują dane zgodnie z podanym formatem opisanym niżej Funkcje scanf i vscanf odczytujądane ze standardowego wejścia (tj stdin) fscanf i vfscanf ze strumienia podanego jako argument asscanf i vsscanf z podanego ciągu znakoacutew

Funkcje vscanf vfscanf i vsscanf roacuteżnią się od odpowiadających im funkcjom scanf fscanf i sscanftym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

C63 Argumenty

format format odczytu danych

stream strumień wejściowy z ktoacuterego mają być odczytane dane

str tablica znakoacutew z ktoacuterej mają być odczytane dane

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

200 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C64 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) oraz sekwencji sterujących zaczynającychsię od symbolu procenta po ktoacuterym następuje

opcjonalna gwiazdka

opcjonalne maksymalna szerokość pola

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

Wystąpienie w formacie białego znaku powoduje że funkcje z rodziny scanf będą odczytywać iodrzucać znaki aż do napotkania pierwszego znaku nie będącego białym znakiem

Wszystkie inne znaki (tj nie białe znaki oraz nie sekwencje sterujące) muszą dokładnie pasowaćdo danych wejściowych

Wszystkie białe znaki z wejścia są ignorowane chyba że sekwencja sterująca określa format [ c lubn

Jeżeli w sekwencji sterującej występuje gwiazdka to dane z wejścia zostaną pobrane zgodnie zformatem ale wynik konwersji nie zostanie nigdzie zapisany W ten sposoacuteb można pomijać częśćdanych

Maksymalna szerokość pola przyjmuje postać dodatniej liczby całkowitej zaczynającej się od cyfryroacuteżnej od zera Określa ona ile maksymalnie znakoacutew dany format może odczytać Jest to szczegoacutelnieprzydatne przy odczytywaniu ciągu znakoacutew gdyż dzięki temu można podać wielkość tablicy (minusjeden) i tym samym uniknąć błędoacutew przepełnienia bufora

Rozmiar argumentu

Dla formatoacutew d i o u x i n można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu wskaźnik na signed char lub unsignedchar

h mdash oznacza że format odnosi się do argumentu typu wskaźnik na short lub wskaźnik na unsi-gned short

l (el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long lub wskaźnik naunsigned long

ll (el el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long long lub wskaźnikna unsigned long long

j mdash oznacza że format odnosi się do argumentu typu wskaźnik na intmax t lub wskaźnik nauintmax t

z mdash oznacza że że format odnosi się do argumentu typu wskaźnik na size t lub odpowiedni typze znakiem

t mdash oznacza że że format odnosi się do argumentu typu wskaźnik na ptrdiff t lub odpowiednityp bez znaku

Dla formatoacutew a e f i g można użyć modyfikatoroacutew rozmiaru

l ktoacutery oznacza że format odnosi się do argumenty typu wskaźnik na double lub

L ktoacutery oznacza że format odnosi się do argumentu typu wskaźnik na long double

Dla formatoacutew c s i [ modyfikator l oznacza że format odnosi się do argumentu typu wskaźnik nawchar t

C6 SCANF 201

Format

Funkcje z rodziny scanf obsługują następujące formaty

d i odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przywywołaniufunkcji strtol z argumentem base roacutewnym odpowiednio dla d lub dla i argument powinienbyć wskaźnikiem na int

o u x odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przy wy-wołaniu funkcji strtoul z argumentem base roacutewnym odpowiednio dla o dla u lub dla xargument powinien być wskaźnikiem na unsigned int

a e f g odczytuje liczbę rzeczywistą nieskończoność lub NaN ktoacuterych format jest taki sam jakoczekiwany przy wywołaniu funkcji strtod argument powinien być wskaźnikiem na float

c odczytuje dokładnie tyle znakoacutew ile określono w maksymalnym rozmiarze pola (domyślnie )argument powinien być wskaźnikiem na char

s odczytuje sekwencje znakoacutew nie będących białymi znakami argument powinien być wskaźni-kiem na char

[ odczytuje niepusty ciąg znakoacutew z ktoacuterych każdymusi należeć do określonego zbioru argumentpowinien być wskaźnikiem na char

p odczytuje sekwencje znakoacutew zależną od implementacji odpowiadającą ciągowi wypisywa-nemu przez funkcję printf gdy podano sekwencję p argument powinien być typu wskaźnikna wskaźnik na void

n nie odczytuje żadnych znakoacutew ale zamiast tego zapisuje do podanej zmiennej liczbę odczyta-nych do tej pory znakoacutew argument powinien być typu wskaźnik na int

Słoacutewko więcej o formacie [ Po otwierającym nawiasie następuje ciąg określający znaki jakie mogąwystępować w odczytanym napisie i kończy się on nawiasem zamykającym tj ] Znaki pomiędzynawiasami (tzw scanlist) określają możliwe znaki chyba że pierwszym znakiem jest ˆ mdash woacutewczasw odczytanym ciągu znakoacutew mogą występować znaki nie występujące w scanlist Jeżeli sekwencjazaczyna się od [] lub [ˆ] to ten pierwszy nawias zamykający nie jest traktowany jako koniec sekwencjitylko jak zwykły znak Jeżeli wewnątrz sekwencji występuje znak - (minus) ktoacutery nie jest pierwszymlub drugim jeżeli pierwszym jest ˆ ani ostatnim znakiem zachowanie jest zależne od implementacji

Formaty A E F G i X są roacutewnież dopuszczalne i mają takie same działanie jak a e f g i x

C65 Wartość zwracanaFunkcja zwraca EOF jeżeli nastąpi koniec danych lub błąd odczytu zanim jakiekolwiek konwersje zo-staną dokonane lub liczbę poprawnie wczytanych poacutel (ktoacutera może być roacutewna zero)

202 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Dodatek D

Składnia

D1 Symbole i słowa kluczoweJęzyk C definiuje pewną ilość słoacutew za pomocą ktoacuterych tworzy się np pętle itp Są to tzw słowakluczowe tzn nie można użyć ich jako nazwy zmiennej czy też stałej (o nich poniżej) Oto lista słoacutewkluczowych języka C (według norm ANSI C z roku oraz ISO C z roku )

203

204 DODATEK D SKŁADNIA

Tablica D1 Symbole i słowa kluczoweSłowo Opis w tym podręcznikuauto Zmiennebreak Instrukcje sterującecase Instrukcje sterującear Zmienneconst Zmienne

continue Instrukcje sterującedefault Instrukcje sterujące

do Instrukcje sterującedouble Zmienneelse Instrukcje sterująceenum Typy złożoneextern Bibliotekifloat Zmiennefor Instrukcje sterującegoto Instrukcje sterująceif Instrukcje sterująceint Zmiennelong Zmienne

register Zmiennereturn Procedury i funkcjeshort Zmiennesigned Zmiennesizeof Zmiennestatic Biblioteki Zmiennestruct Typy złożoneswit Instrukcje sterującetypedef Typy złożoneunion Typy złożone

unsigned Zmiennevoid Wskaźniki

volatile Zmiennewhile Instrukcje sterujące

D2 POLSKIE ZNAKI 205

Specyfikacja ISO C z roku dodaje następujące słowa

Bool

Complex

Imaginary

inline

restrict

D2 Polskie znakiPisząc program możemy stosować polskie litery (tj ldquoąćęłńoacuteśźżrdquo) tylko w

komentarzach

ciągach znakoacutew (łańcuchach)

Niedopuszczalne jest stosowanie polskich znakoacutew w innych miejscach

D3 Operatory

D31 Operatory arytmetyczneSą to operatory wykonujące znane wszystkim dodawanie odejmowanie itp

operator znaczenie+ dodawanie- odejmowanie mnożenie dzielenie dzielenie modulo mdash daje w wyniku samą resztę z dzielenia= operator przypisania mdash wykonuje działanie po prawej stronie i wynik

przypisuje obiektowi po lewej

D32 Operatory logiczneSłużą poroacutewnaniu zawartości dwoacutech zmiennych według określonych kryterioacutew

Operator Rodzaj poroacutewnania== czy roacutewnegt większygt= większy bądź roacutewnylt mniejszylt= mniejszy bądź roacutewny= czy roacuteżny(nieroacutewny)

Są jeszcze operatory służące do grupowania poroacutewnań (patrz też logika w Wikipedii)

|| lub(OR)ampamp ioraz(AND) negacja(NOT)

206 DODATEK D SKŁADNIA

D33 Operatory binarne

Są to operatory ktoacutere działają na bitach

operator funkcja przykład| suma bitowa(OR) 5 | 2 da w wyniku 7 ( 00000101 OR 00000010 =

00000111)amp iloczyn bitowy 7 amp 2 da w wyniku 2 ( 00000111 AND 00000010

= 00000010)~ negacja bitowa 2 da wwyniku 253 (NOT 00000010 = 11111101

)gtgt przesunięcie bitoacutew o X w prawo 7 gtgt 2 da w wyniku 1 ( 00000111 gtgt 2 =

00000001)ltlt przesunięcie bitoacutew o X w lewo 7 ltlt 2 da w wyniku 28 ( 00000111 ltlt 2 =

00011100)^ alternatywa wyłączna 7 ˆ 2 da w wyniku 5 ( 00000111 ˆ 00000010 =

00000101)

D34 Operatory inkrementacjidekrementacji

Służą do dodawaniaodejmowania od liczby wartości jeden

Przykłady

Operacja Opis operacji Wartość wyrażeniax++ zwiększy wartość w x o jeden wartość zmiennej x przed zmianą++x zwiększy wartość w x o jeden wartość zmiennej x powiększona o jedenxndash zmniejszy wartość w x o jeden wartość zmiennej x przed zmianąndashx zmniejszy wartość w x o jeden wartość zmiennej x pomniejszona o jeden

Parę przykładoacutew dla zrozumienia

int a=7if ((a++)==7) najpierw poroacutewnuje potem dodaje

printf (dna) wypisze 8 if ((++a)==9) najpierw dodaje potem poroacutewnuje

printf (dn a) wypisze 9

Analogicznie ma się sytuacja z operatorami dekrementacji

D4 TYPY DANYCH 207

D35 PozostałeOperacja Opis operacji Wartość wyrażenia

x operator wyłuskania dla wskaźnika wartość trzymana w pamięci pod adre-sem przechowywanym we wskaźniku

ampx operator pobrania adresu zwraca adres zmiennejx[a] operator wybrania elementu tablicy zwraca element tablicy o indeksie a

(numerowanym od zera)xa operator wyboru składnika a ze zmien-

nej xwybiera składnik ze struktury lub unii

x-gta operator wyboru składnika a przezwskaźnik do zmiennej x

wybiera składnik ze struktury gdy uży-wamy wskaźnika do struktury zamiastzwykłej zmiennej

sizeof (typ) operator pobrania rozmiaru typu zwraca rozmiar typu w bajtachsizeof wyrażenie operator pobrania rozmiaru typu zwraca rozmiar typu rezultatu wyraże-

nia

D36 Operator ternarnyIstnieje jeden operator przyjmujący trzy argumenty mdash jest to operator wyrażenia warunkowego a b c Zwraca on b gdy a jest prawdą lub c w przeciwnym wypadku

D4 Typy dany

Tablica D Typy danych według roacuteżnych specyfikacji języka C

Typ Opis Inne nazwyTypy dany wg norm C i C

ar Służy głoacutewnie do przechowywania znakoacutew Od kom-pilatora zależy czy jest to liczba ze znakiem czy bez wwiększości kompilatoroacutew jest liczbą ze znakiem

signed ar Typ char ze znakiemunsigned ar Typ char bez znakushort Występuje gdy docelowa maszyna wyszczegoacutelnia

kroacutetki typ danych całkowitych w przeciwnym wy-padku jest tożsamy z typem int Często ma rozmiarjednego słowa maszynowego

short int signed shortsigned short int

unsigned short Liczba typu short bez znaku Podobnie jak short uży-wana do zredukowania zużycia pamięci przez program

unsigned short int

int Liczba całkowita odpowiadająca podstawowemu roz-miarowi liczby całkowitej w danym komputerze Pod-stawowy typ dla liczb całkowitych

signed int signed

unsigned Liczba całkowita bez znaku unsigned intlong Długa liczba całkowita long int signed long

signed long intunsigned long Długa liczba całkowita bez znaku unsigned long intfloat Podstawowy typ do przechowywania liczb zmienno-

przecinkowych W nowszym standardzie zgodny jest znormą IEEE Nie można stosować go z modyfika-torem signed ani unsigned

double Liczba zmiennoprzecinkowa podwoacutejnej precyzji Po-dobnie jak float nie łączy się z modyfikatorem signedani unsigned

208 DODATEK D SKŁADNIA

long double Największa możliwa dokładność liczb zmiennoprzecin-kowych Nie łączy się z modyfikatorem signed aniunsigned

Typy dany według normy CBool Przechowuje wartości lub long long Nowy typ umożliwiający obliczeniach na bardzo du-

żych liczbach całkowitych bez użycia typu floatlong long int signedlong long signed longlong int

unsigned long long Długie liczby całkowite bez znaku unsigned long long intfloat Complex Słuzy do przechowywania liczb zespolonychdouble Complex Słuzy do przechowywania liczb zespolonychlong double Complex Słuzy do przechowywania liczb zespolonych

Typy dany definiowane przez użytkownikastruct Więcej o kompilowaniuunion Rozmiar typu jest taki jak rozmiar największego polatypedef Nowo zdefiniowany typ przyjmuje taki sam rozmiar

jak typ macierzystyenum Zwykle elementy mają taką samą długość jak typ int

Zależności rozmiaru typoacutew danych są następujące

sizeof(cokolwiek) = sizeof(signed cokolwiek) = sizeof(unsigned cokolwiek)

= sizeof(ar) le sizeof(short) le sizeof(int) le sizeof(long) le sizeof(long long)

sizeof(float) le sizeof(double) le sizeof(long double)

sizeof(cokolwiek Complex) = sizeof(cokolwiek)

sizeof(void ) = sizeof(ar ) ge sizeof(cokolwiek )

sizeof(cokolwiek ) = sizeof(signed cokolwiek ) = sizeof(unsigned cokolwiek )

sizeof(cokolwiek ) = sizeof(const cokolwiek )

Dodatkowo jeżeli przez V(typ) oznaczymy liczbę bitoacutew wykorzystywanych w typie to zachodzi

le V(ar) = V(signed ar) = V(unsigned ar)

le V(short) = V(unsigned short)

le V(int) = V(unsigned int)

le V(long) = V(unsigned long)

le V(long long) = V(unsigned long long)

V(ar) le V(short) le V(int) le V(long) le V(long long)

Dodatek E

Przykłady z komentarzem

E01 Liczby losowePoniższy program generuje wiersz po wierszu macierz o określonych przez użytkownika wymiarachzawierającą losowo wybrane liczby Każdy wygenerowany wiersz macierzy zapisywany jest w plikutekstowym o wprowadzonej przez użytkownika nazwie W pierwszym wierszu pliku wynikowego za-pisano wymiary utworzonej macierzy Program napisany i skompilowany został w środowisku GNU-Linux

include ltstdiohgtinclude ltstdlibhgt dla funkcji rand() oraz srand() include lttimehgt dla funkcji [time()

main()

int i j n mfloat reFILE fpchar fileName[128]

printf(Wprowadz nazwe pliku wynikowegon)scanf(sampfileName)

printf(Wprowadz po sobie liczbe wierszy i kolumn macierzy oddzielone spacjąn)scanf(d d ampn ampm)

jeżeli wystąpił błąd w otwieraniu pliku i go nie otwartowoacutewczas funkcja fclose(fp) wywołana na końcu programu zgłosi błądwykonania i wysypie nam program z działania stąd musimy umieścićwarunek ktoacutery w kontrolowany sposoacuteb zatrzyma program (funkcja exit)

if ( (fp = fopen(fileName w)) == NULL )

puts(Otwarcie pliku nie jest mozliwe)exit jeśli w procedurze glownej

to piszemy bez nawiasow

else puts(Plik otwarty prawidłowo)

209

210 DODATEK E PRZYKŁADY Z KOMENTARZEM

fprintf(fp d dn n m) w pierwszym wierszu umieszczono wymiary macierzy

srand( (unsigned int) time(0) )for (i=1 ilt=n ++i)

for (j=1 jlt=m ++j)re = ((rand() 200)-100) 100fprintf(fp1f re )if (j=m) fprintf(fp )

fprintf(fpn)fclose(fp)return 0

E02 Zamiana liczb dziesiętny na liczby w systemie dwoacutejkowym

Zajmijmy się teraz innym zagadnieniem Wiemy że komputer zapisuje wszystkie liczby w postacibinarnej (czyli za pomocą jedynek i zer) Sproacutebujmy zatem zamienić liczbę zapisaną w ldquonaszymrdquo dzie-siątkowym systemie na zapis binarny Uwaga Program działa jedynie dla liczb od do maksymalnejwartości ktoacuterą może przyjąć typ unsigned short int w twoim kompilatorze

include ltstdiohgtinclude ltlimitshgt

void dectobin (unsigned short a)

int licznik

CHAR_BIT to liczba bitoacutew w bajcie licznik = CHAR_BIT sizeof(a)while (--licznik gt= 0)

putchar(((a gtgt licznik) amp 1)) 1 0)

int main ()

unsigned short a

printf (Podaj liczbę od 0 do hd USHRT_MAX)scanf (hd ampa)printf (hd(10) = a)dectobin(a)printf ((2)n)

return 0

211

E03 Zalążek przeglądarki

Zajmiemy się tym razem inną kwestią a mianowicie programowaniem sieci Jest to zagadnienie bar-dzo ostatnio popularne Nasz program będzie miał za zadanie połączyć się z serwerem ktoacuterego adresużytkownik będzie podawał jako pierwszy parametr programu wysłać zapytanie HTTP i odebrać treśćktoacuterą wyśle do nas serwer Zacznijmy może od tego że obsługa sieci jest niemal identyczna w roacuteżnychsystemach operacyjnych Na przykład między systemami z rodziny Unix oraz Windowsem roacuteżnica po-lega tylko na dołączeniu innych plikoacutew nagłoacutewkowych (dla Windowsa mdash winsockh) Przeanalizujmyzatem poniższy kod

include ltstdiohgtinclude ltstdlibhgtinclude ltstringhgtinclude ltunistdhgtinclude ltarpainethgtinclude ltsystypeshgtinclude ltnetinetinhgtinclude ltsyssockethgt

define MAXRCVLEN 512define PORTNUM 80

char query = GET HTTP11nn

int main(int argc char argv[])

char buffer[MAXRCVLEN+1]int len mysocketstruct sockaddr_in destchar host_ip = NULLif (argc = 2)

printf (Podaj adres serweran)exit (1)

host_ip = argv[1]mysocket = socket(AF_INET SOCK_STREAM 0)

destsin_family = AF_INETdestsin_addrs_addr = inet_addr(host_ip) ustawiamy adres hosta destsin_port = htons (PORTNUM) numer portu przechowuje dwubajtowa zmienna -

musimy ustalić porządek sieciowy - Big Endian memset(amp(destsin_zero) 0 8) zerowanie reszty struktury

connect(mysocket (struct sockaddr )ampdestsizeof(struct sockaddr)) łączymy się z hostem write (mysocket query strlen(query)) wysyłamy zapytanie len=read(mysocket buffer MAXRCVLEN) i pobieramy odpowiedź

buffer[len]=0

printf(Rcvd sbuffer)close(mysocket) zamykamy gniazdo return EXIT_SUCCESS

212 DODATEK E PRZYKŁADY Z KOMENTARZEM

Powyższy przykład może być odrobinę niezrozumiały dlatego przyda się kilka słoacutew wyjaśnieniaPliki nagłoacutewkowe ktoacutere dołączamy zawierają deklarację nowych dla Ciebie funkcji mdash socket() con-nect() write() oraz read() Oproacutecz tego spotkałeś się z nową strukturą mdash sockaddr in Wszystkie teobiekty są niezbędne do stworzenia połączenia

Dodatek F

Informacje o pliku i historia

F1 HistoriaTa książka została stworzona na polskojęzycznej wersji projektu Wikibooks przez autoroacutew wymie-nionych poniżej w sekcji Autorzy Najnowsza wersja podręcznika jest dostępna pod adresem httpplwikibooksorgwikiC

F2 Informacje o pliku i historia został utworzony przez Derbetha dnia listopada na podstawie wersji z listopada podręcznika na Wikibooks Wykorzystany został poprawiony program WikiLaTeX autorstwa użyt-kownika angielskichWikibooks Hagindaza Wynikowy kod po ręcznych poprawkach został przekształ-cony w książkę za pomocą systemu składu XeLaTeX Wykorzystano wolną dostępną na licencjach i czcionkę Linux Libertine oraz wolną czcionkę DejaVu Sans Mono

Najnowsza wersja tego -u jest postępna pod adresem httpplwikibooksorgwikiImageCpdf

F3 AutorzyAdam majewski Adiblol Akira Albmont Ananas Arfrever BartekChom Bercik Bla Bociex CathyRichards Cnr CzarnyInaczej CzarnyZajaczek DaniXTeam Derbeth Equadus Faw GDR GangGk Gynvael Incuś Karol Ossowski Kazet Kj Lethern MTM Marcin MastiBot MeaglinMerdis Michael Migol Mina MonteChristof Mt Myki Mythov Narf Noisy Norill PawelkgPawlosck Peter de Sowaro Piotr Pkierski Ponton Przykuta RedRad Sasek Sblive Silbarad T zielWarszk Webprog Wentuq ZiomekPL Zjem ci chleb i anonimowi autorzy

F4 GrafikiAutorzy i licencje grafik

grafika na okładce Saint-Elme Gautier rycina z książki Le Corset agrave travers les acircges Paryż źroacutedło Wikimedia Commons public domain

logoWikibooks zastrzeżony znak towarowycopy ampAll rights reserved Wikimedia FoundationInc

grafika a (strona ) autor Claudio Rocchini źroacutedło Wikimedia Commons licencja

grafika b (strona ) autor Adam majewski źroacutedło Wikimedia Commons licencja CreativeCommons Aribution Unported

213

214 DODATEK F INFORMACJE O PLIKU

grafia (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Daniel B źroacutedo Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Derrick Coetzee źroacutedło Wikimedia Commons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

Indeks

adres alternatywa

biblioteka standardowa big endian blok

Cjęzyk

dekrementacja dynamiczna alokacja pamięci

enum

funkcja definicja deklaracja rekurencyjna

inkrementacja

komentarz kompilacja

warunkowa kompilator

lista używanie

koniunkcja konwersja

libc lile endian Porownaj big endian

main makefile

napis poroacutewnywanie

negacja

operatordekrementacji inkrementacji modulo

pobrania adresu sizeof wyrażenia warunkowego wyłuskania

plikczytanie i pisanie nagłowkowy

porządek bajtoacutew prawda i fałsz preprocesor procedury prototyp funkcji przekazywanie argumentoacutew do funkcji

przez wartość przez wskaźnik

przepełnienie bufora przesunięcie bitowe

rzutowanie

sizeof stała struktura słowa kluczowe

tablica wielowymiarowa znakoacutew

typ definiowanie wyliczeniowy

unia

Valgrind void

jako typ zwracany na liście argumentoacutew void

volatile

wejściewyjście wskaźnik wyciek pamięci

215

216 INDEKS

wyroacutewnywanie

zmienna globalna lokalna statyczna

znaki specjalne

  • O podręczniku
    • O czym moacutewi ten podręcznik
    • Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika
    • Konwencje przyjęte w tym podręczniku
    • Czy mogę pomoacutec
    • Autorzy
    • Źroacutedła
      • O języku C
        • Historia C
        • Zastosowania języka C
        • Przyszłość C
          • Czego potrzebujesz
            • Czego potrzebujesz
            • Zintegrowane Środowiska Programistyczne
            • Dodatkowe narzędzia
              • Używanie kompilatora
                • GCC
                • Borland
                • Czytanie komunikatoacutew o błędach
                  • Pierwszy program
                    • Twoacutej pierwszy program
                    • Rozwiązywanie problemoacutew
                      • Podstawy
                        • Kompilacja Jak działa C
                        • Co może C
                        • Struktura blokowa
                        • Zasięg
                        • Funkcje
                        • Biblioteki standardowe
                        • Komentarze i styl
                        • Preprocesor
                        • Nazwy zmiennych stałych i funkcji
                          • Zmienne
                            • Czym są zmienne
                            • Typy zmiennych
                            • Specyfikatory
                            • Modyfikatory
                            • Uwagi
                              • Operatory
                                • Przypisanie
                                • Rzutowanie
                                • Operatory arytmetyczne
                                • Operacje bitowe
                                • Poroacutewnanie
                                • Operatory logiczne
                                • Operator wyrażenia warunkowego
                                • Operator przecinek
                                • Operator sizeof
                                • Inne operatory
                                • Priorytety i kolejność obliczeń
                                • Kolejność wyliczania argumentoacutew operatora
                                • Uwagi
                                • Zobacz też
                                  • Instrukcje sterujące
                                    • Instrukcje warunkowe
                                    • Pętle
                                    • Instrukcja goto
                                    • Natychmiastowe kończenie programu --- funkcja exit
                                    • Uwagi
                                      • Podstawowe procedury wejścia i wyjścia
                                        • Wejściewyjście
                                        • Funkcje wyjścia
                                        • Funkcja puts
                                        • Funkcja fputs
                                        • Funkcje wejścia
                                          • Funkcje
                                            • Tworzenie funkcji
                                            • Wywoływanie
                                            • Zwracanie wartości
                                            • Zwracana wartość
                                            • Funkcja main()
                                            • Dalsze informacje
                                            • Zobacz też
                                              • Preprocesor
                                                • Wstęp
                                                • Dyrektywy preprocesora
                                                • Predefiniowane makra
                                                  • Biblioteka standardowa
                                                    • Czym jest biblioteka
                                                    • Po co nam biblioteka standardowa
                                                    • Gdzie są funkcje z biblioteki standardowej
                                                    • Opis funkcji biblioteki standardowej
                                                    • Uwagi
                                                      • Czytanie i pisanie do plikoacutew
                                                        • Pojęcie pliku
                                                        • Identyfikacja pliku
                                                        • Podstawowa obsługa plikoacutew
                                                        • Rozmiar pliku
                                                        • Przykład --- pliki graficzny
                                                        • Co z katalogami
                                                          • Ćwiczenia dla początkujących
                                                            • Ćwiczenia
                                                              • Tablice
                                                                • Wstęp
                                                                • Odczytzapis wartości do tablicy
                                                                • Tablice znakoacutew
                                                                • Tablice wielowymiarowe
                                                                • Ograniczenia tablic
                                                                • Ciekawostki
                                                                  • Wskaźniki
                                                                    • Co to jest wskaźnik
                                                                    • Operowanie na wskaźnikach
                                                                    • Arytmetyka wskaźnikoacutew
                                                                    • Tablice a wskaźniki
                                                                    • Gdy argument jest wskaźnikiem
                                                                    • Pułapki wskaźnikoacutew
                                                                    • Na co wskazuje NULL
                                                                    • Stałe wskaźniki
                                                                    • Dynamiczna alokacja pamięci
                                                                    • Wskaźniki na funkcje
                                                                    • Możliwe deklaracje wskaźnikoacutew
                                                                    • Popularne błędy
                                                                    • Ciekawostki
                                                                      • Napisy
                                                                        • Łańcuchy znakoacutew w języku C
                                                                        • Operacje na łańcuchach
                                                                        • Bezpieczeństwo kodu a łańcuchy
                                                                        • Konwersje
                                                                        • Operacje na znakach
                                                                        • Częste błędy
                                                                        • Unicode
                                                                          • Typy złożone
                                                                            • typedef
                                                                            • Typ wyliczeniowy
                                                                            • Struktury
                                                                            • Unie
                                                                            • Inicjalizacja struktur i unii
                                                                            • Wspoacutelne własności typoacutew wyliczeniowych unii i struktur
                                                                            • Studium przypadku --- implementacja listy wskaźnikowej
                                                                              • Biblioteki
                                                                                • Czym jest biblioteka
                                                                                • Jak zbudowana jest biblioteka
                                                                                  • Więcej o kompilowaniu
                                                                                    • Ciekawe opcje kompilatora GCC
                                                                                    • Program make
                                                                                    • Optymalizacje
                                                                                    • Kompilacja krzyżowa
                                                                                    • Inne narzędzia
                                                                                      • Zaawansowane operacje matematyczne
                                                                                        • Biblioteka matematyczna
                                                                                        • Prezentacja liczb rzeczywistych w pamięci komputera
                                                                                        • Liczby zespolone
                                                                                          • Powszechne praktyki
                                                                                            • Konstruktory i destruktory
                                                                                            • Zerowanie zwolnionych wskaźnikoacutew
                                                                                            • Konwencje pisania makr
                                                                                            • Jak dostać się do konkretnego bitu
                                                                                            • Skroacutety notacji
                                                                                              • Przenośność programoacutew
                                                                                                • Niezdefiniowane zachowanie i zachowanie zależne od implementacji
                                                                                                • Rozmiar zmiennych
                                                                                                • Porządek bajtoacutew i bitoacutew
                                                                                                • Biblioteczne problemy
                                                                                                • Kompilacja warunkowa
                                                                                                  • Łączenie z innymi językami
                                                                                                    • Język C i Asembler
                                                                                                    • C++
                                                                                                      • Indeks alfabetyczny
                                                                                                      • Indeks tematyczny
                                                                                                        • asserth
                                                                                                        • ctypeh
                                                                                                        • errnoh
                                                                                                        • floath
                                                                                                        • limitsh
                                                                                                        • localeh
                                                                                                        • mathh
                                                                                                        • setjmph
                                                                                                        • signalh
                                                                                                        • stdargh
                                                                                                        • stddefh
                                                                                                        • stdioh
                                                                                                        • stdlibh
                                                                                                        • stringh
                                                                                                        • timeh
                                                                                                          • Wybrane funkcje biblioteki standardowej
                                                                                                            • assert
                                                                                                            • atoi
                                                                                                            • isalnum
                                                                                                            • malloc
                                                                                                            • printf
                                                                                                            • scanf
                                                                                                              • Składnia
                                                                                                                • Symbole i słowa kluczowe
                                                                                                                • Polskie znaki
                                                                                                                • Operatory
                                                                                                                • Typy danych
                                                                                                                  • Przykłady z komentarzem
                                                                                                                  • Informacje o pliku
                                                                                                                    • Historia
                                                                                                                    • Informacje o pliku PDF i historia
                                                                                                                    • Autorzy
                                                                                                                    • Grafiki
                                                                                                                      • Skorowidz
Page 5: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ

Biblioteka standardowa Czym jest biblioteka 97 Po co nam biblioteka standardowa 97 Gdzie są funkcje z biblioteki standardowej 97 Opis funkcji biblioteki standardowej 98 Uwagi 98

Czytanie i pisanie do plikoacutew Pojęcie pliku 99 Identyfikacja pliku 99 Podstawowa obsługa plikoacutew 99 Rozmiar pliku 102 Przykład mdash pliki graficzny 103 Co z katalogami 104

Ćwiczenia dla początkujący Ćwiczenia 105

Tablice Wstęp 107 Odczytzapis wartości do tablicy 109 Tablice znakoacutew 109 Tablice wielowymiarowe 110 Ograniczenia tablic 110 Ciekawostki 111

Wskaźniki Co to jest wskaźnik 113 Operowanie na wskaźnikach 114 Arytmetyka wskaźnikoacutew 117 Tablice a wskaźniki 118 Gdy argument jest wskaźnikiem 119 Pułapki wskaźnikoacutew 120 Na co wskazuje 120 Stałe wskaźniki 121 Dynamiczna alokacja pamięci 122 Wskaźniki na funkcje 125 Możliwe deklaracje wskaźnikoacutew 127 Popularne błędy 127 Ciekawostki 128

Napisy Łańcuchy znakoacutew w języku C 129 Operacje na łańcuchach 132 Bezpieczeństwo kodu a łańcuchy 134 Konwersje 137 Operacje na znakach 137 Częste błędy 137 Unicode 138

5

Typy złożone typedef 141 Typ wyliczeniowy 141 Struktury 142 Unie 142 Inicjalizacja struktur i unii 144 Wspoacutelne własności typoacutew wyliczeniowych unii i struktur 144 Studium przypadku mdash implementacja listy wskaźnikowej 146

Biblioteki Czym jest biblioteka 151 Jak zbudowana jest biblioteka 151

Więcej o kompilowaniu Ciekawe opcje kompilatora 155 Program make 155 Optymalizacje 157 Kompilacja krzyżowa 159 Inne narzędzia 159

Zaawansowane operacje matematyczne Biblioteka matematyczna 161 Prezentacja liczb rzeczywistych w pamięci komputera 162 Liczby zespolone 163

Powszene praktyki Konstruktory i destruktory 165 Zerowanie zwolnionych wskaźnikoacutew 166 Konwencje pisania makr 166 Jak dostać się do konkretnego bitu 167 Skroacutety notacji 168

Przenośność programoacutew Niezdefiniowane zachowanie i zachowanie zależne od implementacji 171 Rozmiar zmiennych 172 Porządek bajtoacutew i bitoacutew 172 Biblioteczne problemy 175 Kompilacja warunkowa 175

Łączenie z innymi językami Język C i Asembler 177 C++ 180

A Indeks alfabetyczny

B Indeks tematyczny B asserth 183B ctypeh 183B errnoh 183B floath 183B limitsh 183

6

B localeh 184B mathh 184B setjmph 185B signalh 185B stdargh 185B stddefh 185B stdioh 185B stdlibh 186B stringh 186B timeh 186

C Wybrane funkcje biblioteki standardowej C assert 189C atoi 190C isalnum 191C malloc 193C printf 195C scanf 199

D Składnia D Symbole i słowa kluczowe 203D Polskie znaki 205D Operatory 205D Typy danych 207

E Przykłady z komentarzem

F Informacje o pliku F Historia 213F Informacje o pliku i historia 213F Autorzy 213F Grafiki 213

Skorowidz

7

8

Spis tablic

Priorytety operatoroacutew 53

D Symbole i słowa kluczowe 204D Typy danych według roacuteżnych specyfikacji języka C 207

9

Rozdział 1

O podręczniku

11 O czym moacutewi ten podręcznikNiniejszy podręcznik stanowi przewodnik dla początkujących programistoacutew po języku pro-gramowania C

12 Co trzeba wiedzieć żeby skorzystać z niniejszego pod-ręcznika

Ten podręcznik ma nauczyć programowania w C od podstaw do poziomu zaawansowanegoDo zrozumienia rozdziału dla początkujących wymagana jest jedynie znajomość podstawo-wych pojęć z zakresu algebry oraz terminoacutew komputerowych Doświadczenie w programo-waniu w innych językach bardzo pomaga ale nie jest konieczne

13 Konwencje przyjęte w tym podręcznikuInformacje ważne oznaczamy w następujący sposoacuteb

Ważna informacja

Dodatkowe informacje ktoacutere odrobinę wykraczają poza zakres podręcznika a także wy-jaśniają kwestie niezwiązane bezpośrednio z językiem C oznaczamy tak

Wyjaśnienie

Ponadto kod w języku C będzie prezentowany w następujący sposoacuteb

include ltstdiohgt

int main (int argc char argv[])

return 0

11

12 ROZDZIAŁ 1 O PODRĘCZNIKU

Innego rodzaju przykłady dialog użytkownika z konsolą i programem wejście wyjścieprogramu informacje teoretyczne będą wyglądały tak

typ zmienna = wartość

14 Czy mogę pomoacutecOczywiście że możesz Mało tego będziemy zadowoleni z każdej pomocy ndash możesz pisaćrozdziały lub tłumaczyć je z angielskiej wersji tego podręcznika Nie musisz pytać się nikogoo zgodę mdash jeśli chcesz możesz zacząć już teraz Prosimy jedynie o zapoznanie się ze stylempodręcznika użytymi w nim szablonami i zachowanie układu rozdziałoacutew Propozycje zmianyspisu treści należy zgłaszać na stronie dyskusji

Jeśli znalazłeś jakiś błąd a nie umiesz go poprawić koniecznie powiadom o tym fakcieautoroacutew tego podręcznika za pomocą strony dyskusji danego modułu książki Dzięki temuprzyczyniasz się do rozwoju tego podręcznika

15 AutorzyIstotny wkład w powstanie podręcznika mają

CzarnyZajaczek

Derbeth

Kj

mina

Dodatkowo w rozwoju podręcznika pomagali między innymi

Lrds

Noisy

16 Źroacutedła podręcznik C Programming na anglojęzycznej wersji Wikibooks licencja GFDL

Brian W Kernighan Dennis M Ritchie Język ANSI C

ISO C Commiee Dra stycznia

Bruce Eckel inking in C++ Rozdział Język C w programie C++

Rozdział 2

O języku C

Zobacz w Wikipedii C (ję-zyk programowania)C jest językiem programowania wysokiego poziomu Jego nazwę interpretuje się jako na-

stępną literę po B (nazwa jego poprzednika) lub drugą literę języka BCPL (poprzednik językaB)

21 Historia CW roku trzej naukowcy z Bell Telephone Laboratories mdashWilliam Shockley Walter Brat-tain i John Bardeen mdash stworzyli pierwszy tranzystor w roku w MIT skonstruowanopierwszy komputer oparty wyłącznie na tranzystorach TX-O w roku Jack Kilby z Te-xas Instruments skonstruował układ scalony Ale zanim powstał pierwszy układ scalonypierwszy język wysokiego poziomu został już napisany

W powstał Fortran (Formula Translator) ktoacutery zapoczątkował napisanie języka For-tran I () Poacuteźniej powstały kolejno

Algol mdash Algorithmic Language w r

Algol ()

CPL mdash Combined Programming Language ()

BCPL mdash Basic CPL ()

B ()

i C w oparciu o BB został stworzony przez Kena ompsona z Bell Labs był to język interpretowany uży-

wany we wczesnych wewnętrznych wersjach systemu operacyjnego UNIX Inni pracownicyBell Labs ompson i Dennis Richie rozwinęli B nazywając go NB dalszy rozwoacutej NB dał Cmdash język kompilowany Większa część UNIX-a została ponownie napisana w NB a następniew C co dało w efekcie bardziej przenośny system operacyjny W roku wydana zostałaksiążka pt ldquoe C Programming Languagerdquo ktoacutera stała się pierwszym podręcznikiem donauki języka C

Możliwość uruchamiania UNIX-a na roacuteżnych komputerach była głoacutewną przyczyną po-czątkowej popularności zaroacutewno UNIX-a jak i C zamiast tworzyć nowy system operacyjnyprogramiści mogli po prostu napisać tylko te części systemu ktoacuterych wymagał inny sprzętoraz napisać kompilator C dla nowego systemu Odkąd większa część narzędzi systemowychbyła napisana w C logiczne było pisanie kolejnych w tym samym języku

13

14 ROZDZIAŁ 2 O JĘZYKU C

Kilka z obecnie powszechnie stosowanych systemoacutew operacyjnych takich jak Linux Mi-croso Windows zostały napisane w języku C

211 Standaryzacje

W roku Ritchie i Kerninghan opublikowali pierwszą książkę nt języka C mdash ldquoe CProgramming Languagerdquo Owa książka przez wiele lat była swoistym ldquowyznacznikiemrdquo jakprogramować w języku C Była więc to niejako pierwsza standaryzacja nazywana od na-zwisk twoacutercoacutew ldquoKampRrdquo Oto nowości wprowadzone przez nią do języka C w stosunku dojego pierwszych wersji (pochodzących z początku lat )

możliwość tworzenia struktur (słowo struct)

dłuższe typy danych (modyfikator long)

liczby całkowite bez znaku (modyfikator unsigned)

zmieniono operator ldquo=+rdquo na ldquo+=rdquo

Ponadto producenci kompilatoroacutew (zwłaszcza ATampT) wprowadzali swoje zmiany nieob-jęte standardem

funkcje nie zwracające wartości (void) oraz typ void

funkcje zwracające struktury i unie

przypisywanie wartości strukturom

wprowadzenie słowa kluczowego const

utworzenie biblioteki standardowej

wprowadzenie słowa kluczowego enum

Owe nieoficjalne rozszerzenia zagroziły spoacutejności języka dlatego też powstał standardregulujący wprowadzone nowinki Od roku trwały prace standaryzacyjne aby w roku wydać standard C (poprawna nazwa to ANSI X-) Niektoacutere zmiany wpro-wadzono z języka C++ jednak rewolucję miał dopiero przynieść standard C ktoacutery wpro-wadził min

funkcje inline

nowe typy danych (np long long int)

nowy sposoacuteb komentowania zapożyczony od C++ ()

przechowywanie liczb zmiennoprzecinkowych zostało zaadaptowane do norm IEEE

utworzono kilka nowych plikoacutew nagłoacutewkowych (stdboolh inypesh)

Na dzień dzisiejszy normą obowiązującą jest norma C

22 ZASTOSOWANIA JĘZYKA C 15

22 Zastosowania języka CJęzyk C został opracowany jako strukturalny język programowania do celoacutew ogoacutelnych Przezcałą swą historię (czyli ponad lat) służył do tworzenia przeroacuteżnych programoacutewmdash od syste-moacutew operacyjnych po programy nadzorujące pracę urządzeń przemysłowych C jako językdużo szybszy od językoacutew interpretowanych (Perl Python) oraz uruchamianych w maszy-nach wirtualnych (np C Java) może bez problemu wykonywać złożone operacje nawetwtedy gdy nałożone są dość duże limity czasu wykonywania pewnych operacji Jest on przytym bardzo przenośny mdash może działać praktycznie na każdej architekturze sprzętowej podwarunkiem opracowania odpowiedniego kompilatora Często wykorzystywany jest takżedo oprogramowywania mikrokontroleroacutew i systemoacutew wbudowanych Jednak w niektoacuterychsytuacjach język C okazuje się być mało przydatny zwłaszcza chodzi tu o obliczenia mate-matyczne wymagające dużej precyzji (w tej dziedzinie znakomicie spisuje się Fortran) lubteż dużej optymalizacji dla danego sprzętu (wtedy niezastąpiony jest język asemblera)

Kolejną zaletą C jest jego dostępność mdash właściwie każdy system typu UNIX posiada kom-pilator C w C pisane są funkcje systemowe

Problemem w przypadku C jest zarządzanie pamięcią ktoacutere nie wybacza programiściebłędoacutew niewygodne operowanie napisami i niestety pewna liczba ldquokruczkoacutewrdquo ktoacutere mogązaskakiwać nowicjuszy Na tle młodszych językoacutew programowania C jest językiem dosyćniskiego poziomu więc wiele rzeczy trzeba w nim robić ręcznie jednak zarazem umożliwia torobienie rzeczy nieprzewidzianych w samym języku (np implementację liczb bitowych)a także łatwe łączenie C z Asemblerem

23 Przyszłość CPomimo sędziwego już wieku (C ma ponad lat) nadal jest on jednym z najczęściej stosowa-nych językoacutew programowania Doczekał się już swoich następcoacutew z ktoacuterymi w niektoacuterychdziedzinach nadal udaje mu się wygrywać Widać zatem że pomimo pozornej prostoty iniewielkich możliwości język C nadal spełnia stawiane przed nim wymagania Warto zatemuczyć się języka C gdyż nadal jest on wykorzystywany (i nic nie wskazuje na to by miałosię to zmienić) a wiedza ktoacuterą zdobędziesz ucząc się C na pewno się nie zmarnuje Skład-nia języka C pomimo że przez wielu uważana za nieczytelną stała się podstawą dla takichjęzykoacutew jak C++ C czy też Java

16 ROZDZIAŁ 2 O JĘZYKU C

Rozdział 3

Czego potrzebujesz

31 Czego potrzebujeszWbrew powszechnej opinii nauczenie się ktoacuteregoś z językoacutew programowania (w tym językaC) nie jest takie trudne Do nauki wystarczą Ci

komputer z dowolnym systemem operacyjnym takim jak FreeBSD Linux Windows

Język C jest bardzo przenośny więc będzie działał właściwie na każdej platformiesprzętowej i w każdym nowoczesnym systemie operacyjnym

kompilator języka C

Kompilator języka C jest programem ktoacutery tłumaczy kod źroacutedłowy napisany przeznas do języka asembler a następnie do postaci zrozumiałej dla komputera (maszynycyfrowej) czyli do postaci ciągu zer i jedynek ktoacutere sterują pracą poszczegoacutelnych ele-mentoacutew komputera Kompilator języka C można dostać za darmo Przykładem sągcc pod systemy uniksowe DJGPP pod systemy DOS MinGW oraz lcc pod systemytypu Windows Jako kompilator C może dobrze służyć kompilator języka C++ (roacuteżnicemiędzy tymi językami przy pisaniu prostych programoacutew są nieistotne) Spokojnie mo-żesz więc użyć na przykład Microso Visual C++reg lub kompilatoroacutew firmy BorlandJeśli lubisz eksperymentować wyproacutebuj Tiny C Compiler bardzo szybki kompilatoro ciekawych funkcjach Możesz ponadto wyproacutebować interpreter języka C Więcejinformacji na Wikipedii

Linker (często jest razem z kompilatorem)

Linker jest to program ktoacutery uruchamiany jest po etapie kompilacji jednego lub kilkuplikoacutew źroacutedłowych (pliki z rozszerzeniem c cpp lub innym) skompilowanych do-wolnym kompilatorem Taki program łączy wszystkie nasze skompilowane pliki źroacute-dłowe i inne funkcje (np printf scan) ktoacutere były użyte (dołączone do naszego pro-gramu poprzez użycie dyrektywy include) w naszym programie a nie były zdefinio-wane(napisane przez nas) w naszych plikach źroacutedłowych lub nagłoacutewkowych Linkerjest to czasami jeden program połączony z kompilatorem Wywoływany jest on naogoacuteł automatycznie przez kompilator w wyniku czego dostajemy gotowy program douruchomienia

Debuger (opcjonalnie według potrzeb)

17

18 ROZDZIAŁ 3 CZEGO POTRZEBUJESZ

Debugger jest to program ktoacutery umożliwia prześledzenie(określenie wartości poszcze-goacutelnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijcewykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu Używasię go w celu określenia czemu nasz program nie działa po naszej myśli lub czemu pro-gram niespodziewanie kończy działanie bez powodu Aby użyć debuggera kompilatormusi dołączyć kod źroacutedłowy do gotowego skompilowanego programu Przykładowymidebuggerami są gdb pod Linuksem lub debugger firmy Borland pod Windowsa

edytora tekstowego

Systemy uniksowe oferują wiele edytoroacutew przydatnych dla programisty jak choćbyvim i Emacs w trybie tekstowym Kate w KDE czy gedit w GNOME Windows maedytor całkowicie wystarczający do pisania programoacutew w C mdash nieśmiertelny Notatnikmdash ale z łatwością znajdziesz w Internecie wiele wygodniejszych narzędzi takich jak npNotepad++ Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE

dużo chęci i dobrej motywacji

32 Zintegrowane Środowiska ProgramistyczneZamiast osobnego kompilatora i edytora możesz wybrać Zintegrowane Środowisko Progra-mistyczne (Integrated Development Environment IDE) IDE jest zestawem wszystkich pro-gramoacutew ktoacutere potrzebuje programista najczęściej z interfejsem graficznym IDE zawierakompilator linker i edytor z reguły roacutewnież debugger

Bardzo popularny IDE to płatny (istnieje także jego darmowa wersja) Microso VisualC++ (MS VC++) popularne darmowe IDE to np

CodeBlocks dla Windows jak i Linux dostępny na stronie wwwcodeblocksorg

KDevelop (Linux) dla KDE

NetBeans multiplatformowy darmowy do ściągnięcia na stronie wwwnetbeansorg

Eclipse z wtyczką CDT (wspoacutełpracuje z MinGW i GCC)

Borland C++ Builder dostępny za darmo do użytku prywatnego

Xcode dlaMac OS X i nowszy kompatybilny z procesorami PowerPC i Intel (moż-liwość stworzenia Universal Binary)

Geany dla systemoacutewWindows i Linux wspoacutełpracuje zMinGW iGCCwwwgeanyorg

Pelles C wwwsmorgasbordetcom

Dev-C++ dla Windows dostępny na stronie wwwbloodshednet

33 Dodatkowe narzędziaWśroacuted narzędzi ktoacutere nie są niezbędne ale zasługują na uwagę można wymienić Valgrindandash specjalnego rodzaju debugger Valgrind kontroluje wykonanie programu i wykrywa nie-prawidłowe operacje w pamięci oraz wycieki pamięci Użycie Valgrinda jest proste mdash kom-pilujemy program jak do debugowania następnie podajemy jako argument Valgrindowi

Rozdział 4

Używanie kompilatora

Język C jest językiem kompilowanym co oznacza że potrzebuje specjalnego programu mdashkompilatora mdash ktoacutery tłumaczy kod źroacutedłowy pisany przez człowieka na język rozkazoacutew da-nego komputera W skroacutecie działanie kompilatora sprowadza się do czytania tekstowegopliku z kodem programu raportowania ewentualnych błędoacutew i produkowania pliku wyniko-wego

Kompilator uruchamiamy ze Zintegrowanego Środowiska Programistycznego lub z kon-soli (linii poleceń) Przejść do konsoli można dla systemoacutew typu UNIX w trybie graficz-nym użyć programoacutew gnome-terminal konsole albo xterm w Windows ldquoWiersz poleceniardquo(można go znaleźćwmenuAkcesoria albo uruchomićwpisującw Start -gtUruchom ldquocmdrdquo)

41 GCCZobacz w Wikipedii GCC

GCC jest to darmowy zestaw kompilatoroacutew min języka C rozwijany w ramach projektuGNU Dostępny jest on na dużą ilość platform sprzętowych obsługiwanych przez takie sys-temy operacyjne jak AIX BSD Linux Mac OS X SunOS Windows Na niektoacuterych sys-temach (np Windows) nie jest on jednak dostępny automatycznie Należy zainstalowaćodpowiednie narzędza (poprzedni rozdział)

Aby skompilować kod języka C za pomocą kompilatora GCC napisany wcześniej w do-wolnym edytorze tekstu należy uruchomić program z odpowiednimi parametrami Podsta-wowym parametrem ktoacutery jest wymagany jest nazwa pliku zawierającego kod programuktoacutery chcemy skompilować

gcc kodc

Rezultatem kompilacji będzie plik wykonywalny z domyślną nazwą (w systemach Unixjest to ldquoaoutrdquo) Jest to metoda niezalecana ponieważ jeżeli skompilujemy w tym samymkatalogu kilka plikoacutew z kodem kolejne pliki wykonywalne zostaną nadpisane i w rezultacieotrzymamy tylko jeden (ten ostatni) skompilowany kod Aby wymusić na GCC nazwę plikuwykonywalnego musimy skorzystać z parametru ldquo-o ltnazwagtrdquo

gcc -o program kodc

W rezultacie otrzymujemy plik wykonywalny o nazwie programPracując nad złożonym programem składającym się z kilku plikoacutew źroacutedłowych (c) mo-

żemy skompilować je niezależnie od siebie tworząc tak zwane pliki typu obiekt z rozszerze-niem o (ang Object File) Następnie możemy stworzyć z nich jednolity program w procesie

19

20 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

konsolidacji (linkowaniu) Jest to bardzo wygodne i praktyczne rozwiązanie ze względu nato iż nie jesteśmy zmuszeni kompilować wszystkich plikoacutew tworzących program za każdymrazem na nowo a jedynie te w ktoacuterych wprowadziliśmy zmiany Aby skompilować plik bezlinkowania używamy parametru ldquo-c ltplikgtrdquo

gcc -o program1o -c kod1cgcc -o program2o -c kod2c

Otrzymujemy w ten sposoacuteb pliki typu obiekt programo i programo A następnie two-rzymy z nich program głoacutewny

gcc -o program program1o program2o

Możemy użyć roacutewnież flag min aby włączyć dokładne rygorystyczne sprawdzanie na-pisanego kodu (co może być przydatne jeśli chcemy dążyć do perfekcji) używamy przełącz-nikoacutew

gcc kodc -o program -Werror -Wall -W -pedantic -ansi

Więcej informacji na temat parametroacutew i działania kompilatora GCC można znaleźć na

Strona domowa projektu GNU GCC

Kroacutetki przekrojowy opis GCC po polsku

Strona podręcznika systemu UNIX (man)

42 BorlandZobacz podręcznik Borland C++ Compiler

43 Czytanie komunikatoacutew o błędaJedną z najbardziej podstawowych umiejętności ktoacutere musi posiąść początkujący progra-mista jest umiejętność rozumienia komunikatoacutew o roacuteżnego rodzaju błędach ktoacutere sygnali-zuje kompilator Wszystkie te informacje pomogą Ci szybko wychwycić ewentualne błędy(ktoacuterych na początku zawsze jest bardzo dużo) Nie martw się że na początku dość częstobędziesz oglądał wydruki błędoacutew zasygnalizowanych przez kompilator mdash nawet zaawanso-wanym programistom się to zdarza Kompilator ma za zadanie pomoacutec Ci w szybkiej popra-wie ewentualnych błędoacutew dlatego też umiejętność analizy komunikatoacutew o błędach jest takważna

431 GCC

Kompilator jest w stanie wychwycić błędy składniowe ktoacutere z pewnością będziesz popełniałKompilator GCC wyświetla je w następującej formie

nazwa_plikucnumer_linijkiopis błędu

Kompilator dość często podaje także nazwę funkcji w ktoacuterej wystąpił błąd Przykładowobłąd deklaracji zmiennej w pliku testc

43 CZYTANIE KOMUNIKATOacuteW O BŁĘDACH 21

include ltstdiohgt

int main ()

intr rprintf (dn r)

Spowoduje wygenerowanie następującego komunikatu o błędzie

testc In function lsquomainrsquotestc5 error lsquointrrsquo undeclared (first use in this function)testc5 error (Each undeclared identifier is reported only oncetestc5 error for each function it appears in)testc5 error syntax error before lsquorrsquotestc6 error lsquorrsquo undeclared (first use in this function)

Co widzimy w raporcie o błędach W linii użyliśmy nieznanego (undeclared) identy-fikatora intr mdash kompilator moacutewi że nie zna tego identyfikatora jest to pierwsze użycie wdanej funkcji i że więcej nie ostrzeże o użyciu tego identyfykatora w tej funkcji Ponieważintr nie został rozpoznany jako żaden znany typ linijka intr r nie została rozpoznana jakodeklaracja zmiennej i kompilator zgłasza błąd składniowy (syntax error) W konsekwencji rnie zostało rozpoznane jako zmienna i kompilator zgłosi to jeszcze w następnej linijce gdzieużywamy r

22 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

Rozdział 5

Pierwszy program

51 Twoacutej pierwszy program

Przyjęło się że pierwszy program napisany w dowolnym języku programowania powinienwyświetlić tekst ldquoHello Worldrdquo (Witaj Świecie) Zauważ że sam język C nie ma żadnychmechanizmoacutew przeznaczonych do wprowadzania i wypisywania danych mdash musimy zatemskorzystać z odpowiadających za to funkcji mdash w tym przypadku printf zawartej w standar-dowej bibliotece C (ang C Standard Library) (podobnie jak w Pascalu używa się do tegoprocedur Pascalowskim odpowiednikiem funkcji printf są procedury writewriteln)

W języku C deklaracje funkcji zawarte są w plika nagłoacutewkowy posiadających naj-częściej rozszerzenie h choć można także spotkać rozszerzenie hpp przy czym to drugiezwykło się stosować w języku C++ (rozszerzenie nie ma swych ldquotechnicznychrdquo korzeni mdash jestto tylko pewna konwencja) W celu umieszczenia w swoim kodzie pewnego pliku nagłoacutewko-wego używamy dyrektywy kompilacyjnej include Przed procesem kompilacji w miejscetej dyrektywy wstawiana jest treść podanego pliku nagłoacutewkowego dostarczając deklaracjifunkcji

Poniższy przykład obrazuje jak przy użyciu dyrektywy include umieścimyw kodzie plikstandardowej biblioteki C stdioh (Standard InputOutputHeaderfile) zawierającą definicjęfunkcji printf

include ltstdiohgt

W nawiasach troacutejkątnych lt gt umieszcza się nazwy standardowych plikoacutew nagłoacutewko-wych1 Żeby włączyć inny plik nagłoacutewkowy (np własny) znajdujący się w katalogu z kodemprogramu trzeba go wpisać w cudzysłoacutew

include moacutej_plik_nagłoacutewkowyh

Mamy więc funkcję printf jak i wiele innych do wprowadzania i wypisywania danychczas na pisanie programu

W programie definujemy głoacutewną funkcję main uruchamianą przy starcie programu za-wierającą właściwy kod Definicja funkcji zawiera oproacutecz nazwy i kodu także typ wartościzwracanej i argumentoacutew pobieranych Konstrukcja funkcji main

1Domyślne pliki nagłoacutewkowe znajdują się w katalogu z plikami nagłoacutewkowymi kompilatora W systemach zrodziny Unix będzie to katalog usrinclude natomiast w systemie Windows oacutew katalog będzie umieszczony wkatalogu z kompilatorem

23

24 ROZDZIAŁ 5 PIERWSZY PROGRAM

int main (void)

Typem zwracany przez funkcję jest int (Integer) czyli liczba całkowita (w przypadkumainbędzie to kod wyjściowy programu) W nawiasach umieszczane są argumenty funkcji tutajzapis void oznacza ich pominięcie Funkcja main jako argumenty może pobierać parametrylinii poleceń z jakimi program został uruchomiony i pełną ścieżkę do katalogu z programem

Kod funkcji umieszcza się w nawiasach klamrowych i Wewnątrz funkcji możemy wpisać poniższy kod

printf(Hello World)return 0

Wszystkie polecenia kończymy średnikiem return określa wartość jaką zwroacuteci funkcja(program) Liczba zero zwracana przez funkcję main() oznacza że program zakończył siębez błędoacutew błędne zakończenie często (choć nie zawsze) określane jest przez liczbę jeden2Funkcję main kończymy nawiasem klamrowym zamykającym

Twoacutej kod powinien wyglądać jak poniżej

include ltstdiohgtint main (void)

printf (Hello World)return 0

Teraz wystarczy go tylko skompilować i uruchomić

52 Rozwiązywanie problemoacutewJeśli nie możesz skompilować powyższego programu to najprawdopodobniej popełniłeś li-teroacutewkę przy ręcznym przepisywaniu go Zobacz też instrukcje na temat używania kompi-latora

Może też się zdarzyć że program skompiluje się uruchomi ale jego efektu działania niebędzie widać Dzieje się tak ponieważ nasz pierwszy program po prostu wypisuje komunikati od razu kończy działanie nie czekając na reakcję użytkownika Nie jest to problememgdy program jest uruchamiany z konsoli tekstowej ale w innych przypadkach nie widzimyefektoacutew jego działania

Jeśli korzystasz ze Zintegrowanego Środowiska Programistycznego (ang IDE) możeszzaznaczyć by nie zamykało ono programu po zakończeniu jego działania Innym sposobemjest dodanie instrukcji ktoacutere wstrzymywałyby zakończenie programu Można to zrobić do-dając przed linią z return funkcję pobierającą znak z wejścia

getchar()

2Jeżeli chcesz mieć pewność że twoacutej program będzie działał poprawnie roacutewnież na platformach gdzie 1 oznaczapoprawne zakończenie (lub nie oznacza nic) możesz skorzystać z makr EXIT SUCCESS i EXIT FAILURE zdefiniowanychw pliku nagłoacutewkowym stdlibh

52 ROZWIĄZYWANIE PROBLEMOacuteW 25

Jest też prostszy (choć nieprzenośny) sposoacuteb mianowicie wywołanie polecenia systemo-wego W zależności od używanego systemu operacyjnego mamy do dyspozycji roacuteżne po-lecenia powodujące roacuteżne efekty Do tego celu skorzystamy z funkcji system() ktoacutera jakoparametr przyjmuje polecenie systemowe ktoacutere chcemy wykonać np

Rodzina systemoacutew UnixLinux

system(sleep 10) oczekiwanie 10 s system(read discard) oczekiwanie na wpisanie tekstu

Rodzina systemoacutew oraz MS Windows

system(pause) oczekiwanie na wciśnięcie dowolnego klawisza

Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnychWindows w ktoacuterychto z reguły pracuje się w trybie okienkowym a z konsoli korzystamy tylko podczas urucha-mianiu programu Z kolei w systemach UnixLinux jest ona praktycznie w ogoacutele nie używanaw tym celu ze względu na uruchamianie programu bezpośrednio z konsoli

26 ROZDZIAŁ 5 PIERWSZY PROGRAM

Rozdział 6

Podstawy

Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogoacutel-nych informacji

61 Kompilacja Jak działa C

Jak każdy język programowania C sam w sobie jest niezrozumiały dla procesora Został onstworzony w celu umożliwienia ludziom łatwego pisania kodu ktoacutery może zostać przetwo-rzony na kod maszynowy Program ktoacutery zamienia kod C na wykonywalny kod binarnyto kompilator Jeśli pracujesz nad projektem ktoacutery wymaga kilku plikoacutew kodu źroacutedłowego(np pliki nagłoacutewkowe) wtedy jest uruchamiany kolejny program mdash linker Linker służy dopołączenia roacuteżnych plikoacutew i stworzenia jednej aplikacji lub biblioteki (library) Bibliotekajest zestawem procedur ktoacutery sam w sobie nie jest wykonywalny ale może być używanaprzez inne programy Kompilacja i łączenie plikoacutew są ze sobą bardzo ściśle powiązane stądsą przez wielu traktowane jako jeden proces Jedną rzecz warto sobie uświadomić mdash kompila-cja jest jednokierunkowa przekształcenie kodu źroacutedłowego C w kod maszynowy jest bardzoproste natomiast odwrotnie mdash nie Dekompilatory co prawda istnieją ale rzadko tworząużyteczny kod C

Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collec-tion dostępny na stronie gccgnuorg

62 Co może C

Pewnie zaskoczy Cię to że tak naprawdę ldquoczystyrdquo język C nie może zbyt wiele Język Cw grupie językoacutew programowania wysokiego poziomu jest stosunkowo nisko Dzięki temukod napisany w języku C można dość łatwo przetłumaczyć na kod asemblera Bardzo łatwojest też łączyć ze sobą kod napisany w języku asemblera z kodem napisanym w C Dla bar-dzo wielu ludzi przeszkodą jest także dość duża liczba i częsta dwuznaczność operatoroacutewPoczątkujący programista czytający kod programu w C może odnieść bardzo nieprzyjemnewrażenie ktoacutere można opisać cytatem ldquoja nigdy tego nie opanujęrdquo Wszystkie te elementyjęzyka C ktoacutere wydają Ci się dziwne i nielogiczne wmiarę jak będziesz nabierał doświadcze-nia nagle okażą się całkiem przemyślanie dobrane i takie a nie inne konstrukcje przypadnąCi do gustu Dalsza lektura tego podręcznika oraz zaznajamianie się z funkcjami z roacuteżnychbibliotek ukażą Ci całą gamę możliwości ktoacutere daje język C doświadczonemu programiście

27

28 ROZDZIAŁ 6 PODSTAWY

63 Struktura blokowaTeraz omoacutewimy podstawową strukturę programu napisanego w C Jeśli miałeś styczność zjęzykiem Pascal to pewnie słyszałeś o nim że jest to język programowania strukturalny WC nie ma tak ścisłej struktury blokowej mimo to jest bardzo ważne zrozumienie co oznaczastruktura blokowa Blok jest grupą instrukcji połączonych w ten sposoacuteb że są traktowanejak jedna całość W C blok zawiera się pomiędzy nawiasami klamrowymi Blok możetakże zawierać kolejne bloki

Zawartość bloku Generalnie blok zawiera ciąg kolejno wykonywanych poleceń Polece-nia zawsze (z nielicznymi wyjątkami) kończą się średnikiem () W jednej linii może znajdo-wać się wiele poleceń choć dla zwiększenia czytelności kodu najczęściej pisze się pojedyncząinstrukcję w każdej linii Jest kilka rodzajoacutew poleceń np instrukcje przypisania warunkoweczy pętli W dużej części tego podręcznika będziemy zajmować się właśnie instrukcjami

Pomiędzy poleceniami są roacutewnież odstępymdash spacje tabulacje oraz przejścia do następnejlinii przy czym dla kompilatora te trzy rodzaje odstępoacutew mają takie samo znaczenie Dlaprzykładu poniższe trzy fragmenty kodu źroacutedłowego dla kompilatora są takie same

printf(Hello world) return 0

printf(Hello world)return 0

printf(Hello world)

return 0

W tej regule istnieje jednak jeden wyjątek Dotyczy on stałych tekstowych W powyż-szych przykładach stałą tekstową jest ldquoHello worldrdquo Gdy jednak rozbijemy ten napis kom-pilator zasygnalizuje błąd

printf(Helloworld)return 0

Należy tylko zapamiętać że stałe tekstowe powinny zaczynać się i kończyć w tej samejlini (można ominąć to ograniczenie mdash więcej w rozdziale Napisy) Oproacutecz tego jednego przy-padku dla kompilatora ma znaczenie samo istnienie odstępu a nie jego wielkość czy rodzajJednak stosowanie odstępoacutew jest bardzo ważne dla zwiększenia czytelności kodu mdash dziękiczemu możemy zaoszczędzić sporo czasu i nerwoacutew ponieważ znalezienie błędu (ktoacutere sięzdarzają każdemu) w nieczytelnym kodzie może być bardzo trudne

64 ZasięgPojęcie to dotyczy zmiennych (ktoacutere przechowują dane przetwarzane przez program) Wkaż-dym programie (oproacutecz tych najprostszych) są zaroacutewno zmienne wykorzystywane przez całyczas działania programu oraz takie ktoacutere są używane przez pojedynczy blok programu (npfunkcję) Na przykład w pewnym programie w pewnym momencie jest wykonywane skom-plikowane obliczenie ktoacutere wymaga zadeklarowania wielu zmiennych do przechowywaniapośrednich wynikoacutew Ale przez większą część tego działania te zmienne są niepotrzebne

65 FUNKCJE 29

i zajmują tylko miejsce w pamięci mdash najlepiej gdyby to miejsce zostało zarezerwowane tużprzed wykonaniemwspomnianych obliczeń a zaraz po ich wykonaniu zwolnione Dlatego wC istnieją zmienne globalne oraz lokalne Zmienne globalne mogą być używane w każdymmiejscu programu natomiast lokalne mdash tylko w określonym bloku czy funkcji (oraz blokachw nim zawartych) Generalnie mdash zmienna zadeklarowanaw danym bloku jest dostępna tylkowewnątrz niego

65 Funkcje

Funkcje są ściśle związane ze strukturą blokową mdash funkcją jest po prostu blok instrukcjiktoacutery jest potem wywoływany w programie za pomocą pojedynczego polecenia Zazwyczajfunkcja wykonuje pewne określone zadanie np we wspomnianym programie wykonują-cym pewne skomplikowane obliczenie Każda funkcja ma swoją nazwę za pomocą ktoacuterejjest potem wywoływana w programie oraz blok wykonywanych poleceń Wiele funkcji po-biera pewne dane czyli argumenty funkcji wiele funkcji także zwraca pewną wartość pozakończeniu wykonywania Dobrym nawykiem jest dzielenie dużego programu na zestawmniejszych funkcji mdash dzięki temu będziesz moacutegł łatwiej odnaleźć błąd w programie

Jeśli chcesz użyć jakiejś funkcji to powinieneś wiedzieć

jakie zadanie wykonuje dana funkcja

rodzaj wczytywanych argumentoacutew i do czego są one potrzebne tej funkcji

rodzaj zwroacuteconych danych i co one oznaczają

W programach w języku C jedna funkcja ma szczegoacutelne znaczenie mdash jest tomain() Funk-cję tę zwaną funkcją głoacutewną musi zawierać każdy program W niej zawiera się głoacutewny kodprogramu przekazywane są do niej argumenty z ktoacuterymi wywoływany jest program (jakoparametry argc i argv) Więcej o funkcji main() dowiesz się poacuteźniej w rozdziale Funkcje

66 Biblioteki standardowe

Język C w przeciwieństwie do innych językoacutew programowania (np Fortranu czy Pascala)nie posiada absolutnie żadny słoacutew kluczowych ktoacutere odpowiedzialne by były za obsługęwejścia i wyjścia Może się to wydawać dziwne mdash język ktoacutery sam w sobie nie posiadapodstawowych funkcji musi być językiem o ograniczonym zastosowaniu Jednak brak pod-stawowych funkcji wejścia-wyjścia jest jedną z największych zalet tego języka Jego składniaopracowana jest tak by można było bardzo łatwo przełożyć ją na kod maszynowy To wła-śnie dzięki temu programy napisane w języku C są takie szybkie Pozostaje jednak pytaniemdash jak umożliwić programom komunikację z użytkownikiem

W roku kiedy zapoczątkowano prace nad standaryzacją C zdecydowano że po-winien być zestaw instrukcji identycznych w każdej implementacji C Nazwano je BibliotekąStandardową (czasemnazywaną ldquolibcrdquo) Zawiera ona podstawowe funkcje ktoacutere umożliwiająwykonywanie takich zadań jak wczytywanie i zwracanie danych modyfikowanie zmiennychłańcuchowych działania matematyczne operacje na plikach i wiele innych jednak nie za-wiera żadnych funkcji ktoacutere mogą być zależne od systemu operacyjnego czy sprzętu jakgrafika dźwięk czy obsługa sieci W programie ldquoHello Worldrdquo użyto funkcji z biblioteki stan-dardowej mdash printf ktoacutera wyświetla na ekranie sformatowany tekst

30 ROZDZIAŁ 6 PODSTAWY

67 Komentarze i stylKomentarze mdash to tekst włączony do kodu źroacutedłowego ktoacutery jest pomijany przez kompilatori służy jedynie dokumentacji W języku C komentarze zaczynają się od

a kończą

Dobre komentowanie ma duże znaczenie dla rozwijania oprogramowania nie tylko dlategoże inni będą kiedyś potrzebowali przeczytać napisany przez ciebie kod źroacutedłowy ale takżemożesz chcieć po dłuższym czasie powroacutecić do swojego programu i możesz zapomnieć doczego służy dany blok kodu albo dlaczego akurat użyłeś tego polecenia a nie innego Wchwili pisania programu to może być dla ciebie oczywiste ale po dłuższym czasie możeszmieć problemy ze zrozumieniem własnego kodu Jednak nie należy też wstawiać zbyt dużokomentarzy ponieważ wtedy kod może stać się jeszcze mniej czytelny mdash najlepiej komen-tować fragmenty ktoacutere nie są oczywiste dla programisty oraz te o szczegoacutelnym znaczeniuAle tego nauczysz się już w praktyce

Dobry styl pisania kodu jest o tyle ważny że powinien on być czytelny i zrozumiały po tow końcu wymyślono języki programowania wysokiego poziomu (w tym C) aby kod było ła-two zrozumieć ) I tak mdash należy stosować wcięcia dla odroacuteżnienia blokoacutew kolejnego poziomu(zawartych w innym bloku podrzędnych) nawiasy klamrowe otwierające i zamykające blokpowinny mieć takie same wcięcia staraj się aby nazwy funkcji i zmiennych kojarzyły się zzadaniem jakie dana funkcja czy zmienna pełni w programie W dalszej części podręcznikamożesz napotkać więcej zaleceń dotyczących stylu pisania kodu Staraj się stosować do tychzaleceń mdash dzięki temu kod pisanych przez ciebie programoacutew będzie łatwiejszy do czytania izrozumienia

Jeśli masz doświadczenia z językiem C++ pamiętaj że w C nie powinno się stosowaćkomentarzy zaczynających się od dwoacutech znakoacutew slash tak nie komentujemy w CJest to niezgodne ze standardem ANSI C i niektoacutere kompilatory mogą nie skompilować koduz komentarzami w stylu C++ (choć standard ISO C dopuszcza komentarze w stylu C++)

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentoacutew kodu Jeśli częśćprogramu źle działa i chcemy ją chwilowo wyłączyć albo fragment kodu jest nam już nie-potrzebny ale mamy wątpliwości czy w przyszłości nie będziemy chcieli go użyć mdash umiesz-czamy go po prostu wewnątrz komentarza

Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uważać najedną subtelność Otoacuteż komentarze w języku C nie mogą być zagnieżdżone Trzebana to uważać gdy chcemy objąć komentarzem obszar w ktoacuterym już istnieje komentarz (na-leży wtedy usunąć wewnętrzny komentarz) W nowszym standardzie C dopuszcza się abykomentarz typu zawierał w sobie komentarz

671 Po polsku czy angielsku

Jak jużwcześniej byłowspomniane zmiennym i funkcjom powinno się nadawać nazwy ktoacutereodpowiadają ich znaczeniu Zdecydowanie łatwiej jest czytać kod gdy średnią liczb przecho-wuje zmienna srednia niż a a znajdowaniemmaksimumw ciągu liczb zajmuje się funkcja maxalbo znajdz max niż nazwana f Często nazwy funkcji to właśnie czasowniki

67 KOMENTARZE I STYL 31

Powstaje pytanie w jakim języku należy pisać nazwy Jeśli chcemy by nasz kod mogłyczytać osoby nieznające polskiego mdash warto użyć języka angielskiego Jeśli nie mdash można bezproblemu użyć polskiego Bardzo istotne jest jednak by nie mieszać językoacutew Jeśli zdecy-dowaliśmy się używać polskiego używajmy go od początku do końca przeplatanie ze sobądwoacutech językoacutew robi złe wrażenie

Warto roacutewnież zdecydować się na sposoacuteb zapisywania nazw składających się z więcej niżjednego słowa Istnieje kilka możliwości najważniejsze z nich

oddzielanie podkreśleniem int to str

ldquokonwencja pascalowskardquo każde słowo dużą literą IntToStr

ldquokonwencja wielbłądziardquo pierwsze słowo małą kolejne dużą literą intToStr

Ponownie najlepiej stosować konsekwentnie jedną z konwencji i nie mieszać ze sobąkilku

672 Notacja węgierska

Czasem programista może zapomnieć jakiego typu była dana zmienna Wtedy musi znaleźćodpowiednią deklarację (co nie zawsze jest łatwe) Dlatego więc wymyślono sposoacuteb by temuzaradzić Pomyślano by w nazwie zmiennej (bądź wskaźnika na zmienną) napisać jakiegojest ona typu np

a liczba (liczba typu int)

w ll dlugaLiczba (wskaźnik na zmienną typu long long)

t5x5 ch tabliczka (tablica x elementoacutew typu char)

func i silnia (funkcja zwracająca int)

Jest to bardzo wygodne przy bardzo zagmatwanych zmiennych

w t4 w t2x2 s pomieszaniec (wskaźnik na tablicę czterech wskaźnikoacutew na tablice dwu-wymiarowe zmiennych typu short)

Lub gdy nie pamiętamy wymiaroacutew tablicy

t4x5x6 f powalonaKostkaRubika (od razu wiemy żet4x5x6 f powalonaKostkaRubika[5][4][6] jest niewłaściwe)

Taki zapis ma też swoje wady Gdy zdecydujemy się zmienić typ zmiennej zamiast poprostu przemienić w deklaracji int na long musimy zmieniać nazwy w całym programieCzęsto takie nazwy są po prostu długie i nie chce nam się ich pisać (no coacuteż programista teżczłowiek) więc wolimy wprowadzić pomieszaniec zamiast w t4 w t2x2 s pomieszaniec Naj-ważniejsze to jednak trzymać się rozwiązania ktoacutere wybraliśmy na początku bo mieszaniejest przerażające

32 ROZDZIAŁ 6 PODSTAWY

68 PreprocesorNie cały napisany przez ciebie kod będzie przekształcany przez kompilator bezpośrednio nakodwykonywalny programu Wwielu przypadkach będziesz używać poleceń ldquoskierowanychdo kompilatorardquo tzw dyrektyw kompilacyjnych Na początku procesu kompilacji specjalnypodprogram tzw preprocesor wyszukuje wszystkie dyrektywy kompilacyjne i wykonujeodpowiednie akcje mdash ktoacutere polegają notabene na edycji kodu źroacutedłowego (np wstawieniudeklaracji funkcji zamianie jednego ciągu znakoacutew na inny) Właściwy kompilator zamie-niający kod C na kod wykonywalny nie napotka już dyrektyw kompilacyjnych ponieważzostały one przez preprocesor usunięte po wykonaniu odpowiednich akcji

W C dyrektywy kompilacyjne zaczynają się od znaku hash () Przykładem najczęściejużywanej dyrektywy jest include ktoacutera jest użyta nawet w tak prostym programie jakldquoHello Worldrdquo include nakazuje preprocesorowi włączyć (ang include) w tym miejscuzawartość podanego pliku tzw pliku nagłoacutewkowego najczęściej to będzie plik zawierającyfunkcje z ktoacuterejś biblioteki standardowej (stdioh mdash STandard Input-Output rozszerzenie hoznacza plik nagłoacutewkowy C) Dzięki temu zamiast wklejać do kodu swojego programu dekla-racje kilkunastu a nawet kilkudziesięciu funkcji wystarczy wpisać jedną magiczną linijkę

69 Nazwy zmienny stały i funkcjiIdentyfikatory czyli nazwy zmiennych stałych i funkcji mogą składać się z liter (bez polskichznakoacutew) cyfr i znaku podkreślenia z tym że nazwa taka nie może zaczynać się od cyfry Niemożna używać nazw zarezerwowanych (patrz Składnia)

Przykłady błędnych nazw

2liczba (nie można zaczynać nazwy od cyfry)moja funkcja (nie można używać spacji)$i (nie można używać znaku $)if (if to słowo kluczowe)

Aby kod był bardziej czytelny przestrzegajmy poniższych (umownych) reguł

nazwy zmiennych piszemy małymi literami i file

nazwy stałych (zadeklarowanych przy pomocy define) piszemy wielkimi literamiSIZE

nazwy funkcji piszemy małymi literami print

wyrazy w nazwach oddzielamy znakiem podkreślenia open file close all files

Są to tylko konwencje mdash żaden kompilator nie zgłosi błędu jeśli wprowadzimy swoacutej wła-sny system nazewnictwa Jednak warto pamiętać że być może nad naszym kodem będą pra-cowali także inni programiści ktoacuterzy mogą mieć trudności z analizą kodu niespełniającegopewnych zasad

Rozdział 7

Zmienne

Procesor komputera stworzony jest tak aby przetwarzał dane znajdujące się w pamięci kom-putera Z punktu widzenia programu napisanego w języku C (ktoacutery jak wiadomo jest języ-kiem wysokiego poziomu) dane umieszczane są w postaci tzw zmienny Zmienne uła-twiają programiście pisanie programu Dzięki nim programista nie musi się przejmowaćgdzie w pamięci owe zmienne się znajdują tzn nie operuje fizycznymi adresami pamięcijak np 0x14613467 tylko prostą do zapamiętania nazwą zmiennej

71 Czym są zmienneZmienna jest to pewien fragment pamięci o ustalonym rozmiarze ktoacutery posiada własny iden-tyfikator (nazwę) oraz może przechowywać pewną wartość zależną od typu zmiennej

711 Deklaracja zmienny

Aby moacutec skorzystać ze zmiennej należy ją przed użyciem zadeklarować to znaczy poinfor-mować kompilator jak zmienna będzie się nazywać i jaki typ ma mieć Zmienne deklarujesię w sposoacuteb następujący

typ nazwa_zmiennej

Oto deklaracja zmiennej o nazwie ldquowiekrdquo typu ldquointrdquo czyli liczby całkowitej

int wiek

Zmiennej w momencie zadeklarowania można od razu przypisać wartość

int wiek = 17

W języku C zmienne deklaruje się na samym początku bloku (czyli przed pierwszą in-strukcją)

int wiek = 17printf(dn wiek)int kopia_wieku tu stary kompilator C zgłosi błąd

deklaracja występuje po instrukcji (printf) kopia_wieku = wiek

33

34 ROZDZIAŁ 7 ZMIENNE

Według nowszych standardoacutewmożliwe jest deklarowanie zmiennej w dowolnymmiejscuprogramu ale wtedy musimy pamiętać aby zadeklarować zmienną przed jej użyciem Toznaczy że taki kod jest niepoprawny

printf (Mam d latn wiek)int wiek = 17

Należy go zapisać tak

int wiek = 17printf (Mam d latn wiek)

Język C nie inicjalizuje zmiennych lokalnych Oznacza to że w nowo zadeklarowanejzmiennej znajdują się śmieci - to co wcześniej zawierał przydzielony zmiennej fragmentpamięci Aby uniknąć ciężkich do wykrycia błędoacutew dobrze jest inicjalizować (przypisywaćwartość) wszystkie zmienne w momencie zadeklarowania

712 Zasięg zmiennej

Zmienne mogą być dostępne dla wszystkich funkcji programu mdash nazywamy je wtedy zmien-nymi globalnymi Deklaruje się je przed wszystkimi funkcjami programu

include ltstdiohgt

int ab nasze zmienne globalne

void func1 ()

instrukcje a=3 dalsze instrukcje

int main ()

b=3a=2return 0

Zmienne globalne jeśli programista nie przypisze im innej wartości podczas definiowa-nia są inicjalizowane wartością

Zmienne ktoacutere funkcja deklaruje do ldquowłasnych potrzebrdquo nazywamy zmiennymi lokal-nymi Nasuwa się pytanie ldquoczy będzie błędem nazwanie tą samą nazwą zmiennej globalneji lokalnejrdquo Otoacuteż odpowiedź może być zaskakująca nie Natomiast w danej funkcji da sięużywać tylko jej zmiennej lokalnej Tej konstrukcji należy z wiadomych względoacutew unikać

int a=1 zmienna globalna

int main()

71 CZYM SĄ ZMIENNE 35

int a=2 to już zmienna lokalna printf(d a) wypisze 2

713 Czas życia

Czas życia to czas od momentu przydzielenia dla zmiennej miejsca w pamięci (stworzenieobiektu) do momentu zwolnienia miejsca w pamięci (likwidacja obiektu)

Zakres ważności to część programu w ktoacuterej nazwa znana jest kompilatorowi

main()

int a = 10 otwarcie lokalnego bloku

int b = 10printf(d d a b)

zamknięcie lokalnego bloku zmienna b jest usuwana

printf(d d a b) BŁĄD b juz nie istnieje tu usuwana jest zmienna a

Zdefiniowaliśmy dwie zmienne typu int Zaroacutewno a i b istnieją przez cały program (czasżycia) Nazwa zmiennej a jest znana kompilatorowi przez cały program Nazwa zmiennej bjest znana tylko w lokalnym bloku dlatego nastąpi błąd w ostatniej instrukcji

Niektoacutere kompilatory (prawdopodobniemożna tu zaliczyćMicrosoVisual C++ dowersji) uznają powyższy kod za poprawny W dodatku można ustawić w opcjach niektoacuterychkompilatoroacutew zachowanie w takiej sytuacji włącznie z zachowaniami niezgodnymi ze stan-dardem języka

Możemy świadomie ograniczyć ważność zmiennej do kilku linijek programu (tak jak ro-biliśmy wyżej) tworząc blok Nazwa zmiennej jest znana tylko w tym bloku

714 Stałe

Stała roacuteżni się od zmiennej tylko tym że nie można jej przypisać innej wartości w trak-cie działania programu Wartość stałej ustala się w kodzie programu i nigdy ona nie ulegazmianie Stałą deklaruje się z użyciem słowa kluczowego const w sposoacuteb następujący

const typ nazwa_stałej=wartość

Dobrze jest używać stałych w programie ponieważ unikniemy wtedy przypadkowychpomyłek a kompilator może często zoptymalizować ich użycie (np od razu podstawiając ichwartość do kodu)

36 ROZDZIAŁ 7 ZMIENNE

const int WARTOSC_POCZATKOWA=5int i=WARTOSC_POCZATKOWAWARTOSC_POCZATKOWA=4 tu kompilator zaprotestuje int j=WARTOSC_POCZATKOWA

Przykład pokazuje dobry zwyczaj programistyczny jakim jest zastępowanie umieszczo-nych na stałe w kodzie liczb stałymi W ten sposoacuteb będziemy mieli większą kontrolę nadkodem mdash stałe umieszczone w jednym miejscu można łatwo modyfikować zamiast szukaćpo całym kodzie liczb ktoacutere chcemy zmienić

Nie mamy jednak pełnej gwarancji że stała będzie miała tę samą wartość przez cały czaswykonania programu możliwe jest bowiem dostanie się do wartości stałej (miejsca jej prze-chowywania w pamięci) pośrednio mdash za pomocą wskaźnikoacutew Można zatem dojść do wnio-sku że słowo kluczowe const służy tylko do poinformowania kompilatora aby ten nie zezwa-lał na jawną zmianę wartości stałej Z drugiej strony zgodnie ze standardem proacuteba mody-fikacji wartości stałej ma niezdefiniowane działanie (tzw undefined behaviour) i w związkuz tym może się powieść lub nie ale może też spowodować jakieś subtelne zmiany ktoacutere wefekcie spowodują że program będzie źle działał

Podobnie do zdefiniowania stałej możemy użyć dyrektywy preprocesora define (opi-sanej w dalszej części podręcznika) Tak zdefiniowaną stałą nazywamy stałą symbolicznąW przeciwieństwie do stałej zadeklarowanej z użyciem słowa const stała zdefiniowana przyużyciu define jest zastępowana daną wartością w każdym miejscu gdzie występuje dlategoteż może być używana w miejscach gdzie ldquonormalnardquo stała nie mogłaby dobrze spełnić swejroli

W przeciwieństwie do języka C++ w C stała to cały czas zmienna ktoacuterej kompilatorpilnuje by nie zmieniła się

72 Typy zmienny

Każdy program w C operuje na zmiennych mdash wydzielonych w pamięci komputera obsza-rach ktoacutere mogą reprezentować obiekty nam znane takie jak liczby znaki czy też bardziejzłożone obiekty Jednak dla komputera każdy obszar w pamięci jest taki sam mdash to ciąg zeri jedynek w takiej postaci zupełnie nieprzydatny dla programisty i użytkownika Podczaspisania programu musimy wskazać w jaki sposoacuteb ten ciąg ma być interpretowany

Typ zmiennej wskazuje właśnie sposoacuteb w jaki pamięć w ktoacuterej znajduje się zmiennabędzie wykorzystywana Określając go przekazuje się kompilatorowi informację ile pamięcitrzeba zarezerwować dla zmiennej a także w jaki sposoacuteb wykonywać na nim operacje

Każda zmienna musi mieć określony swoacutej typ w miejscu deklaracji i tego typu nie możejuż zmienić Lecz co jeśli mamy zmienną jednego typu ale potrzebujemy w pewnymmiejscuprogramu innego typu danych W takimwypadku stosujemy konwersję (rzutowanie) jednejzmiennej na inną zmienną Rzutowanie zostanie opisane poacuteźniej w rozdziale Operatory

Istnieją wbudowane i zdefiniowane przez użytkownika typy danych Wbudowane typydanych to te ktoacutere zna kompilator są one w nim bezpośrednio ldquozaszyterdquo Można też tworzyćwłasne typy danych ale należy je kompilatorowi opisać Więcej informacji znajduje się wrozdziale Typy złożone

W języku C wyroacuteżniamy podstawowe typy zmiennych Są to

char mdash jednobajtowe liczby całkowite służy do przechowywania znakoacutew

int mdash typ całkowity o długości domyślnej dla danej architektury komputera

72 TYPY ZMIENNYCH 37

float mdash typ zmiennopozycyjny (zwany roacutewnież zmiennoprzecinkowym) reprezentującyliczby rzeczywiste ( bajty)

double mdash typ zmiennopozycyjny podwoacutejnej precyzji ( bajtoacutew)

Typy zmiennoprzecinkowe zostały dokładnie opisane w IEEE

W języku C nie istnieje specjalny typ zmiennych przeznaczony na zmienne typu logicz-nego (albo ldquoprawda albo ldquofałszrdquo) Jest to inne podejście niż na przykład w językach Pascalalbo Java - definiujących osobny typ ldquobooleanrdquo ktoacuterego nie można ldquomieszaćz innymi typamizmiennych W C do przechowywania wartości logicznych zazwyczaj używa się typu ldquointrdquoWięcej na temat tego jak język C rozumie prawdę i fałsz znajduje się w rozdziale Operatory

721 int

Ten typ przeznaczony jest do liczb całkowitych Liczby temożemy zapisać na kilka sposoboacutew

System dziesiętny

12 13 45 35 itd

System oacutesemkowy (oktalny)

010 czyli 8016 czyli 8 + 6 = 14018 BŁĄD

System ten operuje na cyfrach od do Tak wiec jest niedozwolona Jeżeli chcemyużyć takiego zapisu musimy zacząć liczbę od

System szesnastkowy (heksadecymalny)

0x10 czyli 116 + 0 = 160x12 czyli 116 + 2 = 180xff czyli 1516 + 15 = 255

W tym systemie możliwe cyfry to hellip i dodatkowo a b c d e f ktoacutere oznaczają Aby użyć takiego systemu musimy poprzedzić liczbę ciągiem 0x Wielkośćznakoacutew w takich literałach nie ma znaczenia

Ponadto w niektoacuterych kompilatorach przeznaczonych głoacutewnie domikrokontroleroacutew spo-tyka się jeszcze użycie systemu binarnego Zazwyczaj dodaje się przedrostek 0b przed liczbą(analogicznie do zapisu spotykanego w języku Python) W tym systemie możemy oczywiścieużywać tylko i wyłącznie cyfr i Tego typu rozszerzenie bardzo ułatwia programowanieniskopoziomowe układoacutew Należy jednak pamiętać że jest to tylko i wyłącznie rozszerzenie

38 ROZDZIAŁ 7 ZMIENNE

722 float

Ten typ oznacza liczby zmiennoprzecinkowe czyli ułamki Istnieją dwa sposoby zapisu

System dziesiętny

314 45644 2354 321 itd

System ldquonaukowyrdquo mdash wykładniczy

6e2 czyli 6 102 czyli 60015e3 czyli 15 103 czyli 150034e-3 czyli 34 10minus3 czyli 00034

Należy wziąć pod uwagę że reprezentacja liczb rzeczywistych w komputerze jest niedo-skonała i możemy otrzymywać wyniki o zauważalnej niedokładności

723 double

Doublemdash czyli ldquopodwoacutejnyrdquomdash oznacza liczby zmiennoprzecinkowe podwoacutejnej precyzji Ozna-cza to że liczba taka zajmuje zazwyczaj w pamięci dwa razy więcej miejsca niż float (np bity wobec dla float) ale ma też dwa razy lepszą dokładność

Domyślnie ułamki wpisane w kodzie są typu double Możemy to zmienić dodając nakońcu literę ldquordquo

15f (float)15 (double)

724 ar

Jest to typ znakowy umożliwiający zapis znakoacutew ASCII Może też być traktowany jako liczbaz zakresu Znaki zapisujemywpojedynczych cudzysłowach (czasami nazywanymi apo-strofami) by odroacuteżnić je od łańcuchoacutew tekstowych (pisanych w podwoacutejnych cudzysłowach)

a 7 $

Pojedynczy cudzysłoacutew rsquo zapisujemy tak a null (czyli zero ktoacutere między innymikończy napisy) tak 0 Więcej znakoacutew specjalnych

Warto zauważyć że typ char to zwykły typ liczbowy i można go używać tak samo jaktypu int (zazwyczaj ma jednak mniejszy zakres) Co więcej literały znakowe (np rsquoarsquo) sątraktowane jako liczby i w języku C są typu int (w języku C++ są typu char)

725 void

Słowa kluczowego void można w określonych sytuacjach użyć tam gdzie oczekiwana jestnazwa typu void nie jest właściwym typem bo nie można utworzyć zmiennej takiego typujest to ldquopustyrdquo typ (ang void znaczy ldquopustyrdquo) Typ void przydaje się do zaznaczania żefunkcja nie zwraca żadnej wartości lub że nie przyjmuje żadnych parametroacutew (więcej o tymw rozdziale Funkcje) Można też tworzyć zmienne będące typu ldquowskaźnik na voidrdquo

73 SPECYFIKATORY 39

73 Specyfikatory

Specyfikatory to słowa kluczowe ktoacutere postawione przy typie danych zmieniają jego zna-czenie

731 signed i unsigned

Na początku zastanoacutewmy się jak komputer może przechować liczbę ujemną Otoacuteż w przy-padku przechowywania liczb ujemnych musimyw zmiennej przechować jeszcze jej znak Jakwiadomo zmienna składa się z szeregu bitoacutew W przypadku użycia zmiennej pierwszy bit zlewej strony (nazywany także bitem najbardziej znaczącym) przechowuje znak liczby Efek-tem tego jest spadek ldquopojemnościrdquo zmiennej czyli zmniejszenie największej wartości ktoacuterąmożemy przechować w zmiennej

Signed oznacza liczbę ze znakiem unsigned mdash bez znaku (nieujemną) Mogą być zasto-sowane do typoacutew char i int i łączone ze specyfikatorami short i long (gdy ma to sens)

Jeśli przy signed lub unsigned nie napiszemy o jaki typ nam chodzi kompilator przyjmiewartość domyślną czyli int

Przykładowo dla zmiennej char(zajmującej bitoacutew zapisanej w formacie uzupełnień dodwoacutech) wygląda to tak

signed char a zmienna a przyjmuje wartości od -128 do 127 unsigned char b zmienna b przyjmuje wartości od 0 do 255 unsigned short cunsigned long int d

Jeżeli nie podamy żadnego ze specyfikatora wtedy liczba jest domyślnie przyjmowanajako signed (nie dotyczy to typu char dla ktoacuterego jest to zależne od kompilatora)

signed int i = 0 jest roacutewnoznaczne zint i = 0

Liczby bez znaku pozwalają nam zapisać większe liczby przy tej samej wielkości zmiennejmdash ale trzeba uważać by nie zejść z nimi poniżej zera mdash wtedy ldquoprzewijająrdquo się na sam konieczakresu co może powodować trudne do wykrycia błędy w programach

732 short i long

Short i long są wskazoacutewkami dla kompilatora by zarezerwował dla danego typu mniej (od-powiednio mdash więcej) pamięci Mogą być zastosowane do dwoacutech typoacutew int i double (tylkolong) mając roacuteżne znaczenie

Jeśli przy short lub long nie napiszemy o jaki typ nam chodzi kompilator przyjmie war-tość domyślną czyli int

Należy pamiętać że to jedynie życzenie wobec kompilatora mdash w wielu kompilatorachtypy int i long int mają ten sam rozmiar Standard języka C nakłada jedynie na kompilatorynastępujące ograniczenia int mdash nie może być kroacutetszy niż bitoacutew int mdash musi byćdłuższy lub roacutewny short a nie może być dłuższy niż long short int mdash nie może byćkroacutetszy niż bitoacutew long int mdash nie może być kroacutetszy niż bity

Zazwyczaj typ int jest typem danych o długości odpowiadającej wielkości rejestroacutew pro-cesora czyli na procesorze szesnastobitowym ma bitoacutew na trzydziestodwubitowym mdash

40 ROZDZIAŁ 7 ZMIENNE

itd1 Z tego powodu jeśli to tylko możliwe do reprezentacji liczb całkowitych preferowanejest użycie typu int bez żadnych specyfikatoroacutew rozmiaru

74 Modyfikatory

741 volatile

volatile znaczy ulotny Oznacza to że kompilator wyłączy dla takiej zmiennej optymaliza-cje typu zastąpienia przez stałą lub zawartość rejestru za to wygeneruje kod ktoacutery będzieodwoływał się zawsze do komoacuterek pamięci danego obiektu Zapobiegnie to błędowi gdyobiekt zostaje zmieniony przez część programu ktoacutera nie ma zauważalnego dla kompilatorazwiązku z danym fragmentem kodu lub nawet przez zupełnie inny proces

volatile float liczba1float liczba2

printf (fnfn liczba1 liczba2) instrukcje nie związane ze zmiennymi printf (fnf liczba1 liczba2)

Jeżeli zmienne liczba i liczba zmienią się niezauważalnie dla kompilatora to odczytując

liczba mdash nastąpi odwołanie do komoacuterek pamięci Kompilator pobierze nową wartośćzmiennej

liczba mdash kompilator może wypisać poprzednią wartość ktoacuterą przechowywał w reje-strze

Modyfikator volatile jest rzadko stosowany i przydaje się w wąskich zastosowaniachjak wspoacutełbieżność i wspoacutełdzielenie zasoboacutew oraz przerwania systemowe

742 register

Jeżeli utworzymy zmienną ktoacuterej będziemy używać w swoim programie bardzo często mo-żemy wykorzystać modyfikator register Kompilator może wtedy umieścić zmienną w re-jestrze do ktoacuterego ma szybki dostęp co przyśpieszy odwołania do tej zmiennej

register int liczba

W nowoczesnych kompilatorach ten modyfikator praktycznie nie ma wpływu na pro-gram Optymalizator sam decyduje czy i co należy umieścić w rejestrze Nie mamy żadnejgwarancji że zmienna tak zadeklarowana rzeczywiście się tam znajdzie chociaż dostęp doniej może zostać przyspieszony w inny sposoacuteb Raczej powinno się unikać tego typu kon-strukcji w programie

1Wiąże się to z pewnymi uwarunkowaniami historycznymi Podręcznik do języka C duetu KampR zakładał żetyp int miał się odnosić do typowej dla danego procesora długości liczby całkowitej Natomiast jeśli procesor moacutegłobsługiwać typy dłuższe lub kroacutetsze stosownego znaczenia nabierałymodyfikatory short i long Dobrymprzykłademmoże być architektura i386 ktoacutera umożliwia obliczenia na liczbach 16-bitowych Dlatego też modyfikator shortpowoduje skroacutecenie zmiennej do 16 bitoacutew

75 UWAGI 41

743 static

Pozwala na zdefiniowanie zmiennej statycznej ldquoStatycznośćrdquo polega na zachowaniu warto-ści pomiędzy kolejnymi definicjami tej samej zmiennej Jest to przede wszystkim przydatnew funkcjach Gdy zdefiniujemy zmienną w ciele funkcji to zmienna ta będzie od nowa defi-niowana wraz z domyślną wartością (jeżeli taką podano) W wypadku zmiennej określonejjako statyczna jej wartość się nie zmieni przy ponownym wywołaniu funkcji Na przykład

void dodaj(int liczba)

int zmienna = 0 bez staticzmienna = zmienna + liczbaprintf (Wartosc zmiennej dn zmienna)

Gdy wywołamy tę funkcję np razy w ten sposoacuteb

dodaj(3)dodaj(5)dodaj(4)

to ujrzymy na ekranie

Wartosc zmiennej 3Wartosc zmiennej 5Wartosc zmiennej 4

jeżeli jednak deklarację zmiennej zmienimy na static int zmienna = 0 to wartość zmiennejzostanie zachowana i po podobnym wykonaniu funkcji powinnyśmy ujrzeć

Wartosc zmiennej 3Wartosc zmiennej 8Wartosc zmiennej 12

Zupełnie co innego oznacza static zastosowane dla zmiennej globalnej Jest ona wtedywidoczna tylko w jednym pliku Zobacz też rozdział Biblioteki

744 extern

Przez extern oznacza się zmienne globalne zadeklarowane w innych plikach mdash informujemyw ten sposoacuteb kompilator żeby nie szukał jej w aktualnym pliku Zobacz też rozdział Biblio-teki

745 auto

Zupełnym archaizmem jest modyfikator auto ktoacutery oznacza tyle że zmienna jest lokalnaPonieważ zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna modyfikator tennie ma obecnie żadnego zastosowania praktycznego auto jest spadkiem po wcześniejszychjęzykach programowania na ktoacuterych oparty jest C (np B)

75 Uwagi Język C++ pozwala na mieszanie deklaracji zmiennych z kodem Więcej informacji w

C++Zmienne

42 ROZDZIAŁ 7 ZMIENNE

Rozdział 8

Operatory

81 Przypisanie

Operator przypisania (=rdquo) jak sama nazwa wskazuje przypisuje wartość prawego argu-mentu lewemu np

int a = 5 bb = aprintf(dn b) wypisze 5

Operator ten ma łączność prawostronną tzn obliczanie przypisań następuje z prawa nalewo i zwraca on przypisaną wartość dzięki czemu może być użyty kaskadowo

int a b ca = b = c = 3printf(d d dn a b c) wypisze 3 3 3

811 Skroacutecony zapis

C umożliwia też skroacutecony zapis postaci a = b gdzie jest jednym z operatoroacutew + - amp | ˆ ltlt lub gtgt (opisanych niżej) Ogoacutelnie rzecz ujmując zapis a = b jest roacutewnoważnyzapisowi a = a (b) np

int a = 1a += 5 to samo co a = a + 5 a = a + 2 to samo co a = a (a + 2) a = 2 to samo co a = a 2

Początkowo skroacutecona notacja miała następującą składnię a = b co często prowadziło doniejasności np i =- (i = - czy też i = i-) Dlatego też zdecydowano się zmienić kolejnośćoperatoroacutew

43

44 ROZDZIAŁ 8 OPERATORY

82 Rzutowanie

Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu Konwer-sja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podanaexplicite przez programistę) Oto kilka przykładoacutew konwersji niejawnej

int i = 427 konwersja z double do int float f = i konwersja z int do float double d = f konwersja z float do double unsigned u = i konwersja z int do unsigned int f = 42 konwersja z double do float i = d konwersja z double do int char str = foo konwersja z const char do char [1] const char cstr = str konwersja z char do const char void ptr = str konwersja z char do void

Podczas konwersji zmiennych zawierających większe ilości danych do typoacutew prostszych(np double do int) musimy liczyć się z utratą informacji jak to miało miejsce w pierwszejlinijce mdash zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta iw rezultacie zmiennej została przypisana wartość

Zaskakująca może się wydać linijka oznaczona przez 1 Niejawna konwersja z typu constchar do typu char nie jest dopuszczana przez standard C Jednak literały napisowe (ktoacutere sątypu const char) stanowią tutaj wyjątek Wynika on z faktu że były one używane na długoprzed wprowadzeniem słoacutewka const do języka i brak wspomnianegowyjątku spowodowałbyże duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod

Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania np

double d = 314int pi = (int)d 1 pi = (unsigned)pi gtgt 4 2

W pierwszym przypadku operator został użyty by zwroacutecić uwagę na utratę precyzji Wdrugim dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej

Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne kon-wersje (tj konwersja z double do int oraz z int do unsigned int) jednak niektoacutere konwersjesą błędne np

const char cstr = foochar str = cstr

W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję

const char cstr = foochar str = (char)cstr

Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompi-lator Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzieon robił i czy nie ma innego sposobu wykonania danej operacji ktoacutery nie wymagałby podej-mowania tak drastycznych krokoacutew

83 OPERATORY ARYTMETYCZNE 45

83 Operatory arytmetyczne

W arytmetyce komputerowej nie działa prawo łączności oraz rozdzielności Wynika toz ograniczonego rozmiaru zmiennych ktoacutere przechowują wartości Przykład dla zmiennycho długości bitoacutew (bez znaku) Maksymalna wartość ktoacuterą może przechowywać typ to216minus1 = 65535 Zatem operacja typu 65530+10minus20 zapisana jako (65530+10)minus20 możezaowocować czymś zupełnie innym niż 65530+(10minus20) W pierwszym przypadku zapewnedojdzie do tzw przepełnienia - procesor nie będzie miał miejsca aby zapisać dodatkowybit Zachowanie programu będzie w takim przypadku zależało od architektury procesoraAnalogiczny przykład możemy podać dla rozdzielności mnożenia względem dodawania

Język C definiuje następujące dwuargumentowe operatory arytmetyczne

dodawanie (+rdquo)

odejmowanie (-rdquo)

mnożenie (rdquo)

dzielenie (rdquo)

reszta z dzielenia (rdquo) określona tylko dla liczb całkowitych (tzw dzielenie modulo)

int a=7 b=2 cc = a bprintf (dnc) wypisze 1

Należy pamiętać że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak naj-większy z argumentoacutew Oznacza to że operacja wykonana na dwoacutech liczbach całkowitychnadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej Dla przy-kładu poniższy kod

float a = 7 2printf(fn a)

wypisze (wbrew oczekiwaniu początkujących programistoacutew) 30 a nie 35 Odnosi sięto nie tylko do dzielenia ale także mnożenia np

float a = 1000 1000 1000 1000 1000 1000printf(fn a)

prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali Aby wymusićobliczenia rzeczywiste należy zmienić typ jednego z argumentoacutew na liczbę rzeczywistą poprostu zmieniając literał lub korzystając z rzutowania np

float a = 70 2float b = (float)1000 1000 1000 1000 1000 1000printf(fn a)printf(fn b)

Operatory dodawania i odejmowania są określone roacutewnież gdy jednym z argumentoacutewjest wskaźnik a drugim liczba całkowita Ten drugi jest także określony gdy oba argumentysą wskaźnikami O takim użyciu tych operatoroacutew dowiesz się więcej CWskaźniki|w dalszejczęści książki

46 ROZDZIAŁ 8 OPERATORY

831 Inkrementacja i dekrementacja

Aby skroacutecić zapis wprowadzono dodatkowe operatory inkrementacji (++rdquo) i dekrementa-cji (ndashrdquo) ktoacutere dodatkowo mogą być pre- lub postfiksowe W rezultacie mamy więc czteryoperatory

pre-inkrementacja (++irdquo)

post-inkrementacja (i++rdquo)

pre-dekrementacja (ndashirdquo) i

post-dekrementacja (indashrdquo)

Operatory inkrementacji zwiększa a dekrementacji zmniejsza argument o jeden Ponadtooperatory pre- zwracają nową wartość argumentu natomiast post- starą wartość argumentu

int a b ca = 3b = a-- po operacji b=3 a=2 c = --b po operacji b=2 c=2

Czasami (szczegoacutelnie w C++) użycie operatoroacutew stawianych za argumentem jest niecomniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tym-czasową)

Bardzo ważne jest abyśmy poprawnie stosowali operatory dekrementacji i inkrementa-cji Chodzi o to aby w jednej instrukcji nie umieszczać kilku operatoroacutew ktoacutere modyfikująten sam obiekt (zmienną) Jeżeli taka sytuacja zaistnieje to efekt działania instrukcji jestnieokreślony Prostym przykładem mogą być następujące instrukcje

int a = 1a = a++a = ++aa = a++ + ++aprintf(d dn ++a ++a)printf(d dn a++ a++)

Kompilator potrafi ostrzegać przed takimi błędami - aby to czynił należy podać mujako argument opcję -Wsequence-point

84 Operacje bitoweOproacutecz operacji znanych z lekcji matematyki w podstawoacutewce język C został wyposażonytakże w operatory bitowe zdefiniowane dla liczb całkowitych Są to

negacja bitowa (˜rdquo)

koniunkcja bitowa (amprdquo)

alternatywa bitowa (|rdquo) i

84 OPERACJE BITOWE 47

alternatywa rozłączna () (ˆrdquo)

Działają one na poszczegoacutelnych bitach przez co mogą być szybsze od innych operacjiDziałanie tych operatoroacutew można zdefiniować za pomocą poniższych tabel

~ | 0 1 amp | 0 1 | | 0 1 ^ | 0 1-----+----- -----+----- -----+----- -----+-----

| 1 0 0 | 0 0 0 | 0 1 0 | 0 11 | 0 1 1 | 1 1 1 | 1 0

a | 0101 = 5b | 0011 = 3

-------+------~a | 1010 = 10~b | 1100 = 12

a amp b | 0001 = 1a | b | 0111 = 7a ^ b | 0110 = 6

Lub bardziej opisowo

negacja bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych argument miał bity roacutewne zero

koniunkcja bitowa daje wwyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych oba argumenty miały bity roacutewne jeden (mnemonik gdy wszystkie)

alternatywa bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden na wszystkichtych pozycjach na ktoacuterych jeden z argumentoacutew miał bit roacutewny jeden (mnemonik jeśli jest )

alternatywa rozłączna daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tychpozycjach na ktoacuterych tylko jeden z argumentoacutew miał bit roacutewny jeden (mnemonik gdy roacuteżne)

Przy okazji warto zauważyć że aˆbˆb to po prostu a Właściwość ta została wykorzystanaw roacuteżnych algorytmach szyfrowania oraz funkcjach haszujących Alternatywę wyłączną sto-suje się np do szyfrowania kodu wirusoacutew polimorficznych

841 Przesunięcie bitowe

Dodatkowo język C wyposażony jest w operatory przesunięcia bitowego w lewo (ltltrdquo) iprawo (gtgtrdquo) Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycjipodaną jako prawy argument Brzmi to może strasznie ale wcale takie nie jest Rozważmy-bitowe liczby bez znaku (taki hipotetyczny unsigned int) woacutewczas

a | altlt1 | altlt2 | agtgt1 | agtgt2------+------+------+------+------0001 | 0010 | 0100 | 0000 | 00000011 | 0110 | 1100 | 0001 | 00000101 | 1010 | 0100 | 0010 | 0001

48 ROZDZIAŁ 8 OPERATORY

1000 | 0000 | 0000 | 0100 | 00101111 | 1110 | 1100 | 0111 | 00111001 | 0010 | 0100 | 0100 | 0010

Nie jest to zatem takie straszne na jakie wygląda Widać że bity będące na skraju sątracone a w pusterdquo miejsca wpisywane są zera

Inaczej rzecz się ma jeżeli lewy argument jest liczbą ze znakiem Dla przesunięcia bito-wego w lewo a ltlt b jeżeli a jest nieujemna i wartość a middot 2b mieści się w zakresie liczby tojest to wynikiem operacji W przeciwnym wypadku działanie jest niezdefiniowane1

Dla przesunięcia bitowego w lewo jeżeli lewy argument jest nieujemny to operacja za-chowuje się tak jak w przypadku liczb bez znaku Jeżeli jest on ujemny to zachowanie jestzależne od implementacji

Zazwyczaj operacja przesunięcia w lewo zachowuje się tak samo jak dla liczb bez znakunatomiast przy przesuwaniu w prawo bit znaku nie zmienia się2

a | agtgt1 | agtgt2------+------+------0001 | 0000 | 00000011 | 0001 | 00000101 | 0010 | 00011000 | 1100 | 11101111 | 1111 | 11111001 | 1100 | 1110

Przesunięcie bitowe w lewo odpowiada pomnożeniu natomiast przesunięcie bitowe wprawo podzieleniu liczby przez dwa do potęgi jaką wyznacza prawy argument Jeżeli prawyargument jest ujemny lub większy lub roacutewny liczbie bitoacutew w typie działanie jest niezdefi-niowane

include ltstdiohgt

int main ()

int a = 6printf (6 ltlt 2 = dn altlt2) wypisze 24 printf (6 gtgt 2 = dn agtgt2) wypisze 1 return 0

85 PoroacutewnanieW języku C występują następujące operatory poroacutewnania

roacutewne (==rdquo)

roacuteżne (=rdquo)

mniejsze (ltrdquo)

1Niezdefiniowane w takim samym sensie jak niezdefiniowane jest zachowanie programu gdy proacutebujemy odwo-łać się do wartości wskazywanej przez wartość czy do zmiennych poza tablicą

2ale jeżeli zależy Ci na przenośności kodu nie możesz na tym polegać

85 POROacuteWNANIE 49

większe (gtrdquo)

mniejsze lub roacutewne (lt=rdquo) i

większe lub roacutewne (gt=rdquo)

Wykonują one odpowiednie poroacutewnanie swoich argumentoacutew i zwracają jedynkę jeżeliwarunek jest spełniony lub zero jeżeli nie jest

851 Częste błędy

Osoby ktoacutere poprzednio uczyły się innych językoacutew programowania często mają nawykużywania w instrukcjach logicznych zamiast operatora poroacutewnania == operatora przypi-sania = Ma to często zgubne efekty gdyż przypisanie zwraca wartość przypisaną lewemuargumentowi

Poroacutewnajmy ze sobą dwa warunki

(a = 1)(a == 1)

Pierwszy z nich zawsze będzie prawdziwy niezależnie od wartości zmiennej a Dziejesię tak ponieważ zostaje wykonane przypisanie do a wartości a następnie jako wartość jestzwracane to co zostało przypisane mdash czyli jeden Drugi natomiast będzie prawdziwy tylkogdy a jest roacutewne

W celu uniknięcia takich błędoacutew niektoacuterzy programiści zamiast pisać a == 1 piszą 1 == adzięki czemu pomyłka spowoduje że kompilator zgłosi błąd

Warto zauważyć że kompilator potrafi w pewnych sytuacjach wychwycić taki błądAby zaczął to robić należy podać mu argument -Wparentheses

Innym błędem jest użycie zwykłych operatoroacutew poroacutewnania do sprawdzania relacji po-między liczbami rzeczywistymi Ponieważ operacje zmiennoprzecinkowe wykonywane są zpewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie roacutewne Dlaprzykładu

include ltstdiohgtint main ()

float a b ca = 1e10 tj 10 do potęgi 10 b = 1e-10 tj 10 do potęgi -10 c = b c = b c = c + a c = b + a (teoretycznie) c = c - a c = b + a - a = b (teoretycznie) printf(dn c == b) wypisze 0

Obejściem jest poroacutewnywanie modułu roacuteżnicy liczb Roacutewnież i takie błędy kompilator potrafi wykrywać mdash aby to robił należy podać mu argument -Wfloat-equal

50 ROZDZIAŁ 8 OPERATORY

86 Operatory logiczneAnalogicznie do części operatoroacutew bitowych w C definiuje się operatory logiczne miano-wicie

negację (zaprzeczenie)

koniunkcję (ldquoirdquo) ampamp

alternatywę (ldquolubrdquo) ||

Działają one bardzo podobnie do operatoroacutew bitowych jednak zamiast operować na po-szczegoacutelnych bitach biorą pod uwagę wartość logiczną argumentoacutew

861 ldquoPrawdardquo i ldquofałszrdquo w języku C

Język C nie przewiduje specjalnego typu danych do operacji logicznych mdash operatory logicznemożna stosować do liczb (np typu int) tak samo jak operatory bitowe albo arytmetyczne

Wyrażenie ma wartość logiczną wtedy i tylko wtedy gdy jest roacutewne (jest ldquofałszywerdquo)W przeciwnym wypadku ma wartość (jest ldquoprawdziwerdquo) Operatory logiczne w wynikudają zawsze albo albo

Żeby w pełni uzmysłowić sobie co to to oznacza spoacutejrzmy na wynik wykonania poniż-szych trzech linijek

printf(koniunkcja dn 18 ampamp 19)printf(alternatywa dn a || b)printf(negacja dn 20)

koniunkcja 1alternatywa 1negacja 0

Liczba nie jest roacutewna więc ma wartość logiczną Podobnie ma wartość logiczną Dlatego ich koniunkcja jest roacutewna Znaki a i b zostaną w wyrażeniu logicznympotraktowane jako liczby o wartości odpowiadającej kodowi znaku mdash czyli oba będąmiały wartość logiczną

862 Skroacutecone obliczanie wyrażeń logiczny

Język C wykonuje skroacutecone obliczanie wyrażeń logicznych mdash to znaczy oblicza wyrażenietylko tak długo jak nie wie jaka będzie jego ostateczna wartość To znaczy idzie od lewejdo prawej obliczając kolejne wyrażenia (dodatkowo na kolejność wpływ mają nawiasy) i gdybędzie miał na tyle informacji by obliczyć wartość całości nie liczy reszty Może to wydawaćsię niejasne ale przyjrzyjmy się wyrażeniom logicznym

A ampamp BA || B

Jeśli A jest fałszywe to nie trzeba liczyć B w pierwszym wyrażeniu bo fałsz i dowolne wyra-żenie zawsze da fałsz Analogicznie jeśli A jest prawdziwe to wyrażenie jest prawdziwe iwartość B nie ma znaczenia

Poza zwiększoną szybkością zysk z takiego rozwiązania polega na możliwości stosowaniaefektoacutew ubocznych Idea efektu ubocznego opiera się na tym że w wyrażeniu można wywo-łać funkcje ktoacutere będą robiły poza zwracaniemwyniku inne rzeczy oraz używać podstawieńPopatrzmy na poniższy przykład

87 OPERATOR WYRAŻENIA WARUNKOWEGO 51

( (a gt 0) || (a lt 0) || (a = 1) )

Jeśli a będzie większe od to obliczona zostanie tylko wartość wyrażenia (a gt 0) mdash da onoprawdę czyli reszta obliczeń nie będzie potrzebna Jeśli a będzie mniejsze od zera najpierwzostanie obliczone pierwsze podwyrażenie a następnie drugie ktoacutere da prawdę Ciekawy bę-dzie jednak przypadek gdy a będzie roacutewne zero mdash do a zostanie wtedy podstawiona jedynkai całość wyrażenia zwroacuteci prawdę (bo jest traktowane jak prawda)

Efekty uboczne pozwalają na roacuteżne szaleństwa i wykonywanie złożonych operacji w sa-mych warunkach logicznych jednak przesadne używanie tego typu konstrukcji powodujeże kod staje się nieczytelny i jest uważane za zły styl programistyczny

87 Operator wyrażenia warunkowegoC posiada szczegoacutelny rodzaj operatora mdash to operator zwany też operatorem wyrażeniawarunkowego Jest to jedyny operator w tym języku przyjmujący trzy argumenty

a b c

Jego działanie wygląda następująco najpierw oceniana jest wartość logiczna wyrażenia ajeśli jest ono prawdziwe to zwracana jest wartość b jeśli natomiast wyrażenie a jest nie-prawdziwe zwracana jest wartość c

Praktyczne zastosowanie mdash znajdowanie większej z dwoacutech liczb

a = (bgt=c) b c Jeśli b jest większe bądź roacutewne c to zwroacuteć bW przeciwnym wypadku zwroacuteć c

lub zwracanie modułu liczby

a = a lt 0 -a a

Wartości wyrażeń są przy tym operatorze obliczane tylko jeżeli zachodzi taka potrzebanp w wyrażeniu 1 1 foo() funkcja foo() nie zostanie wywołana

88 Operator przecinekOperator przecinek jest dość dziwnym operatorem Powoduje on obliczanie wartości wyra-żeń od lewej do prawej po czym zwroacutecenie wartości ostatniego wyrażenia W zasadzie wnormalnym kodzie programu ma on niewielkie zastosowanie gdyż zamiast niego lepiej roz-dzielać instrukcje zwykłymi średnikami Ma on jednak zastosowanie w instrukcji sterującejfor

89 Operator sizeofOperator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanegotypu lub typu podanego wyrażenia Ma on dwa rodzaje sizeof(typ) lub sizeof wyrażeniePrzykładowo

include ltstdiohgt

int main()

52 ROZDZIAŁ 8 OPERATORY

printf(sizeof(short ) = dn sizeof(short ))printf(sizeof(int ) = dn sizeof(int ))printf(sizeof(long ) = dn sizeof(long ))printf(sizeof(float ) = dn sizeof(float ))printf(sizeof(double) = dn sizeof(double))return 0

Operator ten jest często wykorzystywany przy dynamicznej alokacji pamięci co zostanieopisane w rozdziale poświęconym wskaźnikom

Pomimo że w swej budowie operator sizeof bardzo przypomina funkcję to jednak niąnie jest Wynika to z trudności w implementacji takowej funkcji mdash jej specyfika musiałabyodnosić się bezpośrednio do kompilatora Ponadto jej argumentem musiałyby być typy anie zmienne W języku C nie jest możliwe przekazywanie typu jako argumentu Ponadtoczęsto zdarza się że rozmiar zmiennej musi być wiadomy jeszcze w czasie kompilacji mdash toewidentnie wyklucza implementację sizeof() jako funkcji

810 Inne operatory

Poza wyżej opisanymi operatorami istnieją jeszcze

operator []rdquo opisany przy okazji opisywania tablic

jednoargumentowe operatory rdquo i amprdquo opisane przy okazji opisywania wskaźnikoacutew

operatory rdquo i -gtrdquo opisywane przy okazji opisywania struktur i unii

operator ()rdquo będący operatorem wywołania funkcji

operator ()rdquo grupujący wyrażenia (np w celu zmiany kolejności obliczania

811 Priorytety i kolejność obliczeń

Jak w matematyce roacutewnież i w języku C obowiązuje pewna ustalona kolejność działań Abymoacutec ją określić należy ustalić dwa parametry danego operatora jego priorytet oraz łącz-ność Przykładowo operator mnożenia ma wyższy priorytet niż operator dodawania i z tegopowodu wwyrażeniu 2+2 middot2 najpierw wykonuje się mnożenie a dopiero potem dodawanie

Drugim parametrem jest łączność mdash określa ona od ktoacuterej stronywykonywane są działaniaw przypadku połączenia operatoroacutew o tym samym priorytecie Na przykład odejmowaniema łączność lewostronną i 2 minus 2 minus 2 da w wyniku - Gdyby miało łączność prawostronnąw wynikiem byłoby Przykładem matematycznego operatora ktoacutery ma łączność prawo-stronną jest potęgowanie np 322

jest roacutewne W języku C występuje dużo poziomoacutew operatoroacutew Poniżej przedstawiamy tabelkę ze

wszystkimi operatorami poczynając od tych z najwyższym priorytetem (wykonywanych napoczątku)

Duża liczba poziomoacutew pozwala czasami zaoszczędzić trochę milisekund w trakcie pisaniaprogramu i bajtoacutew na dysku gdyż często nawiasy nie są potrzebne nie należy jednak z tymprzesadzać gdyż kod programu może stać się mylący nie tylko dla innych ale po latach (czynawet i dniach) roacutewnież dla nas

812 KOLEJNOŚĆ WYLICZANIA ARGUMENTOacuteW OPERATORA 53

Tablica 81 Priorytety operatoroacutewOperator Łącznośćnawiasy nie dotyczyjednoargumentowe przyrostkowe [] -gt wywołanie funkcji postinkre-mentacja postdekrementacja

lewostronna

jednoargumentowe przedrostkowe ˜ + - amp sizeof preinkrementacjapredekrementacja rzutowanie

prawostronna

lewostronna+ - lewostronnaltlt gtgt lewostronnaltlt= gtgt= lewostronna== = lewostronnaamp lewostronnaˆ lewostronna| lewostronnaampamp lewostronna|| lewostronna prawostronnaoperatory przypisania prawostronna lewostronna

Warto także podkreślić że operator koniunkcji ma niższy priorytet niż operator poroacutew-nania3 Oznacza to że kod

if (flags amp FL_MASK == FL_FOO)

zazwyczaj da rezultat inny od oczekiwanego Najpierw bowiem wykona się poroacutewna-nie wartości FL MASK z wartością FL FOO a dopiero potem koniunkcja bitowa W takichsytuacjach należy pamiętać o użyciu nawiasoacutew

if ((flags amp FL_MASK) == FL_FOO)

Kompilator potrafi wykrywać takie błędy i aby to robił należy podać mu argument-Wparentheses

812 Kolejność wyliczania argumentoacutew operatoraW przypadku większości operatoroacutew (wyjątkami są tu ampamp || i przecinek) nie da się określićktoacutera wartość argumentu zostanie obliczona najpierw W większości przypadkoacutew nie mato większego znaczenia lecz w przypadku wyrażeń ktoacutere mają efekty uboczne wymuszeniekonkretnej kolejności może być potrzebne Weźmy dla przykładu program

include ltstdiohgt

int foo(int a) printf(dn a)

3Jest to zaszłość historyczna z czasoacutew gdy nie było logicznych operatoroacutew ampamp oraz || i zamiast nich stosowanooperatory bitowe amp oraz |

54 ROZDZIAŁ 8 OPERATORY

return 0

int main(void) return foo(1) + foo(2)

Otoacuteż nie wiemy czy najpierw zostanie wywołana funkcja foo z parametrem jeden czydwa Jeżeli ma to znaczenie należy użyć zmiennych pomocniczych zmieniając definicję funk-cji main na

int main(void) int tmp = foo(1)return tmp + foo(2)

Teraz już na pewno najpierw zostanie wypisana jedynka a potem dopiero dwoacutejka Sy-tuacja jeszcze bardziej się komplikuje gdy używamy wyrażeń z efektami ubocznymi jakoargumentoacutew funkcji np

include ltstdiohgt

int foo(int a) printf(dn a)return 0

int bar(int a int b int c int d) return a + b + c + d

int main(void) return foo(1) + foo(2) + foo(3) + foo(4)

Teraz też nie wiemy ktoacutera z permutacji liczb i zostanie wypisana i ponownienależy pomoacutec sobie zmiennymi tymczasowymi jeżeli zależy nam na konkretnej kolejności

int main(void) int tmp = foo(1)tmp += foo(2)tmp += foo(3)return tmp + foo(4)

813 Uwagi

W języku C++ wprowadzony został dodatkowo inny sposoacuteb zapisu rzutowania ktoacuterypozwala na łatwiejsze znalezienie w kodzie miejsc w ktoacuterych dokonujemy rzutowaniaWięcej na stronie C++Zmienne

814 ZOBACZ TEŻ 55

814 Zobacz też CSkładniaOperatory

56 ROZDZIAŁ 8 OPERATORY

Rozdział 9

Instrukcje sterujące

C jest językiem imperatywnym mdash oznacza to że instrukcje wykonują się jedna po drugiej wtakiej kolejności w jakiej są napisane Aby moacutec zmienić kolejność wykonywania instrukcjipotrzebne są instrukcje sterujące

Na wstępie przypomnijmy jeszcze informację z rozdziału Operatory że wyrażenie jestprawdziwe wtedy i tylko wtedy gdy jest roacuteżne od zera a fałszywe wtedy i tylko wtedy gdyjest roacutewne zeru

91 Instrukcje warunkowe

911 Instrukcja if

Użycie instrukcji if wygląda tak

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

dalsze instrukcje

Istnieje także możliwość reakcji na nieprawdziwość wyrażenia mdash wtedy należy zastosowaćsłowo kluczowe else

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

else blok wykonany jeśli wyrażenie jest nieprawdziwe

dalsze instrukcje

Przypatrzmy się bardziej ldquożyciowemurdquo programowi ktoacutery poroacutewnuje ze sobą dwie liczby

include ltstdiohgt

int main ()

int a ba = 4

57

58 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

b = 6if (a==b)

printf (a jest roacutewne bn) else

printf (a nie jest roacutewne bn)return 0

Stosowany jest też kroacutetszy zapis warunkoacutew logicznych korzystający z tego jak C rozumieprawdę i fałsz Jeśli zmienna a jest typu integer zamiast

if (a = 0) b = 1a

można napisać

if (a) b = 1a

a zamiast

if (a == 0) b = 1a

można napisać

if (a) b = 1a

Czasami zamiast pisać instrukcję if możemy użyć operatora wyrażenia warunkowego(patrz Operatory)

if (a = 0)b = 1a

elseb = 0

ma dokładnie taki sam efekt jak

b = (a =0) 1a 0

912 Instrukcja swit

Aby ograniczyćwielokrotne stosowanie instrukcji if możemy użyć swit Jej użyciewyglądatak

switch (wyrażenie) case wartość1 instrukcje jeśli wyrażenie == wartość1

breakcase wartość2 instrukcje jeśli wyrażenie == wartość2

break default instrukcje jeśli żaden z wcześniejszych warunkoacutew

break nie został spełniony

Należy pamiętać o użyciu break po zakończeniu listy instrukcji następujących po case Je-śli tego nie zrobimy program przejdzie do wykonywania instrukcji z następnego case Możemieć to fatalne skutki

91 INSTRUKCJE WARUNKOWE 59

include ltstdiohgt

int main ()

int a bprintf (Podaj a )scanf (d ampa)printf (Podaj b )scanf (d ampb)switch (b)

case 0 printf (Nie można dzielić przez 0n) tutaj zabrakło break default printf (ab=dn ab)

return 0

A czasami może być celowym zabiegiem (tzw ldquofall-throughrdquo) mdash woacutewczas warto zazna-czyć to w komentarzu Oto przykład

include ltstdiohgt

int main ()

int a = 4switch ((a3))

case 0printf (Liczba d dzieli się przez 3n a)break

case -2case -1case 1case 2printf (Liczba d nie dzieli się przez 3n a)break

return 0

Przeanalizujmy teraz działający przykład

include ltstdiohgt

int main ()

unsigned int dzieci = 3 podatek=1000switch (dzieci)

case 0 break brak dzieci - czyli brak ulgi case 1 ulga 2

podatek = podatek - (podatek100 2)break

case 2 ulga 5

60 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

podatek = podatek - (podatek100 5)break

default ulga 10 podatek = podatek - (podatek10010)break

printf (Do zapłaty dn podatek)

92 Pętle

921 Instrukcja while

Często zdarza się że nasz programmusi wielokrotnie powtarzać ten sam ciąg instrukcji Abynie przepisywać wiele razy tego samego kodu można skorzystać z tzw pętli Pętla wykonujesię dotąd dopoacuteki prawdziwy jest warunek

while (warunek) instrukcje do wykonania w pętli

dalsze instrukcje

Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie Załoacuteżmy żemamy obliczyć kwadraty liczb od do Piszemy zatem program

include ltstdiohgt

int main ()

int a = 1while (a lt= 10) dopoacuteki a nie przekracza 10

printf (dn aa) wypisz aa na ekran++a zwiększamy a o jeden

return 0

Po analizie kodu mogą nasunąć się dwa pytania

Po co zwiększać wartość a o jeden Otoacuteż gdybyśmy nie dodali instrukcji zwiększająceja to warunek zawsze byłby spełniony a pętla ldquokręciłabyrdquo się w nieskończoność

Dlaczego warunek to ldquoa lt= rdquo a nie ldquoa=rdquo Odpowiedź jest dość prosta Pętlasprawdza warunek przed wykonaniem kolejnego ldquoobroturdquo Dlatego też gdyby waru-nek brzmiał ldquoa=rdquo to dla a= jest on nieprawdziwy i pętla nie wykonałaby ostatniejiteracji przez co program generowałby kwadraty liczb od do a nie do

922 Instrukcja for

Od instrukcji while czasami wygodniejsza jest instrukcja for Umożliwia ona wpisanie usta-wiania zmiennej sprawdzania warunku i inkrementowania zmiennej w jednej linijce co czę-sto zwiększa czytelność kodu Instrukcję for stosuje się w następujący sposoacuteb

92 PĘTLE 61

for (wyrażenie1 wyrażenie2 wyrażenie3) instrukcje do wykonania w pętli

dalsze instrukcje

Jak widać pętla for znacznie roacuteżni się od tego typu pętli znanych w innych językachprogramowania Opiszemy więc co oznaczają poszczegoacutelne wyrażenia

wyrażenie mdash jest to instrukcja ktoacutera będzie wykonana przed pierwszym przebiegiempętli Zwykle jest to inicjalizacja zmiennej ktoacutera będzie służyła jako ldquolicznikrdquo przebie-goacutew pętli

wyrażenie mdash jest warunkiem zakończenia pętli Pętla wykonuje się tak długo jakprawdziwy jest ten warunek

wyrażenie mdash jest to instrukcja ktoacutera wykonywana będzie po każdym przejściu pętliZamieszczone są tu instrukcje ktoacutere zwiększają licznik o odpowiednią wartość

Jeżeli wewnątrz pętli nie ma żadnych instrukcji continue (opisanych niżej) to jest onaroacutewnoważna z

wyrażenie1while (wyrażenie2)

instrukcje do wykonania w pętli wyrażenie3

dalsze instrukcje

Ważną rzeczą jest tutaj to żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla forPoczątkującym programistom nieznajomość tego faktu sprawia wiele problemoacutew

W pierwszej kolejności w pętli for wykonuje się wyrażenie1 Wykonuje się ono zawszenawet jeżeli warunek przebiegu pętli jest od samego początku fałszywy Po wykonaniuwyrażenie1 pętla for sprawdza warunek zawarty w wyrażenie2 jeżeli jest on prawdziwyto wykonywana jest treść pętli for czyli najczęściej to co znajduje się między klamrami lubgdy ich nie ma następna pojedyncza instrukcja W szczegoacutelności musimy pamiętać że samśrednik też jest instrukcją mdash instrukcją pustą Gdy już zostanie wykonana treść pętli for na-stępuje wykonanie wyrażenie3 Należy zapamiętać że wyrażenie zostanie wykonane nawetjeżeli był to już ostatni obieg pętli Poniższe przykłady pętli for w rezultacie dadzą ten samwynik Wypiszą na ekran liczby od do

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 printf(d i++ ) )

Dwa pierwsze przykłady korzystają z własności struktury blokowej kolejny przykład jestjuż bardziej wyrafinowany i korzysta z tego że jako wyrażenie3może zostać podane dowolnebardziej skomplikowane wyrażenie zawierające w sobie inne podwyrażenia A oto kolejnyprogram ktoacutery najpierw wyświetla liczby w kolejności rosnącej a następnie wraca

62 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

include ltstdiohgtint main()int ifor(i=1 ilt=5 ++i)

printf(d i)

for( igt=1 i--)printf(d i)

return 0

Po analizie powyższego kodu początkujący programista może stwierdzić że pętla wy-pisze 123454321 Stanie się natomiast inaczej Wynikiem działania powyższego programubędzie ciąg cyfr 12345654321 Pierwsza pętla wypisze cyfry ldquordquo lecz po ostatnim swoimobiegu pętla for (tak jak zwykle) zinkrementuje zmienną i Gdy druga pętla przystąpi dopracy zacznie ona odliczać począwszy od liczby i= a nie By spowodować wyświetlanieliczb od do i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli fora pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej i o

Niech podsumowaniem będzie jakiś działający fragment kodu ktoacutery może obliczać war-tości kwadratoacutew liczb od do

include ltstdiohgt

int main ()

int afor (a=1 alt=10 ++a)

printf (dn aa)return 0

W kodzie źroacutedłowym spotyka się często inkrementację i++ Jest to zły zwyczaj biorącysię z wzorowania się na nazwie języka C++ Post-inkrementacja i++ powoduje że tworzonyjest obiekt tymczasowy ktoacutery jest zwracany jako wynik operacji (choć wynik ten nie jestnigdzie czytany) Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie ale wpętli ldquoforrdquo takie kopiowanie odbywa się po każdym przebiegu pętli Dodatkowo w C++ po-dobną konstrukcję stosuje się do obiektoacutew mdash kopiowanie obiektu może być już czasochłonnączynnością Dlatego w pętli ldquoforrdquo należy stosować wyłącznie ++i

923 Instrukcja dowhile

Pętle while i for mają jeden zasadniczy mankament mdash może się zdarzyć że nie wykonają sięani razu Aby mieć pewność że nasza pętla będzie miała co najmniej jeden przebieg musimyzastosować pętlę do while Wygląda ona następująco

92 PĘTLE 63

do instrukcje do wykonania w pętli

while (warunek) dalsze instrukcje

Zasadniczą roacuteżnicą pętli do while jest fakt iż sprawdza ona warunek pod koniec swojegoprzebiegu To właśnie ta cecha decyduje o tym że pętla wykona się co najmniej raz A terazprzykład działającego kodu ktoacutery tym razem będzie obliczał trzecią potęgę liczb od do

include ltstdiohgt

int main ()

int a = 1do

printf (dn aaa)++a

while (a lt= 10)return 0

Może się to wydać zaskakujące ale roacutewnież przy tej pętli zamiast bloku instrukcji możnazastosować pojedynczą instrukcję np

include ltstdiohgt

int main ()

int a = 1do printf (dn aaa) while (++a lt= 10)return 0

924 Instrukcja break

Instrukcja break pozwala na opuszczenie wykonywania pętli w dowolnym momencie Przy-kład użycia

int afor (a=1 a = 9 ++a)

if (a == 5) breakprintf (dn a)

Program wykona tylko przebiegi pętli gdyż przy przebiegu instrukcja break spowo-duje wyjście z pętli

Break i pętle nieskończone

W przypadku pętli for nie trzeba podawać warunku W takim przypadku kompilator przyj-mie że warunek jest stale spełniony Oznacza to że poniższe pętle są roacutewnoważne

64 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

for () for (1) for (aaa) gdzie a jest dowolną liczba rzeczywistą roacuteżną od 0while (1) do while (1)

Takie pętle nazywamy pętlami nieskończonymi ktoacutere przerwać może jedynie instrukcjabreak1(z racji tego że warunek pętli zawsze jest prawdziwy) 2

Wszystkie fragmenty kodu działają identycznie

int i = 0for (i=5++i)

kod

int i = 0for (++i)

if (i == 5) break

int i = 0for ()

if (i == 5) break++i

925 Instrukcja continue

W przeciwieństwie do break ktoacutera przerywa wykonywanie pętli instrukcja continue powo-duje przejście do następnej iteracji o ile tylko warunek pętli jest spełniony Przykład

int ifor (i = 0 i lt 100 ++i)

printf (Poczatekn)if (i gt 40) continue printf (Koniecn)

Dla wartości i większej od nie będzie wyświetlany komunikat ldquoKoniecrdquo Pętla wykonapełne przejść

Oto praktyczny przykład użycia tej instrukcji

include ltstdiohgtint main()

int i

1Tak naprawdę podobną operacje możemy wykonać za pomocą polecenia goto W praktyce jednak stosujesię zasadę że break stosuje się do przerwania działania pętli i wyjścia z niej goto stosuje się natomiast wtedykiedy chce się wydostać się z kilku zagnieżdżonych pętli za jednym zamachem Do przerwania pracy pętli mogąnam jeszcze posłużyć polecenia exit() lub return ale woacutewczas zakończymy nie tylko działanie pętli ale i całegoprogramufunkcji

2Żartobliwie można powiedzieć że stosując pętlę nieskończoną to najlepiej korzystać z pętli for() gdyżwymaga ona napisania najmniejszej liczby znakoacutew w poroacutewnaniu do innych konstrukcji

93 INSTRUKCJA GOTO 65

for (i = 1 i lt= 50 ++i) if (i4==0) continue printf (d i)

return 0

Powyższy program generuje liczby z zakresu od do ktoacutere nie są podzielne przez

93 Instrukcja goto

Istnieje także instrukcja ktoacutera dokonuje skoku do dowolnegomiejsca programu oznaczonegotzw etykietą

etykieta instrukcje goto etykieta

Uwaga kompilator w wersji i wyższych jest bardzo uczulony na etykiety za-mieszczone przed nawiasem klamrowym zamykającym blok instrukcji Innymi słowy nie-dopuszczalne jest umieszczanie etykiety zaraz przed klamrą ktoacutera kończy blok instrukcjizawartych np w pętli for Można natomiast stosować etykietę przed klamrą kończącą danąfunkcję

Instrukcja goto łamie sekwencję instrukcji i powoduje skok do dowolnie odległego miej-sca w programie - co może mieć nieprzewidziane skutki Zbyt częste używanie goto możeprowadzić do trudnych do zlokalizowania błędoacutew Oproacutecz tego kompilatory mają kłopotyz optymalizacją kodu w ktoacuterym występują skoki Z tego powodu zaleca się ograniczeniezastosowania tej instrukcji wyłącznie do opuszczania wielokrotnie zagnieżdżonych pętli

Przykład uzasadnionego użycia

int ijfor (i = 0 i lt 10 ++i)

for (j = i j lt i+10 ++j) if (i + j 21 == 0) goto koniec

koniec dalsza czesc programu

94 Natymiastowe kończenie programu mdash funkcja exit

Program może zostać w każdej chwili zakończony mdash do tego właśnie celu służy funkcja exitUżywamy jej następująco

exit (kod_wyjścia)

66 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

Liczba całkowita kod wyjścia jest przekazywana do procesu macierzystego dzięki czemudostaje on informację czy programw ktoacuterymwywołaliśmy tą funkcję zakończył się popraw-nie lub czy się tak nie stało Kody wyjścia są nieustandaryzowane i żeby program był w pełniprzenośny należy stosować makra EXIT SUCCESS i EXIT FAILURE choć na wielu systemach kod oznacza poprawne zakończenie a kod roacuteżny od błędne W każdym przypadku jeżeli naszprogram potrafi generować wiele roacuteżnych kodoacutew warto je wszystkie udokumentować w ewdokumentacji Są one też czasem pomocne przy wykrywaniu błędoacutew

95 Uwagi W języku C++ można deklarować zmienne w nagłoacutewku pętli ldquoforrdquo w następujący spo-

soacuteb for(int i=0 ilt10 ++i) (więcej informacji w C++Zmienne)

Rozdział 10

Podstawowe procedury wejścia iwyjścia

101 Wejściewyjście

Komputer byłby całkowicie bezużyteczny gdyby użytkownik nie moacutegł się z nim porozumieć(tj wprowadzić danych lub otrzymać wynikoacutew pracy programu) Programy komputerowesłużą w największym uproszczeniu do obroacutebki danych mdash więc muszą te dane jakoś od nasotrzymać przetworzyć i przekazać nam wynik

Takiewczytywanie i ldquowyrzucanierdquo danychw terminologii komputerowej nazywamywej-ściem (input) iwyjściem (output) Bardzo często moacutewi się o wejściu i wyjściu danych łączniemdash inputoutput albo po prostu IO

W C do komunikacji z użytkownikiem służą odpowiednie funkcje Zresztą do wielu za-dań w C służą funkcje Używając funkcji nie musimy wiedzieć w jaki sposoacuteb komputerwykonuje jakieś zadanie interesuje nas tylko to co ta funkcja robi Funkcje niejako ldquowyko-nują za nas część pracyrdquo ponieważ nie musimy pisać być może dziesiątek linijek kodu żebynp wypisać tekst na ekranie (wbrew pozorom mdash kod funkcji wyświetlającej tekst na ekraniejest dość skomplikowany) Jeszcze taka uwaga mdash gdy piszemy o jakiejś funkcji zazwyczajpodając jej nazwę dopisujemy na końcu nawias

printf()scanf()

żeby było jasne że chodzi o funkcję a nie o coś innego

Wyżej wymienione funkcje to jedne z najczęściej używanych funkcji w C mdash pierwszasłuży do wypisywania danych na ekran natomiast druga do wczytywania danych z klawia-tury1

1W zasadzie standard C nie definiuje czegoś takiego jak ekran i klawiatura mdash mowa w nim o standardowymwyjściu i standardowym wejściu Zazwyczaj jest to właśnie ekran i klawiatura ale nie zawsze W szczegoacutelności użyt-kownicy Linuksa lub innych systemoacutew uniksowych mogą być przyzwyczajeniu do przekierowania wejściawyjściazdo pliku czy łączenie komend w potoki (ang pipe) W takich sytuacjach dane nie są wyświetlane na ekranie aniodczytywane z klawiatury

67

68 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

102 Funkcje wyjścia

1021 Funkcja printf

W przykładzie ldquoHello Worldrdquo użyliśmy już jednej z dostępnych funkcji wyjścia a miano-wicie funkcji printf() Z punktu widzenia swoich możliwości jest to jedna z bardziej skom-plikowanych funkcji a jednocześnie jest jedną z najczęściej używanych Przyjrzyjmy sięponownie kodowi programu ldquoHello Worldrdquo

include ltstdiohgt

int main(void)

printf(Hello worldn)return 0

Po skompilowaniu i uruchomieniu program wypisze na ekranie

Hello world

W naszym przykładowym programie chcąc by funkcja printf() wypisała tekst na ekra-nie umieściliśmy go w cudzysłowach wewnątrz nawiasoacutew Ogoacutelnie wywołanie funkcjiprintf() wygląda następująco

printf(format argument1 argument2 )

Przykładowo

int i = 500printf(Liczbami całkowitymi są na przykład i oraz in 1 i)

wypisze

Liczbami całkowitymi są na przykład 1 oraz 500

Format to napis ujęty w cudzysłowy ktoacutery określa ogoacutelny kształt schemat tego co ma byćwyświetlone Format jest drukowany tak jak go napiszemy jednak niektoacutere znaki specjalnezostaną w nim podmienione na co innego Przykładowo znak specjalny n jest zamienianyna znak nowej linii 2 Natomiast procent jest podmieniany na jeden z argumentoacutew Po pro-cencie następuje specyfikacja jak wyświetlić dany argument W tym przykładzie i (od int)oznacza że argument ma być wyświetlony jak liczba całkowita W związku z tym że i mają specjalne znaczenie aby wydrukować je należy użyć ich podwoacutejnie

printf(Procent Backslash )

drukuje

Procent Backslash

(bez przejścia do nowej linii) Na liście argumentoacutew możemy mieszać ze sobą zmienne roacuteż-nych typoacutew liczby napisy itp w dowolnej liczbie Funkcja printf przyjmie ich tyle ile tylkonapiszemy Należy uważać by nie pomylić się w formatowaniu

2Zmiana ta następuje w momencie kompilacji programu i dotyczy wszystkich literałoacutew napisowych Nie jestto jakaś szczegoacutelna własność funkcji printf() Więcej o tego typu sekwencjach i ciągach znakoacutew w szczegoacutelnościopisane jest w rozdziale Napisy

103 FUNKCJA PUTS 69

int i = 5printf(i s i 5 4 napis) powinno być i i s

Przywłączeniu ostrzeżeń (opcja -Wall lub -WformatwGCC) kompilator powinien nas ostrzecgdy format nie odpowiada podanym elementom

Najczęstsze użycie printf()

printf(i i) gdy i jest typu int zamiast i można użyć d

printf(f i) gdy i jest typu float lub double

printf(c i) gdy i jest typu char (i chcemy wydrukować znak)

printf(s i) gdy i jest napisem (typu char)

Funkcja printf() nie jest żadną specjalną konstrukcją języka i łańcuch formatujący możebyć podany jako zmienna W związku z tym możliwa jest np taka konstrukcja

include ltstdiohgt

int main(void)

char buf[100]scanf(99s buf) funkcja wczytuje tekst do tablicy buf printf(buf)return 0

Program wczytuje tekst a następnie wypisuje go Jednak ponieważ znak procentu jesttraktowany w specjalny sposoacuteb toteż jeżeli na wejściu pojawi się ciąg znakoacutew zawierającyten znak mogą się stać roacuteżne dziwne rzeczy Między innymi z tego powodu w takich sytu-acjach lepiej używać funkcji puts() lub fputs() opisanych niżej lub wywołania printf(szmienna)

Więcej o funkcji printf()

103 Funkcja putsFunkcja puts() przyjmuje jako swoacutej argument ciąg znakoacutew ktoacutery następnie bezmyślnie wy-pisuje na ekran kończąc go znakiem przejścia do nowej linii W ten sposoacuteb nasz pierwszyprogram moglibyśmy napisać w ten sposoacuteb

include ltstdiohgt

int main(void)

puts(Hello world)return 0

W swoim działaniu funkcja ta jest w zasadzie identyczna do wywołania printf(snargument) jednak prawdopodobnie będzie działać szybciej Jedynym jejmankamentemmożebyć fakt że zawsze na końcu podawany jest znak przejścia do nowej linii Jeżeli jest to efektniepożądany (nie zawsze tak jest) należy skorzystać z funkcji fputs() opisanej niżej lub wy-wołania printf(s argument)

Więcej o funkcji puts()

70 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

104 Funkcja fputs

Opisując funkcję fputs() wybiegamy już trochę w przyszłość (a konkretnie do opisu operacjina plikach) ale warto o niej wspomnieć już teraz gdyż umożliwia ona wypisanie swojegoargumentu bez wypisania na końcu znaku przejścia do nowej linii

include ltstdiohgt

int main(void)

fputs(Hello worldn stdout)return 0

W chwili obecnej możesz się nie przejmować tym zagadkowym stdout wpisanym jakodrugi argument funkcji Jest to określenie strumienia wyjściowego (w naszymwypadku stan-dardowe wyjście mdash standard output)

Więcej o funkcji fputs()

1041 Funkcja putar

Funkcja putchar() służy do wypisywania pojedynczych znakoacutew Przykładowo jeżeli chcieli-byśmy napisać programwypisującyw prostej tabelce wszystkie liczby od do moglibyśmyto zrobić tak

include ltstdiohgt

int main(void)

int i = 0for ( ilt100 ++i) Nie jest to pierwsza liczba w wierszu if (i 10)

putchar( )printf(2d i) Jest to ostatnia liczba w wierszu if ((i 10)==9)

putchar(n)

return 0

Więcej o funkcji putchar()

105 FUNKCJE WEJŚCIA 71

105 Funkcje wejścia

1051 Funkcja scanf()

Teraz pomyślmy o sytuacji odwrotnej Tym razem to użytkownik musi powiedzieć coś pro-gramowi W poniższym przykładzie program podaje kwadrat liczby podanej przez użytkow-nika

include ltstdiohgt

int main ()

int liczba = 0printf (Podaj liczbę )scanf (d ampliczba)printf (dd=dn liczba liczba liczbaliczba)return 0

Zauważyłeś że w tej funkcji przy zmiennej pojawił się nowy operator mdash amp (etka) Jeston ważny gdyż bez niego funkcja scanf() nie skopiuje odczytanej wartości liczby do odpo-wiedniej zmiennej Właściwie oznacza przekazanie do funkcji adresu zmiennej by funkcjamogła zmienić jej wartość Nie musisz teraz rozumieć jak to się odbywa wszystko zostaniewyjaśnione w rozdziale Wskaźniki

Oznaczenia są podobne takie jak przy printf() czyli scanf(i ampliczba) wczytuje liczbętypu int scanf(f ampliczba) ndash liczbę typu float a scanf(s tablica znakoacutew) ciąg zna-koacutew Ale czemu w tym ostatnim przypadku nie ma etki Otoacuteż gdy podajemy jako argumentdo funkcji wyrażenie typu tablicowego zamieniane jest ono automatycznie na adres pierw-szego elementu tablicy Będzie to dokładniej opisane w rozdziale poświęconym wskaźnikom

Brak etki jest częstym błędem szczegoacutelnie wśroacuted początkujących programistoacutew Ponie-waż funkcja scanf() akceptuje zmienną liczbę argumentoacutew to nawet kompilator może miećkłopoty z wychwyceniem takich błędoacutew (konkretnie chodzi o to że standard nie wymagaod kompilatora wykrywania takich pomyłek) choć kompilator GCC radzi sobie z tym jeżelipodamy mu argument -Wformat

Należy jednak uważać na to ostatnie użycie Rozważmy na przykład poniższy kod

include ltstdiohgt

int main(void)

char tablica[100] 1 scanf(s tablica) 2 return 0

Robi on niewiele W linijce deklarujemy tablicę znakoacutew czyli mogącą przechowaćnapis długości znakoacutew Nie przejmuj się jeżeli nie do końca to wszystko rozumiesz mdash po-jęcia takie jak tablica czy ciąg znakoacutew staną się dla Ciebie jasne w miarę czytania kolejnych

72 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

rozdziałoacutew W linijce wywołujemy funkcję scanf() ktoacutera odczytuje tekst ze standardowegowejścia Nie zna ona jednak rozmiaru tablicy i nie wie ile znakoacutewmoże ona przechować przezco będzie czytać tyle znakoacutew aż napotka biały znak (format s nakazuje czytanie pojedyn-czego słowa) co może doprowadzić do przepełnienia bufora Niebezpieczne skutki czegośtakiego opisane są w rozdziale poświęconym napisom Na chwilę obecną musisz zapamiętaćżeby zaraz po znaku procentu podawać maksymalną liczbę znakoacutew ktoacutere może przechowaćbufor czyli liczbę o jeden mniejszą niż rozmiar tablicy Bezpieczna wersją powyższego kodujest

include ltstdiohgt

int main(void)

char tablica[100]scanf(99s tablica)return 0

Funkcja scanf() zwraca liczbę poprawnie wczytanych zmiennych lub EOF jeżeli nie majuż danych w strumieniu lub nastąpił błąd Załoacuteżmy dla przykładu że chcemy stworzyćprogram ktoacutery odczytuje po kolei liczby i wypisuje ich potęgi W pewnym momenciedane się kończą lub jest wprowadzana niepoprawna dana i woacutewczas nasz program powinienzakończyć działanie Aby to zrobić należy sprawdzać wartość zwracaną przez funkcję scanf()w warunku pętli

include ltstdiohgt

int main(void)

int nwhile (scanf(d ampn)==1)printf(dn nnn)

return 0

Podobnie możemy napisać program ktoacutery wczytuje po dwie liczby i je sumuje

include ltstdiohgt

int main(void)

int a bwhile (scanf(d d ampa ampb)==2)printf(dn a+b)

return 0

105 FUNKCJE WEJŚCIA 73

Rozpatrzmy teraz trochę bardziej skomplikowany przykład Otoacuteż ponownie jak poprzed-nio nasz program będzie wypisywał potęgę podanej liczby ale tym razem musi ignorowaćbłędne dane (tzn pomijać ciągi znakoacutew ktoacutere nie są liczbami) i kończyć działanie tylko wmomencie gdy nastąpi błąd odczytu lub koniec pliku3

include ltstdiohgt

int main(void)

int result ndoresult = scanf(d ampn)if (result==1)

printf(dn nnn)else if (result) result to to samo co result==0

result = scanf(s)

while (result=EOF)return 0

Zastanoacutewmy się przez chwilę co się dzieje w programie Najpierw wywoływana jestfunkcja scanf() i następuje proacuteba odczytu liczby typu int Jeżeli funkcja zwroacuteciła to liczbazostała poprawnie odczytana i następuje wypisanie jej trzeciej potęgi Jeżeli funkcja zwroacuteciła to na wejściu były jakieś dane ktoacutere nie wyglądały jak liczba W tej sytuacji wywołujemyfunkcję scanf() z formatem odczytującym dowolny ciąg znakoacutew nie będący białymi znakamiz jednoczesnym określeniem żeby nie zapisywała nigdzie wyniku W ten sposoacuteb niepopraw-nie wpisana dana jest omijana Pętla głoacutewna wykonuje się tak długo jak długo funkcja scanf()nie zwroacuteci wartości EOF

Więcej o funkcji scanf()

1052 Funkcja gets

Funkcja gets służy do wczytania pojedynczej linii Może Ci się to wydać dziwne ale funkcjitej nie należy używać pod żadnym pozorem Przyjmuje ona jeden argument mdash adres pierw-szego elementu tablicy do ktoacuterego należy zapisać odczytaną linię mdash i nic poza tym Z tegopowodu nie ma żadnej możliwości przekazania do tej funkcji rozmiaru bufora podanego jakoargument Podobnie jak w przypadku scanf() może to doprowadzić do przepełnienia buforaco może mieć tragiczne skutki Zamiast tej funkcji należy używać funkcji fgets()

Więcej o funkcji gets()

1053 Funkcja fgets

Funkcja fgets() jest bezpieczną wersją funkcji gets() ktoacutera dodatkowo może operować nadowolnych strumieniach wejściowych Jej użycie jest następujące

3Jak rozroacuteżniać te dwa zdarzenia dowiesz się w rozdziale Czytanie i pisanie do plikoacutew

74 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

fgets(tablica_znakoacutew rozmiar_tablicy_znakoacutew stdin)

Na chwilę obecną nie musisz się przejmować ostatnim argumentem (jest to określeniestrumienia w naszym przypadku standardowewejście mdash standard input) Funkcja czyta tekstaż do napotkania znaku przejścia do nowej linii ktoacutery także zapisuje w wynikowej tablicy(funkcja gets() tego nie robi) Jeżeli brakuje miejsca w tablicy to funkcja przerywa czytanie wten sposoacuteb aby sprawdzić czy została wczytana cała linia czy tylko jej część należy sprawdzićczy ostatnim znakiem nie jest znak przejścia do nowej linii Jeżeli nastąpił jakiś błąd lub nawejściu nie ma już danych funkcja zwraca wartość NULL

include ltstdiohgt

int main(void) char buffer[128] whole_line = 1 chwhile (fgets(buffer sizeof buffer stdin)) 1

if (whole_line) 2 putchar(gt)if (buffer[0]=gt)

putchar( )

fputs(buffer stdout) 3 for (ch = buffer ch ampamp ch=n ++ch) 4 whole_line = ch == n

if (whole_line)

putchar(n)return 0

Powyższy kod wczytuje dane ze standardowego wejścia mdash linia po linii mdash i dodaje napoczątku każdej linii znak większości po ktoacuterym dodaje spację jeżeli pierwszym znakiem nalinii nie jest znak większości W linijce następuje odczytywanie linii Jeżeli nie ma już wię-cej danych lub nastąpił błąd wejścia funkcja zwraca wartość NULL ktoacutera ma logiczną war-tość i woacutewczas pętla kończy działanie W przeciwnym wypadku funkcja zwraca po prostupierwszy argument ktoacutery ma wartość logiczną W linijce sprawdzamy czy poprzedniewywołanie funkcji wczytało całą linię czy tylko jej część mdash jeżeli całą to teraz jesteśmy napoczątku linii i należy dodać znakwiększości W linii najzwyczajniej w świecie wypisujemylinię W linii przeszukujemy tablicę znak po znaku aż do momentu gdy znajdziemy znako kodzie kończącym ciąg znakoacutew albo znak przejścia do nowej linii Ten drugi przypadekoznacza że funkcja fgets() wczytała całą linię

Więcej o funkcji fgets()

1054 Funkcja getar()

Jest to bardzo prosta funkcja wczytująca znak z klawiatury W wielu przypadkach danemogą być buforowane przez co wysyłane są do programu dopiero gdy bufor zostaje przepeł-niony lub na wejściu jest znak przejścia do nowej linii Z tego powodu po wpisaniu danegoznaku należy nacisnąć klawisz enter aczkolwiek trzeba pamiętać że w następnym wywoła-niu zostanie zwroacutecony znak przejścia do nowej linii Gdy nastąpił błąd lub nie ma już więcej

105 FUNKCJE WEJŚCIA 75

danych funkcja zwraca wartość EOF (ktoacutera ma jednak wartość logiczną toteż zwykła pętlawhile (getchar()) nie da oczekiwanego rezultatu)

include ltstdiohgt

int main(void)

int cwhile ((c = getchar())=EOF)

if (c== ) c = _

putchar(c)

return 0

Ten prosty program wczytuje dane znak po znaku i zamienia wszystkie spacje na znakipodkreślenia Może wydać się dziwne że zmienną c zdefiniowaliśmy jako trzymającą typ inta nie char Właśnie taki typ (tj int) zwraca funkcja getchar() i jest to konieczne ponieważwartość EOF wykracza poza zakres wartości typu char (gdyby tak nie było to nie byłobymożliwości rozroacuteżnienia wartości EOF od poprawnie wczytanego znaku) Więcej o funkcjigetchar()

76 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

Rozdział 11

Funkcje

W matematyce pod pojęciem funkcji rozumiemy twoacuter ktoacutery pobiera pewną liczbę argumen-toacutew i zwraca wynik1 Jeśli dla przykładu weźmiemy funkcję sin(x) to x będzie zmiennąrzeczywistą ktoacutera określa kąt a w rezultacie otrzymamy inną liczbę rzeczywistą mdash sinustego kąta

W C funkcja (czasami nazywana podprogramem rzadziej procedurą) to wydzielona częśćprogramu ktoacutera przetwarza argumenty i ewentualnie zwraca wartość ktoacutera następnie możebyć wykorzystana jako argument w innych działaniach lub funkcjach Funkcja może posia-dać własne zmienne lokalne W odroacuteżnieniu od funkcji matematycznych funkcje w C mogązwracać dla tych samych argumentoacutew roacuteżne wartości

Po lekturze poprzednich części podręcznika zapewne moacutegłbyś podać kilka przykładoacutewfunkcji z ktoacuterych korzystałeś Były to np

funkcja printf() drukująca tekst na ekranie czy

funkcja main() czyli głoacutewna funkcja programu

Głoacutewną motywacją tworzenia funkcji jest unikanie powtarzania kilka razy tego samegokodu W poniższym fragmencie

for(i=1 i lt= 5 ++i) printf(d ii)

for(i=1 i lt= 5 ++i)

printf(d iii)for(i=1 i lt= 5 ++i)

printf(d ii)

widzimy że pierwsza i trzecia pętla for są takie same Zamiast kopiować fragment kodukilka razy (co jest mało wygodne i może powodować błędy) lepszym rozwiązaniem mogłobybyć wydzielenie tego fragmentu tak by można go było wywoływać kilka razy Tak właśniedziałają funkcje

Innym niemniej ważnym powodem używania funkcji jest rozbicie programu na frag-menty wg ich funkcjonalności Oznacza to że z jeden duży program dzieli się na mniejsze

1Aby nie urażać matematykoacutew sprecyzujmy że chodzi o relację między zbiorami X i Y (X jest dziedziną Y jestprzeciwdziedziną) takie że każdy element zbioru X jest w relacji z dokładnie jednym elementem ze zbioru Y

77

78 ROZDZIAŁ 11 FUNKCJE

funkcje ktoacutere są ldquowyspecjalizowanerdquo w wykonywaniu określonych czynności Dzięki temułatwiej jest zlokalizować błąd Ponadto takie funkcje można potem przenieść do innych pro-gramoacutew

111 Tworzenie funkcji

Dobrze jest uczyć się na przykładach Rozważmy następujący kod

int iloczyn (int x int y)

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

int iloczyn (int x int y) to nagłoacutewek funkcji ktoacutery opisuje jakie argumenty przyj-muje funkcja i jaką wartość zwraca (funkcja może przyjmować wiele argumentoacutew lecz możezwracać tylko jedną wartość)2 Na początku podajemy typ zwracanej wartości mdash u nas intNastępnie mamy nazwę funkcji i w nawiasach listę argumentoacutew

Ciało funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasachklamrowych Pierwszą instrukcją jest deklaracja zmiennej mdash jest to zmienna lokalna czyliniewidoczna poza funkcją Dalej przeprowadzamy odpowiednie działania i zwracamy rezul-tat za pomocą instrukcji return

1111 Ogoacutelnie

Funkcję w języku C tworzy się następująco

typ identyfikator (typ1 argument1 typ2 argument2 typ_n argument_n)

instrukcje

Oczywiście istnieje możliwość utworzenia funkcji ktoacutera nie posiada żadnych argumen-toacutew Definiuje się ją tak samo jak funkcję z argumentami z tą tylko roacuteżnicą że międzyokrągłymi nawiasami nie znajduje się żaden argument lub pojedyncze słoacutewko void mdash w de-finicji funkcji nie ma to znaczenia jednak w deklaracji puste nawiasy oznaczają że prototypnie informuje jakie argumenty przyjmuje funkcja dlatego bezpieczniej jest stosować słoacutewkovoid

Funkcje definiuje się poza głoacutewną funkcją programu (main) W języku C nie można two-rzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji)

1112 Procedury

Przyjęło się że procedura od funkcji roacuteżni się tym że ta pierwsza nie zwraca żadnej wartościZatem aby stworzyć procedurę należy napisać

2Bardziej precyzyjnie można powiedzieć że funkcja może zwroacutecić tylko jedną wartość typu prostego lub jedenadres do jakiegoś obiektu w pamięci

112 WYWOŁYWANIE 79

void identyfikator (argument1 argument2 argumentn)

instrukcje

void (z ang pusty proacuteżny) jest słowem kluczowym mającym kilka znaczeń w tym przy-padku oznacza ldquobrak wartościrdquo

Generalnie w terminologii C pojęcie ldquoprocedurardquo nie jest używane moacutewi się raczej ldquofunk-cja zwracająca voidrdquo

Jeśli nie podamy typu danych zwracanych przez funkcję kompilator domyślnie przyjmietyp int choć już w standardzie C nieokreślenie wartości zwracanej jest błędem

1113 Stary sposoacuteb definiowania funkcji

Zanim powstał standard ANSI C w liście parametroacutew nie podawało się typoacutew argumentoacutewa jedynie ich nazwy Roacutewnież z tamtych czasoacutew wywodzi się oznaczenie iż puste nawiasy(w prototypie funkcji nie w definicji) oznaczają że funkcja przyjmuje nieokreśloną liczbęargumentoacutew Tego archaicznego sposobu definiowania funkcji nie należy już stosować aleponieważ w swojej przygodzie z językiem C Czytelnik może się na nią natknąć a co więcejstandard nadal (z powodu zgodności z wcześniejszymi wersjami) dopuszcza taką deklaracjęto należy tutaj o niej wspomnieć Otoacuteż wygląda ona następująco

typ_zwracany nazwa_funkcji(argument1 argument2 argumentn)typ1 argumenty typ2 argumenty

instrukcje

Na przykład wcześniejsza funkcja iloczyn wyglądałaby następująco

int iloczyn(x y)int x y

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

Najpoważniejszą wadą takiego sposobu jest fakt że w prototypie funkcji niema podanychtypoacutew argumentoacutew przez co kompilator nie jest w stanie sprawdzić poprawności wywołaniafunkcji Naprawiono to (wprowadzając definicje takie jak je znamy obecnie) najpierw wjęzyku C++ a potem rozwiązanie zapożyczono w standardzie ANSI C z roku

112 WywoływanieFunkcje wywołuje się następująco

80 ROZDZIAŁ 11 FUNKCJE

identyfikator (argument1 argument2 argumentn)

Jeśli chcemy aby przypisać zmiennej wartość ktoacuterą zwraca funkcja należy napisać tak

zmienna = funkcja (argument1 argument2 argumentn)

Programiści mający doświadczenia np z językiem Pascal mogą popełniać błąd polegającyna wywoływaniu funkcji bez nawiasoacutew okrągłych gdy nie przyjmuje ona żadnych argumen-toacutew

Przykładowo mamy funkcję

void pisz_komunikat()

printf(To jest komunikatn)

Jeśli teraz ją wywołamy

pisz_komunikat ŹLE pisz_komunikat() dobrze

to pierwsze polecenie nie spowoduje wywołania funkcji Dlaczego Aby kompilator Czrozumiał że chodzi nam o wywołanie funkcji musimy po jej nazwie dodać nawiasy okrą-głe nawet gdy funkcja nie ma argumentoacutew Użycie samej nazwy funkcji ma zupełnie inneznaczenie mdash oznacza pobranie jej adresu W jakim celu O tym będzie mowa w rozdzialeWskaźniki

PrzykładA oto działający przykład ktoacutery demonstruje wiadomości podane powyżej

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

int m = suma (4 5)printf (4+5=dn m)return 0

113 Zwracanie wartościreturn to słowo kluczowe języka C

W przypadku funkcji służy ono do

przerwania funkcji (i przejścia do następnej instrukcji w funkcji wywołującej)

114 ZWRACANA WARTOŚĆ 81

zwroacutecenia wartości

W przypadku procedur powoduje przerwania procedury bez zwracania wartościUżycie tej instrukcji jest bardzo proste i wygląda tak

return zwracana_wartość

lub dla procedur

return

Możliwe jest użycie kilku instrukcji returnw obrębie jednej funkcji Wielu programistoacutewuważa jednak że lepsze jest użycie jednej instrukcji return na końcu funkcji gdyż ułatwia tośledzenie przebiegu programu

114 Zwracana wartość

W C zwykle przyjmuje się że oznacza poprawne zakończenie funkcji

return 0 funkcja zakończona sukcesem

a inne wartości oznaczają niepoprawne zakończenie

return 1 funkcja zakończona niepowodzeniem

Ta wartość może być wykorzystana przez inne instrukcje np if

115 Funkcja main()

Do tej pory we wszystkich programach istniała funkcja main() Po co tak właściwie onajest Otoacuteż jest to funkcja ktoacutera zostaje wywołana przez fragment kodu inicjującego pracęprogramu Kod ten tworzony jest przez kompilator i nie mamy na niego wpływu Istotnejest że każdy program w języku C musi zawierać funkcję main()

Istnieją dwa możliwe prototypy (nagłoacutewki) omawianej funkcji

int main(void)

int main(int argc char argv) 3

Argument argc jest liczbą nieujemną określającą ile ciągoacutew znakoacutew przechowywanychjest w tablicy argv Wyrażenie argv[argc] ma zawsze wartość Pierwszym elementemtablicy argv (o ile istnieje4) jest nazwa programu czy komenda ktoacuterą program został urucho-miony Pozostałe przechowują argumenty podane przy uruchamianiu programu

Zazwyczaj jeśli program uruchomimy poleceniem

program argument1 argument2

3Czasami można się spotkać z prototypem int main(int argc char argv char env) ktoacutery jest definio-wany w standardzie ale wykracza już poza standard C

4Inne standardy mogą wymuszać istnienie tego elementu jednak jeśli chodzi o standard języka C to nic nie stoina przeszkodzie aby argument argc miał wartość zero

82 ROZDZIAŁ 11 FUNKCJE

to argc będzie roacutewne ( argumenty + nazwa programu) a argv będzie zawierać napisy pro-gram argument argument umieszczone w tablicy indeksowanej od do

Weźmy dla przykładu program ktoacutery wypisuje to co otrzymuje w argumentach argc iargv

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) while (argv)

puts(argv++) Ewentualnie można użycint ifor (i = 0 iltargc ++i)

puts(argv[i])return EXIT_SUCCESS

Uruchomiony w systemie typu UNIX poleceniem test foo bar baz powinien wypisać

testfoobarbaz

Na razie nie musisz rozumieć powyższych kodoacutew i opisoacutew gdyż odwołują się do pojęćtakich jak tablica oraz wskaźnik ktoacutere opisane zostaną w dalszej części podręcznika

Co ciekawe funkcja main nie roacuteżni się zanadto od innych funkcji i tak jak inne możewołać sama siebie (patrz rekurencja niżej) przykładowo powyższy program można zapisaćtak5

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) if (argv)

puts(argv)return main(argc-1 argv+1)

else return EXIT_SUCCESS

Ostatnią rzeczą dotyczącą funkcji main jest zwracana przez nią wartość Już przy oma-wianiu pierwszego programu wspomniane zostało że jedynymi wartościami ktoacutere znaczą

5Jeżeli ktoś lubi ekstrawagancki kod ciało funkcji main można zapisać jako return argv puts(argv)main(argc-1 argv+1) EXIT SUCCESS ale nie radzimy stosować tak skomplikowanych i bądź co bądź małoczytelnych konstrukcji

116 DALSZE INFORMACJE 83

zawsze to samowewszystkich implementacjach języka są EXIT SUCCESS i EXIT FAILURE6

zdefiniowane w pliku nagłoacutewkowym stdlibh Wartość i EXIT SUCCESS oznaczają po-prawne zakończenie programu (co wcale nie oznacza że makro EXIT SUCCESS ma wartośćzero) natomiast EXIT FAILURE zakończenie błędne Wszystkie inne wartości są zależne odimplementacji

116 Dalsze informacjePoniżej przekażemy ci parę bardziej zaawansowanych informacji o funkcjach w C jeśli niemasz ochoty wgłębiać się w szczegoacuteły możesz spokojnie pominąć tę część i wroacutecić tu poacuteźniej

1161 Jak zwroacutecić kilka wartości

Jeśli chcesz zwroacutecić z funkcji kilka wartości musisz zrobić to w trochę inny sposoacuteb Ge-neralnie możliwe są dwa podejścia jedno to ldquoupakowanierdquo zwracanych wartości ndash możnastworzyć tak zwaną strukturę ktoacutera będzie przechowywała kilka zmiennych (jest to opisanew rozdziale Typy złożone) Prostszym sposobem jest zwracanie jednej z wartości w normalnysposoacuteb a pozostałych jako parametroacutew Za chwilę dowiesz się jak to zrobić jeśli chcesz zo-baczyć przykład możesz przyjrzeć się funkcji scanf() z biblioteki standardowej

1162 Przekazywanie parametroacutew

Gdy wywołujemy funkcję wartość argumentoacutew z ktoacuterymi ją wywołujemy jest kopiowanado funkcji Kopiowana mdash to znaczy że nie możemy normalnie zmienić wartości zewnętrz-nych dla funkcji zmiennych Formalnie moacutewi się że w C argumenty są przekazywane przezwartość czyli wewnątrz funkcji operujemy tylko na ich kopiach

Możliwe jest modyfikowanie zmiennych przekazywanych do funkcji jako parametry mdashale do tego w C potrzebne są wskaźniki

1163 Funkcje rekurencyjne

Język Cmamożliwość tworzenia tzw funkcji rekurencyjny Jest to funkcja ktoacuteraw swojejwłasnej definicji (ciele) wywołuje samą siebie Najbardziej klasycznym przykładem może tubyć silnia Napiszmy sobie zatem naszą funkcję rekurencyjną ktoacutera oblicza silnię

int silnia (int liczba)

int silif (liczbalt0) return 0 wywołanie jest bezsensowne

zwracamy 0 jako kod błędu if (liczba==0 || liczba==1) return 1sil = liczbasilnia(liczba-1)return sil

Musimy być ostrożni przy funkcjach rekurencyjnych gdyż łatwo za ich pomocą utworzyćfunkcję ktoacutera będzie sama siebie wywoływała w nieskończoność a co za tym idzie będzie za-wieszała program Tutaj pierwszymi instrukcjami if ustalamy ldquowarunki stopurdquo gdzie kończy

6Uwaga Makra EXIT SUCCESS i EXIT FAILURE te służą tylko i wyłącznie jako wartości do zwracania przezfunkcję main() Nigdzie indziej nie mają one zastosowania

84 ROZDZIAŁ 11 FUNKCJE

się wywoływanie funkcji przez samą siebie a następnie określamy jak funkcja będzie wy-woływać samą siebie (odjęcie jedynki od argumentu co do ktoacuterego wiemy że jest dodatnigwarantuje że dojdziemy do warunku stopu w skończonej liczbie krokoacutew)

Warto też zauważyć że funkcje rekurencyjne czasami mogą być znacznie wolniejsze niżpodejście nierekurencyjne (iteracyjne przy użyciu pętli) Flagowym przykłademmoże tu byćfunkcja obliczająca wyrazy ciągu Fibonacciego

include ltstdiohgt

unsigned count

unsigned fib_rec(unsigned n) ++countreturn nlt2 n (fib_rec(n-2) + fib_rec(n-1))

unsigned fib_it (unsigned n) unsigned a = 0 b = 0 c = 1++countif (n) return 0while (--n)

++counta = bb = cc = a + b

return c

int main(void) unsigned n resultprintf(Ktory element ciagu Fibonacciego obliczyc )while (scanf(d ampn)==1)

count = 0result = fib_rec(n)printf(fib_ret(3u) = 6u (wywolan 5u)n n result count)

count = 0result = fib_it (n)printf(fib_it (3u) = 6u (wywolan 5u)n n result count)

return 0

W tym przypadku funkcja rekurencyjna choć łatwiejsza w napisaniu jest bardzo nie-efektywna

116 DALSZE INFORMACJE 85

1164 Deklarowanie funkcji

Czasami możemy chcieć przed napisaniem funkcji poinformować kompilator że dana funkcjaistnieje Niekiedy kompilator może zaprotestować jeśli użyjemy funkcji przed określeniemjaka to funkcja na przykład

int a()

return b(0)

int b(int p)

if( p == 0 )return 1

elsereturn a()

int main()

return b(1)

W tym przypadku nie jesteśmy w stanie zamienić a i b miejscami bo obie funkcje korzy-stają z siebie nawzajem Rozwiązaniem jest wcześniejsze zadeklarowanie funkcji Deklara-cja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji(przed otwierającym nawiasem klamrowym) z dodatkowo dodanym średnikiem na końcu Wnaszym przykładzie wystarczy na samym początku wstawić

int b(int p)

W deklaracji można pominąć nazwy parametroacutew funkcji

int b(int)

Bardzo częstym zwyczajem jest wypisanie przed funkcją main samych prototypoacutew funk-cji by ich definicje umieścić po definicji funkcji main np

int a(void)int b(int p)

int main()

return b(1)

int a()

return b(0)

86 ROZDZIAŁ 11 FUNKCJE

int b(int p)

if( p == 0 )return 1

elsereturn a()

Z poprzednich rozdziałoacutew pamiętasz że na początku programu dołączaliśmy tzw plikinagłoacutewkowe Zawierają one właśnie prototypy funkcji i ułatwiają pisanie dużych progra-moacutew Dalsze informacje o plikach nagłoacutewkowych zawarte są w rozdziale Tworzenie biblio-tek

1165 Zmienna liczba parametroacutew

Zauważyłeś zapewne że używając funkcji printf() lub scanf() po argumencie zawierającymtekst z odpowiednimi modyfikatorami mogłeś podać praktycznie nieograniczoną liczbę ar-gumentoacutew Zapewne deklaracja obu funkcji zadziwi Cię jeszcze bardziej

int printf(const char format )int scanf(const char format )

Jak widzisz w deklaracji zostały użyte kropki Otoacuteż język C ma możliwość przekazywa-nia nieograniczonej liczby argumentoacutew do funkcji (tzn jedynym ograniczeniem jest rozmiarstosu programu) Cała zabawa polega na tym aby umieć dostać się do odpowiedniego ar-gumentu oraz poznać jego typ (używając funkcji printf mogliśmy wpisać jako argument do-wolny typ danych) Do tego celu możemy użyć wszystkich ciekawostek zawartych w plikunagłoacutewkowym stdargh

Załoacuteżmy że chcemy napisać prostą funkcję ktoacutera dajmy na to mnoży wszystkie swojeargumenty (zakładamy że argumenty są typu int) Przyjmujemy przy tym że ostatni argu-ment będzie Będzie ona wyglądała tak

include ltstdarghgt

int mnoz (int pierwszy )

va_list argint iloczyn = 1 tva_start (arg pierwszy)for (t = pierwszy t t = va_arg(arg int))

iloczyn = tva_end (arg)return iloczyn

va list oznacza specjalny typ danych w ktoacuterym przechowywane będą argumenty prze-kazane do funkcji ldquova startrdquo inicjuje arg do dalszego użytku Jako drugi parametr musimypodać nazwę ostatniego znanego argumentu funkcji Makropolecenie va arg odczytuje ko-lejne argumenty i przekształca je do odpowiedniego typu danych Na zakończenie używanejest makro va end mdash jest ono obowiązkowe

117 ZOBACZ TEŻ 87

Oczywiście tak samo jak w przypadku funkcji printf() czy scanf() argumenty nie musząbyć takich samych typoacutew Rozważmy dla przykładu funkcję podobną do printf() ale znacznieuproszczoną

include ltstdarghgt

void wypisz(const char format ) va_list argva_start (arg format)for ( format ++format)

switch (format) case i printf(d va_arg(arg int)) breakcase I printf(u va_arg(arg unsigned)) breakcase l printf(ld va_arg(arg int)) breakcase L printf(lu va_arg(arg unsigned long)) breakcase f printf(f va_arg(arg double)) breakcase x printf(x va_arg(arg unsigned)) breakcase X printf(X va_arg(arg unsigned)) breakcase s printf(s va_arg(arg const char )) breakdefault putc(format)

va_end (arg)

Przyjmuje ona jako argument ciąg znakoacutew w ktoacuterych niektoacutere instruują funkcję bypobrała argument i go wypisała Nie przejmuj się jeżeli nie rozumiesz wyrażeń format i++format Istotne jest to że pętla sprawdza po kolei wszystkie znaki formatu

1166 Ezoteryka C

C ma wiele niuansoacutew o ktoacuterych wielu programistoacutew nie wie lub łatwo o nich zapomina

jeśli nie podamy typu wartości zwracanej w funkcji zostanie przyjęty typ int (wedługnajnowszego standardu C nie podanie typu wartości jest zwracane jako błąd)

jeśli nie podamy żadnych parametroacutew funkcji to funkcja będzie używała zmiennej ilo-ści parametroacutew (inaczej niż wC++ gdzie przyjęte zostanie że funkcja nie przyjmuje ar-gumentoacutew) Aby wymusić pustą listę argumentoacutew należy napisać int funkcja(void)(dotyczy to jedynie prototypoacutew czy deklaracji funkcji)

jeśli nie użyjemy w funkcji instrukcji return wartość zwracana będzie przypadkowa(dostaniemy śmieci z pamięci)

Kompilator C++ użyty do kompilacji kodu C najczęściej zaprotestuje i ostrzeże nas jeśliużyjemy powyższych konstrukcji Natomiast czysty kompilator C z domyślnymi ustawie-niami nie napisze nic i bez mrugnięcia okiem skompiluje taki kod

117 Zobacz też C++Funkcje inline mdash funkcje rozwijane wmiejscu wywoływania (dostępne też w stan-

dardzie C)

88 ROZDZIAŁ 11 FUNKCJE

C++Przeciążanie funkcji

Rozdział 12

Preprocesor

121 Wstęp

W języku C wszystkie linijki zaczynające się od symbolu rdquo nie podlegają bezpośrednio pro-cesowi kompilacji Są to natomiast instrukcje preprocesora mdash elementu kompilatora ktoacuteryanalizuje plik źroacutedłowy w poszukiwaniu wszystkich wyrażeń zaczynających się od rdquo Napodstawie tych instrukcji generuje on kod w czystymrdquo języku C ktoacutery następnie jest kom-pilowany przez kompilator Ponieważ za pomocą preprocesora można niemal sterowaćrdquokompilatorem daje on niezwykłe możliwości ktoacutere nie były dotąd znane w innych językachprogramowania Aby przekonać się jak wygląda kod przetworzony przez preprocesor użyj(w kompilatorze gcc) przełącznika -Erdquo

gcc testc -E -o testtxt

W pliku testtxt zostanie umieszczony cały kod w postaci ktoacutera zdatna jest do przetwo-rzenia przez kompilator

122 Dyrektywy preprocesora

Dyrektywy preprocesora są towyrażenia ktoacutere występują zaraz za symbolem rdquo i to właśnieza ich pomocą możemy używać preprocesora Dyrektywa zaczyna się od znaku i kończysię wraz z końcem linii Aby przenieść dalszą część dyrektywy do następnej linii należyzakończyć linię znakiem rdquo

define add(ab) a+b

Omoacutewimy teraz kilka ważniejszych dyrektyw

1221 include

Najpopularniejsza dyrektywa wstawiająca w swoje miejsce treść pliku podanego w nawia-sach ostrych lub cudzysłowie Składnia

89

90 ROZDZIAŁ 12 PREPROCESOR

Przykład 1

include ltplik_naglowkowy_do_dolaczeniagt

Za pomocą include możemy dołączyć dowolny plik mdash niekoniecznie plik nagłoacutewkowy

Przykład 2

include plik_naglowkowy_do_dolaczenia

Jeżeli nazwa pliku nagłoacutewkowego będzie ujęta w nawiasy ostre (przykład ) to kompi-lator poszuka go wśroacuted własnych plikoacutew nagłoacutewkowych (ktoacutere najczęściej się znajdują wpodkatalogu includesrdquo w katalogu kompilatora) Jeśli jednak nazwa ta będzie ujęta w po-dwoacutejne cudzysłowy(przykład ) to kompilator poszuka jej w katalogu w ktoacuterym znajdujesię kompilowany plik (można zmienić to zachowanie w opcjach niektoacuterych kompilatoroacutew)Przy użyciu tej dyrektywy można także wskazać dokładne położenie plikoacutew nagłoacutewkowychpoprzez wpisanie bezwzględnej lub względnej ścieżki dostępu do tego pliku nagłoacutewkowego

Przykład 3 mdash ścieżka bezwzględna do pliku nagłoacutewkowego w Linuksie i w Windowsie

Opis W miejsce jednej i drugiej linijki zostanie wczytany plik umieszczony w danej lokali-zacji

include usrincludeplik_nagłoacutewkowyhinclude Cborlandincludesplik_nagłoacutewkowyh

Przykład 4 mdash ścieżka względna do pliku nagłoacutewkowego

Opis W miejsce linijki zostanie wczytany plik umieszczony w katalogu katalogrdquo a tenkatalog jest w katalogu z plikiem źroacutedłowym Inaczej moacutewiąc jeśli plik źroacutedłowy jest wkatalogu homeuserdokumentyzrodlardquo to plik nagłoacutewkowy jest umieszczony w kataloguhomeuserdokumentyzrodlakatalogrdquo

include katalog1plik_naglowkowyh

Przykład 5 mdash ścieżka względna do pliku nagłoacutewkowego

Opis Jeśli plik źroacutedłowy jest umieszczony w katalogu homeuserdokumentyzrodlardquo toplik nagłoacutewkowy znajduje się w katalogu homeuserdokumentykatalogkatalogrdquo

include katalog1katalog2plik_naglowkowyh

Więcej informacji możesz uzyskać w rozdziale Biblioteki

1222 define

Linia pozwalająca zdefiniować stałą funkcję lub słowo kluczowe ktoacutere będzie potem pod-mienione w kodzie programu na odpowiednią wartość lub może zostać użyte w instrukcjachwarunkowych dla preprocesora Składnia

define NAZWA_STALEJ WARTOSC

lub

define NAZWA_STALEJ

122 DYREKTYWY PREPROCESORA 91

Przykład

define LICZBA mdash spowoduje że każde wystąpienie słowa LICZBA w kodzie zostanie za-stąpione oacutesemkądefine SUMA(ab) (a+b) mdash spowoduje ze każde wystąpienie wywołania funkcjirdquo SUMA zo-stanie zastąpione przez sumę argumentoacutew

1223 undef

Ta instrukcja odwołuje definicję wykonaną instrukcją define

undef STALA

1224 instrukcje warunkowe

Preprocesor zawiera roacutewnież instrukcje warunkowe pozwalające nawyboacuter tego coma zostaćskompilowane w zależności od tego czy stała jest zdefiniowana lub jaką ma wartość

if elif else endif

Te instrukcje uzależniają kompilacje od warunkoacutew Ich działanie jest podobne do instrukcjiwarunkowych w samym języku C I tak

if wprowadza warunek ktoacutery jeśli nie jest prawdziwy powoduje pominięcie kompilowaniakodu aż do napotkania jednej z poniższych instrukcji

else spowoduje skompilowanie kodu jeżeli warunek za if jest nieprawdziwy aż do napo-tkania ktoacuteregoś z poniższych instrukcji

elif wprowadza nowy warunek ktoacutery będzie sprawdzony jeżeli poprzedni był niepraw-dziwy Stanowi połączenie instrukcji if i else

endif zamyka blok ostatniej instrukcji warunkowej

Przykład

if INSTRUKCJE == 2printf (Podaj liczbę z przedziału 10 do 0n) 1

elif INSTRUKCJE == 1printf (Podaj liczbę ) 2

elseprintf (Podaj parametr ) 3

endifscanf (dn ampliczba)4

wiersz nr zostanie skompilowany jeżeli stała INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany gdy INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany w pozostałych wypadkach

wiersz nr będzie kompilowany zawsze

92 ROZDZIAŁ 12 PREPROCESOR

ifdef ifndef else endif

Te instrukcje warunkują kompilację od tego czy odpowiednia stała została zdefiniowana

ifdef spowoduje że kompilator skompiluje poniższy kod tylko gdy została zdefiniowanaodpowiednia stała

ifndef ma odwrotne działanie do ifdef a mianowicie brak definicji odpowiedniej stałejumożliwia kompilacje poniższego kodu

elseendif mają identyczne zastosowanie jak te z powyższej grupy

Przykład

define INFO definicja stałej INFOifdef INFO

printf (Twoacutercą tego programu jest Jan Kowalskin)1endififndef INFO

printf (Twoacutercą tego programu jest znany programistan)2endif

To czy dowiemy się kto jest twoacutercą tego programu zależy czy instrukcja definiująca stałąINFO będzie istnieć W powyższym przypadku na ekranie powinno się wyświetlić

Twoacutercą tego programu jest Jan Kowalski

1225 error

Powoduje przerwanie kompilacji i wyświetlenie tekstu ktoacutery znajduje się za tą instrukcjąPrzydatne gdy chcemy zabezpieczyć się przed zdefiniowaniem nieodpowiednich stałych

Przykład

if BLAD == 1error Poważny błąd kompilacjiendif

Co jeżeli zdefiniujemy stałą BLAD z wartością Spowoduje to wyświetlenie w trakcie kom-pilacji komunikatu podobnego do poniższego

Fatal error programc 6 Error directive Poważny błąd kompilacjiin function main() 1 errors in Compile

wraz z przerwaniem kompilacji

1226 warning

Wyświetla tekst zawarty w cudzysłowach jako ostrzeżenie Jest często używany do sygna-lizacji programiście że dana część programu jest przestarzała lub może sprawiać problemy

122 DYREKTYWY PREPROCESORA 93

Przykład

warning To jest bardzo prosty program

Spowoduje to takie oto zachowanie kompilatora

testc32 warning warning To jest bardzo prosty program

Użycie dyrektywy warning nie przerywa procesu kompilacji i służy tylko do wyświetlaniakomunikatoacutew dla programisty w czasie kompilacji programu

1227 line

Powoduje wyzerowanie licznika linii kompilatora ktoacutery jest używany przy wyświetlaniuopisu błędoacutew kompilacji Pozwala to na szybkie znalezienie możliwej przyczyny błędu wrozbudowanym programie

Przykład

printf (Podaj wartość funkcji)lineprintf (W przedziale od 10 do 0n) tutaj jest błąd - brak cudzysłowu zamykającego

Jeżeli teraz nastąpi proacuteba skompilowania tego kodu to kompilator poinformuje że wystąpiłbłąd składni w linii a nie np

1228 Makra

Preprocesor języka C umożliwia też tworzenie makr czyli automatycznie wykonywanychczynności Makra deklaruje się za pomocą dyrektywy define

define MAKRO(arg1 arg2 ) (wyrażenie)

Wmomencie wystąpienia MAKRA w tekście preprocesor automatycznie zamieni makrona wyrażenie Makra mogą być pewnego rodzaju alternatywami dla funkcji ale powinnosię ich używać tylko w specjalnych przypadkach Ponieważ makro sprowadza się do pro-stego zastąpienia przez preprocesor wywołania makra przez jego tekst jest bardzo podatnena trudne do zlokalizowania błędy (kompilator będzie podawał błędy wmiejscach w ktoacuterychnic nie widzimy mdash bo preprocesor wstawił tam tekst) Makra są szybsze (nie następuje wy-wołanie funkcji ktoacutere zawsze zajmuje trochę czasu1) ale też mniej bezpieczne i elastyczneniż funkcje

Przeanalizujmy teraz fragment kodu

include ltstdiohgtdefine KWADRAT(x) ((x)(x))

int main ()

printf (2 do kwadratu wynosi dn KWADRAT(2))return 0

1Tak naprawdę wg standardu C99 istnieje możliwość napisania funkcji ktoacuterej kod także będzie wstawiany wmiejscu wywołania Odbywa się to dzięki inline

94 ROZDZIAŁ 12 PREPROCESOR

Preprocesor w miejsce wyrażenia KWADRAT(2) wstawił ((2)(2)) Zastanoacutewmy się costałoby się gdybyśmy napisali KWADRAT(2) Preprocesor po prostu wstawi napis do koduco da wyrażenie ((2)(2)) ktoacutere jest nieprawidłowe Kompilator zgłosi błąd ale pro-gramista widzi tylko w kodzie użycie makra a nie prawdziwą przyczynę błędu Widać tu żebezpieczniejsze jest użycie funkcji ktoacutere dają możliwość wyspecyfikowania typoacutew argumen-toacutew

Nawet jeżeli program się skompiluje to makro może dawać nieoczekiwany wynik Jesttak w przypadku poniższego kodu

int x = 1int y = KWADRAT(++x)

Dzieje się tak dlatego że makra rozwijane są przez preprocesor i kompilator widzi kod

int x = 1int y = ((++x)(++x))

Roacutewnież poniższe makra są błędne pomimo że opisany problem w nich nie występuje

define SUMA(a b) a + bdefine ILOCZYN(a b) a b

Dają one nieoczekiwane wyniki dla wywołań

SUMA(2 2) 2 6 zamiast 8 ILOCZYN(2 + 2 2 + 2) 8 zamiast 16

Z tego powodu istotne jest użycie nawiasoacutew

define SUMA(a b) ((a) + (b))define ILOCZYN(a b) ((a) (b))

1229 oraz

Dość ciekawe możliwości ma w makrach znak rdquo Zamienia on stojący za nim identyfikatorna napis

include ltstdiohgtdefine wypisz(x) printf(s=in x x)

int main()

int i=1char a=5wypisz(i)wypisz(a)return 0

Program wypisze

i=1a=5

123 PREDEFINIOWANE MAKRA 95

Czyli wypisz(a) jest rozwijane w printf(s=in a a)Natomiast znaki rdquo łączą dwie nazwy w jedną Przykład

include ltstdiohgtdefine abc(x) int zmienna x

int main()

abc(nasza) dzięki temu zadeklarujemy zmienną o nazwie zmiennanasza zmiennanasza = 2return 0

Więcej o dobrych zwyczajach w tworzeniu makr można się dowiedzieć w rozdziale Po-wszechne praktyki

123 Predefiniowane makraW języku wprowadzono roacutewnież serię predefiniowanych makr ktoacutere mają ułatwić życie pro-gramiście Oto one

DATE mdash data w momencie kompilacji

TIME mdash godzina w momencie kompilacji

FILE mdash łańcuch ktoacutery zawiera nazwę pliku ktoacutery aktualnie jest kompilowany przezkompilator

LINE mdash definiuje numer linijki

STDC mdash w kompilatorach zgodnych ze standardem ANSI lub nowszym makro toprzyjmuje wartość

STDC VERSION mdash zależnie od poziomu zgodności kompilatora makro przyjmuje roacuteżnewartości

ndash jeżeli kompilator jest zgodny z ANSI (rok ) makro nie jest zdefiniowane

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199409L

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199901L

Warto roacutewnież wspomnieć o identyfikatorze func zdefiniowanym w standardzie Cktoacuterego wartość to nazwa funkcji

Sproacutebujmy użyć tych makr w praktyce

include ltstdiohgt

if __STDC_VERSION__ gt= 199901L Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go define BUG(message) fprintf(stderr sd s (w funkcji s)n

__FILE__ __LINE__ message __func__)else Jezeli __func__ nie ma to go nie używamy

96 ROZDZIAŁ 12 PREPROCESOR

define BUG(message) fprintf(stderr sd sn __FILE__ __LINE__ message)

endif

int main(void) printf(Program ABC data kompilacji s sn __DATE__ __TIME__)

BUG(Przykladowy komunikat bledu)return 0

Efekt działania programu gdy kompilowany jest kompilatorem C

Program ABC data kompilacji Sep 1 2008 191213testc17 Przykladowy komunikat bledu (w funkcji main)

Gdy kompilowany jest kompilatorem ANSI C

Program ABC data kompilacji Sep 1 2008 191316testc17 Przykladowy komunikat bledu

Rozdział 13

Biblioteka standardowa

131 Czym jest biblioteka

Bibliotekę w języku C stanowi zbioacuter skompilowanych wcześniej funkcji ktoacutery można łączyćz programem Biblioteki tworzy się aby udostępnić zbioacuter pewnych ldquowyspecjalizowanychrdquofunkcji do dyspozycji innych programoacutew Tworzenie bibliotek jest o tyle istotne że takiepodejście znacznie ułatwia tworzenie nowych programoacutew Łatwiej jest utworzyć program woparciu o istniejące biblioteki niż pisać programwraz zewszystkimi potrzebnymi funkcjami1

132 Po co nam biblioteka standardowa

W ktoacuterymś z początkowych rozdziałoacutew tego podręcznika napisane jest że czysty język C niemoże zbyt wiele Tak naprawdę to język C sam w sobie praktycznie nie ma mechanizmoacutewdo obsługi np wejścia-wyjścia Dlatego też większość systemoacutew operacyjnych posiada tzwbibliotekę standardową zwaną też biblioteką języka C To właśnie w niej zawarte są pod-stawowe funkcjonalności dzięki ktoacuterym twoacutej program może np napisać coś na ekranie

1321 Jak skonstruowana jest biblioteka standardowa

Zapytacie zapewne jak biblioteka standardowa realizuje te funkcje skoro sam język C tegonie potrafi Odpowiedź jest prosta mdash biblioteka standardowa nie jest napisana w samym ję-zyku C Ponieważ C jest językiem tłumaczonym do kodu maszynowego to w praktyce niema żadnych przeszkoacuted żeby np połączyć go z językiem niskiego poziomu jakim jest npasembler Dlatego biblioteka C z jednej strony udostępnia gotowe funkcje w języku C a zdrugiej za pomocą niskopoziomowych mechanizmoacutew2 komunikuje się z systemem operacyj-nym ktoacutery wykonuje odpowiednie czynności

133 Gdzie są funkcje z biblioteki standardowej

Pisząc program w języku C używamy roacuteżnego rodzaju funkcji takich jak np printf Niejesteśmy jednak ich autorami mało tego nie widzimy nawet deklaracji tych funkcji w naszymprogramie Pamiętacie program ldquoHello worldrdquo Zaczynał on się od takiej oto linijki

1Początkujący programista zapewne nie byłby w stanie napisać nawet funkcji printf2Takich jak np wywoływanie przerwań programowych

97

98 ROZDZIAŁ 13 BIBLIOTEKA STANDARDOWA

include ltstdiohgt

linijka ta oznacza ldquow tym miejscu wstaw zawartość pliku stdiohrdquo Nawiasy ldquoltrdquo i ldquogtrdquooznaczają że plik stdioh znajduje się w standardowym katalogu z plikami nagłoacutewkowymiWszystkie pliki z rozszerzeniem h są właśnie plikami nagłoacutewkowymi Wroacutećmy teraz do te-matu biblioteki standardowej Każdy system operacyjny ma za zadanie wykonywać pewnefunkcje na rzecz programoacutew Wszystkie te funkcje zawarte są właśnie w bibliotece standar-dowej W systemach z rodziny UNIX nazywa się ją LibC (biblioteka języka C) To tamwłaśnieznajduje się funkcja printf scanf puts i inne

Oproacutecz podstawowych funkcji wejścia-wyjścia biblioteka standardowa udostępnia teżmożliwość wykonywania funkcji matematycznych komunikacji przez sieć oraz wykonywa-nia wielu innych rzeczy

1331 Jeśli biblioteka nie jest potrzebna

Czasami korzystanie z funkcji bibliotecznych oraz standardowych plikoacutew nagłoacutewkowych jestniepożądane np wtedy gdy programista pisze swoacutej własny system operacyjny oraz biblio-tekę do niego Aby wyłączyć używanie biblioteki C w opcjach kompilatora GCC możemydodać następujące argumenty

-nostdinc -fno-builtin

134 Opis funkcji biblioteki standardowejPodręcznik C na Wikibooks zawiera opis dużej części biblioteki standardowej C

Indeks alfabetyczny

Indeks tematyczny

W systemach uniksowych możesz uzyskać pomoc dzięki narzędziu man przykładowopisząc

man printf

135 UwagiProgramy w języku C++ mogą dokładnie w ten sam sposoacuteb korzystać z biblioteki standar-dowej ale zalecane jest by robić to raczej w trochę odmienny sposoacuteb właściwy dla C++Szczegoacuteły w podręczniku C++

Rozdział 14

Czytanie i pisanie do plikoacutew

141 Pojęcie plikuNa początku dobrze by było abyś dowiedział się czym jest plik Odpowiedni artykuł do-stępny jest w Wikipedii Najprościej moacutewiąc plik to pewne dane zapisane na dysku

142 Identyfikacja plikuKażdy z nas korzystając na co dzień z komputera przyzwyczaił się do tego że plik ma okre-śloną nazwę Jednak w pisaniu programu posługiwanie się całą nazwą niosło by ze sobą conajmniej dwa problemy

pamięciożerność mdash przechowywanie całego (czasami nawet -bajtowego łańcucha)zajmuje niepotrzebnie pamięć

ryzyko błędoacutew (owe błędy szerzej omoacutewione zostały w rozdziale Napisy)

Aby uprościć korzystanie z plikoacutew programiści wpadli na pomysł aby identyfikatorempliku stała się liczba Dzięki temu kod programu stał się czytelniejszy oraz wyeliminowanokonieczność ciągłego korzystania z łańcuchoacutew Jednak sam plik nadal jest identyfikowany poswojej nazwie Aby ldquoprzetworzyćrdquo nazwę pliku na odpowiednią liczbę korzystamy z funkcjiopen lub fopen Roacuteżnica wyjaśniona jest poniżej

143 Podstawowa obsługa plikoacutewIstnieją dwie metody obsługi czytania i pisania do plikoacutew

wysokopoziomowa

niskopoziomowa

Nazwy funkcji z pierwszej grupy zaczynają się od litery ldquordquo (np fopen() fread() fclose())a identyfikatorem pliku jest wskaźnik na strukturę typu FILE Owa struktura to pewna grupazmiennych ktoacutera przechowuje dane o danym pliku mdash jak na przykład aktualną pozycję wnim Szczegoacutełami nie musisz się przejmować funkcje biblioteki standardowej same zajmująsię wykorzystaniem struktury FILE programista może więc zapomnieć czym tak naprawdęjest struktura FILE i traktować taką zmienną jako ldquouchwytrdquo identyfikator pliku

99

100 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

Druga grupa to funkcje typu read() open() write() i close()Podstawowym identyfikatorem pliku jest liczba całkowita ktoacutera jednoznacznie identy-

fikuje dany plik w systemie operacyjnym Liczba ta w systemach typu jest nazywanadeskryptorem pliku

Należy pamiętać że nie wolno nam używać funkcji z obu tych grup jednocześnie w sto-sunku do jednego otwartego pliku tzn nie można najpierw otworzyć pliku za pomocą fo-pen() a następnie odczytywać danych z tego samego pliku za pomocą read()

Czym roacuteżnią się oba podejścia do obsługi plikoacutew Otoacuteż metoda wysokopoziomowa maswoacutej własny bufor w ktoacuterym znajdują się dane po odczytaniu z dysku a przed wysłaniemich do programu użytkownika W przypadku funkcji niskopoziomowych dane kopiowane sąbezpośrednio z pliku do pamięci programu W praktyce używanie funkcji wysokopoziomo-wych jest prostsze a przy czytaniu danych małymi porcjami roacutewnież często szybsze i właśnieten model zostanie tutaj zaprezentowany

1431 Dane znakowe

Skupimy się teraz na najprostszym zmożliwych zagadnień mdash zapisie i odczycie pojedynczychznakoacutew oraz całych łańcuchoacutew

Napiszmy zatem nasz pierwszy program ktoacutery stworzy plik ldquotesttxtrdquo i umieści w nimtekst ldquoHello worldrdquo

include ltstdiohgtinclude ltstdlibhgt

int main ()

FILE fp używamy metody wysokopoziomowej musimy mieć zatem identyfikator pliku uwaga na gwiazdkę

char tekst[] = Hello worldif ((fp=fopen(testtxt w))==NULL)

printf (Nie mogę otworzyć pliku testtxt do zapisun)exit(1)

fprintf (fp s tekst) zapisz nasz łańcuch w pliku fclose (fp) zamknij plik return 0

Teraz omoacutewimy najważniejsze elementy programu Jak już było wspomniane wyżej doidentyfikacji pliku używa się wskaźnika na strukturę FILE (czyli FILE ) Funkcja fopenzwraca oacutew wskaźnik w przypadku poprawnego otwarcia pliku bądź też NULL gdy plik niemoże zostać otwarty Pierwszy argument funkcji to nazwa pliku natomiast drugi to trybdostępu mdash w oznacza ldquowriterdquo (pisanie) zwroacutecony ldquouchwytrdquo do pliku będzie moacutegł być wyko-rzystany jedynie w funkcjach zapisujących dane I odwrotnie gdy otworzymy plik podająctryb r (ldquoreadrdquo czytanie) będzie można z niego jedynie czytać dane Funkcja fopen zostaładokładniej opisana w odpowiedniej części rozdziału o bibliotece standardowej

Po zakończeniu korzystania z pliku należy plik zamknąć Robi się to za pomocą funk-cji fclose Jeśli zapomnimy o zamknięciu pliku wszystkie dokonane w nim zmiany zostanąutracone

143 PODSTAWOWA OBSŁUGA PLIKOacuteW 101

1432 Pliki a strumienie

Można zauważyć że do zapisu do pliku używamy funkcji fprintf ktoacutera wygląda bardzopodobnie do printf mdash jedyną roacuteżnicą jest to że w fprintf musimy jako pierwszy argu-ment podać identyfikator pliku Nie jest to przypadek mdash obie funkcje tak naprawdę robiątak samo Używana do wczytywania danych z klawiatury funkcja scanf też ma swoacutej od-powiednik wśroacuted funkcji operujących na plikach mdash jak nietrudno zgadnąć nosi ona nazwęfscanf

W rzeczywistości język C traktuje tak samo klawiaturę i plik mdash są to źroacutedła danych po-dobnie jak ekran i plik do ktoacuterych można dane kierować Jest to myślenie typowe dla sys-temoacutew typu UNIX jednak dla użytkownikoacutew przyzwyczajonych do systemu Windows albojęzykoacutew typu Pascal może być to co najmniej dziwne Nie da się ukryć że między klawia-turą i plikiem na dysku zachodzą podstawowe roacuteżnice i dostęp do nich odbywa się inaczejmdash jednak funkcje języka C pozwalają nam o tym zapomnieć i same zajmują się szczegoacutełamitechnicznymi Z punktu widzenia programisty urządzenia te sprowadzają się do nadanegoim identyfikatora Uogoacutelnione pliki nazywa się w C strumieniami

Każdy program w momencie uruchomienia ldquootrzymujerdquo od razu trzy otwarte strumienie

stdin (wejście)

stdout (wyjście)

stderr (wyjście błędoacutew)

(aby z nich korzystać należy dołączyć plik nagłoacutewkowy stdioh)Pierwszy z tych plikoacutew umożliwia odczytywanie danych wpisywanych przez użytkow-

nika natomiast pozostałe dwa służą do wyprowadzania informacji dla użytkownika oraz po-wiadamiania o błędach

Warto tutaj zauważyć że konstrukcja

fprintf (stdout Hej ja działam)

jest roacutewnoważna konstrukcji

printf (Hej ja działam)

Podobnie jest z funkcją scanf()

fscanf (stdin d ampzmienna)

działa tak samo jak

scanf(d ampzmienna)

1433 Obsługa błędoacutew

Jeśli nastąpił błąd możemy się dowiedzieć o jego przyczynie na podstawie zmiennej errnozadeklarowanej w pliku nagłoacutewkowym errnoh Możliwe jest też wydrukowanie komunikatuo błedzie za pomocą funkcji perror Na przykład używając

fp = fopen (tego pliku nie ma r)if( fp == NULL )

perror(błąd otwarcia pliku)exit(-10)

102 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

dostaniemy komunikat

błąd otwarcia pliku No such file or directory

1434 Zaawansowane operacje

Pora na kolejny tym razem bardziej złożony przykład Oto kroacutetki program ktoacutery swojewejście zapisuje do pliku o nazwie podanej w linii poleceń

include ltstdiohgtinclude ltstdlibhgt program udający bardzo prymitywną wersję programu tee(1)

int main (int argc char argv[])

FILE fpint cif (argc lt 2)

fprintf (stderr Uzycie s nazwa_plikun argv[0])exit (-1)

fp = fopen (argv[1] w)if (fp)

fprintf (stderr Nie moge otworzyc pliku sn argv[1])exit (-1)

printf(Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczycn)while ( (c = fgetc(stdin)) = EOF)

fputc (c stdout)fputc (c fp)

fclose(fp)return 0

Tym razem skorzystaliśmy już z dużo większego repertuaru funkcji Między innymimożna zauważyć tutaj funkcję fputc() ktoacutera umieszcza pojedynczy znak w pliku Ponadto wwyżej zaprezentowanym programie została użyta stała EOF ktoacutera reprezentuje koniec pliku(ang End Of File) Powyższy program otwiera plik ktoacuterego nazwa przekazywana jest jakopierwszy argument programu a następnie kopiuje dane z wejścia programu (stdin) na wyj-ście (stdout) oraz do utworzonego pliku (identyfikowanego za pomocą fp) Program robi todotąd aż naciśniemy kombinację klawiszy Ctrl+D(w systemach Unixowych) lub Ctrl+Z(wWindows) ktoacutera wyśle do programu informację że skończyliśmy wpisywać dane Programwyjdzie wtedy z pętli i zamknie utworzony plik

144 Rozmiar plikuDzięki standardowym funkcjom języka C możemy min określić długość pliku Do tego celusłużą funkcje fsetpos fgetpos oraz fseek Ponieważ przy każdym odczyciezapisie zdo plikuwskaźnik niejako ldquoprzesuwardquo się o liczbę przeczytanychzapisanych bajtoacutew Możemy jednak

145 PRZYKŁAD mdash PLIKI GRAFICZNY 103

ustawić wskaźnik w dowolnie wybranym miejscu Do tego właśnie służą wyżej wymienionefunkcje Aby odczytać rozmiar pliku powinniśmy ustawić nasz wskaźnik na koniec plikupo czym odczytać ile bajtoacutew od początku pliku się znajdujemy Wiem brzmi to strasznieale działa wyjątkowo prosto i skutecznie Użyjemy do tego tylko dwoacutech funkcji fseek orazfgetpos Pierwsza służy do ustawiania wskaźnika na odpowiedniej pozycji w pliku a drugado odczytywania na ktoacuterym bajcie pliku znajduje się wskaźnik Kod ktoacutery określa rozmiarpliku znajduje się tutaj

include ltstdiohgt

int main (int argc char argv)

FILE fp = NULLfpos_t dlugoscif (argc = 2)

printf (Użycie s ltnazwa plikugtn argv[0])return 1

if ((fp=fopen(argv[1] rb))==NULL) printf (Błąd otwarcia pliku sn argv[1])return 1

fseek (fp 0 SEEK_END) ustawiamy wskaźnik na koniec pliku fgetpos (fp ampdlugosc)printf (Rozmiar pliku dn dlugosc)fclose (fp)return 0

Znajomość rozmiaru pliku przydaje się w wielu roacuteżnych sytuacjach więc dobrze prze-analizuj przykład

145 Przykład mdash pliki graficznyNajprostszym przykładem rastrowego pliku graficznego jest plik Poniższy program po-kazuje jak utworzyć plik w katalogu roboczym programu Do zapisu

nagłoacutewka pliku używana jest funkcja fprintf

tablicy do pliku używana jest funkcja fwrite

include ltstdiohgtint main()

const int dimx = 800const int dimy = 800int i jFILE fp = fopen(firstppm wb) b - tryb binarny fprintf(fp P6nd dn255n dimx dimy)for(j=0 jltdimy ++j)

for(i=0 iltdimx ++i)static unsigned char color[3]

104 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

color[0]=i 255 red color[1]=j 255 green color[2]=(ij) 255 blue fwrite(color13fp)

fclose(fp)return 0

W powyższym przykładzie dostęp do danych jest sekwencyjny Jeśli chcemy mieć swo-bodny dostęp do danych to

korzystać z funkcji fsetpos fgetpos oraz fseek

utworzyć tablicę (dla dużych plikoacutew dynamiczną) zapisać do niej wszystkie dane anastępnie zapisać całą tablicę do pliku Ten sposoacuteb jest prostszy i szybszy Należyzwroacutecić uwagę że do obliczania rozmiaru całej tablicy nie możemy użyć funkcji sizeof

(a) Przykład użycia tej techniki sekwencyjnydostęp do danych (kod źroacutedłowy)

(b) Przykład użycia tej techniki swobodny do-stęp do danych (kod źroacutedłowy)

146 Co z katalogamiFaktycznie zapomnieliśmy o nich Jednak wynika to z tego że specyfikacja ANSI C nieuwzględnia obsługi katalogoacutew

Rozdział 15

Ćwiczenia dla początkujący

151 ĆwiczeniaWszystkie zamieszczone tutaj ćwiczenia mają na celu pomoacutec Ci w sprawdzeniu Twojej wie-dzy oraz umożliwieniu Tobie wykorzystania nowo nabytych wiadomości w praktyce Pa-miętaj także że ten podręcznik ma służyć także innym więc nie zamieszczaj tutaj Twoichrozwiązań Zachowaj je dla siebie

1511 Ćwiczenie 1

Napisz program ktoacutery wyświetli na ekranie twoje imię i nazwisko

1512 Ćwiczenie 2

Napisz program ktoacutery poprosi o podanie dwoacutech liczb rzeczywistych i wyświetli wynik mno-żenia obu zmiennych

1513 Ćwiczenie 3

Napisz program ktoacutery pobierze jako argumenty z linii komend nazwy dwoacutech plikoacutew i prze-kopiuje zawartość pierwszego pliku do drugiego (tworząc lub zamazując drugi)

1514 Ćwiczenie 4

Napisz program ktoacutery utworzy nowy plik (o dowolnie wybranej przez Ciebie nazwie) i za-pisze tam

Twoje imię

wiek

miasto w ktoacuterym mieszkasz

Przykładowy plik powinien wyglądać tak

Stanisław30Krakoacutew

105

106 ROZDZIAŁ 15 ĆWICZENIA DLA POCZĄTKUJĄCYCH

1515 Ćwiczenie 5

Napisz program generujący tabliczkę mnożenia x i wyświetlający ją na ekranie

1516 Ćwiczenie 6 mdash dla ętny

Napisz program znajdujący pierwiastki troacutejmianu kwadratowego ax2+bx+c= dla zadanychparametroacutew a b c

Rozdział 16

Tablice

W rozdziale Zmienne w C dowiedziałeś się jak przechowywać pojedyncze liczby oraz znakiCzasami zdarza się jednak że potrzebujemy przechować kilka kilkanaście albo iwięcej zmien-nych jednego typu Nie tworzymy wtedy np dwudziestu osobnych zmiennych W takichprzypadkach z pomocą przychodzi nam tablica

Rysunek 161 tablica 10-elementowa

Tablica to ciąg zmiennych jednego typu Ciąg taki posiada jedną nazwę a do jego po-szczegoacutelnych elementoacutew odnosimy się przez numer (indeks)

161 Wstęp

1611 Sposoby deklaracji tablic

Tablicę deklaruje się w następujący sposoacuteb

typ nazwa_tablicy[rozmiar]

gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy Zatemaby np zadeklarować tablicę mieszczącą liczb całkowitych możemy napisać tak

int tablica[20]

Podobnie jak przy deklaracji zmiennych także tablicy możemy nadać wartości począt-kowe przy jej deklaracji Odbywa się to przez umieszczenie wartości kolejnych elementoacutewoddzielonych przecinkami wewnątrz nawiasoacutew klamrowych

int tablica[3] = 123

Może to się wydać dziwne ale po ostatnim elemencie tablicy może występować przeci-nek Ponadto jeżeli poda się tylko część wartości w pozostałe wpisywane są zera

107

108 ROZDZIAŁ 16 TABLICE

int tablica[20] = 1

Niekoniecznie trzeba podawać rozmiar tablicy np

int tablica[] = 1 2 3 4 5

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku mdash elemen-toacutew)

Rozpatrzmy następujący kod

include ltstdiohgtdefine ROZMIAR 3int main()

int tab[ROZMIAR] = 368int iprintf (Druk tablicy tabn)

for (i=0 iltROZMIAR ++i) printf (Element numer d = dn i tab[i])

return 0

Wynik

Druk tablicy tabElement numer 0 = 3Element numer 1 = 6Element numer 2 = 8

Jak widać wszystko się zgadza W powyżej zamieszczonym przykładzie użyliśmy stałejdo podania rozmiaru tablicy Jest to o tyle pożądany zwyczaj że w razie konieczności zmianyrozmiaru tablicy zmieniana jest tylko jedna linijka kodu przy stałej a nie kilkadziesiąt innychlinijek rozsianych po kodzie całego programu

W pierwotnym standardzie języka C rozmiar tablicy nie moacutegł być określany przezzmienną lub nawet stałą zadeklarowaną przy użyciu słowa kluczowego const Dopiero wpoacuteźniejszej wersji standardu (tzw C) dopuszczono taką możliwość Dlatego do dekla-rowania rozmiaru tablic często używa się dyrektywy preprocesora define Powinni na tozwroacutecić uwagę zwłaszcza programiści C++ gdyż tam zawsze możliwe były oba sposoby

Innym sposobem jest użycie operatora sizeof do poznania wielkości tablicy Poniższy kodrobi to samo co przedstawiony

include ltstdiohgtint main()

int tab[3] = 368int i

162 ODCZYTZAPIS WARTOŚCI DO TABLICY 109

printf (Druk tablicy tabn)

for (i=0 ilt(sizeof tab sizeof tab) ++i) printf (Element numer d = dn i tab[i])

return 0

Należy pamiętać że działa on tylko dla tablic a nie wskaźnikoacutew (jak poacuteźniej się dowieszwskaźnik też można w pewnym stopniu traktować jak tablicę)

162 Odczytzapis wartości do tablicyTablicami posługujemy się tak samo jak zwykłymi zmiennymi Roacuteżnica polega jedynie napodaniu indeksu tablicy Określa on jednoznacznie z ktoacuterego elementu (wartości) chcemyskorzystać Indeksem jest liczba naturalna począwszy od zera To oznacza że pierwszy ele-ment tablicy ma indeks roacutewny drugi trzeci itd

Osoby ktoacutere wcześniej programowały w językach takich jak Pascal Basic czy Fortranmuszą przyzwyczaić się do tego że w języku C indeks numeruje się od Ponadto indeksempowinna być liczba - istnieje możliwość indeksowania za pomocą np pojedynczych znakoacutew(rsquoarsquo rsquobrsquo itp) jednak Cwewnętrznie konwertuje takie znaki na liczby im odpowiadające zatemtablica indeksowana znakami byłaby niepraktyczna i musiałaby mieć odpowiednio większyrozmiar

Sproacutebujmy przedstawić to na działającym przykładzie Przeanalizuj następujący kod

int tablica[5] = 0int i = 0tablica[2] = 3tablica[3] = 7for (i=0i=5++i)

printf (tablica[d]=dn i tablica[i])

Jak widać na początku deklarujemy -elementową tablicę ktoacuterą od razu zerujemy Na-stępnie pod trzeci i czwarty element podstawiamy liczby i Pętla ma za zadanie wypro-wadzić wynik naszych działań

163 Tablice znakoacutewTablice znakoacutew tj typu char oraz unsigned char posiadają dwie ogoacutelnie przyjęte nazwyzależnie od ich przeznaczenia

bufory mdash gdy wykorzystujemy je do przechowywania ogoacutelnie pojętych danych gdytraktujemy je jako po prostu ldquociągi bajtoacutewrdquo (typ char ma rozmiar bajta więc jestelastyczny do przechowywania np danych wczytanych z pliku przed ich przetworze-niem)

napisy mdash gdy zawarte w nich dane traktujemy jako ciągi liter jest im poświęconyosobny rozdział Napisy

110 ROZDZIAŁ 16 TABLICE

164 Tablice wielowymiarowe

Rysunek tablica dwuwymia-rowa (x)

Rozważmy teraz konieczność przechowania w pa-mięci komputera całej macierzy o wymiarach x Można by tego dokonać tworząc osobnych ta-blic jednowymiarowych reprezentujących poszcze-goacutelne wiersze macierzy Jednak język C dostarczanam dużo wygodniejszej metody ktoacutera w dodatkujest bardzo łatwa w użyciu Są to tablice wielowy-miarowe lub inaczej ldquotablice tablicrdquo Tablice wielo-wymiarowe definiujemy podając przy zmiennej kilkawymiaroacutew np

float macierz[10][10]

Tak samo wygląda dostęp do poszczegoacutelnych ele-mentoacutew tablicy

macierz[2][3] = 12

Jakwidać ten sposoacuteb jest dużowygodniejszy (i za-pewne dużo bardziej ldquonaturalnyrdquo) niż deklarowanie osobnych tablic jednowymiarowych Aby zaini-cjować tablicę wielowymiarową należy zastosowaćzagłębianie klamer np

float macierz[3][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz

Dodatkowo pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowy-miarowych) i woacutewczas kompilator sam ustali odpowiednią wielkość np

float macierz[][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz 63 27 57 27 czwarty wiersz

Innym bardziej elastycznym sposobem deklarowania tablic wielowymiarowych jest uży-cie wskaźnikoacutew Opisane to zostało w następnym rozdziale

165 Ograniczenia tablicPomimo swej wygody tablice mają ograniczony z goacutery zdefiniowany rozmiar ktoacuterego niemożna zmienić w trakcie działania programu Dlatego też w niektoacuterych zastosowaniach ta-blice zostały wyparte przez dynamiczną alokację pamięci Opisane to zostało w następnymrozdziale

166 CIEKAWOSTKI 111

Przy używaniu tablic trzeba być szczegoacutelnie ostrożnym przy konstruowaniu pętli ponie-waż ani kompilator ani skompilowany program nie będą w stanie wychwycić przekroczeniaprzez indeks rozmiaru tablicy 1 Efektem będzie odczyt lub zapis pamięci znajdującej się pozatablicą

Wystarczy pomylić się o jednomiejsce (tzw błąd off by one) by spowodować że działanieprogramu zostanie nagle przerwane przez system operacyjny

int foo[100]int i

for (i=0 ilt=100 ++i) powinno być ilt100 foo[i] = 0

166 CiekawostkiW pierwszej edycji konkursu IOCCC zwyciężył program napisany w C ktoacutery wyglądał dośćnietypowo

short main[] = 277 04735 -4129 25 0 477 1019 0xbef 0 12800-113 21119 0x52d7 -1006 -7151 0 0x4bc 02000414880 10541 2056 04010 4548 3044 -6716 0x94407 6 5568 1 -30460 0 0x9 5570 512 -304190x7e82 0760 6 0 4 02400 15 0 4 1280 4 04 0 0 0 0x8 0 4 0 0 12 0 4 0 0 020 0 4 0 30 0 026 0 0x6176 120 25712p 072163 r 29303 29801 e

Co ciekawe mdash program ten bez przeszkoacuted wykonywał się na komputerach VAX- orazPDP- Cały program to po prostu tablica z zawartym wewnątrz kodem maszynowym Taknaprawdę jest to wykorzystanie pewnych właściwości programu ktoacutery ostatecznie produ-kuje kod maszynowy Linker (to o nim mowa) nie rozroacuteżnia na dobrą sprawę nazw funkcjiod nazw zmiennych więc bez problemu ustawił punkt wejścia programu na tablicę wartościw ktoacuterych zapisany był kod maszynowy Tak przygotowany program został bez problemuwykonany przez komputer

1W zasadzie kompilatory mają możliwość dodania takiego sprawdzania ale nie robi się tego gdyż znaczniespowolniłoby to działanie programu Takie postępowanie jest jednak pożądane w okresie testowania programu

112 ROZDZIAŁ 16 TABLICE

Rozdział 17

Wskaźniki

Zobacz w WikipediiZmienna wskaźnikowaZmienne w komputerze są przechowywane w pamięci To wie każdy programista a dobry

programista potrafi kontrolować zachowanie komputera w przydzielaniu i obsługi pamięcidla zmiennych W tym celu pomocne są wskaźniki

171 Co to jest wskaźnik

Dla ułatwienia przyjęto poniżej że bajt ma bitoacutew typ int składa się z dwoacutech bajtoacutew(bitoacutew) typ long składa się z czterech bajtoacutew ( bitoacutew) oraz liczby zapisane są w formaciebig endian (tzn bardziej znaczący bajt na początku) co niekoniecznie musi być prawdą naTwoim komputerze

Rysunek Wskaźnik awskazu-jący na zmienną b Zauważmy żeb przechowuje liczbę podczas gdya przechowuje adres b w pamięci()

Wskaźnik (ang pointer) to specjalny rodzaj zmiennejw ktoacuterej zapisany jest adres w pamięci komputera tznwskaźnik wskazuje miejsce gdzie zapisana jest jakaśinformacja Oczywiście nic nie stoi na przeszkodzie abywskazywaną daną był innywskaźnik do kolejnegomiej-sca w pamięci

Obrazowo możemy wyobrazić sobie pamięć kom-putera jako bibliotekę a zmienne jako książki Zamiastbrać książkę z poacutełki samemu (analogicznie do korzy-stania wprost ze zwykłych zmiennych) możemy podaćbibliotekarzowi wypisany rewers z numerem katalogo-wym książki a on znajdzie ją za nas Analogia ta niejest doskonała ale pozwalawyobrazić sobie niektoacutere ce-chy wskaźnikoacutew kilka rewersoacutew może dotyczyć tej sa-mej książki numer w rewersie możemy skreślić i użyćgo do zamoacutewienia innej książki jeśli wpiszemy niepra-widłowy numer katalogowy to możemy dostać nie tąksiążkę ktoacuterą chcemy albo też nie dostać nic

Warto też poznać w tym miejscu definicję adresupamięci Możemy powiedzieć że adres to pewna liczba całkowita jednoznacznie definiującapołożenie pewnego obiektu (czyli np znaku czy liczby) w pamięci komputera Dokładniejsządefinicję możesz znaleźć w Wikipedii

113

114 ROZDZIAŁ 17 WSKAŹNIKI

172 Operowanie na wskaźnikaBy stworzyć wskaźnik do zmiennej i moacutec się nim posługiwać należy przypisać mu odpo-wiednią wartość (adres obiektu na jaki ma wskazywać) Skąd mamy znać ten adres Wy-starczy zapytać nasz komputer jaki adres przydzielił zmiennej ktoacuterą np wcześniej gdzieśstworzyliśmy Robi się to za pomocą operatora amp (operatora pobrania adresu) Przeanalizujnastępujący kod1

include ltstdiohgt

int main (void)

int liczba = 80printf(Zmienna znajduje sie pod adresem p i przechowuje wartosc dn

(void)ampliczba liczba)return 0

Program ten wypisuje adres pamięci pod ktoacuterym znajduje się zmienna oraz wartość jakąkryje zmienna przechowywana pod owym adresem

Aby moacutec zapisać gdzieś taki adres należy zadeklarować zmienną wskaźnikową Robi sięto poprzez dodanie (gwiazdki) po typie na jaki zmienna ma wskazywać np

int wskaznik1char wskaznik2floatwskaznik3

Niektoacuterzy programiści mogą nieco błędnie interpretować wskaźnik do typu jako nowytyp i uważać że jeśli napiszą

int abc

to otrzymają trzy wskaźniki do liczby całkowitej Tymczasem wskaźnikiem będzie tylkozmienna a natomiast b i c będą po prostu liczbami Powodem jest to że rdquogwiazdkaodnosi siędo zmiennej a nie do typu W tym przypadku trzy wskaźniki otrzymamy pisząc

int abc

Aby uniknąć pomyłek lepiej jest pisać gwiazdkę tuż przy zmiennej

int abc

albo jeszcze lepiej nie mieszać deklaracji wskaźnikoacutew i zmiennych

int aint bc

Aby dobrać się dowartości wskazywanej przez wskaźnik należy użyć unarnego operatora (gwiazdka) zwanego operatorem wyłuskania

1Warto zwroacutecić uwagę na rzutowanie do typu wskaźnik na void Rzutowanie to jest wymagane przez funkcjęprintf gdyż ta oczekuje że argumentem dla formatu p będzie właśnie wskaźnik na void gdy tymczasem w naszymprzykładzie wyrażenie ampliczba jest typu wskaźnik na int

172 OPEROWANIE NA WSKAŹNIKACH 115

include ltstdiohgt

int main (void)

int liczba = 80int wskaznik = ampliczbaprintf(Wartosc zmiennej d jej adres pn liczba (void)ampliczba)printf(Adres zapisany we wskazniku p wskazywana wartosc dn

(void)wskaznik wskaznik)

wskaznik = 42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

liczba = 0x42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

return 0

1721 O coodzi z tym typem na ktoacutery ma wskazywać Czemu to takieważne

Jest to ważne z kilku powodoacutewRoacuteżne typy zajmują w pamięci roacuteżną wielkość Przykładowo jeżeli w zmiennej typu

unsigned int zapiszemy liczbę to w pamięci będzie istnieć jako

+--------+--------+|komoacuterka1|komoacuterka2|+--------+--------+|11111111|11111010| = (unsigned int) 65530+--------+--------+

Wskaźnik do takiej zmiennej (jak i do dowolnej innej) będzie wskazywać na pierwsząkomoacuterkę w ktoacuterej ta zmienna ma swoją wartość

Jeżeli teraz stworzymy drugi wskaźnik do tego adresu tym razem typu unsigned arto wskaźnik przejmie ten adres prawidłowo2 lecz gdy sproacutebujemy odczytać wartość na jakąwskazuje ten wskaźnik to zostanie odczytana tylko pierwsza komoacuterka i wynik będzie roacutewny

+--------+|komoacuterka1|+--------+|11111111| = (unsigned char) 255+--------+

2Tak naprawdę nie zawsze można przypisywać wartości jednych wskaźnikoacutew do innych Standard C gwaran-tuje jedynie że można przypisać wskaźnikowi typu void wartość dowolnego wskaźnika a następnie przypisać tąwartość do wskaźnika pierwotnego typu oraz że dowolny wskaźnik można przypisać do wskaźnika typu char

116 ROZDZIAŁ 17 WSKAŹNIKI

Gdybyśmy natomiast stworzyli inny wskaźnik do tego adresu tym razem typu unsignedlong to przy proacutebie odczytu odczytane zostaną dwa bajty z wartością zapisaną w zmiennejunsigned int oraz dodatkowe dwa bajty z niewiadomą zawartością i woacutewczas wynik będzieroacutewny + przypadkowa wartość

+--------+--------+--------+--------+|komoacuterka1|komoacuterka2|komoacuterka3|komoacuterka4|+--------+--------+--------+--------+|11111111|11111010|||+--------+--------+--------+--------+

Ponadto zapis czy odczyt poza przydzielonym obszarem pamięci może prowadzić donieprzyjemnych skutkoacutew takich jak zmiana wartości innych zmiennych czy wręcz natych-miastowe przerwanie programu Jako przykład można podać ten (błędny) program3

include ltstdiohgt

int main(void)

unsigned char tab[10] = 100 101 102 103 104 105 106 107 108 109 unsigned short ptr = (unsigned short)amptab[2]unsigned i

ptr = 0xfffffor (i = 0 i lt 10 ++i)

printf(dn tab[i])tab[i] = tab[i] - 100

printf(poza tablica dn tab[10])tab[10] = -1return 0

Nie można roacutewnież zapominać że na niektoacuterych architekturach dane wielobajtowe mu-szą być odpowiednio wyroacutewnane w pamięci Np zmienna dwubajtowa może się znajdowaćjedynie pod parzystymi adresami Woacutewczas gdybyśmy chcieli adres zmiennej jednobajto-wej przypisać wskaźnikowi na zmienną dwubajtową mogłoby dojść do nieprzewidzianychbłędoacutew wynikających z proacuteby odczytu niewyroacutewnanej danej

Zaskakującemoże się okazać że roacuteżnewskaźniki mogąmieć roacuteżny rozmiar Np wskaźnikna ar może być większy od wskaźnika na int ale roacutewnież na odwroacutet Co więcej wskaźnikiroacuteżnych typoacutewmogą się roacuteżnić reprezentacją adresoacutew Dla przykładuwskaźnik naar możeprzechowywać adres do bajtu natomiast wskaźnik na int ten adres podzielony przez

Podsumowując roacuteżne wskaźniki to roacuteżne typy i nie należy beztrosko rzutować wyrażeńpomiędzy roacuteżnymi typami wskaźnikowymi bo grozi to nieprzewidywalnymi błędami

1722 Do czego służy typ void

Czasami zdarza się że nie wiemy na jaki typwskazuje danywskaźnik W takich przypadkachstosujemy typ void Sam void nie znaczy nic natomiast void oznacza ldquowskaźnik na obiekt

3Może się okazać że błąd nie będzie widoczny na Twoim komputerze

173 ARYTMETYKA WSKAŹNIKOacuteW 117

w pamięci niewiadomego typurdquo Taki wskaźnik możemy potem odnieść do konkretnego typudanych (w języku C++ wymagana jest do tego operacja rzutowania) Na przykład funkcjamalloc zwraca właśnie wskaźnik za pomocą void

173 Arytmetyka wskaźnikoacutew

W języku C do wskaźnikoacutew można dodawać lub odejmować liczby całkowite Istotne jestjednak że dodanie do wskaźnika liczby nie spowoduje przesunięcia się w pamięci kom-putera o dwa bajty Tak naprawdę przesuniemy się o rozmiar zmiennej Jest to bardzoważna informacja Początkujący programiści popełniają często dużo błędoacutew związanych znieprawidłową arytmetyką wskaźnikoacutew

Zobaczmy na przykład

int ptrint a[] = 1 2 3 5 7ptr = ampa[0]

Rysunek 172 Wskaźnik wskazuje na pierwszą komoacuterkę pamięci

Otrzymujemy następującą sytuacjęGdy wykonamy

ptr += 2

Rysunek 173 Przesunięcie wskaźnika na kolejne komoacuterki

wskaźnik ustawi się na trzecim elemencie tablicyWskaźniki można roacutewnież od siebie odejmować czego wynikiem jest odległość dwoacutech

wskazywanych wartości Odległość zwracana jest jako liczba obiektoacutew danego typu a nieliczba bajtoacutew Np

int a[] = 1 2 3 5 7int ptr = ampa[2]int diff = ptr - a diff ma wartość 2 (a nie 2sizeof(int))

118 ROZDZIAŁ 17 WSKAŹNIKI

Wynikiem może być oczywiście liczba ujemna Operacja jest przydatna do obliczaniawielkości tablicy (długości łańcucha znakoacutew) jeżeli mamy wskaźnik na jej pierwszy i ostatnielement

Operacje arytmetyczne na wskaźnikach mają pewne ograniczenia Przede wszystkim niemożna (tzn standard tego nie definiuje) skonstruować wskaźnika wskazującego gdzieś pozazadeklarowaną tablicę chyba że jest to obiekt zaraz za ostatnim (one past last) np

int a[] = 1 2 3 5 7int ptrptr = a + 10 niezdefiniowane ptr = a - 10 niezdefiniowane ptr = a + 5 zdefiniowane (element za ostatnim) ptr = 10 to już nie

Nie można4 roacutewnież odejmować od siebie wskaźnikoacutew wskazujących na obiekty znajdu-jące się w roacuteżnych tablicach np

int a[] = 1 2 3 b[] = 5 7int ptr1 = a ptr2 = bint diff = a - b niezdefiniowane

174 Tablice a wskaźniki

Trzeba wiedzieć że tablice to też rodzaj zmiennej wskaźnikowej Taki wskaźnik wskazuje namiejsce w pamięci gdzie przechowywany jest jej pierwszy element Następne elementy znaj-dują się bezpośrednio w następnych komoacuterkach pamięci w odstępie zgodnym z wielkościąodpowiedniego typu zmiennej

Na przykład tablica

int tab[] = 100200300

występuje w pamięci w sześciu komoacuterkach5

+--------+--------+--------+--------+--------+--------+|wartosc1| |wartosc2| |wartosc3| |+--------+--------+--------+--------+--------+--------+|00000000|01100100|00000000|11001000|00000001|00101100|+--------+--------+--------+--------+--------+--------+

Stąd do trzeciej wartości można się dostać tak (komoacuterki w tablicy numeruje się od zera)

zmienna = tab[2]

albo wykorzystując metodę wskaźnikową

zmienna = (tab + 2)

4To znaczy standard nie definiuje co się wtedy stanie aczkolwiek na większości architektur odejmowanie do-wolnych dwoacutech wskaźnikoacutew ma zdefiniowane zachowanie Pisząc przenośne programy nie można jednak na tympolegać zwłaszcza że odejmowanie wskaźnikoacutew wskazujących na elementy roacuteżnych tablic zazwyczaj nie ma sensu

5Ponownie przyjmując że bajt ma 8 bitoacutew int dwa bajty i liczby zapisywane są w formacie lile endian

175 GDY ARGUMENT JEST WSKAŹNIKIEM 119

Z definicji obie te metody są roacutewnoważneZ definicji (z wyjątkiem użycia operatora sizeo) wartością zmiennej lub wyrażenia typu tablico-

wego jest wskaźnik na jej pierwszy element (tab == amptab[0])Co więcej można poacutejść w drugą stronę i potraktować wskaźnik jak tablicę

int wskaznikwskaznik = amptab[1] lub wskaznik = tab + 1 zmienna = wskaznik[1] przypisze 300

Jako ciekawostkę podamy iż w języku C można odnosić się do elementoacutew tablicy jeszcze w innysposoacuteb

printf (dn 1[tab])

Skąd ta dziwna notacja Uzasadnienie jest proste

tab[1] = (tab + 1) = (1 + tab) = 1[tab]

Podobną składnię stosuje min asembler GNU

175 Gdy argument jest wskaźnikiem Czasami zdarza się że argumentem (lub argumentami) funkcji są wskaźniki W przypadku ldquonormal-nychrdquo zmiennych nasza funkcja działa tylko na lokalnych kopiach tychże argumentoacutew natomiast niezmienia zmiennych ktoacutere zostały podane jako argument Natomiast w przypadku wskaźnika każdaoperacja na wartości wskazywanej powoduje zmianę wartości zmiennej zewnętrznej Sproacutebujmy roz-patrzeć poniższy przykład

include ltstdiohgt

void func (int zmienna)

zmienna = 5

int main ()

int z=3printf (z=dn z) wypisze 3 func(ampz)printf (z=dn z) wypisze 5

Widzimy że funkcje w języku C nie tylko potrafią zwracać określoną wartość lecz także zmieniaćdane podane im jako argumenty Ten sposoacuteb przekazywania argumentoacutew do funkcji jest nazywanyprzekazywaniem przez wskaźnik (w przeciwieństwie do normalnego przekazywania przez wartość)

Zwroacutećmy uwagę na wywołanie func(ampz) Należy pamiętać by do funkcji przekazać adres zmien-nej a nie samą zmienną Jeśli byśmy napisali func(z) to funkcja starałaby się zmienić komoacuterkę pamięcio numerze Kompilator powinien ostrzec w takim przypadku o konwersji z typu int do wskaźnikaale często kompiluje taki program pozostając na ostrzeżeniu

Nie gra roli czy przy deklaracji funkcji jako argument funkcji podamy wskaźnik czy tablicę (z po-danym rozmiarem lub nie) np poniższe deklaracje są identyczne

120 ROZDZIAŁ 17 WSKAŹNIKI

void func(int ptr[])void func(int ptr)

Można przyjąć konwencję że deklaracja określa czy funkcji przekazujemy wskaźnik do pojedyn-czego argumentu czy do sekwencji ale roacutewnie dobrze można za każdym razem stosować gwiazdkę

176 Pułapki wskaźnikoacutewWażne jest aby przy posługiwaniu się wskaźnikami nigdy nie proacutebować odwoływać się do komoacuterkiwskazywanej przez wskaźnik o wartości lub niezainicjowany wskaźnik Przykładem nieprawi-dłowego kodu może być np

int wskprintf (zawartosc komorki dn (wsk)) Błąd wsk = 0 0 w kontekście wskaźnikoacutew oznacza wskaźnik NULL printf (zawartosc komorki dn (wsk)) Błąd

Należy roacutewnież uważać aby nie odwoływać się do komoacuterek poza przydzieloną pamięcią np

int tab[] = 0 1 2 tab[3] = 3 Błąd

Pamiętaj też że możesz być rozczarowany używając operatora sizeof podając zmienną wskaźni-kową Uzyskana wielkość będzie wielkością wskaźnika a nie wielkością typu użytego podczas deklaro-wania naszego wskaźnika Wielkość ta będzie zawsze miała taki sam rozmiar dla każdego wskaźnikaw zależności od kompilatora a także docelowej platformy Zamiast tego używaj sizeof(wskaźnik)Przykład

char zmiennaint a = sizeof zmienna a wynosi np 4 tj sizeof(char) a = sizeof(char) robimy to samo co wyżej a = sizeof zmienna zmienna a ma teraz przypisany rozmiar

pojedynczego znaku tj 1 a = sizeof(char) robimy to samo co wyżej

177 Na co wskazuje Analizując kody źroacutedłowe programoacutew często można spotkać taki oto zapis

void wskaznik = NULL lub = 0

Wiesz już że nie możemy odwołać się pod komoacuterkę pamięci wskazywaną przez wskaźnik Poco zatem przypisywać wskaźnikowi Odpowiedź może być zaskakująca właśnie po to aby uniknąćbłędoacutew Wydaje się to zabawne ale większość (jeśli nie wszystkie) funkcji ktoacutere zwracają wskaźnikw przypadku błędu zwroacuteci właśnie czyli zero Tutaj rodzi się kolejna wskazoacutewka jeśli w danejzmiennej przechowujemy wskaźnik zwroacutecony wcześniej przez jakąś funkcję zawsze sprawdzajmy czynie jest on roacutewny () Wtedy mamy pewność że funkcja zadziałała poprawnie

Dokładniej nie jest słowem kluczowym lecz stałą (makrem) zadeklarowaną przez dyrektywypreprocesora Deklaracja taka może być albo wartością albo też wartością zrzutowaną na void(((void )0)) ale też jakimś słowem kluczowym deklarowanym przez kompilator

Warto zauważyć że pomimo przypisywania wskaźnikowi zera nie oznacza to że wskaźnik jest reprezentowany przez same zerowe bity Co więcej wskaźniki roacuteżnych typoacutew mogą miećroacuteżną wartość Z tego powodu poniższy kod jest niepoprawny

int tablica_wskaznikow = calloc(100 sizeof tablica_wskaznikow)

178 STAŁE WSKAŹNIKI 121

Zakłada on że w reprezentacji wskaźnika występują same zera Poprawnym zainicjowaniemdynamicznej tablicy wskaźnikoacutew wartościami jest (pomijamy sprawzdanie wartości zwroacuteconejprzez malloc())

int tablica_wskaznikow = malloc(100 sizeof tablica_wskaznikow)int i = 0while (ilt100)

tablica_wskaznikow[i++] = 0

178 Stałe wskaźnikiTak jak istnieją zwykłe stałe tak samo możemy mieć stałe wskaźniki mdash jednak są ich dwa rodzajeWskaźniki na stałą wartość

const int a lub roacutewnoważnie int const a

oraz stałe wskaźniki

int const b

Pierwszy to wskaźnik ktoacuterym nie można zmienić wskazywanej wartości Drugi to wskaźnik ktoacute-rego nie można przestawić na inny adres Dodatkowo można zadeklarować stały wskaźnik ktoacuterym niemożna zmienić wartości wskazywanej zmiennej i roacutewnież można zrobić to na dwa sposoby

const int const c alternatywnie int const const c

int i=0const int a=ampiint const b=ampiint const const c=ampia = 1 kompilator zaprotestuje b = 2 ok c = 3 kompilator zaprotestuje a = b ok b = a kompilator zaprotestuje c = a kompilator zaprotestuje

Wskaźniki na stałą wartość są przydatne między innymi w sytuacji gdy mamy duży obiekt (naprzykład strukturę z kilkoma polami) Jeśli przypiszemy taką zmienną do innej zmiennej kopiowaniemoże potrwać dużo czasu a oproacutecz tego zostanie zajęte dużo pamięci Przekazanie takiej struktury dofunkcji albo zwroacutecenie jej jako wartość funkcji wiąże się z takim samym narzutem W takim wypadkudobrze jest użyć wskaźnika na stałą wartość

void funkcja(const duza_struktura ds)

czytamy z ds i wykonujemy obliczenia

funkcja(ampdane) mamy pewność że zmienna dane nie zostanie zmieniona

122 ROZDZIAŁ 17 WSKAŹNIKI

179 Dynamiczna alokacja pamięciMając styczność z tablicami można się zastanowić czy nie dałoby się mieć tablic ktoacuterych rozmiardostosowuje się do naszych potrzeb a nie jest na stałe zaszyty w kodzie programu Chcąc pomieścićwięcej danych możemy po prostu zwiększyć rozmiar tablicy mdash ale gdy do przechowania będzie mniejelementoacutew okaże się że marnujemy pamięć Język C umożliwia dzięki wskaźnikom i dynamicznejalokacji pamięci tworzenie tablic takiej wielkości jakiej akurat potrzebujemy

1791 O co odziCzym jest dynamiczna alokacja pamięci Normalnie zmienne programu przechowywane są na tzwstosie (ang sta) mdash powstają gdy program wchodzi do bloku w ktoacuterym zmienne są zadeklarowane azwalniane w momencie kiedy program opuszcza ten blok Jeśli deklarujemy tak tablice to ich rozmiarmusi być znanywmomencie kompilacji mdash żeby kompilator wygenerował kod rezerwujący odpowiedniąilość pamięci Dostępny jest jednak drugi rodzaj rezerwacji (czyli alokacji) pamięci Jest to alokacja nastercie (ang heap) Sterta to obszar pamięci wspoacutelny dla całego programu przechowywane są w nimzmienne ktoacuterych czas życia nie jest związany z poszczegoacutelnymi blokami Musimy sami rezerwować dlanich miejsce i to miejsce zwalniać ale dzięki temu możemy to zrobić w dowolnym momencie działaniaprogramu

Należy pamiętać że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analo-giczne działania na stosie Dodatkowo zmienna zajmuje na stercie więcej miejsca niż na stosie mdash stertautrzymuje specjalną strukturę w ktoacuterej trzymane są wolne partie (może to być np lista) Tak więcużywajmy dynamicznej alokacji tam gdzie jest potrzebna mdash dla danych ktoacuterych rozmiaru nie jesteśmyw stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem w ktoacuterymzostały zaalokowane

1792 Obsługa pamięciPodstawową funkcją do rezerwacji pamięci jest funkcja malloc Jest to niezbyt skomplikowana funkcjamdash podając jej rozmiar (w bajtach) potrzebnej pamięci dostajemy wskaźnik do zaalokowanego obszaru

Załoacuteżmy że chcemy stworzyć tablicę liczb typu float

int rozmiarfloat tablica

rozmiar = 3tablica = (float) malloc(rozmiar sizeof tablica)tablica[0] = 01

Przeanalizujmy teraz po kolei co dzieje się w powyższym fragmencie Najpierw deklarujemyzmiennemdash rozmiar tablicy i wskaźnik ktoacutery będzie wskazywał obszarw pamięci gdzie będzie trzymanatablica Do zmiennej rozmiar możemy w trakcie działania programu przypisać cokolwiek mdash wczytaćją z pliku z klawiatury obliczyć wylosować mdash nie jest to istotne rozmiar sizeof tablica obliczapotrzebną wielkość tablicy Dla każdej zmiennej float potrzebujemy tyle bajtoacutew ile zajmuje ten typdanych Ponieważ może się to roacuteżnić na rozmaitych maszynach istnieje operator sizeof zwracającydla danego wyrażenia rozmiar jego typu w bajtach

W wielu książkach (roacutewnież KampRv) i w Internecie stosuje się inny schemat użycia funkcji malloca mianowicie tablica = (float)malloc(rozmiar sizeof(float)) Takie użycie należy traktowaćjako błędne gdyż nie sprzyja ono poprawnemu wykrywaniu błędoacutew

Rozważmy sytuację gdy programista zapomni dodać plik nagłoacutewkowy stdlibh woacutewczas kompila-tor (z braku deklaracji funkcji malloc) przyjmie że zwraca ona typ int zatem do zmiennej tablica (ktoacuterajest wskaźnikiem) będzie przypisywana liczba całkowita co od razu spowoduje błąd kompilacji (a przy-najmniej ostrzeżenie) dzięki czemu będzie można szybko poprawić kod programu Rzutowanie jestkonieczne tylko w języku C++ gdzie konwersja z void na inne typy wskaźnikowe nie jest domyślnaale język ten oferuje nowe sposoby alokacji pamięci

179 DYNAMICZNA ALOKACJA PAMIĘCI 123

Teraz rozważmy sytuację gdy zdecydujemy się zwiększyć dokładność obliczeń i zamiast typu floatużyć typu double Będziemy musieli wyszukać wszystkie wywołania funkcji malloc calloc i reallocodnoszące się do naszej tablicy i zmieniać wszędzie sizeof(float) na sizeof(double) Aby temu zapo-biec lepiej od razu użyć sizeof tablica (lub jeśli ktoś woli z nawiasami sizeof(tablica)) woacutewczaszmiana typu zmiennej tablica na double zostanie od razu uwzględniona przy alokacji pamięci

Dodatkowo należy sprawdzić czy funkcja malloc nie zwroacuteciła wartości mdash dzieje się tak gdyzabrakło pamięci Ale uwaga może się tak stać roacutewnież jeżeli jako argument funkcji podano zero

Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić aby sys-tem operacyjny moacutegł go przydzielić innym potrzebującym procesom Do zwolnienia obszaru pamięciużywamy funkcji free() ktoacutera przyjmuje tylko jeden argument mdash wskaźnik ktoacutery otrzymaliśmy wwyniku działania funkcji malloc()

free (addr)

Należy pamiętać o zwalnianiu pamięci mdash inaczej dojdzie do tzw wycieku pamięci mdash program będzierezerwował nową pamięć ale nie zwracał jej z powrotem i w końcu pamięci może mu zabraknąć

Należy też uważać by nie zwalniać dwa razy tego samegomiejsca Po wywołaniu free wskaźnik niezmienia wartości pamięć wskazywana przez niego może też nie od razu ulec zmianie Czasemmożemywięc korzystać ze wskaźnika (zwłaszcza czytać) po wywołaniu free nie orientując się że robimy coś źlemdash i w pewnym momencie dostać komunikat o nieprawidłowym dostępie do pamięci Z tego powoduzaraz po wywołaniu funkcji free można przypisać wskaźnikowi wartość

Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci Tu z pomocąprzychodzi funkcja realloc

tablica = realloc(tablica 2rozmiarsizeof tablica)

Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub gdy zabrakło pa-mięci) Uwaga mdash może to być inny wskaźnik Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanymaktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca funkcja znajdzie nowe miejscei przekopiuje tam starą zawartość Jak widać wywołanie tej funkcji może być więc kosztowne podwzględem czasu

Ostatnią funkcją jest funkcja calloc() Przyjmuje ona dwa argumenty liczbę elementoacutew tablicyoraz wielkość pojedynczego elementu Podstawową roacuteżnicą pomiędzy funkcjami malloc() i calloc() jestto że ta druga zeruje wartość przydzielonej pamięci (do wszystkich bajtoacutew wpisuje wartość )

1793 Tablice wielowymiarowe

Rysunek 174 tablica dwuwymiarowa mdash w rzeczywistości tablica ze wskaźnikami do tablic

W rozdziale Tablice pokazaliśmy jak tworzyć tablice wielowymiarowe gdy ich rozmiar jest znanyw czasie kompilacji Teraz zaprezentujemy jak to wykonać za pomocą wskaźnikoacutew i to w sytuacji gdyrozmiar może się zmieniać Załoacuteżmy że chcemy stworzyć tabliczkę mnożenia

124 ROZDZIAŁ 17 WSKAŹNIKI

int rozmiarint iint tabliczka

printf(Podaj rozmiar tabliczki mnozenia )scanf(i amprozmiar) dla prostoty nie będziemy sprawdzali

czy użytkownik wpisał sensowną wartość

tabliczka = malloc(rozmiar sizeof tabliczka) 1 for (i = 0 iltrozmiar ++i) 2

tabliczka[i] = malloc(rozmiar sizeof tabliczka) 3 4

for (i = 0 iltrozmiar ++i) int jfor (j = 0 jltrozmiar ++j)

tabliczka[i][j] = (i+1)(j+1)

Najpierw musimy przydzielić pamięć mdash najpierw dla ldquotablicy tablicrdquo () a potem dla każdej z pod-tablic osobno (-) Ponieważ tablica jest typu int to nasza tablica tablic będzie wskaźnikiem na intczyli int Podobnie osobno ale w odwrotnej kolejności będziemy zwalniać tablicę wielowymiarową

for (i = 0 iltrozmiar ++i) free(tabliczka[i])

free(tabliczka)

Należy nie pomylić kolejności po wykonaniu free(tabliczka) nie będziemy mieli prawa odwoły-wać się do tabliczka[i] (bo wcześniej dokonaliśmy zwolnienia tego obszaru pamięci)

Można także zastosować bardziej oszczędny sposoacuteb alokowania tablicy wielowymiarowej a mia-nowicie

define ROZMIAR 10int iint tabliczka = malloc(ROZMIAR sizeof tabliczka)tabliczka = malloc(ROZMIAR ROZMIAR sizeof tabliczka)for (i = 1 iltROZMIAR ++i)

tabliczka[i] = tabliczka[0] + (i ROZMIAR)

for (i = 0 iltROZMIAR ++i) int jfor (j = 0 jltROZMIAR ++j)

tabliczka[i][j] = (i+1)(j+1)

free(tabliczka)free(tabliczka)

Powyższy kod działa w ten sposoacuteb że zamiast dla poszczegoacutelnych wierszy alokować osobno pamięćalokuje pamięć dla wszystkich elementoacutew tablicy i dopiero poacuteźniej przypisuje wskazania poszczegoacutel-nych wskaźnikoacutew-wierszy na kolejne bloki po elementoacutew

1710 WSKAŹNIKI NA FUNKCJE 125

Sposoacuteb ten jest bardziej oszczędny z dwoacutech powodoacutew Po pierwsze wykonywanych jest mniej ope-racji przydzielania pamięci (bo tylko dwie) Po drugie za każdym razem gdy alokuje się pamięć trochęmiejsca się marnuje gdyż funkcja malloc musi w stogu przechowywać roacuteżne dodatkowe informacje natemat każdej zaalokowanej przestrzeni Ponadto czasami alokacja odbywa się blokami i gdy zażąda sięniepełny blok to reszta bloku jest tracona

Zauważmy że w ten sposoacuteb możemy uzyskać nie tylko normalną ldquokwadratowąrdquo tablicę (dla dwoacutechwymiaroacutew) Możliwe jest np uzyskanie tablicy troacutejkątnej

0123012010

lub tablicy o dowolnym innym rozkładzie długości wierszy np

const size_t wymiary[] = 2 4 6 8 1 3 5 7 9 int iint tablica = malloc((sizeof wymiary sizeof wymiary) sizeof tablica)for (i = 0 ilt10 ++i)

tablica[i] = malloc(wymiary[i] sizeof tablica)

Gdy nabierzesz wprawy w używaniu wskaźnikoacutew oraz innych funkcji malloc i realloc nauczyszsię wykonywać roacuteżne inne operacje takie jak dodawanie kolejnych wierszy usuwanie wierszy zmianarozmiaru wierszy zamiana wierszy miejscami itp

1710 Wskaźniki na funkcjeDotychczas zajmowaliśmy się sytuacją gdy wskaźnik wskazywał na jakąś zmienną Jednak nie tylkozmienna ma swoacutej adres w pamięci Oproacutecz zmiennej także i funkcja musi mieć swoje określone miejscew pamięci A ponieważ funkcja ma swoacutej adres6 to nie ma przeszkoacuted aby i na nią wskazywał jakiśwskaźnik

17101 Deklaracja wskaźnika na funkcjęTak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresufunkcji Wskaźnik na funkcję roacuteżni się od innych rodzajoacutew wskaźnikoacutew Jedną z głoacutewnych roacuteżnic jestjego deklaracja Zwykle wygląda ona tak

typ_zwracanej_wartości (nazwa_wskaźnika)(typ1 parametr1 typ2 parametr2)

Oczywiście parametroacutew może być więcej (albo też w ogoacutele może ich nie być) Oto przykład wyko-rzystania wskaźnika na funkcję

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

6Tak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresu funkcji

126 ROZDZIAŁ 17 WSKAŹNIKI

int (wsk_suma)(int a int b)wsk_suma = sumaprintf(4+5=dn wsk_suma(45))return 0

Zwroacutećmy uwagę na dwie rzeczy

przypisując nazwę funkcji bez nawiasoacutew do wskaźnika automatycznie informujemy kompilatorże chodzi nam o adres funkcji

wskaźnika używamy tak jak normalnej funkcji na ktoacuterą on wskazuje

17102 Do czego można użyć wskaźnikoacutew na funkcjeJęzyk C jest językiem strukturalnym jednak dzięki wskaźnikom istnieje w nim możliwość ldquozaszczepie-niardquo pewnych obiektowych właściwości Wskaźnik na funkcję może być np elementem struktury mdashwtedy mamy bardzo prymitywną namiastkę klasy ktoacuterą dobrze znają programiści piszący w językuC++ Ponadto dzięki wskaźnikom możemy tworzyć mechanizmy działające na zasadzie funkcji zwrot-nej7 Dobrym przykładem może być np tworzenie sterownikoacutew gdzie musimy poinformować roacuteżnepodsystemy jakie funkcje w naszym kodzie służą do wykonywania określonych czynności Przykład

struct urzadzenie int (otworz)(void)void (zamknij)(void)

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

int rejestruj_urzadzenie(struct urzadzenie u) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzenieotworz = moje_urzadzenie_otworzmoje_urzadzeniezamknij = moje_urzadzenie_zamknijrejestruj_urzadzenie(ampmoje_urzadzenie)

Wten sposoacutebwpamięci każda klasamusi przechowywaćwszystkiewskaźniki dowszystkichmetodInnym rozwiązaniem może być stworzenie statycznej struktury ze wskaźnikami do funkcji i woacutewczasw strukturze będzie przechowywany jedynie wskaźnik do tej struktury np

struct urzadzenie_metody

7Funkcje zwrotne znalazły zastosowanie głoacutewnie w programowaniu

1711 MOŻLIWE DEKLARACJE WSKAŹNIKOacuteW 127

int (otworz)(void)void (zamknij)(void)

struct urzadzenie const struct urzadzenie_metody m

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

static const struct urzadzenie_metodymoje_urzadzenie_metody = moje_urzadzenie_otworzmoje_urzadzenie_zamknij

int rejestruj_urzadzenie(struct urzadzenie ampu) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzeniem = ampmoje_urzadzenie_metodyrejestruj_urzadzenie(ampmoje_urzadzenie)

1711 Możliwe deklaracje wskaźnikoacutew

Tutaj znajduje się kroacutetkie kompendium jak definiować wskaźniki oraz co oznaczają poszczegoacutelne defi-nicje

1712 Popularne błędy

Jednym z najczęstszych błędoacutew oproacutecz proacuteb wykonania operacji na wskaźniku są odwołania siędo obszaru pamięci po jego zwolnieniu Po wykonaniu funkcji free() nie możemy już wykonywaćżadnych odwołań do zwolnionego obszaru Innym rodzajem błędoacutew są

odwołania do adresoacutew pamięci ktoacutere są poza obszarem przydzielonym funkcją malloc()

brak sprawdzania czy dany wskaźnik nie ma wartości

wycieki pamięci czyli niezwalnianie całej przydzielonej wcześniej pamięci

128 ROZDZIAŁ 17 WSKAŹNIKI

i zmienna całkowita (typu int) ip wskaźnik p wskazujący na zmienną całkowitąa[] tablica a liczb całkowitych typu intf() funkcja f zwracająca liczbę całkowitą typu intpp wskaźnik pp na wskaźnik wskazujący na liczbę całkowitą typu int

(pa)[] wskaźnik pa wskazujący na tablicę liczb całkowitych typu int(pf)() wskaźnik pf na funkcję zwracającą liczbę całkowitą typu intap[] tablica ap wskaźnikoacutew na liczby całkowite typu intfp() funkcja fp ktoacutera zwraca wskaźnik na zmienną typu intppp wskaźnik ppp wskazujący na wskaźnik wskazujący na wskaźnik wskazu-

jący na liczbę typu int(ppa)[] wskaźnik ppa na wskaźnik wskazujący na tablicę liczb całkowitych typu

int(ppf)() wskaźnik ppf wskazujący na wskaźnik funkcji zwracającej dane typu int(pap)[] wskaźnik pap wskazujący na tablicę wskaźnikoacutew na typ int(pfp)() wskaźnik pfp na funkcję zwracającą wskaźnik na typ intapp[] tablica wskaźnikoacutew app wskazujących na typ int

(apa[])[] tablica wskaźnikoacutew apa wskazujących wskaźniki na typ int(apf[])() tablica wskaźnikoacutew apf na funkcję ktoacutere zwracają wskaźniki na typ intfpp() funkcja fpp ktoacutera zwraca wskaźnik na wskaźnik na wskaźnik ktoacutery wska-

zuje typ int(fpa())[] funkcja fpa ktoacutera zwraca wskaźnik na tablicę liczb typu int(fpf())() funkcja fpf ktoacutera zwraca wskaźnik na funkcję ktoacutera zwraca dane typu int

1713 Ciekawostki w rozdziale Zmienne pisaliśmy o stałych Normalnie nie mamy możliwości zmiany ich wartości

ale z użyciem wskaźnikoacutew staje się to możliwe

const int CONST=0int c=ampCONSTc = 1printf(inCONST) wypisuje 1

Konstrukcja taka może jednak wywołać ostrzeżenie kompilatora bądź nawet jego błąd mdash wtedymoże pomoacutec jawne rzutowanie z const int na int

język C++ oferuje mechanizm podobny do wskaźnikoacutew ale nieco wygodniejszy ndash referencje

język C++ dostarcza też innego sposobu dynamicznej alokacji i zwalniania pamięci mdash przez ope-ratory new i delete

w rozdziale Typy złożone znajduje się opis implementacji listy za pomocą wskaźnikoacutew Przy-kład ten może być bardzo przydatny przy zrozumieniu po co istnieją wskaźniki jak się nimiposługiwać oraz jak dobrze zarządzać pamięcią

Rozdział 18

Napisy

W dzisiejszych czasach komputer przestał być narzędziem tylko i wyłącznie do przetwarzania danychOd programoacutew komputerowych zaczęto wymagać czegoś nowego mdash program w wyniku swojego dzia-łania nie ma zwracać danych rozumianych tylko przez autora programu lecz powinien być na tylekomunikatywny aby przeciętny użytkownik komputera moacutegł bez problemu tenże komputer obsłużyćDo przechowywania tychże komunikatoacutew służą tzw ldquołańcuchyrdquo (ang string) czyli ciągi znakoacutew

Język C nie jest wygodnym narzędziem do manipulacji napisami Jak się wkroacutetce przekonamyzestaw funkcji umożliwiających operacje na napisach w bibliotece standardowej C jest raczej skromnyDodatkowo problemem jest sposoacuteb w jaki łańcuchy przechowywane są w pamięci

Napisy w języku Cmogą być przyczyną wielu trudnych do wykrycia błędoacuteww programach Wartodobrze zrozumieć jak należy operować na łańcuchach znakoacutew i zachować szczegoacutelną ostrożność w tychmiejscach gdzie napisoacutew używamy

181 Łańcuy znakoacutew w języku CNapis jest zapisywany w kodzie programu jako ciąg znakoacutew zawarty pomiędzy dwoma cudzysłowami

printf (Napis w języku C)

Wpamięci taki łańcuch jest następującympo sobie ciągiem znakoacutew (char) ktoacutery kończy się znakiemldquonullrdquo (czyli po prostu liczbą zero) zapisywanym jako rsquorsquo

Jeśli mamy napis do poszczegoacutelnych znakoacutew odwołujemy się jak w tablicy

char tekst = Jakiś tam tekstprintf(cn przykład[0]) wypisze p - znaki w napisach są numerowane od zera printf(cn tekst[2]) wypisze k

Ponieważ napis w pamięci kończy się zerem umieszczonym tuż za jego zawartością odwołanie się doznaku o indeksie roacutewnym długości napisu zwroacuteci zero

printf(d test[4]) wypisze 0

Napisy możemywczytywać z klawiatury i wypisywać na ekran przy pomocy dobrze znanych funk-cji scanf printf i pokrewnych Formatem używanym dla napisoacutew jest s

printf(s tekst)

129

130 ROZDZIAŁ 18 NAPISY

Większość funkcji działających na napisach znajduje się w pliku nagłoacutewkowym stringhJeśli łańcuch jest zbyt długi można zapisać gow kilku linijkach ale wtedy przechodząc do następnej

linii musimy na końcu postawić znak ldquordquo

printf(Ten napis zajmuje więcej niż jedną linię)

Instrukcja taka wydrukuje

Ten napis zajmuje więcej niż jedną linię

Możemy zauważyć że napis ktoacutery w programie zajął więcej niż jedną linię na ekranie zajął tylkojedną Jest tak ponieważ ldquordquo informuje kompilator że łańcuch będzie kontynuowany w następnej liniikodumdash niemawpływu na prezentację łańcucha Abywydrukować napis w kilku liniach należywstawićdo niego n (ldquonrdquo pochodzi tu od ldquonew linerdquo czyli ldquonowa liniardquo)

printf(Ten napisnna ekranienzajmie więcej niż jedną linię)

W wyniku otrzymamy

Ten napisna ekraniezajmie więcej niż jedną linię

1811 Jak komputer przeowuje w pamięci łańcu

Rysunek 181 Napis ldquoMerkkijonordquo przechowywany w pamięci

Zmienna ktoacutera przechowuje łańcuch znakoacutew jest tak naprawdę wskaźnikiem do ciągu znakoacutew(bajtoacutew) w pamięci Możemy też myśleć o napisie jako o tablicy znakoacutew (jak wyjaśnialiśmy wcześniejtablice to też wskaźniki)

Możemy wygodnie zadeklarować napis

char tekst = Jakiś tam tekst Umieszcza napis w obszarze danych programu i przypisuje adres

char tekst[] = Jakiś tam tekst Umieszcza napis w tablicy char tekst[] = Jakis tam tekst0

Tekst to taka tablica jak każda inna

Kompilator automatycznie przydziela wtedy odpowiednią ilość pamięci (tyle bajtoacutew ile jest literplus jeden dla kończącego nulla) Jeśli natomiast wiemy że dany łańcuch powinien przechowywaćokreśloną ilość znakoacutew (nawet jeśli w deklaracji tego łańcucha podajemy mniej znakoacutew) deklarujemygo w taki sam sposoacuteb jak tablicę jednowymiarową

char tekst[80] = Ten tekst musi być kroacutetszy niż 80 znakoacutew

Należy cały czas pamiętać że napis jest tak naprawdę tablicą Jeśli zarezerwowaliśmy dla napisu znakoacutew to przypisanie do niego dłuższego napisu spowoduje pisanie po pamięci

Uwaga Deklaracja char tekst = cokolwiek oraz char tekst = cokolwiek pomimo że wyglądająbardzo podobnie bardzo się od siebie roacuteżnią W przypadku pierwszej deklaracji proacuteba zmodyfikowania

181 ŁAŃCUCHY ZNAKOacuteW W JĘZYKU C 131

napisu (np tekst[0] = C) może mieć nieprzyjemne skutki Dzieje się tak dlatego że char tekst =cokolwiek deklaruje wskaźnik na stały obszar pamięci1

Pisanie po pamięci może czasami skończyć się błędem dostępu do pamięci (ldquosegmentation faultrdquow systemach ) i zamknięciem programu jednak może zdarzyć się jeszcze gorsza ewentualność mdashmożemy zmienić w ten sposoacuteb przypadkowo wartość innych zmiennych Program zacznie wtedy za-chowywać się nieprzewidywalnie mdash zmienne a nawet stałe co do ktoacuterych zakładaliśmy że ich wartośćbędzie ściśle ustalona mogą przyjąć taką wartość jaka absolutnie nie powinna mieć miejsca Wartowięc stosować zabezpieczenia typu makra assert

Kluczowy jest też kończący napis znak null W zasadzie wszystkie funkcje operujące na napisachopierają właśnie na nim Na przykład strlen szuka rozmiaru napisu idąc od początku i zliczając znaki ażnie natrafi na znak o kodzie zero Jeśli nasz napis nie kończy się znakiem null funkcja będzie szła dalejpo pamięci Na szczęście wszystkie operacje podstawienia typu tekst = ldquoTekstrdquo powodują zakończenienapisu nullem (o ile jest na niego miejsce) 2

1812 Znaki specjalneJak zapewne zauważyłeś w poprzednim przykładzie w łańcuchu ostatnim znakiem jest znak o wartościzero (rsquorsquo) Jednak łańcuchy mogą zawierać inne znaki specjalne(sekwencje sterujące) np

rsquoarsquo - alarm (sygnał akustyczny terminala)

rsquobrsquo - backspace (usuwa poprzedzający znak)

rsquorsquo - wysuniecie strony (np w drukarce)

rsquorrsquo - powroacutet kursora (karetki) do początku wiersza

rsquonrsquo - znak nowego wiersza

rsquordquo - cudzysłoacutew

rsquordquo - apostrof rsquorsquo - ukośnik wsteczny (backslash)

rsquotrsquo - tabulacja pozioma

rsquovrsquo - tabulacja pionowa

rsquorsquo - znak zapytania (pytajnik)

rsquoooorsquo - liczba zapisana w systemie oktalnym (oacutesemkowym) gdzie rsquoooorsquo należy zastąpić trzycy-frową liczbą w tym systemie

rsquoxhhrsquo - liczba zapisana w systemie heksadecymalnym (szesnastkowym) gdzie rsquohhrsquo należy za-stąpić dwucyfrową liczbą w tym systemie

rsquounnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnrsquo należy zastąpić czterocyfrowym identyfika-torem znaku w systemie szesnatkowym rsquonnnnrsquo odpowiada dłuższej formie w postaci rsquonnnnrsquo

rsquounnnnnnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnnnnnrsquo należy zastąpić ośmiocyfrowymidentyfikatorem znaku w systemie szesnatkowym

Warto zaznaczyć że znak nowej linii (rsquonrsquo) jest w roacuteżny sposoacuteb przechowywany w roacuteżnych sys-temach operacyjnych Wiąże się to z pewnymi historycznymi uwarunkowaniami W niektoacuterych sys-temach używa się do tego jednego znaku o kodzie xA (Line Feed mdash nowa linia) Do tej rodzinyzaliczamy systemy z rodziny Unix Linux BSD Mac OS X inne Drugą konwencją jest zapisywaniersquonrsquo za pomocą dwoacutech znakoacutew LF (Line Feed) + CR (Carriage return mdash powroacutet karetki) Znak CRreprezentowany jest przez wartość xD Kombinacji tych dwoacutech znakoacutew używają min CPM DOSOS Microso Windows Trzecia grupa systemoacutew używa do tego celu samego znaku CR Są to sys-temy działające na komputerach Commodore Apple II oraz Mac OS do wersji W związku z tym plikutworzony w systemie Linux może wyglądać dziwnie pod systemem Windows

1Można się zatem zastanawiać czemu kompilator dopuszcza przypisanie do zwykłego wskaźnika wskazania nastały obszar skoro kod const int foo int bar = foo generuje ostrzeżenie lub wręcz się nie kompiluje Jest topewna zaszłość historyczna wynikająca z faktu że słoacutewko const zostało wprowadzone do języka gdy już był on wpowszechnym użyciu

2Nie należy mylić znaku null (czyli znaku o kodzie zero) ze wskaźnikiem null (czy też )

132 ROZDZIAŁ 18 NAPISY

182 Operacje na łańcua

1821 Poroacutewnywanie łańcuoacutewNapisy to tak naprawdęwskaźniki Tak więc używając zwykłego operatora poroacutewnania == otrzymamywynik poroacutewnania adresoacutew a nie tekstoacutew

Do poroacutewnywania dwoacutech ciągoacutew znakoacutew należy użyć funkcji strcmp zadeklarowanej w pliku na-głoacutewkowym stringh Jako argument przyjmuje ona dwa napisy i zwraca wartość ujemną jeżeli napispierwszy jestmniejszy od drugiego jeżeli napisy są roacutewne lub wartość dodatnią jeżeli napis pierwszyjest większy od drugiego Ciągi znakoacutew poroacutewnywalne są leksykalnie kody znakoacutew czyli np (przyj-mując kodowanie ASCII) a jest mniejsze od b ale jest większe od B Np

include ltstdiohgtinclude ltstringhgt

int main(void) char str1[100] str2[100]int cmp

puts(Podaj dwa ciagi znakow )fgets(str1 sizeof str1 stdin)fgets(str2 sizeof str2 stdin)

cmp = strcmp(str1 str2)if (cmplt0)

puts(Pierwszy napis jest mniejszy) else if (cmpgt0)

puts(Pierwszy napis jest wiekszy) else

puts(Napisy sa takie same)

return 0

Czasami możemy chcieć poroacutewnać tylko fragment napisu np sprawdzić czy zaczyna się od jakie-goś ciągu W takich sytuacjach pomocna jest funkcja strncmp W poroacutewnaniu do strcmp() przyjmujeona jeszcze jeden argument oznaczający maksymalną liczbę znakoacutew do poroacutewnania

include ltstdiohgtinclude ltstringhgt

int main(void) char str[100]int cmp

fputs(Podaj ciag znakow stdout)fgets(str sizeof str stdin)

if (strncmp(str foo 3)) puts(Podany ciag zaczyna sie od foo)

return 0

182 OPERACJE NA ŁAŃCUCHACH 133

1822 Kopiowanie napisoacutewDo kopiowania ciągoacutew znakoacutew służy funkcja strcpy ktoacutera kopiuje drugi napis w miejsce pierwszegoMusimy pamiętać by w pierwszym łańcuchu było wystarczająco dużo miejsca

char napis[100]strcpy(napis Ala ma kota)

Znacznie bezpieczniej jest używać funkcji strncpy ktoacutera kopiuje co najwyżej tyle bajtoacutew ile podanojako trzeci parametr Uwaga Jeżeli drugi napis jest za długi funkcja nie kopiuje znaku null na koniecpierwszego napisu dlatego zawsze trzeba to robić ręcznie

char napis[100]strncpy(napis Ala ma kota sizeof napis - 1)napis[sizeof napis - 1] = 0

1823 Łączenie napisoacutewDo łączenia napisoacutew służy funkcja strcat ktoacutera kopiuje drugi napis do pierwszego Ponownie jak wprzypadku strcpymusimy zagwarantować by w pierwszym łańcuchu było wystarczająco dużo miejsca

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrcat(napis1 napis2)puts(napis1)return 0

I ponownie jak w przypadku strcpy istnieje funkcja strncat ktoacutera skopiuje co najwyżej tyle bajtoacutewile podano jako trzeci argument i dodatkowo dopisze znak null Przykładowo powyższy kod bezpieczniejzapisać jako

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrncat(napis1 napis2 sizeof napis1 - 1)puts(napis1)return 0

Osoby ktoacutere programowały w językach skryptowych muszą bardzo uważać na łączenie i kopiowa-nie napisoacutew Kompilator języka C nie wykryje nadpisania pamięci za zmienną łańcuchową i nie przy-dzieli dodatkowego obszaru pamięci Może się zdarzyć że program pomimo nadpisywania pamięci załańcuchem będzie nadal działał co bardzo utrudni wykrywanie tego typu błędoacutew

134 ROZDZIAŁ 18 NAPISY

183 Bezpieczeństwo kodu a łańcuy

1831 Przepełnienie buforaO co właściwie chodzi z tymi funkcjami strncpy i strncat Otoacuteż niewinnie wyglądające łańcuchy mogąokazać się zaboacutejcze dla bezpieczeństwa programu a przez to nawet dla systemu w ktoacuterym ten programdziała Może brzmi to strasznie lecz jest to prawda Może pojawić się tutaj pytanie ldquow jaki sposoacutebłańcuch może zaszkodzić programowirdquo Otoacuteż może i to całkiem łatwo Przeanalizujmy następującykod

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strcpy(haslo argv[1]) tutaj następuje przepełnienie bufora if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Jest to bardzo prosty program ktoacutery wykonuje jakąś akcję jeżeli podane jako pierwszy argumenthasło jest poprawne Sprawdźmy czy działa

$ aout niepoprawnePodales bledne haslo$ aout poprawneWitaj wprowadziles poprawne haslo

Jednak okazuje się że z powodu użycia funkcji strcpy włamywacz nie musi znać hasła aby programuznał że zna hasło np

$ aout 11111111111111111111111111111111Witaj wprowadziles poprawne haslo

Co się stało Podaliśmy ciąg jedynek dłuższy niż miejsce przewidziane na hasło Funkcja strcpy()kopiując znaki z argv[1] do tablicy (bufora) haslo przekroczyła przewidziane dla niego miejsce i szładalej mdash gdzie znajdowała się zmienna haslo poprawne strcpy() kopiowała znaki już tam gdzie znajdo-wały się inne dane mdash między innymi wpisała jedynkę do haslo poprawne

Podany przykład może się roacuteżnie zachowywać w zależności od kompilatora jakim został skompi-lowany i systemu na jakim działa ale ogoacutelnie mamy do czynienia z poważnym niebezpieczeństwem

183 BEZPIECZEŃSTWO KODU A ŁAŃCUCHY 135

Taką sytuację nazywamy przepełnieniem bufora Może umożliwić dostęp do komputera osobomnieuprzywilejowanym Należy wystrzegać się tego typu konstrukcji a wmiejsce niebezpiecznej funkcjistrcpy stosować bardziej bezpieczną strncpy

Oto bezpieczna wersja poprzedniego programu

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strncpy(haslo argv[1] sizeof haslo - 1)haslo[sizeof haslo - 1] = 0if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Bezpiecznymi alternatywami do strcpy i strcat są też funkcje strlcpy oraz strlcat opracowane przezprojekt OpenBSD i dostępne do ściągnięcia na wolnej licencji strlcpy strlcat strlcpy() działa podobniedo strncpy strlcpy (buf argv[1] sizeof buf) jednak jest szybsza (nie wypełnia pustego miejscazerami) i zawsze kończy napis nullem (czego nie gwarantuje strncpy) strlcat(dst src size) działanatomiast jak strncat(dst src size-1)

Do innych niebezpiecznych funkcji należy np gets zamiast ktoacuterej należy używać fgetsZawsze możemy też alokować napisy dynamicznie

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

136 ROZDZIAŁ 18 NAPISY

haslo = malloc(strlen(argv[1]) + 1) +1 dla znaku null if (haslo)

fputs(Za malo pamiecin stderr)return EXIT_FAILURE

strcpy(haslo argv[1])if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)free(haslo)return EXIT_SUCCESS

1832 Nadużycia z udziałem ciągoacutew formatującyJednak to nie koniec kłopotoacutew z napisami Wielu programistoacutew nieświadomych zagrożenia częstoużywa tego typu konstrukcji

include ltstdiohgtint main (int argc char argv[])

printf (argv[1])

Z punktu widzenia bezpieczeństwa jest to bardzo poważny błąd programu ktoacutery może nieść zesobą katastrofalne skutki Prawidłowo napisany kod powinien wyglądać następująco

include ltstdiohgtint main (int argc char argv[])

printf (s argv[1])

lub

include ltstdiohgtint main (int argc char argv[])

fputs (argv[1] stdout)

Źroacutedło problemu leży w konstrukcji funkcji printf Przyjmuje ona bowiem za pierwszy parametrłańcuch ktoacutery następnie przetwarza Jeśli w pierwszym parametrze wstawimy jakąś zmienną to funk-cja printf potraktuje ją jako ciąg znakoacutew razem ze znakami formatującymi Zatem ważne aby wcześniewyrobić sobie nawyk stosowania funkcji printf z co najmniej dwoma parametrami nawet w przypadkuwyświetlenia samego tekstu

184 KONWERSJE 137

184 KonwersjeCzasami zdarza się że łańcuch można interpretować nie tylko jako ciąg znakoacutew lecz np jako liczbęJednak aby dało się taką liczbę przetworzyć musimy skopiować ją do pewnej zmiennej Aby ułatwićprogramistom tego typu zamiany powstał zestaw funkcji bibliotecznych Należą do nich

atol strtol mdash zamienia łańcuch na liczbę całkowitą typu long

atoi mdash zamienia łańcuch na liczbę całkowitą typu int

atoll strtoll mdash zamienia łańcuch na liczbę całkowitą typu long long ( bity) dodatkowo istniejeprzestarzała funkcja atoq będąca rozszerzeniem

atof strtod mdash przekształca łańcuch na liczbę typu double

Ogoacutelnie rzecz ujmując funkcje z serii ato nie pozwalają na wykrycie błędoacutew przy konwersji i dla-tego gdy jest to potrzebne należy stosować funkcje strto

Czasami przydaje się też konwersja w drugą stronę tzn z liczby na łańcuch Do tego celu możeposłużyć funkcja sprintf lub snprintf sprintf jest bardzo podobna do printf tyle że wyniki jej praczwracane są do pewnego łańcucha a nie wyświetlane np na ekranie monitora Należy jednak uwa-żać przy jej użyciu (patrz mdash Bezpieczeństwo kodu a łańcuchy) snprintf (zdefiniowana w nowszymstandardzie) dodatkowo przyjmuje jako argument wielkość bufora docelowego

185 Operacje na znakaWarto też powiedzieć w tymmiejscu o operacjach na samych znakach Spoacutejrzmy na poniższy program

include ltstdiohgtinclude ltctypehgtinclude ltstringhgt

int main()

int znakwhile ((znak = getchar())=EOF)

if( islower(znak) ) znak = toupper(znak)

else if( isupper](znak) ) znak = tolower(znak)

putchar(znak)

return 0

Program ten zmieniawewczytywanym tekściewielkie litery namałe i odwrotnie Wykorzystujemyfunkcje operujące na znakach z pliku nagłoacutewkowego ctypeh isupper sprawdza czy znak jest wielkąliterą natomiast toupper zmienia znak (o ile jest literą) na wielką literę Analogicznie jest dla funkcjiislower i tolower

Jako ćwiczenie możesz tak zmodyfikować program żeby odczytywał dane z pliku podanego jakoargument lub wprowadzonego z klawiatury

186 Częste błędy pisanie do niezaalokowanego miejsca

138 ROZDZIAŁ 18 NAPISY

char tekstscanf(s tekst)

zapominanie o kończącym napis nullu

char test[4] = test nie zmieścił się null kończący napis

nieprawidłowe poroacutewnywanie łańcuchoacutew

char tekst1[] = jakis tekstchar tekst2[] = jakis tekstif( tekst1 == tekst2 ) tu zawsze będzie fałsz bo == poroacutewnuje adresy należy użyć strcmp()

187 UnicodeZobacz w Wikipedii Uni-code W dzisiejszych czasach brak obsługi wielu językoacutew praktycznie marginalizowałoby język Dlatego też

C wprowadza możliwość zapisu znakoacutew wg norm Unicode

1871 Jaki typDo przechowywania znakoacutew zakodowanych w Unicode powinno się korzystać z typu war t Jegodomyślny rozmiar jest zależny od użytego kompilatora lecz w większości zaktualizowanych kompila-toroacutew powinny to być bajty Typ ten jest częścią języka C++ natomiast w C znajduje się w plikunagłoacutewkowym stddefh

Alternatywą jest wykorzystanie gotowych bibliotek dla Unicode (większość jest dostępnych jedyniedla C++ nie wspoacutełpracuje z C) ktoacutere często mają zdefiniowane własne typy jednak zmuszeni jesteśmywtedy do przejścia ze znanych nam już funkcji jak np strcpy strcmp na funkcje dostarczane przezbibliotekę co jest dość niewygodne My zajmiemy się pierwszym wyjściem

1872 Jaki rozmiar i jakie kodowanieUnicode określa jedynie jakiej liczbie odpowiada jaki znak nie moacutewi zaś nic o sposobie dekodowania(tzn jaka sekwencja znakoacutew odpowiada jakiemu znakuznakom) Jako że Unicode obejmuje tysznakoacutew zmienna zdolna pomieścić go w całości musi mieć przynajmniej bajty Niestety procesory niefunkcjonują na zmiennych o tym rozmiarze pracują jedynie na zmiennych o wielkościach oraz bajtoacutew (kolejne potęgi liczby ) Dlatego też jeśli wciąż uparcie chcemy być dokładni i zastosowaćprzejrzyste kodowanie musimy skorzystać ze zmiennej -bajtowej ( bity) Tak do sprawy podeszlitwoacutercy kodowania Unicode nazwanego -UCS- Ten typ kodowania po prostu przydziela każ-Zobacz w Wikipedii -32demu znakowi Unicode kolejne liczby Jest to najbardziej intuicyjny i wygodny typ kodowania ale jakwidać ciągi znakoacutew zakodowane w nim są bardzo obszerne co zajmuje dostępną pamięć spowalniadziałanie programu oraz drastycznie pogarsza wydajność podczas transferu przez sieć Poza -istnieje jeszcze wiele innych kodowań Najpopularniejsze z nich to

- mdash od do bajtoacutew (dla znakoacutew poniżej do bajtoacutew) na znak przez co jest skraj-nie niewygodny gdy chcemy przeprowadzać jakiekolwiek operacje na tekście bez korzystania zgotowych funkcji

- mdash lub bajty na znak ręczne modyfikacje łańcucha są bardziej skomplikowane niż przy-

UCS- mdash bajty na znak przez co znaki z numerami powyżej nie są uwzględnione roacutewniewygodny w użytkowaniu co -

187 UNICODE 139

Ręczne operacje na ciągach zakodowanych w - i - są utrudnione ponieważ w przeci-wieństwie do - gdzie można określić iż powiedzmy znak ciągu zajmuje bajty od do (gdyżz goacutery wiemy że znak zajął bajty od do ) w tych kodowaniach musimy najpierw określić rozmiar znaku Ponadto gdy korzystamy z nich nie działają wtedy funkcje udostępniane przez biblioteki Cdo operowania na ciągach znakoacutew

Priorytet Proponowane kodowaniamały rozmiar -8

łatwa i wydajna edycja -32 lub -2przenośność -83

ogoacutelna szybkość -2 lub -8

Co należy zrobić by zacząć korzystać z kodowania - (domyślne kodowanie dla C)

powinniśmy korzystać z typu wchar t (ang ldquowide characterrdquo) jednak jeśli chcemy udostępniaćkod źroacutedłowy programu do kompilacji na innych platformach powinniśmy ustawić odpowiednieparametry dla kompilatoroacutew by rozmiar był identyczny niezależnie od platformy

korzystamy z odpowiednikoacutew funkcji operujących na typie char pracujących na wchar t (z re-guły składnia jest identyczna z tą roacuteżnicą że w nazwach funkcji zastępujemy ldquostrrdquo na ldquowcsrdquo npstrcpy mdash wcscpy strcmp mdash wcscmp)

jeśli przyzwyczajeni jesteśmy do korzystania z klasy string powinniśmy zamiast niej korzystaćz wstring ktoacutera posiada zbliżoną składnię ale pracuje na typie wchar t

Co należy zrobić by zacząć korzystać z Unicode

gdy korzystamy z kodowań innych niż - i - powinniśmy zdefiniować własny typ

w wykorzystywanych przez nas bibliotekach podajemy typ wykorzystanego kodowania

gdy chcemy ręcznie modyfikować ciąg musimy przeczytać specyfikację danego kodowania sąone wyczerpująco opisane na siostrzanym projekcie Wikibooks mdash Wikipedii

Przykład użycia kodowania -

include ltstddefhgt jeśli używamy C++ możemy opuścić tę linijkę include ltstdiohgtinclude ltstringhgt

int main() wchar_t wcs1 = LAla ma kotawchar_t wcs2 = LKot ma Alewchar_t calosc[25]

wcscpy(calosc wcs1)(calosc + wcslen(wcs1)) = L wcscpy(calosc + wcslen(wcs1) + 1 wcs2)

printf(lancuch wyjsciowy lsn calosc)return 0

140 ROZDZIAŁ 18 NAPISY

Rozdział 19

Typy złożone

191 typedefJest to słowo kluczowe ktoacutere służy do definiowania typoacutew pochodnych np

typedef stara_nazwa nowa_nazwatypedef int mojInttypedef int WskNaInt

od tej pory mozna używać typoacutew mojInt i WskNaInt

192 Typ wyliczeniowySłuży do tworzenia zmiennych ktoacutere powinny przechowywać tylko pewne z goacutery ustalone wartości

enum Nazwa WARTOSC_1 WARTOSC_2 WARTOSC_N

Na przykład można w ten sposoacuteb stworzyć zmienną przechowującą kierunek

enum Kierunek W_GORE W_DOL W_LEWO W_PRAWO

enum Kierunek kierunek = W_GORE

ktoacuterą można na przykład wykorzystać w instrukcji switch

switch(kierunek)

case W_GOREprintf(w goacuteręn)break

case W_DOLprintf(w doacutełn)break

defaultprintf(gdzieś w bokn)

Tradycyjnie przechowywane wielkości zapisuje się wielkimi literami (W GORE W DOL)Tak naprawdę C przechowuje wartości typu wyliczeniowego jako liczby całkowite o czym można

się łatwo przekonać

141

142 ROZDZIAŁ 19 TYPY ZŁOŻONE

kierunek = W_DOLprintf(in kierunek) wypisze 1

Kolejne wartości to po prostu liczby naturalne domyślnie pierwsza to zero druga jeden itp Mo-żemy przy deklarowaniu typu wyliczeniowego zmienić domyślne przyporządkowanie

enum Kierunek W_GORE W_DOL = 8 W_LEWO W_PRAWO printf(i in W_DOL W_LEWO) wypisze 8 9

Co więcej liczby mogą się powtarzać i wcale nie muszą być ustawione w kolejności rosnącej

enum Kierunek W_GORE = 5 W_DOL = 5 W_LEWO = 2 W_PRAWO = 1 printf(i in W_DOL W_LEWO) wypisze 5 2

Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajną ich obsługęale stwarza niebezpieczeństwa mdash można przypisywać pod typ wyliczeniowy liczby nawet nie mająceodpowiednika w wartościach a kompilator może o tym nawet nie ostrzec

kierunek = 40

193 StrukturyStruktury to specjalny typ danych mogący przechowywać wiele wartości w jednej zmiennej Od tablicjednakże roacuteżni się tym iż te wartości mogą być roacuteżnych typoacutew

Struktury definiuje się w następujący sposoacuteb

struct Struktura int pole1int pole2char pole3

gdzie ldquoStrukturardquo to nazwa tworzonej strukturyNazewnictwo ilość i typ poacutel definiuje programista według własnego uznaniaZmienną posiadającą strukturę tworzy się podając jako jej typ nazwę struktury

struct Struktura zmienna

Dostęp do poszczegoacutelnych poacutel uzyskuje się przy pomocy operatora wyboru składnika kropki (rsquorsquo)

zmiennaSpole1 = 60 przypisanie liczb do poacutel zmiennaSpole2 = 2zmiennaSpole3 = a a teraz znaku

194 UnieUnie to kolejny sposoacuteb prezentacji danychw pamięci Na pierwszy rzut oka wyglądają bardzo podobniedo struktur

union Nazwa typ1 nazwa1typ2 nazwa2

Na przykład

194 UNIE 143

union LiczbaLubZnak int calkowitachar znakdouble rzeczywista

Pola w unii nakładają się na siebie w ten sposoacuteb że w danej chwili można w niej przechowywaćwartość tylko jednego typu Unia zajmuje w pamięci tyle miejsca ile zajmuje największa z jej składo-wych W powyższym przypadku unia będzie miała prawdopodobnie rozmiar typu double czyli często bity a całkowita i znak będą wskazywały odpowiednio na pierwsze cztery bajty lub na pierwszy bajtunii (choć nie musi tak być zawsze) Dlaczego tak Taka forma często przydaje się np do konwersji po-między roacuteżnymi typami danych Możemy dzięki unii podzielić zmienną -bitową na cztery składowezmienne o długości bitoacutew każda

Do konkretnych wartości poacutel unii odwołujemy się podobnie jak w przypadku struktur za pomocąkropki

union LiczbaLubZnak liczbaliczbacalkowita = 10printf(dn liczbacalkowita)

Zazwyczaj użycie unii ma na celu zmniejszenie zapotrzebowania na pamięć gdy naraz będzie wy-korzystywane tylko jedno pole i jest często łączone z użyciem struktur

Przyjrzyjmy się teraz przykładowi ktoacutery powinien dobitnie zademonstrować działanie unii

include ltstdiohgt

struct adres_bajtowy __uint8_t a__uint8_t b__uint8_t c__uint8_t d

union adres __uint32_t ipstruct adres_bajtowy badres

int main ()

union adres addraddrbadresa = 192addrbadresb = 168addrbadresc = 1addrbadresd = 1printf (Adres IP w postaci 32-bitowej zmiennej 08xnaddrip)return 0

Zauważyłeś pewien ciekawy efekt Jeśli uruchomiłeś ten program na typowym komputerze domo-wym (rodzina i) na ekranie zapewne pojawił Ci się taki oto napis

Adres IP w postaci 32-bitowej zmiennej 0101a8c0

Dlaczego jedynki są na początku zmiennej skoro w programie były to dwa ostatnie bajty (pola c id struktury) Jest to problem kolejności bajtoacutew Aby dowiedzieć się o nim więcej przeczytaj rozdział

144 ROZDZIAŁ 19 TYPY ZŁOŻONE

przenośność programoacutew Zauważyłeś zatem że za pomocą tego programu w prosty sposoacuteb zamienili-śmy cztery zmienne jednobajtowe w jedną czterobajtową Jest to tylko jedno z możliwych zastosowańunii

195 Inicjalizacja struktur i uniiJeśli tworzymy nową strukturę lub unię możemy zaraz po jej deklaracji wypełnić ją określonymi da-nymi Rozważmy tutaj przykład

struct moja_struct int achar b moja = 1c

Wzasadzie taka deklaracja nie roacuteżni się niczym odwypełnienia np tablicy danymi Jednak standardC wprowadza pewne udogodnienie zaroacutewno przy deklaracji struktur jak i unii Polega ono na tymże w nawiasie klamrowym możemy podać nazwy poacutel struktury lub unii ktoacuterym przypisujemy wartośćnp

struct moja_struct int achar b moja = b = c pozostawiamy pole a niewypełnione żadną konkretną wartością

196 Wspoacutelnewłasności typoacutewwyliczeniowy unii i struk-tur

Warto w zwroacutecić uwagę że język C++ przy deklaracji zmiennych typoacutew wyliczeniowych unii lubstruktur nie wymaga przed nazwą typu odpowiedniego słowa kluczowego Na przykład poniższy kodjest poprawnym programem C++

enum Enum A B C union Union int a float b struct Struct int a float b int main()

Enum eUnion uStruct se = Aua = 0sa = 0return e + ua + sa

Nie jest to jednak poprawny kod C i należy o tym pamiętać szczegoacutelnie jeżeli uczysz się języka Ckorzystając z kompilatora C++

Należy roacutewnież pamiętać że po klamrze zamykającej definicje musi następować średnik Brak tegośrednika jest częstym błędem powodującym czasami niezrozumiałe komunikaty błędoacutew Jedynym wy-jątkiem jest natychmiastowa definicja zmiennych danego typu na przykład

struct Struktura int pole

s1 s2 s3

196 WSPOacuteLNE WŁASNOŚCI TYPOacuteW WYLICZENIOWYCH UNII I STRUKTUR 145

Definicja typoacutew wyliczeniowych unii i struktur jest lokalna do bloku To znaczy możemy zdefi-niować strukturę wewnątrz jednej z funkcji (czy wręcz wewnątrz jakiegoś bloku funkcji) i tylko tambędzie można używać tego typu

Częstym idiomem w C jest użycie typedef od razu z definicją typu by uniknąć pisania enum unionczy struct przy deklaracji zmiennych danego typu

typedef struct struktura int pole

StrukturaStruktura s1struct struktura s2

W tym przypadku zmienne s i s są tego samego typu Możemy też zrezygnować z nazywaniasamej struktury

typedef struct int pole

StrukturaStruktura s1

1961 Wskaźnik na unię i strukturęPodobnie jak na każdą inną zmienna wskaźnik może wskazywać także na unię lub strukturę Otoprzykład

typedef struct int p1 p2

Struktura

int main ()

Struktura s = 0 0 Struktura wsk = ampswsk-gtp1 = 2wsk-gtp2 = 3return 0

Zapis wsk-gtp1 jest (z definicji) roacutewnoważny (wsk)p1 ale bardziej przejrzysty i powszechnie sto-sowany Wyrażenie wskp1 spowoduje błąd kompilacji (strukturą jest wsk a nie wsk)

1962 Zobacz też Powszechne praktyki mdash konstruktory i destruktory

1963 Pola bitoweStruktury mają pewne dodatkowe możliwości w stosunku do zmiennych Mowa tutaj o rozmiarzeelementu struktury W przeciwieństwie do zmiennej może on mieć nawet bit Aby moacutec zdefiniowaćtaką zmienną musimy użyć tzw pola bitowego Wygląda ono tak

struct moja unsigned int a14 4 bity

a28 8 bitoacutew (często 1 bajt) a31 1 bit a43 3 bity

146 ROZDZIAŁ 19 TYPY ZŁOŻONE

Wszystkie pola tej struktury mają w sumie rozmiar bitoacutew jednak możemy odwoływać się donichw taki sam sposoacuteb jak do innych elementoacutew struktury W ten sposoacuteb efektywniej wykorzystujemypamięć jednak istnieją pewne zjawiska ktoacuterych musimy być świadomi przy stosowaniu poacutel bitowychWięcej na ten temat w rozdziale przenośność programoacutew

Pola bitowe znalazły zastosowanie głoacutewnie w implementacjach protokołoacutew sieciowych

197 Studium przypadku mdash implementacja listy wskaźniko-wej

Zobacz w Wikipedii ListaRozważmy teraz coś co każdy z nas może spotkać w codziennym życiu Każdy z nas widział kiedyśjakiś przykład listy (czy to zakupoacutew czy też listę wierzycieli) Język C też oferuje listy jednak w progra-mowaniu listy będą służyły do czegoś innego Wyobraźmy sobie sytuację w ktoacuterej jesteśmy autoramigenialnego programu ktoacutery znajduje kolejne liczby pierwsze Oczywiście każdą kolejną liczbę pierw-szą może wyświetlać na ekran jednak z matematyki wiemy że dana liczba jest liczbą pierwszą jeśli niedzieli się przez żadną liczbę pierwszą ją poprzedzającą mniejszą od pierwiastka z badanej liczby Uffmniej więcej chodzi o to że moglibyśmy wykorzystać znalezione wcześniej liczby do przyspieszeniadziałania naszego programu Jednak nasze liczby trzeba jakoś mądrze przechować w pamięci Tablicemają ograniczenie mdash musimy z goacutery znać ich rozmiar Jeśli zapełnilibyśmy tablicę to przy znalezieniukażdej kolejnej liczby musielibyśmy

przydzielać nowy obszar pamięci o rozmiarze poprzedniego rozmiaru + rozmiar zmiennej prze-chowującej nowo znalezioną liczbę

kopiować zawartość starego obszaru do nowego

zwalniać stary nieużywany obszar pamięci

w ostatnim elemencie nowej tablicy zapisać znalezioną liczbę

Coacuteż trochę tutaj roboty jest a kopiowanie całej zawartości jednego obszaru w drugi jest czaso-chłonne W takim przypadku możemy użyć listy Tworząc listę możemy w prosty sposoacuteb przechowaćnowo znalezione liczby Przy użyciu listy nasze postępowanie ograniczy się do

przydzielenia obszaru pamięci aby przechować wartość obliczeń

dodać do listy nowy element

Prawda że proste Dodatkowo lista zajmuje w pamięci tylko tyle pamięci ile potrzeba na aktualnąliczbę elementoacutew Pusta tablica zajmuje natomiast tyle samo miejsca co pełna tablica

1971 Implementacja listyW języku C aby stworzyć listę musimy użyć struktur Dlaczego Ponieważ musimy przechować conajmniej dwie wartości

pewną zmienną (np liczbę pierwszą z przykładu)

wskaźnik na kolejny element listy

Przyjmijmy że szukając liczb pierwszych nie przekroczymy możliwości typu unsigned long

typedef struct element struct element next wskaźnik na kolejny element listy unsigned long val przechowywana wartość

el_listy

Zacznijmy zatem pisać nasz eksperymentalny program do wyszukiwania liczb pierwszych Pierw-szą liczbą pierwszą jest liczba Pierwszym elementem naszej listy będzie zatem struktura ktoacutera będzieprzechowywała liczbę Na co będzie wskazywało pole next Ponieważ na początku działania pro-gramu będziemy mieć tylko jeden element listy pole next powinno wskazywać na Umoacutewmy sięzatem że pole next ostatniego elementu listy będzie wskazywało mdash po tym poznamy że lista sięskończyła

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 147

include ltstdiohgtinclude ltstdlibhgttypedef struct element

struct element nextunsigned long val

el_listy

el_listy first pierwszy element listy

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (ilt=END++i) tutaj powinien znajdować się kod ktoacutery sprawdza podzielność sprawdzanej liczby przezpoprzednio znalezione liczby pierwsze oraz dodaje liczbę do listy w przypadku stwierdzenia

że jest ona liczbą pierwszą

wypisz_liste(first)return 0

Na początek zajmiemy się wypisywaniem listy W tym celu będziemy musieli ldquoodwiedzićrdquo każdyelement listy Elementy listy są połączone polem next aby przeglądnąć listę użyjemy następującegoalgorytmu

Ustaw wskaźnik roboczy na pierwszym elemencie listy

Jeśli wskaźnik ma wartość przerwij

Wypisz element wskazywany przez wskaźnik

Przesuń wskaźnik na element ktoacutery jest wskazywany przez pole next

Wroacuteć do punktu

void wypisz_liste(el_listy lista)

el_listy wsk=lista 1 while( wsk = NULL ) 2

printf (lun wsk-gtval) 3 wsk = wsk-gtnext 4 5

Zastanoacutewmy się teraz jak powinien wyglądać kod ktoacutery dodaje do listy następny element Takafunkcja powinna

znaleźć ostatni element (tj element ktoacuterego pole next == )

przydzielić odpowiedni obszar pamięci

skopiować w pole val w nowo przydzielonym obszarze znalezioną liczbę pierwszą

nadać polu next ostatniego elementu listy wartość

w pole next ostatniego elementu listy wpisać adres nowo przydzielonego obszaru

148 ROZDZIAŁ 19 TYPY ZŁOŻONE

Napiszmy zatem odpowiednią funkcję

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL) 1

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy)) 2 nowy-gtval = liczba 3 nowy-gtnext = NULL 4 wsk-gtnext = nowy 5

Ihellip to już właściwie koniec naszej funkcji (warto zwroacutecić uwagę że funkcja w tej wersji zakłada żena liście jest już przynajmniej jeden element) Wstaw ją do kodu przed funkcją main Został namjeszcze jeden problem w pętli for musimy dodać kod ktoacutery odpowiednio będzie ldquobadałrdquo liczby oraz wprzypadku stwierdzenia pierwszeństwa liczby będzie dodawał ją do listy Ten kod powinien wyglądaćmniej więcej tak

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczba wsk-gtval)==0) return 0 jeśli reszta z dzielenialiczby przez ktoacuterąkolwiek z poprzednio znalezionychliczb pierwszych jest roacutewna zero to znaczy że liczba tanie jest liczbą pierwszą

wsk = wsk-gtnext

natomiast jeśli sprawdzimy wszystkie poprzednio znalezione liczbyi żadna z nich nie będzie dzieliła liczby imożemy liczbę i dodać do listy liczb pierwszych

return 1for (ilt=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (firsti)

Podsumujmy teraz efekty naszej pracy Oto cały kod naszego programu

include ltstdiohgtinclude ltstdlibhgt

typedef struct element struct element nextunsigned long val

el_listy

el_listy first

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 149

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL)

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy))nowy-gtval = liczbanowy-gtnext = NULLwsk-gtnext = nowy podczepiamy nowy element do ostatniego z listy

void wypisz_liste(el_listy lista)

el_listy wsk=listawhile( wsk = NULL )

printf (lun wsk-gtval)wsk = wsk-gtnext

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczbawsk-gtval)==0) return 0wsk = wsk-gtnext

return 1

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (i=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (first i)

wypisz_liste(first)return 0

Możemy jeszcze pomyśleć jak można by wykonać usuwanie elementu z listy Najprościej byłobyzrobić

wsk-gtnext = wsk-gtnext-gtnext

150 ROZDZIAŁ 19 TYPY ZŁOŻONE

ale wtedy element na ktoacutery wskazywał wcześniej wsk-gtnext przestaje być dostępny i zaśmieca pa-mięć Trzeba go usunąć Zauważmy że aby usunąć element potrzebujemy wskaźnika do elementu gopoprzedzającego (po to by nie rozerwać listy) Popatrzmy na poniższą funkcję

void usun_z_listy(el_listy lista int element)

el_listy wsk=listawhile (wsk-gtnext = NULL)

if (wsk-gtnext-gtval == element) musimy mieć wskaźnik do elementu poprzedzającego el_listy usuwany=wsk-gtnext zapamiętujemy usuwany element wsk-gtnext = usuwany-gtnext przestawiamy wskaźnik next by omijał usuwany element free(usuwany) usuwamy z pamięci else

wsk = wsk-gtnext idziemy dalej tylko wtedy kiedy nie usuwaliśmy bo nie chcemy zostawić duplikatoacutew

Funkcja ta jest tak napisana by usuwała z listy wszystkie wystąpienia danego elementu (w naszymprogramie nie ma to miejsca ale lista jest zrobiona tak że może trzymać dowolne liczby) Zauważmyże wskaźnik wsk jest przesuwany tylko wtedy gdy nie kasowaliśmy Gdybyśmy zawsze go przesuwaliprzegapilibyśmy element gdyby występował kilka razy pod rząd

Funkcja ta działa poprawnie tylko wtedy gdy nie chcemy usuwać pierwszego elementu Można topoprawić mdash dodając instrukcję warunkową do funkcji lub dodając do listy ldquogłowęrdquo mdash pierwszy elementnie przechowujący niczego ale upraszczający operacje na liście Zostawiamy to do samodzielnej pracy

Cały powyższy przykład omawiał tylko jeden przypadek listy mdash listę jednokierunkową Jednakistnieją jeszcze inne typy list np lista jednokierunkowa cykliczna lista dwukierunkowa oraz dwukie-runkowa cykliczna Roacuteżnią się one od siebie tylko tym że

w przypadku list dwukierunkowych mdashw strukturze el listy znajduje się jeszcze pole ktoacutere wska-zuje na element poprzedni

w przypadku list cyklicznych mdash ostatni element wskazuje na pierwszy (nie rozroacuteżnia się wtedyelementu pierwszego ani ostatniego)

Rozdział 20

Biblioteki

201 Czym jest bibliotekaBiblioteka jest to zbioacuter funkcji ktoacutere zostały wydzielone po to aby dało się z nich korzystać wwielu pro-gramach Ułatwia to programowanie mdash nie musimy np sami tworzyć funkcji printf Każda bibliotekaposiada swoje pliki nagłoacutewkowe ktoacutere zawierają deklaracje funkcji bibliotecznych oraz często zawartesą w nich komentarze jak używać danej funkcji W tej części podręcznika nauczymy się tworzyć naszewłasne biblioteki

202 Jak zbudowana jest bibliotekaKażda biblioteka składa się z co najmniej dwoacutech części

pliku nagłoacutewkowego z deklaracjami funkcji (plik z rozszerzeniem h)

pliku źroacutedłowego zawierającego ciała funkcji (plik z rozszerzeniem c)

2021 Budowa pliku nagłoacutewkowegoOto najprostszy możliwy plik nagłoacutewkowy

ifndef PLIK_Hdefine PLIK_H tutaj są wpisane deklaracje funkcji endif PLIK_H

Zapewne zapytasz się na co komu instrukcje ifndef define oraz endif Otoacuteż często się zdarzaże w programie korzystamy z plikoacutew nagłoacutewkowych ktoacutere dołączają się wzajemnie Oznaczałoby to żew kodzie programu kilka razy pojawiła by się zawartość tego samego pliku nagłoacutewkowego Instrukcjaifndef i define temu zapobiega Dzięki temu kompilator nie musi kilkakrotnie kompilować tegosamego kodu

W plikach nagłoacutewkowych często umieszcza się też definicje typoacutew z ktoacuterych korzysta bibliotekaalbo np makr

2022 Budowa najprostszej bibliotekiZałoacuteżmy że nasza biblioteka będzie zawierała jedną funkcję ktoacuterawypisuje na ekran tekst ldquoplWikibooksrdquoUtwoacuterzmy zatem nasz plik nagłoacutewkowy

151

152 ROZDZIAŁ 20 BIBLIOTEKI

ifndef WIKI_Hdefine WIKI_Hvoid wiki (void)endif

Należy pamiętać o podaniu void w liście argumentoacutew funkcji nie przyjmujących argumentoacutew Oile przy definicji funkcji nie trzeba tego robić (jak to często czyniliśmy w przypadku funkcji main) otyle w prototypie brak słoacutewka void oznacza że w prototypie nie ma informacji na temat tego jakieargumenty funkcja przyjmuje

Plik nagłoacutewkowy zapisujemy jako ldquowikihrdquo Teraz napiszmy ciało tej funkcji

include wikihinclude ltstdiohgt

void wiki (void)

printf (plWikibooksn)

Ważne jest dołączenie na początku pliku nagłoacutewkowego Dlaczego Plik nagłoacutewkowy zawieradeklaracje naszych funkcji mdash jeśli popełniliśmy błąd i deklaracja nie zgadza się z definicją kompilatorod razu nas o tym powiadomi Oproacutecz tego plik nagłoacutewkowy może zawierać definicje istotnych typoacutewlub makr

Zapiszmy naszą bibliotekę jako plik ldquowikicrdquo Teraz należy ją skompilować Robi się to trochę ina-czej niż normalny program Należy po prostu do opcji kompilatora gcc dodać opcję ldquo-crdquo

gcc wikic -c -o wikio

Rozszerzenie ldquoordquo jest domyślnym rozszerzeniem dla bibliotek statycznych (typowych bibliotek łą-czonych z resztą programu na etapie kompilacji) Teraz możemy spokojnie skorzystać z naszej nowejbiblioteki Napiszmy nasz program

include wikih

int main ()

wiki()return 0

Zapiszmy program jako ldquomaincrdquo Teraz musimy odpowiednio skompilować nasz program

gcc mainc wikio -o main

Uruchamiamy nasz program

mainplWikibooks

Jak widać nasza pierwsza biblioteka działaZauważmy że kompilatorowi podajemy i pliki z kodem źroacutedłowym (mainc) i pliki ze skompilo-

wanymi bibliotekami (wikio) by uzyskać plik wykonywalny (main) Jeśli nie podalibyśmy plikoacutew zbibliotekami mainc co prawda skompilowałby się ale błąd zostałby zgłoszony przez linker mdash częśćkompilatora odpowiedzialna za wstawienie w miejsce wywołań funkcji ich adresoacutew (takiego adresulinker nie moacutegłby znaleźć)

202 JAK ZBUDOWANA JEST BIBLIOTEKA 153

2023 Zmiana dostępu do funkcji i zmienny (static i extern)Język C w przeciwieństwie do swego młodszego krewnego mdash C++ nie posiada praktycznie żadnychmechanizmoacutew ochrony kodu biblioteki przed modyfikacjami C++ ma w swoim asortymencie minsterowanie uprawnieniami roacuteżnych elementoacutew klasy Jednak programista piszący program w C niejest tak do końca bezradny Autorzy C dali mu do ręki dwa narzędzia extern oraz static Pierwsze ztych słoacutew kluczowych informuje kompilator że dana funkcja lub zmienna istnieje ale w innymmiejscui zostanie dołączona do kodu programu w czasie łączenia go z biblioteką

extern przydaje się gdy zmienna lub funkcja jest zadeklarowana w bibliotece ale nie jest udostęp-niona na zewnątrz (nie pojawia się w pliku nagłoacutewkowym) Przykładowo

bibliotekah extern char zmienna_dzielona[]

bibliotekac include bibliotekah

char zmienna_dzielona[] = Zawartosc

mainc include ltstdiohgtinclude bibliotekah

int main()

printf(sn zmienna_dzielona)return 0

Gdybyśmy tu nie zastosowali extern kompilator (nie linker) zaprotestowałby że nie zna zmiennejzmienna dzielona Proacuteba dopisania deklaracji char zmienna dzielona stworzyłaby nową zmienną iutracilibyśmy dostęp do interesującej nas zawartości

Odwrotne działanie ma słowo kluczowe static użyte w tym kontekście (użyte wewnątrz bloku two-rzy zmienną statyczną więcej informacji w rozdziale Zmienne) Może ono odnosić się zaroacutewno dozmiennych jak i do funkcji globalnych Powoduje że dana zmienna lub funkcja jest niedostępna nazewnątrz biblioteki1 Możemy dzięki temu ukryć np funkcje ktoacutere używane są przez samą bibliotekęby nie dało się ich wykorzystać przez extern

1Tak naprawdę całe ldquoukrycierdquo funkcji polega na zmianie niektoacuterych danych w pliku z kodem binarnym danejbiblioteki (pliku o) przez co linker powoduje wygenerowanie komunikatu o błędzie w czasie łączenia biblioteki zprogramem

154 ROZDZIAŁ 20 BIBLIOTEKI

Rozdział 21

Więcej o kompilowaniu

211 Ciekawe opcje kompilatora Emdash powoduje wygenerowanie kodu programu ze zmianami wprowadzonymi przez preprocesor

S mdash zamiana kodu w języku C na kod asemblera (komenda gcc -S plikc spowoduje utworzeniepliku o nazwie pliks w ktoacuterym znajdzie się kod asemblera)

c mdash kompilacja bez łączenia z bibliotekami

Ikatalog mdash ustawienie domyślnego katalogu z plikami nagłoacutewkowymi na katalog

lbiblioteka mdash wymusza łączenie programu z podaną biblioteką (np -lGL)

212 Program makeDość często może się zdarzyć że nasz program składa się z kilku plikoacutew źroacutedłowych Jeśli tych plikoacutewjest mało (np -) możemy jeszcze proacutebować ręcznie kompilować każdy z nich Jednak jeśli tych plikoacutewjest dużo lub chcemy pokazać nasz program innym użytkownikom musimy stworzyć elegancki sposoacutebkompilacji naszego programu Właśnie po to aby zautomatyzować proces kompilacji powstał programmake Program make analizuje pliki Makefile i na ich podstawie wykonuje określone czynności

2121 Budowa pliku MakefileUwaga poniżej został omoacutewiony Makefile dla Make Istnieją inne programy make i mogą używaćinnej składni Na Wikibooks został też obszernie opisany program make firmy Borland

Najważniejszym elementem pliku Makefile są zależności oraz reguły przetwarzania Zależnościpolegają na tym że np jeśli nasz program ma być zbudowany z plikoacutew to najpierw należy skom-pilować każdy z tych plikoacutew a dopiero poacuteźniej połączyć je w jeden cały program Zatem zależnościokreślają kolejność wykonywanych czynności Natomiast reguły określają jak skompilować dany plikZależności tworzy się tak

co od_czegoreguły

Dzięki temu program make zna już kolejność wykonywanych działań oraz czynności jakie ma wy-konać Aby zbudować ldquocordquo należy wykonać polecenie make co Pierwsza reguła w pliku Makefile jestregułą domyślną Jeśli wydamy polecenie make bez parametroacutew zostanie zbudowana właśnie reguładomyślna Tak więc dobrze jest jako pierwszą regułę wstawić regułę budującą końcowy plik wykony-walny zwyczajowo regułę tą nazywa się all

155

156 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Należy pamiętać by sekcji ldquocordquo niewcinać natomiast ldquoregułyrdquowcinać tabulatorem Część ldquood czegordquomoże być pusta

Plik Makefile umożliwia też definiowanie pewnych zmiennych Nie trzeba tutaj się już troszczyć otyp zmiennej wystarczy napisać

nazwa_zmiennej = wartość

Wten sposoacutebmożemy zadeklarować dowolnie dużo zmiennych Zmiennemogą być roacuteżnemdash nazwakompilatora jego parametry iwiele innych Zmiennej używamywnastępujący sposoacuteb $(nazwa zmiennej)

Komentarze w pliku Makefile tworzymy zaczynając linię od znaku hash ()

2122 Przykładowy plik MakefileDość tej teorii teraz zajmiemy się działającym przykładem Załoacuteżmy że nasz przykładowy programnazywa się test oraz składa się z czterech plikoacutew pierwszyc drugic trzecic czwartyc

Odpowiedni plik Makefile powinien wyglądać mniej więcej tak

Moacutej plik makefile - wpisz make all aby skompilować cały program (właściwie wystarczy wpisać make - all jest domyślny jako pierwszy cel)CC = gcc

all pierwszyo drugio trzecio czwartyo$(CC) pierwszyo drugio trzecio czwartyo -o test

pierwszyo pierwszyc$(CC) pierwszyc -c -o pierwszyo

drugio drugic$(CC) drugic -c -o drugio

trzecio trzecic$(CC) trzecic -c -o trzecio

czwartyo czwartyc$(CC) czwartyc -c -o czwartyo

Widzimy że nasz program zależy od plikoacutew z rozszerzeniem o (pierwszyo itd) potem każdy ztych plikoacutew zależy od plikoacutew c ktoacutere program make skompiluje w pierwszej kolejności a następniepołączy w jeden program (test) Nazwę kompilatora zapisaliśmy jako zmienną ponieważ powtarza sięi zmienna jest sposobem by zmienić ją wszędzie za jednym zamachem

Zatem jak widać używanie pliku Makefile jest bardzo proste Warto na koniec naszego przykładudodać regułę ktoacutera wyczyści katalog z plikoacutew o

cleanrm -f o test

Ta reguła spowoduje usunięcie wszystkich plikoacutew o oraz naszego programu jeśli napiszemy makeclean

Możemy też ukryć wykonywane komendy albo dopisać własny opis czynności

cleanecho Usuwam gotowe plikirm -f o test

Ten sam plik Makefile moacutegłby wyglądać inaczej

213 OPTYMALIZACJE 157

CFLAGS = -g -O tutaj można dodawać inne flagi kompilatoraLIBS = -lm tutaj można dodawać biblioteki

OBJ =pierwszyo drugio trzecio czwartyo

all main

cleanrm -f o test

co$(CC) -c $(INCLUDES) $(CFLAGS) $lt

main $(OBJ)$(CC) $(OBJ) $(LIBS) -o test

Tak naprawdę jest to dopiero bardzo podstawowe wprowadzenie do używania programu makejednak jest ono wystarczające byś zaczął z niego korzystać Wyczerpujące omoacutewienie całego programuniestety przekracza zakres tego podręcznika

213 OptymalizacjeKompilator umożliwia generację kodu zoptymalizowanego dla konkretnej architektury Służą dotego opcje -mar= i -mtune= Stopień optymalizacji ustalamy za pomocą opcji -Ox gdzie x jest nume-rem stopnia optymalizacji (od do ) Możliwe jest też użycie opcji -Os ktoacutera powoduje generowaniekodu o jak najmniejszym rozmiarze Aby skompilować dany plik z optymalizacjami dla procesora Ath-lon należy napisać tak

gcc programc -o program -march=athlon-xp -O3

Z optymalizacjami należy uważać gdyż często zdarza się że kod skompilowany bez optymalizacjidziała zupełnie inaczej niż ten ktoacutery został skompilowany z optymalizacjami

2131 WyroacutewnywanieWyroacutewnywanie jest pewnym zjawiskiem na ktoacutere w bardzo wielu podręcznikach moacutewiących o Cw ogoacutele się nie wspomina Ten rozdział ma za zadanie wyjaśnienie tego zjawiska oraz uprzedzenieprogramisty o pewnych faktach ktoacutere w poacuteźniejszej jego ldquotwoacuterczościrdquo mogą zminimalizować czas naznalezienie pewnych informacji ktoacutere mogą wpływać na to że jego program nie będzie działał popraw-nie

Często zdarza się że kompilator w ramach optymalizacji ldquowyroacutewnujerdquo elementy struktury tak abyprocesor moacutegł łatwiej odczytać i przetworzyć dane Przyjrzyjmy się bliżej następującemu fragmentowikodu

typedef struct unsigned char wiek 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

158 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Aby procesor moacutegł łatwiej przetworzyć dane kompilator może dodać do tej struktury jedno ośmio-bitowe pole Wtedy struktura będzie wyglądała tak

typedef struct unsigned char wiek 8 bitoacutew unsigned char fill[1] 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

Wtedy rozmiar zmiennych przechowujących wiek płeć oraz dochoacuted będzie wynosił bity mdashbędzie zatem potęgą liczby dwa i procesorowi dużo łatwiej będzie tak ułożoną strukturę przechowywaćw pamięci cache Jednak taka sytuacja nie zawsze jest pożądana Może się okazać że nasza strukturamusi odzwierciedlać np pojedynczy pakiet danych przesyłanych przez sieć Nie może być w niejzatem żadnych innych poacutel poza tymi ktoacutere są istotne do transmisji Aby wymusić na kompilatorzewyroacutewnanie -bajtowe (co w praktyce wyłącza je) należy przed definicją struktury dodać dwie linijkiTen kod działa pod Visual C++

pragma pack(push)pragma pack(1)

struct struktura

pragma pack(pop)

W kompilatorze należy po deklaracji struktury dodajemy przed średnikiem kończącym jednąlinijkę

__attribute__ ((packed))

Działa ona dokładnie tak samo jak makra pragma jednak jest ona obecna tylko w kompilatorze

Dzięki użyciu tego atrybutu kompilator zostanie ldquozmuszonyrdquo do braku ingerencji w naszą strukturęJest jednak jeszcze jeden być może bardziej elegancki sposoacuteb na obejście dopełniania Zauważyłeś żedopełnienie dodane przez kompilator pojawiło się między polem o długości bitoacutew (plec) oraz polem odługości bitoacutew (dochod) Wyroacutewnywanie polega na tym że dana zmienna powinna być umieszczonapod adresem będącym wielokrotnością jej rozmiaru Oznacza to że jeśli np mamy w strukturze napoczątku dwie zmienne o rozmiarze jednego bajta a potem jedną zmienną o rozmiarze bajtoacutew topomiędzy polami o rozmiarze bajtoacutew a polem czterobajtowym pojawi się dwubajtowe dopełnienieMoże Ci się wydawać że jest to tylko niepotrzebne mącenie w głowie jednak niektoacutere architektury(zwłaszcza typu ) mogą nie wykonać kodu ktoacutery nie został wyroacutewnany Dlatego naszą strukturępowinniśmy zapisać mniej więcej tak

typedef struct unsigned short dochod 16 bitoacutew unsigned char wiek 8 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

W ten sposoacuteb wyroacutewnana struktura nie będzie podlegała modyfikacjom przez kompilator oraz bę-dzie przenośna pomiędzy roacuteżnymi kompilatorami

Wyroacutewnywanie działa także na pojedynczych zmiennych w programie jednak ten problem nie po-woduje tyle zamieszania co ingerencja kompilatora w układ poacutel struktury Wyroacutewnywanie zmiennychpolega tylko na tym że kompilator umieszcza je pod adresami ktoacutere są wielokrotnością ich rozmiaru

214 KOMPILACJA KRZYŻOWA 159

214 Kompilacja krzyżowaMając w domu dwa komputery o odmiennych architekturach (np i oraz Sparc) możemy potrze-bować stworzyć program dla jednej maszyny mając do dyspozycji tylko drugi komputer Nie musimywtedy latać do znajomego posiadającego odpowiedni sprzęt Możemy skorzystać z tzw kompilacjikrzyżowej (ang cross-compile) Polega ona na tym że program nie jest kompilowany pod procesorna ktoacuterym działa kompilator lecz na inną zdefiniowaną wcześniej maszynę Efekt będzie taki sam askompilowany program możemy bez problemu uruchomić na drugim komputerze

215 Inne narzędziaWśroacuted przydatnych narzędzi warto wymienić roacutewnież program objdump (zaroacutewno pod Unix jak ipod Windows) oraz readelf (tylko Unix) Objdump służy do deasemblacji i analizy skompilowanychprogramoacutew Readelf służy do analizy pliku wykonywalnego w formacie (używanego w większościsystemoacutew z rodziny Unix) Więcej informacji możesz uzyskać pisząc (w systemach Unix)

man 1 objdumpman 1 readelf

160 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Rozdział 22

Zaawansowane operacjematematyczne

221 Biblioteka matematycznaAby moacutec korzystać z wszystkich dobrodziejstw funkcji matematycznych musimy na początku dołączyćplik mathh

include ltmathhgt

A w procesie kompilacji (dotyczy kompilatora GCC) musimy niekiedy dodać flagę ldquo-lmrdquo

gcc plikc -o plik -lm

Funkcje matematyczne ktoacutere znajdują się w bibliotece standardowej możesz znaleźć tutaj Przykorzystaniu z nich musisz wziąć pod uwagę min to że biblioteka matematyczna prowadzi kalkulacjęw oparciu o radiany a nie stopnie

2211 Stałe matematyczneW pliku mathh zdefiniowane są pewne stałe ktoacutere mogą być przydatne do obliczeń Są to min

M E mdash podstawa logarytmu naturalnego (e liczba Eulera)

M LOG2E mdash logarytm o podstawie z liczby e

M LOG10E mdash logarytm o podstawie z liczby e

M LN2 mdash logarytm naturalny z liczby

M LN10 mdash logarytm naturalny z liczby

M PI mdash liczba π

M PI 2 mdash liczba π

M PI 4 mdash liczba π

M 1 PI mdash liczba π

M 2 PI mdash liczba π

161

162 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

222 Prezentacja liczb rzeczywisty w pamięci komputeraByć może ten temat może wydać Ci się niepotrzebnym lecz w wielu książkach nie ma w ogoacutele tegotematu Dzięki niemu zrozumiesz jak komputer radzi sobie z przecinkiem oraz dlaczego niektoacutere ob-liczenia dają niezbyt dokładne wyniki Na początek trochę teorii do przechowywania liczb rzeczywi-stych przeznaczone są typy float double oraz long double Zajmują one odpowiednio oraz bitoacutew Wiemy też że komputer nie ma fizycznej możliwości zapisania przecinka Sproacutebujmy terazzapisać jakąś liczbę wymierną w formie liczb binarnych Nasza liczba to powiedzmy 425 Sproacutebujmy jąrozbić na sumę potęg dwoacutejki 4 = 1 middot22 +0 middot21 +0 middot20 Dobra mdash rozpisaliśmy liczbę 4 ale co z częściądziesiętną Skorzystajmy z zasad matematyki 025 = 2minus2 Zatem nasza liczba powinna wyglądaćtak

10001Ponieważ komputer nie jest w stanie przechować pozycji przecinka ktoś wpadł na prosty ale

sprytny pomysł ustawienia przecinka jak najbliżej początku liczby i tylko mnożenia jej przez odpowied-nią potęgę dwoacutejki Taki sposoacuteb przechowywania liczb nazywamy zmiennoprzecinkowym a procesprzekształcania naszej liczby z postaci czytelnej przez człowieka na format zmiennoprzecinkowy na-zywamy normalizacją Wroacutećmy do naszej liczby mdash 425 W postaci binarnej wygląda ona tak 10001natomiast po normalizacji będzie wyglądała tak 10001 middot 22 W ten sposoacuteb w pamięci komputeraznajdą się dwie informacje liczba zakodowana w pamięci z ldquowirtualnymrdquo przecinkiem oraz numerpotęgi dwoacutejki Te dwie informacje wystarczają do przechowania wartości liczby Jednak pojawia sięinny problem mdash co się stanie jeśli np będziemy chcieli przełożyć liczbę typu 1

3 Otoacuteż tutaj wychodzą

na wierzch pewne niedociągnięcia komputera w dziedzinie samej matematyki daje w rozwinięciudziesiętnym 0(3) Jak zatem zapisać taką liczbę Otoacuteż nie możemy przechować całego jej rozwinięcia(wynika to z ograniczeń typu danych mdash ma on niestety skończoną liczbę bitoacutew) Dlatego przechowujesię tylko pewne przybliżenie liczby Jest ono tym bardziej dokładne im dany typ ma więcej bitoacutew Za-tem do obliczeń wymagających dokładnych danych powinniśmy użyć typu double lub long double Naszczęście w większości przeciętnych programoacutew tego typu problemy zwykle nie występują A ponie-waż początkujący programista nie odpowiada za tworzenie programoacutew sterujących np lotem statkukosmicznego więc drobne przekłamania na odległych miejscach po przecinku nie stanowią większegoproblemu

Należy brać pod uwagę że w komputerze liczby rzeczywiste nie są tym samym czym w mate-matyce Komputery nie potrafią przechować każdej liczby zmiennoprzecinkowej w związku z tymobliczenia prowadzone przy użyciu komputera mogą być niedokładne i odbiegać od prawidłowych wy-nikoacutew Szczegoacutelnie ważne jest to przy programowaniu aplikacji inżynieryjnych oraz w medycyniegdzie takie błędy mogą skutkować katastrofą ilub narażeniem ludzkiego życia oraz zdrowia

Na ile poważny jest to problem Sproacutebujmy przyjrzeć się działaniu polegającym na -krotnymdodawaniu do liczby wartości Oto kod

include ltstdiohgt

int main ()

float a = 0int i = 0for (ilt1000i++)

a += 1030printf (fn a)

Z matematyki wynika że 1000 middot 13

= 333(3) podczas gdy komputer wypisze wynik nieco roacuteżniącysię od oczekiwanego (w moim przypadku)

223 LICZBY ZESPOLONE 163

333334106

Błąd pojawił się na cyfrze części tysięcznej liczby Nie jest to może poważny błąd jednak zastanoacutewmysię czy ten błąd nie będzie się powiększał Zamieniamy w kodzie ilość iteracji z na Tymrazem moacutej komputer wskazał już nieco inny wynik

33356554688

Błąd przesunął się na cyfrę dziesiątek w liczbie Tak więc nie należy do końca polegać na prezentacjiliczb zmiennoprzecinkowych w komputerze

223 Liczby zespoloneOperacje na liczba zespolony są częścią uaktualnionego standardu języka C o nazwie C ktoacuteryjest obsługiwany jedynie przez część kompilatoroacutew

Podane tutaj informacje zostały sprawdzone na systemie Gentoo Linux z biblioteką GNU libc wwersji i kompilatorem GCC w wersji

Dotychczas korzystaliśmy tylko z liczb rzeczywistych lecz najnowsze standardy języka C umożli-wiają korzystanie także z innych liczb mdash np z liczb zespolonych

Abymoacutec korzystać z liczb zespolonychwnaszymprogramie należywnagłoacutewku programu umieścićnastępującą linijkę

include ltcomplexhgt

Wiemy że liczba zespolona zdeklarowana jest następująco

z = a+bi gdzie a b są liczbami rzeczywistymi a ii=(-1)

W pliku complexh liczba i zdefiniowana jest jako I Zatem wyproacutebujmy możliwości liczb zespolo-nych

include ltmathhgtinclude ltcomplexhgtinclude ltstdiohgt

int main ()

float _Complex z = 4+25Iprintf (Liczba z f+fin creal(z) cimag (z))return 0

następnie kompilujemy nasz program

gcc plik1c -o plik1 -lm

Po wykonaniu naszego programu powinniśmy otrzymać

Liczba z 400+250i

W programie zamieszczonym powyżej użyliśmy dwoacutech funkcji mdash creal i cimag

creal mdash zwraca część rzeczywistą liczby zespolonej

cimag mdash zwraca część urojoną liczby zespolonej

164 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

Rozdział 23

Powszene praktyki

Rozdział tenma za zadanie pokazać powszechnie stosowanemetody programowania wC Nie będziemytu uczyć jak należy stawiać nawiasy klamrowe ani ktoacutery sposoacuteb nazewnictwa zmiennych jest najlep-szy mdash prowadzone są o to spory z ktoacuterych niewiele wynika Zaprezentowane tu rozwiązania mająkonkretny wpływ na jakość tworzonych programoacutew

231 Konstruktory i destruktoryW większości obiektowych językoacutew programowania obiekty nie mogą być tworzone bezpośrednio mdashobiekty otrzymuje się wywołując specjalną metodę danej klasy zwaną konstruktorem Konstruktorysą ważne ponieważ pozwalają zapewnić obiektowi odpowiedni stan początkowy Destruktory wywo-ływane na końcu czasu życia obiektu są istotne gdy obiekt ma wyłączny dostęp do pewnych zasoboacutewi konieczne jest upewnienie się czy te zasoby zostaną zwolnione

Ponieważ C nie jest językiem obiektowym nie ma wbudowanego wsparcia dla konstruktoroacutew idestruktoroacutew Często programiści bezpośrednio modyfikują tworzone obiekty i struktury Jednakżeprowadzi to do potencjalnych błędoacutew ponieważ operacje na obiekcie mogą się nie powieść lub zacho-wać się nieprzewidywalnie jeśli obiekt nie został prawidłowo zainicjalizowany Lepszym podejściemjest stworzenie funkcji ktoacutera tworzy instancję obiektu ewentualnie przyjmując pewne parametry

struct string size_t sizechar data

struct string create_string(const char initial) assert (initial = NULL)struct string new_string = malloc(sizeof(new_string))if (new_string = NULL)

new_string-gtsize = strlen(initial)new_string-gtdata = strdup(initial)

return new_string

Podobnie bezpośrednie usuwanie obiektoacutew może nie do końca się udać prowadząc do wyciekuzasoboacutew Lepiej jest użyć destruktora

void free_string(struct string s)

165

166 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

assert (s = NULL)free(s-gtdata) zwalniamy pamięć zajmowaną przez strukturę free(s) usuwamy samą strukturę

Często łączy się destruktory z zerowaniem zwolnionych wskaźnikoacutewCzasami dobrze jest ukryć definicję obiektu żeby mieć pewność że użytkownicy nie utworzą go

ręcznie Aby to zapewnić struktura jest definiowanaw pliku źroacutedłowym (lub prywatnymnagłoacutewku nie-dostępnym dla użytkownikoacutew) zamiast w pliku nagłoacutewkowym a deklaracja wyprzedzająca jest umiesz-czona w pliku nagłoacutewkowym

struct stringstruct string create_string(const char initial)void free_string(struct string s)

232 Zerowanie zwolniony wskaźnikoacutewJak powiedziano już wcześniej po wywołaniu free() dla wskaźnika staje się on ldquowiszącym wskaź-nikiemrdquo Co gorsze większość nowoczesnych platform nie potrafi wykryć kiedy taki wskaźnik jestużywany zanim zostanie ponownie przypisany

Jednym z prostych rozwiązań tego problemu jest zapewnienie że każdy wskaźnik jest zerowanynatychmiast po zwolnieniu

free(p)p = NULL

Inaczej niż w przypadku ldquowiszących wskaźnikoacutewrdquo na wielu nowoczesnych architekturach przyproacutebie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek Dodatkowo programy mogązawierać sprawdzanie błędoacutew dla zerowych wartości ale nie dla ldquowiszących wskaźnikoacutewrdquo Aby zapew-nić że jest to wykonywane dla każdego wskaźnika możemy użyć makra

define FREE(p) do free(p) (p) = NULL while(0)

(aby zobaczyć dlaczego makro jest napisane w ten sposoacuteb zobacz Konwencje pisania makr)Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik ktoacutery przekazuje się do

nich więc argument musi być do nich przekazywany przez referencję Na przykład oto zaktualizowanydestruktor z sekcji Konstruktory i destruktory

void free_string(struct string s)

assert(s = NULL ampamp s = NULL)FREE((s)-gtdata) zwalniamy pamięć zajmowaną przez strukturę FREE(s) usuwamy strukturę

Niestety ten idiom nie jest wstanie pomoacutec wwypadkuwskazywania przez inne wskaźniki zwolnio-nej pamięci Z tego powodu niektoacuterzy eksperci C uważają go za niebezpieczny jako kreujący fałszywepoczucie bezpieczeństwa

233 Konwencje pisania makrPonieważ makra preprocesora działają na zasadzie zwykłego zastępowania napisoacutew są podatne nawiele kłopotliwych błędoacutew z ktoacuterych części można uniknąć przez stosowanie się do poniższych reguł

234 JAK DOSTAĆ SIĘ DO KONKRETNEGO BITU 167

Umieszczaj nawiasy dookoła argumentoacutew makra kiedy to tylko możliwe Zapewnia to że gdysą wyrażeniami kolejność działań nie zostanie zmieniona Na przykład

Źle define kwadrat(x) (xx)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Załoacuteżmy że w programie makro kwadrat() zdefiniowane bez nawiasoacutew zostałowywołane następująco kwadrat(a+b) Wtedy zostanie ono zamienione przez preprocesorna (a+ba+b) Z kolejności działań wiemy że najpierw zostanie wykonane mnożeniewięc wartość wyrażenia kwadrat(a+b) będzie roacuteżna od kwadratu wyrażenia a+b

Umieszczaj nawiasy dookoła całegomakra jeśli jest pojedynczymwyrażeniem Ponownie chronito przed zaburzeniem kolejności działań

Źle define kwadrat(x) (x)(x)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Definiujemy makro define suma(a b) (a)+(b) i wywołujemy je w kodziewynik = suma(3 4) 5 Makro zostanie rozwinięte jako wynik = (3)+(4)5 co mdash zpowodu kolejności działań mdash da wynik inny niż pożądany

Jeśli makro składa się z wielu instrukcji lub deklaruje zmienne powinno być umieszczone w pętlido while(0) bez kończącego średnika Pozwala to na użycie makra jak pojedynczejinstrukcji w każdym miejscu jak ciało innego wyrażenia pozwalając jednocześnie na umiesz-czenie średnika po makrze bez tworzenia zerowego wyrażenia Należy uważać by zmienne wmakrze potencjalnie nie kolidowały z argumentami makra

Źle define FREE(p) free(p) p = NULL

Dobrze define FREE(p) do free(p) p = NULL while(0)

Unikaj używania argumentoacutew makra więcej niż raz wewnątrz makra Może to spowodowaćkłopoty gdy argument makra ma efekty uboczne (np zawiera operator inkrementacji)

Przykład define kwadrat(x) ((x)(x)) nie powinno być wywoływane z operatoreminkrementacji kwadrat(a++) ponieważ zostanie to rozwinięte jako ((a++) (a++)) co jestniezgodne ze specyfikacją języka i zachowanie takiego wyrażenia jest niezdefiniowane(dwukrotna inkrementacja w tym samym wyrażeniu)

Jeśli makro może być w przyszłości zastąpione przez funkcję rozważ użycie w nazwie małychliter jak w funkcji

234 Jak dostać się do konkretnego bituWiemy że komputer to maszyna ktoacuterej najmniejszą jednostką pamięci jest bit jednak w C najmniejszazmienna ma rozmiar bitoacutew (czyli jednego bajtu) Jak zatem można odczytać wartość pojedynczychbitoacutew W bardzo prosty sposoacuteb mdashw zestawie operatoroacutew języka C znajdują się tzw operatory bitoweSą to m in

amp mdash logiczne ldquoirdquo

| mdash logiczne ldquolubrdquo

˜ mdash logiczne ldquonierdquo

Oproacutecz tego są także przesunięcia (ltlt oraz gtgt) Zastanoacutewmy się teraz jak je wykorzystać w prak-tyce Załoacuteżmy że zajmujemy się jednobajtową zmienną

unsigned char i = 2

Zmatematyki wiemy że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej) Jeśli teraz np chcielibyśmy ldquozapalićrdquo drugi bit od lewej (tj bit ktoacuterego zapalenie niejako ldquododardquo doliczby wartość 6) powinniśmy użyć logicznego lub

168 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

unsigned char i = 2i |= 64

Gdzie =6 Odczytywanie wykonuje się za pomocą tzw maski bitowej Polega to na

wyzerowaniu bitoacutew ktoacutere są nam w danej chwili niepotrzebne

odpowiedniemu przesunięciu bitoacutew dzięki czemu szukany bit znajdzie się na pozycji pierwszegobitu od prawej

Do ldquowyłuskaniardquo odpowiedniego bitu możemy posłużyć się operacją ldquoirdquo mdash czyli operatorem ampWygląda to analogicznie do posługiwania się operatorem ldquolubrdquo

unsigned char i = 3 bitowo 00000011 unsigned char temp = 0temp = i amp 1 sprawdzamy najmniej znaczący bit - czyli pierwszy z prawej if (temp)

printf (bit zapalony)else

printf (bit zgaszony)

Jeśli nie władasz biegle kodem binarnym tworzenie masek bitowych ułatwią ci przesunięcia bitoweAby uzyskać liczbę ktoacutera ma zapalony bit o numerze n (bity są liczone od zera) przesuwamy bitowo wlewo jedynkę o n pozycji

1 ltlt n

Jeśli chcemy uzyskać liczbę w ktoacuterej zapalone są bity na pozycjach l m n mdash używamy sumylogicznej (ldquolubrdquo)

(1 ltlt l) | (1 ltlt m) | (1 ltlt n)

Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone sąwszystkie bity poza n odwracamy ją za pomocąoperatora logicznej negacji

~(1 ltlt n)

Warto władać biegle operacjami na bitach ale początkujący mogą (po uprzednim przeanalizowa-niu) zdefiniować następujące makra i ich używać

Sprawdzenie czy w liczbie k jest zapalony bit n define IS_BIT_SET(k n) ((k) amp (1 ltlt (n)))

Zapalenie bitu n w zmiennej k define SET_BIT(k n) (k |= (1 ltlt (n)))

Zgaszenie bitu n w zmiennej k define RESET_BIT(k n) (k amp= ~(1 ltlt (n)))

235 Skroacutety notacjiIstnieją pewne sposoby ograniczenia ilości niepotrzebnego kodu Przykładem może być wykonywaniejednej operacji w razie wystąpienia jakiegoś warunku np zamiast pisać

if (warunek) printf (Warunek prawdziwyn)

235 SKROacuteTY NOTACJI 169

możesz skroacutecić notację do

if (warunek)printf (Warunek prawdziwyn)

Podobnie jest w przypadku pętli for

for (warunek)printf (Wyświetlam się w pętlin)

Niestety ograniczeniemw tymwypadku jest to że można w ten sposoacuteb zapisać tylko jedną instruk-cję

170 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

Rozdział 24

Przenośność programoacutew

Jak dowiedziałeś się z poprzednich rozdziałoacutew tego podręcznika język C umożliwia tworzenie progra-moacutew ktoacutere mogą być uruchamiane na roacuteżnych platformach sprzętowych pod warunkiem ich powtoacuter-nej kompilacji Język C należy do grupy językoacutew wysokiego poziomu ktoacutere tłumaczone są do poziomukodumaszynowego (tzn kod źroacutedłowy jest kompilowany) Z jednej strony jest to korzystne posunięciegdyż programy są szybsze i mniejsze niż programy napisane w językach interpretowanych (takich wktoacuterych kod źroacutedłowy nie jest kompilowany do kodu maszynowego tylko na bieżąco interpretowanyprzez tzw interpreter) Jednak istnieje także druga strona medalu mdash pewne zawiłości sprzętu ktoacutereograniczają przenośność programoacutew Ten rozdział ma wyjaśnić Ci mechanizmy działania sprzętu wtaki sposoacuteb abyś bez problemu moacutegł tworzyć poprawne i całkowicie przenośne programy

241 Niezdefiniowane zaowanie i zaowanie zależne odimplementacji

Wtrakcie czytania kolejnych rozdziałoacutewmożna było się natknąć na zwroty takie jak zachowanie niezde-finiowane (ang undefined behaviour) czy zachowanie zależne od implementacji (ang implementation-defined behaviour) Coacuteż one tak właściwie oznaczają

Zacznijmy od tego drugiego Autorzy standardu języka C czuli że wymuszanie jakiegoś konkret-nego działania danego wyrażenia byłoby zbytnim obciążeniem dla osoacuteb piszących kompilatory gdyżdany wymoacuteg moacutegłby być bardzo trudny do zrealizowania na konkretnej architekturze Dla przykładugdyby standard wymagał że typ unsigned char ma dokładnie bitoacutew to napisanie kompilatora dla ar-chitektury na ktoacuterej bajt ma bitoacutew byłoby cokolwiek kłopotliwe a z pewnością wynikowy programdziałałby o wiele wolniej niżby to było możliwe

Z tego właśnie powodu niektoacutere aspekty języka nie są określone bezpośrednio w standardzie i sąpozostawione do decyzji zespołu (osoby) piszącego konkretną implementację W ten sposoacuteb nie mażadnych przeciwwskazań (ze strony standardu) aby na architekturze gdzie bajty mają bitoacutew typchar roacutewnież miał tyle bitoacutew Dokonany wyboacuter musi być jednak opisany w dokumentacji kompilatoratak żeby osoba pisząca program w C mogła sprawdzić jak dana konstrukcja zadziała

Należy zatem pamiętać że poleganie na jakimś konkretnym działaniu programu w przypadkachzachowania zależnego od implementacji drastycznie zmniejsza przenośność kodu źroacutedłowego

Zachowania niezdefiniowane są o wiele groźniejsze gdyż zaistnienie takowego może spowodo-wać dowolny efekt ktoacutery nie musi być nigdzie udokumentowany Przykładem może tutaj być proacutebaodwołania się do wartości wskazywanej przez wskaźnik o wartości

Jeżeli gdzieś w naszym programie zaistnieje sytuacja niezdefiniowanego zachowania to nie jest jużto kwestia przenośności kodu ale po prostu błędu w kodzie chyba że świadomie korzystamy z roz-szerzenia naszego kompilatora Rozważmy odwoływanie się do wartości wskazywanej przez wskaźniko wartości Ponieważ według standardu operacja taka ma niezdefiniowany skutek to w szcze-goacutelności może wywołać jakąś z goacutery określoną funkcję mdash kompilator może coś takiego zrealizować

171

172 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

sprawdzając wartość wskaźnika przed każdą dereferencją w ten sposoacuteb niezdefiniowane zachowaniedla konkretnego kompilatora stanie się jak najbardziej zdefiniowane

Sytuacją wziętą z życia są operatory przesunięć bitowych gdy działają na liczbach ze znakiemKonkretnie przesuwanie w lewo liczb jest dla wielu przypadkoacutew niezdefiniowane Bardzo często jed-nak w dokumentacji kompilatora działanie przesunięć bitowych jest dokładnie opisane Jest to o tyleinteresujący fakt iż wielu programistoacutew nie zdaje sobie z niego sprawy i nieświadomie korzysta z roz-szerzeń kompilatora

Istnieje jeszcze trzecia klasa zachowań Zachowania nieokreślone (ang unspecified behaviour)Są to sytuacje gdy standard określa kilka możliwych sposoboacutew w jaki dane wyrażenie może działaći pozostawia kompilatorowi decyzję co z tym dalej zrobić Coś takiego nie musi być nigdzie opisanew dokumentacji i znowu poleganie na konkretnym zachowaniu jest błędem Klasycznym przykłademmoże być kolejność obliczania argumentoacutew wywołania funkcji

242 Rozmiar zmiennyRozmiar poszczegoacutelnych typoacutew danych (np char int czy long) jest roacuteżna na roacuteżnych platformachgdyż nie jest definiowany w sztywny sposoacuteb jak np ldquolong int zawsze powinien mieć bityrdquo (takieokreślenie wiązałoby się z wyżej opisanymi trudnościami) lecz w na zasadzie zależności typu ldquolongpowinien być nie kroacutetszy niż intrdquo ldquoshort nie powinien być dłuższy od intrdquo Pierwsza standaryzacjajęzyka C zakładała że typ int będzie miał taki rozmiar jak domyślna długość liczb całkowitych nadanym komputerze natomiast modyfikatory short oraz long zmieniały długość tego typu tylko wtedygdy dana maszyna obsługiwała typy o mniejszej lub większej długości1

Z tego powodu nigdy nie zakładaj że dany typ będzie miał określony rozmiar Jeżeli potrzebujesztypu o konkretnym rozmiarze (a dokładnej konkretnej liczbie bitoacutew wartości) możesz skorzystać z plikunagłoacutewkowego stdinth wprowadzonego do języka przez standard ISO C z roku Definiuje on typyint t int t int t int t uint t uint t uint t i uint t (o ile w danej architekturze występujątypy o konkretnej liczbie bitoacutew)

Jednak możemy posiadać implementację ktoacutera nie posiada tego pliku nagłoacutewkowego W takiej sy-tuacji nie pozostaje nam nic innego jak tworzyć własny plik nagłoacutewkowy w ktoacuterym za pomocą słoacutewkatypedef sami zdefiniujemy potrzebne nam typy Np

typedef unsigned char u8typedef signed char s8typedef unsigned short u16typedef signed short s16typedef unsigned long u32typedef signed long s32typedef unsigned long long u64typedef signed long long s64

Aczkolwiek należy pamiętać że taki plik będzie trzeba pisać od nowa dla każdej architektury najakiej chcemy kompilować nasz program

243 Porządek bajtoacutew i bitoacutew

2431 Bajty i słowaWiesz zapewne że podstawową jednostką danych jest bit ktoacutery może mieć wartość lub Kilkakolejnych bitoacutew2 stanowi bajt (dla skupienia uwagi przyjmijmy że bajt składa się z bitoacutew) Częstotyp short ma wielkość dwoacutech bajtoacutew i woacutewczas pojawia się pytanie w jaki sposoacuteb są one zapisane

1Dokładniejszy opis rozmiaroacutew dostępny jest w rozdziale Składnia2Standard wymaga aby było ich co najmniej 8 i liczba bitoacutew w bajcie w konkretnej implementacji jest określona

przez makro CHAR BIT zdefiniowane w pliku nagłoacutewkowym limitsh

243 PORZĄDEK BAJTOacuteW I BITOacuteW 173

w pamięci mdash czy najpierw ten bardziej znaczący mdash big-endian czy najpierw ten mniej znaczący mdashlittle-endian

Skąd takie nazwy Otoacuteż pochodzą one z książki Podroacuteże Guliwera w ktoacuterej liliputy kłoacuteciły się ostronę od ktoacuterej należy rozbijać jajko na twardo Jedni uważali że trzeba je rozbijać od grubszegokońca (big-endian) a drudzy że od cieńszego (lile-endian) Nazwy te są o tyle trafne że w wypadkuprocesoroacutew wyboacuter kolejności bajtoacutew jest sprawą czysto polityczną ktoacutera jest technicznie neutralna

Sprawa się jeszcze bardziej komplikuje w przypadku typoacutew ktoacutere składają się np z bajtoacutew Woacutew-czas są aż ( silnia) sposoby zapisania kolejnych fragmentoacutew takiego typu W praktyce zapewne spo-tkasz się jedynie z kolejnościami big-endian lub lile-endian co nie zmienia faktu że inne możliwościtakże istnieją i przy pisaniu programoacutew ktoacutere mają być przenośne należy to brać pod uwagę

Poniższy przykład dobrze obrazuje oba sposoby przechowywania zawartości zmiennych w pamięcikomputera (przyjmujemy CHAR BIT == oraz sizeof(long) == bez bitoacutew wypełnienia (ang paddingbits)) unsigned long zmienna = 0x01020304 w pamięci komputera będzie przechowywana tak

adres | 0 | 1 | 2 | 3 |big-endian |0x01|0x02|0x03|0x04|little-endian |0x04|0x03|0x02|0x01|

2432 Konwersja z jednego porządku do innegoCzasami zdarza się że napisany przez nas program musi się komunikować z innym programem (możeteż przez nas napisanym) ktoacutery działa na komputerze o (potencjalnie) innym porządku bajtoacutew Częstonajprościej jest przesyłać liczby jako tekst gdyż jest on niezależny od innych czynnikoacutew jednak takiformat zajmuje więcej miejsca a nie zawsze możemy sobie pozwolić na taką rozrzutność

Przykładem może być komunikacja sieciowa w ktoacuterej przyjęło się że dane przesyłane są w po-rządku big-endian Aby moacutec łatwo operować na takich danych w standardzie zdefiniowanonastępujące funkcje (w zasadzie zazwyczaj są to makra)

include ltarpainethgtuint32_t htonl(uint32_t hostlong)uint16_t htons(uint16_t hostshort)uint32_t ntohl(uint32_t netlong)uint16_t ntohs(uint16_t netshort)

Pierwsze dwie konwertują liczbę z reprezentacji lokalnej na reprezentację big-endian (host to ne-twork) natomiast kolejne dwie dokonują konwersji w drugą stronę (network to host)

Można roacutewnież skorzystać z pliku nagłoacutewkowego endianh w ktoacuterym definiowane są makra po-zwalające określić porządek bajtoacutew

include ltendianhgtinclude ltstdiohgt

int main() if __BYTE_ORDER == __BIG_ENDIAN

printf(Porządek big-endian (4321)n)elif __BYTE_ORDER == __LITTLE_ENDIAN

printf(Porządek little-endian (1234)n)elif defined __PDP_ENDIAN ampamp __BYTE_ORDER == __PDP_ENDIAN

printf(Porządek PDP (3412)n)else

printf(Inny porządek (d)n __BYTE_ORDER)endif

return 0

174 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

Na podstawie makra BYTE ORDER można skonstruować funkcję ktoacutera będzie konwertowaćliczby pomiędzy porządkiem roacuteżnymi porządkami

include ltendianhgtinclude ltstdiohgtinclude ltstdinthgt

uint32_t convert_order32(uint32_t val unsigned from unsigned to) if (from==to)

return val else

uint32_t ret = 0unsigned char tmp[5] = 0 0 0 0 0 unsigned char ptr = (unsigned char)ampvalunsigned div = 1000do tmp[from div 10] = ptr++ while ((div = 10))ptr = (unsigned char)ampretdiv = 1000do ptr++ = tmp[to div 10] while ((div = 10))return ret

define LE_TO_H(val) convert_order32((val) 1234 __BYTE_ORDER)define H_TO_LE(val) convert_order32((val) __BYTE_ORDER 1234)define BE_TO_H(val) convert_order32((val) 4321 __BYTE_ORDER)define H_TO_BE(val) convert_order32((val) __BYTE_ORDER 4321)define PDP_TO_H(val) convert_order32((val) 3412 __BYTE_ORDER)define H_TO_PDP(val) convert_order32((val) __BYTE_ORDER 3412)

int main ()

printf(08xn LE_TO_H(0x01020304))printf(08xn H_TO_LE(0x01020304))printf(08xn BE_TO_H(0x01020304))printf(08xn H_TO_BE(0x01020304))printf(08xn PDP_TO_H(0x01020304))printf(08xn H_TO_PDP(0x01020304))return 0

Ciągle jednak polegamy na niestandardowym pliku nagłoacutewkowym endianh Można go wyelimi-nować sprawdzając porządek bajtoacutew w czasie wykonywania programu

include ltstdiohgtinclude ltstdinthgt

int main() uint32_t val = 0x04030201unsigned char v = (unsigned char )ampvalint byte_order = v[0] 1000 + v[1] 100 + v[2] 10 + v[3]

if (byte_order == 4321) printf(Porządek big-endian (4321)n)

else if (byte_order == 1234)

244 BIBLIOTECZNE PROBLEMY 175

printf(Porządek little-endian (1234)n) else if (byte_order == 3412)

printf(Porządek PDP (3412)n) else

printf(Inny porządek (d)n byte_order)return 0

Powyższe przykłady opisują jedynie część problemoacutew jakie mogą wynikać z proacuteby przenoszeniabinarnych danych pomiędzy wieloma platformami Wszystkie co więcej zakładają że bajt ma bitoacutewco wcale nie musi być prawdą dla konkretnej architektury na ktoacuterą piszemy aplikację Co więcej liczbymogą posiadać w swojej reprezentacje bity wypełnienia (ang padding bits) ktoacutere nie biorą udziaływ przechowywaniu wartości liczby Te wszystkie roacuteżnice mogą dodatkowo skomplikować kod Toteżnależy być świadomym iż przenosząc dane binarnie musimy uważać na roacuteżne reprezentacje liczb

244 Biblioteczne problemy

2441 Dostępność bibliotekPisząc programy nieraz będziemy musieli korzystać z roacuteżnych bibliotek Problem polega na tym żenie zawsze będą one dostępne na komputerze na ktoacuterym inny użytkownik naszego programu będzieproacutebował go kompilować Dlatego też ważne jest abyśmy korzystali z łatwo dostępnych bibliotek ktoacuteredostępne są na wiele roacuteżnych systemoacutew i platform sprzętowych Zapamiętaj Twoacutej program jest natyle przenośny na ile przenośne są biblioteki z ktoacuterych korzysta

2442 Odmiany bibliotekPod Windows funkcje atan floor i fabs są w tej samej bibliotece co standardowe funkcje C

Pod Uniksami są w osobnej bibliotece matematycznej libm w wersji

statycznej (zwykle usrliblibma) i pliku nagłoacutewkowym mathh (zwykle usrincludemathh)3

ladowanej dynamicznie ( usrliblibmso )

Aby korzystać z tych funkcji potrzebujemy

dodać include ltmathhgt

przy kompilacji dołączyć bibliotekę libm gcc mainc -lm

Opcja -lm używa libmso albo libma w zależności od tego ktoacutere są znalezione i w zależności odobecności opcji -static45

245 Kompilacja warunkowaPrzy zwiększaniu przenośności kodu może pomoacutec preprocessor Przyjmijmy np że chcemy korzy-stać ze słoacutewka kluczowego inline wprowadzonego w standardzie C ale roacutewnocześnie chcemy abynasz program był rozumiany przez kompilatory ANSI CWoacutewczas możemy skorzystać z następującegokodu

ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline

3An Introduction to mdashfor the compilers gcc and g++ 27 Linking with external libraries4man ld5Dyskusja na grupie plcomposlinuxprogramowanie na temat c gc atan2 floor fabs

176 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

else define __inline__ endifendif

a w kodzie programu zamiast słoacutewka inline stosować inline Co więcej kompilator rozumiesłoacutewka kluczowe tak tworzone i w jego przypadku warto nie redefiniować ich wartości

ifndef __GNUC__ ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline else define __inline__ endif endifendif

Korzystając z kompilacji warunkowej można także korzystać z roacuteżnego kodu zależnie od (np) sys-temu operacyjnego Przykładowo przed kompilacją na konkretnej platformie tworzymy odpowiedniplik configh ktoacutery następnie dołączamy do wszystkich plikoacutew źroacutedłowych w ktoacuterych podejmujemydecyzje na podstawie zdefiniowanych makr Dla przykładu plik configh

ifndef CONFIG_Hdefine CONFIG_H

Uncomment if using Windows define USE_WINDOWS

Uncomment if using Linux define USE_LINUX

error You must edit configh fileerror Edit it and remove those error lines

endif

Jakiś plik źroacutedłowy

include configh

ifdef USE_WINDOWSrob_cos_wersja_dla_windows()

elserob_cos_wersja_dla_linux()

endif

Istnieją roacuteżne narzędzia ktoacutere pozwalają na automatyczne tworzenie takich plikoacutew configh dziękiczemu użytkownik przed skompilowaniem programu nie musi się trudzić i edytować ich ręcznie ajedynie uruchomić odpowiednie polecenie Przykładem jest zestaw autoconf i automake

Rozdział 25

Łączenie z innymi językami

Programista pisząc jakiś program ma problem z wyborem najbardziej odpowiedniego języka do utwo-rzenia tego programu Niekiedy zdarza się że najlepiej byłoby pisać program korzystając z roacuteżnychjęzykoacutew Język C może być z łatwością łączony z innymi językami programowania ktoacutere podlegająkompilacji bezpośrednio do kodu maszynowego (Asembler Fortran czy też C++) Ponadto dzięki spe-cjalnym bibliotekom można go łączyć z językami bardzo wysokiego poziomu (takimi jak np Pythonczy też Ruby) Ten rozdział ma za zadanie wytłumaczyć Ci w jaki sposoacuteb można mieszać roacuteżne językiprogramowania w jednym programie

251 Język C i Asembler

Informacje zawarte w tym rozdziale odnoszą się do komputeroacutew z procesorem i i pokrewnych

Łączenie języka C i języka asemblera jest dość powszechnym zjawiskiem Dzięki możliwości połączeniaobu tych językoacutew programowania można było utworzyć bibliotekę dla języka C ktoacutera niskopoziomowokomunikuje się z jądrem systemu operacyjnego komputera Ponieważ zaroacutewno asembler jak i C sąjęzykami tłumaczonymi do poziomu kodu maszynowego za ich łączenie odpowiada program zwanylinkerem (popularny ld) Ponadto niektoacuterzy producenci kompilatoroacutew umożliwiają stosowanie tzwwstawek asemblerowy ktoacutere umieszcza się bezpośrednio w kodzie programu napisanego w językuC Kompilator kompilując taki kod wstawi w miejsce tychże wstawek odpowiedni kod maszynowyktoacutery jest efektem przetłumaczenia kodu asemblera zawartegow takiej wstawce Opiszę tu oba sposobyłączenia obydwu językoacutew

2511 Łączenie na poziomie kodu maszynowegoW naszym przykładzie założymy że w pliku fS zawarty będzie kod napisany w asemblerze a fcto kod z programem w języku C Program w języku C będzie wykorzystywał jedną funkcję napisanąw języku asemblera ktoacutera wyświetli prosty napis ldquoHello worldrdquo Z powodu ograniczeń technicznychzakładamy że program uruchomiony zostanie w środowisku POSIX na platformie i i skompilowanykompilatorem gcc Używaną składnią asemblera będzie ATampT (domyślna dla asemblera ) Oto plikfS

text

globl _f1_f1

pushl ebpmovl esp ebp

177

178 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

movl $4 eax 4 to funkcja systemowa write movl $1 ebx 1 to stdout movl $tekst ecx adres naszego napisu movl $len edx długość napisu w bajtach int $0x80 wywołanie przerwania systemowego popl ebpret

datatekst

string Hello worldnlen = - tekst

W systemach z rodziny UNIX należy pominąć znak rdquo rdquoprzed nazwą funkcji f

Teraz kolej na fc

extern void f1 (void) musimy użyć słowa extern int main ()

f1()return 0

Teraz możemy skompilować oba programy

as f1S -o f1ogcc f2c -c -o f2ogcc f2o f1o -o program

W ten sposoacuteb uzyskujemy plik wykonywalny o nazwie ldquoprogramrdquo Efekt działania programu powinienbyć następujący

Hello world

Na razie utworzyliśmy bardzo prostą funkcję ktoacutera w zasadzie nie komunikuje się z językiem Cczyli nie zwraca żadnej wartości ani nie pobiera argumentoacutew Jednak aby zacząć pisać obsługę funk-cji ktoacutera będzie pobierała argumenty i zwracała wyniki musimy poznać działanie języka C od trochęniższego poziomu

Argumenty

Do komunikacji z funkcją język C korzysta ze stosu Argumenty odkładane sąw kolejności od ostatniegodo pierwszego Ponadto na końcu odkładany jest tzw adres powrotu dzięki czemu po wykonaniufunkcji program ldquowierdquo w ktoacuterym miejscu ma kontynuować działanie Ponadto początek funkcji wasemblerze wygląda tak

pushl ebpmovl esp ebp

Zatem na stosie znajdują się kolejno zawartość rejestru EBP adres powrotu a następnie argumenty odpierwszego do n-tego

251 JĘZYK C I ASEMBLER 179

Zwracanie wartości

Na architekturze i do zwracaniawynikoacutew pracy programu używa się rejestru EAX bądź jego ldquomniej-szychrdquo odpowiednikoacutew tj AX i AHAL Zatem aby funkcja napisana w asemblerze zwroacuteciła ldquordquo przedrozkazem ret należy napisać

movl $1 eax

Nazewnictwo

Kompilatory języka CC++ dodają podkreślnik ldquo rdquo na początku każdej nazwy Dla przykładu funkcja

void funkcja()

W pliku wyjściowym będzie posiadać nazwę funkcja Dlatego aby korzystać z poziomu języka C zfunkcji zakodowanych w asemblerze muszą one mieć przy definicji w pliku asemblera wspomnianydodatkowy podkreślnik na początku

Łączymy wszystko w całość

Pora abyśmy napisali jakąś funkcję ktoacutera pobierze argumenty i zwroacuteci jakiś konkretny wynik Otokod fS

text

globl _funkcja_funkcja

pushl ebpmovl esp ebpmovl 8(esp) eax kopiujemy pierwszy argument do eax addl 12(esp) eax do pierwszego argumentu w eax dodajemy drugi argument popl ebpret i zwracamy wynik dodawania

oraz fc

include ltstdiohgtextern int funkcja (int a int b)int main ()printf (2+3=dn funkcja(23))return 0

Po skompilowaniu i uruchomieniu programu powinniśmy otrzymać wydruk +=

2512 Wstawki asembleroweOproacutecz możliwości wstępnie skompilowanych modułoacutew możesz posłużyć się także tzw wstawkamiasemblerowymi Ich użycie powoduje wstawienie w miejsce wystąpienia wstawki odpowiedniegokodumaszynowego ktoacutery powstanie po przetłumaczeniu kodu asemblerowego Ponieważ jednakwstawkiasemblerowe nie są standardowym elementem języka C każdy kompilator ma całkowicie odmiennąfilozofię ich stosowania (lub nie ma ich w ogoacutele) Ponieważ w tym podręczniku używamy głoacutewniekompilatora więc w tym rozdziale zostanie omoacutewiona filozofia stosowania wstawek asemblerawedług programistoacutew

Ze wstawek asemblerowych korzysta się tak

180 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

int main ()

asm (nop)

W tym wypadku wstawiona zostanie instrukcja ldquonoprdquo (no operation) ktoacutera tak naprawdę służytylko i wyłącznie do konstruowania pętli opoacuteźniających

252 C++Język C++ z racji swojego podobieństwa do C będzie wyjątkowo łatwy do łączenia Pewnym utrudnie-niem może być obiektowość języka C++ oraz występowanie w nim przestrzeni nazw oraz możliwośćprzeciążania funkcji Oczywiście nadal zakładamy że głoacutewny program piszemy w C natomiast korzy-stamy tylko z pojedynczych funkcji napisanych w C++ Ponieważ język C nie oferuje tego wszystkiegoco daje programiście język C++ to musimy ldquozmusićrdquo C++ do wyłączenia pewnych swoich możliwościaby można było połączyć ze sobą elementy programu napisane w dwoacutech roacuteżnych językach Używa siędo tego następującej konstrukcji

extern C funkcje zmienne i wszystko to co będziemy łączyć z programem w C

W zrozumieniu teorii pomoże Ci prosty przykład plik fc

include ltstdiohgtextern int f2(int a)

int main ()

printf (dn f2(2))return 0

oraz plik fcpp

include ltiostreamgtusing namespace stdextern C

int f2 (int a)

cout ltlt a= ltlt a ltlt endlreturn a2

Teraz oba pliki kompilujemy

gcc f1c -c -o f1og++ f2cpp -c -o f2o

Przy łączeniu obu tych plikoacutew musimy pamiętać że język C++ także korzysta ze swojej bibliotekiZatem poprawna postać polecenia kompilacji powinna wyglądać

gcc f1o f2o -o program -lstdc++

(stdc++ mdash biblioteka standardowa języka C++) Bardzo istotne jest tutaj to abyśmy zawsze pamiętalio extern ldquoCrdquo gdyż w przeciwnym razie funkcje napisane w C++ będą dla programu w C całkowicieniewidoczne

Dodatek A

Indeks alfabetyczny

Alfabetyczny spis funkcji biblioteki standardowej ANSI C (tzw libc) w wersji C

A01 A abort()

abs()

acos()

asctime()

asin()

assert()

atan()

atan()

atexit()

atof()

atoi()

atol()

A02 B bsearch()

A03 C calloc()

ceil()

clearerr()

clock()

cos()

cosh()

ctime()

A04 D diime()

div()

A05 E errno (zmienna)

exit()

exp()

A06 F fabs()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

floor()

fmod()

fopen()

fprintf()

fputc()

fputs()

fread()

free()

freopen()

frexp()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

A07 G getc()

getchar()

getenv()

gets()

gmtime()

A08 I isalnum()

isalpha()

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

181

182 DODATEK A INDEKS ALFABETYCZNY

A09 L labs()

ldexp()

ldiv()

localeconv()

localtime()

log()

log()

longjmp()

A010 M malloc()

mblen()

mbstowcs()

mbtowc()

memchr()

memcmp()

memcpy()

memmove()

memset()

mktime()

modf()

A011 O offsetof()

A012 P perror()

pow()

printf()

putc()

putchar()

puts()

A013 Q qsort()

A014 R raise()

rand()

realloc()

remove()

rename()

rewind()

A015 S scanf()

setbuf()

setjmp()

setlocale()

setvbuf()

signal()

sin()

sinh()

sprintf()

sqrt()

srand()

sscanf()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strime()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtod()

strtok()

strtol()

strtoul()

strxfrm()

system()

A016 T tan()

tanh()

time()

tm (struktura)

tmpfile()

tmpnam()

tolower()

toupper()

A017 U ungetc()

A018 V va arg()

va end()

va start()

vfprintf()

vprintf()

vsprintf()

A019 W wcstombs()

wctomb()

Dodatek B

Indeks tematyczny

Spis plikoacutew nagłoacutewkowych oraz zawartych w nich funkcji i makr biblioteki standardowej C Funkcjemakra i typy wprowadzone dopiero w standardzie C zostały oznaczone poprzez ldquo[C]rdquo po nazwie

B1 asserthMakro asercji

assert()

B2 ctypehKlasyfikowanie znakoacutew

isalnum()

isalpha()

isblank() [C]

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

tolower()

toupper()

B3 errnohDeklaracje kodoacutew błędoacutew

EDOM (makro)

EILSEQ (makro) [C]

ERANGE (makro)

errno (zmienna)

B4 floathWłaściwości typoacutew zmiennoprzecinkowych zależne od implementacji

B5 limitshWłaściwości typoacutew całkowitych zależne od implementacji

183

184 DODATEK B INDEKS TEMATYCZNY

B6 localehUstawienia międzynarodowe

localeconv()

setlocale()

B7 mathhFunkcje matematyczne

FP FAST FMAF (makro) [C]

FP FAST FMAL (makro) [C]

FP FAST FMA (makro) [C]

FP ILOGB (makro) [C]

FP ILOGBNAN (makro) [C]

FP INFINITE (makro) [C]

FP NAN (makro) [C]

FP NORMAL (makro) [C]

FP SUBNORMAL (makro) [C]

FP ZERO (makro) [C]

HUGE VALF (makro) [C]

HUGE VALL (makro) [C]

HUGE VAL (makro)

INFINITY (makro) [C]

MATH ERREXCEPT (makro) [C]

MATH ERRNO (makro) [C]

NAN (makro) [C]

acosh()

acos()

asinh()

asin()

atan()

atanh()

atan()

cbrt() [C]

ceil()

copysign() [C]

cosh()

cos()

double t (typ) [C]

erfc() [C]

erf() [C]

exp() [C]

expm() [C]

exp()

fabs()

fdim() [C]

flaot t (typ) [C]

floor()

fmax() [C]

fma() [C]

fmin() [C]

fmod()

fpclassify() [C]

frexp()

hypot() [C]

ilogb() [C]

isfinite() [C]

isgreaterequal() [C]

isgreater() [C]

isinf() [C]

islessequal() [C]

islessgreater() [C]

isless() [C]

isnan() [C]

isnormal() [C]

isunordered() [C]

ldexp()

lgamma() [C]

llrint() [C]

llround() [C]

log()

logp() [C]

log() [C]

logb() [C]

log()

B8 SETJMPH 185

lrint() [C]

lround() [C]

math errhandling (makro) [C]

modf()

nan() [C]

nearbyint() [C]

nextaer() [C]

nexoward() [C]

pow()

remainder() [C]

remquo() [C]

rint() [C]

round() [C]

scalbln() [C]

scalbn() [C]

signbit() [C]

sinh()

sin()

sqrt()

tanh()

tan()

tgamma() [C]

trunc() [C]

B8 setjmphObsługa nielokalnych skokoacutew

longjmp()

setjmp()

B9 signalhObsługa sygnałoacutew

raise()

signal()

B10 stdarghNarzędzia dla funkcji ze zmienną liczbą argumentoacutew

va arg()

va end()

va start()

B11 stddefhStandardowe definicje

offsetof()

B12 stdiohStandard InputOutput czyli standardowe wejście-wyjście

clearerr()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

fopen()

186 DODATEK B INDEKS TEMATYCZNY

fprintf()

fputc()

fputs()

fread()

freopen()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

getc()

getchar()

gets()

perror()

printf()

putc()

putchar()

puts()

remove()

rename()

rewind()

scanf()

setbuf()

setvbuf()

sprintf()

sscanf()

tmpfile()

tmpnam()

ungetc()

vfprintf()

vprintf()

vsprintf()

B13 stdlibhNajbardziej podstawowe funkcje

abort()

abs()

atexit()

atof()

atoi()

atol()

bsearch()

calloc()

div()

exit()

free()

getenv()

labs()

ldiv()

malloc()

mblen()

mbstowcs()

mbtowc()

qsort()

rand()

realloc()

srand()

strtod()

strtol()

strtoul()

system()

wctomb()

wcstombs()

B14 stringhOperacje na łańcuchach znakoacutew

memchr()

memcmp()

memcpy()

memmove()

memset()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtok()

strxfrm()

strdup()

B15 timehFunkcje obsługi czasu

B15 TIMEH 187

asctime()

clock()

ctime()

diime()

gmtime()

localtime()

mktime()

strime()

time()

tm (struktura)

188 DODATEK B INDEKS TEMATYCZNY

Dodatek C

Wybrane funkcje bibliotekistandardowej

C1 assert

C11 Deklaracjadefine assert(expr)

C12 Plik nagłoacutewkowyasserth

C13 OpisMakro przypominające w użyciu funkcję służy do debuggowania programoacutew Gdy testowany waruneklogiczny expr przyjmuje wartość fałsz na standardowe wyjście błędoacutew wypisywany jest komunikat obłędzie (zawierające min argument wywołania makra nazwę funkcji w ktoacuterej zostało wywołanenazwę pliku źroacutedłowego oraz numer linii w formacie zależnym od implementacji) i program jest prze-rywany poprzez wywołanie funkcji abort

W ten sposoacuteb możemy oznaczyć w programie niezmienniki czyli warunki ktoacutere niezależnie odwartości zmiennych muszą pozostać prawdziwe Jeśli asercja zawiedzie oznacza to że popełniliśmybłąd w algorytmie piszemy sobie po pamięci (nadając zmiennym wartości ktoacuterych nigdy nie powinnymieć) albo nastąpiła po drodze sytuacja wyjątkowa na przykład związana z obsługą operacji wejścia-wyjścia

Można łatwo pozbyć się asercji uwalniając kod od spowalniających obciążeń a jednocześnie niemusząc kasować wystąpień assert i zachowując je na przyszłość Aby to zrobić należy przed dołą-czeniem pliku nagłoacutewkowego asserth zdefiniować makro NDEBUG woacutewczas makro assert przyjmujepostać

define assert(ignore) ((void)0)

Makro assert jest redefiniowane za każdym dołączeniem pliku nagłoacutewkowego asserth

C14 Wartość zwracanaMakro nie zwraca żadnej wartości

189

190 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C15 Przykładinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

Program wypisze komunikat podobny do

Assertion failed err==0 file testc line 6

Natomiast jeśli uruchomimy

define NDEBUGinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

nie pojawi się żaden komunikat o błędach

C2 atoi

C21 Deklaracjaint atoi (const char string)

C22 Plik nagłoacutewkowystdlibh

C23 OpisFunkcja jako argument pobiera liczbę w postaci ciągu znakoacutew ASCII a następnie zwraca jej wartość wformacie int Liczbę może poprzedzać dowolona ilość białych znakoacutew (spacje tabulatory itp) oraz jejznak (plus (+) lub minus (-)) Funkcja atoi() kończy wczytywać znaki w momencie napotkania jakiego-kowiek znaku ktoacutery nie jest cyfrą

C24 Wartość zwracanaW przypadku gdy ciąg nie zawiera cyfr zwracana jest wartość

C25 UwagiZnak musi bezpośrednio poprzedzać liczbę czyli możliwy jest zapis ldquo-rdquo natomiast proacuteba potraktowa-nia funkcją atoi ciągu ldquo- rdquo skutkuje zwracaną wartością

C3 ISALNUM 191

C26 Przykładinclude ltstdiohgtinclude ltstdlibhgtint main(void)

char c_Numer = nt 2004uint i_Numeri_Numer = atoi(c_Numer)printf(n Liczba typu int d oraz jako ciąg znakoacutew s n i_Numer c_Numer)return 0

C3 isalnum

C31 Deklaracjainclude ltctypehgt

int isalnum(int c)int isalpha(int c)int isblank(int c)int iscntrl(int c)int isdigit(int c)int isgraph(int c)int islower(int c)int isprint(int c)int ispuntc(int c)int isspace(int c)int isupper(int c)int isxdigit(int c)

C32 Argumentyc wartość znaku reprezentowana w jako typ unsigned char lub wartość makra EOF Z tego powodu

przed przekazaniem funkcji argumentu typu char lub signed char należy go zrzutować na typunsigned char lub unsigned int

C33 OpisFunkcje sprawdzają czy podany znak spełnia jakiś konkretny warunek Biorą pod uwagę ustawieniajęzyka i dla roacuteżnych znakoacutew w roacuteżnych localersquoach mogą zwracać roacuteżne wartości

isalnum sprawdza czy znak jest liczbą lub literą

isalpha sprawdza czy znak jest literą

isblank sprawdza czy znak jest znakiem odstępu służącym do oddzielania wyrazoacutew (standardowymiznakami odstępu są spacja i znak tabulacji)

iscntrl sprawdza czy znak jest znakiem sterującym

isdigit sprawdza czy znak jest cyfrą dziesiętna

isgraph sprawdza czy znak jest znakiem drukowalnym roacuteżnym od spacji

islower sprawdza czy znak jest małą literą

isprint sprawdza czy znak jest znakiem drukowalnym (włączając w to spację)

192 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

ispunct sprawdza czy znak jest znakiem przestankowym dla ktoacuterego ani isspace ani isalnum nie sąprawdziwe (standardowo są to wszystkie znaki drukowalne dla ktoacuterych te funkcje zwracajązero)

isspace sprawdza czy znak jest tzw białym znakiem (standardowymi białymi znakami są spacjawysunięcie strony rsquorsquo znak przejścia do nowej linii rsquonrsquo znak powrotu karetki rsquorrsquo tabulacjapozioma rsquotrsquo i tabulacja pionowa rsquovrsquo)

isupper sprawdza czy znak jest dużą literą

isxdigit sprawdza czy znak jest cyfrą szesnastkową tj cyfrą dziesiętną lub literą od rsquoarsquo do rsquorsquo niezależnieod wielkości

Funkcja isblank niewystępowaław oryginalnym standardzie ANSI C z roku (tzw C) i zostaładodana dopiero w nowszym standardzie z roku (tzw C)

C34 Wartość zwracanaLiczba niezerowa gdy podany argument spełnia konkretny warunek w przeciwnym wypadku mdash zero

C35 Przykład użyciainclude ltctypehgt funkcje is include ltlocalehgt setlocale include ltstdiohgt printf i scanf

void identify_char(int c) printf( Litera lub cyfra sn isalnum (c) tak nie)

if __STDC_VERSION__ gt= 199901Lprintf( Odstęp sn isblank (c) tak nie)

endifprintf( Znak sterujący sn iscntrl (c) tak nie)printf( Cyfra dziesiętna sn isdigit (c) tak nie)printf( Graficzny sn isgraph (c) tak nie)printf( Mała litera sn islower (c) tak nie)printf( Drukowalny sn isprint (c) tak nie)printf( Przestankowy sn ispunct (c) tak nie)printf( Biały znak sn isspace (c) tak nie)printf( Wielka litera sn isupper (c) tak nie)printf( Cyfra szesnastkowa sn isxdigit(c) tak nie)

int main() unsigned char cprintf(Naciśnij jakiś klawiszn)if (scanf(c ampc)==1)

identify_char(c)setlocale(LC_ALL pl_PL) przystosowanie do warunkoacutew polskich puts(Po zmianie ustawień języka)identify_char(c)

return 0

C36 Zobacz też tolower toupper

C4 MALLOC 193

C4 malloc

C41 Deklaracjainclude ltstdlibhgt

void calloc(size_t nmeb size_t size)void malloc(size_t size)void free(void ptr)void realloc(void ptr size_t size)

C42 Argumentynmeb liczba elementoacutew dla ktoacuterych ma być przydzielona pamięć

size rozmiar (w bajtach) pamięci do zarezerwowania bądź rozmiar pojedynczego elementu

ptr wskaźnik zwroacutecony przez poprzednie wywołanie jednej z funkcji lub

C43 OpisFunkcja calloc przydziela pamięć dla nmeb elementoacutew o rozmiarze size każdy i zeruje przydzielonąpamięć

Funkcja malloc przydziela pamięć o wielkości size bajtoacutewFunkcja free zwalnia blok pamięci wskazywany przez ptr wcześniej przydzielony przez jedną z

funkcji malloc calloc lub realloc Jeżeli ptr ma wartość funkcja nie robi nicFunkcja realloc zmienia rozmiar przydzielonego wcześniej bloku pamięci wskazywanego przez ptr

do size bajtoacutew Pierwsze n bajtoacutew bloku nie ulegnie zmianie gdzie n jest minimum z rozmiaru staregobloku i size Jeżeli ptr jest roacutewny zero (tj ) funkcja zachowuje się tak samo jako malloc

C44 Wartość zwracanaJeżeli przydzielanie pamięci się powiodło funkcje calloc malloc i realloc zwracają wskaźnik do nowoprzydzielonego bloku pamięci W przypadku funkcji realloc może to być wartość inna niż ptr

Jeśli jako size nmeb podano zero zwracany jest albo wskaźnik albo prawidłowy wskaźnikktoacutery można podać do funkcji free (zauważmy że jest też prawidłowym argumentem free)

Jeśli działanie funkcji nie powiedzie się zwracany jest i odpowiedni kod błędu jest wpisywanydo zmiennej errno Dzieje się tak zazwyczaj gdy nie ma wystarczająco dużo miejsca w pamięci

C45 Przykładinclude ltstdiohgtinclude ltstdlibhgt

int main(void)

size_t size num = 0float tab tmp

Przydzielenie początkowego bloku pamięci size = 64tab = malloc(size sizeof tab)if (tab)

perror(malloc)return EXIT_FAILURE

194 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Odczyt liczb while (scanf(f amptmp)==1)

Jeżeli zapełniono całą tablicę trzeba ją zwiększyć if (num==size)float ptr = realloc(tab (size = 2) sizeof ptr)if (ptr)

free(tab)perror(realloc)return EXIT_FAILURE

tab = ptr

tab[num++] = tmp

Wypisanie w odwrotnej kolejnosci while (num)

printf(fn tab[--num])

Zwolnienie pamieci i zakonczenie programu free(tab)return EXIT_SUCCESS

C46 Uwagi

Użycie rzutowania przy wywołaniach funkcji malloc realloc oraz calloc w języku C jest zbędne i szko-dliwe W przypadku braku deklaracji tych funkcji (np gdy programista zapomni dodać plik nagłoacutew-kowy stdlibh) kompilator przyjmuje domyślną deklaracje w ktoacuterej funkcja zwraca int Przy brakurzutowania spowoduje to błąd kompilacji (z powodu niemożności skonwertowania liczby na wskaźnik)co pozwoli na szybkie wychwycenie błędu w programie Rzutowanie powoduje że kompilator zostajezmuszony do przeprowadzenia konwersji typoacutew i nie wyświetla żadnych błędoacutew W przypadku językaC++ rzutowanie jest konieczne

Zastosowanie operatora sizeof z wyrażeniem (np sizeof tablica) a nie typem (np sizeof float)ułatwia poacuteźniejszą modyfikację programoacutew Gdyby w pewnym momencie programista zdecydował sięzmienić tablicę z tablicy floatoacutew na tablice doublersquoi musiałby wyszukiwać wszystkie wywołania funkcjimalloc realloc i calloc co nie jest konieczne przy użyciu operatora sizeof z wyrażeniem

Ponieważ dla parametru size roacutewnego zero funkcja może zwroacutecić albo wskaźnik roacuteżny od wartości albo jej roacutewny zwykłe sprawdzanie poprawności wywołania poprzez przyroacutewnanie zwroacuteconejwartości do zera może nie dać prawidłowego wyniku

C47 Zobacz też

Wskaźniki (dokładne omoacutewienie zastosowania)

C5 PRINTF 195

C5 printf

C51 Deklaracjainclude ltstdiohgt

int printf(const char format )int fprintf(FILE stream const char format )int sprintf(char str const char format )int snprintf(char str size_t size const char format )

include ltstdarghgt

int vprintf(const char format va_list ap)int vfprintf(FILE stream const char format va_list ap)int vsprintf(char str const char format va_list ap)int vsnprintf(char str size_t size const char format va_list ap)

C52 OpisFunkcje formatują tekst zgodnie z podanym formatem opisanym poniżej Funkcje printf i vprintf wy-pisują tekst na standardowe wyjście (tj do stdout) fprintf i vfprintf do strumienia podanego jakoargument a sprintf vsprintf snprintf i vsnprintf zapisują go w podanej jako argument tablicy znakoacutew

Funkcje vprintf vfprintf vsprintf i vsnprintf roacuteżnią się od odpowiadających im funkcjom printffprintf sprintf i snprintf tym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

Funkcje snprintf i vsnprintf roacuteżnią się od sprintf i vsprintf tym że nie zapisuje do tablicy nie wię-cej niż size znakoacutew (wliczając kończący znak rsquorsquo) Oznacza to że można je używać bez obawy owystąpienie przepełnienia bufora

C53 Argumentyformat format w jakim zostaną wypisane następne argumenty

stream strumień wyjściowy do ktoacuterego mają być zapisane dane

str tablica znakoacutew do ktoacuterej ma być zapisany sformatowany tekst

size rozmiar tablicy znakoacutew

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

C54 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) ktoacutere są kopiowane bez zmian na wyjścieoraz sekwencji sterujących zaczynających się od symbolu procenta po ktoacuterym następuje

dowolna liczba flag

opcjonalne określenie minimalnej szerokości pola

opcjonalne określenie precyzji

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

196 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Flagi

W sekwencji możliwe są następujące flagi

- (minus) oznacza że pole ma być wyroacutewnane do lewej a nie do prawej

+ (plus) oznacza że dane liczbowe zawsze poprzedzone są znakiem (plusem dla liczb nieujem-nych lub minusem dla ujemnych)

spacja oznacza że liczby nieujemne poprzedzone są dodatkową spacją jeżeli flaga plus i spacjasą użyte jednocześnie to spacja jest ignorowana

(hash) powoduje że wynik jest przedstawiony w alternatywnej postaci

ndash dla formatu o powoduje to zwiększenie precyzji jeżeli jest to konieczne aby na początkuwyniku było zero

ndash dla formatoacutew x i X niezerowa liczba poprzedzona jest ciągiem x lub X

ndash dla formatoacutew a A e E f F g i G wynik zawsze zawiera kropkę nawet jeżeli nie ma za niążadnych cyfr

ndash dla formatoacutew g i G końcowe zera nie są usuwane

(zero) dla formatoacutew d i o u xX aA e E f F g iG do wyroacutewnania pola wykorzystywane sązera zamiast spacji za wyjątkiem wypisywania wartości nieskończoność i NaN Jeżeli obie flagi i mdash są obecne to flaga zero jest ignorowana Dla formatoacutew d i o u x i X jeżeli określona jestprecyzja flaga ta jest ignorowana

Szerokość pola i precyzja

Minimalna szerokość pola oznacza ile najmniej znakoacutew ma zająć dane pole Jeżeli wartość po formato-waniu zajmuje mniej miejsca jest ona wyroacutewnywana spacjami z lewej strony (chyba że podano flagiktoacutere modyfikują to zachowanie) Domyślna wartość tego pola to

Precyzja dla formatoacutew

d i o u x iX określa minimalną liczbę cyfr ktoacutere mają być wyświetlone i ma domyślną wartość

a A e E f i F mdash liczbę cyfr ktoacutere mają być wyświetlone po kropce i ma domyślną wartość

g i G określa liczbę cyfr znaczących i ma domyślną wartość

dla formatu s mdash maksymalną liczbę znakoacutew ktoacutere mają być wypisane

Szerokość pola może być albo dodatnią liczbą zaczynającą się od cyfry roacuteżnej od zera albo gwiazdkąPodobnie precyzja z tą roacuteżnicą że jest jeszcze poprzedzona kropką Gwiazdka oznacza że brany jestkolejny z argumentoacutew ktoacutery musi być typu int Wartość ujemna przy określeniu szerokości jest trak-towana tak jakby podano flagę - (minus)

Rozmiar argumentu

Dla formatoacutew d i i można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu signed char

h mdash oznacza że format odnosi się do argumentu typu short

l (el) mdash oznacza że format odnosi się do argumentu typu long

ll (el el) mdash oznacza że format odnosi się do argumentu typu long long

j mdash oznacza że format odnosi się do argumentu typu intmax t

z mdash oznacza że że format odnosi się do argumentu typu będącego odpowiednikiem typu size tze znakiem

t mdash oznacza że że format odnosi się do argumentu typu ptrdiff t

C5 PRINTF 197

Dla formatoacutew o u x i X można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d ioznaczają one że format odnosi się do argumentu odpowiedniego typu bez znaku

Dla formatu n można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d i oznaczająone że format odnosi się do argumentu będącego wskaźnikiem na dany typ

Dla formatoacutew a A e E f F g iGmożna użyć modyfikatoroacutew rozmiaru L ktoacutery oznacza że formatodnosi się do argumentu typu long double

Dodatkowo modyfikator l (el) dla formatu c oznacza że odnosi się on do argumentu typu wint ta dla formatu s że odnosi się on do argumentu typu wskaźnik na wchar t

Format

Funkcje z rodziny printf obsługują następujące formaty

d i mdash argument typu int jest przedstawiany jako liczba całkowita ze znakiem w postaci [-]ddd

o u x X mdash argument typu unsigned int jest przedstawiany jako nieujemna liczba całkowitazapisana w systemie oktalnym (o) dziesiętnym (u) lub heksadecymalnym (x i X)

f F mdash argument typu double jest przedstawiany w postaci [-]dddddd

e E mdash argument typu double jest reprezentowany w postaci [i]dddde+dd gdzie liczba przedkropką dziesiętną jest roacuteżna od zera jeżeli liczba jest roacuteżna od zera a + oznacza znak wykładnikaFormat E używa wielkiej litery E zamiast małej

g G mdash argument typu double jest reprezentowany w formacie takim jak f lub e (odpowiednio Flub E) zależnie od liczby znaczących cyfr w liczbie oraz określonej precyzji

a A mdash argument typu double przedstawiany jest w formacie [-]xhhhhp+d czyli analogiczniejak dla e i E tyle że liczba zapisana jest w systemie heksadecymalnym

c mdash argument typu int jest konwertowany do unsigned char i wynikowy znak jest wypisywanyJeżeli podanomodyfikator rozmiaru l argument typuwint t konwertowany jest dowielobajtowejsekwencji i wypisywany

s mdash argument powinien być typu wskaźnik na char (lub wchar t) Wszystkie znaki z podanejtablicy aż do i z wyłączeniem znaku null są wypisywane

pmdashargument powinien być typuwskaźnik na void Jest to konwertowany na serię drukowalnychznakoacutew w sposoacuteb zależny od implementacji

n mdash argument powinien być wskaźnikiem na liczbę całkowitą ze znakiem do ktoacuterego zapisanajest liczba zapisanych znakoacutew

W przypadku formatoacutew f F e E gG a iAwartość nieskończoność jest przedstawiana w formacie[-]inf lub [-]infinity zależnie od implementacji Wartość NaN jest przedstawiana w postaci [-]nan lub[i]nan(sekwencja) gdzie sekwencja jest zależna od implementacji W przypadku formatoacutew określo-nych wielką literą roacutewnież wynikowy ciąg znakoacutew jest wypisywany wielką literą

C55 Wartość zwracana

Jeżeli funkcje zakończą się sukcesem zwracają liczbę znakoacutew w tekście (wypisanym na standardowewyjście do podanego strumienia lub tablicy znakoacutew) nie wliczając kończącego rsquorsquo W przeciwnymwypadku zwracana jest liczba ujemna

Wyjątkami są funkcje snprintf i vsnprintf ktoacutere zwracają liczbę znakoacutew ktoacutere zostałyby zapisanedo tablicy znakoacutew gdyby była wystarczająco duża

198 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C56 Przykład użyciainclude ltstdiohgtint main()

int i = 4float f = 31415char s = Monty Pythonprintf(i = inf = 1fnWskaźnik s wskazuje na napis sn i f s)return 0

Wyświetli

i = 4f = 31Wskaźnik s wskazuje na napis Monty Python

Funkcja formatująca ciąg znakoacutew i alokująca odpowiednią ilość pamięci

include ltstdarghgtinclude ltstdlibhgt

char sprintfalloc(const char format ) int retsize_t size = 100char str = malloc(size)if (str)

return 0

for()va_list apchar tmp

va_start(ap format)ret = vsnprintf(str size format ap)va_end(ap)

if (retltsize) break

tmp = realloc(str (size_t)ret + 1)if (tmp) ret = -1break

else str = tmpsize = (size_t)ret + 1

if (retlt0) free(str)str = 0

C6 SCANF 199

else if (size-1gtret) char tmp = realloc(str (size_t)ret + 1)if (tmp) str = tmp

return str

C57 Uwagi

Funkcje snprintf i vsnprintf nie były zdefiniowane w standardzie C Zostały one dodane dopiero wstandardzie C

Biblioteka glibc do wersji włącznie posiadała implementacje funkcji snprintf oraz vsnprintfktoacutere były niezgodne ze standardem gdyż zwracały - w przypadku gdy wynikowy tekst nie mieściłsię w podanej tablicy znakoacutew

C6 scanf

C61 Deklaracja

W pliku nagłoacutewkowym stdioh

int scanf(const char format )int fscanf(FILE stream const char format )int sscanf(const char str const char format )

W pliku nagłoacutewkowym stdargh

int vscanf(const char format va_list ap)int vsscanf(const char str const char format va_list ap)int vfscanf(FILE stream const char format va_list ap)

C62 Opis

Funkcje odczytują dane zgodnie z podanym formatem opisanym niżej Funkcje scanf i vscanf odczytujądane ze standardowego wejścia (tj stdin) fscanf i vfscanf ze strumienia podanego jako argument asscanf i vsscanf z podanego ciągu znakoacutew

Funkcje vscanf vfscanf i vsscanf roacuteżnią się od odpowiadających im funkcjom scanf fscanf i sscanftym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

C63 Argumenty

format format odczytu danych

stream strumień wejściowy z ktoacuterego mają być odczytane dane

str tablica znakoacutew z ktoacuterej mają być odczytane dane

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

200 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C64 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) oraz sekwencji sterujących zaczynającychsię od symbolu procenta po ktoacuterym następuje

opcjonalna gwiazdka

opcjonalne maksymalna szerokość pola

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

Wystąpienie w formacie białego znaku powoduje że funkcje z rodziny scanf będą odczytywać iodrzucać znaki aż do napotkania pierwszego znaku nie będącego białym znakiem

Wszystkie inne znaki (tj nie białe znaki oraz nie sekwencje sterujące) muszą dokładnie pasowaćdo danych wejściowych

Wszystkie białe znaki z wejścia są ignorowane chyba że sekwencja sterująca określa format [ c lubn

Jeżeli w sekwencji sterującej występuje gwiazdka to dane z wejścia zostaną pobrane zgodnie zformatem ale wynik konwersji nie zostanie nigdzie zapisany W ten sposoacuteb można pomijać częśćdanych

Maksymalna szerokość pola przyjmuje postać dodatniej liczby całkowitej zaczynającej się od cyfryroacuteżnej od zera Określa ona ile maksymalnie znakoacutew dany format może odczytać Jest to szczegoacutelnieprzydatne przy odczytywaniu ciągu znakoacutew gdyż dzięki temu można podać wielkość tablicy (minusjeden) i tym samym uniknąć błędoacutew przepełnienia bufora

Rozmiar argumentu

Dla formatoacutew d i o u x i n można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu wskaźnik na signed char lub unsignedchar

h mdash oznacza że format odnosi się do argumentu typu wskaźnik na short lub wskaźnik na unsi-gned short

l (el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long lub wskaźnik naunsigned long

ll (el el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long long lub wskaźnikna unsigned long long

j mdash oznacza że format odnosi się do argumentu typu wskaźnik na intmax t lub wskaźnik nauintmax t

z mdash oznacza że że format odnosi się do argumentu typu wskaźnik na size t lub odpowiedni typze znakiem

t mdash oznacza że że format odnosi się do argumentu typu wskaźnik na ptrdiff t lub odpowiednityp bez znaku

Dla formatoacutew a e f i g można użyć modyfikatoroacutew rozmiaru

l ktoacutery oznacza że format odnosi się do argumenty typu wskaźnik na double lub

L ktoacutery oznacza że format odnosi się do argumentu typu wskaźnik na long double

Dla formatoacutew c s i [ modyfikator l oznacza że format odnosi się do argumentu typu wskaźnik nawchar t

C6 SCANF 201

Format

Funkcje z rodziny scanf obsługują następujące formaty

d i odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przywywołaniufunkcji strtol z argumentem base roacutewnym odpowiednio dla d lub dla i argument powinienbyć wskaźnikiem na int

o u x odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przy wy-wołaniu funkcji strtoul z argumentem base roacutewnym odpowiednio dla o dla u lub dla xargument powinien być wskaźnikiem na unsigned int

a e f g odczytuje liczbę rzeczywistą nieskończoność lub NaN ktoacuterych format jest taki sam jakoczekiwany przy wywołaniu funkcji strtod argument powinien być wskaźnikiem na float

c odczytuje dokładnie tyle znakoacutew ile określono w maksymalnym rozmiarze pola (domyślnie )argument powinien być wskaźnikiem na char

s odczytuje sekwencje znakoacutew nie będących białymi znakami argument powinien być wskaźni-kiem na char

[ odczytuje niepusty ciąg znakoacutew z ktoacuterych każdymusi należeć do określonego zbioru argumentpowinien być wskaźnikiem na char

p odczytuje sekwencje znakoacutew zależną od implementacji odpowiadającą ciągowi wypisywa-nemu przez funkcję printf gdy podano sekwencję p argument powinien być typu wskaźnikna wskaźnik na void

n nie odczytuje żadnych znakoacutew ale zamiast tego zapisuje do podanej zmiennej liczbę odczyta-nych do tej pory znakoacutew argument powinien być typu wskaźnik na int

Słoacutewko więcej o formacie [ Po otwierającym nawiasie następuje ciąg określający znaki jakie mogąwystępować w odczytanym napisie i kończy się on nawiasem zamykającym tj ] Znaki pomiędzynawiasami (tzw scanlist) określają możliwe znaki chyba że pierwszym znakiem jest ˆ mdash woacutewczasw odczytanym ciągu znakoacutew mogą występować znaki nie występujące w scanlist Jeżeli sekwencjazaczyna się od [] lub [ˆ] to ten pierwszy nawias zamykający nie jest traktowany jako koniec sekwencjitylko jak zwykły znak Jeżeli wewnątrz sekwencji występuje znak - (minus) ktoacutery nie jest pierwszymlub drugim jeżeli pierwszym jest ˆ ani ostatnim znakiem zachowanie jest zależne od implementacji

Formaty A E F G i X są roacutewnież dopuszczalne i mają takie same działanie jak a e f g i x

C65 Wartość zwracanaFunkcja zwraca EOF jeżeli nastąpi koniec danych lub błąd odczytu zanim jakiekolwiek konwersje zo-staną dokonane lub liczbę poprawnie wczytanych poacutel (ktoacutera może być roacutewna zero)

202 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Dodatek D

Składnia

D1 Symbole i słowa kluczoweJęzyk C definiuje pewną ilość słoacutew za pomocą ktoacuterych tworzy się np pętle itp Są to tzw słowakluczowe tzn nie można użyć ich jako nazwy zmiennej czy też stałej (o nich poniżej) Oto lista słoacutewkluczowych języka C (według norm ANSI C z roku oraz ISO C z roku )

203

204 DODATEK D SKŁADNIA

Tablica D1 Symbole i słowa kluczoweSłowo Opis w tym podręcznikuauto Zmiennebreak Instrukcje sterującecase Instrukcje sterującear Zmienneconst Zmienne

continue Instrukcje sterującedefault Instrukcje sterujące

do Instrukcje sterującedouble Zmienneelse Instrukcje sterująceenum Typy złożoneextern Bibliotekifloat Zmiennefor Instrukcje sterującegoto Instrukcje sterująceif Instrukcje sterująceint Zmiennelong Zmienne

register Zmiennereturn Procedury i funkcjeshort Zmiennesigned Zmiennesizeof Zmiennestatic Biblioteki Zmiennestruct Typy złożoneswit Instrukcje sterującetypedef Typy złożoneunion Typy złożone

unsigned Zmiennevoid Wskaźniki

volatile Zmiennewhile Instrukcje sterujące

D2 POLSKIE ZNAKI 205

Specyfikacja ISO C z roku dodaje następujące słowa

Bool

Complex

Imaginary

inline

restrict

D2 Polskie znakiPisząc program możemy stosować polskie litery (tj ldquoąćęłńoacuteśźżrdquo) tylko w

komentarzach

ciągach znakoacutew (łańcuchach)

Niedopuszczalne jest stosowanie polskich znakoacutew w innych miejscach

D3 Operatory

D31 Operatory arytmetyczneSą to operatory wykonujące znane wszystkim dodawanie odejmowanie itp

operator znaczenie+ dodawanie- odejmowanie mnożenie dzielenie dzielenie modulo mdash daje w wyniku samą resztę z dzielenia= operator przypisania mdash wykonuje działanie po prawej stronie i wynik

przypisuje obiektowi po lewej

D32 Operatory logiczneSłużą poroacutewnaniu zawartości dwoacutech zmiennych według określonych kryterioacutew

Operator Rodzaj poroacutewnania== czy roacutewnegt większygt= większy bądź roacutewnylt mniejszylt= mniejszy bądź roacutewny= czy roacuteżny(nieroacutewny)

Są jeszcze operatory służące do grupowania poroacutewnań (patrz też logika w Wikipedii)

|| lub(OR)ampamp ioraz(AND) negacja(NOT)

206 DODATEK D SKŁADNIA

D33 Operatory binarne

Są to operatory ktoacutere działają na bitach

operator funkcja przykład| suma bitowa(OR) 5 | 2 da w wyniku 7 ( 00000101 OR 00000010 =

00000111)amp iloczyn bitowy 7 amp 2 da w wyniku 2 ( 00000111 AND 00000010

= 00000010)~ negacja bitowa 2 da wwyniku 253 (NOT 00000010 = 11111101

)gtgt przesunięcie bitoacutew o X w prawo 7 gtgt 2 da w wyniku 1 ( 00000111 gtgt 2 =

00000001)ltlt przesunięcie bitoacutew o X w lewo 7 ltlt 2 da w wyniku 28 ( 00000111 ltlt 2 =

00011100)^ alternatywa wyłączna 7 ˆ 2 da w wyniku 5 ( 00000111 ˆ 00000010 =

00000101)

D34 Operatory inkrementacjidekrementacji

Służą do dodawaniaodejmowania od liczby wartości jeden

Przykłady

Operacja Opis operacji Wartość wyrażeniax++ zwiększy wartość w x o jeden wartość zmiennej x przed zmianą++x zwiększy wartość w x o jeden wartość zmiennej x powiększona o jedenxndash zmniejszy wartość w x o jeden wartość zmiennej x przed zmianąndashx zmniejszy wartość w x o jeden wartość zmiennej x pomniejszona o jeden

Parę przykładoacutew dla zrozumienia

int a=7if ((a++)==7) najpierw poroacutewnuje potem dodaje

printf (dna) wypisze 8 if ((++a)==9) najpierw dodaje potem poroacutewnuje

printf (dn a) wypisze 9

Analogicznie ma się sytuacja z operatorami dekrementacji

D4 TYPY DANYCH 207

D35 PozostałeOperacja Opis operacji Wartość wyrażenia

x operator wyłuskania dla wskaźnika wartość trzymana w pamięci pod adre-sem przechowywanym we wskaźniku

ampx operator pobrania adresu zwraca adres zmiennejx[a] operator wybrania elementu tablicy zwraca element tablicy o indeksie a

(numerowanym od zera)xa operator wyboru składnika a ze zmien-

nej xwybiera składnik ze struktury lub unii

x-gta operator wyboru składnika a przezwskaźnik do zmiennej x

wybiera składnik ze struktury gdy uży-wamy wskaźnika do struktury zamiastzwykłej zmiennej

sizeof (typ) operator pobrania rozmiaru typu zwraca rozmiar typu w bajtachsizeof wyrażenie operator pobrania rozmiaru typu zwraca rozmiar typu rezultatu wyraże-

nia

D36 Operator ternarnyIstnieje jeden operator przyjmujący trzy argumenty mdash jest to operator wyrażenia warunkowego a b c Zwraca on b gdy a jest prawdą lub c w przeciwnym wypadku

D4 Typy dany

Tablica D Typy danych według roacuteżnych specyfikacji języka C

Typ Opis Inne nazwyTypy dany wg norm C i C

ar Służy głoacutewnie do przechowywania znakoacutew Od kom-pilatora zależy czy jest to liczba ze znakiem czy bez wwiększości kompilatoroacutew jest liczbą ze znakiem

signed ar Typ char ze znakiemunsigned ar Typ char bez znakushort Występuje gdy docelowa maszyna wyszczegoacutelnia

kroacutetki typ danych całkowitych w przeciwnym wy-padku jest tożsamy z typem int Często ma rozmiarjednego słowa maszynowego

short int signed shortsigned short int

unsigned short Liczba typu short bez znaku Podobnie jak short uży-wana do zredukowania zużycia pamięci przez program

unsigned short int

int Liczba całkowita odpowiadająca podstawowemu roz-miarowi liczby całkowitej w danym komputerze Pod-stawowy typ dla liczb całkowitych

signed int signed

unsigned Liczba całkowita bez znaku unsigned intlong Długa liczba całkowita long int signed long

signed long intunsigned long Długa liczba całkowita bez znaku unsigned long intfloat Podstawowy typ do przechowywania liczb zmienno-

przecinkowych W nowszym standardzie zgodny jest znormą IEEE Nie można stosować go z modyfika-torem signed ani unsigned

double Liczba zmiennoprzecinkowa podwoacutejnej precyzji Po-dobnie jak float nie łączy się z modyfikatorem signedani unsigned

208 DODATEK D SKŁADNIA

long double Największa możliwa dokładność liczb zmiennoprzecin-kowych Nie łączy się z modyfikatorem signed aniunsigned

Typy dany według normy CBool Przechowuje wartości lub long long Nowy typ umożliwiający obliczeniach na bardzo du-

żych liczbach całkowitych bez użycia typu floatlong long int signedlong long signed longlong int

unsigned long long Długie liczby całkowite bez znaku unsigned long long intfloat Complex Słuzy do przechowywania liczb zespolonychdouble Complex Słuzy do przechowywania liczb zespolonychlong double Complex Słuzy do przechowywania liczb zespolonych

Typy dany definiowane przez użytkownikastruct Więcej o kompilowaniuunion Rozmiar typu jest taki jak rozmiar największego polatypedef Nowo zdefiniowany typ przyjmuje taki sam rozmiar

jak typ macierzystyenum Zwykle elementy mają taką samą długość jak typ int

Zależności rozmiaru typoacutew danych są następujące

sizeof(cokolwiek) = sizeof(signed cokolwiek) = sizeof(unsigned cokolwiek)

= sizeof(ar) le sizeof(short) le sizeof(int) le sizeof(long) le sizeof(long long)

sizeof(float) le sizeof(double) le sizeof(long double)

sizeof(cokolwiek Complex) = sizeof(cokolwiek)

sizeof(void ) = sizeof(ar ) ge sizeof(cokolwiek )

sizeof(cokolwiek ) = sizeof(signed cokolwiek ) = sizeof(unsigned cokolwiek )

sizeof(cokolwiek ) = sizeof(const cokolwiek )

Dodatkowo jeżeli przez V(typ) oznaczymy liczbę bitoacutew wykorzystywanych w typie to zachodzi

le V(ar) = V(signed ar) = V(unsigned ar)

le V(short) = V(unsigned short)

le V(int) = V(unsigned int)

le V(long) = V(unsigned long)

le V(long long) = V(unsigned long long)

V(ar) le V(short) le V(int) le V(long) le V(long long)

Dodatek E

Przykłady z komentarzem

E01 Liczby losowePoniższy program generuje wiersz po wierszu macierz o określonych przez użytkownika wymiarachzawierającą losowo wybrane liczby Każdy wygenerowany wiersz macierzy zapisywany jest w plikutekstowym o wprowadzonej przez użytkownika nazwie W pierwszym wierszu pliku wynikowego za-pisano wymiary utworzonej macierzy Program napisany i skompilowany został w środowisku GNU-Linux

include ltstdiohgtinclude ltstdlibhgt dla funkcji rand() oraz srand() include lttimehgt dla funkcji [time()

main()

int i j n mfloat reFILE fpchar fileName[128]

printf(Wprowadz nazwe pliku wynikowegon)scanf(sampfileName)

printf(Wprowadz po sobie liczbe wierszy i kolumn macierzy oddzielone spacjąn)scanf(d d ampn ampm)

jeżeli wystąpił błąd w otwieraniu pliku i go nie otwartowoacutewczas funkcja fclose(fp) wywołana na końcu programu zgłosi błądwykonania i wysypie nam program z działania stąd musimy umieścićwarunek ktoacutery w kontrolowany sposoacuteb zatrzyma program (funkcja exit)

if ( (fp = fopen(fileName w)) == NULL )

puts(Otwarcie pliku nie jest mozliwe)exit jeśli w procedurze glownej

to piszemy bez nawiasow

else puts(Plik otwarty prawidłowo)

209

210 DODATEK E PRZYKŁADY Z KOMENTARZEM

fprintf(fp d dn n m) w pierwszym wierszu umieszczono wymiary macierzy

srand( (unsigned int) time(0) )for (i=1 ilt=n ++i)

for (j=1 jlt=m ++j)re = ((rand() 200)-100) 100fprintf(fp1f re )if (j=m) fprintf(fp )

fprintf(fpn)fclose(fp)return 0

E02 Zamiana liczb dziesiętny na liczby w systemie dwoacutejkowym

Zajmijmy się teraz innym zagadnieniem Wiemy że komputer zapisuje wszystkie liczby w postacibinarnej (czyli za pomocą jedynek i zer) Sproacutebujmy zatem zamienić liczbę zapisaną w ldquonaszymrdquo dzie-siątkowym systemie na zapis binarny Uwaga Program działa jedynie dla liczb od do maksymalnejwartości ktoacuterą może przyjąć typ unsigned short int w twoim kompilatorze

include ltstdiohgtinclude ltlimitshgt

void dectobin (unsigned short a)

int licznik

CHAR_BIT to liczba bitoacutew w bajcie licznik = CHAR_BIT sizeof(a)while (--licznik gt= 0)

putchar(((a gtgt licznik) amp 1)) 1 0)

int main ()

unsigned short a

printf (Podaj liczbę od 0 do hd USHRT_MAX)scanf (hd ampa)printf (hd(10) = a)dectobin(a)printf ((2)n)

return 0

211

E03 Zalążek przeglądarki

Zajmiemy się tym razem inną kwestią a mianowicie programowaniem sieci Jest to zagadnienie bar-dzo ostatnio popularne Nasz program będzie miał za zadanie połączyć się z serwerem ktoacuterego adresużytkownik będzie podawał jako pierwszy parametr programu wysłać zapytanie HTTP i odebrać treśćktoacuterą wyśle do nas serwer Zacznijmy może od tego że obsługa sieci jest niemal identyczna w roacuteżnychsystemach operacyjnych Na przykład między systemami z rodziny Unix oraz Windowsem roacuteżnica po-lega tylko na dołączeniu innych plikoacutew nagłoacutewkowych (dla Windowsa mdash winsockh) Przeanalizujmyzatem poniższy kod

include ltstdiohgtinclude ltstdlibhgtinclude ltstringhgtinclude ltunistdhgtinclude ltarpainethgtinclude ltsystypeshgtinclude ltnetinetinhgtinclude ltsyssockethgt

define MAXRCVLEN 512define PORTNUM 80

char query = GET HTTP11nn

int main(int argc char argv[])

char buffer[MAXRCVLEN+1]int len mysocketstruct sockaddr_in destchar host_ip = NULLif (argc = 2)

printf (Podaj adres serweran)exit (1)

host_ip = argv[1]mysocket = socket(AF_INET SOCK_STREAM 0)

destsin_family = AF_INETdestsin_addrs_addr = inet_addr(host_ip) ustawiamy adres hosta destsin_port = htons (PORTNUM) numer portu przechowuje dwubajtowa zmienna -

musimy ustalić porządek sieciowy - Big Endian memset(amp(destsin_zero) 0 8) zerowanie reszty struktury

connect(mysocket (struct sockaddr )ampdestsizeof(struct sockaddr)) łączymy się z hostem write (mysocket query strlen(query)) wysyłamy zapytanie len=read(mysocket buffer MAXRCVLEN) i pobieramy odpowiedź

buffer[len]=0

printf(Rcvd sbuffer)close(mysocket) zamykamy gniazdo return EXIT_SUCCESS

212 DODATEK E PRZYKŁADY Z KOMENTARZEM

Powyższy przykład może być odrobinę niezrozumiały dlatego przyda się kilka słoacutew wyjaśnieniaPliki nagłoacutewkowe ktoacutere dołączamy zawierają deklarację nowych dla Ciebie funkcji mdash socket() con-nect() write() oraz read() Oproacutecz tego spotkałeś się z nową strukturą mdash sockaddr in Wszystkie teobiekty są niezbędne do stworzenia połączenia

Dodatek F

Informacje o pliku i historia

F1 HistoriaTa książka została stworzona na polskojęzycznej wersji projektu Wikibooks przez autoroacutew wymie-nionych poniżej w sekcji Autorzy Najnowsza wersja podręcznika jest dostępna pod adresem httpplwikibooksorgwikiC

F2 Informacje o pliku i historia został utworzony przez Derbetha dnia listopada na podstawie wersji z listopada podręcznika na Wikibooks Wykorzystany został poprawiony program WikiLaTeX autorstwa użyt-kownika angielskichWikibooks Hagindaza Wynikowy kod po ręcznych poprawkach został przekształ-cony w książkę za pomocą systemu składu XeLaTeX Wykorzystano wolną dostępną na licencjach i czcionkę Linux Libertine oraz wolną czcionkę DejaVu Sans Mono

Najnowsza wersja tego -u jest postępna pod adresem httpplwikibooksorgwikiImageCpdf

F3 AutorzyAdam majewski Adiblol Akira Albmont Ananas Arfrever BartekChom Bercik Bla Bociex CathyRichards Cnr CzarnyInaczej CzarnyZajaczek DaniXTeam Derbeth Equadus Faw GDR GangGk Gynvael Incuś Karol Ossowski Kazet Kj Lethern MTM Marcin MastiBot MeaglinMerdis Michael Migol Mina MonteChristof Mt Myki Mythov Narf Noisy Norill PawelkgPawlosck Peter de Sowaro Piotr Pkierski Ponton Przykuta RedRad Sasek Sblive Silbarad T zielWarszk Webprog Wentuq ZiomekPL Zjem ci chleb i anonimowi autorzy

F4 GrafikiAutorzy i licencje grafik

grafika na okładce Saint-Elme Gautier rycina z książki Le Corset agrave travers les acircges Paryż źroacutedło Wikimedia Commons public domain

logoWikibooks zastrzeżony znak towarowycopy ampAll rights reserved Wikimedia FoundationInc

grafika a (strona ) autor Claudio Rocchini źroacutedło Wikimedia Commons licencja

grafika b (strona ) autor Adam majewski źroacutedło Wikimedia Commons licencja CreativeCommons Aribution Unported

213

214 DODATEK F INFORMACJE O PLIKU

grafia (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Daniel B źroacutedo Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Derrick Coetzee źroacutedło Wikimedia Commons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

Indeks

adres alternatywa

biblioteka standardowa big endian blok

Cjęzyk

dekrementacja dynamiczna alokacja pamięci

enum

funkcja definicja deklaracja rekurencyjna

inkrementacja

komentarz kompilacja

warunkowa kompilator

lista używanie

koniunkcja konwersja

libc lile endian Porownaj big endian

main makefile

napis poroacutewnywanie

negacja

operatordekrementacji inkrementacji modulo

pobrania adresu sizeof wyrażenia warunkowego wyłuskania

plikczytanie i pisanie nagłowkowy

porządek bajtoacutew prawda i fałsz preprocesor procedury prototyp funkcji przekazywanie argumentoacutew do funkcji

przez wartość przez wskaźnik

przepełnienie bufora przesunięcie bitowe

rzutowanie

sizeof stała struktura słowa kluczowe

tablica wielowymiarowa znakoacutew

typ definiowanie wyliczeniowy

unia

Valgrind void

jako typ zwracany na liście argumentoacutew void

volatile

wejściewyjście wskaźnik wyciek pamięci

215

216 INDEKS

wyroacutewnywanie

zmienna globalna lokalna statyczna

znaki specjalne

  • O podręczniku
    • O czym moacutewi ten podręcznik
    • Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika
    • Konwencje przyjęte w tym podręczniku
    • Czy mogę pomoacutec
    • Autorzy
    • Źroacutedła
      • O języku C
        • Historia C
        • Zastosowania języka C
        • Przyszłość C
          • Czego potrzebujesz
            • Czego potrzebujesz
            • Zintegrowane Środowiska Programistyczne
            • Dodatkowe narzędzia
              • Używanie kompilatora
                • GCC
                • Borland
                • Czytanie komunikatoacutew o błędach
                  • Pierwszy program
                    • Twoacutej pierwszy program
                    • Rozwiązywanie problemoacutew
                      • Podstawy
                        • Kompilacja Jak działa C
                        • Co może C
                        • Struktura blokowa
                        • Zasięg
                        • Funkcje
                        • Biblioteki standardowe
                        • Komentarze i styl
                        • Preprocesor
                        • Nazwy zmiennych stałych i funkcji
                          • Zmienne
                            • Czym są zmienne
                            • Typy zmiennych
                            • Specyfikatory
                            • Modyfikatory
                            • Uwagi
                              • Operatory
                                • Przypisanie
                                • Rzutowanie
                                • Operatory arytmetyczne
                                • Operacje bitowe
                                • Poroacutewnanie
                                • Operatory logiczne
                                • Operator wyrażenia warunkowego
                                • Operator przecinek
                                • Operator sizeof
                                • Inne operatory
                                • Priorytety i kolejność obliczeń
                                • Kolejność wyliczania argumentoacutew operatora
                                • Uwagi
                                • Zobacz też
                                  • Instrukcje sterujące
                                    • Instrukcje warunkowe
                                    • Pętle
                                    • Instrukcja goto
                                    • Natychmiastowe kończenie programu --- funkcja exit
                                    • Uwagi
                                      • Podstawowe procedury wejścia i wyjścia
                                        • Wejściewyjście
                                        • Funkcje wyjścia
                                        • Funkcja puts
                                        • Funkcja fputs
                                        • Funkcje wejścia
                                          • Funkcje
                                            • Tworzenie funkcji
                                            • Wywoływanie
                                            • Zwracanie wartości
                                            • Zwracana wartość
                                            • Funkcja main()
                                            • Dalsze informacje
                                            • Zobacz też
                                              • Preprocesor
                                                • Wstęp
                                                • Dyrektywy preprocesora
                                                • Predefiniowane makra
                                                  • Biblioteka standardowa
                                                    • Czym jest biblioteka
                                                    • Po co nam biblioteka standardowa
                                                    • Gdzie są funkcje z biblioteki standardowej
                                                    • Opis funkcji biblioteki standardowej
                                                    • Uwagi
                                                      • Czytanie i pisanie do plikoacutew
                                                        • Pojęcie pliku
                                                        • Identyfikacja pliku
                                                        • Podstawowa obsługa plikoacutew
                                                        • Rozmiar pliku
                                                        • Przykład --- pliki graficzny
                                                        • Co z katalogami
                                                          • Ćwiczenia dla początkujących
                                                            • Ćwiczenia
                                                              • Tablice
                                                                • Wstęp
                                                                • Odczytzapis wartości do tablicy
                                                                • Tablice znakoacutew
                                                                • Tablice wielowymiarowe
                                                                • Ograniczenia tablic
                                                                • Ciekawostki
                                                                  • Wskaźniki
                                                                    • Co to jest wskaźnik
                                                                    • Operowanie na wskaźnikach
                                                                    • Arytmetyka wskaźnikoacutew
                                                                    • Tablice a wskaźniki
                                                                    • Gdy argument jest wskaźnikiem
                                                                    • Pułapki wskaźnikoacutew
                                                                    • Na co wskazuje NULL
                                                                    • Stałe wskaźniki
                                                                    • Dynamiczna alokacja pamięci
                                                                    • Wskaźniki na funkcje
                                                                    • Możliwe deklaracje wskaźnikoacutew
                                                                    • Popularne błędy
                                                                    • Ciekawostki
                                                                      • Napisy
                                                                        • Łańcuchy znakoacutew w języku C
                                                                        • Operacje na łańcuchach
                                                                        • Bezpieczeństwo kodu a łańcuchy
                                                                        • Konwersje
                                                                        • Operacje na znakach
                                                                        • Częste błędy
                                                                        • Unicode
                                                                          • Typy złożone
                                                                            • typedef
                                                                            • Typ wyliczeniowy
                                                                            • Struktury
                                                                            • Unie
                                                                            • Inicjalizacja struktur i unii
                                                                            • Wspoacutelne własności typoacutew wyliczeniowych unii i struktur
                                                                            • Studium przypadku --- implementacja listy wskaźnikowej
                                                                              • Biblioteki
                                                                                • Czym jest biblioteka
                                                                                • Jak zbudowana jest biblioteka
                                                                                  • Więcej o kompilowaniu
                                                                                    • Ciekawe opcje kompilatora GCC
                                                                                    • Program make
                                                                                    • Optymalizacje
                                                                                    • Kompilacja krzyżowa
                                                                                    • Inne narzędzia
                                                                                      • Zaawansowane operacje matematyczne
                                                                                        • Biblioteka matematyczna
                                                                                        • Prezentacja liczb rzeczywistych w pamięci komputera
                                                                                        • Liczby zespolone
                                                                                          • Powszechne praktyki
                                                                                            • Konstruktory i destruktory
                                                                                            • Zerowanie zwolnionych wskaźnikoacutew
                                                                                            • Konwencje pisania makr
                                                                                            • Jak dostać się do konkretnego bitu
                                                                                            • Skroacutety notacji
                                                                                              • Przenośność programoacutew
                                                                                                • Niezdefiniowane zachowanie i zachowanie zależne od implementacji
                                                                                                • Rozmiar zmiennych
                                                                                                • Porządek bajtoacutew i bitoacutew
                                                                                                • Biblioteczne problemy
                                                                                                • Kompilacja warunkowa
                                                                                                  • Łączenie z innymi językami
                                                                                                    • Język C i Asembler
                                                                                                    • C++
                                                                                                      • Indeks alfabetyczny
                                                                                                      • Indeks tematyczny
                                                                                                        • asserth
                                                                                                        • ctypeh
                                                                                                        • errnoh
                                                                                                        • floath
                                                                                                        • limitsh
                                                                                                        • localeh
                                                                                                        • mathh
                                                                                                        • setjmph
                                                                                                        • signalh
                                                                                                        • stdargh
                                                                                                        • stddefh
                                                                                                        • stdioh
                                                                                                        • stdlibh
                                                                                                        • stringh
                                                                                                        • timeh
                                                                                                          • Wybrane funkcje biblioteki standardowej
                                                                                                            • assert
                                                                                                            • atoi
                                                                                                            • isalnum
                                                                                                            • malloc
                                                                                                            • printf
                                                                                                            • scanf
                                                                                                              • Składnia
                                                                                                                • Symbole i słowa kluczowe
                                                                                                                • Polskie znaki
                                                                                                                • Operatory
                                                                                                                • Typy danych
                                                                                                                  • Przykłady z komentarzem
                                                                                                                  • Informacje o pliku
                                                                                                                    • Historia
                                                                                                                    • Informacje o pliku PDF i historia
                                                                                                                    • Autorzy
                                                                                                                    • Grafiki
                                                                                                                      • Skorowidz
Page 6: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ

Typy złożone typedef 141 Typ wyliczeniowy 141 Struktury 142 Unie 142 Inicjalizacja struktur i unii 144 Wspoacutelne własności typoacutew wyliczeniowych unii i struktur 144 Studium przypadku mdash implementacja listy wskaźnikowej 146

Biblioteki Czym jest biblioteka 151 Jak zbudowana jest biblioteka 151

Więcej o kompilowaniu Ciekawe opcje kompilatora 155 Program make 155 Optymalizacje 157 Kompilacja krzyżowa 159 Inne narzędzia 159

Zaawansowane operacje matematyczne Biblioteka matematyczna 161 Prezentacja liczb rzeczywistych w pamięci komputera 162 Liczby zespolone 163

Powszene praktyki Konstruktory i destruktory 165 Zerowanie zwolnionych wskaźnikoacutew 166 Konwencje pisania makr 166 Jak dostać się do konkretnego bitu 167 Skroacutety notacji 168

Przenośność programoacutew Niezdefiniowane zachowanie i zachowanie zależne od implementacji 171 Rozmiar zmiennych 172 Porządek bajtoacutew i bitoacutew 172 Biblioteczne problemy 175 Kompilacja warunkowa 175

Łączenie z innymi językami Język C i Asembler 177 C++ 180

A Indeks alfabetyczny

B Indeks tematyczny B asserth 183B ctypeh 183B errnoh 183B floath 183B limitsh 183

6

B localeh 184B mathh 184B setjmph 185B signalh 185B stdargh 185B stddefh 185B stdioh 185B stdlibh 186B stringh 186B timeh 186

C Wybrane funkcje biblioteki standardowej C assert 189C atoi 190C isalnum 191C malloc 193C printf 195C scanf 199

D Składnia D Symbole i słowa kluczowe 203D Polskie znaki 205D Operatory 205D Typy danych 207

E Przykłady z komentarzem

F Informacje o pliku F Historia 213F Informacje o pliku i historia 213F Autorzy 213F Grafiki 213

Skorowidz

7

8

Spis tablic

Priorytety operatoroacutew 53

D Symbole i słowa kluczowe 204D Typy danych według roacuteżnych specyfikacji języka C 207

9

Rozdział 1

O podręczniku

11 O czym moacutewi ten podręcznikNiniejszy podręcznik stanowi przewodnik dla początkujących programistoacutew po języku pro-gramowania C

12 Co trzeba wiedzieć żeby skorzystać z niniejszego pod-ręcznika

Ten podręcznik ma nauczyć programowania w C od podstaw do poziomu zaawansowanegoDo zrozumienia rozdziału dla początkujących wymagana jest jedynie znajomość podstawo-wych pojęć z zakresu algebry oraz terminoacutew komputerowych Doświadczenie w programo-waniu w innych językach bardzo pomaga ale nie jest konieczne

13 Konwencje przyjęte w tym podręcznikuInformacje ważne oznaczamy w następujący sposoacuteb

Ważna informacja

Dodatkowe informacje ktoacutere odrobinę wykraczają poza zakres podręcznika a także wy-jaśniają kwestie niezwiązane bezpośrednio z językiem C oznaczamy tak

Wyjaśnienie

Ponadto kod w języku C będzie prezentowany w następujący sposoacuteb

include ltstdiohgt

int main (int argc char argv[])

return 0

11

12 ROZDZIAŁ 1 O PODRĘCZNIKU

Innego rodzaju przykłady dialog użytkownika z konsolą i programem wejście wyjścieprogramu informacje teoretyczne będą wyglądały tak

typ zmienna = wartość

14 Czy mogę pomoacutecOczywiście że możesz Mało tego będziemy zadowoleni z każdej pomocy ndash możesz pisaćrozdziały lub tłumaczyć je z angielskiej wersji tego podręcznika Nie musisz pytać się nikogoo zgodę mdash jeśli chcesz możesz zacząć już teraz Prosimy jedynie o zapoznanie się ze stylempodręcznika użytymi w nim szablonami i zachowanie układu rozdziałoacutew Propozycje zmianyspisu treści należy zgłaszać na stronie dyskusji

Jeśli znalazłeś jakiś błąd a nie umiesz go poprawić koniecznie powiadom o tym fakcieautoroacutew tego podręcznika za pomocą strony dyskusji danego modułu książki Dzięki temuprzyczyniasz się do rozwoju tego podręcznika

15 AutorzyIstotny wkład w powstanie podręcznika mają

CzarnyZajaczek

Derbeth

Kj

mina

Dodatkowo w rozwoju podręcznika pomagali między innymi

Lrds

Noisy

16 Źroacutedła podręcznik C Programming na anglojęzycznej wersji Wikibooks licencja GFDL

Brian W Kernighan Dennis M Ritchie Język ANSI C

ISO C Commiee Dra stycznia

Bruce Eckel inking in C++ Rozdział Język C w programie C++

Rozdział 2

O języku C

Zobacz w Wikipedii C (ję-zyk programowania)C jest językiem programowania wysokiego poziomu Jego nazwę interpretuje się jako na-

stępną literę po B (nazwa jego poprzednika) lub drugą literę języka BCPL (poprzednik językaB)

21 Historia CW roku trzej naukowcy z Bell Telephone Laboratories mdashWilliam Shockley Walter Brat-tain i John Bardeen mdash stworzyli pierwszy tranzystor w roku w MIT skonstruowanopierwszy komputer oparty wyłącznie na tranzystorach TX-O w roku Jack Kilby z Te-xas Instruments skonstruował układ scalony Ale zanim powstał pierwszy układ scalonypierwszy język wysokiego poziomu został już napisany

W powstał Fortran (Formula Translator) ktoacutery zapoczątkował napisanie języka For-tran I () Poacuteźniej powstały kolejno

Algol mdash Algorithmic Language w r

Algol ()

CPL mdash Combined Programming Language ()

BCPL mdash Basic CPL ()

B ()

i C w oparciu o BB został stworzony przez Kena ompsona z Bell Labs był to język interpretowany uży-

wany we wczesnych wewnętrznych wersjach systemu operacyjnego UNIX Inni pracownicyBell Labs ompson i Dennis Richie rozwinęli B nazywając go NB dalszy rozwoacutej NB dał Cmdash język kompilowany Większa część UNIX-a została ponownie napisana w NB a następniew C co dało w efekcie bardziej przenośny system operacyjny W roku wydana zostałaksiążka pt ldquoe C Programming Languagerdquo ktoacutera stała się pierwszym podręcznikiem donauki języka C

Możliwość uruchamiania UNIX-a na roacuteżnych komputerach była głoacutewną przyczyną po-czątkowej popularności zaroacutewno UNIX-a jak i C zamiast tworzyć nowy system operacyjnyprogramiści mogli po prostu napisać tylko te części systemu ktoacuterych wymagał inny sprzętoraz napisać kompilator C dla nowego systemu Odkąd większa część narzędzi systemowychbyła napisana w C logiczne było pisanie kolejnych w tym samym języku

13

14 ROZDZIAŁ 2 O JĘZYKU C

Kilka z obecnie powszechnie stosowanych systemoacutew operacyjnych takich jak Linux Mi-croso Windows zostały napisane w języku C

211 Standaryzacje

W roku Ritchie i Kerninghan opublikowali pierwszą książkę nt języka C mdash ldquoe CProgramming Languagerdquo Owa książka przez wiele lat była swoistym ldquowyznacznikiemrdquo jakprogramować w języku C Była więc to niejako pierwsza standaryzacja nazywana od na-zwisk twoacutercoacutew ldquoKampRrdquo Oto nowości wprowadzone przez nią do języka C w stosunku dojego pierwszych wersji (pochodzących z początku lat )

możliwość tworzenia struktur (słowo struct)

dłuższe typy danych (modyfikator long)

liczby całkowite bez znaku (modyfikator unsigned)

zmieniono operator ldquo=+rdquo na ldquo+=rdquo

Ponadto producenci kompilatoroacutew (zwłaszcza ATampT) wprowadzali swoje zmiany nieob-jęte standardem

funkcje nie zwracające wartości (void) oraz typ void

funkcje zwracające struktury i unie

przypisywanie wartości strukturom

wprowadzenie słowa kluczowego const

utworzenie biblioteki standardowej

wprowadzenie słowa kluczowego enum

Owe nieoficjalne rozszerzenia zagroziły spoacutejności języka dlatego też powstał standardregulujący wprowadzone nowinki Od roku trwały prace standaryzacyjne aby w roku wydać standard C (poprawna nazwa to ANSI X-) Niektoacutere zmiany wpro-wadzono z języka C++ jednak rewolucję miał dopiero przynieść standard C ktoacutery wpro-wadził min

funkcje inline

nowe typy danych (np long long int)

nowy sposoacuteb komentowania zapożyczony od C++ ()

przechowywanie liczb zmiennoprzecinkowych zostało zaadaptowane do norm IEEE

utworzono kilka nowych plikoacutew nagłoacutewkowych (stdboolh inypesh)

Na dzień dzisiejszy normą obowiązującą jest norma C

22 ZASTOSOWANIA JĘZYKA C 15

22 Zastosowania języka CJęzyk C został opracowany jako strukturalny język programowania do celoacutew ogoacutelnych Przezcałą swą historię (czyli ponad lat) służył do tworzenia przeroacuteżnych programoacutewmdash od syste-moacutew operacyjnych po programy nadzorujące pracę urządzeń przemysłowych C jako językdużo szybszy od językoacutew interpretowanych (Perl Python) oraz uruchamianych w maszy-nach wirtualnych (np C Java) może bez problemu wykonywać złożone operacje nawetwtedy gdy nałożone są dość duże limity czasu wykonywania pewnych operacji Jest on przytym bardzo przenośny mdash może działać praktycznie na każdej architekturze sprzętowej podwarunkiem opracowania odpowiedniego kompilatora Często wykorzystywany jest takżedo oprogramowywania mikrokontroleroacutew i systemoacutew wbudowanych Jednak w niektoacuterychsytuacjach język C okazuje się być mało przydatny zwłaszcza chodzi tu o obliczenia mate-matyczne wymagające dużej precyzji (w tej dziedzinie znakomicie spisuje się Fortran) lubteż dużej optymalizacji dla danego sprzętu (wtedy niezastąpiony jest język asemblera)

Kolejną zaletą C jest jego dostępność mdash właściwie każdy system typu UNIX posiada kom-pilator C w C pisane są funkcje systemowe

Problemem w przypadku C jest zarządzanie pamięcią ktoacutere nie wybacza programiściebłędoacutew niewygodne operowanie napisami i niestety pewna liczba ldquokruczkoacutewrdquo ktoacutere mogązaskakiwać nowicjuszy Na tle młodszych językoacutew programowania C jest językiem dosyćniskiego poziomu więc wiele rzeczy trzeba w nim robić ręcznie jednak zarazem umożliwia torobienie rzeczy nieprzewidzianych w samym języku (np implementację liczb bitowych)a także łatwe łączenie C z Asemblerem

23 Przyszłość CPomimo sędziwego już wieku (C ma ponad lat) nadal jest on jednym z najczęściej stosowa-nych językoacutew programowania Doczekał się już swoich następcoacutew z ktoacuterymi w niektoacuterychdziedzinach nadal udaje mu się wygrywać Widać zatem że pomimo pozornej prostoty iniewielkich możliwości język C nadal spełnia stawiane przed nim wymagania Warto zatemuczyć się języka C gdyż nadal jest on wykorzystywany (i nic nie wskazuje na to by miałosię to zmienić) a wiedza ktoacuterą zdobędziesz ucząc się C na pewno się nie zmarnuje Skład-nia języka C pomimo że przez wielu uważana za nieczytelną stała się podstawą dla takichjęzykoacutew jak C++ C czy też Java

16 ROZDZIAŁ 2 O JĘZYKU C

Rozdział 3

Czego potrzebujesz

31 Czego potrzebujeszWbrew powszechnej opinii nauczenie się ktoacuteregoś z językoacutew programowania (w tym językaC) nie jest takie trudne Do nauki wystarczą Ci

komputer z dowolnym systemem operacyjnym takim jak FreeBSD Linux Windows

Język C jest bardzo przenośny więc będzie działał właściwie na każdej platformiesprzętowej i w każdym nowoczesnym systemie operacyjnym

kompilator języka C

Kompilator języka C jest programem ktoacutery tłumaczy kod źroacutedłowy napisany przeznas do języka asembler a następnie do postaci zrozumiałej dla komputera (maszynycyfrowej) czyli do postaci ciągu zer i jedynek ktoacutere sterują pracą poszczegoacutelnych ele-mentoacutew komputera Kompilator języka C można dostać za darmo Przykładem sągcc pod systemy uniksowe DJGPP pod systemy DOS MinGW oraz lcc pod systemytypu Windows Jako kompilator C może dobrze służyć kompilator języka C++ (roacuteżnicemiędzy tymi językami przy pisaniu prostych programoacutew są nieistotne) Spokojnie mo-żesz więc użyć na przykład Microso Visual C++reg lub kompilatoroacutew firmy BorlandJeśli lubisz eksperymentować wyproacutebuj Tiny C Compiler bardzo szybki kompilatoro ciekawych funkcjach Możesz ponadto wyproacutebować interpreter języka C Więcejinformacji na Wikipedii

Linker (często jest razem z kompilatorem)

Linker jest to program ktoacutery uruchamiany jest po etapie kompilacji jednego lub kilkuplikoacutew źroacutedłowych (pliki z rozszerzeniem c cpp lub innym) skompilowanych do-wolnym kompilatorem Taki program łączy wszystkie nasze skompilowane pliki źroacute-dłowe i inne funkcje (np printf scan) ktoacutere były użyte (dołączone do naszego pro-gramu poprzez użycie dyrektywy include) w naszym programie a nie były zdefinio-wane(napisane przez nas) w naszych plikach źroacutedłowych lub nagłoacutewkowych Linkerjest to czasami jeden program połączony z kompilatorem Wywoływany jest on naogoacuteł automatycznie przez kompilator w wyniku czego dostajemy gotowy program douruchomienia

Debuger (opcjonalnie według potrzeb)

17

18 ROZDZIAŁ 3 CZEGO POTRZEBUJESZ

Debugger jest to program ktoacutery umożliwia prześledzenie(określenie wartości poszcze-goacutelnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijcewykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu Używasię go w celu określenia czemu nasz program nie działa po naszej myśli lub czemu pro-gram niespodziewanie kończy działanie bez powodu Aby użyć debuggera kompilatormusi dołączyć kod źroacutedłowy do gotowego skompilowanego programu Przykładowymidebuggerami są gdb pod Linuksem lub debugger firmy Borland pod Windowsa

edytora tekstowego

Systemy uniksowe oferują wiele edytoroacutew przydatnych dla programisty jak choćbyvim i Emacs w trybie tekstowym Kate w KDE czy gedit w GNOME Windows maedytor całkowicie wystarczający do pisania programoacutew w C mdash nieśmiertelny Notatnikmdash ale z łatwością znajdziesz w Internecie wiele wygodniejszych narzędzi takich jak npNotepad++ Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE

dużo chęci i dobrej motywacji

32 Zintegrowane Środowiska ProgramistyczneZamiast osobnego kompilatora i edytora możesz wybrać Zintegrowane Środowisko Progra-mistyczne (Integrated Development Environment IDE) IDE jest zestawem wszystkich pro-gramoacutew ktoacutere potrzebuje programista najczęściej z interfejsem graficznym IDE zawierakompilator linker i edytor z reguły roacutewnież debugger

Bardzo popularny IDE to płatny (istnieje także jego darmowa wersja) Microso VisualC++ (MS VC++) popularne darmowe IDE to np

CodeBlocks dla Windows jak i Linux dostępny na stronie wwwcodeblocksorg

KDevelop (Linux) dla KDE

NetBeans multiplatformowy darmowy do ściągnięcia na stronie wwwnetbeansorg

Eclipse z wtyczką CDT (wspoacutełpracuje z MinGW i GCC)

Borland C++ Builder dostępny za darmo do użytku prywatnego

Xcode dlaMac OS X i nowszy kompatybilny z procesorami PowerPC i Intel (moż-liwość stworzenia Universal Binary)

Geany dla systemoacutewWindows i Linux wspoacutełpracuje zMinGW iGCCwwwgeanyorg

Pelles C wwwsmorgasbordetcom

Dev-C++ dla Windows dostępny na stronie wwwbloodshednet

33 Dodatkowe narzędziaWśroacuted narzędzi ktoacutere nie są niezbędne ale zasługują na uwagę można wymienić Valgrindandash specjalnego rodzaju debugger Valgrind kontroluje wykonanie programu i wykrywa nie-prawidłowe operacje w pamięci oraz wycieki pamięci Użycie Valgrinda jest proste mdash kom-pilujemy program jak do debugowania następnie podajemy jako argument Valgrindowi

Rozdział 4

Używanie kompilatora

Język C jest językiem kompilowanym co oznacza że potrzebuje specjalnego programu mdashkompilatora mdash ktoacutery tłumaczy kod źroacutedłowy pisany przez człowieka na język rozkazoacutew da-nego komputera W skroacutecie działanie kompilatora sprowadza się do czytania tekstowegopliku z kodem programu raportowania ewentualnych błędoacutew i produkowania pliku wyniko-wego

Kompilator uruchamiamy ze Zintegrowanego Środowiska Programistycznego lub z kon-soli (linii poleceń) Przejść do konsoli można dla systemoacutew typu UNIX w trybie graficz-nym użyć programoacutew gnome-terminal konsole albo xterm w Windows ldquoWiersz poleceniardquo(można go znaleźćwmenuAkcesoria albo uruchomićwpisującw Start -gtUruchom ldquocmdrdquo)

41 GCCZobacz w Wikipedii GCC

GCC jest to darmowy zestaw kompilatoroacutew min języka C rozwijany w ramach projektuGNU Dostępny jest on na dużą ilość platform sprzętowych obsługiwanych przez takie sys-temy operacyjne jak AIX BSD Linux Mac OS X SunOS Windows Na niektoacuterych sys-temach (np Windows) nie jest on jednak dostępny automatycznie Należy zainstalowaćodpowiednie narzędza (poprzedni rozdział)

Aby skompilować kod języka C za pomocą kompilatora GCC napisany wcześniej w do-wolnym edytorze tekstu należy uruchomić program z odpowiednimi parametrami Podsta-wowym parametrem ktoacutery jest wymagany jest nazwa pliku zawierającego kod programuktoacutery chcemy skompilować

gcc kodc

Rezultatem kompilacji będzie plik wykonywalny z domyślną nazwą (w systemach Unixjest to ldquoaoutrdquo) Jest to metoda niezalecana ponieważ jeżeli skompilujemy w tym samymkatalogu kilka plikoacutew z kodem kolejne pliki wykonywalne zostaną nadpisane i w rezultacieotrzymamy tylko jeden (ten ostatni) skompilowany kod Aby wymusić na GCC nazwę plikuwykonywalnego musimy skorzystać z parametru ldquo-o ltnazwagtrdquo

gcc -o program kodc

W rezultacie otrzymujemy plik wykonywalny o nazwie programPracując nad złożonym programem składającym się z kilku plikoacutew źroacutedłowych (c) mo-

żemy skompilować je niezależnie od siebie tworząc tak zwane pliki typu obiekt z rozszerze-niem o (ang Object File) Następnie możemy stworzyć z nich jednolity program w procesie

19

20 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

konsolidacji (linkowaniu) Jest to bardzo wygodne i praktyczne rozwiązanie ze względu nato iż nie jesteśmy zmuszeni kompilować wszystkich plikoacutew tworzących program za każdymrazem na nowo a jedynie te w ktoacuterych wprowadziliśmy zmiany Aby skompilować plik bezlinkowania używamy parametru ldquo-c ltplikgtrdquo

gcc -o program1o -c kod1cgcc -o program2o -c kod2c

Otrzymujemy w ten sposoacuteb pliki typu obiekt programo i programo A następnie two-rzymy z nich program głoacutewny

gcc -o program program1o program2o

Możemy użyć roacutewnież flag min aby włączyć dokładne rygorystyczne sprawdzanie na-pisanego kodu (co może być przydatne jeśli chcemy dążyć do perfekcji) używamy przełącz-nikoacutew

gcc kodc -o program -Werror -Wall -W -pedantic -ansi

Więcej informacji na temat parametroacutew i działania kompilatora GCC można znaleźć na

Strona domowa projektu GNU GCC

Kroacutetki przekrojowy opis GCC po polsku

Strona podręcznika systemu UNIX (man)

42 BorlandZobacz podręcznik Borland C++ Compiler

43 Czytanie komunikatoacutew o błędaJedną z najbardziej podstawowych umiejętności ktoacutere musi posiąść początkujący progra-mista jest umiejętność rozumienia komunikatoacutew o roacuteżnego rodzaju błędach ktoacutere sygnali-zuje kompilator Wszystkie te informacje pomogą Ci szybko wychwycić ewentualne błędy(ktoacuterych na początku zawsze jest bardzo dużo) Nie martw się że na początku dość częstobędziesz oglądał wydruki błędoacutew zasygnalizowanych przez kompilator mdash nawet zaawanso-wanym programistom się to zdarza Kompilator ma za zadanie pomoacutec Ci w szybkiej popra-wie ewentualnych błędoacutew dlatego też umiejętność analizy komunikatoacutew o błędach jest takważna

431 GCC

Kompilator jest w stanie wychwycić błędy składniowe ktoacutere z pewnością będziesz popełniałKompilator GCC wyświetla je w następującej formie

nazwa_plikucnumer_linijkiopis błędu

Kompilator dość często podaje także nazwę funkcji w ktoacuterej wystąpił błąd Przykładowobłąd deklaracji zmiennej w pliku testc

43 CZYTANIE KOMUNIKATOacuteW O BŁĘDACH 21

include ltstdiohgt

int main ()

intr rprintf (dn r)

Spowoduje wygenerowanie następującego komunikatu o błędzie

testc In function lsquomainrsquotestc5 error lsquointrrsquo undeclared (first use in this function)testc5 error (Each undeclared identifier is reported only oncetestc5 error for each function it appears in)testc5 error syntax error before lsquorrsquotestc6 error lsquorrsquo undeclared (first use in this function)

Co widzimy w raporcie o błędach W linii użyliśmy nieznanego (undeclared) identy-fikatora intr mdash kompilator moacutewi że nie zna tego identyfikatora jest to pierwsze użycie wdanej funkcji i że więcej nie ostrzeże o użyciu tego identyfykatora w tej funkcji Ponieważintr nie został rozpoznany jako żaden znany typ linijka intr r nie została rozpoznana jakodeklaracja zmiennej i kompilator zgłasza błąd składniowy (syntax error) W konsekwencji rnie zostało rozpoznane jako zmienna i kompilator zgłosi to jeszcze w następnej linijce gdzieużywamy r

22 ROZDZIAŁ 4 UŻYWANIE KOMPILATORA

Rozdział 5

Pierwszy program

51 Twoacutej pierwszy program

Przyjęło się że pierwszy program napisany w dowolnym języku programowania powinienwyświetlić tekst ldquoHello Worldrdquo (Witaj Świecie) Zauważ że sam język C nie ma żadnychmechanizmoacutew przeznaczonych do wprowadzania i wypisywania danych mdash musimy zatemskorzystać z odpowiadających za to funkcji mdash w tym przypadku printf zawartej w standar-dowej bibliotece C (ang C Standard Library) (podobnie jak w Pascalu używa się do tegoprocedur Pascalowskim odpowiednikiem funkcji printf są procedury writewriteln)

W języku C deklaracje funkcji zawarte są w plika nagłoacutewkowy posiadających naj-częściej rozszerzenie h choć można także spotkać rozszerzenie hpp przy czym to drugiezwykło się stosować w języku C++ (rozszerzenie nie ma swych ldquotechnicznychrdquo korzeni mdash jestto tylko pewna konwencja) W celu umieszczenia w swoim kodzie pewnego pliku nagłoacutewko-wego używamy dyrektywy kompilacyjnej include Przed procesem kompilacji w miejscetej dyrektywy wstawiana jest treść podanego pliku nagłoacutewkowego dostarczając deklaracjifunkcji

Poniższy przykład obrazuje jak przy użyciu dyrektywy include umieścimyw kodzie plikstandardowej biblioteki C stdioh (Standard InputOutputHeaderfile) zawierającą definicjęfunkcji printf

include ltstdiohgt

W nawiasach troacutejkątnych lt gt umieszcza się nazwy standardowych plikoacutew nagłoacutewko-wych1 Żeby włączyć inny plik nagłoacutewkowy (np własny) znajdujący się w katalogu z kodemprogramu trzeba go wpisać w cudzysłoacutew

include moacutej_plik_nagłoacutewkowyh

Mamy więc funkcję printf jak i wiele innych do wprowadzania i wypisywania danychczas na pisanie programu

W programie definujemy głoacutewną funkcję main uruchamianą przy starcie programu za-wierającą właściwy kod Definicja funkcji zawiera oproacutecz nazwy i kodu także typ wartościzwracanej i argumentoacutew pobieranych Konstrukcja funkcji main

1Domyślne pliki nagłoacutewkowe znajdują się w katalogu z plikami nagłoacutewkowymi kompilatora W systemach zrodziny Unix będzie to katalog usrinclude natomiast w systemie Windows oacutew katalog będzie umieszczony wkatalogu z kompilatorem

23

24 ROZDZIAŁ 5 PIERWSZY PROGRAM

int main (void)

Typem zwracany przez funkcję jest int (Integer) czyli liczba całkowita (w przypadkumainbędzie to kod wyjściowy programu) W nawiasach umieszczane są argumenty funkcji tutajzapis void oznacza ich pominięcie Funkcja main jako argumenty może pobierać parametrylinii poleceń z jakimi program został uruchomiony i pełną ścieżkę do katalogu z programem

Kod funkcji umieszcza się w nawiasach klamrowych i Wewnątrz funkcji możemy wpisać poniższy kod

printf(Hello World)return 0

Wszystkie polecenia kończymy średnikiem return określa wartość jaką zwroacuteci funkcja(program) Liczba zero zwracana przez funkcję main() oznacza że program zakończył siębez błędoacutew błędne zakończenie często (choć nie zawsze) określane jest przez liczbę jeden2Funkcję main kończymy nawiasem klamrowym zamykającym

Twoacutej kod powinien wyglądać jak poniżej

include ltstdiohgtint main (void)

printf (Hello World)return 0

Teraz wystarczy go tylko skompilować i uruchomić

52 Rozwiązywanie problemoacutewJeśli nie możesz skompilować powyższego programu to najprawdopodobniej popełniłeś li-teroacutewkę przy ręcznym przepisywaniu go Zobacz też instrukcje na temat używania kompi-latora

Może też się zdarzyć że program skompiluje się uruchomi ale jego efektu działania niebędzie widać Dzieje się tak ponieważ nasz pierwszy program po prostu wypisuje komunikati od razu kończy działanie nie czekając na reakcję użytkownika Nie jest to problememgdy program jest uruchamiany z konsoli tekstowej ale w innych przypadkach nie widzimyefektoacutew jego działania

Jeśli korzystasz ze Zintegrowanego Środowiska Programistycznego (ang IDE) możeszzaznaczyć by nie zamykało ono programu po zakończeniu jego działania Innym sposobemjest dodanie instrukcji ktoacutere wstrzymywałyby zakończenie programu Można to zrobić do-dając przed linią z return funkcję pobierającą znak z wejścia

getchar()

2Jeżeli chcesz mieć pewność że twoacutej program będzie działał poprawnie roacutewnież na platformach gdzie 1 oznaczapoprawne zakończenie (lub nie oznacza nic) możesz skorzystać z makr EXIT SUCCESS i EXIT FAILURE zdefiniowanychw pliku nagłoacutewkowym stdlibh

52 ROZWIĄZYWANIE PROBLEMOacuteW 25

Jest też prostszy (choć nieprzenośny) sposoacuteb mianowicie wywołanie polecenia systemo-wego W zależności od używanego systemu operacyjnego mamy do dyspozycji roacuteżne po-lecenia powodujące roacuteżne efekty Do tego celu skorzystamy z funkcji system() ktoacutera jakoparametr przyjmuje polecenie systemowe ktoacutere chcemy wykonać np

Rodzina systemoacutew UnixLinux

system(sleep 10) oczekiwanie 10 s system(read discard) oczekiwanie na wpisanie tekstu

Rodzina systemoacutew oraz MS Windows

system(pause) oczekiwanie na wciśnięcie dowolnego klawisza

Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnychWindows w ktoacuterychto z reguły pracuje się w trybie okienkowym a z konsoli korzystamy tylko podczas urucha-mianiu programu Z kolei w systemach UnixLinux jest ona praktycznie w ogoacutele nie używanaw tym celu ze względu na uruchamianie programu bezpośrednio z konsoli

26 ROZDZIAŁ 5 PIERWSZY PROGRAM

Rozdział 6

Podstawy

Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogoacutel-nych informacji

61 Kompilacja Jak działa C

Jak każdy język programowania C sam w sobie jest niezrozumiały dla procesora Został onstworzony w celu umożliwienia ludziom łatwego pisania kodu ktoacutery może zostać przetwo-rzony na kod maszynowy Program ktoacutery zamienia kod C na wykonywalny kod binarnyto kompilator Jeśli pracujesz nad projektem ktoacutery wymaga kilku plikoacutew kodu źroacutedłowego(np pliki nagłoacutewkowe) wtedy jest uruchamiany kolejny program mdash linker Linker służy dopołączenia roacuteżnych plikoacutew i stworzenia jednej aplikacji lub biblioteki (library) Bibliotekajest zestawem procedur ktoacutery sam w sobie nie jest wykonywalny ale może być używanaprzez inne programy Kompilacja i łączenie plikoacutew są ze sobą bardzo ściśle powiązane stądsą przez wielu traktowane jako jeden proces Jedną rzecz warto sobie uświadomić mdash kompila-cja jest jednokierunkowa przekształcenie kodu źroacutedłowego C w kod maszynowy jest bardzoproste natomiast odwrotnie mdash nie Dekompilatory co prawda istnieją ale rzadko tworząużyteczny kod C

Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collec-tion dostępny na stronie gccgnuorg

62 Co może C

Pewnie zaskoczy Cię to że tak naprawdę ldquoczystyrdquo język C nie może zbyt wiele Język Cw grupie językoacutew programowania wysokiego poziomu jest stosunkowo nisko Dzięki temukod napisany w języku C można dość łatwo przetłumaczyć na kod asemblera Bardzo łatwojest też łączyć ze sobą kod napisany w języku asemblera z kodem napisanym w C Dla bar-dzo wielu ludzi przeszkodą jest także dość duża liczba i częsta dwuznaczność operatoroacutewPoczątkujący programista czytający kod programu w C może odnieść bardzo nieprzyjemnewrażenie ktoacutere można opisać cytatem ldquoja nigdy tego nie opanujęrdquo Wszystkie te elementyjęzyka C ktoacutere wydają Ci się dziwne i nielogiczne wmiarę jak będziesz nabierał doświadcze-nia nagle okażą się całkiem przemyślanie dobrane i takie a nie inne konstrukcje przypadnąCi do gustu Dalsza lektura tego podręcznika oraz zaznajamianie się z funkcjami z roacuteżnychbibliotek ukażą Ci całą gamę możliwości ktoacutere daje język C doświadczonemu programiście

27

28 ROZDZIAŁ 6 PODSTAWY

63 Struktura blokowaTeraz omoacutewimy podstawową strukturę programu napisanego w C Jeśli miałeś styczność zjęzykiem Pascal to pewnie słyszałeś o nim że jest to język programowania strukturalny WC nie ma tak ścisłej struktury blokowej mimo to jest bardzo ważne zrozumienie co oznaczastruktura blokowa Blok jest grupą instrukcji połączonych w ten sposoacuteb że są traktowanejak jedna całość W C blok zawiera się pomiędzy nawiasami klamrowymi Blok możetakże zawierać kolejne bloki

Zawartość bloku Generalnie blok zawiera ciąg kolejno wykonywanych poleceń Polece-nia zawsze (z nielicznymi wyjątkami) kończą się średnikiem () W jednej linii może znajdo-wać się wiele poleceń choć dla zwiększenia czytelności kodu najczęściej pisze się pojedyncząinstrukcję w każdej linii Jest kilka rodzajoacutew poleceń np instrukcje przypisania warunkoweczy pętli W dużej części tego podręcznika będziemy zajmować się właśnie instrukcjami

Pomiędzy poleceniami są roacutewnież odstępymdash spacje tabulacje oraz przejścia do następnejlinii przy czym dla kompilatora te trzy rodzaje odstępoacutew mają takie samo znaczenie Dlaprzykładu poniższe trzy fragmenty kodu źroacutedłowego dla kompilatora są takie same

printf(Hello world) return 0

printf(Hello world)return 0

printf(Hello world)

return 0

W tej regule istnieje jednak jeden wyjątek Dotyczy on stałych tekstowych W powyż-szych przykładach stałą tekstową jest ldquoHello worldrdquo Gdy jednak rozbijemy ten napis kom-pilator zasygnalizuje błąd

printf(Helloworld)return 0

Należy tylko zapamiętać że stałe tekstowe powinny zaczynać się i kończyć w tej samejlini (można ominąć to ograniczenie mdash więcej w rozdziale Napisy) Oproacutecz tego jednego przy-padku dla kompilatora ma znaczenie samo istnienie odstępu a nie jego wielkość czy rodzajJednak stosowanie odstępoacutew jest bardzo ważne dla zwiększenia czytelności kodu mdash dziękiczemu możemy zaoszczędzić sporo czasu i nerwoacutew ponieważ znalezienie błędu (ktoacutere sięzdarzają każdemu) w nieczytelnym kodzie może być bardzo trudne

64 ZasięgPojęcie to dotyczy zmiennych (ktoacutere przechowują dane przetwarzane przez program) Wkaż-dym programie (oproacutecz tych najprostszych) są zaroacutewno zmienne wykorzystywane przez całyczas działania programu oraz takie ktoacutere są używane przez pojedynczy blok programu (npfunkcję) Na przykład w pewnym programie w pewnym momencie jest wykonywane skom-plikowane obliczenie ktoacutere wymaga zadeklarowania wielu zmiennych do przechowywaniapośrednich wynikoacutew Ale przez większą część tego działania te zmienne są niepotrzebne

65 FUNKCJE 29

i zajmują tylko miejsce w pamięci mdash najlepiej gdyby to miejsce zostało zarezerwowane tużprzed wykonaniemwspomnianych obliczeń a zaraz po ich wykonaniu zwolnione Dlatego wC istnieją zmienne globalne oraz lokalne Zmienne globalne mogą być używane w każdymmiejscu programu natomiast lokalne mdash tylko w określonym bloku czy funkcji (oraz blokachw nim zawartych) Generalnie mdash zmienna zadeklarowanaw danym bloku jest dostępna tylkowewnątrz niego

65 Funkcje

Funkcje są ściśle związane ze strukturą blokową mdash funkcją jest po prostu blok instrukcjiktoacutery jest potem wywoływany w programie za pomocą pojedynczego polecenia Zazwyczajfunkcja wykonuje pewne określone zadanie np we wspomnianym programie wykonują-cym pewne skomplikowane obliczenie Każda funkcja ma swoją nazwę za pomocą ktoacuterejjest potem wywoływana w programie oraz blok wykonywanych poleceń Wiele funkcji po-biera pewne dane czyli argumenty funkcji wiele funkcji także zwraca pewną wartość pozakończeniu wykonywania Dobrym nawykiem jest dzielenie dużego programu na zestawmniejszych funkcji mdash dzięki temu będziesz moacutegł łatwiej odnaleźć błąd w programie

Jeśli chcesz użyć jakiejś funkcji to powinieneś wiedzieć

jakie zadanie wykonuje dana funkcja

rodzaj wczytywanych argumentoacutew i do czego są one potrzebne tej funkcji

rodzaj zwroacuteconych danych i co one oznaczają

W programach w języku C jedna funkcja ma szczegoacutelne znaczenie mdash jest tomain() Funk-cję tę zwaną funkcją głoacutewną musi zawierać każdy program W niej zawiera się głoacutewny kodprogramu przekazywane są do niej argumenty z ktoacuterymi wywoływany jest program (jakoparametry argc i argv) Więcej o funkcji main() dowiesz się poacuteźniej w rozdziale Funkcje

66 Biblioteki standardowe

Język C w przeciwieństwie do innych językoacutew programowania (np Fortranu czy Pascala)nie posiada absolutnie żadny słoacutew kluczowych ktoacutere odpowiedzialne by były za obsługęwejścia i wyjścia Może się to wydawać dziwne mdash język ktoacutery sam w sobie nie posiadapodstawowych funkcji musi być językiem o ograniczonym zastosowaniu Jednak brak pod-stawowych funkcji wejścia-wyjścia jest jedną z największych zalet tego języka Jego składniaopracowana jest tak by można było bardzo łatwo przełożyć ją na kod maszynowy To wła-śnie dzięki temu programy napisane w języku C są takie szybkie Pozostaje jednak pytaniemdash jak umożliwić programom komunikację z użytkownikiem

W roku kiedy zapoczątkowano prace nad standaryzacją C zdecydowano że po-winien być zestaw instrukcji identycznych w każdej implementacji C Nazwano je BibliotekąStandardową (czasemnazywaną ldquolibcrdquo) Zawiera ona podstawowe funkcje ktoacutere umożliwiająwykonywanie takich zadań jak wczytywanie i zwracanie danych modyfikowanie zmiennychłańcuchowych działania matematyczne operacje na plikach i wiele innych jednak nie za-wiera żadnych funkcji ktoacutere mogą być zależne od systemu operacyjnego czy sprzętu jakgrafika dźwięk czy obsługa sieci W programie ldquoHello Worldrdquo użyto funkcji z biblioteki stan-dardowej mdash printf ktoacutera wyświetla na ekranie sformatowany tekst

30 ROZDZIAŁ 6 PODSTAWY

67 Komentarze i stylKomentarze mdash to tekst włączony do kodu źroacutedłowego ktoacutery jest pomijany przez kompilatori służy jedynie dokumentacji W języku C komentarze zaczynają się od

a kończą

Dobre komentowanie ma duże znaczenie dla rozwijania oprogramowania nie tylko dlategoże inni będą kiedyś potrzebowali przeczytać napisany przez ciebie kod źroacutedłowy ale takżemożesz chcieć po dłuższym czasie powroacutecić do swojego programu i możesz zapomnieć doczego służy dany blok kodu albo dlaczego akurat użyłeś tego polecenia a nie innego Wchwili pisania programu to może być dla ciebie oczywiste ale po dłuższym czasie możeszmieć problemy ze zrozumieniem własnego kodu Jednak nie należy też wstawiać zbyt dużokomentarzy ponieważ wtedy kod może stać się jeszcze mniej czytelny mdash najlepiej komen-tować fragmenty ktoacutere nie są oczywiste dla programisty oraz te o szczegoacutelnym znaczeniuAle tego nauczysz się już w praktyce

Dobry styl pisania kodu jest o tyle ważny że powinien on być czytelny i zrozumiały po tow końcu wymyślono języki programowania wysokiego poziomu (w tym C) aby kod było ła-two zrozumieć ) I tak mdash należy stosować wcięcia dla odroacuteżnienia blokoacutew kolejnego poziomu(zawartych w innym bloku podrzędnych) nawiasy klamrowe otwierające i zamykające blokpowinny mieć takie same wcięcia staraj się aby nazwy funkcji i zmiennych kojarzyły się zzadaniem jakie dana funkcja czy zmienna pełni w programie W dalszej części podręcznikamożesz napotkać więcej zaleceń dotyczących stylu pisania kodu Staraj się stosować do tychzaleceń mdash dzięki temu kod pisanych przez ciebie programoacutew będzie łatwiejszy do czytania izrozumienia

Jeśli masz doświadczenia z językiem C++ pamiętaj że w C nie powinno się stosowaćkomentarzy zaczynających się od dwoacutech znakoacutew slash tak nie komentujemy w CJest to niezgodne ze standardem ANSI C i niektoacutere kompilatory mogą nie skompilować koduz komentarzami w stylu C++ (choć standard ISO C dopuszcza komentarze w stylu C++)

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentoacutew kodu Jeśli częśćprogramu źle działa i chcemy ją chwilowo wyłączyć albo fragment kodu jest nam już nie-potrzebny ale mamy wątpliwości czy w przyszłości nie będziemy chcieli go użyć mdash umiesz-czamy go po prostu wewnątrz komentarza

Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uważać najedną subtelność Otoacuteż komentarze w języku C nie mogą być zagnieżdżone Trzebana to uważać gdy chcemy objąć komentarzem obszar w ktoacuterym już istnieje komentarz (na-leży wtedy usunąć wewnętrzny komentarz) W nowszym standardzie C dopuszcza się abykomentarz typu zawierał w sobie komentarz

671 Po polsku czy angielsku

Jak jużwcześniej byłowspomniane zmiennym i funkcjom powinno się nadawać nazwy ktoacutereodpowiadają ich znaczeniu Zdecydowanie łatwiej jest czytać kod gdy średnią liczb przecho-wuje zmienna srednia niż a a znajdowaniemmaksimumw ciągu liczb zajmuje się funkcja maxalbo znajdz max niż nazwana f Często nazwy funkcji to właśnie czasowniki

67 KOMENTARZE I STYL 31

Powstaje pytanie w jakim języku należy pisać nazwy Jeśli chcemy by nasz kod mogłyczytać osoby nieznające polskiego mdash warto użyć języka angielskiego Jeśli nie mdash można bezproblemu użyć polskiego Bardzo istotne jest jednak by nie mieszać językoacutew Jeśli zdecy-dowaliśmy się używać polskiego używajmy go od początku do końca przeplatanie ze sobądwoacutech językoacutew robi złe wrażenie

Warto roacutewnież zdecydować się na sposoacuteb zapisywania nazw składających się z więcej niżjednego słowa Istnieje kilka możliwości najważniejsze z nich

oddzielanie podkreśleniem int to str

ldquokonwencja pascalowskardquo każde słowo dużą literą IntToStr

ldquokonwencja wielbłądziardquo pierwsze słowo małą kolejne dużą literą intToStr

Ponownie najlepiej stosować konsekwentnie jedną z konwencji i nie mieszać ze sobąkilku

672 Notacja węgierska

Czasem programista może zapomnieć jakiego typu była dana zmienna Wtedy musi znaleźćodpowiednią deklarację (co nie zawsze jest łatwe) Dlatego więc wymyślono sposoacuteb by temuzaradzić Pomyślano by w nazwie zmiennej (bądź wskaźnika na zmienną) napisać jakiegojest ona typu np

a liczba (liczba typu int)

w ll dlugaLiczba (wskaźnik na zmienną typu long long)

t5x5 ch tabliczka (tablica x elementoacutew typu char)

func i silnia (funkcja zwracająca int)

Jest to bardzo wygodne przy bardzo zagmatwanych zmiennych

w t4 w t2x2 s pomieszaniec (wskaźnik na tablicę czterech wskaźnikoacutew na tablice dwu-wymiarowe zmiennych typu short)

Lub gdy nie pamiętamy wymiaroacutew tablicy

t4x5x6 f powalonaKostkaRubika (od razu wiemy żet4x5x6 f powalonaKostkaRubika[5][4][6] jest niewłaściwe)

Taki zapis ma też swoje wady Gdy zdecydujemy się zmienić typ zmiennej zamiast poprostu przemienić w deklaracji int na long musimy zmieniać nazwy w całym programieCzęsto takie nazwy są po prostu długie i nie chce nam się ich pisać (no coacuteż programista teżczłowiek) więc wolimy wprowadzić pomieszaniec zamiast w t4 w t2x2 s pomieszaniec Naj-ważniejsze to jednak trzymać się rozwiązania ktoacutere wybraliśmy na początku bo mieszaniejest przerażające

32 ROZDZIAŁ 6 PODSTAWY

68 PreprocesorNie cały napisany przez ciebie kod będzie przekształcany przez kompilator bezpośrednio nakodwykonywalny programu Wwielu przypadkach będziesz używać poleceń ldquoskierowanychdo kompilatorardquo tzw dyrektyw kompilacyjnych Na początku procesu kompilacji specjalnypodprogram tzw preprocesor wyszukuje wszystkie dyrektywy kompilacyjne i wykonujeodpowiednie akcje mdash ktoacutere polegają notabene na edycji kodu źroacutedłowego (np wstawieniudeklaracji funkcji zamianie jednego ciągu znakoacutew na inny) Właściwy kompilator zamie-niający kod C na kod wykonywalny nie napotka już dyrektyw kompilacyjnych ponieważzostały one przez preprocesor usunięte po wykonaniu odpowiednich akcji

W C dyrektywy kompilacyjne zaczynają się od znaku hash () Przykładem najczęściejużywanej dyrektywy jest include ktoacutera jest użyta nawet w tak prostym programie jakldquoHello Worldrdquo include nakazuje preprocesorowi włączyć (ang include) w tym miejscuzawartość podanego pliku tzw pliku nagłoacutewkowego najczęściej to będzie plik zawierającyfunkcje z ktoacuterejś biblioteki standardowej (stdioh mdash STandard Input-Output rozszerzenie hoznacza plik nagłoacutewkowy C) Dzięki temu zamiast wklejać do kodu swojego programu dekla-racje kilkunastu a nawet kilkudziesięciu funkcji wystarczy wpisać jedną magiczną linijkę

69 Nazwy zmienny stały i funkcjiIdentyfikatory czyli nazwy zmiennych stałych i funkcji mogą składać się z liter (bez polskichznakoacutew) cyfr i znaku podkreślenia z tym że nazwa taka nie może zaczynać się od cyfry Niemożna używać nazw zarezerwowanych (patrz Składnia)

Przykłady błędnych nazw

2liczba (nie można zaczynać nazwy od cyfry)moja funkcja (nie można używać spacji)$i (nie można używać znaku $)if (if to słowo kluczowe)

Aby kod był bardziej czytelny przestrzegajmy poniższych (umownych) reguł

nazwy zmiennych piszemy małymi literami i file

nazwy stałych (zadeklarowanych przy pomocy define) piszemy wielkimi literamiSIZE

nazwy funkcji piszemy małymi literami print

wyrazy w nazwach oddzielamy znakiem podkreślenia open file close all files

Są to tylko konwencje mdash żaden kompilator nie zgłosi błędu jeśli wprowadzimy swoacutej wła-sny system nazewnictwa Jednak warto pamiętać że być może nad naszym kodem będą pra-cowali także inni programiści ktoacuterzy mogą mieć trudności z analizą kodu niespełniającegopewnych zasad

Rozdział 7

Zmienne

Procesor komputera stworzony jest tak aby przetwarzał dane znajdujące się w pamięci kom-putera Z punktu widzenia programu napisanego w języku C (ktoacutery jak wiadomo jest języ-kiem wysokiego poziomu) dane umieszczane są w postaci tzw zmienny Zmienne uła-twiają programiście pisanie programu Dzięki nim programista nie musi się przejmowaćgdzie w pamięci owe zmienne się znajdują tzn nie operuje fizycznymi adresami pamięcijak np 0x14613467 tylko prostą do zapamiętania nazwą zmiennej

71 Czym są zmienneZmienna jest to pewien fragment pamięci o ustalonym rozmiarze ktoacutery posiada własny iden-tyfikator (nazwę) oraz może przechowywać pewną wartość zależną od typu zmiennej

711 Deklaracja zmienny

Aby moacutec skorzystać ze zmiennej należy ją przed użyciem zadeklarować to znaczy poinfor-mować kompilator jak zmienna będzie się nazywać i jaki typ ma mieć Zmienne deklarujesię w sposoacuteb następujący

typ nazwa_zmiennej

Oto deklaracja zmiennej o nazwie ldquowiekrdquo typu ldquointrdquo czyli liczby całkowitej

int wiek

Zmiennej w momencie zadeklarowania można od razu przypisać wartość

int wiek = 17

W języku C zmienne deklaruje się na samym początku bloku (czyli przed pierwszą in-strukcją)

int wiek = 17printf(dn wiek)int kopia_wieku tu stary kompilator C zgłosi błąd

deklaracja występuje po instrukcji (printf) kopia_wieku = wiek

33

34 ROZDZIAŁ 7 ZMIENNE

Według nowszych standardoacutewmożliwe jest deklarowanie zmiennej w dowolnymmiejscuprogramu ale wtedy musimy pamiętać aby zadeklarować zmienną przed jej użyciem Toznaczy że taki kod jest niepoprawny

printf (Mam d latn wiek)int wiek = 17

Należy go zapisać tak

int wiek = 17printf (Mam d latn wiek)

Język C nie inicjalizuje zmiennych lokalnych Oznacza to że w nowo zadeklarowanejzmiennej znajdują się śmieci - to co wcześniej zawierał przydzielony zmiennej fragmentpamięci Aby uniknąć ciężkich do wykrycia błędoacutew dobrze jest inicjalizować (przypisywaćwartość) wszystkie zmienne w momencie zadeklarowania

712 Zasięg zmiennej

Zmienne mogą być dostępne dla wszystkich funkcji programu mdash nazywamy je wtedy zmien-nymi globalnymi Deklaruje się je przed wszystkimi funkcjami programu

include ltstdiohgt

int ab nasze zmienne globalne

void func1 ()

instrukcje a=3 dalsze instrukcje

int main ()

b=3a=2return 0

Zmienne globalne jeśli programista nie przypisze im innej wartości podczas definiowa-nia są inicjalizowane wartością

Zmienne ktoacutere funkcja deklaruje do ldquowłasnych potrzebrdquo nazywamy zmiennymi lokal-nymi Nasuwa się pytanie ldquoczy będzie błędem nazwanie tą samą nazwą zmiennej globalneji lokalnejrdquo Otoacuteż odpowiedź może być zaskakująca nie Natomiast w danej funkcji da sięużywać tylko jej zmiennej lokalnej Tej konstrukcji należy z wiadomych względoacutew unikać

int a=1 zmienna globalna

int main()

71 CZYM SĄ ZMIENNE 35

int a=2 to już zmienna lokalna printf(d a) wypisze 2

713 Czas życia

Czas życia to czas od momentu przydzielenia dla zmiennej miejsca w pamięci (stworzenieobiektu) do momentu zwolnienia miejsca w pamięci (likwidacja obiektu)

Zakres ważności to część programu w ktoacuterej nazwa znana jest kompilatorowi

main()

int a = 10 otwarcie lokalnego bloku

int b = 10printf(d d a b)

zamknięcie lokalnego bloku zmienna b jest usuwana

printf(d d a b) BŁĄD b juz nie istnieje tu usuwana jest zmienna a

Zdefiniowaliśmy dwie zmienne typu int Zaroacutewno a i b istnieją przez cały program (czasżycia) Nazwa zmiennej a jest znana kompilatorowi przez cały program Nazwa zmiennej bjest znana tylko w lokalnym bloku dlatego nastąpi błąd w ostatniej instrukcji

Niektoacutere kompilatory (prawdopodobniemożna tu zaliczyćMicrosoVisual C++ dowersji) uznają powyższy kod za poprawny W dodatku można ustawić w opcjach niektoacuterychkompilatoroacutew zachowanie w takiej sytuacji włącznie z zachowaniami niezgodnymi ze stan-dardem języka

Możemy świadomie ograniczyć ważność zmiennej do kilku linijek programu (tak jak ro-biliśmy wyżej) tworząc blok Nazwa zmiennej jest znana tylko w tym bloku

714 Stałe

Stała roacuteżni się od zmiennej tylko tym że nie można jej przypisać innej wartości w trak-cie działania programu Wartość stałej ustala się w kodzie programu i nigdy ona nie ulegazmianie Stałą deklaruje się z użyciem słowa kluczowego const w sposoacuteb następujący

const typ nazwa_stałej=wartość

Dobrze jest używać stałych w programie ponieważ unikniemy wtedy przypadkowychpomyłek a kompilator może często zoptymalizować ich użycie (np od razu podstawiając ichwartość do kodu)

36 ROZDZIAŁ 7 ZMIENNE

const int WARTOSC_POCZATKOWA=5int i=WARTOSC_POCZATKOWAWARTOSC_POCZATKOWA=4 tu kompilator zaprotestuje int j=WARTOSC_POCZATKOWA

Przykład pokazuje dobry zwyczaj programistyczny jakim jest zastępowanie umieszczo-nych na stałe w kodzie liczb stałymi W ten sposoacuteb będziemy mieli większą kontrolę nadkodem mdash stałe umieszczone w jednym miejscu można łatwo modyfikować zamiast szukaćpo całym kodzie liczb ktoacutere chcemy zmienić

Nie mamy jednak pełnej gwarancji że stała będzie miała tę samą wartość przez cały czaswykonania programu możliwe jest bowiem dostanie się do wartości stałej (miejsca jej prze-chowywania w pamięci) pośrednio mdash za pomocą wskaźnikoacutew Można zatem dojść do wnio-sku że słowo kluczowe const służy tylko do poinformowania kompilatora aby ten nie zezwa-lał na jawną zmianę wartości stałej Z drugiej strony zgodnie ze standardem proacuteba mody-fikacji wartości stałej ma niezdefiniowane działanie (tzw undefined behaviour) i w związkuz tym może się powieść lub nie ale może też spowodować jakieś subtelne zmiany ktoacutere wefekcie spowodują że program będzie źle działał

Podobnie do zdefiniowania stałej możemy użyć dyrektywy preprocesora define (opi-sanej w dalszej części podręcznika) Tak zdefiniowaną stałą nazywamy stałą symbolicznąW przeciwieństwie do stałej zadeklarowanej z użyciem słowa const stała zdefiniowana przyużyciu define jest zastępowana daną wartością w każdym miejscu gdzie występuje dlategoteż może być używana w miejscach gdzie ldquonormalnardquo stała nie mogłaby dobrze spełnić swejroli

W przeciwieństwie do języka C++ w C stała to cały czas zmienna ktoacuterej kompilatorpilnuje by nie zmieniła się

72 Typy zmienny

Każdy program w C operuje na zmiennych mdash wydzielonych w pamięci komputera obsza-rach ktoacutere mogą reprezentować obiekty nam znane takie jak liczby znaki czy też bardziejzłożone obiekty Jednak dla komputera każdy obszar w pamięci jest taki sam mdash to ciąg zeri jedynek w takiej postaci zupełnie nieprzydatny dla programisty i użytkownika Podczaspisania programu musimy wskazać w jaki sposoacuteb ten ciąg ma być interpretowany

Typ zmiennej wskazuje właśnie sposoacuteb w jaki pamięć w ktoacuterej znajduje się zmiennabędzie wykorzystywana Określając go przekazuje się kompilatorowi informację ile pamięcitrzeba zarezerwować dla zmiennej a także w jaki sposoacuteb wykonywać na nim operacje

Każda zmienna musi mieć określony swoacutej typ w miejscu deklaracji i tego typu nie możejuż zmienić Lecz co jeśli mamy zmienną jednego typu ale potrzebujemy w pewnymmiejscuprogramu innego typu danych W takimwypadku stosujemy konwersję (rzutowanie) jednejzmiennej na inną zmienną Rzutowanie zostanie opisane poacuteźniej w rozdziale Operatory

Istnieją wbudowane i zdefiniowane przez użytkownika typy danych Wbudowane typydanych to te ktoacutere zna kompilator są one w nim bezpośrednio ldquozaszyterdquo Można też tworzyćwłasne typy danych ale należy je kompilatorowi opisać Więcej informacji znajduje się wrozdziale Typy złożone

W języku C wyroacuteżniamy podstawowe typy zmiennych Są to

char mdash jednobajtowe liczby całkowite służy do przechowywania znakoacutew

int mdash typ całkowity o długości domyślnej dla danej architektury komputera

72 TYPY ZMIENNYCH 37

float mdash typ zmiennopozycyjny (zwany roacutewnież zmiennoprzecinkowym) reprezentującyliczby rzeczywiste ( bajty)

double mdash typ zmiennopozycyjny podwoacutejnej precyzji ( bajtoacutew)

Typy zmiennoprzecinkowe zostały dokładnie opisane w IEEE

W języku C nie istnieje specjalny typ zmiennych przeznaczony na zmienne typu logicz-nego (albo ldquoprawda albo ldquofałszrdquo) Jest to inne podejście niż na przykład w językach Pascalalbo Java - definiujących osobny typ ldquobooleanrdquo ktoacuterego nie można ldquomieszaćz innymi typamizmiennych W C do przechowywania wartości logicznych zazwyczaj używa się typu ldquointrdquoWięcej na temat tego jak język C rozumie prawdę i fałsz znajduje się w rozdziale Operatory

721 int

Ten typ przeznaczony jest do liczb całkowitych Liczby temożemy zapisać na kilka sposoboacutew

System dziesiętny

12 13 45 35 itd

System oacutesemkowy (oktalny)

010 czyli 8016 czyli 8 + 6 = 14018 BŁĄD

System ten operuje na cyfrach od do Tak wiec jest niedozwolona Jeżeli chcemyużyć takiego zapisu musimy zacząć liczbę od

System szesnastkowy (heksadecymalny)

0x10 czyli 116 + 0 = 160x12 czyli 116 + 2 = 180xff czyli 1516 + 15 = 255

W tym systemie możliwe cyfry to hellip i dodatkowo a b c d e f ktoacutere oznaczają Aby użyć takiego systemu musimy poprzedzić liczbę ciągiem 0x Wielkośćznakoacutew w takich literałach nie ma znaczenia

Ponadto w niektoacuterych kompilatorach przeznaczonych głoacutewnie domikrokontroleroacutew spo-tyka się jeszcze użycie systemu binarnego Zazwyczaj dodaje się przedrostek 0b przed liczbą(analogicznie do zapisu spotykanego w języku Python) W tym systemie możemy oczywiścieużywać tylko i wyłącznie cyfr i Tego typu rozszerzenie bardzo ułatwia programowanieniskopoziomowe układoacutew Należy jednak pamiętać że jest to tylko i wyłącznie rozszerzenie

38 ROZDZIAŁ 7 ZMIENNE

722 float

Ten typ oznacza liczby zmiennoprzecinkowe czyli ułamki Istnieją dwa sposoby zapisu

System dziesiętny

314 45644 2354 321 itd

System ldquonaukowyrdquo mdash wykładniczy

6e2 czyli 6 102 czyli 60015e3 czyli 15 103 czyli 150034e-3 czyli 34 10minus3 czyli 00034

Należy wziąć pod uwagę że reprezentacja liczb rzeczywistych w komputerze jest niedo-skonała i możemy otrzymywać wyniki o zauważalnej niedokładności

723 double

Doublemdash czyli ldquopodwoacutejnyrdquomdash oznacza liczby zmiennoprzecinkowe podwoacutejnej precyzji Ozna-cza to że liczba taka zajmuje zazwyczaj w pamięci dwa razy więcej miejsca niż float (np bity wobec dla float) ale ma też dwa razy lepszą dokładność

Domyślnie ułamki wpisane w kodzie są typu double Możemy to zmienić dodając nakońcu literę ldquordquo

15f (float)15 (double)

724 ar

Jest to typ znakowy umożliwiający zapis znakoacutew ASCII Może też być traktowany jako liczbaz zakresu Znaki zapisujemywpojedynczych cudzysłowach (czasami nazywanymi apo-strofami) by odroacuteżnić je od łańcuchoacutew tekstowych (pisanych w podwoacutejnych cudzysłowach)

a 7 $

Pojedynczy cudzysłoacutew rsquo zapisujemy tak a null (czyli zero ktoacutere między innymikończy napisy) tak 0 Więcej znakoacutew specjalnych

Warto zauważyć że typ char to zwykły typ liczbowy i można go używać tak samo jaktypu int (zazwyczaj ma jednak mniejszy zakres) Co więcej literały znakowe (np rsquoarsquo) sątraktowane jako liczby i w języku C są typu int (w języku C++ są typu char)

725 void

Słowa kluczowego void można w określonych sytuacjach użyć tam gdzie oczekiwana jestnazwa typu void nie jest właściwym typem bo nie można utworzyć zmiennej takiego typujest to ldquopustyrdquo typ (ang void znaczy ldquopustyrdquo) Typ void przydaje się do zaznaczania żefunkcja nie zwraca żadnej wartości lub że nie przyjmuje żadnych parametroacutew (więcej o tymw rozdziale Funkcje) Można też tworzyć zmienne będące typu ldquowskaźnik na voidrdquo

73 SPECYFIKATORY 39

73 Specyfikatory

Specyfikatory to słowa kluczowe ktoacutere postawione przy typie danych zmieniają jego zna-czenie

731 signed i unsigned

Na początku zastanoacutewmy się jak komputer może przechować liczbę ujemną Otoacuteż w przy-padku przechowywania liczb ujemnych musimyw zmiennej przechować jeszcze jej znak Jakwiadomo zmienna składa się z szeregu bitoacutew W przypadku użycia zmiennej pierwszy bit zlewej strony (nazywany także bitem najbardziej znaczącym) przechowuje znak liczby Efek-tem tego jest spadek ldquopojemnościrdquo zmiennej czyli zmniejszenie największej wartości ktoacuterąmożemy przechować w zmiennej

Signed oznacza liczbę ze znakiem unsigned mdash bez znaku (nieujemną) Mogą być zasto-sowane do typoacutew char i int i łączone ze specyfikatorami short i long (gdy ma to sens)

Jeśli przy signed lub unsigned nie napiszemy o jaki typ nam chodzi kompilator przyjmiewartość domyślną czyli int

Przykładowo dla zmiennej char(zajmującej bitoacutew zapisanej w formacie uzupełnień dodwoacutech) wygląda to tak

signed char a zmienna a przyjmuje wartości od -128 do 127 unsigned char b zmienna b przyjmuje wartości od 0 do 255 unsigned short cunsigned long int d

Jeżeli nie podamy żadnego ze specyfikatora wtedy liczba jest domyślnie przyjmowanajako signed (nie dotyczy to typu char dla ktoacuterego jest to zależne od kompilatora)

signed int i = 0 jest roacutewnoznaczne zint i = 0

Liczby bez znaku pozwalają nam zapisać większe liczby przy tej samej wielkości zmiennejmdash ale trzeba uważać by nie zejść z nimi poniżej zera mdash wtedy ldquoprzewijająrdquo się na sam konieczakresu co może powodować trudne do wykrycia błędy w programach

732 short i long

Short i long są wskazoacutewkami dla kompilatora by zarezerwował dla danego typu mniej (od-powiednio mdash więcej) pamięci Mogą być zastosowane do dwoacutech typoacutew int i double (tylkolong) mając roacuteżne znaczenie

Jeśli przy short lub long nie napiszemy o jaki typ nam chodzi kompilator przyjmie war-tość domyślną czyli int

Należy pamiętać że to jedynie życzenie wobec kompilatora mdash w wielu kompilatorachtypy int i long int mają ten sam rozmiar Standard języka C nakłada jedynie na kompilatorynastępujące ograniczenia int mdash nie może być kroacutetszy niż bitoacutew int mdash musi byćdłuższy lub roacutewny short a nie może być dłuższy niż long short int mdash nie może byćkroacutetszy niż bitoacutew long int mdash nie może być kroacutetszy niż bity

Zazwyczaj typ int jest typem danych o długości odpowiadającej wielkości rejestroacutew pro-cesora czyli na procesorze szesnastobitowym ma bitoacutew na trzydziestodwubitowym mdash

40 ROZDZIAŁ 7 ZMIENNE

itd1 Z tego powodu jeśli to tylko możliwe do reprezentacji liczb całkowitych preferowanejest użycie typu int bez żadnych specyfikatoroacutew rozmiaru

74 Modyfikatory

741 volatile

volatile znaczy ulotny Oznacza to że kompilator wyłączy dla takiej zmiennej optymaliza-cje typu zastąpienia przez stałą lub zawartość rejestru za to wygeneruje kod ktoacutery będzieodwoływał się zawsze do komoacuterek pamięci danego obiektu Zapobiegnie to błędowi gdyobiekt zostaje zmieniony przez część programu ktoacutera nie ma zauważalnego dla kompilatorazwiązku z danym fragmentem kodu lub nawet przez zupełnie inny proces

volatile float liczba1float liczba2

printf (fnfn liczba1 liczba2) instrukcje nie związane ze zmiennymi printf (fnf liczba1 liczba2)

Jeżeli zmienne liczba i liczba zmienią się niezauważalnie dla kompilatora to odczytując

liczba mdash nastąpi odwołanie do komoacuterek pamięci Kompilator pobierze nową wartośćzmiennej

liczba mdash kompilator może wypisać poprzednią wartość ktoacuterą przechowywał w reje-strze

Modyfikator volatile jest rzadko stosowany i przydaje się w wąskich zastosowaniachjak wspoacutełbieżność i wspoacutełdzielenie zasoboacutew oraz przerwania systemowe

742 register

Jeżeli utworzymy zmienną ktoacuterej będziemy używać w swoim programie bardzo często mo-żemy wykorzystać modyfikator register Kompilator może wtedy umieścić zmienną w re-jestrze do ktoacuterego ma szybki dostęp co przyśpieszy odwołania do tej zmiennej

register int liczba

W nowoczesnych kompilatorach ten modyfikator praktycznie nie ma wpływu na pro-gram Optymalizator sam decyduje czy i co należy umieścić w rejestrze Nie mamy żadnejgwarancji że zmienna tak zadeklarowana rzeczywiście się tam znajdzie chociaż dostęp doniej może zostać przyspieszony w inny sposoacuteb Raczej powinno się unikać tego typu kon-strukcji w programie

1Wiąże się to z pewnymi uwarunkowaniami historycznymi Podręcznik do języka C duetu KampR zakładał żetyp int miał się odnosić do typowej dla danego procesora długości liczby całkowitej Natomiast jeśli procesor moacutegłobsługiwać typy dłuższe lub kroacutetsze stosownego znaczenia nabierałymodyfikatory short i long Dobrymprzykłademmoże być architektura i386 ktoacutera umożliwia obliczenia na liczbach 16-bitowych Dlatego też modyfikator shortpowoduje skroacutecenie zmiennej do 16 bitoacutew

75 UWAGI 41

743 static

Pozwala na zdefiniowanie zmiennej statycznej ldquoStatycznośćrdquo polega na zachowaniu warto-ści pomiędzy kolejnymi definicjami tej samej zmiennej Jest to przede wszystkim przydatnew funkcjach Gdy zdefiniujemy zmienną w ciele funkcji to zmienna ta będzie od nowa defi-niowana wraz z domyślną wartością (jeżeli taką podano) W wypadku zmiennej określonejjako statyczna jej wartość się nie zmieni przy ponownym wywołaniu funkcji Na przykład

void dodaj(int liczba)

int zmienna = 0 bez staticzmienna = zmienna + liczbaprintf (Wartosc zmiennej dn zmienna)

Gdy wywołamy tę funkcję np razy w ten sposoacuteb

dodaj(3)dodaj(5)dodaj(4)

to ujrzymy na ekranie

Wartosc zmiennej 3Wartosc zmiennej 5Wartosc zmiennej 4

jeżeli jednak deklarację zmiennej zmienimy na static int zmienna = 0 to wartość zmiennejzostanie zachowana i po podobnym wykonaniu funkcji powinnyśmy ujrzeć

Wartosc zmiennej 3Wartosc zmiennej 8Wartosc zmiennej 12

Zupełnie co innego oznacza static zastosowane dla zmiennej globalnej Jest ona wtedywidoczna tylko w jednym pliku Zobacz też rozdział Biblioteki

744 extern

Przez extern oznacza się zmienne globalne zadeklarowane w innych plikach mdash informujemyw ten sposoacuteb kompilator żeby nie szukał jej w aktualnym pliku Zobacz też rozdział Biblio-teki

745 auto

Zupełnym archaizmem jest modyfikator auto ktoacutery oznacza tyle że zmienna jest lokalnaPonieważ zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna modyfikator tennie ma obecnie żadnego zastosowania praktycznego auto jest spadkiem po wcześniejszychjęzykach programowania na ktoacuterych oparty jest C (np B)

75 Uwagi Język C++ pozwala na mieszanie deklaracji zmiennych z kodem Więcej informacji w

C++Zmienne

42 ROZDZIAŁ 7 ZMIENNE

Rozdział 8

Operatory

81 Przypisanie

Operator przypisania (=rdquo) jak sama nazwa wskazuje przypisuje wartość prawego argu-mentu lewemu np

int a = 5 bb = aprintf(dn b) wypisze 5

Operator ten ma łączność prawostronną tzn obliczanie przypisań następuje z prawa nalewo i zwraca on przypisaną wartość dzięki czemu może być użyty kaskadowo

int a b ca = b = c = 3printf(d d dn a b c) wypisze 3 3 3

811 Skroacutecony zapis

C umożliwia też skroacutecony zapis postaci a = b gdzie jest jednym z operatoroacutew + - amp | ˆ ltlt lub gtgt (opisanych niżej) Ogoacutelnie rzecz ujmując zapis a = b jest roacutewnoważnyzapisowi a = a (b) np

int a = 1a += 5 to samo co a = a + 5 a = a + 2 to samo co a = a (a + 2) a = 2 to samo co a = a 2

Początkowo skroacutecona notacja miała następującą składnię a = b co często prowadziło doniejasności np i =- (i = - czy też i = i-) Dlatego też zdecydowano się zmienić kolejnośćoperatoroacutew

43

44 ROZDZIAŁ 8 OPERATORY

82 Rzutowanie

Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu Konwer-sja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podanaexplicite przez programistę) Oto kilka przykładoacutew konwersji niejawnej

int i = 427 konwersja z double do int float f = i konwersja z int do float double d = f konwersja z float do double unsigned u = i konwersja z int do unsigned int f = 42 konwersja z double do float i = d konwersja z double do int char str = foo konwersja z const char do char [1] const char cstr = str konwersja z char do const char void ptr = str konwersja z char do void

Podczas konwersji zmiennych zawierających większe ilości danych do typoacutew prostszych(np double do int) musimy liczyć się z utratą informacji jak to miało miejsce w pierwszejlinijce mdash zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta iw rezultacie zmiennej została przypisana wartość

Zaskakująca może się wydać linijka oznaczona przez 1 Niejawna konwersja z typu constchar do typu char nie jest dopuszczana przez standard C Jednak literały napisowe (ktoacutere sątypu const char) stanowią tutaj wyjątek Wynika on z faktu że były one używane na długoprzed wprowadzeniem słoacutewka const do języka i brak wspomnianegowyjątku spowodowałbyże duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod

Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania np

double d = 314int pi = (int)d 1 pi = (unsigned)pi gtgt 4 2

W pierwszym przypadku operator został użyty by zwroacutecić uwagę na utratę precyzji Wdrugim dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej

Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne kon-wersje (tj konwersja z double do int oraz z int do unsigned int) jednak niektoacutere konwersjesą błędne np

const char cstr = foochar str = cstr

W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję

const char cstr = foochar str = (char)cstr

Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompi-lator Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzieon robił i czy nie ma innego sposobu wykonania danej operacji ktoacutery nie wymagałby podej-mowania tak drastycznych krokoacutew

83 OPERATORY ARYTMETYCZNE 45

83 Operatory arytmetyczne

W arytmetyce komputerowej nie działa prawo łączności oraz rozdzielności Wynika toz ograniczonego rozmiaru zmiennych ktoacutere przechowują wartości Przykład dla zmiennycho długości bitoacutew (bez znaku) Maksymalna wartość ktoacuterą może przechowywać typ to216minus1 = 65535 Zatem operacja typu 65530+10minus20 zapisana jako (65530+10)minus20 możezaowocować czymś zupełnie innym niż 65530+(10minus20) W pierwszym przypadku zapewnedojdzie do tzw przepełnienia - procesor nie będzie miał miejsca aby zapisać dodatkowybit Zachowanie programu będzie w takim przypadku zależało od architektury procesoraAnalogiczny przykład możemy podać dla rozdzielności mnożenia względem dodawania

Język C definiuje następujące dwuargumentowe operatory arytmetyczne

dodawanie (+rdquo)

odejmowanie (-rdquo)

mnożenie (rdquo)

dzielenie (rdquo)

reszta z dzielenia (rdquo) określona tylko dla liczb całkowitych (tzw dzielenie modulo)

int a=7 b=2 cc = a bprintf (dnc) wypisze 1

Należy pamiętać że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak naj-większy z argumentoacutew Oznacza to że operacja wykonana na dwoacutech liczbach całkowitychnadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej Dla przy-kładu poniższy kod

float a = 7 2printf(fn a)

wypisze (wbrew oczekiwaniu początkujących programistoacutew) 30 a nie 35 Odnosi sięto nie tylko do dzielenia ale także mnożenia np

float a = 1000 1000 1000 1000 1000 1000printf(fn a)

prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali Aby wymusićobliczenia rzeczywiste należy zmienić typ jednego z argumentoacutew na liczbę rzeczywistą poprostu zmieniając literał lub korzystając z rzutowania np

float a = 70 2float b = (float)1000 1000 1000 1000 1000 1000printf(fn a)printf(fn b)

Operatory dodawania i odejmowania są określone roacutewnież gdy jednym z argumentoacutewjest wskaźnik a drugim liczba całkowita Ten drugi jest także określony gdy oba argumentysą wskaźnikami O takim użyciu tych operatoroacutew dowiesz się więcej CWskaźniki|w dalszejczęści książki

46 ROZDZIAŁ 8 OPERATORY

831 Inkrementacja i dekrementacja

Aby skroacutecić zapis wprowadzono dodatkowe operatory inkrementacji (++rdquo) i dekrementa-cji (ndashrdquo) ktoacutere dodatkowo mogą być pre- lub postfiksowe W rezultacie mamy więc czteryoperatory

pre-inkrementacja (++irdquo)

post-inkrementacja (i++rdquo)

pre-dekrementacja (ndashirdquo) i

post-dekrementacja (indashrdquo)

Operatory inkrementacji zwiększa a dekrementacji zmniejsza argument o jeden Ponadtooperatory pre- zwracają nową wartość argumentu natomiast post- starą wartość argumentu

int a b ca = 3b = a-- po operacji b=3 a=2 c = --b po operacji b=2 c=2

Czasami (szczegoacutelnie w C++) użycie operatoroacutew stawianych za argumentem jest niecomniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tym-czasową)

Bardzo ważne jest abyśmy poprawnie stosowali operatory dekrementacji i inkrementa-cji Chodzi o to aby w jednej instrukcji nie umieszczać kilku operatoroacutew ktoacutere modyfikująten sam obiekt (zmienną) Jeżeli taka sytuacja zaistnieje to efekt działania instrukcji jestnieokreślony Prostym przykładem mogą być następujące instrukcje

int a = 1a = a++a = ++aa = a++ + ++aprintf(d dn ++a ++a)printf(d dn a++ a++)

Kompilator potrafi ostrzegać przed takimi błędami - aby to czynił należy podać mujako argument opcję -Wsequence-point

84 Operacje bitoweOproacutecz operacji znanych z lekcji matematyki w podstawoacutewce język C został wyposażonytakże w operatory bitowe zdefiniowane dla liczb całkowitych Są to

negacja bitowa (˜rdquo)

koniunkcja bitowa (amprdquo)

alternatywa bitowa (|rdquo) i

84 OPERACJE BITOWE 47

alternatywa rozłączna () (ˆrdquo)

Działają one na poszczegoacutelnych bitach przez co mogą być szybsze od innych operacjiDziałanie tych operatoroacutew można zdefiniować za pomocą poniższych tabel

~ | 0 1 amp | 0 1 | | 0 1 ^ | 0 1-----+----- -----+----- -----+----- -----+-----

| 1 0 0 | 0 0 0 | 0 1 0 | 0 11 | 0 1 1 | 1 1 1 | 1 0

a | 0101 = 5b | 0011 = 3

-------+------~a | 1010 = 10~b | 1100 = 12

a amp b | 0001 = 1a | b | 0111 = 7a ^ b | 0110 = 6

Lub bardziej opisowo

negacja bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych argument miał bity roacutewne zero

koniunkcja bitowa daje wwyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tych pozy-cjach na ktoacuterych oba argumenty miały bity roacutewne jeden (mnemonik gdy wszystkie)

alternatywa bitowa daje w wyniku liczbę ktoacutera ma bity roacutewne jeden na wszystkichtych pozycjach na ktoacuterych jeden z argumentoacutew miał bit roacutewny jeden (mnemonik jeśli jest )

alternatywa rozłączna daje w wyniku liczbę ktoacutera ma bity roacutewne jeden tylko na tychpozycjach na ktoacuterych tylko jeden z argumentoacutew miał bit roacutewny jeden (mnemonik gdy roacuteżne)

Przy okazji warto zauważyć że aˆbˆb to po prostu a Właściwość ta została wykorzystanaw roacuteżnych algorytmach szyfrowania oraz funkcjach haszujących Alternatywę wyłączną sto-suje się np do szyfrowania kodu wirusoacutew polimorficznych

841 Przesunięcie bitowe

Dodatkowo język C wyposażony jest w operatory przesunięcia bitowego w lewo (ltltrdquo) iprawo (gtgtrdquo) Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycjipodaną jako prawy argument Brzmi to może strasznie ale wcale takie nie jest Rozważmy-bitowe liczby bez znaku (taki hipotetyczny unsigned int) woacutewczas

a | altlt1 | altlt2 | agtgt1 | agtgt2------+------+------+------+------0001 | 0010 | 0100 | 0000 | 00000011 | 0110 | 1100 | 0001 | 00000101 | 1010 | 0100 | 0010 | 0001

48 ROZDZIAŁ 8 OPERATORY

1000 | 0000 | 0000 | 0100 | 00101111 | 1110 | 1100 | 0111 | 00111001 | 0010 | 0100 | 0100 | 0010

Nie jest to zatem takie straszne na jakie wygląda Widać że bity będące na skraju sątracone a w pusterdquo miejsca wpisywane są zera

Inaczej rzecz się ma jeżeli lewy argument jest liczbą ze znakiem Dla przesunięcia bito-wego w lewo a ltlt b jeżeli a jest nieujemna i wartość a middot 2b mieści się w zakresie liczby tojest to wynikiem operacji W przeciwnym wypadku działanie jest niezdefiniowane1

Dla przesunięcia bitowego w lewo jeżeli lewy argument jest nieujemny to operacja za-chowuje się tak jak w przypadku liczb bez znaku Jeżeli jest on ujemny to zachowanie jestzależne od implementacji

Zazwyczaj operacja przesunięcia w lewo zachowuje się tak samo jak dla liczb bez znakunatomiast przy przesuwaniu w prawo bit znaku nie zmienia się2

a | agtgt1 | agtgt2------+------+------0001 | 0000 | 00000011 | 0001 | 00000101 | 0010 | 00011000 | 1100 | 11101111 | 1111 | 11111001 | 1100 | 1110

Przesunięcie bitowe w lewo odpowiada pomnożeniu natomiast przesunięcie bitowe wprawo podzieleniu liczby przez dwa do potęgi jaką wyznacza prawy argument Jeżeli prawyargument jest ujemny lub większy lub roacutewny liczbie bitoacutew w typie działanie jest niezdefi-niowane

include ltstdiohgt

int main ()

int a = 6printf (6 ltlt 2 = dn altlt2) wypisze 24 printf (6 gtgt 2 = dn agtgt2) wypisze 1 return 0

85 PoroacutewnanieW języku C występują następujące operatory poroacutewnania

roacutewne (==rdquo)

roacuteżne (=rdquo)

mniejsze (ltrdquo)

1Niezdefiniowane w takim samym sensie jak niezdefiniowane jest zachowanie programu gdy proacutebujemy odwo-łać się do wartości wskazywanej przez wartość czy do zmiennych poza tablicą

2ale jeżeli zależy Ci na przenośności kodu nie możesz na tym polegać

85 POROacuteWNANIE 49

większe (gtrdquo)

mniejsze lub roacutewne (lt=rdquo) i

większe lub roacutewne (gt=rdquo)

Wykonują one odpowiednie poroacutewnanie swoich argumentoacutew i zwracają jedynkę jeżeliwarunek jest spełniony lub zero jeżeli nie jest

851 Częste błędy

Osoby ktoacutere poprzednio uczyły się innych językoacutew programowania często mają nawykużywania w instrukcjach logicznych zamiast operatora poroacutewnania == operatora przypi-sania = Ma to często zgubne efekty gdyż przypisanie zwraca wartość przypisaną lewemuargumentowi

Poroacutewnajmy ze sobą dwa warunki

(a = 1)(a == 1)

Pierwszy z nich zawsze będzie prawdziwy niezależnie od wartości zmiennej a Dziejesię tak ponieważ zostaje wykonane przypisanie do a wartości a następnie jako wartość jestzwracane to co zostało przypisane mdash czyli jeden Drugi natomiast będzie prawdziwy tylkogdy a jest roacutewne

W celu uniknięcia takich błędoacutew niektoacuterzy programiści zamiast pisać a == 1 piszą 1 == adzięki czemu pomyłka spowoduje że kompilator zgłosi błąd

Warto zauważyć że kompilator potrafi w pewnych sytuacjach wychwycić taki błądAby zaczął to robić należy podać mu argument -Wparentheses

Innym błędem jest użycie zwykłych operatoroacutew poroacutewnania do sprawdzania relacji po-między liczbami rzeczywistymi Ponieważ operacje zmiennoprzecinkowe wykonywane są zpewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie roacutewne Dlaprzykładu

include ltstdiohgtint main ()

float a b ca = 1e10 tj 10 do potęgi 10 b = 1e-10 tj 10 do potęgi -10 c = b c = b c = c + a c = b + a (teoretycznie) c = c - a c = b + a - a = b (teoretycznie) printf(dn c == b) wypisze 0

Obejściem jest poroacutewnywanie modułu roacuteżnicy liczb Roacutewnież i takie błędy kompilator potrafi wykrywać mdash aby to robił należy podać mu argument -Wfloat-equal

50 ROZDZIAŁ 8 OPERATORY

86 Operatory logiczneAnalogicznie do części operatoroacutew bitowych w C definiuje się operatory logiczne miano-wicie

negację (zaprzeczenie)

koniunkcję (ldquoirdquo) ampamp

alternatywę (ldquolubrdquo) ||

Działają one bardzo podobnie do operatoroacutew bitowych jednak zamiast operować na po-szczegoacutelnych bitach biorą pod uwagę wartość logiczną argumentoacutew

861 ldquoPrawdardquo i ldquofałszrdquo w języku C

Język C nie przewiduje specjalnego typu danych do operacji logicznych mdash operatory logicznemożna stosować do liczb (np typu int) tak samo jak operatory bitowe albo arytmetyczne

Wyrażenie ma wartość logiczną wtedy i tylko wtedy gdy jest roacutewne (jest ldquofałszywerdquo)W przeciwnym wypadku ma wartość (jest ldquoprawdziwerdquo) Operatory logiczne w wynikudają zawsze albo albo

Żeby w pełni uzmysłowić sobie co to to oznacza spoacutejrzmy na wynik wykonania poniż-szych trzech linijek

printf(koniunkcja dn 18 ampamp 19)printf(alternatywa dn a || b)printf(negacja dn 20)

koniunkcja 1alternatywa 1negacja 0

Liczba nie jest roacutewna więc ma wartość logiczną Podobnie ma wartość logiczną Dlatego ich koniunkcja jest roacutewna Znaki a i b zostaną w wyrażeniu logicznympotraktowane jako liczby o wartości odpowiadającej kodowi znaku mdash czyli oba będąmiały wartość logiczną

862 Skroacutecone obliczanie wyrażeń logiczny

Język C wykonuje skroacutecone obliczanie wyrażeń logicznych mdash to znaczy oblicza wyrażenietylko tak długo jak nie wie jaka będzie jego ostateczna wartość To znaczy idzie od lewejdo prawej obliczając kolejne wyrażenia (dodatkowo na kolejność wpływ mają nawiasy) i gdybędzie miał na tyle informacji by obliczyć wartość całości nie liczy reszty Może to wydawaćsię niejasne ale przyjrzyjmy się wyrażeniom logicznym

A ampamp BA || B

Jeśli A jest fałszywe to nie trzeba liczyć B w pierwszym wyrażeniu bo fałsz i dowolne wyra-żenie zawsze da fałsz Analogicznie jeśli A jest prawdziwe to wyrażenie jest prawdziwe iwartość B nie ma znaczenia

Poza zwiększoną szybkością zysk z takiego rozwiązania polega na możliwości stosowaniaefektoacutew ubocznych Idea efektu ubocznego opiera się na tym że w wyrażeniu można wywo-łać funkcje ktoacutere będą robiły poza zwracaniemwyniku inne rzeczy oraz używać podstawieńPopatrzmy na poniższy przykład

87 OPERATOR WYRAŻENIA WARUNKOWEGO 51

( (a gt 0) || (a lt 0) || (a = 1) )

Jeśli a będzie większe od to obliczona zostanie tylko wartość wyrażenia (a gt 0) mdash da onoprawdę czyli reszta obliczeń nie będzie potrzebna Jeśli a będzie mniejsze od zera najpierwzostanie obliczone pierwsze podwyrażenie a następnie drugie ktoacutere da prawdę Ciekawy bę-dzie jednak przypadek gdy a będzie roacutewne zero mdash do a zostanie wtedy podstawiona jedynkai całość wyrażenia zwroacuteci prawdę (bo jest traktowane jak prawda)

Efekty uboczne pozwalają na roacuteżne szaleństwa i wykonywanie złożonych operacji w sa-mych warunkach logicznych jednak przesadne używanie tego typu konstrukcji powodujeże kod staje się nieczytelny i jest uważane za zły styl programistyczny

87 Operator wyrażenia warunkowegoC posiada szczegoacutelny rodzaj operatora mdash to operator zwany też operatorem wyrażeniawarunkowego Jest to jedyny operator w tym języku przyjmujący trzy argumenty

a b c

Jego działanie wygląda następująco najpierw oceniana jest wartość logiczna wyrażenia ajeśli jest ono prawdziwe to zwracana jest wartość b jeśli natomiast wyrażenie a jest nie-prawdziwe zwracana jest wartość c

Praktyczne zastosowanie mdash znajdowanie większej z dwoacutech liczb

a = (bgt=c) b c Jeśli b jest większe bądź roacutewne c to zwroacuteć bW przeciwnym wypadku zwroacuteć c

lub zwracanie modułu liczby

a = a lt 0 -a a

Wartości wyrażeń są przy tym operatorze obliczane tylko jeżeli zachodzi taka potrzebanp w wyrażeniu 1 1 foo() funkcja foo() nie zostanie wywołana

88 Operator przecinekOperator przecinek jest dość dziwnym operatorem Powoduje on obliczanie wartości wyra-żeń od lewej do prawej po czym zwroacutecenie wartości ostatniego wyrażenia W zasadzie wnormalnym kodzie programu ma on niewielkie zastosowanie gdyż zamiast niego lepiej roz-dzielać instrukcje zwykłymi średnikami Ma on jednak zastosowanie w instrukcji sterującejfor

89 Operator sizeofOperator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanegotypu lub typu podanego wyrażenia Ma on dwa rodzaje sizeof(typ) lub sizeof wyrażeniePrzykładowo

include ltstdiohgt

int main()

52 ROZDZIAŁ 8 OPERATORY

printf(sizeof(short ) = dn sizeof(short ))printf(sizeof(int ) = dn sizeof(int ))printf(sizeof(long ) = dn sizeof(long ))printf(sizeof(float ) = dn sizeof(float ))printf(sizeof(double) = dn sizeof(double))return 0

Operator ten jest często wykorzystywany przy dynamicznej alokacji pamięci co zostanieopisane w rozdziale poświęconym wskaźnikom

Pomimo że w swej budowie operator sizeof bardzo przypomina funkcję to jednak niąnie jest Wynika to z trudności w implementacji takowej funkcji mdash jej specyfika musiałabyodnosić się bezpośrednio do kompilatora Ponadto jej argumentem musiałyby być typy anie zmienne W języku C nie jest możliwe przekazywanie typu jako argumentu Ponadtoczęsto zdarza się że rozmiar zmiennej musi być wiadomy jeszcze w czasie kompilacji mdash toewidentnie wyklucza implementację sizeof() jako funkcji

810 Inne operatory

Poza wyżej opisanymi operatorami istnieją jeszcze

operator []rdquo opisany przy okazji opisywania tablic

jednoargumentowe operatory rdquo i amprdquo opisane przy okazji opisywania wskaźnikoacutew

operatory rdquo i -gtrdquo opisywane przy okazji opisywania struktur i unii

operator ()rdquo będący operatorem wywołania funkcji

operator ()rdquo grupujący wyrażenia (np w celu zmiany kolejności obliczania

811 Priorytety i kolejność obliczeń

Jak w matematyce roacutewnież i w języku C obowiązuje pewna ustalona kolejność działań Abymoacutec ją określić należy ustalić dwa parametry danego operatora jego priorytet oraz łącz-ność Przykładowo operator mnożenia ma wyższy priorytet niż operator dodawania i z tegopowodu wwyrażeniu 2+2 middot2 najpierw wykonuje się mnożenie a dopiero potem dodawanie

Drugim parametrem jest łączność mdash określa ona od ktoacuterej stronywykonywane są działaniaw przypadku połączenia operatoroacutew o tym samym priorytecie Na przykład odejmowaniema łączność lewostronną i 2 minus 2 minus 2 da w wyniku - Gdyby miało łączność prawostronnąw wynikiem byłoby Przykładem matematycznego operatora ktoacutery ma łączność prawo-stronną jest potęgowanie np 322

jest roacutewne W języku C występuje dużo poziomoacutew operatoroacutew Poniżej przedstawiamy tabelkę ze

wszystkimi operatorami poczynając od tych z najwyższym priorytetem (wykonywanych napoczątku)

Duża liczba poziomoacutew pozwala czasami zaoszczędzić trochę milisekund w trakcie pisaniaprogramu i bajtoacutew na dysku gdyż często nawiasy nie są potrzebne nie należy jednak z tymprzesadzać gdyż kod programu może stać się mylący nie tylko dla innych ale po latach (czynawet i dniach) roacutewnież dla nas

812 KOLEJNOŚĆ WYLICZANIA ARGUMENTOacuteW OPERATORA 53

Tablica 81 Priorytety operatoroacutewOperator Łącznośćnawiasy nie dotyczyjednoargumentowe przyrostkowe [] -gt wywołanie funkcji postinkre-mentacja postdekrementacja

lewostronna

jednoargumentowe przedrostkowe ˜ + - amp sizeof preinkrementacjapredekrementacja rzutowanie

prawostronna

lewostronna+ - lewostronnaltlt gtgt lewostronnaltlt= gtgt= lewostronna== = lewostronnaamp lewostronnaˆ lewostronna| lewostronnaampamp lewostronna|| lewostronna prawostronnaoperatory przypisania prawostronna lewostronna

Warto także podkreślić że operator koniunkcji ma niższy priorytet niż operator poroacutew-nania3 Oznacza to że kod

if (flags amp FL_MASK == FL_FOO)

zazwyczaj da rezultat inny od oczekiwanego Najpierw bowiem wykona się poroacutewna-nie wartości FL MASK z wartością FL FOO a dopiero potem koniunkcja bitowa W takichsytuacjach należy pamiętać o użyciu nawiasoacutew

if ((flags amp FL_MASK) == FL_FOO)

Kompilator potrafi wykrywać takie błędy i aby to robił należy podać mu argument-Wparentheses

812 Kolejność wyliczania argumentoacutew operatoraW przypadku większości operatoroacutew (wyjątkami są tu ampamp || i przecinek) nie da się określićktoacutera wartość argumentu zostanie obliczona najpierw W większości przypadkoacutew nie mato większego znaczenia lecz w przypadku wyrażeń ktoacutere mają efekty uboczne wymuszeniekonkretnej kolejności może być potrzebne Weźmy dla przykładu program

include ltstdiohgt

int foo(int a) printf(dn a)

3Jest to zaszłość historyczna z czasoacutew gdy nie było logicznych operatoroacutew ampamp oraz || i zamiast nich stosowanooperatory bitowe amp oraz |

54 ROZDZIAŁ 8 OPERATORY

return 0

int main(void) return foo(1) + foo(2)

Otoacuteż nie wiemy czy najpierw zostanie wywołana funkcja foo z parametrem jeden czydwa Jeżeli ma to znaczenie należy użyć zmiennych pomocniczych zmieniając definicję funk-cji main na

int main(void) int tmp = foo(1)return tmp + foo(2)

Teraz już na pewno najpierw zostanie wypisana jedynka a potem dopiero dwoacutejka Sy-tuacja jeszcze bardziej się komplikuje gdy używamy wyrażeń z efektami ubocznymi jakoargumentoacutew funkcji np

include ltstdiohgt

int foo(int a) printf(dn a)return 0

int bar(int a int b int c int d) return a + b + c + d

int main(void) return foo(1) + foo(2) + foo(3) + foo(4)

Teraz też nie wiemy ktoacutera z permutacji liczb i zostanie wypisana i ponownienależy pomoacutec sobie zmiennymi tymczasowymi jeżeli zależy nam na konkretnej kolejności

int main(void) int tmp = foo(1)tmp += foo(2)tmp += foo(3)return tmp + foo(4)

813 Uwagi

W języku C++ wprowadzony został dodatkowo inny sposoacuteb zapisu rzutowania ktoacuterypozwala na łatwiejsze znalezienie w kodzie miejsc w ktoacuterych dokonujemy rzutowaniaWięcej na stronie C++Zmienne

814 ZOBACZ TEŻ 55

814 Zobacz też CSkładniaOperatory

56 ROZDZIAŁ 8 OPERATORY

Rozdział 9

Instrukcje sterujące

C jest językiem imperatywnym mdash oznacza to że instrukcje wykonują się jedna po drugiej wtakiej kolejności w jakiej są napisane Aby moacutec zmienić kolejność wykonywania instrukcjipotrzebne są instrukcje sterujące

Na wstępie przypomnijmy jeszcze informację z rozdziału Operatory że wyrażenie jestprawdziwe wtedy i tylko wtedy gdy jest roacuteżne od zera a fałszywe wtedy i tylko wtedy gdyjest roacutewne zeru

91 Instrukcje warunkowe

911 Instrukcja if

Użycie instrukcji if wygląda tak

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

dalsze instrukcje

Istnieje także możliwość reakcji na nieprawdziwość wyrażenia mdash wtedy należy zastosowaćsłowo kluczowe else

if (wyrażenie) blok wykonany jeśli wyrażenie jest prawdziwe

else blok wykonany jeśli wyrażenie jest nieprawdziwe

dalsze instrukcje

Przypatrzmy się bardziej ldquożyciowemurdquo programowi ktoacutery poroacutewnuje ze sobą dwie liczby

include ltstdiohgt

int main ()

int a ba = 4

57

58 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

b = 6if (a==b)

printf (a jest roacutewne bn) else

printf (a nie jest roacutewne bn)return 0

Stosowany jest też kroacutetszy zapis warunkoacutew logicznych korzystający z tego jak C rozumieprawdę i fałsz Jeśli zmienna a jest typu integer zamiast

if (a = 0) b = 1a

można napisać

if (a) b = 1a

a zamiast

if (a == 0) b = 1a

można napisać

if (a) b = 1a

Czasami zamiast pisać instrukcję if możemy użyć operatora wyrażenia warunkowego(patrz Operatory)

if (a = 0)b = 1a

elseb = 0

ma dokładnie taki sam efekt jak

b = (a =0) 1a 0

912 Instrukcja swit

Aby ograniczyćwielokrotne stosowanie instrukcji if możemy użyć swit Jej użyciewyglądatak

switch (wyrażenie) case wartość1 instrukcje jeśli wyrażenie == wartość1

breakcase wartość2 instrukcje jeśli wyrażenie == wartość2

break default instrukcje jeśli żaden z wcześniejszych warunkoacutew

break nie został spełniony

Należy pamiętać o użyciu break po zakończeniu listy instrukcji następujących po case Je-śli tego nie zrobimy program przejdzie do wykonywania instrukcji z następnego case Możemieć to fatalne skutki

91 INSTRUKCJE WARUNKOWE 59

include ltstdiohgt

int main ()

int a bprintf (Podaj a )scanf (d ampa)printf (Podaj b )scanf (d ampb)switch (b)

case 0 printf (Nie można dzielić przez 0n) tutaj zabrakło break default printf (ab=dn ab)

return 0

A czasami może być celowym zabiegiem (tzw ldquofall-throughrdquo) mdash woacutewczas warto zazna-czyć to w komentarzu Oto przykład

include ltstdiohgt

int main ()

int a = 4switch ((a3))

case 0printf (Liczba d dzieli się przez 3n a)break

case -2case -1case 1case 2printf (Liczba d nie dzieli się przez 3n a)break

return 0

Przeanalizujmy teraz działający przykład

include ltstdiohgt

int main ()

unsigned int dzieci = 3 podatek=1000switch (dzieci)

case 0 break brak dzieci - czyli brak ulgi case 1 ulga 2

podatek = podatek - (podatek100 2)break

case 2 ulga 5

60 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

podatek = podatek - (podatek100 5)break

default ulga 10 podatek = podatek - (podatek10010)break

printf (Do zapłaty dn podatek)

92 Pętle

921 Instrukcja while

Często zdarza się że nasz programmusi wielokrotnie powtarzać ten sam ciąg instrukcji Abynie przepisywać wiele razy tego samego kodu można skorzystać z tzw pętli Pętla wykonujesię dotąd dopoacuteki prawdziwy jest warunek

while (warunek) instrukcje do wykonania w pętli

dalsze instrukcje

Całą zasadę pętli zrozumiemy lepiej na jakimś działającym przykładzie Załoacuteżmy żemamy obliczyć kwadraty liczb od do Piszemy zatem program

include ltstdiohgt

int main ()

int a = 1while (a lt= 10) dopoacuteki a nie przekracza 10

printf (dn aa) wypisz aa na ekran++a zwiększamy a o jeden

return 0

Po analizie kodu mogą nasunąć się dwa pytania

Po co zwiększać wartość a o jeden Otoacuteż gdybyśmy nie dodali instrukcji zwiększająceja to warunek zawsze byłby spełniony a pętla ldquokręciłabyrdquo się w nieskończoność

Dlaczego warunek to ldquoa lt= rdquo a nie ldquoa=rdquo Odpowiedź jest dość prosta Pętlasprawdza warunek przed wykonaniem kolejnego ldquoobroturdquo Dlatego też gdyby waru-nek brzmiał ldquoa=rdquo to dla a= jest on nieprawdziwy i pętla nie wykonałaby ostatniejiteracji przez co program generowałby kwadraty liczb od do a nie do

922 Instrukcja for

Od instrukcji while czasami wygodniejsza jest instrukcja for Umożliwia ona wpisanie usta-wiania zmiennej sprawdzania warunku i inkrementowania zmiennej w jednej linijce co czę-sto zwiększa czytelność kodu Instrukcję for stosuje się w następujący sposoacuteb

92 PĘTLE 61

for (wyrażenie1 wyrażenie2 wyrażenie3) instrukcje do wykonania w pętli

dalsze instrukcje

Jak widać pętla for znacznie roacuteżni się od tego typu pętli znanych w innych językachprogramowania Opiszemy więc co oznaczają poszczegoacutelne wyrażenia

wyrażenie mdash jest to instrukcja ktoacutera będzie wykonana przed pierwszym przebiegiempętli Zwykle jest to inicjalizacja zmiennej ktoacutera będzie służyła jako ldquolicznikrdquo przebie-goacutew pętli

wyrażenie mdash jest warunkiem zakończenia pętli Pętla wykonuje się tak długo jakprawdziwy jest ten warunek

wyrażenie mdash jest to instrukcja ktoacutera wykonywana będzie po każdym przejściu pętliZamieszczone są tu instrukcje ktoacutere zwiększają licznik o odpowiednią wartość

Jeżeli wewnątrz pętli nie ma żadnych instrukcji continue (opisanych niżej) to jest onaroacutewnoważna z

wyrażenie1while (wyrażenie2)

instrukcje do wykonania w pętli wyrażenie3

dalsze instrukcje

Ważną rzeczą jest tutaj to żeby zrozumieć i zapamiętać jak tak naprawdę działa pętla forPoczątkującym programistom nieznajomość tego faktu sprawia wiele problemoacutew

W pierwszej kolejności w pętli for wykonuje się wyrażenie1 Wykonuje się ono zawszenawet jeżeli warunek przebiegu pętli jest od samego początku fałszywy Po wykonaniuwyrażenie1 pętla for sprawdza warunek zawarty w wyrażenie2 jeżeli jest on prawdziwyto wykonywana jest treść pętli for czyli najczęściej to co znajduje się między klamrami lubgdy ich nie ma następna pojedyncza instrukcja W szczegoacutelności musimy pamiętać że samśrednik też jest instrukcją mdash instrukcją pustą Gdy już zostanie wykonana treść pętli for na-stępuje wykonanie wyrażenie3 Należy zapamiętać że wyrażenie zostanie wykonane nawetjeżeli był to już ostatni obieg pętli Poniższe przykłady pętli for w rezultacie dadzą ten samwynik Wypiszą na ekran liczby od do

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 ++i)printf(d i)

for(i=1 ilt=10 printf(d i++ ) )

Dwa pierwsze przykłady korzystają z własności struktury blokowej kolejny przykład jestjuż bardziej wyrafinowany i korzysta z tego że jako wyrażenie3może zostać podane dowolnebardziej skomplikowane wyrażenie zawierające w sobie inne podwyrażenia A oto kolejnyprogram ktoacutery najpierw wyświetla liczby w kolejności rosnącej a następnie wraca

62 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

include ltstdiohgtint main()int ifor(i=1 ilt=5 ++i)

printf(d i)

for( igt=1 i--)printf(d i)

return 0

Po analizie powyższego kodu początkujący programista może stwierdzić że pętla wy-pisze 123454321 Stanie się natomiast inaczej Wynikiem działania powyższego programubędzie ciąg cyfr 12345654321 Pierwsza pętla wypisze cyfry ldquordquo lecz po ostatnim swoimobiegu pętla for (tak jak zwykle) zinkrementuje zmienną i Gdy druga pętla przystąpi dopracy zacznie ona odliczać począwszy od liczby i= a nie By spowodować wyświetlanieliczb od do i z powrotem wystarczy gdzieś między ostatnim obiegiem pierwszej pętli fora pierwszym obiegiem drugiej pętli for zmniejszyć wartość zmiennej i o

Niech podsumowaniem będzie jakiś działający fragment kodu ktoacutery może obliczać war-tości kwadratoacutew liczb od do

include ltstdiohgt

int main ()

int afor (a=1 alt=10 ++a)

printf (dn aa)return 0

W kodzie źroacutedłowym spotyka się często inkrementację i++ Jest to zły zwyczaj biorącysię z wzorowania się na nazwie języka C++ Post-inkrementacja i++ powoduje że tworzonyjest obiekt tymczasowy ktoacutery jest zwracany jako wynik operacji (choć wynik ten nie jestnigdzie czytany) Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie ale wpętli ldquoforrdquo takie kopiowanie odbywa się po każdym przebiegu pętli Dodatkowo w C++ po-dobną konstrukcję stosuje się do obiektoacutew mdash kopiowanie obiektu może być już czasochłonnączynnością Dlatego w pętli ldquoforrdquo należy stosować wyłącznie ++i

923 Instrukcja dowhile

Pętle while i for mają jeden zasadniczy mankament mdash może się zdarzyć że nie wykonają sięani razu Aby mieć pewność że nasza pętla będzie miała co najmniej jeden przebieg musimyzastosować pętlę do while Wygląda ona następująco

92 PĘTLE 63

do instrukcje do wykonania w pętli

while (warunek) dalsze instrukcje

Zasadniczą roacuteżnicą pętli do while jest fakt iż sprawdza ona warunek pod koniec swojegoprzebiegu To właśnie ta cecha decyduje o tym że pętla wykona się co najmniej raz A terazprzykład działającego kodu ktoacutery tym razem będzie obliczał trzecią potęgę liczb od do

include ltstdiohgt

int main ()

int a = 1do

printf (dn aaa)++a

while (a lt= 10)return 0

Może się to wydać zaskakujące ale roacutewnież przy tej pętli zamiast bloku instrukcji możnazastosować pojedynczą instrukcję np

include ltstdiohgt

int main ()

int a = 1do printf (dn aaa) while (++a lt= 10)return 0

924 Instrukcja break

Instrukcja break pozwala na opuszczenie wykonywania pętli w dowolnym momencie Przy-kład użycia

int afor (a=1 a = 9 ++a)

if (a == 5) breakprintf (dn a)

Program wykona tylko przebiegi pętli gdyż przy przebiegu instrukcja break spowo-duje wyjście z pętli

Break i pętle nieskończone

W przypadku pętli for nie trzeba podawać warunku W takim przypadku kompilator przyj-mie że warunek jest stale spełniony Oznacza to że poniższe pętle są roacutewnoważne

64 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

for () for (1) for (aaa) gdzie a jest dowolną liczba rzeczywistą roacuteżną od 0while (1) do while (1)

Takie pętle nazywamy pętlami nieskończonymi ktoacutere przerwać może jedynie instrukcjabreak1(z racji tego że warunek pętli zawsze jest prawdziwy) 2

Wszystkie fragmenty kodu działają identycznie

int i = 0for (i=5++i)

kod

int i = 0for (++i)

if (i == 5) break

int i = 0for ()

if (i == 5) break++i

925 Instrukcja continue

W przeciwieństwie do break ktoacutera przerywa wykonywanie pętli instrukcja continue powo-duje przejście do następnej iteracji o ile tylko warunek pętli jest spełniony Przykład

int ifor (i = 0 i lt 100 ++i)

printf (Poczatekn)if (i gt 40) continue printf (Koniecn)

Dla wartości i większej od nie będzie wyświetlany komunikat ldquoKoniecrdquo Pętla wykonapełne przejść

Oto praktyczny przykład użycia tej instrukcji

include ltstdiohgtint main()

int i

1Tak naprawdę podobną operacje możemy wykonać za pomocą polecenia goto W praktyce jednak stosujesię zasadę że break stosuje się do przerwania działania pętli i wyjścia z niej goto stosuje się natomiast wtedykiedy chce się wydostać się z kilku zagnieżdżonych pętli za jednym zamachem Do przerwania pracy pętli mogąnam jeszcze posłużyć polecenia exit() lub return ale woacutewczas zakończymy nie tylko działanie pętli ale i całegoprogramufunkcji

2Żartobliwie można powiedzieć że stosując pętlę nieskończoną to najlepiej korzystać z pętli for() gdyżwymaga ona napisania najmniejszej liczby znakoacutew w poroacutewnaniu do innych konstrukcji

93 INSTRUKCJA GOTO 65

for (i = 1 i lt= 50 ++i) if (i4==0) continue printf (d i)

return 0

Powyższy program generuje liczby z zakresu od do ktoacutere nie są podzielne przez

93 Instrukcja goto

Istnieje także instrukcja ktoacutera dokonuje skoku do dowolnegomiejsca programu oznaczonegotzw etykietą

etykieta instrukcje goto etykieta

Uwaga kompilator w wersji i wyższych jest bardzo uczulony na etykiety za-mieszczone przed nawiasem klamrowym zamykającym blok instrukcji Innymi słowy nie-dopuszczalne jest umieszczanie etykiety zaraz przed klamrą ktoacutera kończy blok instrukcjizawartych np w pętli for Można natomiast stosować etykietę przed klamrą kończącą danąfunkcję

Instrukcja goto łamie sekwencję instrukcji i powoduje skok do dowolnie odległego miej-sca w programie - co może mieć nieprzewidziane skutki Zbyt częste używanie goto możeprowadzić do trudnych do zlokalizowania błędoacutew Oproacutecz tego kompilatory mają kłopotyz optymalizacją kodu w ktoacuterym występują skoki Z tego powodu zaleca się ograniczeniezastosowania tej instrukcji wyłącznie do opuszczania wielokrotnie zagnieżdżonych pętli

Przykład uzasadnionego użycia

int ijfor (i = 0 i lt 10 ++i)

for (j = i j lt i+10 ++j) if (i + j 21 == 0) goto koniec

koniec dalsza czesc programu

94 Natymiastowe kończenie programu mdash funkcja exit

Program może zostać w każdej chwili zakończony mdash do tego właśnie celu służy funkcja exitUżywamy jej następująco

exit (kod_wyjścia)

66 ROZDZIAŁ 9 INSTRUKCJE STERUJĄCE

Liczba całkowita kod wyjścia jest przekazywana do procesu macierzystego dzięki czemudostaje on informację czy programw ktoacuterymwywołaliśmy tą funkcję zakończył się popraw-nie lub czy się tak nie stało Kody wyjścia są nieustandaryzowane i żeby program był w pełniprzenośny należy stosować makra EXIT SUCCESS i EXIT FAILURE choć na wielu systemach kod oznacza poprawne zakończenie a kod roacuteżny od błędne W każdym przypadku jeżeli naszprogram potrafi generować wiele roacuteżnych kodoacutew warto je wszystkie udokumentować w ewdokumentacji Są one też czasem pomocne przy wykrywaniu błędoacutew

95 Uwagi W języku C++ można deklarować zmienne w nagłoacutewku pętli ldquoforrdquo w następujący spo-

soacuteb for(int i=0 ilt10 ++i) (więcej informacji w C++Zmienne)

Rozdział 10

Podstawowe procedury wejścia iwyjścia

101 Wejściewyjście

Komputer byłby całkowicie bezużyteczny gdyby użytkownik nie moacutegł się z nim porozumieć(tj wprowadzić danych lub otrzymać wynikoacutew pracy programu) Programy komputerowesłużą w największym uproszczeniu do obroacutebki danych mdash więc muszą te dane jakoś od nasotrzymać przetworzyć i przekazać nam wynik

Takiewczytywanie i ldquowyrzucanierdquo danychw terminologii komputerowej nazywamywej-ściem (input) iwyjściem (output) Bardzo często moacutewi się o wejściu i wyjściu danych łączniemdash inputoutput albo po prostu IO

W C do komunikacji z użytkownikiem służą odpowiednie funkcje Zresztą do wielu za-dań w C służą funkcje Używając funkcji nie musimy wiedzieć w jaki sposoacuteb komputerwykonuje jakieś zadanie interesuje nas tylko to co ta funkcja robi Funkcje niejako ldquowyko-nują za nas część pracyrdquo ponieważ nie musimy pisać być może dziesiątek linijek kodu żebynp wypisać tekst na ekranie (wbrew pozorom mdash kod funkcji wyświetlającej tekst na ekraniejest dość skomplikowany) Jeszcze taka uwaga mdash gdy piszemy o jakiejś funkcji zazwyczajpodając jej nazwę dopisujemy na końcu nawias

printf()scanf()

żeby było jasne że chodzi o funkcję a nie o coś innego

Wyżej wymienione funkcje to jedne z najczęściej używanych funkcji w C mdash pierwszasłuży do wypisywania danych na ekran natomiast druga do wczytywania danych z klawia-tury1

1W zasadzie standard C nie definiuje czegoś takiego jak ekran i klawiatura mdash mowa w nim o standardowymwyjściu i standardowym wejściu Zazwyczaj jest to właśnie ekran i klawiatura ale nie zawsze W szczegoacutelności użyt-kownicy Linuksa lub innych systemoacutew uniksowych mogą być przyzwyczajeniu do przekierowania wejściawyjściazdo pliku czy łączenie komend w potoki (ang pipe) W takich sytuacjach dane nie są wyświetlane na ekranie aniodczytywane z klawiatury

67

68 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

102 Funkcje wyjścia

1021 Funkcja printf

W przykładzie ldquoHello Worldrdquo użyliśmy już jednej z dostępnych funkcji wyjścia a miano-wicie funkcji printf() Z punktu widzenia swoich możliwości jest to jedna z bardziej skom-plikowanych funkcji a jednocześnie jest jedną z najczęściej używanych Przyjrzyjmy sięponownie kodowi programu ldquoHello Worldrdquo

include ltstdiohgt

int main(void)

printf(Hello worldn)return 0

Po skompilowaniu i uruchomieniu program wypisze na ekranie

Hello world

W naszym przykładowym programie chcąc by funkcja printf() wypisała tekst na ekra-nie umieściliśmy go w cudzysłowach wewnątrz nawiasoacutew Ogoacutelnie wywołanie funkcjiprintf() wygląda następująco

printf(format argument1 argument2 )

Przykładowo

int i = 500printf(Liczbami całkowitymi są na przykład i oraz in 1 i)

wypisze

Liczbami całkowitymi są na przykład 1 oraz 500

Format to napis ujęty w cudzysłowy ktoacutery określa ogoacutelny kształt schemat tego co ma byćwyświetlone Format jest drukowany tak jak go napiszemy jednak niektoacutere znaki specjalnezostaną w nim podmienione na co innego Przykładowo znak specjalny n jest zamienianyna znak nowej linii 2 Natomiast procent jest podmieniany na jeden z argumentoacutew Po pro-cencie następuje specyfikacja jak wyświetlić dany argument W tym przykładzie i (od int)oznacza że argument ma być wyświetlony jak liczba całkowita W związku z tym że i mają specjalne znaczenie aby wydrukować je należy użyć ich podwoacutejnie

printf(Procent Backslash )

drukuje

Procent Backslash

(bez przejścia do nowej linii) Na liście argumentoacutew możemy mieszać ze sobą zmienne roacuteż-nych typoacutew liczby napisy itp w dowolnej liczbie Funkcja printf przyjmie ich tyle ile tylkonapiszemy Należy uważać by nie pomylić się w formatowaniu

2Zmiana ta następuje w momencie kompilacji programu i dotyczy wszystkich literałoacutew napisowych Nie jestto jakaś szczegoacutelna własność funkcji printf() Więcej o tego typu sekwencjach i ciągach znakoacutew w szczegoacutelnościopisane jest w rozdziale Napisy

103 FUNKCJA PUTS 69

int i = 5printf(i s i 5 4 napis) powinno być i i s

Przywłączeniu ostrzeżeń (opcja -Wall lub -WformatwGCC) kompilator powinien nas ostrzecgdy format nie odpowiada podanym elementom

Najczęstsze użycie printf()

printf(i i) gdy i jest typu int zamiast i można użyć d

printf(f i) gdy i jest typu float lub double

printf(c i) gdy i jest typu char (i chcemy wydrukować znak)

printf(s i) gdy i jest napisem (typu char)

Funkcja printf() nie jest żadną specjalną konstrukcją języka i łańcuch formatujący możebyć podany jako zmienna W związku z tym możliwa jest np taka konstrukcja

include ltstdiohgt

int main(void)

char buf[100]scanf(99s buf) funkcja wczytuje tekst do tablicy buf printf(buf)return 0

Program wczytuje tekst a następnie wypisuje go Jednak ponieważ znak procentu jesttraktowany w specjalny sposoacuteb toteż jeżeli na wejściu pojawi się ciąg znakoacutew zawierającyten znak mogą się stać roacuteżne dziwne rzeczy Między innymi z tego powodu w takich sytu-acjach lepiej używać funkcji puts() lub fputs() opisanych niżej lub wywołania printf(szmienna)

Więcej o funkcji printf()

103 Funkcja putsFunkcja puts() przyjmuje jako swoacutej argument ciąg znakoacutew ktoacutery następnie bezmyślnie wy-pisuje na ekran kończąc go znakiem przejścia do nowej linii W ten sposoacuteb nasz pierwszyprogram moglibyśmy napisać w ten sposoacuteb

include ltstdiohgt

int main(void)

puts(Hello world)return 0

W swoim działaniu funkcja ta jest w zasadzie identyczna do wywołania printf(snargument) jednak prawdopodobnie będzie działać szybciej Jedynym jejmankamentemmożebyć fakt że zawsze na końcu podawany jest znak przejścia do nowej linii Jeżeli jest to efektniepożądany (nie zawsze tak jest) należy skorzystać z funkcji fputs() opisanej niżej lub wy-wołania printf(s argument)

Więcej o funkcji puts()

70 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

104 Funkcja fputs

Opisując funkcję fputs() wybiegamy już trochę w przyszłość (a konkretnie do opisu operacjina plikach) ale warto o niej wspomnieć już teraz gdyż umożliwia ona wypisanie swojegoargumentu bez wypisania na końcu znaku przejścia do nowej linii

include ltstdiohgt

int main(void)

fputs(Hello worldn stdout)return 0

W chwili obecnej możesz się nie przejmować tym zagadkowym stdout wpisanym jakodrugi argument funkcji Jest to określenie strumienia wyjściowego (w naszymwypadku stan-dardowe wyjście mdash standard output)

Więcej o funkcji fputs()

1041 Funkcja putar

Funkcja putchar() służy do wypisywania pojedynczych znakoacutew Przykładowo jeżeli chcieli-byśmy napisać programwypisującyw prostej tabelce wszystkie liczby od do moglibyśmyto zrobić tak

include ltstdiohgt

int main(void)

int i = 0for ( ilt100 ++i) Nie jest to pierwsza liczba w wierszu if (i 10)

putchar( )printf(2d i) Jest to ostatnia liczba w wierszu if ((i 10)==9)

putchar(n)

return 0

Więcej o funkcji putchar()

105 FUNKCJE WEJŚCIA 71

105 Funkcje wejścia

1051 Funkcja scanf()

Teraz pomyślmy o sytuacji odwrotnej Tym razem to użytkownik musi powiedzieć coś pro-gramowi W poniższym przykładzie program podaje kwadrat liczby podanej przez użytkow-nika

include ltstdiohgt

int main ()

int liczba = 0printf (Podaj liczbę )scanf (d ampliczba)printf (dd=dn liczba liczba liczbaliczba)return 0

Zauważyłeś że w tej funkcji przy zmiennej pojawił się nowy operator mdash amp (etka) Jeston ważny gdyż bez niego funkcja scanf() nie skopiuje odczytanej wartości liczby do odpo-wiedniej zmiennej Właściwie oznacza przekazanie do funkcji adresu zmiennej by funkcjamogła zmienić jej wartość Nie musisz teraz rozumieć jak to się odbywa wszystko zostaniewyjaśnione w rozdziale Wskaźniki

Oznaczenia są podobne takie jak przy printf() czyli scanf(i ampliczba) wczytuje liczbętypu int scanf(f ampliczba) ndash liczbę typu float a scanf(s tablica znakoacutew) ciąg zna-koacutew Ale czemu w tym ostatnim przypadku nie ma etki Otoacuteż gdy podajemy jako argumentdo funkcji wyrażenie typu tablicowego zamieniane jest ono automatycznie na adres pierw-szego elementu tablicy Będzie to dokładniej opisane w rozdziale poświęconym wskaźnikom

Brak etki jest częstym błędem szczegoacutelnie wśroacuted początkujących programistoacutew Ponie-waż funkcja scanf() akceptuje zmienną liczbę argumentoacutew to nawet kompilator może miećkłopoty z wychwyceniem takich błędoacutew (konkretnie chodzi o to że standard nie wymagaod kompilatora wykrywania takich pomyłek) choć kompilator GCC radzi sobie z tym jeżelipodamy mu argument -Wformat

Należy jednak uważać na to ostatnie użycie Rozważmy na przykład poniższy kod

include ltstdiohgt

int main(void)

char tablica[100] 1 scanf(s tablica) 2 return 0

Robi on niewiele W linijce deklarujemy tablicę znakoacutew czyli mogącą przechowaćnapis długości znakoacutew Nie przejmuj się jeżeli nie do końca to wszystko rozumiesz mdash po-jęcia takie jak tablica czy ciąg znakoacutew staną się dla Ciebie jasne w miarę czytania kolejnych

72 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

rozdziałoacutew W linijce wywołujemy funkcję scanf() ktoacutera odczytuje tekst ze standardowegowejścia Nie zna ona jednak rozmiaru tablicy i nie wie ile znakoacutewmoże ona przechować przezco będzie czytać tyle znakoacutew aż napotka biały znak (format s nakazuje czytanie pojedyn-czego słowa) co może doprowadzić do przepełnienia bufora Niebezpieczne skutki czegośtakiego opisane są w rozdziale poświęconym napisom Na chwilę obecną musisz zapamiętaćżeby zaraz po znaku procentu podawać maksymalną liczbę znakoacutew ktoacutere może przechowaćbufor czyli liczbę o jeden mniejszą niż rozmiar tablicy Bezpieczna wersją powyższego kodujest

include ltstdiohgt

int main(void)

char tablica[100]scanf(99s tablica)return 0

Funkcja scanf() zwraca liczbę poprawnie wczytanych zmiennych lub EOF jeżeli nie majuż danych w strumieniu lub nastąpił błąd Załoacuteżmy dla przykładu że chcemy stworzyćprogram ktoacutery odczytuje po kolei liczby i wypisuje ich potęgi W pewnym momenciedane się kończą lub jest wprowadzana niepoprawna dana i woacutewczas nasz program powinienzakończyć działanie Aby to zrobić należy sprawdzać wartość zwracaną przez funkcję scanf()w warunku pętli

include ltstdiohgt

int main(void)

int nwhile (scanf(d ampn)==1)printf(dn nnn)

return 0

Podobnie możemy napisać program ktoacutery wczytuje po dwie liczby i je sumuje

include ltstdiohgt

int main(void)

int a bwhile (scanf(d d ampa ampb)==2)printf(dn a+b)

return 0

105 FUNKCJE WEJŚCIA 73

Rozpatrzmy teraz trochę bardziej skomplikowany przykład Otoacuteż ponownie jak poprzed-nio nasz program będzie wypisywał potęgę podanej liczby ale tym razem musi ignorowaćbłędne dane (tzn pomijać ciągi znakoacutew ktoacutere nie są liczbami) i kończyć działanie tylko wmomencie gdy nastąpi błąd odczytu lub koniec pliku3

include ltstdiohgt

int main(void)

int result ndoresult = scanf(d ampn)if (result==1)

printf(dn nnn)else if (result) result to to samo co result==0

result = scanf(s)

while (result=EOF)return 0

Zastanoacutewmy się przez chwilę co się dzieje w programie Najpierw wywoływana jestfunkcja scanf() i następuje proacuteba odczytu liczby typu int Jeżeli funkcja zwroacuteciła to liczbazostała poprawnie odczytana i następuje wypisanie jej trzeciej potęgi Jeżeli funkcja zwroacuteciła to na wejściu były jakieś dane ktoacutere nie wyglądały jak liczba W tej sytuacji wywołujemyfunkcję scanf() z formatem odczytującym dowolny ciąg znakoacutew nie będący białymi znakamiz jednoczesnym określeniem żeby nie zapisywała nigdzie wyniku W ten sposoacuteb niepopraw-nie wpisana dana jest omijana Pętla głoacutewna wykonuje się tak długo jak długo funkcja scanf()nie zwroacuteci wartości EOF

Więcej o funkcji scanf()

1052 Funkcja gets

Funkcja gets służy do wczytania pojedynczej linii Może Ci się to wydać dziwne ale funkcjitej nie należy używać pod żadnym pozorem Przyjmuje ona jeden argument mdash adres pierw-szego elementu tablicy do ktoacuterego należy zapisać odczytaną linię mdash i nic poza tym Z tegopowodu nie ma żadnej możliwości przekazania do tej funkcji rozmiaru bufora podanego jakoargument Podobnie jak w przypadku scanf() może to doprowadzić do przepełnienia buforaco może mieć tragiczne skutki Zamiast tej funkcji należy używać funkcji fgets()

Więcej o funkcji gets()

1053 Funkcja fgets

Funkcja fgets() jest bezpieczną wersją funkcji gets() ktoacutera dodatkowo może operować nadowolnych strumieniach wejściowych Jej użycie jest następujące

3Jak rozroacuteżniać te dwa zdarzenia dowiesz się w rozdziale Czytanie i pisanie do plikoacutew

74 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

fgets(tablica_znakoacutew rozmiar_tablicy_znakoacutew stdin)

Na chwilę obecną nie musisz się przejmować ostatnim argumentem (jest to określeniestrumienia w naszym przypadku standardowewejście mdash standard input) Funkcja czyta tekstaż do napotkania znaku przejścia do nowej linii ktoacutery także zapisuje w wynikowej tablicy(funkcja gets() tego nie robi) Jeżeli brakuje miejsca w tablicy to funkcja przerywa czytanie wten sposoacuteb aby sprawdzić czy została wczytana cała linia czy tylko jej część należy sprawdzićczy ostatnim znakiem nie jest znak przejścia do nowej linii Jeżeli nastąpił jakiś błąd lub nawejściu nie ma już danych funkcja zwraca wartość NULL

include ltstdiohgt

int main(void) char buffer[128] whole_line = 1 chwhile (fgets(buffer sizeof buffer stdin)) 1

if (whole_line) 2 putchar(gt)if (buffer[0]=gt)

putchar( )

fputs(buffer stdout) 3 for (ch = buffer ch ampamp ch=n ++ch) 4 whole_line = ch == n

if (whole_line)

putchar(n)return 0

Powyższy kod wczytuje dane ze standardowego wejścia mdash linia po linii mdash i dodaje napoczątku każdej linii znak większości po ktoacuterym dodaje spację jeżeli pierwszym znakiem nalinii nie jest znak większości W linijce następuje odczytywanie linii Jeżeli nie ma już wię-cej danych lub nastąpił błąd wejścia funkcja zwraca wartość NULL ktoacutera ma logiczną war-tość i woacutewczas pętla kończy działanie W przeciwnym wypadku funkcja zwraca po prostupierwszy argument ktoacutery ma wartość logiczną W linijce sprawdzamy czy poprzedniewywołanie funkcji wczytało całą linię czy tylko jej część mdash jeżeli całą to teraz jesteśmy napoczątku linii i należy dodać znakwiększości W linii najzwyczajniej w świecie wypisujemylinię W linii przeszukujemy tablicę znak po znaku aż do momentu gdy znajdziemy znako kodzie kończącym ciąg znakoacutew albo znak przejścia do nowej linii Ten drugi przypadekoznacza że funkcja fgets() wczytała całą linię

Więcej o funkcji fgets()

1054 Funkcja getar()

Jest to bardzo prosta funkcja wczytująca znak z klawiatury W wielu przypadkach danemogą być buforowane przez co wysyłane są do programu dopiero gdy bufor zostaje przepeł-niony lub na wejściu jest znak przejścia do nowej linii Z tego powodu po wpisaniu danegoznaku należy nacisnąć klawisz enter aczkolwiek trzeba pamiętać że w następnym wywoła-niu zostanie zwroacutecony znak przejścia do nowej linii Gdy nastąpił błąd lub nie ma już więcej

105 FUNKCJE WEJŚCIA 75

danych funkcja zwraca wartość EOF (ktoacutera ma jednak wartość logiczną toteż zwykła pętlawhile (getchar()) nie da oczekiwanego rezultatu)

include ltstdiohgt

int main(void)

int cwhile ((c = getchar())=EOF)

if (c== ) c = _

putchar(c)

return 0

Ten prosty program wczytuje dane znak po znaku i zamienia wszystkie spacje na znakipodkreślenia Może wydać się dziwne że zmienną c zdefiniowaliśmy jako trzymającą typ inta nie char Właśnie taki typ (tj int) zwraca funkcja getchar() i jest to konieczne ponieważwartość EOF wykracza poza zakres wartości typu char (gdyby tak nie było to nie byłobymożliwości rozroacuteżnienia wartości EOF od poprawnie wczytanego znaku) Więcej o funkcjigetchar()

76 ROZDZIAŁ 10 PODSTAWOWE PROCEDURY WEJŚCIA I WYJŚCIA

Rozdział 11

Funkcje

W matematyce pod pojęciem funkcji rozumiemy twoacuter ktoacutery pobiera pewną liczbę argumen-toacutew i zwraca wynik1 Jeśli dla przykładu weźmiemy funkcję sin(x) to x będzie zmiennąrzeczywistą ktoacutera określa kąt a w rezultacie otrzymamy inną liczbę rzeczywistą mdash sinustego kąta

W C funkcja (czasami nazywana podprogramem rzadziej procedurą) to wydzielona częśćprogramu ktoacutera przetwarza argumenty i ewentualnie zwraca wartość ktoacutera następnie możebyć wykorzystana jako argument w innych działaniach lub funkcjach Funkcja może posia-dać własne zmienne lokalne W odroacuteżnieniu od funkcji matematycznych funkcje w C mogązwracać dla tych samych argumentoacutew roacuteżne wartości

Po lekturze poprzednich części podręcznika zapewne moacutegłbyś podać kilka przykładoacutewfunkcji z ktoacuterych korzystałeś Były to np

funkcja printf() drukująca tekst na ekranie czy

funkcja main() czyli głoacutewna funkcja programu

Głoacutewną motywacją tworzenia funkcji jest unikanie powtarzania kilka razy tego samegokodu W poniższym fragmencie

for(i=1 i lt= 5 ++i) printf(d ii)

for(i=1 i lt= 5 ++i)

printf(d iii)for(i=1 i lt= 5 ++i)

printf(d ii)

widzimy że pierwsza i trzecia pętla for są takie same Zamiast kopiować fragment kodukilka razy (co jest mało wygodne i może powodować błędy) lepszym rozwiązaniem mogłobybyć wydzielenie tego fragmentu tak by można go było wywoływać kilka razy Tak właśniedziałają funkcje

Innym niemniej ważnym powodem używania funkcji jest rozbicie programu na frag-menty wg ich funkcjonalności Oznacza to że z jeden duży program dzieli się na mniejsze

1Aby nie urażać matematykoacutew sprecyzujmy że chodzi o relację między zbiorami X i Y (X jest dziedziną Y jestprzeciwdziedziną) takie że każdy element zbioru X jest w relacji z dokładnie jednym elementem ze zbioru Y

77

78 ROZDZIAŁ 11 FUNKCJE

funkcje ktoacutere są ldquowyspecjalizowanerdquo w wykonywaniu określonych czynności Dzięki temułatwiej jest zlokalizować błąd Ponadto takie funkcje można potem przenieść do innych pro-gramoacutew

111 Tworzenie funkcji

Dobrze jest uczyć się na przykładach Rozważmy następujący kod

int iloczyn (int x int y)

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

int iloczyn (int x int y) to nagłoacutewek funkcji ktoacutery opisuje jakie argumenty przyj-muje funkcja i jaką wartość zwraca (funkcja może przyjmować wiele argumentoacutew lecz możezwracać tylko jedną wartość)2 Na początku podajemy typ zwracanej wartości mdash u nas intNastępnie mamy nazwę funkcji i w nawiasach listę argumentoacutew

Ciało funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasachklamrowych Pierwszą instrukcją jest deklaracja zmiennej mdash jest to zmienna lokalna czyliniewidoczna poza funkcją Dalej przeprowadzamy odpowiednie działania i zwracamy rezul-tat za pomocą instrukcji return

1111 Ogoacutelnie

Funkcję w języku C tworzy się następująco

typ identyfikator (typ1 argument1 typ2 argument2 typ_n argument_n)

instrukcje

Oczywiście istnieje możliwość utworzenia funkcji ktoacutera nie posiada żadnych argumen-toacutew Definiuje się ją tak samo jak funkcję z argumentami z tą tylko roacuteżnicą że międzyokrągłymi nawiasami nie znajduje się żaden argument lub pojedyncze słoacutewko void mdash w de-finicji funkcji nie ma to znaczenia jednak w deklaracji puste nawiasy oznaczają że prototypnie informuje jakie argumenty przyjmuje funkcja dlatego bezpieczniej jest stosować słoacutewkovoid

Funkcje definiuje się poza głoacutewną funkcją programu (main) W języku C nie można two-rzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji)

1112 Procedury

Przyjęło się że procedura od funkcji roacuteżni się tym że ta pierwsza nie zwraca żadnej wartościZatem aby stworzyć procedurę należy napisać

2Bardziej precyzyjnie można powiedzieć że funkcja może zwroacutecić tylko jedną wartość typu prostego lub jedenadres do jakiegoś obiektu w pamięci

112 WYWOŁYWANIE 79

void identyfikator (argument1 argument2 argumentn)

instrukcje

void (z ang pusty proacuteżny) jest słowem kluczowym mającym kilka znaczeń w tym przy-padku oznacza ldquobrak wartościrdquo

Generalnie w terminologii C pojęcie ldquoprocedurardquo nie jest używane moacutewi się raczej ldquofunk-cja zwracająca voidrdquo

Jeśli nie podamy typu danych zwracanych przez funkcję kompilator domyślnie przyjmietyp int choć już w standardzie C nieokreślenie wartości zwracanej jest błędem

1113 Stary sposoacuteb definiowania funkcji

Zanim powstał standard ANSI C w liście parametroacutew nie podawało się typoacutew argumentoacutewa jedynie ich nazwy Roacutewnież z tamtych czasoacutew wywodzi się oznaczenie iż puste nawiasy(w prototypie funkcji nie w definicji) oznaczają że funkcja przyjmuje nieokreśloną liczbęargumentoacutew Tego archaicznego sposobu definiowania funkcji nie należy już stosować aleponieważ w swojej przygodzie z językiem C Czytelnik może się na nią natknąć a co więcejstandard nadal (z powodu zgodności z wcześniejszymi wersjami) dopuszcza taką deklaracjęto należy tutaj o niej wspomnieć Otoacuteż wygląda ona następująco

typ_zwracany nazwa_funkcji(argument1 argument2 argumentn)typ1 argumenty typ2 argumenty

instrukcje

Na przykład wcześniejsza funkcja iloczyn wyglądałaby następująco

int iloczyn(x y)int x y

int iloczyn_xyiloczyn_xy = xyreturn iloczyn_xy

Najpoważniejszą wadą takiego sposobu jest fakt że w prototypie funkcji niema podanychtypoacutew argumentoacutew przez co kompilator nie jest w stanie sprawdzić poprawności wywołaniafunkcji Naprawiono to (wprowadzając definicje takie jak je znamy obecnie) najpierw wjęzyku C++ a potem rozwiązanie zapożyczono w standardzie ANSI C z roku

112 WywoływanieFunkcje wywołuje się następująco

80 ROZDZIAŁ 11 FUNKCJE

identyfikator (argument1 argument2 argumentn)

Jeśli chcemy aby przypisać zmiennej wartość ktoacuterą zwraca funkcja należy napisać tak

zmienna = funkcja (argument1 argument2 argumentn)

Programiści mający doświadczenia np z językiem Pascal mogą popełniać błąd polegającyna wywoływaniu funkcji bez nawiasoacutew okrągłych gdy nie przyjmuje ona żadnych argumen-toacutew

Przykładowo mamy funkcję

void pisz_komunikat()

printf(To jest komunikatn)

Jeśli teraz ją wywołamy

pisz_komunikat ŹLE pisz_komunikat() dobrze

to pierwsze polecenie nie spowoduje wywołania funkcji Dlaczego Aby kompilator Czrozumiał że chodzi nam o wywołanie funkcji musimy po jej nazwie dodać nawiasy okrą-głe nawet gdy funkcja nie ma argumentoacutew Użycie samej nazwy funkcji ma zupełnie inneznaczenie mdash oznacza pobranie jej adresu W jakim celu O tym będzie mowa w rozdzialeWskaźniki

PrzykładA oto działający przykład ktoacutery demonstruje wiadomości podane powyżej

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

int m = suma (4 5)printf (4+5=dn m)return 0

113 Zwracanie wartościreturn to słowo kluczowe języka C

W przypadku funkcji służy ono do

przerwania funkcji (i przejścia do następnej instrukcji w funkcji wywołującej)

114 ZWRACANA WARTOŚĆ 81

zwroacutecenia wartości

W przypadku procedur powoduje przerwania procedury bez zwracania wartościUżycie tej instrukcji jest bardzo proste i wygląda tak

return zwracana_wartość

lub dla procedur

return

Możliwe jest użycie kilku instrukcji returnw obrębie jednej funkcji Wielu programistoacutewuważa jednak że lepsze jest użycie jednej instrukcji return na końcu funkcji gdyż ułatwia tośledzenie przebiegu programu

114 Zwracana wartość

W C zwykle przyjmuje się że oznacza poprawne zakończenie funkcji

return 0 funkcja zakończona sukcesem

a inne wartości oznaczają niepoprawne zakończenie

return 1 funkcja zakończona niepowodzeniem

Ta wartość może być wykorzystana przez inne instrukcje np if

115 Funkcja main()

Do tej pory we wszystkich programach istniała funkcja main() Po co tak właściwie onajest Otoacuteż jest to funkcja ktoacutera zostaje wywołana przez fragment kodu inicjującego pracęprogramu Kod ten tworzony jest przez kompilator i nie mamy na niego wpływu Istotnejest że każdy program w języku C musi zawierać funkcję main()

Istnieją dwa możliwe prototypy (nagłoacutewki) omawianej funkcji

int main(void)

int main(int argc char argv) 3

Argument argc jest liczbą nieujemną określającą ile ciągoacutew znakoacutew przechowywanychjest w tablicy argv Wyrażenie argv[argc] ma zawsze wartość Pierwszym elementemtablicy argv (o ile istnieje4) jest nazwa programu czy komenda ktoacuterą program został urucho-miony Pozostałe przechowują argumenty podane przy uruchamianiu programu

Zazwyczaj jeśli program uruchomimy poleceniem

program argument1 argument2

3Czasami można się spotkać z prototypem int main(int argc char argv char env) ktoacutery jest definio-wany w standardzie ale wykracza już poza standard C

4Inne standardy mogą wymuszać istnienie tego elementu jednak jeśli chodzi o standard języka C to nic nie stoina przeszkodzie aby argument argc miał wartość zero

82 ROZDZIAŁ 11 FUNKCJE

to argc będzie roacutewne ( argumenty + nazwa programu) a argv będzie zawierać napisy pro-gram argument argument umieszczone w tablicy indeksowanej od do

Weźmy dla przykładu program ktoacutery wypisuje to co otrzymuje w argumentach argc iargv

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) while (argv)

puts(argv++) Ewentualnie można użycint ifor (i = 0 iltargc ++i)

puts(argv[i])return EXIT_SUCCESS

Uruchomiony w systemie typu UNIX poleceniem test foo bar baz powinien wypisać

testfoobarbaz

Na razie nie musisz rozumieć powyższych kodoacutew i opisoacutew gdyż odwołują się do pojęćtakich jak tablica oraz wskaźnik ktoacutere opisane zostaną w dalszej części podręcznika

Co ciekawe funkcja main nie roacuteżni się zanadto od innych funkcji i tak jak inne możewołać sama siebie (patrz rekurencja niżej) przykładowo powyższy program można zapisaćtak5

include ltstdiohgtinclude ltstdlibhgt

int main(int argc char argv) if (argv)

puts(argv)return main(argc-1 argv+1)

else return EXIT_SUCCESS

Ostatnią rzeczą dotyczącą funkcji main jest zwracana przez nią wartość Już przy oma-wianiu pierwszego programu wspomniane zostało że jedynymi wartościami ktoacutere znaczą

5Jeżeli ktoś lubi ekstrawagancki kod ciało funkcji main można zapisać jako return argv puts(argv)main(argc-1 argv+1) EXIT SUCCESS ale nie radzimy stosować tak skomplikowanych i bądź co bądź małoczytelnych konstrukcji

116 DALSZE INFORMACJE 83

zawsze to samowewszystkich implementacjach języka są EXIT SUCCESS i EXIT FAILURE6

zdefiniowane w pliku nagłoacutewkowym stdlibh Wartość i EXIT SUCCESS oznaczają po-prawne zakończenie programu (co wcale nie oznacza że makro EXIT SUCCESS ma wartośćzero) natomiast EXIT FAILURE zakończenie błędne Wszystkie inne wartości są zależne odimplementacji

116 Dalsze informacjePoniżej przekażemy ci parę bardziej zaawansowanych informacji o funkcjach w C jeśli niemasz ochoty wgłębiać się w szczegoacuteły możesz spokojnie pominąć tę część i wroacutecić tu poacuteźniej

1161 Jak zwroacutecić kilka wartości

Jeśli chcesz zwroacutecić z funkcji kilka wartości musisz zrobić to w trochę inny sposoacuteb Ge-neralnie możliwe są dwa podejścia jedno to ldquoupakowanierdquo zwracanych wartości ndash możnastworzyć tak zwaną strukturę ktoacutera będzie przechowywała kilka zmiennych (jest to opisanew rozdziale Typy złożone) Prostszym sposobem jest zwracanie jednej z wartości w normalnysposoacuteb a pozostałych jako parametroacutew Za chwilę dowiesz się jak to zrobić jeśli chcesz zo-baczyć przykład możesz przyjrzeć się funkcji scanf() z biblioteki standardowej

1162 Przekazywanie parametroacutew

Gdy wywołujemy funkcję wartość argumentoacutew z ktoacuterymi ją wywołujemy jest kopiowanado funkcji Kopiowana mdash to znaczy że nie możemy normalnie zmienić wartości zewnętrz-nych dla funkcji zmiennych Formalnie moacutewi się że w C argumenty są przekazywane przezwartość czyli wewnątrz funkcji operujemy tylko na ich kopiach

Możliwe jest modyfikowanie zmiennych przekazywanych do funkcji jako parametry mdashale do tego w C potrzebne są wskaźniki

1163 Funkcje rekurencyjne

Język Cmamożliwość tworzenia tzw funkcji rekurencyjny Jest to funkcja ktoacuteraw swojejwłasnej definicji (ciele) wywołuje samą siebie Najbardziej klasycznym przykładem może tubyć silnia Napiszmy sobie zatem naszą funkcję rekurencyjną ktoacutera oblicza silnię

int silnia (int liczba)

int silif (liczbalt0) return 0 wywołanie jest bezsensowne

zwracamy 0 jako kod błędu if (liczba==0 || liczba==1) return 1sil = liczbasilnia(liczba-1)return sil

Musimy być ostrożni przy funkcjach rekurencyjnych gdyż łatwo za ich pomocą utworzyćfunkcję ktoacutera będzie sama siebie wywoływała w nieskończoność a co za tym idzie będzie za-wieszała program Tutaj pierwszymi instrukcjami if ustalamy ldquowarunki stopurdquo gdzie kończy

6Uwaga Makra EXIT SUCCESS i EXIT FAILURE te służą tylko i wyłącznie jako wartości do zwracania przezfunkcję main() Nigdzie indziej nie mają one zastosowania

84 ROZDZIAŁ 11 FUNKCJE

się wywoływanie funkcji przez samą siebie a następnie określamy jak funkcja będzie wy-woływać samą siebie (odjęcie jedynki od argumentu co do ktoacuterego wiemy że jest dodatnigwarantuje że dojdziemy do warunku stopu w skończonej liczbie krokoacutew)

Warto też zauważyć że funkcje rekurencyjne czasami mogą być znacznie wolniejsze niżpodejście nierekurencyjne (iteracyjne przy użyciu pętli) Flagowym przykłademmoże tu byćfunkcja obliczająca wyrazy ciągu Fibonacciego

include ltstdiohgt

unsigned count

unsigned fib_rec(unsigned n) ++countreturn nlt2 n (fib_rec(n-2) + fib_rec(n-1))

unsigned fib_it (unsigned n) unsigned a = 0 b = 0 c = 1++countif (n) return 0while (--n)

++counta = bb = cc = a + b

return c

int main(void) unsigned n resultprintf(Ktory element ciagu Fibonacciego obliczyc )while (scanf(d ampn)==1)

count = 0result = fib_rec(n)printf(fib_ret(3u) = 6u (wywolan 5u)n n result count)

count = 0result = fib_it (n)printf(fib_it (3u) = 6u (wywolan 5u)n n result count)

return 0

W tym przypadku funkcja rekurencyjna choć łatwiejsza w napisaniu jest bardzo nie-efektywna

116 DALSZE INFORMACJE 85

1164 Deklarowanie funkcji

Czasami możemy chcieć przed napisaniem funkcji poinformować kompilator że dana funkcjaistnieje Niekiedy kompilator może zaprotestować jeśli użyjemy funkcji przed określeniemjaka to funkcja na przykład

int a()

return b(0)

int b(int p)

if( p == 0 )return 1

elsereturn a()

int main()

return b(1)

W tym przypadku nie jesteśmy w stanie zamienić a i b miejscami bo obie funkcje korzy-stają z siebie nawzajem Rozwiązaniem jest wcześniejsze zadeklarowanie funkcji Deklara-cja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji(przed otwierającym nawiasem klamrowym) z dodatkowo dodanym średnikiem na końcu Wnaszym przykładzie wystarczy na samym początku wstawić

int b(int p)

W deklaracji można pominąć nazwy parametroacutew funkcji

int b(int)

Bardzo częstym zwyczajem jest wypisanie przed funkcją main samych prototypoacutew funk-cji by ich definicje umieścić po definicji funkcji main np

int a(void)int b(int p)

int main()

return b(1)

int a()

return b(0)

86 ROZDZIAŁ 11 FUNKCJE

int b(int p)

if( p == 0 )return 1

elsereturn a()

Z poprzednich rozdziałoacutew pamiętasz że na początku programu dołączaliśmy tzw plikinagłoacutewkowe Zawierają one właśnie prototypy funkcji i ułatwiają pisanie dużych progra-moacutew Dalsze informacje o plikach nagłoacutewkowych zawarte są w rozdziale Tworzenie biblio-tek

1165 Zmienna liczba parametroacutew

Zauważyłeś zapewne że używając funkcji printf() lub scanf() po argumencie zawierającymtekst z odpowiednimi modyfikatorami mogłeś podać praktycznie nieograniczoną liczbę ar-gumentoacutew Zapewne deklaracja obu funkcji zadziwi Cię jeszcze bardziej

int printf(const char format )int scanf(const char format )

Jak widzisz w deklaracji zostały użyte kropki Otoacuteż język C ma możliwość przekazywa-nia nieograniczonej liczby argumentoacutew do funkcji (tzn jedynym ograniczeniem jest rozmiarstosu programu) Cała zabawa polega na tym aby umieć dostać się do odpowiedniego ar-gumentu oraz poznać jego typ (używając funkcji printf mogliśmy wpisać jako argument do-wolny typ danych) Do tego celu możemy użyć wszystkich ciekawostek zawartych w plikunagłoacutewkowym stdargh

Załoacuteżmy że chcemy napisać prostą funkcję ktoacutera dajmy na to mnoży wszystkie swojeargumenty (zakładamy że argumenty są typu int) Przyjmujemy przy tym że ostatni argu-ment będzie Będzie ona wyglądała tak

include ltstdarghgt

int mnoz (int pierwszy )

va_list argint iloczyn = 1 tva_start (arg pierwszy)for (t = pierwszy t t = va_arg(arg int))

iloczyn = tva_end (arg)return iloczyn

va list oznacza specjalny typ danych w ktoacuterym przechowywane będą argumenty prze-kazane do funkcji ldquova startrdquo inicjuje arg do dalszego użytku Jako drugi parametr musimypodać nazwę ostatniego znanego argumentu funkcji Makropolecenie va arg odczytuje ko-lejne argumenty i przekształca je do odpowiedniego typu danych Na zakończenie używanejest makro va end mdash jest ono obowiązkowe

117 ZOBACZ TEŻ 87

Oczywiście tak samo jak w przypadku funkcji printf() czy scanf() argumenty nie musząbyć takich samych typoacutew Rozważmy dla przykładu funkcję podobną do printf() ale znacznieuproszczoną

include ltstdarghgt

void wypisz(const char format ) va_list argva_start (arg format)for ( format ++format)

switch (format) case i printf(d va_arg(arg int)) breakcase I printf(u va_arg(arg unsigned)) breakcase l printf(ld va_arg(arg int)) breakcase L printf(lu va_arg(arg unsigned long)) breakcase f printf(f va_arg(arg double)) breakcase x printf(x va_arg(arg unsigned)) breakcase X printf(X va_arg(arg unsigned)) breakcase s printf(s va_arg(arg const char )) breakdefault putc(format)

va_end (arg)

Przyjmuje ona jako argument ciąg znakoacutew w ktoacuterych niektoacutere instruują funkcję bypobrała argument i go wypisała Nie przejmuj się jeżeli nie rozumiesz wyrażeń format i++format Istotne jest to że pętla sprawdza po kolei wszystkie znaki formatu

1166 Ezoteryka C

C ma wiele niuansoacutew o ktoacuterych wielu programistoacutew nie wie lub łatwo o nich zapomina

jeśli nie podamy typu wartości zwracanej w funkcji zostanie przyjęty typ int (wedługnajnowszego standardu C nie podanie typu wartości jest zwracane jako błąd)

jeśli nie podamy żadnych parametroacutew funkcji to funkcja będzie używała zmiennej ilo-ści parametroacutew (inaczej niż wC++ gdzie przyjęte zostanie że funkcja nie przyjmuje ar-gumentoacutew) Aby wymusić pustą listę argumentoacutew należy napisać int funkcja(void)(dotyczy to jedynie prototypoacutew czy deklaracji funkcji)

jeśli nie użyjemy w funkcji instrukcji return wartość zwracana będzie przypadkowa(dostaniemy śmieci z pamięci)

Kompilator C++ użyty do kompilacji kodu C najczęściej zaprotestuje i ostrzeże nas jeśliużyjemy powyższych konstrukcji Natomiast czysty kompilator C z domyślnymi ustawie-niami nie napisze nic i bez mrugnięcia okiem skompiluje taki kod

117 Zobacz też C++Funkcje inline mdash funkcje rozwijane wmiejscu wywoływania (dostępne też w stan-

dardzie C)

88 ROZDZIAŁ 11 FUNKCJE

C++Przeciążanie funkcji

Rozdział 12

Preprocesor

121 Wstęp

W języku C wszystkie linijki zaczynające się od symbolu rdquo nie podlegają bezpośrednio pro-cesowi kompilacji Są to natomiast instrukcje preprocesora mdash elementu kompilatora ktoacuteryanalizuje plik źroacutedłowy w poszukiwaniu wszystkich wyrażeń zaczynających się od rdquo Napodstawie tych instrukcji generuje on kod w czystymrdquo języku C ktoacutery następnie jest kom-pilowany przez kompilator Ponieważ za pomocą preprocesora można niemal sterowaćrdquokompilatorem daje on niezwykłe możliwości ktoacutere nie były dotąd znane w innych językachprogramowania Aby przekonać się jak wygląda kod przetworzony przez preprocesor użyj(w kompilatorze gcc) przełącznika -Erdquo

gcc testc -E -o testtxt

W pliku testtxt zostanie umieszczony cały kod w postaci ktoacutera zdatna jest do przetwo-rzenia przez kompilator

122 Dyrektywy preprocesora

Dyrektywy preprocesora są towyrażenia ktoacutere występują zaraz za symbolem rdquo i to właśnieza ich pomocą możemy używać preprocesora Dyrektywa zaczyna się od znaku i kończysię wraz z końcem linii Aby przenieść dalszą część dyrektywy do następnej linii należyzakończyć linię znakiem rdquo

define add(ab) a+b

Omoacutewimy teraz kilka ważniejszych dyrektyw

1221 include

Najpopularniejsza dyrektywa wstawiająca w swoje miejsce treść pliku podanego w nawia-sach ostrych lub cudzysłowie Składnia

89

90 ROZDZIAŁ 12 PREPROCESOR

Przykład 1

include ltplik_naglowkowy_do_dolaczeniagt

Za pomocą include możemy dołączyć dowolny plik mdash niekoniecznie plik nagłoacutewkowy

Przykład 2

include plik_naglowkowy_do_dolaczenia

Jeżeli nazwa pliku nagłoacutewkowego będzie ujęta w nawiasy ostre (przykład ) to kompi-lator poszuka go wśroacuted własnych plikoacutew nagłoacutewkowych (ktoacutere najczęściej się znajdują wpodkatalogu includesrdquo w katalogu kompilatora) Jeśli jednak nazwa ta będzie ujęta w po-dwoacutejne cudzysłowy(przykład ) to kompilator poszuka jej w katalogu w ktoacuterym znajdujesię kompilowany plik (można zmienić to zachowanie w opcjach niektoacuterych kompilatoroacutew)Przy użyciu tej dyrektywy można także wskazać dokładne położenie plikoacutew nagłoacutewkowychpoprzez wpisanie bezwzględnej lub względnej ścieżki dostępu do tego pliku nagłoacutewkowego

Przykład 3 mdash ścieżka bezwzględna do pliku nagłoacutewkowego w Linuksie i w Windowsie

Opis W miejsce jednej i drugiej linijki zostanie wczytany plik umieszczony w danej lokali-zacji

include usrincludeplik_nagłoacutewkowyhinclude Cborlandincludesplik_nagłoacutewkowyh

Przykład 4 mdash ścieżka względna do pliku nagłoacutewkowego

Opis W miejsce linijki zostanie wczytany plik umieszczony w katalogu katalogrdquo a tenkatalog jest w katalogu z plikiem źroacutedłowym Inaczej moacutewiąc jeśli plik źroacutedłowy jest wkatalogu homeuserdokumentyzrodlardquo to plik nagłoacutewkowy jest umieszczony w kataloguhomeuserdokumentyzrodlakatalogrdquo

include katalog1plik_naglowkowyh

Przykład 5 mdash ścieżka względna do pliku nagłoacutewkowego

Opis Jeśli plik źroacutedłowy jest umieszczony w katalogu homeuserdokumentyzrodlardquo toplik nagłoacutewkowy znajduje się w katalogu homeuserdokumentykatalogkatalogrdquo

include katalog1katalog2plik_naglowkowyh

Więcej informacji możesz uzyskać w rozdziale Biblioteki

1222 define

Linia pozwalająca zdefiniować stałą funkcję lub słowo kluczowe ktoacutere będzie potem pod-mienione w kodzie programu na odpowiednią wartość lub może zostać użyte w instrukcjachwarunkowych dla preprocesora Składnia

define NAZWA_STALEJ WARTOSC

lub

define NAZWA_STALEJ

122 DYREKTYWY PREPROCESORA 91

Przykład

define LICZBA mdash spowoduje że każde wystąpienie słowa LICZBA w kodzie zostanie za-stąpione oacutesemkądefine SUMA(ab) (a+b) mdash spowoduje ze każde wystąpienie wywołania funkcjirdquo SUMA zo-stanie zastąpione przez sumę argumentoacutew

1223 undef

Ta instrukcja odwołuje definicję wykonaną instrukcją define

undef STALA

1224 instrukcje warunkowe

Preprocesor zawiera roacutewnież instrukcje warunkowe pozwalające nawyboacuter tego coma zostaćskompilowane w zależności od tego czy stała jest zdefiniowana lub jaką ma wartość

if elif else endif

Te instrukcje uzależniają kompilacje od warunkoacutew Ich działanie jest podobne do instrukcjiwarunkowych w samym języku C I tak

if wprowadza warunek ktoacutery jeśli nie jest prawdziwy powoduje pominięcie kompilowaniakodu aż do napotkania jednej z poniższych instrukcji

else spowoduje skompilowanie kodu jeżeli warunek za if jest nieprawdziwy aż do napo-tkania ktoacuteregoś z poniższych instrukcji

elif wprowadza nowy warunek ktoacutery będzie sprawdzony jeżeli poprzedni był niepraw-dziwy Stanowi połączenie instrukcji if i else

endif zamyka blok ostatniej instrukcji warunkowej

Przykład

if INSTRUKCJE == 2printf (Podaj liczbę z przedziału 10 do 0n) 1

elif INSTRUKCJE == 1printf (Podaj liczbę ) 2

elseprintf (Podaj parametr ) 3

endifscanf (dn ampliczba)4

wiersz nr zostanie skompilowany jeżeli stała INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany gdy INSTRUKCJE będzie roacutewna

wiersz nr zostanie skompilowany w pozostałych wypadkach

wiersz nr będzie kompilowany zawsze

92 ROZDZIAŁ 12 PREPROCESOR

ifdef ifndef else endif

Te instrukcje warunkują kompilację od tego czy odpowiednia stała została zdefiniowana

ifdef spowoduje że kompilator skompiluje poniższy kod tylko gdy została zdefiniowanaodpowiednia stała

ifndef ma odwrotne działanie do ifdef a mianowicie brak definicji odpowiedniej stałejumożliwia kompilacje poniższego kodu

elseendif mają identyczne zastosowanie jak te z powyższej grupy

Przykład

define INFO definicja stałej INFOifdef INFO

printf (Twoacutercą tego programu jest Jan Kowalskin)1endififndef INFO

printf (Twoacutercą tego programu jest znany programistan)2endif

To czy dowiemy się kto jest twoacutercą tego programu zależy czy instrukcja definiująca stałąINFO będzie istnieć W powyższym przypadku na ekranie powinno się wyświetlić

Twoacutercą tego programu jest Jan Kowalski

1225 error

Powoduje przerwanie kompilacji i wyświetlenie tekstu ktoacutery znajduje się za tą instrukcjąPrzydatne gdy chcemy zabezpieczyć się przed zdefiniowaniem nieodpowiednich stałych

Przykład

if BLAD == 1error Poważny błąd kompilacjiendif

Co jeżeli zdefiniujemy stałą BLAD z wartością Spowoduje to wyświetlenie w trakcie kom-pilacji komunikatu podobnego do poniższego

Fatal error programc 6 Error directive Poważny błąd kompilacjiin function main() 1 errors in Compile

wraz z przerwaniem kompilacji

1226 warning

Wyświetla tekst zawarty w cudzysłowach jako ostrzeżenie Jest często używany do sygna-lizacji programiście że dana część programu jest przestarzała lub może sprawiać problemy

122 DYREKTYWY PREPROCESORA 93

Przykład

warning To jest bardzo prosty program

Spowoduje to takie oto zachowanie kompilatora

testc32 warning warning To jest bardzo prosty program

Użycie dyrektywy warning nie przerywa procesu kompilacji i służy tylko do wyświetlaniakomunikatoacutew dla programisty w czasie kompilacji programu

1227 line

Powoduje wyzerowanie licznika linii kompilatora ktoacutery jest używany przy wyświetlaniuopisu błędoacutew kompilacji Pozwala to na szybkie znalezienie możliwej przyczyny błędu wrozbudowanym programie

Przykład

printf (Podaj wartość funkcji)lineprintf (W przedziale od 10 do 0n) tutaj jest błąd - brak cudzysłowu zamykającego

Jeżeli teraz nastąpi proacuteba skompilowania tego kodu to kompilator poinformuje że wystąpiłbłąd składni w linii a nie np

1228 Makra

Preprocesor języka C umożliwia też tworzenie makr czyli automatycznie wykonywanychczynności Makra deklaruje się za pomocą dyrektywy define

define MAKRO(arg1 arg2 ) (wyrażenie)

Wmomencie wystąpienia MAKRA w tekście preprocesor automatycznie zamieni makrona wyrażenie Makra mogą być pewnego rodzaju alternatywami dla funkcji ale powinnosię ich używać tylko w specjalnych przypadkach Ponieważ makro sprowadza się do pro-stego zastąpienia przez preprocesor wywołania makra przez jego tekst jest bardzo podatnena trudne do zlokalizowania błędy (kompilator będzie podawał błędy wmiejscach w ktoacuterychnic nie widzimy mdash bo preprocesor wstawił tam tekst) Makra są szybsze (nie następuje wy-wołanie funkcji ktoacutere zawsze zajmuje trochę czasu1) ale też mniej bezpieczne i elastyczneniż funkcje

Przeanalizujmy teraz fragment kodu

include ltstdiohgtdefine KWADRAT(x) ((x)(x))

int main ()

printf (2 do kwadratu wynosi dn KWADRAT(2))return 0

1Tak naprawdę wg standardu C99 istnieje możliwość napisania funkcji ktoacuterej kod także będzie wstawiany wmiejscu wywołania Odbywa się to dzięki inline

94 ROZDZIAŁ 12 PREPROCESOR

Preprocesor w miejsce wyrażenia KWADRAT(2) wstawił ((2)(2)) Zastanoacutewmy się costałoby się gdybyśmy napisali KWADRAT(2) Preprocesor po prostu wstawi napis do koduco da wyrażenie ((2)(2)) ktoacutere jest nieprawidłowe Kompilator zgłosi błąd ale pro-gramista widzi tylko w kodzie użycie makra a nie prawdziwą przyczynę błędu Widać tu żebezpieczniejsze jest użycie funkcji ktoacutere dają możliwość wyspecyfikowania typoacutew argumen-toacutew

Nawet jeżeli program się skompiluje to makro może dawać nieoczekiwany wynik Jesttak w przypadku poniższego kodu

int x = 1int y = KWADRAT(++x)

Dzieje się tak dlatego że makra rozwijane są przez preprocesor i kompilator widzi kod

int x = 1int y = ((++x)(++x))

Roacutewnież poniższe makra są błędne pomimo że opisany problem w nich nie występuje

define SUMA(a b) a + bdefine ILOCZYN(a b) a b

Dają one nieoczekiwane wyniki dla wywołań

SUMA(2 2) 2 6 zamiast 8 ILOCZYN(2 + 2 2 + 2) 8 zamiast 16

Z tego powodu istotne jest użycie nawiasoacutew

define SUMA(a b) ((a) + (b))define ILOCZYN(a b) ((a) (b))

1229 oraz

Dość ciekawe możliwości ma w makrach znak rdquo Zamienia on stojący za nim identyfikatorna napis

include ltstdiohgtdefine wypisz(x) printf(s=in x x)

int main()

int i=1char a=5wypisz(i)wypisz(a)return 0

Program wypisze

i=1a=5

123 PREDEFINIOWANE MAKRA 95

Czyli wypisz(a) jest rozwijane w printf(s=in a a)Natomiast znaki rdquo łączą dwie nazwy w jedną Przykład

include ltstdiohgtdefine abc(x) int zmienna x

int main()

abc(nasza) dzięki temu zadeklarujemy zmienną o nazwie zmiennanasza zmiennanasza = 2return 0

Więcej o dobrych zwyczajach w tworzeniu makr można się dowiedzieć w rozdziale Po-wszechne praktyki

123 Predefiniowane makraW języku wprowadzono roacutewnież serię predefiniowanych makr ktoacutere mają ułatwić życie pro-gramiście Oto one

DATE mdash data w momencie kompilacji

TIME mdash godzina w momencie kompilacji

FILE mdash łańcuch ktoacutery zawiera nazwę pliku ktoacutery aktualnie jest kompilowany przezkompilator

LINE mdash definiuje numer linijki

STDC mdash w kompilatorach zgodnych ze standardem ANSI lub nowszym makro toprzyjmuje wartość

STDC VERSION mdash zależnie od poziomu zgodności kompilatora makro przyjmuje roacuteżnewartości

ndash jeżeli kompilator jest zgodny z ANSI (rok ) makro nie jest zdefiniowane

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199409L

ndash jeżeli kompilator jest zgodny ze standardem z makro ma wartość 199901L

Warto roacutewnież wspomnieć o identyfikatorze func zdefiniowanym w standardzie Cktoacuterego wartość to nazwa funkcji

Sproacutebujmy użyć tych makr w praktyce

include ltstdiohgt

if __STDC_VERSION__ gt= 199901L Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go define BUG(message) fprintf(stderr sd s (w funkcji s)n

__FILE__ __LINE__ message __func__)else Jezeli __func__ nie ma to go nie używamy

96 ROZDZIAŁ 12 PREPROCESOR

define BUG(message) fprintf(stderr sd sn __FILE__ __LINE__ message)

endif

int main(void) printf(Program ABC data kompilacji s sn __DATE__ __TIME__)

BUG(Przykladowy komunikat bledu)return 0

Efekt działania programu gdy kompilowany jest kompilatorem C

Program ABC data kompilacji Sep 1 2008 191213testc17 Przykladowy komunikat bledu (w funkcji main)

Gdy kompilowany jest kompilatorem ANSI C

Program ABC data kompilacji Sep 1 2008 191316testc17 Przykladowy komunikat bledu

Rozdział 13

Biblioteka standardowa

131 Czym jest biblioteka

Bibliotekę w języku C stanowi zbioacuter skompilowanych wcześniej funkcji ktoacutery można łączyćz programem Biblioteki tworzy się aby udostępnić zbioacuter pewnych ldquowyspecjalizowanychrdquofunkcji do dyspozycji innych programoacutew Tworzenie bibliotek jest o tyle istotne że takiepodejście znacznie ułatwia tworzenie nowych programoacutew Łatwiej jest utworzyć program woparciu o istniejące biblioteki niż pisać programwraz zewszystkimi potrzebnymi funkcjami1

132 Po co nam biblioteka standardowa

W ktoacuterymś z początkowych rozdziałoacutew tego podręcznika napisane jest że czysty język C niemoże zbyt wiele Tak naprawdę to język C sam w sobie praktycznie nie ma mechanizmoacutewdo obsługi np wejścia-wyjścia Dlatego też większość systemoacutew operacyjnych posiada tzwbibliotekę standardową zwaną też biblioteką języka C To właśnie w niej zawarte są pod-stawowe funkcjonalności dzięki ktoacuterym twoacutej program może np napisać coś na ekranie

1321 Jak skonstruowana jest biblioteka standardowa

Zapytacie zapewne jak biblioteka standardowa realizuje te funkcje skoro sam język C tegonie potrafi Odpowiedź jest prosta mdash biblioteka standardowa nie jest napisana w samym ję-zyku C Ponieważ C jest językiem tłumaczonym do kodu maszynowego to w praktyce niema żadnych przeszkoacuted żeby np połączyć go z językiem niskiego poziomu jakim jest npasembler Dlatego biblioteka C z jednej strony udostępnia gotowe funkcje w języku C a zdrugiej za pomocą niskopoziomowych mechanizmoacutew2 komunikuje się z systemem operacyj-nym ktoacutery wykonuje odpowiednie czynności

133 Gdzie są funkcje z biblioteki standardowej

Pisząc program w języku C używamy roacuteżnego rodzaju funkcji takich jak np printf Niejesteśmy jednak ich autorami mało tego nie widzimy nawet deklaracji tych funkcji w naszymprogramie Pamiętacie program ldquoHello worldrdquo Zaczynał on się od takiej oto linijki

1Początkujący programista zapewne nie byłby w stanie napisać nawet funkcji printf2Takich jak np wywoływanie przerwań programowych

97

98 ROZDZIAŁ 13 BIBLIOTEKA STANDARDOWA

include ltstdiohgt

linijka ta oznacza ldquow tym miejscu wstaw zawartość pliku stdiohrdquo Nawiasy ldquoltrdquo i ldquogtrdquooznaczają że plik stdioh znajduje się w standardowym katalogu z plikami nagłoacutewkowymiWszystkie pliki z rozszerzeniem h są właśnie plikami nagłoacutewkowymi Wroacutećmy teraz do te-matu biblioteki standardowej Każdy system operacyjny ma za zadanie wykonywać pewnefunkcje na rzecz programoacutew Wszystkie te funkcje zawarte są właśnie w bibliotece standar-dowej W systemach z rodziny UNIX nazywa się ją LibC (biblioteka języka C) To tamwłaśnieznajduje się funkcja printf scanf puts i inne

Oproacutecz podstawowych funkcji wejścia-wyjścia biblioteka standardowa udostępnia teżmożliwość wykonywania funkcji matematycznych komunikacji przez sieć oraz wykonywa-nia wielu innych rzeczy

1331 Jeśli biblioteka nie jest potrzebna

Czasami korzystanie z funkcji bibliotecznych oraz standardowych plikoacutew nagłoacutewkowych jestniepożądane np wtedy gdy programista pisze swoacutej własny system operacyjny oraz biblio-tekę do niego Aby wyłączyć używanie biblioteki C w opcjach kompilatora GCC możemydodać następujące argumenty

-nostdinc -fno-builtin

134 Opis funkcji biblioteki standardowejPodręcznik C na Wikibooks zawiera opis dużej części biblioteki standardowej C

Indeks alfabetyczny

Indeks tematyczny

W systemach uniksowych możesz uzyskać pomoc dzięki narzędziu man przykładowopisząc

man printf

135 UwagiProgramy w języku C++ mogą dokładnie w ten sam sposoacuteb korzystać z biblioteki standar-dowej ale zalecane jest by robić to raczej w trochę odmienny sposoacuteb właściwy dla C++Szczegoacuteły w podręczniku C++

Rozdział 14

Czytanie i pisanie do plikoacutew

141 Pojęcie plikuNa początku dobrze by było abyś dowiedział się czym jest plik Odpowiedni artykuł do-stępny jest w Wikipedii Najprościej moacutewiąc plik to pewne dane zapisane na dysku

142 Identyfikacja plikuKażdy z nas korzystając na co dzień z komputera przyzwyczaił się do tego że plik ma okre-śloną nazwę Jednak w pisaniu programu posługiwanie się całą nazwą niosło by ze sobą conajmniej dwa problemy

pamięciożerność mdash przechowywanie całego (czasami nawet -bajtowego łańcucha)zajmuje niepotrzebnie pamięć

ryzyko błędoacutew (owe błędy szerzej omoacutewione zostały w rozdziale Napisy)

Aby uprościć korzystanie z plikoacutew programiści wpadli na pomysł aby identyfikatorempliku stała się liczba Dzięki temu kod programu stał się czytelniejszy oraz wyeliminowanokonieczność ciągłego korzystania z łańcuchoacutew Jednak sam plik nadal jest identyfikowany poswojej nazwie Aby ldquoprzetworzyćrdquo nazwę pliku na odpowiednią liczbę korzystamy z funkcjiopen lub fopen Roacuteżnica wyjaśniona jest poniżej

143 Podstawowa obsługa plikoacutewIstnieją dwie metody obsługi czytania i pisania do plikoacutew

wysokopoziomowa

niskopoziomowa

Nazwy funkcji z pierwszej grupy zaczynają się od litery ldquordquo (np fopen() fread() fclose())a identyfikatorem pliku jest wskaźnik na strukturę typu FILE Owa struktura to pewna grupazmiennych ktoacutera przechowuje dane o danym pliku mdash jak na przykład aktualną pozycję wnim Szczegoacutełami nie musisz się przejmować funkcje biblioteki standardowej same zajmująsię wykorzystaniem struktury FILE programista może więc zapomnieć czym tak naprawdęjest struktura FILE i traktować taką zmienną jako ldquouchwytrdquo identyfikator pliku

99

100 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

Druga grupa to funkcje typu read() open() write() i close()Podstawowym identyfikatorem pliku jest liczba całkowita ktoacutera jednoznacznie identy-

fikuje dany plik w systemie operacyjnym Liczba ta w systemach typu jest nazywanadeskryptorem pliku

Należy pamiętać że nie wolno nam używać funkcji z obu tych grup jednocześnie w sto-sunku do jednego otwartego pliku tzn nie można najpierw otworzyć pliku za pomocą fo-pen() a następnie odczytywać danych z tego samego pliku za pomocą read()

Czym roacuteżnią się oba podejścia do obsługi plikoacutew Otoacuteż metoda wysokopoziomowa maswoacutej własny bufor w ktoacuterym znajdują się dane po odczytaniu z dysku a przed wysłaniemich do programu użytkownika W przypadku funkcji niskopoziomowych dane kopiowane sąbezpośrednio z pliku do pamięci programu W praktyce używanie funkcji wysokopoziomo-wych jest prostsze a przy czytaniu danych małymi porcjami roacutewnież często szybsze i właśnieten model zostanie tutaj zaprezentowany

1431 Dane znakowe

Skupimy się teraz na najprostszym zmożliwych zagadnień mdash zapisie i odczycie pojedynczychznakoacutew oraz całych łańcuchoacutew

Napiszmy zatem nasz pierwszy program ktoacutery stworzy plik ldquotesttxtrdquo i umieści w nimtekst ldquoHello worldrdquo

include ltstdiohgtinclude ltstdlibhgt

int main ()

FILE fp używamy metody wysokopoziomowej musimy mieć zatem identyfikator pliku uwaga na gwiazdkę

char tekst[] = Hello worldif ((fp=fopen(testtxt w))==NULL)

printf (Nie mogę otworzyć pliku testtxt do zapisun)exit(1)

fprintf (fp s tekst) zapisz nasz łańcuch w pliku fclose (fp) zamknij plik return 0

Teraz omoacutewimy najważniejsze elementy programu Jak już było wspomniane wyżej doidentyfikacji pliku używa się wskaźnika na strukturę FILE (czyli FILE ) Funkcja fopenzwraca oacutew wskaźnik w przypadku poprawnego otwarcia pliku bądź też NULL gdy plik niemoże zostać otwarty Pierwszy argument funkcji to nazwa pliku natomiast drugi to trybdostępu mdash w oznacza ldquowriterdquo (pisanie) zwroacutecony ldquouchwytrdquo do pliku będzie moacutegł być wyko-rzystany jedynie w funkcjach zapisujących dane I odwrotnie gdy otworzymy plik podająctryb r (ldquoreadrdquo czytanie) będzie można z niego jedynie czytać dane Funkcja fopen zostaładokładniej opisana w odpowiedniej części rozdziału o bibliotece standardowej

Po zakończeniu korzystania z pliku należy plik zamknąć Robi się to za pomocą funk-cji fclose Jeśli zapomnimy o zamknięciu pliku wszystkie dokonane w nim zmiany zostanąutracone

143 PODSTAWOWA OBSŁUGA PLIKOacuteW 101

1432 Pliki a strumienie

Można zauważyć że do zapisu do pliku używamy funkcji fprintf ktoacutera wygląda bardzopodobnie do printf mdash jedyną roacuteżnicą jest to że w fprintf musimy jako pierwszy argu-ment podać identyfikator pliku Nie jest to przypadek mdash obie funkcje tak naprawdę robiątak samo Używana do wczytywania danych z klawiatury funkcja scanf też ma swoacutej od-powiednik wśroacuted funkcji operujących na plikach mdash jak nietrudno zgadnąć nosi ona nazwęfscanf

W rzeczywistości język C traktuje tak samo klawiaturę i plik mdash są to źroacutedła danych po-dobnie jak ekran i plik do ktoacuterych można dane kierować Jest to myślenie typowe dla sys-temoacutew typu UNIX jednak dla użytkownikoacutew przyzwyczajonych do systemu Windows albojęzykoacutew typu Pascal może być to co najmniej dziwne Nie da się ukryć że między klawia-turą i plikiem na dysku zachodzą podstawowe roacuteżnice i dostęp do nich odbywa się inaczejmdash jednak funkcje języka C pozwalają nam o tym zapomnieć i same zajmują się szczegoacutełamitechnicznymi Z punktu widzenia programisty urządzenia te sprowadzają się do nadanegoim identyfikatora Uogoacutelnione pliki nazywa się w C strumieniami

Każdy program w momencie uruchomienia ldquootrzymujerdquo od razu trzy otwarte strumienie

stdin (wejście)

stdout (wyjście)

stderr (wyjście błędoacutew)

(aby z nich korzystać należy dołączyć plik nagłoacutewkowy stdioh)Pierwszy z tych plikoacutew umożliwia odczytywanie danych wpisywanych przez użytkow-

nika natomiast pozostałe dwa służą do wyprowadzania informacji dla użytkownika oraz po-wiadamiania o błędach

Warto tutaj zauważyć że konstrukcja

fprintf (stdout Hej ja działam)

jest roacutewnoważna konstrukcji

printf (Hej ja działam)

Podobnie jest z funkcją scanf()

fscanf (stdin d ampzmienna)

działa tak samo jak

scanf(d ampzmienna)

1433 Obsługa błędoacutew

Jeśli nastąpił błąd możemy się dowiedzieć o jego przyczynie na podstawie zmiennej errnozadeklarowanej w pliku nagłoacutewkowym errnoh Możliwe jest też wydrukowanie komunikatuo błedzie za pomocą funkcji perror Na przykład używając

fp = fopen (tego pliku nie ma r)if( fp == NULL )

perror(błąd otwarcia pliku)exit(-10)

102 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

dostaniemy komunikat

błąd otwarcia pliku No such file or directory

1434 Zaawansowane operacje

Pora na kolejny tym razem bardziej złożony przykład Oto kroacutetki program ktoacutery swojewejście zapisuje do pliku o nazwie podanej w linii poleceń

include ltstdiohgtinclude ltstdlibhgt program udający bardzo prymitywną wersję programu tee(1)

int main (int argc char argv[])

FILE fpint cif (argc lt 2)

fprintf (stderr Uzycie s nazwa_plikun argv[0])exit (-1)

fp = fopen (argv[1] w)if (fp)

fprintf (stderr Nie moge otworzyc pliku sn argv[1])exit (-1)

printf(Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczycn)while ( (c = fgetc(stdin)) = EOF)

fputc (c stdout)fputc (c fp)

fclose(fp)return 0

Tym razem skorzystaliśmy już z dużo większego repertuaru funkcji Między innymimożna zauważyć tutaj funkcję fputc() ktoacutera umieszcza pojedynczy znak w pliku Ponadto wwyżej zaprezentowanym programie została użyta stała EOF ktoacutera reprezentuje koniec pliku(ang End Of File) Powyższy program otwiera plik ktoacuterego nazwa przekazywana jest jakopierwszy argument programu a następnie kopiuje dane z wejścia programu (stdin) na wyj-ście (stdout) oraz do utworzonego pliku (identyfikowanego za pomocą fp) Program robi todotąd aż naciśniemy kombinację klawiszy Ctrl+D(w systemach Unixowych) lub Ctrl+Z(wWindows) ktoacutera wyśle do programu informację że skończyliśmy wpisywać dane Programwyjdzie wtedy z pętli i zamknie utworzony plik

144 Rozmiar plikuDzięki standardowym funkcjom języka C możemy min określić długość pliku Do tego celusłużą funkcje fsetpos fgetpos oraz fseek Ponieważ przy każdym odczyciezapisie zdo plikuwskaźnik niejako ldquoprzesuwardquo się o liczbę przeczytanychzapisanych bajtoacutew Możemy jednak

145 PRZYKŁAD mdash PLIKI GRAFICZNY 103

ustawić wskaźnik w dowolnie wybranym miejscu Do tego właśnie służą wyżej wymienionefunkcje Aby odczytać rozmiar pliku powinniśmy ustawić nasz wskaźnik na koniec plikupo czym odczytać ile bajtoacutew od początku pliku się znajdujemy Wiem brzmi to strasznieale działa wyjątkowo prosto i skutecznie Użyjemy do tego tylko dwoacutech funkcji fseek orazfgetpos Pierwsza służy do ustawiania wskaźnika na odpowiedniej pozycji w pliku a drugado odczytywania na ktoacuterym bajcie pliku znajduje się wskaźnik Kod ktoacutery określa rozmiarpliku znajduje się tutaj

include ltstdiohgt

int main (int argc char argv)

FILE fp = NULLfpos_t dlugoscif (argc = 2)

printf (Użycie s ltnazwa plikugtn argv[0])return 1

if ((fp=fopen(argv[1] rb))==NULL) printf (Błąd otwarcia pliku sn argv[1])return 1

fseek (fp 0 SEEK_END) ustawiamy wskaźnik na koniec pliku fgetpos (fp ampdlugosc)printf (Rozmiar pliku dn dlugosc)fclose (fp)return 0

Znajomość rozmiaru pliku przydaje się w wielu roacuteżnych sytuacjach więc dobrze prze-analizuj przykład

145 Przykład mdash pliki graficznyNajprostszym przykładem rastrowego pliku graficznego jest plik Poniższy program po-kazuje jak utworzyć plik w katalogu roboczym programu Do zapisu

nagłoacutewka pliku używana jest funkcja fprintf

tablicy do pliku używana jest funkcja fwrite

include ltstdiohgtint main()

const int dimx = 800const int dimy = 800int i jFILE fp = fopen(firstppm wb) b - tryb binarny fprintf(fp P6nd dn255n dimx dimy)for(j=0 jltdimy ++j)

for(i=0 iltdimx ++i)static unsigned char color[3]

104 ROZDZIAŁ 14 CZYTANIE I PISANIE DO PLIKOacuteW

color[0]=i 255 red color[1]=j 255 green color[2]=(ij) 255 blue fwrite(color13fp)

fclose(fp)return 0

W powyższym przykładzie dostęp do danych jest sekwencyjny Jeśli chcemy mieć swo-bodny dostęp do danych to

korzystać z funkcji fsetpos fgetpos oraz fseek

utworzyć tablicę (dla dużych plikoacutew dynamiczną) zapisać do niej wszystkie dane anastępnie zapisać całą tablicę do pliku Ten sposoacuteb jest prostszy i szybszy Należyzwroacutecić uwagę że do obliczania rozmiaru całej tablicy nie możemy użyć funkcji sizeof

(a) Przykład użycia tej techniki sekwencyjnydostęp do danych (kod źroacutedłowy)

(b) Przykład użycia tej techniki swobodny do-stęp do danych (kod źroacutedłowy)

146 Co z katalogamiFaktycznie zapomnieliśmy o nich Jednak wynika to z tego że specyfikacja ANSI C nieuwzględnia obsługi katalogoacutew

Rozdział 15

Ćwiczenia dla początkujący

151 ĆwiczeniaWszystkie zamieszczone tutaj ćwiczenia mają na celu pomoacutec Ci w sprawdzeniu Twojej wie-dzy oraz umożliwieniu Tobie wykorzystania nowo nabytych wiadomości w praktyce Pa-miętaj także że ten podręcznik ma służyć także innym więc nie zamieszczaj tutaj Twoichrozwiązań Zachowaj je dla siebie

1511 Ćwiczenie 1

Napisz program ktoacutery wyświetli na ekranie twoje imię i nazwisko

1512 Ćwiczenie 2

Napisz program ktoacutery poprosi o podanie dwoacutech liczb rzeczywistych i wyświetli wynik mno-żenia obu zmiennych

1513 Ćwiczenie 3

Napisz program ktoacutery pobierze jako argumenty z linii komend nazwy dwoacutech plikoacutew i prze-kopiuje zawartość pierwszego pliku do drugiego (tworząc lub zamazując drugi)

1514 Ćwiczenie 4

Napisz program ktoacutery utworzy nowy plik (o dowolnie wybranej przez Ciebie nazwie) i za-pisze tam

Twoje imię

wiek

miasto w ktoacuterym mieszkasz

Przykładowy plik powinien wyglądać tak

Stanisław30Krakoacutew

105

106 ROZDZIAŁ 15 ĆWICZENIA DLA POCZĄTKUJĄCYCH

1515 Ćwiczenie 5

Napisz program generujący tabliczkę mnożenia x i wyświetlający ją na ekranie

1516 Ćwiczenie 6 mdash dla ętny

Napisz program znajdujący pierwiastki troacutejmianu kwadratowego ax2+bx+c= dla zadanychparametroacutew a b c

Rozdział 16

Tablice

W rozdziale Zmienne w C dowiedziałeś się jak przechowywać pojedyncze liczby oraz znakiCzasami zdarza się jednak że potrzebujemy przechować kilka kilkanaście albo iwięcej zmien-nych jednego typu Nie tworzymy wtedy np dwudziestu osobnych zmiennych W takichprzypadkach z pomocą przychodzi nam tablica

Rysunek 161 tablica 10-elementowa

Tablica to ciąg zmiennych jednego typu Ciąg taki posiada jedną nazwę a do jego po-szczegoacutelnych elementoacutew odnosimy się przez numer (indeks)

161 Wstęp

1611 Sposoby deklaracji tablic

Tablicę deklaruje się w następujący sposoacuteb

typ nazwa_tablicy[rozmiar]

gdzie rozmiar oznacza ile zmiennych danego typu możemy zmieścić w tablicy Zatemaby np zadeklarować tablicę mieszczącą liczb całkowitych możemy napisać tak

int tablica[20]

Podobnie jak przy deklaracji zmiennych także tablicy możemy nadać wartości począt-kowe przy jej deklaracji Odbywa się to przez umieszczenie wartości kolejnych elementoacutewoddzielonych przecinkami wewnątrz nawiasoacutew klamrowych

int tablica[3] = 123

Może to się wydać dziwne ale po ostatnim elemencie tablicy może występować przeci-nek Ponadto jeżeli poda się tylko część wartości w pozostałe wpisywane są zera

107

108 ROZDZIAŁ 16 TABLICE

int tablica[20] = 1

Niekoniecznie trzeba podawać rozmiar tablicy np

int tablica[] = 1 2 3 4 5

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku mdash elemen-toacutew)

Rozpatrzmy następujący kod

include ltstdiohgtdefine ROZMIAR 3int main()

int tab[ROZMIAR] = 368int iprintf (Druk tablicy tabn)

for (i=0 iltROZMIAR ++i) printf (Element numer d = dn i tab[i])

return 0

Wynik

Druk tablicy tabElement numer 0 = 3Element numer 1 = 6Element numer 2 = 8

Jak widać wszystko się zgadza W powyżej zamieszczonym przykładzie użyliśmy stałejdo podania rozmiaru tablicy Jest to o tyle pożądany zwyczaj że w razie konieczności zmianyrozmiaru tablicy zmieniana jest tylko jedna linijka kodu przy stałej a nie kilkadziesiąt innychlinijek rozsianych po kodzie całego programu

W pierwotnym standardzie języka C rozmiar tablicy nie moacutegł być określany przezzmienną lub nawet stałą zadeklarowaną przy użyciu słowa kluczowego const Dopiero wpoacuteźniejszej wersji standardu (tzw C) dopuszczono taką możliwość Dlatego do dekla-rowania rozmiaru tablic często używa się dyrektywy preprocesora define Powinni na tozwroacutecić uwagę zwłaszcza programiści C++ gdyż tam zawsze możliwe były oba sposoby

Innym sposobem jest użycie operatora sizeof do poznania wielkości tablicy Poniższy kodrobi to samo co przedstawiony

include ltstdiohgtint main()

int tab[3] = 368int i

162 ODCZYTZAPIS WARTOŚCI DO TABLICY 109

printf (Druk tablicy tabn)

for (i=0 ilt(sizeof tab sizeof tab) ++i) printf (Element numer d = dn i tab[i])

return 0

Należy pamiętać że działa on tylko dla tablic a nie wskaźnikoacutew (jak poacuteźniej się dowieszwskaźnik też można w pewnym stopniu traktować jak tablicę)

162 Odczytzapis wartości do tablicyTablicami posługujemy się tak samo jak zwykłymi zmiennymi Roacuteżnica polega jedynie napodaniu indeksu tablicy Określa on jednoznacznie z ktoacuterego elementu (wartości) chcemyskorzystać Indeksem jest liczba naturalna począwszy od zera To oznacza że pierwszy ele-ment tablicy ma indeks roacutewny drugi trzeci itd

Osoby ktoacutere wcześniej programowały w językach takich jak Pascal Basic czy Fortranmuszą przyzwyczaić się do tego że w języku C indeks numeruje się od Ponadto indeksempowinna być liczba - istnieje możliwość indeksowania za pomocą np pojedynczych znakoacutew(rsquoarsquo rsquobrsquo itp) jednak Cwewnętrznie konwertuje takie znaki na liczby im odpowiadające zatemtablica indeksowana znakami byłaby niepraktyczna i musiałaby mieć odpowiednio większyrozmiar

Sproacutebujmy przedstawić to na działającym przykładzie Przeanalizuj następujący kod

int tablica[5] = 0int i = 0tablica[2] = 3tablica[3] = 7for (i=0i=5++i)

printf (tablica[d]=dn i tablica[i])

Jak widać na początku deklarujemy -elementową tablicę ktoacuterą od razu zerujemy Na-stępnie pod trzeci i czwarty element podstawiamy liczby i Pętla ma za zadanie wypro-wadzić wynik naszych działań

163 Tablice znakoacutewTablice znakoacutew tj typu char oraz unsigned char posiadają dwie ogoacutelnie przyjęte nazwyzależnie od ich przeznaczenia

bufory mdash gdy wykorzystujemy je do przechowywania ogoacutelnie pojętych danych gdytraktujemy je jako po prostu ldquociągi bajtoacutewrdquo (typ char ma rozmiar bajta więc jestelastyczny do przechowywania np danych wczytanych z pliku przed ich przetworze-niem)

napisy mdash gdy zawarte w nich dane traktujemy jako ciągi liter jest im poświęconyosobny rozdział Napisy

110 ROZDZIAŁ 16 TABLICE

164 Tablice wielowymiarowe

Rysunek tablica dwuwymia-rowa (x)

Rozważmy teraz konieczność przechowania w pa-mięci komputera całej macierzy o wymiarach x Można by tego dokonać tworząc osobnych ta-blic jednowymiarowych reprezentujących poszcze-goacutelne wiersze macierzy Jednak język C dostarczanam dużo wygodniejszej metody ktoacutera w dodatkujest bardzo łatwa w użyciu Są to tablice wielowy-miarowe lub inaczej ldquotablice tablicrdquo Tablice wielo-wymiarowe definiujemy podając przy zmiennej kilkawymiaroacutew np

float macierz[10][10]

Tak samo wygląda dostęp do poszczegoacutelnych ele-mentoacutew tablicy

macierz[2][3] = 12

Jakwidać ten sposoacuteb jest dużowygodniejszy (i za-pewne dużo bardziej ldquonaturalnyrdquo) niż deklarowanie osobnych tablic jednowymiarowych Aby zaini-cjować tablicę wielowymiarową należy zastosowaćzagłębianie klamer np

float macierz[3][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz

Dodatkowo pierwszego wymiaru nie musimy określać (podobnie jak dla tablic jednowy-miarowych) i woacutewczas kompilator sam ustali odpowiednią wielkość np

float macierz[][4] = 16 45 24 56 pierwszy wiersz 57 43 36 43 drugi wiersz 88 75 43 86 trzeci wiersz 63 27 57 27 czwarty wiersz

Innym bardziej elastycznym sposobem deklarowania tablic wielowymiarowych jest uży-cie wskaźnikoacutew Opisane to zostało w następnym rozdziale

165 Ograniczenia tablicPomimo swej wygody tablice mają ograniczony z goacutery zdefiniowany rozmiar ktoacuterego niemożna zmienić w trakcie działania programu Dlatego też w niektoacuterych zastosowaniach ta-blice zostały wyparte przez dynamiczną alokację pamięci Opisane to zostało w następnymrozdziale

166 CIEKAWOSTKI 111

Przy używaniu tablic trzeba być szczegoacutelnie ostrożnym przy konstruowaniu pętli ponie-waż ani kompilator ani skompilowany program nie będą w stanie wychwycić przekroczeniaprzez indeks rozmiaru tablicy 1 Efektem będzie odczyt lub zapis pamięci znajdującej się pozatablicą

Wystarczy pomylić się o jednomiejsce (tzw błąd off by one) by spowodować że działanieprogramu zostanie nagle przerwane przez system operacyjny

int foo[100]int i

for (i=0 ilt=100 ++i) powinno być ilt100 foo[i] = 0

166 CiekawostkiW pierwszej edycji konkursu IOCCC zwyciężył program napisany w C ktoacutery wyglądał dośćnietypowo

short main[] = 277 04735 -4129 25 0 477 1019 0xbef 0 12800-113 21119 0x52d7 -1006 -7151 0 0x4bc 02000414880 10541 2056 04010 4548 3044 -6716 0x94407 6 5568 1 -30460 0 0x9 5570 512 -304190x7e82 0760 6 0 4 02400 15 0 4 1280 4 04 0 0 0 0x8 0 4 0 0 12 0 4 0 0 020 0 4 0 30 0 026 0 0x6176 120 25712p 072163 r 29303 29801 e

Co ciekawe mdash program ten bez przeszkoacuted wykonywał się na komputerach VAX- orazPDP- Cały program to po prostu tablica z zawartym wewnątrz kodem maszynowym Taknaprawdę jest to wykorzystanie pewnych właściwości programu ktoacutery ostatecznie produ-kuje kod maszynowy Linker (to o nim mowa) nie rozroacuteżnia na dobrą sprawę nazw funkcjiod nazw zmiennych więc bez problemu ustawił punkt wejścia programu na tablicę wartościw ktoacuterych zapisany był kod maszynowy Tak przygotowany program został bez problemuwykonany przez komputer

1W zasadzie kompilatory mają możliwość dodania takiego sprawdzania ale nie robi się tego gdyż znaczniespowolniłoby to działanie programu Takie postępowanie jest jednak pożądane w okresie testowania programu

112 ROZDZIAŁ 16 TABLICE

Rozdział 17

Wskaźniki

Zobacz w WikipediiZmienna wskaźnikowaZmienne w komputerze są przechowywane w pamięci To wie każdy programista a dobry

programista potrafi kontrolować zachowanie komputera w przydzielaniu i obsługi pamięcidla zmiennych W tym celu pomocne są wskaźniki

171 Co to jest wskaźnik

Dla ułatwienia przyjęto poniżej że bajt ma bitoacutew typ int składa się z dwoacutech bajtoacutew(bitoacutew) typ long składa się z czterech bajtoacutew ( bitoacutew) oraz liczby zapisane są w formaciebig endian (tzn bardziej znaczący bajt na początku) co niekoniecznie musi być prawdą naTwoim komputerze

Rysunek Wskaźnik awskazu-jący na zmienną b Zauważmy żeb przechowuje liczbę podczas gdya przechowuje adres b w pamięci()

Wskaźnik (ang pointer) to specjalny rodzaj zmiennejw ktoacuterej zapisany jest adres w pamięci komputera tznwskaźnik wskazuje miejsce gdzie zapisana jest jakaśinformacja Oczywiście nic nie stoi na przeszkodzie abywskazywaną daną był innywskaźnik do kolejnegomiej-sca w pamięci

Obrazowo możemy wyobrazić sobie pamięć kom-putera jako bibliotekę a zmienne jako książki Zamiastbrać książkę z poacutełki samemu (analogicznie do korzy-stania wprost ze zwykłych zmiennych) możemy podaćbibliotekarzowi wypisany rewers z numerem katalogo-wym książki a on znajdzie ją za nas Analogia ta niejest doskonała ale pozwalawyobrazić sobie niektoacutere ce-chy wskaźnikoacutew kilka rewersoacutew może dotyczyć tej sa-mej książki numer w rewersie możemy skreślić i użyćgo do zamoacutewienia innej książki jeśli wpiszemy niepra-widłowy numer katalogowy to możemy dostać nie tąksiążkę ktoacuterą chcemy albo też nie dostać nic

Warto też poznać w tym miejscu definicję adresupamięci Możemy powiedzieć że adres to pewna liczba całkowita jednoznacznie definiującapołożenie pewnego obiektu (czyli np znaku czy liczby) w pamięci komputera Dokładniejsządefinicję możesz znaleźć w Wikipedii

113

114 ROZDZIAŁ 17 WSKAŹNIKI

172 Operowanie na wskaźnikaBy stworzyć wskaźnik do zmiennej i moacutec się nim posługiwać należy przypisać mu odpo-wiednią wartość (adres obiektu na jaki ma wskazywać) Skąd mamy znać ten adres Wy-starczy zapytać nasz komputer jaki adres przydzielił zmiennej ktoacuterą np wcześniej gdzieśstworzyliśmy Robi się to za pomocą operatora amp (operatora pobrania adresu) Przeanalizujnastępujący kod1

include ltstdiohgt

int main (void)

int liczba = 80printf(Zmienna znajduje sie pod adresem p i przechowuje wartosc dn

(void)ampliczba liczba)return 0

Program ten wypisuje adres pamięci pod ktoacuterym znajduje się zmienna oraz wartość jakąkryje zmienna przechowywana pod owym adresem

Aby moacutec zapisać gdzieś taki adres należy zadeklarować zmienną wskaźnikową Robi sięto poprzez dodanie (gwiazdki) po typie na jaki zmienna ma wskazywać np

int wskaznik1char wskaznik2floatwskaznik3

Niektoacuterzy programiści mogą nieco błędnie interpretować wskaźnik do typu jako nowytyp i uważać że jeśli napiszą

int abc

to otrzymają trzy wskaźniki do liczby całkowitej Tymczasem wskaźnikiem będzie tylkozmienna a natomiast b i c będą po prostu liczbami Powodem jest to że rdquogwiazdkaodnosi siędo zmiennej a nie do typu W tym przypadku trzy wskaźniki otrzymamy pisząc

int abc

Aby uniknąć pomyłek lepiej jest pisać gwiazdkę tuż przy zmiennej

int abc

albo jeszcze lepiej nie mieszać deklaracji wskaźnikoacutew i zmiennych

int aint bc

Aby dobrać się dowartości wskazywanej przez wskaźnik należy użyć unarnego operatora (gwiazdka) zwanego operatorem wyłuskania

1Warto zwroacutecić uwagę na rzutowanie do typu wskaźnik na void Rzutowanie to jest wymagane przez funkcjęprintf gdyż ta oczekuje że argumentem dla formatu p będzie właśnie wskaźnik na void gdy tymczasem w naszymprzykładzie wyrażenie ampliczba jest typu wskaźnik na int

172 OPEROWANIE NA WSKAŹNIKACH 115

include ltstdiohgt

int main (void)

int liczba = 80int wskaznik = ampliczbaprintf(Wartosc zmiennej d jej adres pn liczba (void)ampliczba)printf(Adres zapisany we wskazniku p wskazywana wartosc dn

(void)wskaznik wskaznik)

wskaznik = 42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

liczba = 0x42printf(Wartosc zmiennej d wartosc wskazywana przez wskaznik pn

liczba wskaznik)

return 0

1721 O coodzi z tym typem na ktoacutery ma wskazywać Czemu to takieważne

Jest to ważne z kilku powodoacutewRoacuteżne typy zajmują w pamięci roacuteżną wielkość Przykładowo jeżeli w zmiennej typu

unsigned int zapiszemy liczbę to w pamięci będzie istnieć jako

+--------+--------+|komoacuterka1|komoacuterka2|+--------+--------+|11111111|11111010| = (unsigned int) 65530+--------+--------+

Wskaźnik do takiej zmiennej (jak i do dowolnej innej) będzie wskazywać na pierwsząkomoacuterkę w ktoacuterej ta zmienna ma swoją wartość

Jeżeli teraz stworzymy drugi wskaźnik do tego adresu tym razem typu unsigned arto wskaźnik przejmie ten adres prawidłowo2 lecz gdy sproacutebujemy odczytać wartość na jakąwskazuje ten wskaźnik to zostanie odczytana tylko pierwsza komoacuterka i wynik będzie roacutewny

+--------+|komoacuterka1|+--------+|11111111| = (unsigned char) 255+--------+

2Tak naprawdę nie zawsze można przypisywać wartości jednych wskaźnikoacutew do innych Standard C gwaran-tuje jedynie że można przypisać wskaźnikowi typu void wartość dowolnego wskaźnika a następnie przypisać tąwartość do wskaźnika pierwotnego typu oraz że dowolny wskaźnik można przypisać do wskaźnika typu char

116 ROZDZIAŁ 17 WSKAŹNIKI

Gdybyśmy natomiast stworzyli inny wskaźnik do tego adresu tym razem typu unsignedlong to przy proacutebie odczytu odczytane zostaną dwa bajty z wartością zapisaną w zmiennejunsigned int oraz dodatkowe dwa bajty z niewiadomą zawartością i woacutewczas wynik będzieroacutewny + przypadkowa wartość

+--------+--------+--------+--------+|komoacuterka1|komoacuterka2|komoacuterka3|komoacuterka4|+--------+--------+--------+--------+|11111111|11111010|||+--------+--------+--------+--------+

Ponadto zapis czy odczyt poza przydzielonym obszarem pamięci może prowadzić donieprzyjemnych skutkoacutew takich jak zmiana wartości innych zmiennych czy wręcz natych-miastowe przerwanie programu Jako przykład można podać ten (błędny) program3

include ltstdiohgt

int main(void)

unsigned char tab[10] = 100 101 102 103 104 105 106 107 108 109 unsigned short ptr = (unsigned short)amptab[2]unsigned i

ptr = 0xfffffor (i = 0 i lt 10 ++i)

printf(dn tab[i])tab[i] = tab[i] - 100

printf(poza tablica dn tab[10])tab[10] = -1return 0

Nie można roacutewnież zapominać że na niektoacuterych architekturach dane wielobajtowe mu-szą być odpowiednio wyroacutewnane w pamięci Np zmienna dwubajtowa może się znajdowaćjedynie pod parzystymi adresami Woacutewczas gdybyśmy chcieli adres zmiennej jednobajto-wej przypisać wskaźnikowi na zmienną dwubajtową mogłoby dojść do nieprzewidzianychbłędoacutew wynikających z proacuteby odczytu niewyroacutewnanej danej

Zaskakującemoże się okazać że roacuteżnewskaźniki mogąmieć roacuteżny rozmiar Np wskaźnikna ar może być większy od wskaźnika na int ale roacutewnież na odwroacutet Co więcej wskaźnikiroacuteżnych typoacutewmogą się roacuteżnić reprezentacją adresoacutew Dla przykładuwskaźnik naar możeprzechowywać adres do bajtu natomiast wskaźnik na int ten adres podzielony przez

Podsumowując roacuteżne wskaźniki to roacuteżne typy i nie należy beztrosko rzutować wyrażeńpomiędzy roacuteżnymi typami wskaźnikowymi bo grozi to nieprzewidywalnymi błędami

1722 Do czego służy typ void

Czasami zdarza się że nie wiemy na jaki typwskazuje danywskaźnik W takich przypadkachstosujemy typ void Sam void nie znaczy nic natomiast void oznacza ldquowskaźnik na obiekt

3Może się okazać że błąd nie będzie widoczny na Twoim komputerze

173 ARYTMETYKA WSKAŹNIKOacuteW 117

w pamięci niewiadomego typurdquo Taki wskaźnik możemy potem odnieść do konkretnego typudanych (w języku C++ wymagana jest do tego operacja rzutowania) Na przykład funkcjamalloc zwraca właśnie wskaźnik za pomocą void

173 Arytmetyka wskaźnikoacutew

W języku C do wskaźnikoacutew można dodawać lub odejmować liczby całkowite Istotne jestjednak że dodanie do wskaźnika liczby nie spowoduje przesunięcia się w pamięci kom-putera o dwa bajty Tak naprawdę przesuniemy się o rozmiar zmiennej Jest to bardzoważna informacja Początkujący programiści popełniają często dużo błędoacutew związanych znieprawidłową arytmetyką wskaźnikoacutew

Zobaczmy na przykład

int ptrint a[] = 1 2 3 5 7ptr = ampa[0]

Rysunek 172 Wskaźnik wskazuje na pierwszą komoacuterkę pamięci

Otrzymujemy następującą sytuacjęGdy wykonamy

ptr += 2

Rysunek 173 Przesunięcie wskaźnika na kolejne komoacuterki

wskaźnik ustawi się na trzecim elemencie tablicyWskaźniki można roacutewnież od siebie odejmować czego wynikiem jest odległość dwoacutech

wskazywanych wartości Odległość zwracana jest jako liczba obiektoacutew danego typu a nieliczba bajtoacutew Np

int a[] = 1 2 3 5 7int ptr = ampa[2]int diff = ptr - a diff ma wartość 2 (a nie 2sizeof(int))

118 ROZDZIAŁ 17 WSKAŹNIKI

Wynikiem może być oczywiście liczba ujemna Operacja jest przydatna do obliczaniawielkości tablicy (długości łańcucha znakoacutew) jeżeli mamy wskaźnik na jej pierwszy i ostatnielement

Operacje arytmetyczne na wskaźnikach mają pewne ograniczenia Przede wszystkim niemożna (tzn standard tego nie definiuje) skonstruować wskaźnika wskazującego gdzieś pozazadeklarowaną tablicę chyba że jest to obiekt zaraz za ostatnim (one past last) np

int a[] = 1 2 3 5 7int ptrptr = a + 10 niezdefiniowane ptr = a - 10 niezdefiniowane ptr = a + 5 zdefiniowane (element za ostatnim) ptr = 10 to już nie

Nie można4 roacutewnież odejmować od siebie wskaźnikoacutew wskazujących na obiekty znajdu-jące się w roacuteżnych tablicach np

int a[] = 1 2 3 b[] = 5 7int ptr1 = a ptr2 = bint diff = a - b niezdefiniowane

174 Tablice a wskaźniki

Trzeba wiedzieć że tablice to też rodzaj zmiennej wskaźnikowej Taki wskaźnik wskazuje namiejsce w pamięci gdzie przechowywany jest jej pierwszy element Następne elementy znaj-dują się bezpośrednio w następnych komoacuterkach pamięci w odstępie zgodnym z wielkościąodpowiedniego typu zmiennej

Na przykład tablica

int tab[] = 100200300

występuje w pamięci w sześciu komoacuterkach5

+--------+--------+--------+--------+--------+--------+|wartosc1| |wartosc2| |wartosc3| |+--------+--------+--------+--------+--------+--------+|00000000|01100100|00000000|11001000|00000001|00101100|+--------+--------+--------+--------+--------+--------+

Stąd do trzeciej wartości można się dostać tak (komoacuterki w tablicy numeruje się od zera)

zmienna = tab[2]

albo wykorzystując metodę wskaźnikową

zmienna = (tab + 2)

4To znaczy standard nie definiuje co się wtedy stanie aczkolwiek na większości architektur odejmowanie do-wolnych dwoacutech wskaźnikoacutew ma zdefiniowane zachowanie Pisząc przenośne programy nie można jednak na tympolegać zwłaszcza że odejmowanie wskaźnikoacutew wskazujących na elementy roacuteżnych tablic zazwyczaj nie ma sensu

5Ponownie przyjmując że bajt ma 8 bitoacutew int dwa bajty i liczby zapisywane są w formacie lile endian

175 GDY ARGUMENT JEST WSKAŹNIKIEM 119

Z definicji obie te metody są roacutewnoważneZ definicji (z wyjątkiem użycia operatora sizeo) wartością zmiennej lub wyrażenia typu tablico-

wego jest wskaźnik na jej pierwszy element (tab == amptab[0])Co więcej można poacutejść w drugą stronę i potraktować wskaźnik jak tablicę

int wskaznikwskaznik = amptab[1] lub wskaznik = tab + 1 zmienna = wskaznik[1] przypisze 300

Jako ciekawostkę podamy iż w języku C można odnosić się do elementoacutew tablicy jeszcze w innysposoacuteb

printf (dn 1[tab])

Skąd ta dziwna notacja Uzasadnienie jest proste

tab[1] = (tab + 1) = (1 + tab) = 1[tab]

Podobną składnię stosuje min asembler GNU

175 Gdy argument jest wskaźnikiem Czasami zdarza się że argumentem (lub argumentami) funkcji są wskaźniki W przypadku ldquonormal-nychrdquo zmiennych nasza funkcja działa tylko na lokalnych kopiach tychże argumentoacutew natomiast niezmienia zmiennych ktoacutere zostały podane jako argument Natomiast w przypadku wskaźnika każdaoperacja na wartości wskazywanej powoduje zmianę wartości zmiennej zewnętrznej Sproacutebujmy roz-patrzeć poniższy przykład

include ltstdiohgt

void func (int zmienna)

zmienna = 5

int main ()

int z=3printf (z=dn z) wypisze 3 func(ampz)printf (z=dn z) wypisze 5

Widzimy że funkcje w języku C nie tylko potrafią zwracać określoną wartość lecz także zmieniaćdane podane im jako argumenty Ten sposoacuteb przekazywania argumentoacutew do funkcji jest nazywanyprzekazywaniem przez wskaźnik (w przeciwieństwie do normalnego przekazywania przez wartość)

Zwroacutećmy uwagę na wywołanie func(ampz) Należy pamiętać by do funkcji przekazać adres zmien-nej a nie samą zmienną Jeśli byśmy napisali func(z) to funkcja starałaby się zmienić komoacuterkę pamięcio numerze Kompilator powinien ostrzec w takim przypadku o konwersji z typu int do wskaźnikaale często kompiluje taki program pozostając na ostrzeżeniu

Nie gra roli czy przy deklaracji funkcji jako argument funkcji podamy wskaźnik czy tablicę (z po-danym rozmiarem lub nie) np poniższe deklaracje są identyczne

120 ROZDZIAŁ 17 WSKAŹNIKI

void func(int ptr[])void func(int ptr)

Można przyjąć konwencję że deklaracja określa czy funkcji przekazujemy wskaźnik do pojedyn-czego argumentu czy do sekwencji ale roacutewnie dobrze można za każdym razem stosować gwiazdkę

176 Pułapki wskaźnikoacutewWażne jest aby przy posługiwaniu się wskaźnikami nigdy nie proacutebować odwoływać się do komoacuterkiwskazywanej przez wskaźnik o wartości lub niezainicjowany wskaźnik Przykładem nieprawi-dłowego kodu może być np

int wskprintf (zawartosc komorki dn (wsk)) Błąd wsk = 0 0 w kontekście wskaźnikoacutew oznacza wskaźnik NULL printf (zawartosc komorki dn (wsk)) Błąd

Należy roacutewnież uważać aby nie odwoływać się do komoacuterek poza przydzieloną pamięcią np

int tab[] = 0 1 2 tab[3] = 3 Błąd

Pamiętaj też że możesz być rozczarowany używając operatora sizeof podając zmienną wskaźni-kową Uzyskana wielkość będzie wielkością wskaźnika a nie wielkością typu użytego podczas deklaro-wania naszego wskaźnika Wielkość ta będzie zawsze miała taki sam rozmiar dla każdego wskaźnikaw zależności od kompilatora a także docelowej platformy Zamiast tego używaj sizeof(wskaźnik)Przykład

char zmiennaint a = sizeof zmienna a wynosi np 4 tj sizeof(char) a = sizeof(char) robimy to samo co wyżej a = sizeof zmienna zmienna a ma teraz przypisany rozmiar

pojedynczego znaku tj 1 a = sizeof(char) robimy to samo co wyżej

177 Na co wskazuje Analizując kody źroacutedłowe programoacutew często można spotkać taki oto zapis

void wskaznik = NULL lub = 0

Wiesz już że nie możemy odwołać się pod komoacuterkę pamięci wskazywaną przez wskaźnik Poco zatem przypisywać wskaźnikowi Odpowiedź może być zaskakująca właśnie po to aby uniknąćbłędoacutew Wydaje się to zabawne ale większość (jeśli nie wszystkie) funkcji ktoacutere zwracają wskaźnikw przypadku błędu zwroacuteci właśnie czyli zero Tutaj rodzi się kolejna wskazoacutewka jeśli w danejzmiennej przechowujemy wskaźnik zwroacutecony wcześniej przez jakąś funkcję zawsze sprawdzajmy czynie jest on roacutewny () Wtedy mamy pewność że funkcja zadziałała poprawnie

Dokładniej nie jest słowem kluczowym lecz stałą (makrem) zadeklarowaną przez dyrektywypreprocesora Deklaracja taka może być albo wartością albo też wartością zrzutowaną na void(((void )0)) ale też jakimś słowem kluczowym deklarowanym przez kompilator

Warto zauważyć że pomimo przypisywania wskaźnikowi zera nie oznacza to że wskaźnik jest reprezentowany przez same zerowe bity Co więcej wskaźniki roacuteżnych typoacutew mogą miećroacuteżną wartość Z tego powodu poniższy kod jest niepoprawny

int tablica_wskaznikow = calloc(100 sizeof tablica_wskaznikow)

178 STAŁE WSKAŹNIKI 121

Zakłada on że w reprezentacji wskaźnika występują same zera Poprawnym zainicjowaniemdynamicznej tablicy wskaźnikoacutew wartościami jest (pomijamy sprawzdanie wartości zwroacuteconejprzez malloc())

int tablica_wskaznikow = malloc(100 sizeof tablica_wskaznikow)int i = 0while (ilt100)

tablica_wskaznikow[i++] = 0

178 Stałe wskaźnikiTak jak istnieją zwykłe stałe tak samo możemy mieć stałe wskaźniki mdash jednak są ich dwa rodzajeWskaźniki na stałą wartość

const int a lub roacutewnoważnie int const a

oraz stałe wskaźniki

int const b

Pierwszy to wskaźnik ktoacuterym nie można zmienić wskazywanej wartości Drugi to wskaźnik ktoacute-rego nie można przestawić na inny adres Dodatkowo można zadeklarować stały wskaźnik ktoacuterym niemożna zmienić wartości wskazywanej zmiennej i roacutewnież można zrobić to na dwa sposoby

const int const c alternatywnie int const const c

int i=0const int a=ampiint const b=ampiint const const c=ampia = 1 kompilator zaprotestuje b = 2 ok c = 3 kompilator zaprotestuje a = b ok b = a kompilator zaprotestuje c = a kompilator zaprotestuje

Wskaźniki na stałą wartość są przydatne między innymi w sytuacji gdy mamy duży obiekt (naprzykład strukturę z kilkoma polami) Jeśli przypiszemy taką zmienną do innej zmiennej kopiowaniemoże potrwać dużo czasu a oproacutecz tego zostanie zajęte dużo pamięci Przekazanie takiej struktury dofunkcji albo zwroacutecenie jej jako wartość funkcji wiąże się z takim samym narzutem W takim wypadkudobrze jest użyć wskaźnika na stałą wartość

void funkcja(const duza_struktura ds)

czytamy z ds i wykonujemy obliczenia

funkcja(ampdane) mamy pewność że zmienna dane nie zostanie zmieniona

122 ROZDZIAŁ 17 WSKAŹNIKI

179 Dynamiczna alokacja pamięciMając styczność z tablicami można się zastanowić czy nie dałoby się mieć tablic ktoacuterych rozmiardostosowuje się do naszych potrzeb a nie jest na stałe zaszyty w kodzie programu Chcąc pomieścićwięcej danych możemy po prostu zwiększyć rozmiar tablicy mdash ale gdy do przechowania będzie mniejelementoacutew okaże się że marnujemy pamięć Język C umożliwia dzięki wskaźnikom i dynamicznejalokacji pamięci tworzenie tablic takiej wielkości jakiej akurat potrzebujemy

1791 O co odziCzym jest dynamiczna alokacja pamięci Normalnie zmienne programu przechowywane są na tzwstosie (ang sta) mdash powstają gdy program wchodzi do bloku w ktoacuterym zmienne są zadeklarowane azwalniane w momencie kiedy program opuszcza ten blok Jeśli deklarujemy tak tablice to ich rozmiarmusi być znanywmomencie kompilacji mdash żeby kompilator wygenerował kod rezerwujący odpowiedniąilość pamięci Dostępny jest jednak drugi rodzaj rezerwacji (czyli alokacji) pamięci Jest to alokacja nastercie (ang heap) Sterta to obszar pamięci wspoacutelny dla całego programu przechowywane są w nimzmienne ktoacuterych czas życia nie jest związany z poszczegoacutelnymi blokami Musimy sami rezerwować dlanich miejsce i to miejsce zwalniać ale dzięki temu możemy to zrobić w dowolnym momencie działaniaprogramu

Należy pamiętać że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analo-giczne działania na stosie Dodatkowo zmienna zajmuje na stercie więcej miejsca niż na stosie mdash stertautrzymuje specjalną strukturę w ktoacuterej trzymane są wolne partie (może to być np lista) Tak więcużywajmy dynamicznej alokacji tam gdzie jest potrzebna mdash dla danych ktoacuterych rozmiaru nie jesteśmyw stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem w ktoacuterymzostały zaalokowane

1792 Obsługa pamięciPodstawową funkcją do rezerwacji pamięci jest funkcja malloc Jest to niezbyt skomplikowana funkcjamdash podając jej rozmiar (w bajtach) potrzebnej pamięci dostajemy wskaźnik do zaalokowanego obszaru

Załoacuteżmy że chcemy stworzyć tablicę liczb typu float

int rozmiarfloat tablica

rozmiar = 3tablica = (float) malloc(rozmiar sizeof tablica)tablica[0] = 01

Przeanalizujmy teraz po kolei co dzieje się w powyższym fragmencie Najpierw deklarujemyzmiennemdash rozmiar tablicy i wskaźnik ktoacutery będzie wskazywał obszarw pamięci gdzie będzie trzymanatablica Do zmiennej rozmiar możemy w trakcie działania programu przypisać cokolwiek mdash wczytaćją z pliku z klawiatury obliczyć wylosować mdash nie jest to istotne rozmiar sizeof tablica obliczapotrzebną wielkość tablicy Dla każdej zmiennej float potrzebujemy tyle bajtoacutew ile zajmuje ten typdanych Ponieważ może się to roacuteżnić na rozmaitych maszynach istnieje operator sizeof zwracającydla danego wyrażenia rozmiar jego typu w bajtach

W wielu książkach (roacutewnież KampRv) i w Internecie stosuje się inny schemat użycia funkcji malloca mianowicie tablica = (float)malloc(rozmiar sizeof(float)) Takie użycie należy traktowaćjako błędne gdyż nie sprzyja ono poprawnemu wykrywaniu błędoacutew

Rozważmy sytuację gdy programista zapomni dodać plik nagłoacutewkowy stdlibh woacutewczas kompila-tor (z braku deklaracji funkcji malloc) przyjmie że zwraca ona typ int zatem do zmiennej tablica (ktoacuterajest wskaźnikiem) będzie przypisywana liczba całkowita co od razu spowoduje błąd kompilacji (a przy-najmniej ostrzeżenie) dzięki czemu będzie można szybko poprawić kod programu Rzutowanie jestkonieczne tylko w języku C++ gdzie konwersja z void na inne typy wskaźnikowe nie jest domyślnaale język ten oferuje nowe sposoby alokacji pamięci

179 DYNAMICZNA ALOKACJA PAMIĘCI 123

Teraz rozważmy sytuację gdy zdecydujemy się zwiększyć dokładność obliczeń i zamiast typu floatużyć typu double Będziemy musieli wyszukać wszystkie wywołania funkcji malloc calloc i reallocodnoszące się do naszej tablicy i zmieniać wszędzie sizeof(float) na sizeof(double) Aby temu zapo-biec lepiej od razu użyć sizeof tablica (lub jeśli ktoś woli z nawiasami sizeof(tablica)) woacutewczaszmiana typu zmiennej tablica na double zostanie od razu uwzględniona przy alokacji pamięci

Dodatkowo należy sprawdzić czy funkcja malloc nie zwroacuteciła wartości mdash dzieje się tak gdyzabrakło pamięci Ale uwaga może się tak stać roacutewnież jeżeli jako argument funkcji podano zero

Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić aby sys-tem operacyjny moacutegł go przydzielić innym potrzebującym procesom Do zwolnienia obszaru pamięciużywamy funkcji free() ktoacutera przyjmuje tylko jeden argument mdash wskaźnik ktoacutery otrzymaliśmy wwyniku działania funkcji malloc()

free (addr)

Należy pamiętać o zwalnianiu pamięci mdash inaczej dojdzie do tzw wycieku pamięci mdash program będzierezerwował nową pamięć ale nie zwracał jej z powrotem i w końcu pamięci może mu zabraknąć

Należy też uważać by nie zwalniać dwa razy tego samegomiejsca Po wywołaniu free wskaźnik niezmienia wartości pamięć wskazywana przez niego może też nie od razu ulec zmianie Czasemmożemywięc korzystać ze wskaźnika (zwłaszcza czytać) po wywołaniu free nie orientując się że robimy coś źlemdash i w pewnym momencie dostać komunikat o nieprawidłowym dostępie do pamięci Z tego powoduzaraz po wywołaniu funkcji free można przypisać wskaźnikowi wartość

Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci Tu z pomocąprzychodzi funkcja realloc

tablica = realloc(tablica 2rozmiarsizeof tablica)

Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub gdy zabrakło pa-mięci) Uwaga mdash może to być inny wskaźnik Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanymaktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca funkcja znajdzie nowe miejscei przekopiuje tam starą zawartość Jak widać wywołanie tej funkcji może być więc kosztowne podwzględem czasu

Ostatnią funkcją jest funkcja calloc() Przyjmuje ona dwa argumenty liczbę elementoacutew tablicyoraz wielkość pojedynczego elementu Podstawową roacuteżnicą pomiędzy funkcjami malloc() i calloc() jestto że ta druga zeruje wartość przydzielonej pamięci (do wszystkich bajtoacutew wpisuje wartość )

1793 Tablice wielowymiarowe

Rysunek 174 tablica dwuwymiarowa mdash w rzeczywistości tablica ze wskaźnikami do tablic

W rozdziale Tablice pokazaliśmy jak tworzyć tablice wielowymiarowe gdy ich rozmiar jest znanyw czasie kompilacji Teraz zaprezentujemy jak to wykonać za pomocą wskaźnikoacutew i to w sytuacji gdyrozmiar może się zmieniać Załoacuteżmy że chcemy stworzyć tabliczkę mnożenia

124 ROZDZIAŁ 17 WSKAŹNIKI

int rozmiarint iint tabliczka

printf(Podaj rozmiar tabliczki mnozenia )scanf(i amprozmiar) dla prostoty nie będziemy sprawdzali

czy użytkownik wpisał sensowną wartość

tabliczka = malloc(rozmiar sizeof tabliczka) 1 for (i = 0 iltrozmiar ++i) 2

tabliczka[i] = malloc(rozmiar sizeof tabliczka) 3 4

for (i = 0 iltrozmiar ++i) int jfor (j = 0 jltrozmiar ++j)

tabliczka[i][j] = (i+1)(j+1)

Najpierw musimy przydzielić pamięć mdash najpierw dla ldquotablicy tablicrdquo () a potem dla każdej z pod-tablic osobno (-) Ponieważ tablica jest typu int to nasza tablica tablic będzie wskaźnikiem na intczyli int Podobnie osobno ale w odwrotnej kolejności będziemy zwalniać tablicę wielowymiarową

for (i = 0 iltrozmiar ++i) free(tabliczka[i])

free(tabliczka)

Należy nie pomylić kolejności po wykonaniu free(tabliczka) nie będziemy mieli prawa odwoły-wać się do tabliczka[i] (bo wcześniej dokonaliśmy zwolnienia tego obszaru pamięci)

Można także zastosować bardziej oszczędny sposoacuteb alokowania tablicy wielowymiarowej a mia-nowicie

define ROZMIAR 10int iint tabliczka = malloc(ROZMIAR sizeof tabliczka)tabliczka = malloc(ROZMIAR ROZMIAR sizeof tabliczka)for (i = 1 iltROZMIAR ++i)

tabliczka[i] = tabliczka[0] + (i ROZMIAR)

for (i = 0 iltROZMIAR ++i) int jfor (j = 0 jltROZMIAR ++j)

tabliczka[i][j] = (i+1)(j+1)

free(tabliczka)free(tabliczka)

Powyższy kod działa w ten sposoacuteb że zamiast dla poszczegoacutelnych wierszy alokować osobno pamięćalokuje pamięć dla wszystkich elementoacutew tablicy i dopiero poacuteźniej przypisuje wskazania poszczegoacutel-nych wskaźnikoacutew-wierszy na kolejne bloki po elementoacutew

1710 WSKAŹNIKI NA FUNKCJE 125

Sposoacuteb ten jest bardziej oszczędny z dwoacutech powodoacutew Po pierwsze wykonywanych jest mniej ope-racji przydzielania pamięci (bo tylko dwie) Po drugie za każdym razem gdy alokuje się pamięć trochęmiejsca się marnuje gdyż funkcja malloc musi w stogu przechowywać roacuteżne dodatkowe informacje natemat każdej zaalokowanej przestrzeni Ponadto czasami alokacja odbywa się blokami i gdy zażąda sięniepełny blok to reszta bloku jest tracona

Zauważmy że w ten sposoacuteb możemy uzyskać nie tylko normalną ldquokwadratowąrdquo tablicę (dla dwoacutechwymiaroacutew) Możliwe jest np uzyskanie tablicy troacutejkątnej

0123012010

lub tablicy o dowolnym innym rozkładzie długości wierszy np

const size_t wymiary[] = 2 4 6 8 1 3 5 7 9 int iint tablica = malloc((sizeof wymiary sizeof wymiary) sizeof tablica)for (i = 0 ilt10 ++i)

tablica[i] = malloc(wymiary[i] sizeof tablica)

Gdy nabierzesz wprawy w używaniu wskaźnikoacutew oraz innych funkcji malloc i realloc nauczyszsię wykonywać roacuteżne inne operacje takie jak dodawanie kolejnych wierszy usuwanie wierszy zmianarozmiaru wierszy zamiana wierszy miejscami itp

1710 Wskaźniki na funkcjeDotychczas zajmowaliśmy się sytuacją gdy wskaźnik wskazywał na jakąś zmienną Jednak nie tylkozmienna ma swoacutej adres w pamięci Oproacutecz zmiennej także i funkcja musi mieć swoje określone miejscew pamięci A ponieważ funkcja ma swoacutej adres6 to nie ma przeszkoacuted aby i na nią wskazywał jakiśwskaźnik

17101 Deklaracja wskaźnika na funkcjęTak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresufunkcji Wskaźnik na funkcję roacuteżni się od innych rodzajoacutew wskaźnikoacutew Jedną z głoacutewnych roacuteżnic jestjego deklaracja Zwykle wygląda ona tak

typ_zwracanej_wartości (nazwa_wskaźnika)(typ1 parametr1 typ2 parametr2)

Oczywiście parametroacutew może być więcej (albo też w ogoacutele może ich nie być) Oto przykład wyko-rzystania wskaźnika na funkcję

include ltstdiohgt

int suma (int a int b)

return a+b

int main ()

6Tak naprawdę kod maszynowy utworzony po skompilowaniu programu odnosi się właśnie do adresu funkcji

126 ROZDZIAŁ 17 WSKAŹNIKI

int (wsk_suma)(int a int b)wsk_suma = sumaprintf(4+5=dn wsk_suma(45))return 0

Zwroacutećmy uwagę na dwie rzeczy

przypisując nazwę funkcji bez nawiasoacutew do wskaźnika automatycznie informujemy kompilatorże chodzi nam o adres funkcji

wskaźnika używamy tak jak normalnej funkcji na ktoacuterą on wskazuje

17102 Do czego można użyć wskaźnikoacutew na funkcjeJęzyk C jest językiem strukturalnym jednak dzięki wskaźnikom istnieje w nim możliwość ldquozaszczepie-niardquo pewnych obiektowych właściwości Wskaźnik na funkcję może być np elementem struktury mdashwtedy mamy bardzo prymitywną namiastkę klasy ktoacuterą dobrze znają programiści piszący w językuC++ Ponadto dzięki wskaźnikom możemy tworzyć mechanizmy działające na zasadzie funkcji zwrot-nej7 Dobrym przykładem może być np tworzenie sterownikoacutew gdzie musimy poinformować roacuteżnepodsystemy jakie funkcje w naszym kodzie służą do wykonywania określonych czynności Przykład

struct urzadzenie int (otworz)(void)void (zamknij)(void)

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

int rejestruj_urzadzenie(struct urzadzenie u) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzenieotworz = moje_urzadzenie_otworzmoje_urzadzeniezamknij = moje_urzadzenie_zamknijrejestruj_urzadzenie(ampmoje_urzadzenie)

Wten sposoacutebwpamięci każda klasamusi przechowywaćwszystkiewskaźniki dowszystkichmetodInnym rozwiązaniem może być stworzenie statycznej struktury ze wskaźnikami do funkcji i woacutewczasw strukturze będzie przechowywany jedynie wskaźnik do tej struktury np

struct urzadzenie_metody

7Funkcje zwrotne znalazły zastosowanie głoacutewnie w programowaniu

1711 MOŻLIWE DEKLARACJE WSKAŹNIKOacuteW 127

int (otworz)(void)void (zamknij)(void)

struct urzadzenie const struct urzadzenie_metody m

int moje_urzadzenie_otworz (void)

kod

void moje_urzadzenie_zamknij (void)

kod

static const struct urzadzenie_metodymoje_urzadzenie_metody = moje_urzadzenie_otworzmoje_urzadzenie_zamknij

int rejestruj_urzadzenie(struct urzadzenie ampu) kod

int init (void)

struct urzadzenie moje_urzadzeniemoje_urzadzeniem = ampmoje_urzadzenie_metodyrejestruj_urzadzenie(ampmoje_urzadzenie)

1711 Możliwe deklaracje wskaźnikoacutew

Tutaj znajduje się kroacutetkie kompendium jak definiować wskaźniki oraz co oznaczają poszczegoacutelne defi-nicje

1712 Popularne błędy

Jednym z najczęstszych błędoacutew oproacutecz proacuteb wykonania operacji na wskaźniku są odwołania siędo obszaru pamięci po jego zwolnieniu Po wykonaniu funkcji free() nie możemy już wykonywaćżadnych odwołań do zwolnionego obszaru Innym rodzajem błędoacutew są

odwołania do adresoacutew pamięci ktoacutere są poza obszarem przydzielonym funkcją malloc()

brak sprawdzania czy dany wskaźnik nie ma wartości

wycieki pamięci czyli niezwalnianie całej przydzielonej wcześniej pamięci

128 ROZDZIAŁ 17 WSKAŹNIKI

i zmienna całkowita (typu int) ip wskaźnik p wskazujący na zmienną całkowitąa[] tablica a liczb całkowitych typu intf() funkcja f zwracająca liczbę całkowitą typu intpp wskaźnik pp na wskaźnik wskazujący na liczbę całkowitą typu int

(pa)[] wskaźnik pa wskazujący na tablicę liczb całkowitych typu int(pf)() wskaźnik pf na funkcję zwracającą liczbę całkowitą typu intap[] tablica ap wskaźnikoacutew na liczby całkowite typu intfp() funkcja fp ktoacutera zwraca wskaźnik na zmienną typu intppp wskaźnik ppp wskazujący na wskaźnik wskazujący na wskaźnik wskazu-

jący na liczbę typu int(ppa)[] wskaźnik ppa na wskaźnik wskazujący na tablicę liczb całkowitych typu

int(ppf)() wskaźnik ppf wskazujący na wskaźnik funkcji zwracającej dane typu int(pap)[] wskaźnik pap wskazujący na tablicę wskaźnikoacutew na typ int(pfp)() wskaźnik pfp na funkcję zwracającą wskaźnik na typ intapp[] tablica wskaźnikoacutew app wskazujących na typ int

(apa[])[] tablica wskaźnikoacutew apa wskazujących wskaźniki na typ int(apf[])() tablica wskaźnikoacutew apf na funkcję ktoacutere zwracają wskaźniki na typ intfpp() funkcja fpp ktoacutera zwraca wskaźnik na wskaźnik na wskaźnik ktoacutery wska-

zuje typ int(fpa())[] funkcja fpa ktoacutera zwraca wskaźnik na tablicę liczb typu int(fpf())() funkcja fpf ktoacutera zwraca wskaźnik na funkcję ktoacutera zwraca dane typu int

1713 Ciekawostki w rozdziale Zmienne pisaliśmy o stałych Normalnie nie mamy możliwości zmiany ich wartości

ale z użyciem wskaźnikoacutew staje się to możliwe

const int CONST=0int c=ampCONSTc = 1printf(inCONST) wypisuje 1

Konstrukcja taka może jednak wywołać ostrzeżenie kompilatora bądź nawet jego błąd mdash wtedymoże pomoacutec jawne rzutowanie z const int na int

język C++ oferuje mechanizm podobny do wskaźnikoacutew ale nieco wygodniejszy ndash referencje

język C++ dostarcza też innego sposobu dynamicznej alokacji i zwalniania pamięci mdash przez ope-ratory new i delete

w rozdziale Typy złożone znajduje się opis implementacji listy za pomocą wskaźnikoacutew Przy-kład ten może być bardzo przydatny przy zrozumieniu po co istnieją wskaźniki jak się nimiposługiwać oraz jak dobrze zarządzać pamięcią

Rozdział 18

Napisy

W dzisiejszych czasach komputer przestał być narzędziem tylko i wyłącznie do przetwarzania danychOd programoacutew komputerowych zaczęto wymagać czegoś nowego mdash program w wyniku swojego dzia-łania nie ma zwracać danych rozumianych tylko przez autora programu lecz powinien być na tylekomunikatywny aby przeciętny użytkownik komputera moacutegł bez problemu tenże komputer obsłużyćDo przechowywania tychże komunikatoacutew służą tzw ldquołańcuchyrdquo (ang string) czyli ciągi znakoacutew

Język C nie jest wygodnym narzędziem do manipulacji napisami Jak się wkroacutetce przekonamyzestaw funkcji umożliwiających operacje na napisach w bibliotece standardowej C jest raczej skromnyDodatkowo problemem jest sposoacuteb w jaki łańcuchy przechowywane są w pamięci

Napisy w języku Cmogą być przyczyną wielu trudnych do wykrycia błędoacuteww programach Wartodobrze zrozumieć jak należy operować na łańcuchach znakoacutew i zachować szczegoacutelną ostrożność w tychmiejscach gdzie napisoacutew używamy

181 Łańcuy znakoacutew w języku CNapis jest zapisywany w kodzie programu jako ciąg znakoacutew zawarty pomiędzy dwoma cudzysłowami

printf (Napis w języku C)

Wpamięci taki łańcuch jest następującympo sobie ciągiem znakoacutew (char) ktoacutery kończy się znakiemldquonullrdquo (czyli po prostu liczbą zero) zapisywanym jako rsquorsquo

Jeśli mamy napis do poszczegoacutelnych znakoacutew odwołujemy się jak w tablicy

char tekst = Jakiś tam tekstprintf(cn przykład[0]) wypisze p - znaki w napisach są numerowane od zera printf(cn tekst[2]) wypisze k

Ponieważ napis w pamięci kończy się zerem umieszczonym tuż za jego zawartością odwołanie się doznaku o indeksie roacutewnym długości napisu zwroacuteci zero

printf(d test[4]) wypisze 0

Napisy możemywczytywać z klawiatury i wypisywać na ekran przy pomocy dobrze znanych funk-cji scanf printf i pokrewnych Formatem używanym dla napisoacutew jest s

printf(s tekst)

129

130 ROZDZIAŁ 18 NAPISY

Większość funkcji działających na napisach znajduje się w pliku nagłoacutewkowym stringhJeśli łańcuch jest zbyt długi można zapisać gow kilku linijkach ale wtedy przechodząc do następnej

linii musimy na końcu postawić znak ldquordquo

printf(Ten napis zajmuje więcej niż jedną linię)

Instrukcja taka wydrukuje

Ten napis zajmuje więcej niż jedną linię

Możemy zauważyć że napis ktoacutery w programie zajął więcej niż jedną linię na ekranie zajął tylkojedną Jest tak ponieważ ldquordquo informuje kompilator że łańcuch będzie kontynuowany w następnej liniikodumdash niemawpływu na prezentację łańcucha Abywydrukować napis w kilku liniach należywstawićdo niego n (ldquonrdquo pochodzi tu od ldquonew linerdquo czyli ldquonowa liniardquo)

printf(Ten napisnna ekranienzajmie więcej niż jedną linię)

W wyniku otrzymamy

Ten napisna ekraniezajmie więcej niż jedną linię

1811 Jak komputer przeowuje w pamięci łańcu

Rysunek 181 Napis ldquoMerkkijonordquo przechowywany w pamięci

Zmienna ktoacutera przechowuje łańcuch znakoacutew jest tak naprawdę wskaźnikiem do ciągu znakoacutew(bajtoacutew) w pamięci Możemy też myśleć o napisie jako o tablicy znakoacutew (jak wyjaśnialiśmy wcześniejtablice to też wskaźniki)

Możemy wygodnie zadeklarować napis

char tekst = Jakiś tam tekst Umieszcza napis w obszarze danych programu i przypisuje adres

char tekst[] = Jakiś tam tekst Umieszcza napis w tablicy char tekst[] = Jakis tam tekst0

Tekst to taka tablica jak każda inna

Kompilator automatycznie przydziela wtedy odpowiednią ilość pamięci (tyle bajtoacutew ile jest literplus jeden dla kończącego nulla) Jeśli natomiast wiemy że dany łańcuch powinien przechowywaćokreśloną ilość znakoacutew (nawet jeśli w deklaracji tego łańcucha podajemy mniej znakoacutew) deklarujemygo w taki sam sposoacuteb jak tablicę jednowymiarową

char tekst[80] = Ten tekst musi być kroacutetszy niż 80 znakoacutew

Należy cały czas pamiętać że napis jest tak naprawdę tablicą Jeśli zarezerwowaliśmy dla napisu znakoacutew to przypisanie do niego dłuższego napisu spowoduje pisanie po pamięci

Uwaga Deklaracja char tekst = cokolwiek oraz char tekst = cokolwiek pomimo że wyglądająbardzo podobnie bardzo się od siebie roacuteżnią W przypadku pierwszej deklaracji proacuteba zmodyfikowania

181 ŁAŃCUCHY ZNAKOacuteW W JĘZYKU C 131

napisu (np tekst[0] = C) może mieć nieprzyjemne skutki Dzieje się tak dlatego że char tekst =cokolwiek deklaruje wskaźnik na stały obszar pamięci1

Pisanie po pamięci może czasami skończyć się błędem dostępu do pamięci (ldquosegmentation faultrdquow systemach ) i zamknięciem programu jednak może zdarzyć się jeszcze gorsza ewentualność mdashmożemy zmienić w ten sposoacuteb przypadkowo wartość innych zmiennych Program zacznie wtedy za-chowywać się nieprzewidywalnie mdash zmienne a nawet stałe co do ktoacuterych zakładaliśmy że ich wartośćbędzie ściśle ustalona mogą przyjąć taką wartość jaka absolutnie nie powinna mieć miejsca Wartowięc stosować zabezpieczenia typu makra assert

Kluczowy jest też kończący napis znak null W zasadzie wszystkie funkcje operujące na napisachopierają właśnie na nim Na przykład strlen szuka rozmiaru napisu idąc od początku i zliczając znaki ażnie natrafi na znak o kodzie zero Jeśli nasz napis nie kończy się znakiem null funkcja będzie szła dalejpo pamięci Na szczęście wszystkie operacje podstawienia typu tekst = ldquoTekstrdquo powodują zakończenienapisu nullem (o ile jest na niego miejsce) 2

1812 Znaki specjalneJak zapewne zauważyłeś w poprzednim przykładzie w łańcuchu ostatnim znakiem jest znak o wartościzero (rsquorsquo) Jednak łańcuchy mogą zawierać inne znaki specjalne(sekwencje sterujące) np

rsquoarsquo - alarm (sygnał akustyczny terminala)

rsquobrsquo - backspace (usuwa poprzedzający znak)

rsquorsquo - wysuniecie strony (np w drukarce)

rsquorrsquo - powroacutet kursora (karetki) do początku wiersza

rsquonrsquo - znak nowego wiersza

rsquordquo - cudzysłoacutew

rsquordquo - apostrof rsquorsquo - ukośnik wsteczny (backslash)

rsquotrsquo - tabulacja pozioma

rsquovrsquo - tabulacja pionowa

rsquorsquo - znak zapytania (pytajnik)

rsquoooorsquo - liczba zapisana w systemie oktalnym (oacutesemkowym) gdzie rsquoooorsquo należy zastąpić trzycy-frową liczbą w tym systemie

rsquoxhhrsquo - liczba zapisana w systemie heksadecymalnym (szesnastkowym) gdzie rsquohhrsquo należy za-stąpić dwucyfrową liczbą w tym systemie

rsquounnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnrsquo należy zastąpić czterocyfrowym identyfika-torem znaku w systemie szesnatkowym rsquonnnnrsquo odpowiada dłuższej formie w postaci rsquonnnnrsquo

rsquounnnnnnnnrsquo - uniwersalna nazwa znaku gdzie rsquonnnnnnnnrsquo należy zastąpić ośmiocyfrowymidentyfikatorem znaku w systemie szesnatkowym

Warto zaznaczyć że znak nowej linii (rsquonrsquo) jest w roacuteżny sposoacuteb przechowywany w roacuteżnych sys-temach operacyjnych Wiąże się to z pewnymi historycznymi uwarunkowaniami W niektoacuterych sys-temach używa się do tego jednego znaku o kodzie xA (Line Feed mdash nowa linia) Do tej rodzinyzaliczamy systemy z rodziny Unix Linux BSD Mac OS X inne Drugą konwencją jest zapisywaniersquonrsquo za pomocą dwoacutech znakoacutew LF (Line Feed) + CR (Carriage return mdash powroacutet karetki) Znak CRreprezentowany jest przez wartość xD Kombinacji tych dwoacutech znakoacutew używają min CPM DOSOS Microso Windows Trzecia grupa systemoacutew używa do tego celu samego znaku CR Są to sys-temy działające na komputerach Commodore Apple II oraz Mac OS do wersji W związku z tym plikutworzony w systemie Linux może wyglądać dziwnie pod systemem Windows

1Można się zatem zastanawiać czemu kompilator dopuszcza przypisanie do zwykłego wskaźnika wskazania nastały obszar skoro kod const int foo int bar = foo generuje ostrzeżenie lub wręcz się nie kompiluje Jest topewna zaszłość historyczna wynikająca z faktu że słoacutewko const zostało wprowadzone do języka gdy już był on wpowszechnym użyciu

2Nie należy mylić znaku null (czyli znaku o kodzie zero) ze wskaźnikiem null (czy też )

132 ROZDZIAŁ 18 NAPISY

182 Operacje na łańcua

1821 Poroacutewnywanie łańcuoacutewNapisy to tak naprawdęwskaźniki Tak więc używając zwykłego operatora poroacutewnania == otrzymamywynik poroacutewnania adresoacutew a nie tekstoacutew

Do poroacutewnywania dwoacutech ciągoacutew znakoacutew należy użyć funkcji strcmp zadeklarowanej w pliku na-głoacutewkowym stringh Jako argument przyjmuje ona dwa napisy i zwraca wartość ujemną jeżeli napispierwszy jestmniejszy od drugiego jeżeli napisy są roacutewne lub wartość dodatnią jeżeli napis pierwszyjest większy od drugiego Ciągi znakoacutew poroacutewnywalne są leksykalnie kody znakoacutew czyli np (przyj-mując kodowanie ASCII) a jest mniejsze od b ale jest większe od B Np

include ltstdiohgtinclude ltstringhgt

int main(void) char str1[100] str2[100]int cmp

puts(Podaj dwa ciagi znakow )fgets(str1 sizeof str1 stdin)fgets(str2 sizeof str2 stdin)

cmp = strcmp(str1 str2)if (cmplt0)

puts(Pierwszy napis jest mniejszy) else if (cmpgt0)

puts(Pierwszy napis jest wiekszy) else

puts(Napisy sa takie same)

return 0

Czasami możemy chcieć poroacutewnać tylko fragment napisu np sprawdzić czy zaczyna się od jakie-goś ciągu W takich sytuacjach pomocna jest funkcja strncmp W poroacutewnaniu do strcmp() przyjmujeona jeszcze jeden argument oznaczający maksymalną liczbę znakoacutew do poroacutewnania

include ltstdiohgtinclude ltstringhgt

int main(void) char str[100]int cmp

fputs(Podaj ciag znakow stdout)fgets(str sizeof str stdin)

if (strncmp(str foo 3)) puts(Podany ciag zaczyna sie od foo)

return 0

182 OPERACJE NA ŁAŃCUCHACH 133

1822 Kopiowanie napisoacutewDo kopiowania ciągoacutew znakoacutew służy funkcja strcpy ktoacutera kopiuje drugi napis w miejsce pierwszegoMusimy pamiętać by w pierwszym łańcuchu było wystarczająco dużo miejsca

char napis[100]strcpy(napis Ala ma kota)

Znacznie bezpieczniej jest używać funkcji strncpy ktoacutera kopiuje co najwyżej tyle bajtoacutew ile podanojako trzeci parametr Uwaga Jeżeli drugi napis jest za długi funkcja nie kopiuje znaku null na koniecpierwszego napisu dlatego zawsze trzeba to robić ręcznie

char napis[100]strncpy(napis Ala ma kota sizeof napis - 1)napis[sizeof napis - 1] = 0

1823 Łączenie napisoacutewDo łączenia napisoacutew służy funkcja strcat ktoacutera kopiuje drugi napis do pierwszego Ponownie jak wprzypadku strcpymusimy zagwarantować by w pierwszym łańcuchu było wystarczająco dużo miejsca

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrcat(napis1 napis2)puts(napis1)return 0

I ponownie jak w przypadku strcpy istnieje funkcja strncat ktoacutera skopiuje co najwyżej tyle bajtoacutewile podano jako trzeci argument i dodatkowo dopisze znak null Przykładowo powyższy kod bezpieczniejzapisać jako

include ltstdiohgtinclude ltstringhgt

int main(void) char napis1[80] = hello char napis2 = worldstrncat(napis1 napis2 sizeof napis1 - 1)puts(napis1)return 0

Osoby ktoacutere programowały w językach skryptowych muszą bardzo uważać na łączenie i kopiowa-nie napisoacutew Kompilator języka C nie wykryje nadpisania pamięci za zmienną łańcuchową i nie przy-dzieli dodatkowego obszaru pamięci Może się zdarzyć że program pomimo nadpisywania pamięci załańcuchem będzie nadal działał co bardzo utrudni wykrywanie tego typu błędoacutew

134 ROZDZIAŁ 18 NAPISY

183 Bezpieczeństwo kodu a łańcuy

1831 Przepełnienie buforaO co właściwie chodzi z tymi funkcjami strncpy i strncat Otoacuteż niewinnie wyglądające łańcuchy mogąokazać się zaboacutejcze dla bezpieczeństwa programu a przez to nawet dla systemu w ktoacuterym ten programdziała Może brzmi to strasznie lecz jest to prawda Może pojawić się tutaj pytanie ldquow jaki sposoacutebłańcuch może zaszkodzić programowirdquo Otoacuteż może i to całkiem łatwo Przeanalizujmy następującykod

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strcpy(haslo argv[1]) tutaj następuje przepełnienie bufora if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Jest to bardzo prosty program ktoacutery wykonuje jakąś akcję jeżeli podane jako pierwszy argumenthasło jest poprawne Sprawdźmy czy działa

$ aout niepoprawnePodales bledne haslo$ aout poprawneWitaj wprowadziles poprawne haslo

Jednak okazuje się że z powodu użycia funkcji strcpy włamywacz nie musi znać hasła aby programuznał że zna hasło np

$ aout 11111111111111111111111111111111Witaj wprowadziles poprawne haslo

Co się stało Podaliśmy ciąg jedynek dłuższy niż miejsce przewidziane na hasło Funkcja strcpy()kopiując znaki z argv[1] do tablicy (bufora) haslo przekroczyła przewidziane dla niego miejsce i szładalej mdash gdzie znajdowała się zmienna haslo poprawne strcpy() kopiowała znaki już tam gdzie znajdo-wały się inne dane mdash między innymi wpisała jedynkę do haslo poprawne

Podany przykład może się roacuteżnie zachowywać w zależności od kompilatora jakim został skompi-lowany i systemu na jakim działa ale ogoacutelnie mamy do czynienia z poważnym niebezpieczeństwem

183 BEZPIECZEŃSTWO KODU A ŁAŃCUCHY 135

Taką sytuację nazywamy przepełnieniem bufora Może umożliwić dostęp do komputera osobomnieuprzywilejowanym Należy wystrzegać się tego typu konstrukcji a wmiejsce niebezpiecznej funkcjistrcpy stosować bardziej bezpieczną strncpy

Oto bezpieczna wersja poprzedniego programu

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo[16]

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

strncpy(haslo argv[1] sizeof haslo - 1)haslo[sizeof haslo - 1] = 0if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)return EXIT_SUCCESS

Bezpiecznymi alternatywami do strcpy i strcat są też funkcje strlcpy oraz strlcat opracowane przezprojekt OpenBSD i dostępne do ściągnięcia na wolnej licencji strlcpy strlcat strlcpy() działa podobniedo strncpy strlcpy (buf argv[1] sizeof buf) jednak jest szybsza (nie wypełnia pustego miejscazerami) i zawsze kończy napis nullem (czego nie gwarantuje strncpy) strlcat(dst src size) działanatomiast jak strncat(dst src size-1)

Do innych niebezpiecznych funkcji należy np gets zamiast ktoacuterej należy używać fgetsZawsze możemy też alokować napisy dynamicznie

include ltstdiohgtinclude ltstringhgtinclude ltstdlibhgt

int main(int argc char argv) char haslo_poprawne = 0char haslo

if (argc=2) fprintf(stderr uzycie s haslo argv[0])return EXIT_FAILURE

136 ROZDZIAŁ 18 NAPISY

haslo = malloc(strlen(argv[1]) + 1) +1 dla znaku null if (haslo)

fputs(Za malo pamiecin stderr)return EXIT_FAILURE

strcpy(haslo argv[1])if (strcmp(haslo poprawne))

haslo_poprawne = 1

if (haslo_poprawne) fputs(Podales bledne haslon stderr)return EXIT_FAILURE

puts(Witaj wprowadziles poprawne haslo)free(haslo)return EXIT_SUCCESS

1832 Nadużycia z udziałem ciągoacutew formatującyJednak to nie koniec kłopotoacutew z napisami Wielu programistoacutew nieświadomych zagrożenia częstoużywa tego typu konstrukcji

include ltstdiohgtint main (int argc char argv[])

printf (argv[1])

Z punktu widzenia bezpieczeństwa jest to bardzo poważny błąd programu ktoacutery może nieść zesobą katastrofalne skutki Prawidłowo napisany kod powinien wyglądać następująco

include ltstdiohgtint main (int argc char argv[])

printf (s argv[1])

lub

include ltstdiohgtint main (int argc char argv[])

fputs (argv[1] stdout)

Źroacutedło problemu leży w konstrukcji funkcji printf Przyjmuje ona bowiem za pierwszy parametrłańcuch ktoacutery następnie przetwarza Jeśli w pierwszym parametrze wstawimy jakąś zmienną to funk-cja printf potraktuje ją jako ciąg znakoacutew razem ze znakami formatującymi Zatem ważne aby wcześniewyrobić sobie nawyk stosowania funkcji printf z co najmniej dwoma parametrami nawet w przypadkuwyświetlenia samego tekstu

184 KONWERSJE 137

184 KonwersjeCzasami zdarza się że łańcuch można interpretować nie tylko jako ciąg znakoacutew lecz np jako liczbęJednak aby dało się taką liczbę przetworzyć musimy skopiować ją do pewnej zmiennej Aby ułatwićprogramistom tego typu zamiany powstał zestaw funkcji bibliotecznych Należą do nich

atol strtol mdash zamienia łańcuch na liczbę całkowitą typu long

atoi mdash zamienia łańcuch na liczbę całkowitą typu int

atoll strtoll mdash zamienia łańcuch na liczbę całkowitą typu long long ( bity) dodatkowo istniejeprzestarzała funkcja atoq będąca rozszerzeniem

atof strtod mdash przekształca łańcuch na liczbę typu double

Ogoacutelnie rzecz ujmując funkcje z serii ato nie pozwalają na wykrycie błędoacutew przy konwersji i dla-tego gdy jest to potrzebne należy stosować funkcje strto

Czasami przydaje się też konwersja w drugą stronę tzn z liczby na łańcuch Do tego celu możeposłużyć funkcja sprintf lub snprintf sprintf jest bardzo podobna do printf tyle że wyniki jej praczwracane są do pewnego łańcucha a nie wyświetlane np na ekranie monitora Należy jednak uwa-żać przy jej użyciu (patrz mdash Bezpieczeństwo kodu a łańcuchy) snprintf (zdefiniowana w nowszymstandardzie) dodatkowo przyjmuje jako argument wielkość bufora docelowego

185 Operacje na znakaWarto też powiedzieć w tymmiejscu o operacjach na samych znakach Spoacutejrzmy na poniższy program

include ltstdiohgtinclude ltctypehgtinclude ltstringhgt

int main()

int znakwhile ((znak = getchar())=EOF)

if( islower(znak) ) znak = toupper(znak)

else if( isupper](znak) ) znak = tolower(znak)

putchar(znak)

return 0

Program ten zmieniawewczytywanym tekściewielkie litery namałe i odwrotnie Wykorzystujemyfunkcje operujące na znakach z pliku nagłoacutewkowego ctypeh isupper sprawdza czy znak jest wielkąliterą natomiast toupper zmienia znak (o ile jest literą) na wielką literę Analogicznie jest dla funkcjiislower i tolower

Jako ćwiczenie możesz tak zmodyfikować program żeby odczytywał dane z pliku podanego jakoargument lub wprowadzonego z klawiatury

186 Częste błędy pisanie do niezaalokowanego miejsca

138 ROZDZIAŁ 18 NAPISY

char tekstscanf(s tekst)

zapominanie o kończącym napis nullu

char test[4] = test nie zmieścił się null kończący napis

nieprawidłowe poroacutewnywanie łańcuchoacutew

char tekst1[] = jakis tekstchar tekst2[] = jakis tekstif( tekst1 == tekst2 ) tu zawsze będzie fałsz bo == poroacutewnuje adresy należy użyć strcmp()

187 UnicodeZobacz w Wikipedii Uni-code W dzisiejszych czasach brak obsługi wielu językoacutew praktycznie marginalizowałoby język Dlatego też

C wprowadza możliwość zapisu znakoacutew wg norm Unicode

1871 Jaki typDo przechowywania znakoacutew zakodowanych w Unicode powinno się korzystać z typu war t Jegodomyślny rozmiar jest zależny od użytego kompilatora lecz w większości zaktualizowanych kompila-toroacutew powinny to być bajty Typ ten jest częścią języka C++ natomiast w C znajduje się w plikunagłoacutewkowym stddefh

Alternatywą jest wykorzystanie gotowych bibliotek dla Unicode (większość jest dostępnych jedyniedla C++ nie wspoacutełpracuje z C) ktoacutere często mają zdefiniowane własne typy jednak zmuszeni jesteśmywtedy do przejścia ze znanych nam już funkcji jak np strcpy strcmp na funkcje dostarczane przezbibliotekę co jest dość niewygodne My zajmiemy się pierwszym wyjściem

1872 Jaki rozmiar i jakie kodowanieUnicode określa jedynie jakiej liczbie odpowiada jaki znak nie moacutewi zaś nic o sposobie dekodowania(tzn jaka sekwencja znakoacutew odpowiada jakiemu znakuznakom) Jako że Unicode obejmuje tysznakoacutew zmienna zdolna pomieścić go w całości musi mieć przynajmniej bajty Niestety procesory niefunkcjonują na zmiennych o tym rozmiarze pracują jedynie na zmiennych o wielkościach oraz bajtoacutew (kolejne potęgi liczby ) Dlatego też jeśli wciąż uparcie chcemy być dokładni i zastosowaćprzejrzyste kodowanie musimy skorzystać ze zmiennej -bajtowej ( bity) Tak do sprawy podeszlitwoacutercy kodowania Unicode nazwanego -UCS- Ten typ kodowania po prostu przydziela każ-Zobacz w Wikipedii -32demu znakowi Unicode kolejne liczby Jest to najbardziej intuicyjny i wygodny typ kodowania ale jakwidać ciągi znakoacutew zakodowane w nim są bardzo obszerne co zajmuje dostępną pamięć spowalniadziałanie programu oraz drastycznie pogarsza wydajność podczas transferu przez sieć Poza -istnieje jeszcze wiele innych kodowań Najpopularniejsze z nich to

- mdash od do bajtoacutew (dla znakoacutew poniżej do bajtoacutew) na znak przez co jest skraj-nie niewygodny gdy chcemy przeprowadzać jakiekolwiek operacje na tekście bez korzystania zgotowych funkcji

- mdash lub bajty na znak ręczne modyfikacje łańcucha są bardziej skomplikowane niż przy-

UCS- mdash bajty na znak przez co znaki z numerami powyżej nie są uwzględnione roacutewniewygodny w użytkowaniu co -

187 UNICODE 139

Ręczne operacje na ciągach zakodowanych w - i - są utrudnione ponieważ w przeci-wieństwie do - gdzie można określić iż powiedzmy znak ciągu zajmuje bajty od do (gdyżz goacutery wiemy że znak zajął bajty od do ) w tych kodowaniach musimy najpierw określić rozmiar znaku Ponadto gdy korzystamy z nich nie działają wtedy funkcje udostępniane przez biblioteki Cdo operowania na ciągach znakoacutew

Priorytet Proponowane kodowaniamały rozmiar -8

łatwa i wydajna edycja -32 lub -2przenośność -83

ogoacutelna szybkość -2 lub -8

Co należy zrobić by zacząć korzystać z kodowania - (domyślne kodowanie dla C)

powinniśmy korzystać z typu wchar t (ang ldquowide characterrdquo) jednak jeśli chcemy udostępniaćkod źroacutedłowy programu do kompilacji na innych platformach powinniśmy ustawić odpowiednieparametry dla kompilatoroacutew by rozmiar był identyczny niezależnie od platformy

korzystamy z odpowiednikoacutew funkcji operujących na typie char pracujących na wchar t (z re-guły składnia jest identyczna z tą roacuteżnicą że w nazwach funkcji zastępujemy ldquostrrdquo na ldquowcsrdquo npstrcpy mdash wcscpy strcmp mdash wcscmp)

jeśli przyzwyczajeni jesteśmy do korzystania z klasy string powinniśmy zamiast niej korzystaćz wstring ktoacutera posiada zbliżoną składnię ale pracuje na typie wchar t

Co należy zrobić by zacząć korzystać z Unicode

gdy korzystamy z kodowań innych niż - i - powinniśmy zdefiniować własny typ

w wykorzystywanych przez nas bibliotekach podajemy typ wykorzystanego kodowania

gdy chcemy ręcznie modyfikować ciąg musimy przeczytać specyfikację danego kodowania sąone wyczerpująco opisane na siostrzanym projekcie Wikibooks mdash Wikipedii

Przykład użycia kodowania -

include ltstddefhgt jeśli używamy C++ możemy opuścić tę linijkę include ltstdiohgtinclude ltstringhgt

int main() wchar_t wcs1 = LAla ma kotawchar_t wcs2 = LKot ma Alewchar_t calosc[25]

wcscpy(calosc wcs1)(calosc + wcslen(wcs1)) = L wcscpy(calosc + wcslen(wcs1) + 1 wcs2)

printf(lancuch wyjsciowy lsn calosc)return 0

140 ROZDZIAŁ 18 NAPISY

Rozdział 19

Typy złożone

191 typedefJest to słowo kluczowe ktoacutere służy do definiowania typoacutew pochodnych np

typedef stara_nazwa nowa_nazwatypedef int mojInttypedef int WskNaInt

od tej pory mozna używać typoacutew mojInt i WskNaInt

192 Typ wyliczeniowySłuży do tworzenia zmiennych ktoacutere powinny przechowywać tylko pewne z goacutery ustalone wartości

enum Nazwa WARTOSC_1 WARTOSC_2 WARTOSC_N

Na przykład można w ten sposoacuteb stworzyć zmienną przechowującą kierunek

enum Kierunek W_GORE W_DOL W_LEWO W_PRAWO

enum Kierunek kierunek = W_GORE

ktoacuterą można na przykład wykorzystać w instrukcji switch

switch(kierunek)

case W_GOREprintf(w goacuteręn)break

case W_DOLprintf(w doacutełn)break

defaultprintf(gdzieś w bokn)

Tradycyjnie przechowywane wielkości zapisuje się wielkimi literami (W GORE W DOL)Tak naprawdę C przechowuje wartości typu wyliczeniowego jako liczby całkowite o czym można

się łatwo przekonać

141

142 ROZDZIAŁ 19 TYPY ZŁOŻONE

kierunek = W_DOLprintf(in kierunek) wypisze 1

Kolejne wartości to po prostu liczby naturalne domyślnie pierwsza to zero druga jeden itp Mo-żemy przy deklarowaniu typu wyliczeniowego zmienić domyślne przyporządkowanie

enum Kierunek W_GORE W_DOL = 8 W_LEWO W_PRAWO printf(i in W_DOL W_LEWO) wypisze 8 9

Co więcej liczby mogą się powtarzać i wcale nie muszą być ustawione w kolejności rosnącej

enum Kierunek W_GORE = 5 W_DOL = 5 W_LEWO = 2 W_PRAWO = 1 printf(i in W_DOL W_LEWO) wypisze 5 2

Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajną ich obsługęale stwarza niebezpieczeństwa mdash można przypisywać pod typ wyliczeniowy liczby nawet nie mająceodpowiednika w wartościach a kompilator może o tym nawet nie ostrzec

kierunek = 40

193 StrukturyStruktury to specjalny typ danych mogący przechowywać wiele wartości w jednej zmiennej Od tablicjednakże roacuteżni się tym iż te wartości mogą być roacuteżnych typoacutew

Struktury definiuje się w następujący sposoacuteb

struct Struktura int pole1int pole2char pole3

gdzie ldquoStrukturardquo to nazwa tworzonej strukturyNazewnictwo ilość i typ poacutel definiuje programista według własnego uznaniaZmienną posiadającą strukturę tworzy się podając jako jej typ nazwę struktury

struct Struktura zmienna

Dostęp do poszczegoacutelnych poacutel uzyskuje się przy pomocy operatora wyboru składnika kropki (rsquorsquo)

zmiennaSpole1 = 60 przypisanie liczb do poacutel zmiennaSpole2 = 2zmiennaSpole3 = a a teraz znaku

194 UnieUnie to kolejny sposoacuteb prezentacji danychw pamięci Na pierwszy rzut oka wyglądają bardzo podobniedo struktur

union Nazwa typ1 nazwa1typ2 nazwa2

Na przykład

194 UNIE 143

union LiczbaLubZnak int calkowitachar znakdouble rzeczywista

Pola w unii nakładają się na siebie w ten sposoacuteb że w danej chwili można w niej przechowywaćwartość tylko jednego typu Unia zajmuje w pamięci tyle miejsca ile zajmuje największa z jej składo-wych W powyższym przypadku unia będzie miała prawdopodobnie rozmiar typu double czyli często bity a całkowita i znak będą wskazywały odpowiednio na pierwsze cztery bajty lub na pierwszy bajtunii (choć nie musi tak być zawsze) Dlaczego tak Taka forma często przydaje się np do konwersji po-między roacuteżnymi typami danych Możemy dzięki unii podzielić zmienną -bitową na cztery składowezmienne o długości bitoacutew każda

Do konkretnych wartości poacutel unii odwołujemy się podobnie jak w przypadku struktur za pomocąkropki

union LiczbaLubZnak liczbaliczbacalkowita = 10printf(dn liczbacalkowita)

Zazwyczaj użycie unii ma na celu zmniejszenie zapotrzebowania na pamięć gdy naraz będzie wy-korzystywane tylko jedno pole i jest często łączone z użyciem struktur

Przyjrzyjmy się teraz przykładowi ktoacutery powinien dobitnie zademonstrować działanie unii

include ltstdiohgt

struct adres_bajtowy __uint8_t a__uint8_t b__uint8_t c__uint8_t d

union adres __uint32_t ipstruct adres_bajtowy badres

int main ()

union adres addraddrbadresa = 192addrbadresb = 168addrbadresc = 1addrbadresd = 1printf (Adres IP w postaci 32-bitowej zmiennej 08xnaddrip)return 0

Zauważyłeś pewien ciekawy efekt Jeśli uruchomiłeś ten program na typowym komputerze domo-wym (rodzina i) na ekranie zapewne pojawił Ci się taki oto napis

Adres IP w postaci 32-bitowej zmiennej 0101a8c0

Dlaczego jedynki są na początku zmiennej skoro w programie były to dwa ostatnie bajty (pola c id struktury) Jest to problem kolejności bajtoacutew Aby dowiedzieć się o nim więcej przeczytaj rozdział

144 ROZDZIAŁ 19 TYPY ZŁOŻONE

przenośność programoacutew Zauważyłeś zatem że za pomocą tego programu w prosty sposoacuteb zamienili-śmy cztery zmienne jednobajtowe w jedną czterobajtową Jest to tylko jedno z możliwych zastosowańunii

195 Inicjalizacja struktur i uniiJeśli tworzymy nową strukturę lub unię możemy zaraz po jej deklaracji wypełnić ją określonymi da-nymi Rozważmy tutaj przykład

struct moja_struct int achar b moja = 1c

Wzasadzie taka deklaracja nie roacuteżni się niczym odwypełnienia np tablicy danymi Jednak standardC wprowadza pewne udogodnienie zaroacutewno przy deklaracji struktur jak i unii Polega ono na tymże w nawiasie klamrowym możemy podać nazwy poacutel struktury lub unii ktoacuterym przypisujemy wartośćnp

struct moja_struct int achar b moja = b = c pozostawiamy pole a niewypełnione żadną konkretną wartością

196 Wspoacutelnewłasności typoacutewwyliczeniowy unii i struk-tur

Warto w zwroacutecić uwagę że język C++ przy deklaracji zmiennych typoacutew wyliczeniowych unii lubstruktur nie wymaga przed nazwą typu odpowiedniego słowa kluczowego Na przykład poniższy kodjest poprawnym programem C++

enum Enum A B C union Union int a float b struct Struct int a float b int main()

Enum eUnion uStruct se = Aua = 0sa = 0return e + ua + sa

Nie jest to jednak poprawny kod C i należy o tym pamiętać szczegoacutelnie jeżeli uczysz się języka Ckorzystając z kompilatora C++

Należy roacutewnież pamiętać że po klamrze zamykającej definicje musi następować średnik Brak tegośrednika jest częstym błędem powodującym czasami niezrozumiałe komunikaty błędoacutew Jedynym wy-jątkiem jest natychmiastowa definicja zmiennych danego typu na przykład

struct Struktura int pole

s1 s2 s3

196 WSPOacuteLNE WŁASNOŚCI TYPOacuteW WYLICZENIOWYCH UNII I STRUKTUR 145

Definicja typoacutew wyliczeniowych unii i struktur jest lokalna do bloku To znaczy możemy zdefi-niować strukturę wewnątrz jednej z funkcji (czy wręcz wewnątrz jakiegoś bloku funkcji) i tylko tambędzie można używać tego typu

Częstym idiomem w C jest użycie typedef od razu z definicją typu by uniknąć pisania enum unionczy struct przy deklaracji zmiennych danego typu

typedef struct struktura int pole

StrukturaStruktura s1struct struktura s2

W tym przypadku zmienne s i s są tego samego typu Możemy też zrezygnować z nazywaniasamej struktury

typedef struct int pole

StrukturaStruktura s1

1961 Wskaźnik na unię i strukturęPodobnie jak na każdą inną zmienna wskaźnik może wskazywać także na unię lub strukturę Otoprzykład

typedef struct int p1 p2

Struktura

int main ()

Struktura s = 0 0 Struktura wsk = ampswsk-gtp1 = 2wsk-gtp2 = 3return 0

Zapis wsk-gtp1 jest (z definicji) roacutewnoważny (wsk)p1 ale bardziej przejrzysty i powszechnie sto-sowany Wyrażenie wskp1 spowoduje błąd kompilacji (strukturą jest wsk a nie wsk)

1962 Zobacz też Powszechne praktyki mdash konstruktory i destruktory

1963 Pola bitoweStruktury mają pewne dodatkowe możliwości w stosunku do zmiennych Mowa tutaj o rozmiarzeelementu struktury W przeciwieństwie do zmiennej może on mieć nawet bit Aby moacutec zdefiniowaćtaką zmienną musimy użyć tzw pola bitowego Wygląda ono tak

struct moja unsigned int a14 4 bity

a28 8 bitoacutew (często 1 bajt) a31 1 bit a43 3 bity

146 ROZDZIAŁ 19 TYPY ZŁOŻONE

Wszystkie pola tej struktury mają w sumie rozmiar bitoacutew jednak możemy odwoływać się donichw taki sam sposoacuteb jak do innych elementoacutew struktury W ten sposoacuteb efektywniej wykorzystujemypamięć jednak istnieją pewne zjawiska ktoacuterych musimy być świadomi przy stosowaniu poacutel bitowychWięcej na ten temat w rozdziale przenośność programoacutew

Pola bitowe znalazły zastosowanie głoacutewnie w implementacjach protokołoacutew sieciowych

197 Studium przypadku mdash implementacja listy wskaźniko-wej

Zobacz w Wikipedii ListaRozważmy teraz coś co każdy z nas może spotkać w codziennym życiu Każdy z nas widział kiedyśjakiś przykład listy (czy to zakupoacutew czy też listę wierzycieli) Język C też oferuje listy jednak w progra-mowaniu listy będą służyły do czegoś innego Wyobraźmy sobie sytuację w ktoacuterej jesteśmy autoramigenialnego programu ktoacutery znajduje kolejne liczby pierwsze Oczywiście każdą kolejną liczbę pierw-szą może wyświetlać na ekran jednak z matematyki wiemy że dana liczba jest liczbą pierwszą jeśli niedzieli się przez żadną liczbę pierwszą ją poprzedzającą mniejszą od pierwiastka z badanej liczby Uffmniej więcej chodzi o to że moglibyśmy wykorzystać znalezione wcześniej liczby do przyspieszeniadziałania naszego programu Jednak nasze liczby trzeba jakoś mądrze przechować w pamięci Tablicemają ograniczenie mdash musimy z goacutery znać ich rozmiar Jeśli zapełnilibyśmy tablicę to przy znalezieniukażdej kolejnej liczby musielibyśmy

przydzielać nowy obszar pamięci o rozmiarze poprzedniego rozmiaru + rozmiar zmiennej prze-chowującej nowo znalezioną liczbę

kopiować zawartość starego obszaru do nowego

zwalniać stary nieużywany obszar pamięci

w ostatnim elemencie nowej tablicy zapisać znalezioną liczbę

Coacuteż trochę tutaj roboty jest a kopiowanie całej zawartości jednego obszaru w drugi jest czaso-chłonne W takim przypadku możemy użyć listy Tworząc listę możemy w prosty sposoacuteb przechowaćnowo znalezione liczby Przy użyciu listy nasze postępowanie ograniczy się do

przydzielenia obszaru pamięci aby przechować wartość obliczeń

dodać do listy nowy element

Prawda że proste Dodatkowo lista zajmuje w pamięci tylko tyle pamięci ile potrzeba na aktualnąliczbę elementoacutew Pusta tablica zajmuje natomiast tyle samo miejsca co pełna tablica

1971 Implementacja listyW języku C aby stworzyć listę musimy użyć struktur Dlaczego Ponieważ musimy przechować conajmniej dwie wartości

pewną zmienną (np liczbę pierwszą z przykładu)

wskaźnik na kolejny element listy

Przyjmijmy że szukając liczb pierwszych nie przekroczymy możliwości typu unsigned long

typedef struct element struct element next wskaźnik na kolejny element listy unsigned long val przechowywana wartość

el_listy

Zacznijmy zatem pisać nasz eksperymentalny program do wyszukiwania liczb pierwszych Pierw-szą liczbą pierwszą jest liczba Pierwszym elementem naszej listy będzie zatem struktura ktoacutera będzieprzechowywała liczbę Na co będzie wskazywało pole next Ponieważ na początku działania pro-gramu będziemy mieć tylko jeden element listy pole next powinno wskazywać na Umoacutewmy sięzatem że pole next ostatniego elementu listy będzie wskazywało mdash po tym poznamy że lista sięskończyła

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 147

include ltstdiohgtinclude ltstdlibhgttypedef struct element

struct element nextunsigned long val

el_listy

el_listy first pierwszy element listy

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (ilt=END++i) tutaj powinien znajdować się kod ktoacutery sprawdza podzielność sprawdzanej liczby przezpoprzednio znalezione liczby pierwsze oraz dodaje liczbę do listy w przypadku stwierdzenia

że jest ona liczbą pierwszą

wypisz_liste(first)return 0

Na początek zajmiemy się wypisywaniem listy W tym celu będziemy musieli ldquoodwiedzićrdquo każdyelement listy Elementy listy są połączone polem next aby przeglądnąć listę użyjemy następującegoalgorytmu

Ustaw wskaźnik roboczy na pierwszym elemencie listy

Jeśli wskaźnik ma wartość przerwij

Wypisz element wskazywany przez wskaźnik

Przesuń wskaźnik na element ktoacutery jest wskazywany przez pole next

Wroacuteć do punktu

void wypisz_liste(el_listy lista)

el_listy wsk=lista 1 while( wsk = NULL ) 2

printf (lun wsk-gtval) 3 wsk = wsk-gtnext 4 5

Zastanoacutewmy się teraz jak powinien wyglądać kod ktoacutery dodaje do listy następny element Takafunkcja powinna

znaleźć ostatni element (tj element ktoacuterego pole next == )

przydzielić odpowiedni obszar pamięci

skopiować w pole val w nowo przydzielonym obszarze znalezioną liczbę pierwszą

nadać polu next ostatniego elementu listy wartość

w pole next ostatniego elementu listy wpisać adres nowo przydzielonego obszaru

148 ROZDZIAŁ 19 TYPY ZŁOŻONE

Napiszmy zatem odpowiednią funkcję

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL) 1

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy)) 2 nowy-gtval = liczba 3 nowy-gtnext = NULL 4 wsk-gtnext = nowy 5

Ihellip to już właściwie koniec naszej funkcji (warto zwroacutecić uwagę że funkcja w tej wersji zakłada żena liście jest już przynajmniej jeden element) Wstaw ją do kodu przed funkcją main Został namjeszcze jeden problem w pętli for musimy dodać kod ktoacutery odpowiednio będzie ldquobadałrdquo liczby oraz wprzypadku stwierdzenia pierwszeństwa liczby będzie dodawał ją do listy Ten kod powinien wyglądaćmniej więcej tak

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczba wsk-gtval)==0) return 0 jeśli reszta z dzielenialiczby przez ktoacuterąkolwiek z poprzednio znalezionychliczb pierwszych jest roacutewna zero to znaczy że liczba tanie jest liczbą pierwszą

wsk = wsk-gtnext

natomiast jeśli sprawdzimy wszystkie poprzednio znalezione liczbyi żadna z nich nie będzie dzieliła liczby imożemy liczbę i dodać do listy liczb pierwszych

return 1for (ilt=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (firsti)

Podsumujmy teraz efekty naszej pracy Oto cały kod naszego programu

include ltstdiohgtinclude ltstdlibhgt

typedef struct element struct element nextunsigned long val

el_listy

el_listy first

197 STUDIUM PRZYPADKU mdash IMPLEMENTACJA LISTY WSKAŹNIKOWEJ 149

void dodaj_do_listy (el_listy lista unsigned long liczba)

el_listy wsk nowywsk = listawhile (wsk-gtnext = NULL)

wsk = wsk-gtnext przesuwamy wsk aż znajdziemy ostatni element

nowy = malloc (sizeof(el_listy))nowy-gtval = liczbanowy-gtnext = NULLwsk-gtnext = nowy podczepiamy nowy element do ostatniego z listy

void wypisz_liste(el_listy lista)

el_listy wsk=listawhile( wsk = NULL )

printf (lun wsk-gtval)wsk = wsk-gtnext

int jest_pierwsza(el_listy lista int liczba)

el_listy wskwsk = firstwhile (wsk = NULL)

if ((liczbawsk-gtval)==0) return 0wsk = wsk-gtnext

return 1

int main ()

unsigned long i = 3 szukamy liczb pierwszych w zakresie od 3 do 1000 const unsigned long END = 1000first = malloc (sizeof(el_listy))first-gtval = 2first-gtnext = NULLfor (i=END++i)

if (jest_pierwsza(first i))dodaj_do_listy (first i)

wypisz_liste(first)return 0

Możemy jeszcze pomyśleć jak można by wykonać usuwanie elementu z listy Najprościej byłobyzrobić

wsk-gtnext = wsk-gtnext-gtnext

150 ROZDZIAŁ 19 TYPY ZŁOŻONE

ale wtedy element na ktoacutery wskazywał wcześniej wsk-gtnext przestaje być dostępny i zaśmieca pa-mięć Trzeba go usunąć Zauważmy że aby usunąć element potrzebujemy wskaźnika do elementu gopoprzedzającego (po to by nie rozerwać listy) Popatrzmy na poniższą funkcję

void usun_z_listy(el_listy lista int element)

el_listy wsk=listawhile (wsk-gtnext = NULL)

if (wsk-gtnext-gtval == element) musimy mieć wskaźnik do elementu poprzedzającego el_listy usuwany=wsk-gtnext zapamiętujemy usuwany element wsk-gtnext = usuwany-gtnext przestawiamy wskaźnik next by omijał usuwany element free(usuwany) usuwamy z pamięci else

wsk = wsk-gtnext idziemy dalej tylko wtedy kiedy nie usuwaliśmy bo nie chcemy zostawić duplikatoacutew

Funkcja ta jest tak napisana by usuwała z listy wszystkie wystąpienia danego elementu (w naszymprogramie nie ma to miejsca ale lista jest zrobiona tak że może trzymać dowolne liczby) Zauważmyże wskaźnik wsk jest przesuwany tylko wtedy gdy nie kasowaliśmy Gdybyśmy zawsze go przesuwaliprzegapilibyśmy element gdyby występował kilka razy pod rząd

Funkcja ta działa poprawnie tylko wtedy gdy nie chcemy usuwać pierwszego elementu Można topoprawić mdash dodając instrukcję warunkową do funkcji lub dodając do listy ldquogłowęrdquo mdash pierwszy elementnie przechowujący niczego ale upraszczający operacje na liście Zostawiamy to do samodzielnej pracy

Cały powyższy przykład omawiał tylko jeden przypadek listy mdash listę jednokierunkową Jednakistnieją jeszcze inne typy list np lista jednokierunkowa cykliczna lista dwukierunkowa oraz dwukie-runkowa cykliczna Roacuteżnią się one od siebie tylko tym że

w przypadku list dwukierunkowych mdashw strukturze el listy znajduje się jeszcze pole ktoacutere wska-zuje na element poprzedni

w przypadku list cyklicznych mdash ostatni element wskazuje na pierwszy (nie rozroacuteżnia się wtedyelementu pierwszego ani ostatniego)

Rozdział 20

Biblioteki

201 Czym jest bibliotekaBiblioteka jest to zbioacuter funkcji ktoacutere zostały wydzielone po to aby dało się z nich korzystać wwielu pro-gramach Ułatwia to programowanie mdash nie musimy np sami tworzyć funkcji printf Każda bibliotekaposiada swoje pliki nagłoacutewkowe ktoacutere zawierają deklaracje funkcji bibliotecznych oraz często zawartesą w nich komentarze jak używać danej funkcji W tej części podręcznika nauczymy się tworzyć naszewłasne biblioteki

202 Jak zbudowana jest bibliotekaKażda biblioteka składa się z co najmniej dwoacutech części

pliku nagłoacutewkowego z deklaracjami funkcji (plik z rozszerzeniem h)

pliku źroacutedłowego zawierającego ciała funkcji (plik z rozszerzeniem c)

2021 Budowa pliku nagłoacutewkowegoOto najprostszy możliwy plik nagłoacutewkowy

ifndef PLIK_Hdefine PLIK_H tutaj są wpisane deklaracje funkcji endif PLIK_H

Zapewne zapytasz się na co komu instrukcje ifndef define oraz endif Otoacuteż często się zdarzaże w programie korzystamy z plikoacutew nagłoacutewkowych ktoacutere dołączają się wzajemnie Oznaczałoby to żew kodzie programu kilka razy pojawiła by się zawartość tego samego pliku nagłoacutewkowego Instrukcjaifndef i define temu zapobiega Dzięki temu kompilator nie musi kilkakrotnie kompilować tegosamego kodu

W plikach nagłoacutewkowych często umieszcza się też definicje typoacutew z ktoacuterych korzysta bibliotekaalbo np makr

2022 Budowa najprostszej bibliotekiZałoacuteżmy że nasza biblioteka będzie zawierała jedną funkcję ktoacuterawypisuje na ekran tekst ldquoplWikibooksrdquoUtwoacuterzmy zatem nasz plik nagłoacutewkowy

151

152 ROZDZIAŁ 20 BIBLIOTEKI

ifndef WIKI_Hdefine WIKI_Hvoid wiki (void)endif

Należy pamiętać o podaniu void w liście argumentoacutew funkcji nie przyjmujących argumentoacutew Oile przy definicji funkcji nie trzeba tego robić (jak to często czyniliśmy w przypadku funkcji main) otyle w prototypie brak słoacutewka void oznacza że w prototypie nie ma informacji na temat tego jakieargumenty funkcja przyjmuje

Plik nagłoacutewkowy zapisujemy jako ldquowikihrdquo Teraz napiszmy ciało tej funkcji

include wikihinclude ltstdiohgt

void wiki (void)

printf (plWikibooksn)

Ważne jest dołączenie na początku pliku nagłoacutewkowego Dlaczego Plik nagłoacutewkowy zawieradeklaracje naszych funkcji mdash jeśli popełniliśmy błąd i deklaracja nie zgadza się z definicją kompilatorod razu nas o tym powiadomi Oproacutecz tego plik nagłoacutewkowy może zawierać definicje istotnych typoacutewlub makr

Zapiszmy naszą bibliotekę jako plik ldquowikicrdquo Teraz należy ją skompilować Robi się to trochę ina-czej niż normalny program Należy po prostu do opcji kompilatora gcc dodać opcję ldquo-crdquo

gcc wikic -c -o wikio

Rozszerzenie ldquoordquo jest domyślnym rozszerzeniem dla bibliotek statycznych (typowych bibliotek łą-czonych z resztą programu na etapie kompilacji) Teraz możemy spokojnie skorzystać z naszej nowejbiblioteki Napiszmy nasz program

include wikih

int main ()

wiki()return 0

Zapiszmy program jako ldquomaincrdquo Teraz musimy odpowiednio skompilować nasz program

gcc mainc wikio -o main

Uruchamiamy nasz program

mainplWikibooks

Jak widać nasza pierwsza biblioteka działaZauważmy że kompilatorowi podajemy i pliki z kodem źroacutedłowym (mainc) i pliki ze skompilo-

wanymi bibliotekami (wikio) by uzyskać plik wykonywalny (main) Jeśli nie podalibyśmy plikoacutew zbibliotekami mainc co prawda skompilowałby się ale błąd zostałby zgłoszony przez linker mdash częśćkompilatora odpowiedzialna za wstawienie w miejsce wywołań funkcji ich adresoacutew (takiego adresulinker nie moacutegłby znaleźć)

202 JAK ZBUDOWANA JEST BIBLIOTEKA 153

2023 Zmiana dostępu do funkcji i zmienny (static i extern)Język C w przeciwieństwie do swego młodszego krewnego mdash C++ nie posiada praktycznie żadnychmechanizmoacutew ochrony kodu biblioteki przed modyfikacjami C++ ma w swoim asortymencie minsterowanie uprawnieniami roacuteżnych elementoacutew klasy Jednak programista piszący program w C niejest tak do końca bezradny Autorzy C dali mu do ręki dwa narzędzia extern oraz static Pierwsze ztych słoacutew kluczowych informuje kompilator że dana funkcja lub zmienna istnieje ale w innymmiejscui zostanie dołączona do kodu programu w czasie łączenia go z biblioteką

extern przydaje się gdy zmienna lub funkcja jest zadeklarowana w bibliotece ale nie jest udostęp-niona na zewnątrz (nie pojawia się w pliku nagłoacutewkowym) Przykładowo

bibliotekah extern char zmienna_dzielona[]

bibliotekac include bibliotekah

char zmienna_dzielona[] = Zawartosc

mainc include ltstdiohgtinclude bibliotekah

int main()

printf(sn zmienna_dzielona)return 0

Gdybyśmy tu nie zastosowali extern kompilator (nie linker) zaprotestowałby że nie zna zmiennejzmienna dzielona Proacuteba dopisania deklaracji char zmienna dzielona stworzyłaby nową zmienną iutracilibyśmy dostęp do interesującej nas zawartości

Odwrotne działanie ma słowo kluczowe static użyte w tym kontekście (użyte wewnątrz bloku two-rzy zmienną statyczną więcej informacji w rozdziale Zmienne) Może ono odnosić się zaroacutewno dozmiennych jak i do funkcji globalnych Powoduje że dana zmienna lub funkcja jest niedostępna nazewnątrz biblioteki1 Możemy dzięki temu ukryć np funkcje ktoacutere używane są przez samą bibliotekęby nie dało się ich wykorzystać przez extern

1Tak naprawdę całe ldquoukrycierdquo funkcji polega na zmianie niektoacuterych danych w pliku z kodem binarnym danejbiblioteki (pliku o) przez co linker powoduje wygenerowanie komunikatu o błędzie w czasie łączenia biblioteki zprogramem

154 ROZDZIAŁ 20 BIBLIOTEKI

Rozdział 21

Więcej o kompilowaniu

211 Ciekawe opcje kompilatora Emdash powoduje wygenerowanie kodu programu ze zmianami wprowadzonymi przez preprocesor

S mdash zamiana kodu w języku C na kod asemblera (komenda gcc -S plikc spowoduje utworzeniepliku o nazwie pliks w ktoacuterym znajdzie się kod asemblera)

c mdash kompilacja bez łączenia z bibliotekami

Ikatalog mdash ustawienie domyślnego katalogu z plikami nagłoacutewkowymi na katalog

lbiblioteka mdash wymusza łączenie programu z podaną biblioteką (np -lGL)

212 Program makeDość często może się zdarzyć że nasz program składa się z kilku plikoacutew źroacutedłowych Jeśli tych plikoacutewjest mało (np -) możemy jeszcze proacutebować ręcznie kompilować każdy z nich Jednak jeśli tych plikoacutewjest dużo lub chcemy pokazać nasz program innym użytkownikom musimy stworzyć elegancki sposoacutebkompilacji naszego programu Właśnie po to aby zautomatyzować proces kompilacji powstał programmake Program make analizuje pliki Makefile i na ich podstawie wykonuje określone czynności

2121 Budowa pliku MakefileUwaga poniżej został omoacutewiony Makefile dla Make Istnieją inne programy make i mogą używaćinnej składni Na Wikibooks został też obszernie opisany program make firmy Borland

Najważniejszym elementem pliku Makefile są zależności oraz reguły przetwarzania Zależnościpolegają na tym że np jeśli nasz program ma być zbudowany z plikoacutew to najpierw należy skom-pilować każdy z tych plikoacutew a dopiero poacuteźniej połączyć je w jeden cały program Zatem zależnościokreślają kolejność wykonywanych czynności Natomiast reguły określają jak skompilować dany plikZależności tworzy się tak

co od_czegoreguły

Dzięki temu program make zna już kolejność wykonywanych działań oraz czynności jakie ma wy-konać Aby zbudować ldquocordquo należy wykonać polecenie make co Pierwsza reguła w pliku Makefile jestregułą domyślną Jeśli wydamy polecenie make bez parametroacutew zostanie zbudowana właśnie reguładomyślna Tak więc dobrze jest jako pierwszą regułę wstawić regułę budującą końcowy plik wykony-walny zwyczajowo regułę tą nazywa się all

155

156 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Należy pamiętać by sekcji ldquocordquo niewcinać natomiast ldquoregułyrdquowcinać tabulatorem Część ldquood czegordquomoże być pusta

Plik Makefile umożliwia też definiowanie pewnych zmiennych Nie trzeba tutaj się już troszczyć otyp zmiennej wystarczy napisać

nazwa_zmiennej = wartość

Wten sposoacutebmożemy zadeklarować dowolnie dużo zmiennych Zmiennemogą być roacuteżnemdash nazwakompilatora jego parametry iwiele innych Zmiennej używamywnastępujący sposoacuteb $(nazwa zmiennej)

Komentarze w pliku Makefile tworzymy zaczynając linię od znaku hash ()

2122 Przykładowy plik MakefileDość tej teorii teraz zajmiemy się działającym przykładem Załoacuteżmy że nasz przykładowy programnazywa się test oraz składa się z czterech plikoacutew pierwszyc drugic trzecic czwartyc

Odpowiedni plik Makefile powinien wyglądać mniej więcej tak

Moacutej plik makefile - wpisz make all aby skompilować cały program (właściwie wystarczy wpisać make - all jest domyślny jako pierwszy cel)CC = gcc

all pierwszyo drugio trzecio czwartyo$(CC) pierwszyo drugio trzecio czwartyo -o test

pierwszyo pierwszyc$(CC) pierwszyc -c -o pierwszyo

drugio drugic$(CC) drugic -c -o drugio

trzecio trzecic$(CC) trzecic -c -o trzecio

czwartyo czwartyc$(CC) czwartyc -c -o czwartyo

Widzimy że nasz program zależy od plikoacutew z rozszerzeniem o (pierwszyo itd) potem każdy ztych plikoacutew zależy od plikoacutew c ktoacutere program make skompiluje w pierwszej kolejności a następniepołączy w jeden program (test) Nazwę kompilatora zapisaliśmy jako zmienną ponieważ powtarza sięi zmienna jest sposobem by zmienić ją wszędzie za jednym zamachem

Zatem jak widać używanie pliku Makefile jest bardzo proste Warto na koniec naszego przykładudodać regułę ktoacutera wyczyści katalog z plikoacutew o

cleanrm -f o test

Ta reguła spowoduje usunięcie wszystkich plikoacutew o oraz naszego programu jeśli napiszemy makeclean

Możemy też ukryć wykonywane komendy albo dopisać własny opis czynności

cleanecho Usuwam gotowe plikirm -f o test

Ten sam plik Makefile moacutegłby wyglądać inaczej

213 OPTYMALIZACJE 157

CFLAGS = -g -O tutaj można dodawać inne flagi kompilatoraLIBS = -lm tutaj można dodawać biblioteki

OBJ =pierwszyo drugio trzecio czwartyo

all main

cleanrm -f o test

co$(CC) -c $(INCLUDES) $(CFLAGS) $lt

main $(OBJ)$(CC) $(OBJ) $(LIBS) -o test

Tak naprawdę jest to dopiero bardzo podstawowe wprowadzenie do używania programu makejednak jest ono wystarczające byś zaczął z niego korzystać Wyczerpujące omoacutewienie całego programuniestety przekracza zakres tego podręcznika

213 OptymalizacjeKompilator umożliwia generację kodu zoptymalizowanego dla konkretnej architektury Służą dotego opcje -mar= i -mtune= Stopień optymalizacji ustalamy za pomocą opcji -Ox gdzie x jest nume-rem stopnia optymalizacji (od do ) Możliwe jest też użycie opcji -Os ktoacutera powoduje generowaniekodu o jak najmniejszym rozmiarze Aby skompilować dany plik z optymalizacjami dla procesora Ath-lon należy napisać tak

gcc programc -o program -march=athlon-xp -O3

Z optymalizacjami należy uważać gdyż często zdarza się że kod skompilowany bez optymalizacjidziała zupełnie inaczej niż ten ktoacutery został skompilowany z optymalizacjami

2131 WyroacutewnywanieWyroacutewnywanie jest pewnym zjawiskiem na ktoacutere w bardzo wielu podręcznikach moacutewiących o Cw ogoacutele się nie wspomina Ten rozdział ma za zadanie wyjaśnienie tego zjawiska oraz uprzedzenieprogramisty o pewnych faktach ktoacutere w poacuteźniejszej jego ldquotwoacuterczościrdquo mogą zminimalizować czas naznalezienie pewnych informacji ktoacutere mogą wpływać na to że jego program nie będzie działał popraw-nie

Często zdarza się że kompilator w ramach optymalizacji ldquowyroacutewnujerdquo elementy struktury tak abyprocesor moacutegł łatwiej odczytać i przetworzyć dane Przyjrzyjmy się bliżej następującemu fragmentowikodu

typedef struct unsigned char wiek 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

158 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Aby procesor moacutegł łatwiej przetworzyć dane kompilator może dodać do tej struktury jedno ośmio-bitowe pole Wtedy struktura będzie wyglądała tak

typedef struct unsigned char wiek 8 bitoacutew unsigned char fill[1] 8 bitoacutew unsigned short dochod 16 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

Wtedy rozmiar zmiennych przechowujących wiek płeć oraz dochoacuted będzie wynosił bity mdashbędzie zatem potęgą liczby dwa i procesorowi dużo łatwiej będzie tak ułożoną strukturę przechowywaćw pamięci cache Jednak taka sytuacja nie zawsze jest pożądana Może się okazać że nasza strukturamusi odzwierciedlać np pojedynczy pakiet danych przesyłanych przez sieć Nie może być w niejzatem żadnych innych poacutel poza tymi ktoacutere są istotne do transmisji Aby wymusić na kompilatorzewyroacutewnanie -bajtowe (co w praktyce wyłącza je) należy przed definicją struktury dodać dwie linijkiTen kod działa pod Visual C++

pragma pack(push)pragma pack(1)

struct struktura

pragma pack(pop)

W kompilatorze należy po deklaracji struktury dodajemy przed średnikiem kończącym jednąlinijkę

__attribute__ ((packed))

Działa ona dokładnie tak samo jak makra pragma jednak jest ona obecna tylko w kompilatorze

Dzięki użyciu tego atrybutu kompilator zostanie ldquozmuszonyrdquo do braku ingerencji w naszą strukturęJest jednak jeszcze jeden być może bardziej elegancki sposoacuteb na obejście dopełniania Zauważyłeś żedopełnienie dodane przez kompilator pojawiło się między polem o długości bitoacutew (plec) oraz polem odługości bitoacutew (dochod) Wyroacutewnywanie polega na tym że dana zmienna powinna być umieszczonapod adresem będącym wielokrotnością jej rozmiaru Oznacza to że jeśli np mamy w strukturze napoczątku dwie zmienne o rozmiarze jednego bajta a potem jedną zmienną o rozmiarze bajtoacutew topomiędzy polami o rozmiarze bajtoacutew a polem czterobajtowym pojawi się dwubajtowe dopełnienieMoże Ci się wydawać że jest to tylko niepotrzebne mącenie w głowie jednak niektoacutere architektury(zwłaszcza typu ) mogą nie wykonać kodu ktoacutery nie został wyroacutewnany Dlatego naszą strukturępowinniśmy zapisać mniej więcej tak

typedef struct unsigned short dochod 16 bitoacutew unsigned char wiek 8 bitoacutew unsigned char plec 8 bitoacutew

nasza_str

W ten sposoacuteb wyroacutewnana struktura nie będzie podlegała modyfikacjom przez kompilator oraz bę-dzie przenośna pomiędzy roacuteżnymi kompilatorami

Wyroacutewnywanie działa także na pojedynczych zmiennych w programie jednak ten problem nie po-woduje tyle zamieszania co ingerencja kompilatora w układ poacutel struktury Wyroacutewnywanie zmiennychpolega tylko na tym że kompilator umieszcza je pod adresami ktoacutere są wielokrotnością ich rozmiaru

214 KOMPILACJA KRZYŻOWA 159

214 Kompilacja krzyżowaMając w domu dwa komputery o odmiennych architekturach (np i oraz Sparc) możemy potrze-bować stworzyć program dla jednej maszyny mając do dyspozycji tylko drugi komputer Nie musimywtedy latać do znajomego posiadającego odpowiedni sprzęt Możemy skorzystać z tzw kompilacjikrzyżowej (ang cross-compile) Polega ona na tym że program nie jest kompilowany pod procesorna ktoacuterym działa kompilator lecz na inną zdefiniowaną wcześniej maszynę Efekt będzie taki sam askompilowany program możemy bez problemu uruchomić na drugim komputerze

215 Inne narzędziaWśroacuted przydatnych narzędzi warto wymienić roacutewnież program objdump (zaroacutewno pod Unix jak ipod Windows) oraz readelf (tylko Unix) Objdump służy do deasemblacji i analizy skompilowanychprogramoacutew Readelf służy do analizy pliku wykonywalnego w formacie (używanego w większościsystemoacutew z rodziny Unix) Więcej informacji możesz uzyskać pisząc (w systemach Unix)

man 1 objdumpman 1 readelf

160 ROZDZIAŁ 21 WIĘCEJ O KOMPILOWANIU

Rozdział 22

Zaawansowane operacjematematyczne

221 Biblioteka matematycznaAby moacutec korzystać z wszystkich dobrodziejstw funkcji matematycznych musimy na początku dołączyćplik mathh

include ltmathhgt

A w procesie kompilacji (dotyczy kompilatora GCC) musimy niekiedy dodać flagę ldquo-lmrdquo

gcc plikc -o plik -lm

Funkcje matematyczne ktoacutere znajdują się w bibliotece standardowej możesz znaleźć tutaj Przykorzystaniu z nich musisz wziąć pod uwagę min to że biblioteka matematyczna prowadzi kalkulacjęw oparciu o radiany a nie stopnie

2211 Stałe matematyczneW pliku mathh zdefiniowane są pewne stałe ktoacutere mogą być przydatne do obliczeń Są to min

M E mdash podstawa logarytmu naturalnego (e liczba Eulera)

M LOG2E mdash logarytm o podstawie z liczby e

M LOG10E mdash logarytm o podstawie z liczby e

M LN2 mdash logarytm naturalny z liczby

M LN10 mdash logarytm naturalny z liczby

M PI mdash liczba π

M PI 2 mdash liczba π

M PI 4 mdash liczba π

M 1 PI mdash liczba π

M 2 PI mdash liczba π

161

162 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

222 Prezentacja liczb rzeczywisty w pamięci komputeraByć może ten temat może wydać Ci się niepotrzebnym lecz w wielu książkach nie ma w ogoacutele tegotematu Dzięki niemu zrozumiesz jak komputer radzi sobie z przecinkiem oraz dlaczego niektoacutere ob-liczenia dają niezbyt dokładne wyniki Na początek trochę teorii do przechowywania liczb rzeczywi-stych przeznaczone są typy float double oraz long double Zajmują one odpowiednio oraz bitoacutew Wiemy też że komputer nie ma fizycznej możliwości zapisania przecinka Sproacutebujmy terazzapisać jakąś liczbę wymierną w formie liczb binarnych Nasza liczba to powiedzmy 425 Sproacutebujmy jąrozbić na sumę potęg dwoacutejki 4 = 1 middot22 +0 middot21 +0 middot20 Dobra mdash rozpisaliśmy liczbę 4 ale co z częściądziesiętną Skorzystajmy z zasad matematyki 025 = 2minus2 Zatem nasza liczba powinna wyglądaćtak

10001Ponieważ komputer nie jest w stanie przechować pozycji przecinka ktoś wpadł na prosty ale

sprytny pomysł ustawienia przecinka jak najbliżej początku liczby i tylko mnożenia jej przez odpowied-nią potęgę dwoacutejki Taki sposoacuteb przechowywania liczb nazywamy zmiennoprzecinkowym a procesprzekształcania naszej liczby z postaci czytelnej przez człowieka na format zmiennoprzecinkowy na-zywamy normalizacją Wroacutećmy do naszej liczby mdash 425 W postaci binarnej wygląda ona tak 10001natomiast po normalizacji będzie wyglądała tak 10001 middot 22 W ten sposoacuteb w pamięci komputeraznajdą się dwie informacje liczba zakodowana w pamięci z ldquowirtualnymrdquo przecinkiem oraz numerpotęgi dwoacutejki Te dwie informacje wystarczają do przechowania wartości liczby Jednak pojawia sięinny problem mdash co się stanie jeśli np będziemy chcieli przełożyć liczbę typu 1

3 Otoacuteż tutaj wychodzą

na wierzch pewne niedociągnięcia komputera w dziedzinie samej matematyki daje w rozwinięciudziesiętnym 0(3) Jak zatem zapisać taką liczbę Otoacuteż nie możemy przechować całego jej rozwinięcia(wynika to z ograniczeń typu danych mdash ma on niestety skończoną liczbę bitoacutew) Dlatego przechowujesię tylko pewne przybliżenie liczby Jest ono tym bardziej dokładne im dany typ ma więcej bitoacutew Za-tem do obliczeń wymagających dokładnych danych powinniśmy użyć typu double lub long double Naszczęście w większości przeciętnych programoacutew tego typu problemy zwykle nie występują A ponie-waż początkujący programista nie odpowiada za tworzenie programoacutew sterujących np lotem statkukosmicznego więc drobne przekłamania na odległych miejscach po przecinku nie stanowią większegoproblemu

Należy brać pod uwagę że w komputerze liczby rzeczywiste nie są tym samym czym w mate-matyce Komputery nie potrafią przechować każdej liczby zmiennoprzecinkowej w związku z tymobliczenia prowadzone przy użyciu komputera mogą być niedokładne i odbiegać od prawidłowych wy-nikoacutew Szczegoacutelnie ważne jest to przy programowaniu aplikacji inżynieryjnych oraz w medycyniegdzie takie błędy mogą skutkować katastrofą ilub narażeniem ludzkiego życia oraz zdrowia

Na ile poważny jest to problem Sproacutebujmy przyjrzeć się działaniu polegającym na -krotnymdodawaniu do liczby wartości Oto kod

include ltstdiohgt

int main ()

float a = 0int i = 0for (ilt1000i++)

a += 1030printf (fn a)

Z matematyki wynika że 1000 middot 13

= 333(3) podczas gdy komputer wypisze wynik nieco roacuteżniącysię od oczekiwanego (w moim przypadku)

223 LICZBY ZESPOLONE 163

333334106

Błąd pojawił się na cyfrze części tysięcznej liczby Nie jest to może poważny błąd jednak zastanoacutewmysię czy ten błąd nie będzie się powiększał Zamieniamy w kodzie ilość iteracji z na Tymrazem moacutej komputer wskazał już nieco inny wynik

33356554688

Błąd przesunął się na cyfrę dziesiątek w liczbie Tak więc nie należy do końca polegać na prezentacjiliczb zmiennoprzecinkowych w komputerze

223 Liczby zespoloneOperacje na liczba zespolony są częścią uaktualnionego standardu języka C o nazwie C ktoacuteryjest obsługiwany jedynie przez część kompilatoroacutew

Podane tutaj informacje zostały sprawdzone na systemie Gentoo Linux z biblioteką GNU libc wwersji i kompilatorem GCC w wersji

Dotychczas korzystaliśmy tylko z liczb rzeczywistych lecz najnowsze standardy języka C umożli-wiają korzystanie także z innych liczb mdash np z liczb zespolonych

Abymoacutec korzystać z liczb zespolonychwnaszymprogramie należywnagłoacutewku programu umieścićnastępującą linijkę

include ltcomplexhgt

Wiemy że liczba zespolona zdeklarowana jest następująco

z = a+bi gdzie a b są liczbami rzeczywistymi a ii=(-1)

W pliku complexh liczba i zdefiniowana jest jako I Zatem wyproacutebujmy możliwości liczb zespolo-nych

include ltmathhgtinclude ltcomplexhgtinclude ltstdiohgt

int main ()

float _Complex z = 4+25Iprintf (Liczba z f+fin creal(z) cimag (z))return 0

następnie kompilujemy nasz program

gcc plik1c -o plik1 -lm

Po wykonaniu naszego programu powinniśmy otrzymać

Liczba z 400+250i

W programie zamieszczonym powyżej użyliśmy dwoacutech funkcji mdash creal i cimag

creal mdash zwraca część rzeczywistą liczby zespolonej

cimag mdash zwraca część urojoną liczby zespolonej

164 ROZDZIAŁ 22 ZAAWANSOWANE OPERACJE MATEMATYCZNE

Rozdział 23

Powszene praktyki

Rozdział tenma za zadanie pokazać powszechnie stosowanemetody programowania wC Nie będziemytu uczyć jak należy stawiać nawiasy klamrowe ani ktoacutery sposoacuteb nazewnictwa zmiennych jest najlep-szy mdash prowadzone są o to spory z ktoacuterych niewiele wynika Zaprezentowane tu rozwiązania mająkonkretny wpływ na jakość tworzonych programoacutew

231 Konstruktory i destruktoryW większości obiektowych językoacutew programowania obiekty nie mogą być tworzone bezpośrednio mdashobiekty otrzymuje się wywołując specjalną metodę danej klasy zwaną konstruktorem Konstruktorysą ważne ponieważ pozwalają zapewnić obiektowi odpowiedni stan początkowy Destruktory wywo-ływane na końcu czasu życia obiektu są istotne gdy obiekt ma wyłączny dostęp do pewnych zasoboacutewi konieczne jest upewnienie się czy te zasoby zostaną zwolnione

Ponieważ C nie jest językiem obiektowym nie ma wbudowanego wsparcia dla konstruktoroacutew idestruktoroacutew Często programiści bezpośrednio modyfikują tworzone obiekty i struktury Jednakżeprowadzi to do potencjalnych błędoacutew ponieważ operacje na obiekcie mogą się nie powieść lub zacho-wać się nieprzewidywalnie jeśli obiekt nie został prawidłowo zainicjalizowany Lepszym podejściemjest stworzenie funkcji ktoacutera tworzy instancję obiektu ewentualnie przyjmując pewne parametry

struct string size_t sizechar data

struct string create_string(const char initial) assert (initial = NULL)struct string new_string = malloc(sizeof(new_string))if (new_string = NULL)

new_string-gtsize = strlen(initial)new_string-gtdata = strdup(initial)

return new_string

Podobnie bezpośrednie usuwanie obiektoacutew może nie do końca się udać prowadząc do wyciekuzasoboacutew Lepiej jest użyć destruktora

void free_string(struct string s)

165

166 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

assert (s = NULL)free(s-gtdata) zwalniamy pamięć zajmowaną przez strukturę free(s) usuwamy samą strukturę

Często łączy się destruktory z zerowaniem zwolnionych wskaźnikoacutewCzasami dobrze jest ukryć definicję obiektu żeby mieć pewność że użytkownicy nie utworzą go

ręcznie Aby to zapewnić struktura jest definiowanaw pliku źroacutedłowym (lub prywatnymnagłoacutewku nie-dostępnym dla użytkownikoacutew) zamiast w pliku nagłoacutewkowym a deklaracja wyprzedzająca jest umiesz-czona w pliku nagłoacutewkowym

struct stringstruct string create_string(const char initial)void free_string(struct string s)

232 Zerowanie zwolniony wskaźnikoacutewJak powiedziano już wcześniej po wywołaniu free() dla wskaźnika staje się on ldquowiszącym wskaź-nikiemrdquo Co gorsze większość nowoczesnych platform nie potrafi wykryć kiedy taki wskaźnik jestużywany zanim zostanie ponownie przypisany

Jednym z prostych rozwiązań tego problemu jest zapewnienie że każdy wskaźnik jest zerowanynatychmiast po zwolnieniu

free(p)p = NULL

Inaczej niż w przypadku ldquowiszących wskaźnikoacutewrdquo na wielu nowoczesnych architekturach przyproacutebie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek Dodatkowo programy mogązawierać sprawdzanie błędoacutew dla zerowych wartości ale nie dla ldquowiszących wskaźnikoacutewrdquo Aby zapew-nić że jest to wykonywane dla każdego wskaźnika możemy użyć makra

define FREE(p) do free(p) (p) = NULL while(0)

(aby zobaczyć dlaczego makro jest napisane w ten sposoacuteb zobacz Konwencje pisania makr)Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik ktoacutery przekazuje się do

nich więc argument musi być do nich przekazywany przez referencję Na przykład oto zaktualizowanydestruktor z sekcji Konstruktory i destruktory

void free_string(struct string s)

assert(s = NULL ampamp s = NULL)FREE((s)-gtdata) zwalniamy pamięć zajmowaną przez strukturę FREE(s) usuwamy strukturę

Niestety ten idiom nie jest wstanie pomoacutec wwypadkuwskazywania przez inne wskaźniki zwolnio-nej pamięci Z tego powodu niektoacuterzy eksperci C uważają go za niebezpieczny jako kreujący fałszywepoczucie bezpieczeństwa

233 Konwencje pisania makrPonieważ makra preprocesora działają na zasadzie zwykłego zastępowania napisoacutew są podatne nawiele kłopotliwych błędoacutew z ktoacuterych części można uniknąć przez stosowanie się do poniższych reguł

234 JAK DOSTAĆ SIĘ DO KONKRETNEGO BITU 167

Umieszczaj nawiasy dookoła argumentoacutew makra kiedy to tylko możliwe Zapewnia to że gdysą wyrażeniami kolejność działań nie zostanie zmieniona Na przykład

Źle define kwadrat(x) (xx)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Załoacuteżmy że w programie makro kwadrat() zdefiniowane bez nawiasoacutew zostałowywołane następująco kwadrat(a+b) Wtedy zostanie ono zamienione przez preprocesorna (a+ba+b) Z kolejności działań wiemy że najpierw zostanie wykonane mnożeniewięc wartość wyrażenia kwadrat(a+b) będzie roacuteżna od kwadratu wyrażenia a+b

Umieszczaj nawiasy dookoła całegomakra jeśli jest pojedynczymwyrażeniem Ponownie chronito przed zaburzeniem kolejności działań

Źle define kwadrat(x) (x)(x)

Dobrze define kwadrat(x) ( (x)(x) )

Przykład Definiujemy makro define suma(a b) (a)+(b) i wywołujemy je w kodziewynik = suma(3 4) 5 Makro zostanie rozwinięte jako wynik = (3)+(4)5 co mdash zpowodu kolejności działań mdash da wynik inny niż pożądany

Jeśli makro składa się z wielu instrukcji lub deklaruje zmienne powinno być umieszczone w pętlido while(0) bez kończącego średnika Pozwala to na użycie makra jak pojedynczejinstrukcji w każdym miejscu jak ciało innego wyrażenia pozwalając jednocześnie na umiesz-czenie średnika po makrze bez tworzenia zerowego wyrażenia Należy uważać by zmienne wmakrze potencjalnie nie kolidowały z argumentami makra

Źle define FREE(p) free(p) p = NULL

Dobrze define FREE(p) do free(p) p = NULL while(0)

Unikaj używania argumentoacutew makra więcej niż raz wewnątrz makra Może to spowodowaćkłopoty gdy argument makra ma efekty uboczne (np zawiera operator inkrementacji)

Przykład define kwadrat(x) ((x)(x)) nie powinno być wywoływane z operatoreminkrementacji kwadrat(a++) ponieważ zostanie to rozwinięte jako ((a++) (a++)) co jestniezgodne ze specyfikacją języka i zachowanie takiego wyrażenia jest niezdefiniowane(dwukrotna inkrementacja w tym samym wyrażeniu)

Jeśli makro może być w przyszłości zastąpione przez funkcję rozważ użycie w nazwie małychliter jak w funkcji

234 Jak dostać się do konkretnego bituWiemy że komputer to maszyna ktoacuterej najmniejszą jednostką pamięci jest bit jednak w C najmniejszazmienna ma rozmiar bitoacutew (czyli jednego bajtu) Jak zatem można odczytać wartość pojedynczychbitoacutew W bardzo prosty sposoacuteb mdashw zestawie operatoroacutew języka C znajdują się tzw operatory bitoweSą to m in

amp mdash logiczne ldquoirdquo

| mdash logiczne ldquolubrdquo

˜ mdash logiczne ldquonierdquo

Oproacutecz tego są także przesunięcia (ltlt oraz gtgt) Zastanoacutewmy się teraz jak je wykorzystać w prak-tyce Załoacuteżmy że zajmujemy się jednobajtową zmienną

unsigned char i = 2

Zmatematyki wiemy że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej) Jeśli teraz np chcielibyśmy ldquozapalićrdquo drugi bit od lewej (tj bit ktoacuterego zapalenie niejako ldquododardquo doliczby wartość 6) powinniśmy użyć logicznego lub

168 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

unsigned char i = 2i |= 64

Gdzie =6 Odczytywanie wykonuje się za pomocą tzw maski bitowej Polega to na

wyzerowaniu bitoacutew ktoacutere są nam w danej chwili niepotrzebne

odpowiedniemu przesunięciu bitoacutew dzięki czemu szukany bit znajdzie się na pozycji pierwszegobitu od prawej

Do ldquowyłuskaniardquo odpowiedniego bitu możemy posłużyć się operacją ldquoirdquo mdash czyli operatorem ampWygląda to analogicznie do posługiwania się operatorem ldquolubrdquo

unsigned char i = 3 bitowo 00000011 unsigned char temp = 0temp = i amp 1 sprawdzamy najmniej znaczący bit - czyli pierwszy z prawej if (temp)

printf (bit zapalony)else

printf (bit zgaszony)

Jeśli nie władasz biegle kodem binarnym tworzenie masek bitowych ułatwią ci przesunięcia bitoweAby uzyskać liczbę ktoacutera ma zapalony bit o numerze n (bity są liczone od zera) przesuwamy bitowo wlewo jedynkę o n pozycji

1 ltlt n

Jeśli chcemy uzyskać liczbę w ktoacuterej zapalone są bity na pozycjach l m n mdash używamy sumylogicznej (ldquolubrdquo)

(1 ltlt l) | (1 ltlt m) | (1 ltlt n)

Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone sąwszystkie bity poza n odwracamy ją za pomocąoperatora logicznej negacji

~(1 ltlt n)

Warto władać biegle operacjami na bitach ale początkujący mogą (po uprzednim przeanalizowa-niu) zdefiniować następujące makra i ich używać

Sprawdzenie czy w liczbie k jest zapalony bit n define IS_BIT_SET(k n) ((k) amp (1 ltlt (n)))

Zapalenie bitu n w zmiennej k define SET_BIT(k n) (k |= (1 ltlt (n)))

Zgaszenie bitu n w zmiennej k define RESET_BIT(k n) (k amp= ~(1 ltlt (n)))

235 Skroacutety notacjiIstnieją pewne sposoby ograniczenia ilości niepotrzebnego kodu Przykładem może być wykonywaniejednej operacji w razie wystąpienia jakiegoś warunku np zamiast pisać

if (warunek) printf (Warunek prawdziwyn)

235 SKROacuteTY NOTACJI 169

możesz skroacutecić notację do

if (warunek)printf (Warunek prawdziwyn)

Podobnie jest w przypadku pętli for

for (warunek)printf (Wyświetlam się w pętlin)

Niestety ograniczeniemw tymwypadku jest to że można w ten sposoacuteb zapisać tylko jedną instruk-cję

170 ROZDZIAŁ 23 POWSZECHNE PRAKTYKI

Rozdział 24

Przenośność programoacutew

Jak dowiedziałeś się z poprzednich rozdziałoacutew tego podręcznika język C umożliwia tworzenie progra-moacutew ktoacutere mogą być uruchamiane na roacuteżnych platformach sprzętowych pod warunkiem ich powtoacuter-nej kompilacji Język C należy do grupy językoacutew wysokiego poziomu ktoacutere tłumaczone są do poziomukodumaszynowego (tzn kod źroacutedłowy jest kompilowany) Z jednej strony jest to korzystne posunięciegdyż programy są szybsze i mniejsze niż programy napisane w językach interpretowanych (takich wktoacuterych kod źroacutedłowy nie jest kompilowany do kodu maszynowego tylko na bieżąco interpretowanyprzez tzw interpreter) Jednak istnieje także druga strona medalu mdash pewne zawiłości sprzętu ktoacutereograniczają przenośność programoacutew Ten rozdział ma wyjaśnić Ci mechanizmy działania sprzętu wtaki sposoacuteb abyś bez problemu moacutegł tworzyć poprawne i całkowicie przenośne programy

241 Niezdefiniowane zaowanie i zaowanie zależne odimplementacji

Wtrakcie czytania kolejnych rozdziałoacutewmożna było się natknąć na zwroty takie jak zachowanie niezde-finiowane (ang undefined behaviour) czy zachowanie zależne od implementacji (ang implementation-defined behaviour) Coacuteż one tak właściwie oznaczają

Zacznijmy od tego drugiego Autorzy standardu języka C czuli że wymuszanie jakiegoś konkret-nego działania danego wyrażenia byłoby zbytnim obciążeniem dla osoacuteb piszących kompilatory gdyżdany wymoacuteg moacutegłby być bardzo trudny do zrealizowania na konkretnej architekturze Dla przykładugdyby standard wymagał że typ unsigned char ma dokładnie bitoacutew to napisanie kompilatora dla ar-chitektury na ktoacuterej bajt ma bitoacutew byłoby cokolwiek kłopotliwe a z pewnością wynikowy programdziałałby o wiele wolniej niżby to było możliwe

Z tego właśnie powodu niektoacutere aspekty języka nie są określone bezpośrednio w standardzie i sąpozostawione do decyzji zespołu (osoby) piszącego konkretną implementację W ten sposoacuteb nie mażadnych przeciwwskazań (ze strony standardu) aby na architekturze gdzie bajty mają bitoacutew typchar roacutewnież miał tyle bitoacutew Dokonany wyboacuter musi być jednak opisany w dokumentacji kompilatoratak żeby osoba pisząca program w C mogła sprawdzić jak dana konstrukcja zadziała

Należy zatem pamiętać że poleganie na jakimś konkretnym działaniu programu w przypadkachzachowania zależnego od implementacji drastycznie zmniejsza przenośność kodu źroacutedłowego

Zachowania niezdefiniowane są o wiele groźniejsze gdyż zaistnienie takowego może spowodo-wać dowolny efekt ktoacutery nie musi być nigdzie udokumentowany Przykładem może tutaj być proacutebaodwołania się do wartości wskazywanej przez wskaźnik o wartości

Jeżeli gdzieś w naszym programie zaistnieje sytuacja niezdefiniowanego zachowania to nie jest jużto kwestia przenośności kodu ale po prostu błędu w kodzie chyba że świadomie korzystamy z roz-szerzenia naszego kompilatora Rozważmy odwoływanie się do wartości wskazywanej przez wskaźniko wartości Ponieważ według standardu operacja taka ma niezdefiniowany skutek to w szcze-goacutelności może wywołać jakąś z goacutery określoną funkcję mdash kompilator może coś takiego zrealizować

171

172 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

sprawdzając wartość wskaźnika przed każdą dereferencją w ten sposoacuteb niezdefiniowane zachowaniedla konkretnego kompilatora stanie się jak najbardziej zdefiniowane

Sytuacją wziętą z życia są operatory przesunięć bitowych gdy działają na liczbach ze znakiemKonkretnie przesuwanie w lewo liczb jest dla wielu przypadkoacutew niezdefiniowane Bardzo często jed-nak w dokumentacji kompilatora działanie przesunięć bitowych jest dokładnie opisane Jest to o tyleinteresujący fakt iż wielu programistoacutew nie zdaje sobie z niego sprawy i nieświadomie korzysta z roz-szerzeń kompilatora

Istnieje jeszcze trzecia klasa zachowań Zachowania nieokreślone (ang unspecified behaviour)Są to sytuacje gdy standard określa kilka możliwych sposoboacutew w jaki dane wyrażenie może działaći pozostawia kompilatorowi decyzję co z tym dalej zrobić Coś takiego nie musi być nigdzie opisanew dokumentacji i znowu poleganie na konkretnym zachowaniu jest błędem Klasycznym przykłademmoże być kolejność obliczania argumentoacutew wywołania funkcji

242 Rozmiar zmiennyRozmiar poszczegoacutelnych typoacutew danych (np char int czy long) jest roacuteżna na roacuteżnych platformachgdyż nie jest definiowany w sztywny sposoacuteb jak np ldquolong int zawsze powinien mieć bityrdquo (takieokreślenie wiązałoby się z wyżej opisanymi trudnościami) lecz w na zasadzie zależności typu ldquolongpowinien być nie kroacutetszy niż intrdquo ldquoshort nie powinien być dłuższy od intrdquo Pierwsza standaryzacjajęzyka C zakładała że typ int będzie miał taki rozmiar jak domyślna długość liczb całkowitych nadanym komputerze natomiast modyfikatory short oraz long zmieniały długość tego typu tylko wtedygdy dana maszyna obsługiwała typy o mniejszej lub większej długości1

Z tego powodu nigdy nie zakładaj że dany typ będzie miał określony rozmiar Jeżeli potrzebujesztypu o konkretnym rozmiarze (a dokładnej konkretnej liczbie bitoacutew wartości) możesz skorzystać z plikunagłoacutewkowego stdinth wprowadzonego do języka przez standard ISO C z roku Definiuje on typyint t int t int t int t uint t uint t uint t i uint t (o ile w danej architekturze występujątypy o konkretnej liczbie bitoacutew)

Jednak możemy posiadać implementację ktoacutera nie posiada tego pliku nagłoacutewkowego W takiej sy-tuacji nie pozostaje nam nic innego jak tworzyć własny plik nagłoacutewkowy w ktoacuterym za pomocą słoacutewkatypedef sami zdefiniujemy potrzebne nam typy Np

typedef unsigned char u8typedef signed char s8typedef unsigned short u16typedef signed short s16typedef unsigned long u32typedef signed long s32typedef unsigned long long u64typedef signed long long s64

Aczkolwiek należy pamiętać że taki plik będzie trzeba pisać od nowa dla każdej architektury najakiej chcemy kompilować nasz program

243 Porządek bajtoacutew i bitoacutew

2431 Bajty i słowaWiesz zapewne że podstawową jednostką danych jest bit ktoacutery może mieć wartość lub Kilkakolejnych bitoacutew2 stanowi bajt (dla skupienia uwagi przyjmijmy że bajt składa się z bitoacutew) Częstotyp short ma wielkość dwoacutech bajtoacutew i woacutewczas pojawia się pytanie w jaki sposoacuteb są one zapisane

1Dokładniejszy opis rozmiaroacutew dostępny jest w rozdziale Składnia2Standard wymaga aby było ich co najmniej 8 i liczba bitoacutew w bajcie w konkretnej implementacji jest określona

przez makro CHAR BIT zdefiniowane w pliku nagłoacutewkowym limitsh

243 PORZĄDEK BAJTOacuteW I BITOacuteW 173

w pamięci mdash czy najpierw ten bardziej znaczący mdash big-endian czy najpierw ten mniej znaczący mdashlittle-endian

Skąd takie nazwy Otoacuteż pochodzą one z książki Podroacuteże Guliwera w ktoacuterej liliputy kłoacuteciły się ostronę od ktoacuterej należy rozbijać jajko na twardo Jedni uważali że trzeba je rozbijać od grubszegokońca (big-endian) a drudzy że od cieńszego (lile-endian) Nazwy te są o tyle trafne że w wypadkuprocesoroacutew wyboacuter kolejności bajtoacutew jest sprawą czysto polityczną ktoacutera jest technicznie neutralna

Sprawa się jeszcze bardziej komplikuje w przypadku typoacutew ktoacutere składają się np z bajtoacutew Woacutew-czas są aż ( silnia) sposoby zapisania kolejnych fragmentoacutew takiego typu W praktyce zapewne spo-tkasz się jedynie z kolejnościami big-endian lub lile-endian co nie zmienia faktu że inne możliwościtakże istnieją i przy pisaniu programoacutew ktoacutere mają być przenośne należy to brać pod uwagę

Poniższy przykład dobrze obrazuje oba sposoby przechowywania zawartości zmiennych w pamięcikomputera (przyjmujemy CHAR BIT == oraz sizeof(long) == bez bitoacutew wypełnienia (ang paddingbits)) unsigned long zmienna = 0x01020304 w pamięci komputera będzie przechowywana tak

adres | 0 | 1 | 2 | 3 |big-endian |0x01|0x02|0x03|0x04|little-endian |0x04|0x03|0x02|0x01|

2432 Konwersja z jednego porządku do innegoCzasami zdarza się że napisany przez nas program musi się komunikować z innym programem (możeteż przez nas napisanym) ktoacutery działa na komputerze o (potencjalnie) innym porządku bajtoacutew Częstonajprościej jest przesyłać liczby jako tekst gdyż jest on niezależny od innych czynnikoacutew jednak takiformat zajmuje więcej miejsca a nie zawsze możemy sobie pozwolić na taką rozrzutność

Przykładem może być komunikacja sieciowa w ktoacuterej przyjęło się że dane przesyłane są w po-rządku big-endian Aby moacutec łatwo operować na takich danych w standardzie zdefiniowanonastępujące funkcje (w zasadzie zazwyczaj są to makra)

include ltarpainethgtuint32_t htonl(uint32_t hostlong)uint16_t htons(uint16_t hostshort)uint32_t ntohl(uint32_t netlong)uint16_t ntohs(uint16_t netshort)

Pierwsze dwie konwertują liczbę z reprezentacji lokalnej na reprezentację big-endian (host to ne-twork) natomiast kolejne dwie dokonują konwersji w drugą stronę (network to host)

Można roacutewnież skorzystać z pliku nagłoacutewkowego endianh w ktoacuterym definiowane są makra po-zwalające określić porządek bajtoacutew

include ltendianhgtinclude ltstdiohgt

int main() if __BYTE_ORDER == __BIG_ENDIAN

printf(Porządek big-endian (4321)n)elif __BYTE_ORDER == __LITTLE_ENDIAN

printf(Porządek little-endian (1234)n)elif defined __PDP_ENDIAN ampamp __BYTE_ORDER == __PDP_ENDIAN

printf(Porządek PDP (3412)n)else

printf(Inny porządek (d)n __BYTE_ORDER)endif

return 0

174 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

Na podstawie makra BYTE ORDER można skonstruować funkcję ktoacutera będzie konwertowaćliczby pomiędzy porządkiem roacuteżnymi porządkami

include ltendianhgtinclude ltstdiohgtinclude ltstdinthgt

uint32_t convert_order32(uint32_t val unsigned from unsigned to) if (from==to)

return val else

uint32_t ret = 0unsigned char tmp[5] = 0 0 0 0 0 unsigned char ptr = (unsigned char)ampvalunsigned div = 1000do tmp[from div 10] = ptr++ while ((div = 10))ptr = (unsigned char)ampretdiv = 1000do ptr++ = tmp[to div 10] while ((div = 10))return ret

define LE_TO_H(val) convert_order32((val) 1234 __BYTE_ORDER)define H_TO_LE(val) convert_order32((val) __BYTE_ORDER 1234)define BE_TO_H(val) convert_order32((val) 4321 __BYTE_ORDER)define H_TO_BE(val) convert_order32((val) __BYTE_ORDER 4321)define PDP_TO_H(val) convert_order32((val) 3412 __BYTE_ORDER)define H_TO_PDP(val) convert_order32((val) __BYTE_ORDER 3412)

int main ()

printf(08xn LE_TO_H(0x01020304))printf(08xn H_TO_LE(0x01020304))printf(08xn BE_TO_H(0x01020304))printf(08xn H_TO_BE(0x01020304))printf(08xn PDP_TO_H(0x01020304))printf(08xn H_TO_PDP(0x01020304))return 0

Ciągle jednak polegamy na niestandardowym pliku nagłoacutewkowym endianh Można go wyelimi-nować sprawdzając porządek bajtoacutew w czasie wykonywania programu

include ltstdiohgtinclude ltstdinthgt

int main() uint32_t val = 0x04030201unsigned char v = (unsigned char )ampvalint byte_order = v[0] 1000 + v[1] 100 + v[2] 10 + v[3]

if (byte_order == 4321) printf(Porządek big-endian (4321)n)

else if (byte_order == 1234)

244 BIBLIOTECZNE PROBLEMY 175

printf(Porządek little-endian (1234)n) else if (byte_order == 3412)

printf(Porządek PDP (3412)n) else

printf(Inny porządek (d)n byte_order)return 0

Powyższe przykłady opisują jedynie część problemoacutew jakie mogą wynikać z proacuteby przenoszeniabinarnych danych pomiędzy wieloma platformami Wszystkie co więcej zakładają że bajt ma bitoacutewco wcale nie musi być prawdą dla konkretnej architektury na ktoacuterą piszemy aplikację Co więcej liczbymogą posiadać w swojej reprezentacje bity wypełnienia (ang padding bits) ktoacutere nie biorą udziaływ przechowywaniu wartości liczby Te wszystkie roacuteżnice mogą dodatkowo skomplikować kod Toteżnależy być świadomym iż przenosząc dane binarnie musimy uważać na roacuteżne reprezentacje liczb

244 Biblioteczne problemy

2441 Dostępność bibliotekPisząc programy nieraz będziemy musieli korzystać z roacuteżnych bibliotek Problem polega na tym żenie zawsze będą one dostępne na komputerze na ktoacuterym inny użytkownik naszego programu będzieproacutebował go kompilować Dlatego też ważne jest abyśmy korzystali z łatwo dostępnych bibliotek ktoacuteredostępne są na wiele roacuteżnych systemoacutew i platform sprzętowych Zapamiętaj Twoacutej program jest natyle przenośny na ile przenośne są biblioteki z ktoacuterych korzysta

2442 Odmiany bibliotekPod Windows funkcje atan floor i fabs są w tej samej bibliotece co standardowe funkcje C

Pod Uniksami są w osobnej bibliotece matematycznej libm w wersji

statycznej (zwykle usrliblibma) i pliku nagłoacutewkowym mathh (zwykle usrincludemathh)3

ladowanej dynamicznie ( usrliblibmso )

Aby korzystać z tych funkcji potrzebujemy

dodać include ltmathhgt

przy kompilacji dołączyć bibliotekę libm gcc mainc -lm

Opcja -lm używa libmso albo libma w zależności od tego ktoacutere są znalezione i w zależności odobecności opcji -static45

245 Kompilacja warunkowaPrzy zwiększaniu przenośności kodu może pomoacutec preprocessor Przyjmijmy np że chcemy korzy-stać ze słoacutewka kluczowego inline wprowadzonego w standardzie C ale roacutewnocześnie chcemy abynasz program był rozumiany przez kompilatory ANSI CWoacutewczas możemy skorzystać z następującegokodu

ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline

3An Introduction to mdashfor the compilers gcc and g++ 27 Linking with external libraries4man ld5Dyskusja na grupie plcomposlinuxprogramowanie na temat c gc atan2 floor fabs

176 ROZDZIAŁ 24 PRZENOŚNOŚĆ PROGRAMOacuteW

else define __inline__ endifendif

a w kodzie programu zamiast słoacutewka inline stosować inline Co więcej kompilator rozumiesłoacutewka kluczowe tak tworzone i w jego przypadku warto nie redefiniować ich wartości

ifndef __GNUC__ ifndef __inline__ if __STDC_VERSION__ gt= 199901L define __inline__ inline else define __inline__ endif endifendif

Korzystając z kompilacji warunkowej można także korzystać z roacuteżnego kodu zależnie od (np) sys-temu operacyjnego Przykładowo przed kompilacją na konkretnej platformie tworzymy odpowiedniplik configh ktoacutery następnie dołączamy do wszystkich plikoacutew źroacutedłowych w ktoacuterych podejmujemydecyzje na podstawie zdefiniowanych makr Dla przykładu plik configh

ifndef CONFIG_Hdefine CONFIG_H

Uncomment if using Windows define USE_WINDOWS

Uncomment if using Linux define USE_LINUX

error You must edit configh fileerror Edit it and remove those error lines

endif

Jakiś plik źroacutedłowy

include configh

ifdef USE_WINDOWSrob_cos_wersja_dla_windows()

elserob_cos_wersja_dla_linux()

endif

Istnieją roacuteżne narzędzia ktoacutere pozwalają na automatyczne tworzenie takich plikoacutew configh dziękiczemu użytkownik przed skompilowaniem programu nie musi się trudzić i edytować ich ręcznie ajedynie uruchomić odpowiednie polecenie Przykładem jest zestaw autoconf i automake

Rozdział 25

Łączenie z innymi językami

Programista pisząc jakiś program ma problem z wyborem najbardziej odpowiedniego języka do utwo-rzenia tego programu Niekiedy zdarza się że najlepiej byłoby pisać program korzystając z roacuteżnychjęzykoacutew Język C może być z łatwością łączony z innymi językami programowania ktoacutere podlegająkompilacji bezpośrednio do kodu maszynowego (Asembler Fortran czy też C++) Ponadto dzięki spe-cjalnym bibliotekom można go łączyć z językami bardzo wysokiego poziomu (takimi jak np Pythonczy też Ruby) Ten rozdział ma za zadanie wytłumaczyć Ci w jaki sposoacuteb można mieszać roacuteżne językiprogramowania w jednym programie

251 Język C i Asembler

Informacje zawarte w tym rozdziale odnoszą się do komputeroacutew z procesorem i i pokrewnych

Łączenie języka C i języka asemblera jest dość powszechnym zjawiskiem Dzięki możliwości połączeniaobu tych językoacutew programowania można było utworzyć bibliotekę dla języka C ktoacutera niskopoziomowokomunikuje się z jądrem systemu operacyjnego komputera Ponieważ zaroacutewno asembler jak i C sąjęzykami tłumaczonymi do poziomu kodu maszynowego za ich łączenie odpowiada program zwanylinkerem (popularny ld) Ponadto niektoacuterzy producenci kompilatoroacutew umożliwiają stosowanie tzwwstawek asemblerowy ktoacutere umieszcza się bezpośrednio w kodzie programu napisanego w językuC Kompilator kompilując taki kod wstawi w miejsce tychże wstawek odpowiedni kod maszynowyktoacutery jest efektem przetłumaczenia kodu asemblera zawartegow takiej wstawce Opiszę tu oba sposobyłączenia obydwu językoacutew

2511 Łączenie na poziomie kodu maszynowegoW naszym przykładzie założymy że w pliku fS zawarty będzie kod napisany w asemblerze a fcto kod z programem w języku C Program w języku C będzie wykorzystywał jedną funkcję napisanąw języku asemblera ktoacutera wyświetli prosty napis ldquoHello worldrdquo Z powodu ograniczeń technicznychzakładamy że program uruchomiony zostanie w środowisku POSIX na platformie i i skompilowanykompilatorem gcc Używaną składnią asemblera będzie ATampT (domyślna dla asemblera ) Oto plikfS

text

globl _f1_f1

pushl ebpmovl esp ebp

177

178 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

movl $4 eax 4 to funkcja systemowa write movl $1 ebx 1 to stdout movl $tekst ecx adres naszego napisu movl $len edx długość napisu w bajtach int $0x80 wywołanie przerwania systemowego popl ebpret

datatekst

string Hello worldnlen = - tekst

W systemach z rodziny UNIX należy pominąć znak rdquo rdquoprzed nazwą funkcji f

Teraz kolej na fc

extern void f1 (void) musimy użyć słowa extern int main ()

f1()return 0

Teraz możemy skompilować oba programy

as f1S -o f1ogcc f2c -c -o f2ogcc f2o f1o -o program

W ten sposoacuteb uzyskujemy plik wykonywalny o nazwie ldquoprogramrdquo Efekt działania programu powinienbyć następujący

Hello world

Na razie utworzyliśmy bardzo prostą funkcję ktoacutera w zasadzie nie komunikuje się z językiem Cczyli nie zwraca żadnej wartości ani nie pobiera argumentoacutew Jednak aby zacząć pisać obsługę funk-cji ktoacutera będzie pobierała argumenty i zwracała wyniki musimy poznać działanie języka C od trochęniższego poziomu

Argumenty

Do komunikacji z funkcją język C korzysta ze stosu Argumenty odkładane sąw kolejności od ostatniegodo pierwszego Ponadto na końcu odkładany jest tzw adres powrotu dzięki czemu po wykonaniufunkcji program ldquowierdquo w ktoacuterym miejscu ma kontynuować działanie Ponadto początek funkcji wasemblerze wygląda tak

pushl ebpmovl esp ebp

Zatem na stosie znajdują się kolejno zawartość rejestru EBP adres powrotu a następnie argumenty odpierwszego do n-tego

251 JĘZYK C I ASEMBLER 179

Zwracanie wartości

Na architekturze i do zwracaniawynikoacutew pracy programu używa się rejestru EAX bądź jego ldquomniej-szychrdquo odpowiednikoacutew tj AX i AHAL Zatem aby funkcja napisana w asemblerze zwroacuteciła ldquordquo przedrozkazem ret należy napisać

movl $1 eax

Nazewnictwo

Kompilatory języka CC++ dodają podkreślnik ldquo rdquo na początku każdej nazwy Dla przykładu funkcja

void funkcja()

W pliku wyjściowym będzie posiadać nazwę funkcja Dlatego aby korzystać z poziomu języka C zfunkcji zakodowanych w asemblerze muszą one mieć przy definicji w pliku asemblera wspomnianydodatkowy podkreślnik na początku

Łączymy wszystko w całość

Pora abyśmy napisali jakąś funkcję ktoacutera pobierze argumenty i zwroacuteci jakiś konkretny wynik Otokod fS

text

globl _funkcja_funkcja

pushl ebpmovl esp ebpmovl 8(esp) eax kopiujemy pierwszy argument do eax addl 12(esp) eax do pierwszego argumentu w eax dodajemy drugi argument popl ebpret i zwracamy wynik dodawania

oraz fc

include ltstdiohgtextern int funkcja (int a int b)int main ()printf (2+3=dn funkcja(23))return 0

Po skompilowaniu i uruchomieniu programu powinniśmy otrzymać wydruk +=

2512 Wstawki asembleroweOproacutecz możliwości wstępnie skompilowanych modułoacutew możesz posłużyć się także tzw wstawkamiasemblerowymi Ich użycie powoduje wstawienie w miejsce wystąpienia wstawki odpowiedniegokodumaszynowego ktoacutery powstanie po przetłumaczeniu kodu asemblerowego Ponieważ jednakwstawkiasemblerowe nie są standardowym elementem języka C każdy kompilator ma całkowicie odmiennąfilozofię ich stosowania (lub nie ma ich w ogoacutele) Ponieważ w tym podręczniku używamy głoacutewniekompilatora więc w tym rozdziale zostanie omoacutewiona filozofia stosowania wstawek asemblerawedług programistoacutew

Ze wstawek asemblerowych korzysta się tak

180 ROZDZIAŁ 25 ŁĄCZENIE Z INNYMI JĘZYKAMI

int main ()

asm (nop)

W tym wypadku wstawiona zostanie instrukcja ldquonoprdquo (no operation) ktoacutera tak naprawdę służytylko i wyłącznie do konstruowania pętli opoacuteźniających

252 C++Język C++ z racji swojego podobieństwa do C będzie wyjątkowo łatwy do łączenia Pewnym utrudnie-niem może być obiektowość języka C++ oraz występowanie w nim przestrzeni nazw oraz możliwośćprzeciążania funkcji Oczywiście nadal zakładamy że głoacutewny program piszemy w C natomiast korzy-stamy tylko z pojedynczych funkcji napisanych w C++ Ponieważ język C nie oferuje tego wszystkiegoco daje programiście język C++ to musimy ldquozmusićrdquo C++ do wyłączenia pewnych swoich możliwościaby można było połączyć ze sobą elementy programu napisane w dwoacutech roacuteżnych językach Używa siędo tego następującej konstrukcji

extern C funkcje zmienne i wszystko to co będziemy łączyć z programem w C

W zrozumieniu teorii pomoże Ci prosty przykład plik fc

include ltstdiohgtextern int f2(int a)

int main ()

printf (dn f2(2))return 0

oraz plik fcpp

include ltiostreamgtusing namespace stdextern C

int f2 (int a)

cout ltlt a= ltlt a ltlt endlreturn a2

Teraz oba pliki kompilujemy

gcc f1c -c -o f1og++ f2cpp -c -o f2o

Przy łączeniu obu tych plikoacutew musimy pamiętać że język C++ także korzysta ze swojej bibliotekiZatem poprawna postać polecenia kompilacji powinna wyglądać

gcc f1o f2o -o program -lstdc++

(stdc++ mdash biblioteka standardowa języka C++) Bardzo istotne jest tutaj to abyśmy zawsze pamiętalio extern ldquoCrdquo gdyż w przeciwnym razie funkcje napisane w C++ będą dla programu w C całkowicieniewidoczne

Dodatek A

Indeks alfabetyczny

Alfabetyczny spis funkcji biblioteki standardowej ANSI C (tzw libc) w wersji C

A01 A abort()

abs()

acos()

asctime()

asin()

assert()

atan()

atan()

atexit()

atof()

atoi()

atol()

A02 B bsearch()

A03 C calloc()

ceil()

clearerr()

clock()

cos()

cosh()

ctime()

A04 D diime()

div()

A05 E errno (zmienna)

exit()

exp()

A06 F fabs()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

floor()

fmod()

fopen()

fprintf()

fputc()

fputs()

fread()

free()

freopen()

frexp()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

A07 G getc()

getchar()

getenv()

gets()

gmtime()

A08 I isalnum()

isalpha()

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

181

182 DODATEK A INDEKS ALFABETYCZNY

A09 L labs()

ldexp()

ldiv()

localeconv()

localtime()

log()

log()

longjmp()

A010 M malloc()

mblen()

mbstowcs()

mbtowc()

memchr()

memcmp()

memcpy()

memmove()

memset()

mktime()

modf()

A011 O offsetof()

A012 P perror()

pow()

printf()

putc()

putchar()

puts()

A013 Q qsort()

A014 R raise()

rand()

realloc()

remove()

rename()

rewind()

A015 S scanf()

setbuf()

setjmp()

setlocale()

setvbuf()

signal()

sin()

sinh()

sprintf()

sqrt()

srand()

sscanf()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strime()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtod()

strtok()

strtol()

strtoul()

strxfrm()

system()

A016 T tan()

tanh()

time()

tm (struktura)

tmpfile()

tmpnam()

tolower()

toupper()

A017 U ungetc()

A018 V va arg()

va end()

va start()

vfprintf()

vprintf()

vsprintf()

A019 W wcstombs()

wctomb()

Dodatek B

Indeks tematyczny

Spis plikoacutew nagłoacutewkowych oraz zawartych w nich funkcji i makr biblioteki standardowej C Funkcjemakra i typy wprowadzone dopiero w standardzie C zostały oznaczone poprzez ldquo[C]rdquo po nazwie

B1 asserthMakro asercji

assert()

B2 ctypehKlasyfikowanie znakoacutew

isalnum()

isalpha()

isblank() [C]

iscntrl()

isdigit()

isgraph()

islower()

isprint()

ispunct()

isspace()

isupper()

isxdigit()

tolower()

toupper()

B3 errnohDeklaracje kodoacutew błędoacutew

EDOM (makro)

EILSEQ (makro) [C]

ERANGE (makro)

errno (zmienna)

B4 floathWłaściwości typoacutew zmiennoprzecinkowych zależne od implementacji

B5 limitshWłaściwości typoacutew całkowitych zależne od implementacji

183

184 DODATEK B INDEKS TEMATYCZNY

B6 localehUstawienia międzynarodowe

localeconv()

setlocale()

B7 mathhFunkcje matematyczne

FP FAST FMAF (makro) [C]

FP FAST FMAL (makro) [C]

FP FAST FMA (makro) [C]

FP ILOGB (makro) [C]

FP ILOGBNAN (makro) [C]

FP INFINITE (makro) [C]

FP NAN (makro) [C]

FP NORMAL (makro) [C]

FP SUBNORMAL (makro) [C]

FP ZERO (makro) [C]

HUGE VALF (makro) [C]

HUGE VALL (makro) [C]

HUGE VAL (makro)

INFINITY (makro) [C]

MATH ERREXCEPT (makro) [C]

MATH ERRNO (makro) [C]

NAN (makro) [C]

acosh()

acos()

asinh()

asin()

atan()

atanh()

atan()

cbrt() [C]

ceil()

copysign() [C]

cosh()

cos()

double t (typ) [C]

erfc() [C]

erf() [C]

exp() [C]

expm() [C]

exp()

fabs()

fdim() [C]

flaot t (typ) [C]

floor()

fmax() [C]

fma() [C]

fmin() [C]

fmod()

fpclassify() [C]

frexp()

hypot() [C]

ilogb() [C]

isfinite() [C]

isgreaterequal() [C]

isgreater() [C]

isinf() [C]

islessequal() [C]

islessgreater() [C]

isless() [C]

isnan() [C]

isnormal() [C]

isunordered() [C]

ldexp()

lgamma() [C]

llrint() [C]

llround() [C]

log()

logp() [C]

log() [C]

logb() [C]

log()

B8 SETJMPH 185

lrint() [C]

lround() [C]

math errhandling (makro) [C]

modf()

nan() [C]

nearbyint() [C]

nextaer() [C]

nexoward() [C]

pow()

remainder() [C]

remquo() [C]

rint() [C]

round() [C]

scalbln() [C]

scalbn() [C]

signbit() [C]

sinh()

sin()

sqrt()

tanh()

tan()

tgamma() [C]

trunc() [C]

B8 setjmphObsługa nielokalnych skokoacutew

longjmp()

setjmp()

B9 signalhObsługa sygnałoacutew

raise()

signal()

B10 stdarghNarzędzia dla funkcji ze zmienną liczbą argumentoacutew

va arg()

va end()

va start()

B11 stddefhStandardowe definicje

offsetof()

B12 stdiohStandard InputOutput czyli standardowe wejście-wyjście

clearerr()

fclose()

feof()

ferror()

fflush()

fgetc()

fgetpos()

fgets()

fopen()

186 DODATEK B INDEKS TEMATYCZNY

fprintf()

fputc()

fputs()

fread()

freopen()

fscanf()

fseek()

fsetpos()

ell()

fwrite()

getc()

getchar()

gets()

perror()

printf()

putc()

putchar()

puts()

remove()

rename()

rewind()

scanf()

setbuf()

setvbuf()

sprintf()

sscanf()

tmpfile()

tmpnam()

ungetc()

vfprintf()

vprintf()

vsprintf()

B13 stdlibhNajbardziej podstawowe funkcje

abort()

abs()

atexit()

atof()

atoi()

atol()

bsearch()

calloc()

div()

exit()

free()

getenv()

labs()

ldiv()

malloc()

mblen()

mbstowcs()

mbtowc()

qsort()

rand()

realloc()

srand()

strtod()

strtol()

strtoul()

system()

wctomb()

wcstombs()

B14 stringhOperacje na łańcuchach znakoacutew

memchr()

memcmp()

memcpy()

memmove()

memset()

strcat()

strchr()

strcmp()

strcoll()

strcpy()

strcspn()

strerror()

strlen()

strncat()

strncmp()

strncpy()

strpbrk()

strrchr()

strspn()

strstr()

strtok()

strxfrm()

strdup()

B15 timehFunkcje obsługi czasu

B15 TIMEH 187

asctime()

clock()

ctime()

diime()

gmtime()

localtime()

mktime()

strime()

time()

tm (struktura)

188 DODATEK B INDEKS TEMATYCZNY

Dodatek C

Wybrane funkcje bibliotekistandardowej

C1 assert

C11 Deklaracjadefine assert(expr)

C12 Plik nagłoacutewkowyasserth

C13 OpisMakro przypominające w użyciu funkcję służy do debuggowania programoacutew Gdy testowany waruneklogiczny expr przyjmuje wartość fałsz na standardowe wyjście błędoacutew wypisywany jest komunikat obłędzie (zawierające min argument wywołania makra nazwę funkcji w ktoacuterej zostało wywołanenazwę pliku źroacutedłowego oraz numer linii w formacie zależnym od implementacji) i program jest prze-rywany poprzez wywołanie funkcji abort

W ten sposoacuteb możemy oznaczyć w programie niezmienniki czyli warunki ktoacutere niezależnie odwartości zmiennych muszą pozostać prawdziwe Jeśli asercja zawiedzie oznacza to że popełniliśmybłąd w algorytmie piszemy sobie po pamięci (nadając zmiennym wartości ktoacuterych nigdy nie powinnymieć) albo nastąpiła po drodze sytuacja wyjątkowa na przykład związana z obsługą operacji wejścia-wyjścia

Można łatwo pozbyć się asercji uwalniając kod od spowalniających obciążeń a jednocześnie niemusząc kasować wystąpień assert i zachowując je na przyszłość Aby to zrobić należy przed dołą-czeniem pliku nagłoacutewkowego asserth zdefiniować makro NDEBUG woacutewczas makro assert przyjmujepostać

define assert(ignore) ((void)0)

Makro assert jest redefiniowane za każdym dołączeniem pliku nagłoacutewkowego asserth

C14 Wartość zwracanaMakro nie zwraca żadnej wartości

189

190 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C15 Przykładinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

Program wypisze komunikat podobny do

Assertion failed err==0 file testc line 6

Natomiast jeśli uruchomimy

define NDEBUGinclude ltasserthgt

int main()

int err=1assert(err==0)return 0

nie pojawi się żaden komunikat o błędach

C2 atoi

C21 Deklaracjaint atoi (const char string)

C22 Plik nagłoacutewkowystdlibh

C23 OpisFunkcja jako argument pobiera liczbę w postaci ciągu znakoacutew ASCII a następnie zwraca jej wartość wformacie int Liczbę może poprzedzać dowolona ilość białych znakoacutew (spacje tabulatory itp) oraz jejznak (plus (+) lub minus (-)) Funkcja atoi() kończy wczytywać znaki w momencie napotkania jakiego-kowiek znaku ktoacutery nie jest cyfrą

C24 Wartość zwracanaW przypadku gdy ciąg nie zawiera cyfr zwracana jest wartość

C25 UwagiZnak musi bezpośrednio poprzedzać liczbę czyli możliwy jest zapis ldquo-rdquo natomiast proacuteba potraktowa-nia funkcją atoi ciągu ldquo- rdquo skutkuje zwracaną wartością

C3 ISALNUM 191

C26 Przykładinclude ltstdiohgtinclude ltstdlibhgtint main(void)

char c_Numer = nt 2004uint i_Numeri_Numer = atoi(c_Numer)printf(n Liczba typu int d oraz jako ciąg znakoacutew s n i_Numer c_Numer)return 0

C3 isalnum

C31 Deklaracjainclude ltctypehgt

int isalnum(int c)int isalpha(int c)int isblank(int c)int iscntrl(int c)int isdigit(int c)int isgraph(int c)int islower(int c)int isprint(int c)int ispuntc(int c)int isspace(int c)int isupper(int c)int isxdigit(int c)

C32 Argumentyc wartość znaku reprezentowana w jako typ unsigned char lub wartość makra EOF Z tego powodu

przed przekazaniem funkcji argumentu typu char lub signed char należy go zrzutować na typunsigned char lub unsigned int

C33 OpisFunkcje sprawdzają czy podany znak spełnia jakiś konkretny warunek Biorą pod uwagę ustawieniajęzyka i dla roacuteżnych znakoacutew w roacuteżnych localersquoach mogą zwracać roacuteżne wartości

isalnum sprawdza czy znak jest liczbą lub literą

isalpha sprawdza czy znak jest literą

isblank sprawdza czy znak jest znakiem odstępu służącym do oddzielania wyrazoacutew (standardowymiznakami odstępu są spacja i znak tabulacji)

iscntrl sprawdza czy znak jest znakiem sterującym

isdigit sprawdza czy znak jest cyfrą dziesiętna

isgraph sprawdza czy znak jest znakiem drukowalnym roacuteżnym od spacji

islower sprawdza czy znak jest małą literą

isprint sprawdza czy znak jest znakiem drukowalnym (włączając w to spację)

192 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

ispunct sprawdza czy znak jest znakiem przestankowym dla ktoacuterego ani isspace ani isalnum nie sąprawdziwe (standardowo są to wszystkie znaki drukowalne dla ktoacuterych te funkcje zwracajązero)

isspace sprawdza czy znak jest tzw białym znakiem (standardowymi białymi znakami są spacjawysunięcie strony rsquorsquo znak przejścia do nowej linii rsquonrsquo znak powrotu karetki rsquorrsquo tabulacjapozioma rsquotrsquo i tabulacja pionowa rsquovrsquo)

isupper sprawdza czy znak jest dużą literą

isxdigit sprawdza czy znak jest cyfrą szesnastkową tj cyfrą dziesiętną lub literą od rsquoarsquo do rsquorsquo niezależnieod wielkości

Funkcja isblank niewystępowaław oryginalnym standardzie ANSI C z roku (tzw C) i zostaładodana dopiero w nowszym standardzie z roku (tzw C)

C34 Wartość zwracanaLiczba niezerowa gdy podany argument spełnia konkretny warunek w przeciwnym wypadku mdash zero

C35 Przykład użyciainclude ltctypehgt funkcje is include ltlocalehgt setlocale include ltstdiohgt printf i scanf

void identify_char(int c) printf( Litera lub cyfra sn isalnum (c) tak nie)

if __STDC_VERSION__ gt= 199901Lprintf( Odstęp sn isblank (c) tak nie)

endifprintf( Znak sterujący sn iscntrl (c) tak nie)printf( Cyfra dziesiętna sn isdigit (c) tak nie)printf( Graficzny sn isgraph (c) tak nie)printf( Mała litera sn islower (c) tak nie)printf( Drukowalny sn isprint (c) tak nie)printf( Przestankowy sn ispunct (c) tak nie)printf( Biały znak sn isspace (c) tak nie)printf( Wielka litera sn isupper (c) tak nie)printf( Cyfra szesnastkowa sn isxdigit(c) tak nie)

int main() unsigned char cprintf(Naciśnij jakiś klawiszn)if (scanf(c ampc)==1)

identify_char(c)setlocale(LC_ALL pl_PL) przystosowanie do warunkoacutew polskich puts(Po zmianie ustawień języka)identify_char(c)

return 0

C36 Zobacz też tolower toupper

C4 MALLOC 193

C4 malloc

C41 Deklaracjainclude ltstdlibhgt

void calloc(size_t nmeb size_t size)void malloc(size_t size)void free(void ptr)void realloc(void ptr size_t size)

C42 Argumentynmeb liczba elementoacutew dla ktoacuterych ma być przydzielona pamięć

size rozmiar (w bajtach) pamięci do zarezerwowania bądź rozmiar pojedynczego elementu

ptr wskaźnik zwroacutecony przez poprzednie wywołanie jednej z funkcji lub

C43 OpisFunkcja calloc przydziela pamięć dla nmeb elementoacutew o rozmiarze size każdy i zeruje przydzielonąpamięć

Funkcja malloc przydziela pamięć o wielkości size bajtoacutewFunkcja free zwalnia blok pamięci wskazywany przez ptr wcześniej przydzielony przez jedną z

funkcji malloc calloc lub realloc Jeżeli ptr ma wartość funkcja nie robi nicFunkcja realloc zmienia rozmiar przydzielonego wcześniej bloku pamięci wskazywanego przez ptr

do size bajtoacutew Pierwsze n bajtoacutew bloku nie ulegnie zmianie gdzie n jest minimum z rozmiaru staregobloku i size Jeżeli ptr jest roacutewny zero (tj ) funkcja zachowuje się tak samo jako malloc

C44 Wartość zwracanaJeżeli przydzielanie pamięci się powiodło funkcje calloc malloc i realloc zwracają wskaźnik do nowoprzydzielonego bloku pamięci W przypadku funkcji realloc może to być wartość inna niż ptr

Jeśli jako size nmeb podano zero zwracany jest albo wskaźnik albo prawidłowy wskaźnikktoacutery można podać do funkcji free (zauważmy że jest też prawidłowym argumentem free)

Jeśli działanie funkcji nie powiedzie się zwracany jest i odpowiedni kod błędu jest wpisywanydo zmiennej errno Dzieje się tak zazwyczaj gdy nie ma wystarczająco dużo miejsca w pamięci

C45 Przykładinclude ltstdiohgtinclude ltstdlibhgt

int main(void)

size_t size num = 0float tab tmp

Przydzielenie początkowego bloku pamięci size = 64tab = malloc(size sizeof tab)if (tab)

perror(malloc)return EXIT_FAILURE

194 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Odczyt liczb while (scanf(f amptmp)==1)

Jeżeli zapełniono całą tablicę trzeba ją zwiększyć if (num==size)float ptr = realloc(tab (size = 2) sizeof ptr)if (ptr)

free(tab)perror(realloc)return EXIT_FAILURE

tab = ptr

tab[num++] = tmp

Wypisanie w odwrotnej kolejnosci while (num)

printf(fn tab[--num])

Zwolnienie pamieci i zakonczenie programu free(tab)return EXIT_SUCCESS

C46 Uwagi

Użycie rzutowania przy wywołaniach funkcji malloc realloc oraz calloc w języku C jest zbędne i szko-dliwe W przypadku braku deklaracji tych funkcji (np gdy programista zapomni dodać plik nagłoacutew-kowy stdlibh) kompilator przyjmuje domyślną deklaracje w ktoacuterej funkcja zwraca int Przy brakurzutowania spowoduje to błąd kompilacji (z powodu niemożności skonwertowania liczby na wskaźnik)co pozwoli na szybkie wychwycenie błędu w programie Rzutowanie powoduje że kompilator zostajezmuszony do przeprowadzenia konwersji typoacutew i nie wyświetla żadnych błędoacutew W przypadku językaC++ rzutowanie jest konieczne

Zastosowanie operatora sizeof z wyrażeniem (np sizeof tablica) a nie typem (np sizeof float)ułatwia poacuteźniejszą modyfikację programoacutew Gdyby w pewnym momencie programista zdecydował sięzmienić tablicę z tablicy floatoacutew na tablice doublersquoi musiałby wyszukiwać wszystkie wywołania funkcjimalloc realloc i calloc co nie jest konieczne przy użyciu operatora sizeof z wyrażeniem

Ponieważ dla parametru size roacutewnego zero funkcja może zwroacutecić albo wskaźnik roacuteżny od wartości albo jej roacutewny zwykłe sprawdzanie poprawności wywołania poprzez przyroacutewnanie zwroacuteconejwartości do zera może nie dać prawidłowego wyniku

C47 Zobacz też

Wskaźniki (dokładne omoacutewienie zastosowania)

C5 PRINTF 195

C5 printf

C51 Deklaracjainclude ltstdiohgt

int printf(const char format )int fprintf(FILE stream const char format )int sprintf(char str const char format )int snprintf(char str size_t size const char format )

include ltstdarghgt

int vprintf(const char format va_list ap)int vfprintf(FILE stream const char format va_list ap)int vsprintf(char str const char format va_list ap)int vsnprintf(char str size_t size const char format va_list ap)

C52 OpisFunkcje formatują tekst zgodnie z podanym formatem opisanym poniżej Funkcje printf i vprintf wy-pisują tekst na standardowe wyjście (tj do stdout) fprintf i vfprintf do strumienia podanego jakoargument a sprintf vsprintf snprintf i vsnprintf zapisują go w podanej jako argument tablicy znakoacutew

Funkcje vprintf vfprintf vsprintf i vsnprintf roacuteżnią się od odpowiadających im funkcjom printffprintf sprintf i snprintf tym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

Funkcje snprintf i vsnprintf roacuteżnią się od sprintf i vsprintf tym że nie zapisuje do tablicy nie wię-cej niż size znakoacutew (wliczając kończący znak rsquorsquo) Oznacza to że można je używać bez obawy owystąpienie przepełnienia bufora

C53 Argumentyformat format w jakim zostaną wypisane następne argumenty

stream strumień wyjściowy do ktoacuterego mają być zapisane dane

str tablica znakoacutew do ktoacuterej ma być zapisany sformatowany tekst

size rozmiar tablicy znakoacutew

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

C54 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) ktoacutere są kopiowane bez zmian na wyjścieoraz sekwencji sterujących zaczynających się od symbolu procenta po ktoacuterym następuje

dowolna liczba flag

opcjonalne określenie minimalnej szerokości pola

opcjonalne określenie precyzji

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

196 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Flagi

W sekwencji możliwe są następujące flagi

- (minus) oznacza że pole ma być wyroacutewnane do lewej a nie do prawej

+ (plus) oznacza że dane liczbowe zawsze poprzedzone są znakiem (plusem dla liczb nieujem-nych lub minusem dla ujemnych)

spacja oznacza że liczby nieujemne poprzedzone są dodatkową spacją jeżeli flaga plus i spacjasą użyte jednocześnie to spacja jest ignorowana

(hash) powoduje że wynik jest przedstawiony w alternatywnej postaci

ndash dla formatu o powoduje to zwiększenie precyzji jeżeli jest to konieczne aby na początkuwyniku było zero

ndash dla formatoacutew x i X niezerowa liczba poprzedzona jest ciągiem x lub X

ndash dla formatoacutew a A e E f F g i G wynik zawsze zawiera kropkę nawet jeżeli nie ma za niążadnych cyfr

ndash dla formatoacutew g i G końcowe zera nie są usuwane

(zero) dla formatoacutew d i o u xX aA e E f F g iG do wyroacutewnania pola wykorzystywane sązera zamiast spacji za wyjątkiem wypisywania wartości nieskończoność i NaN Jeżeli obie flagi i mdash są obecne to flaga zero jest ignorowana Dla formatoacutew d i o u x i X jeżeli określona jestprecyzja flaga ta jest ignorowana

Szerokość pola i precyzja

Minimalna szerokość pola oznacza ile najmniej znakoacutew ma zająć dane pole Jeżeli wartość po formato-waniu zajmuje mniej miejsca jest ona wyroacutewnywana spacjami z lewej strony (chyba że podano flagiktoacutere modyfikują to zachowanie) Domyślna wartość tego pola to

Precyzja dla formatoacutew

d i o u x iX określa minimalną liczbę cyfr ktoacutere mają być wyświetlone i ma domyślną wartość

a A e E f i F mdash liczbę cyfr ktoacutere mają być wyświetlone po kropce i ma domyślną wartość

g i G określa liczbę cyfr znaczących i ma domyślną wartość

dla formatu s mdash maksymalną liczbę znakoacutew ktoacutere mają być wypisane

Szerokość pola może być albo dodatnią liczbą zaczynającą się od cyfry roacuteżnej od zera albo gwiazdkąPodobnie precyzja z tą roacuteżnicą że jest jeszcze poprzedzona kropką Gwiazdka oznacza że brany jestkolejny z argumentoacutew ktoacutery musi być typu int Wartość ujemna przy określeniu szerokości jest trak-towana tak jakby podano flagę - (minus)

Rozmiar argumentu

Dla formatoacutew d i i można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu signed char

h mdash oznacza że format odnosi się do argumentu typu short

l (el) mdash oznacza że format odnosi się do argumentu typu long

ll (el el) mdash oznacza że format odnosi się do argumentu typu long long

j mdash oznacza że format odnosi się do argumentu typu intmax t

z mdash oznacza że że format odnosi się do argumentu typu będącego odpowiednikiem typu size tze znakiem

t mdash oznacza że że format odnosi się do argumentu typu ptrdiff t

C5 PRINTF 197

Dla formatoacutew o u x i X można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d ioznaczają one że format odnosi się do argumentu odpowiedniego typu bez znaku

Dla formatu n można użyć takich samych modyfikatoroacutew rozmiaru jak dla formatu d i oznaczająone że format odnosi się do argumentu będącego wskaźnikiem na dany typ

Dla formatoacutew a A e E f F g iGmożna użyć modyfikatoroacutew rozmiaru L ktoacutery oznacza że formatodnosi się do argumentu typu long double

Dodatkowo modyfikator l (el) dla formatu c oznacza że odnosi się on do argumentu typu wint ta dla formatu s że odnosi się on do argumentu typu wskaźnik na wchar t

Format

Funkcje z rodziny printf obsługują następujące formaty

d i mdash argument typu int jest przedstawiany jako liczba całkowita ze znakiem w postaci [-]ddd

o u x X mdash argument typu unsigned int jest przedstawiany jako nieujemna liczba całkowitazapisana w systemie oktalnym (o) dziesiętnym (u) lub heksadecymalnym (x i X)

f F mdash argument typu double jest przedstawiany w postaci [-]dddddd

e E mdash argument typu double jest reprezentowany w postaci [i]dddde+dd gdzie liczba przedkropką dziesiętną jest roacuteżna od zera jeżeli liczba jest roacuteżna od zera a + oznacza znak wykładnikaFormat E używa wielkiej litery E zamiast małej

g G mdash argument typu double jest reprezentowany w formacie takim jak f lub e (odpowiednio Flub E) zależnie od liczby znaczących cyfr w liczbie oraz określonej precyzji

a A mdash argument typu double przedstawiany jest w formacie [-]xhhhhp+d czyli analogiczniejak dla e i E tyle że liczba zapisana jest w systemie heksadecymalnym

c mdash argument typu int jest konwertowany do unsigned char i wynikowy znak jest wypisywanyJeżeli podanomodyfikator rozmiaru l argument typuwint t konwertowany jest dowielobajtowejsekwencji i wypisywany

s mdash argument powinien być typu wskaźnik na char (lub wchar t) Wszystkie znaki z podanejtablicy aż do i z wyłączeniem znaku null są wypisywane

pmdashargument powinien być typuwskaźnik na void Jest to konwertowany na serię drukowalnychznakoacutew w sposoacuteb zależny od implementacji

n mdash argument powinien być wskaźnikiem na liczbę całkowitą ze znakiem do ktoacuterego zapisanajest liczba zapisanych znakoacutew

W przypadku formatoacutew f F e E gG a iAwartość nieskończoność jest przedstawiana w formacie[-]inf lub [-]infinity zależnie od implementacji Wartość NaN jest przedstawiana w postaci [-]nan lub[i]nan(sekwencja) gdzie sekwencja jest zależna od implementacji W przypadku formatoacutew określo-nych wielką literą roacutewnież wynikowy ciąg znakoacutew jest wypisywany wielką literą

C55 Wartość zwracana

Jeżeli funkcje zakończą się sukcesem zwracają liczbę znakoacutew w tekście (wypisanym na standardowewyjście do podanego strumienia lub tablicy znakoacutew) nie wliczając kończącego rsquorsquo W przeciwnymwypadku zwracana jest liczba ujemna

Wyjątkami są funkcje snprintf i vsnprintf ktoacutere zwracają liczbę znakoacutew ktoacutere zostałyby zapisanedo tablicy znakoacutew gdyby była wystarczająco duża

198 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C56 Przykład użyciainclude ltstdiohgtint main()

int i = 4float f = 31415char s = Monty Pythonprintf(i = inf = 1fnWskaźnik s wskazuje na napis sn i f s)return 0

Wyświetli

i = 4f = 31Wskaźnik s wskazuje na napis Monty Python

Funkcja formatująca ciąg znakoacutew i alokująca odpowiednią ilość pamięci

include ltstdarghgtinclude ltstdlibhgt

char sprintfalloc(const char format ) int retsize_t size = 100char str = malloc(size)if (str)

return 0

for()va_list apchar tmp

va_start(ap format)ret = vsnprintf(str size format ap)va_end(ap)

if (retltsize) break

tmp = realloc(str (size_t)ret + 1)if (tmp) ret = -1break

else str = tmpsize = (size_t)ret + 1

if (retlt0) free(str)str = 0

C6 SCANF 199

else if (size-1gtret) char tmp = realloc(str (size_t)ret + 1)if (tmp) str = tmp

return str

C57 Uwagi

Funkcje snprintf i vsnprintf nie były zdefiniowane w standardzie C Zostały one dodane dopiero wstandardzie C

Biblioteka glibc do wersji włącznie posiadała implementacje funkcji snprintf oraz vsnprintfktoacutere były niezgodne ze standardem gdyż zwracały - w przypadku gdy wynikowy tekst nie mieściłsię w podanej tablicy znakoacutew

C6 scanf

C61 Deklaracja

W pliku nagłoacutewkowym stdioh

int scanf(const char format )int fscanf(FILE stream const char format )int sscanf(const char str const char format )

W pliku nagłoacutewkowym stdargh

int vscanf(const char format va_list ap)int vsscanf(const char str const char format va_list ap)int vfscanf(FILE stream const char format va_list ap)

C62 Opis

Funkcje odczytują dane zgodnie z podanym formatem opisanym niżej Funkcje scanf i vscanf odczytujądane ze standardowego wejścia (tj stdin) fscanf i vfscanf ze strumienia podanego jako argument asscanf i vsscanf z podanego ciągu znakoacutew

Funkcje vscanf vfscanf i vsscanf roacuteżnią się od odpowiadających im funkcjom scanf fscanf i sscanftym że zamiast zmiennej liczby argumentoacutew przyjmują argument typu va list

C63 Argumenty

format format odczytu danych

stream strumień wejściowy z ktoacuterego mają być odczytane dane

str tablica znakoacutew z ktoacuterej mają być odczytane dane

ap wskaźnik na pierwszy argument z listy zmiennej liczby argumentoacutew

200 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

C64 FormatFormat składa się ze zwykłych znakoacutew (innych niż znak rsquorsquo) oraz sekwencji sterujących zaczynającychsię od symbolu procenta po ktoacuterym następuje

opcjonalna gwiazdka

opcjonalne maksymalna szerokość pola

opcjonalne określenie rozmiaru argumentu

określenie formatu

Jeżeli po znaku procenta występuje od razu drugi procent to cała sekwencja traktowana jest jakzwykły znak procenta (tzn jest on wypisywany na wyjście)

Wystąpienie w formacie białego znaku powoduje że funkcje z rodziny scanf będą odczytywać iodrzucać znaki aż do napotkania pierwszego znaku nie będącego białym znakiem

Wszystkie inne znaki (tj nie białe znaki oraz nie sekwencje sterujące) muszą dokładnie pasowaćdo danych wejściowych

Wszystkie białe znaki z wejścia są ignorowane chyba że sekwencja sterująca określa format [ c lubn

Jeżeli w sekwencji sterującej występuje gwiazdka to dane z wejścia zostaną pobrane zgodnie zformatem ale wynik konwersji nie zostanie nigdzie zapisany W ten sposoacuteb można pomijać częśćdanych

Maksymalna szerokość pola przyjmuje postać dodatniej liczby całkowitej zaczynającej się od cyfryroacuteżnej od zera Określa ona ile maksymalnie znakoacutew dany format może odczytać Jest to szczegoacutelnieprzydatne przy odczytywaniu ciągu znakoacutew gdyż dzięki temu można podać wielkość tablicy (minusjeden) i tym samym uniknąć błędoacutew przepełnienia bufora

Rozmiar argumentu

Dla formatoacutew d i o u x i n można użyć jednego ze modyfikator rozmiaru

hh mdash oznacza że format odnosi się do argumentu typu wskaźnik na signed char lub unsignedchar

h mdash oznacza że format odnosi się do argumentu typu wskaźnik na short lub wskaźnik na unsi-gned short

l (el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long lub wskaźnik naunsigned long

ll (el el) mdash oznacza że format odnosi się do argumentu typu wskaźnik na long long lub wskaźnikna unsigned long long

j mdash oznacza że format odnosi się do argumentu typu wskaźnik na intmax t lub wskaźnik nauintmax t

z mdash oznacza że że format odnosi się do argumentu typu wskaźnik na size t lub odpowiedni typze znakiem

t mdash oznacza że że format odnosi się do argumentu typu wskaźnik na ptrdiff t lub odpowiednityp bez znaku

Dla formatoacutew a e f i g można użyć modyfikatoroacutew rozmiaru

l ktoacutery oznacza że format odnosi się do argumenty typu wskaźnik na double lub

L ktoacutery oznacza że format odnosi się do argumentu typu wskaźnik na long double

Dla formatoacutew c s i [ modyfikator l oznacza że format odnosi się do argumentu typu wskaźnik nawchar t

C6 SCANF 201

Format

Funkcje z rodziny scanf obsługują następujące formaty

d i odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przywywołaniufunkcji strtol z argumentem base roacutewnym odpowiednio dla d lub dla i argument powinienbyć wskaźnikiem na int

o u x odczytuje liczbę całkowitą ktoacuterej format jest taki sam jak oczekiwany format przy wy-wołaniu funkcji strtoul z argumentem base roacutewnym odpowiednio dla o dla u lub dla xargument powinien być wskaźnikiem na unsigned int

a e f g odczytuje liczbę rzeczywistą nieskończoność lub NaN ktoacuterych format jest taki sam jakoczekiwany przy wywołaniu funkcji strtod argument powinien być wskaźnikiem na float

c odczytuje dokładnie tyle znakoacutew ile określono w maksymalnym rozmiarze pola (domyślnie )argument powinien być wskaźnikiem na char

s odczytuje sekwencje znakoacutew nie będących białymi znakami argument powinien być wskaźni-kiem na char

[ odczytuje niepusty ciąg znakoacutew z ktoacuterych każdymusi należeć do określonego zbioru argumentpowinien być wskaźnikiem na char

p odczytuje sekwencje znakoacutew zależną od implementacji odpowiadającą ciągowi wypisywa-nemu przez funkcję printf gdy podano sekwencję p argument powinien być typu wskaźnikna wskaźnik na void

n nie odczytuje żadnych znakoacutew ale zamiast tego zapisuje do podanej zmiennej liczbę odczyta-nych do tej pory znakoacutew argument powinien być typu wskaźnik na int

Słoacutewko więcej o formacie [ Po otwierającym nawiasie następuje ciąg określający znaki jakie mogąwystępować w odczytanym napisie i kończy się on nawiasem zamykającym tj ] Znaki pomiędzynawiasami (tzw scanlist) określają możliwe znaki chyba że pierwszym znakiem jest ˆ mdash woacutewczasw odczytanym ciągu znakoacutew mogą występować znaki nie występujące w scanlist Jeżeli sekwencjazaczyna się od [] lub [ˆ] to ten pierwszy nawias zamykający nie jest traktowany jako koniec sekwencjitylko jak zwykły znak Jeżeli wewnątrz sekwencji występuje znak - (minus) ktoacutery nie jest pierwszymlub drugim jeżeli pierwszym jest ˆ ani ostatnim znakiem zachowanie jest zależne od implementacji

Formaty A E F G i X są roacutewnież dopuszczalne i mają takie same działanie jak a e f g i x

C65 Wartość zwracanaFunkcja zwraca EOF jeżeli nastąpi koniec danych lub błąd odczytu zanim jakiekolwiek konwersje zo-staną dokonane lub liczbę poprawnie wczytanych poacutel (ktoacutera może być roacutewna zero)

202 DODATEK C WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Dodatek D

Składnia

D1 Symbole i słowa kluczoweJęzyk C definiuje pewną ilość słoacutew za pomocą ktoacuterych tworzy się np pętle itp Są to tzw słowakluczowe tzn nie można użyć ich jako nazwy zmiennej czy też stałej (o nich poniżej) Oto lista słoacutewkluczowych języka C (według norm ANSI C z roku oraz ISO C z roku )

203

204 DODATEK D SKŁADNIA

Tablica D1 Symbole i słowa kluczoweSłowo Opis w tym podręcznikuauto Zmiennebreak Instrukcje sterującecase Instrukcje sterującear Zmienneconst Zmienne

continue Instrukcje sterującedefault Instrukcje sterujące

do Instrukcje sterującedouble Zmienneelse Instrukcje sterująceenum Typy złożoneextern Bibliotekifloat Zmiennefor Instrukcje sterującegoto Instrukcje sterująceif Instrukcje sterująceint Zmiennelong Zmienne

register Zmiennereturn Procedury i funkcjeshort Zmiennesigned Zmiennesizeof Zmiennestatic Biblioteki Zmiennestruct Typy złożoneswit Instrukcje sterującetypedef Typy złożoneunion Typy złożone

unsigned Zmiennevoid Wskaźniki

volatile Zmiennewhile Instrukcje sterujące

D2 POLSKIE ZNAKI 205

Specyfikacja ISO C z roku dodaje następujące słowa

Bool

Complex

Imaginary

inline

restrict

D2 Polskie znakiPisząc program możemy stosować polskie litery (tj ldquoąćęłńoacuteśźżrdquo) tylko w

komentarzach

ciągach znakoacutew (łańcuchach)

Niedopuszczalne jest stosowanie polskich znakoacutew w innych miejscach

D3 Operatory

D31 Operatory arytmetyczneSą to operatory wykonujące znane wszystkim dodawanie odejmowanie itp

operator znaczenie+ dodawanie- odejmowanie mnożenie dzielenie dzielenie modulo mdash daje w wyniku samą resztę z dzielenia= operator przypisania mdash wykonuje działanie po prawej stronie i wynik

przypisuje obiektowi po lewej

D32 Operatory logiczneSłużą poroacutewnaniu zawartości dwoacutech zmiennych według określonych kryterioacutew

Operator Rodzaj poroacutewnania== czy roacutewnegt większygt= większy bądź roacutewnylt mniejszylt= mniejszy bądź roacutewny= czy roacuteżny(nieroacutewny)

Są jeszcze operatory służące do grupowania poroacutewnań (patrz też logika w Wikipedii)

|| lub(OR)ampamp ioraz(AND) negacja(NOT)

206 DODATEK D SKŁADNIA

D33 Operatory binarne

Są to operatory ktoacutere działają na bitach

operator funkcja przykład| suma bitowa(OR) 5 | 2 da w wyniku 7 ( 00000101 OR 00000010 =

00000111)amp iloczyn bitowy 7 amp 2 da w wyniku 2 ( 00000111 AND 00000010

= 00000010)~ negacja bitowa 2 da wwyniku 253 (NOT 00000010 = 11111101

)gtgt przesunięcie bitoacutew o X w prawo 7 gtgt 2 da w wyniku 1 ( 00000111 gtgt 2 =

00000001)ltlt przesunięcie bitoacutew o X w lewo 7 ltlt 2 da w wyniku 28 ( 00000111 ltlt 2 =

00011100)^ alternatywa wyłączna 7 ˆ 2 da w wyniku 5 ( 00000111 ˆ 00000010 =

00000101)

D34 Operatory inkrementacjidekrementacji

Służą do dodawaniaodejmowania od liczby wartości jeden

Przykłady

Operacja Opis operacji Wartość wyrażeniax++ zwiększy wartość w x o jeden wartość zmiennej x przed zmianą++x zwiększy wartość w x o jeden wartość zmiennej x powiększona o jedenxndash zmniejszy wartość w x o jeden wartość zmiennej x przed zmianąndashx zmniejszy wartość w x o jeden wartość zmiennej x pomniejszona o jeden

Parę przykładoacutew dla zrozumienia

int a=7if ((a++)==7) najpierw poroacutewnuje potem dodaje

printf (dna) wypisze 8 if ((++a)==9) najpierw dodaje potem poroacutewnuje

printf (dn a) wypisze 9

Analogicznie ma się sytuacja z operatorami dekrementacji

D4 TYPY DANYCH 207

D35 PozostałeOperacja Opis operacji Wartość wyrażenia

x operator wyłuskania dla wskaźnika wartość trzymana w pamięci pod adre-sem przechowywanym we wskaźniku

ampx operator pobrania adresu zwraca adres zmiennejx[a] operator wybrania elementu tablicy zwraca element tablicy o indeksie a

(numerowanym od zera)xa operator wyboru składnika a ze zmien-

nej xwybiera składnik ze struktury lub unii

x-gta operator wyboru składnika a przezwskaźnik do zmiennej x

wybiera składnik ze struktury gdy uży-wamy wskaźnika do struktury zamiastzwykłej zmiennej

sizeof (typ) operator pobrania rozmiaru typu zwraca rozmiar typu w bajtachsizeof wyrażenie operator pobrania rozmiaru typu zwraca rozmiar typu rezultatu wyraże-

nia

D36 Operator ternarnyIstnieje jeden operator przyjmujący trzy argumenty mdash jest to operator wyrażenia warunkowego a b c Zwraca on b gdy a jest prawdą lub c w przeciwnym wypadku

D4 Typy dany

Tablica D Typy danych według roacuteżnych specyfikacji języka C

Typ Opis Inne nazwyTypy dany wg norm C i C

ar Służy głoacutewnie do przechowywania znakoacutew Od kom-pilatora zależy czy jest to liczba ze znakiem czy bez wwiększości kompilatoroacutew jest liczbą ze znakiem

signed ar Typ char ze znakiemunsigned ar Typ char bez znakushort Występuje gdy docelowa maszyna wyszczegoacutelnia

kroacutetki typ danych całkowitych w przeciwnym wy-padku jest tożsamy z typem int Często ma rozmiarjednego słowa maszynowego

short int signed shortsigned short int

unsigned short Liczba typu short bez znaku Podobnie jak short uży-wana do zredukowania zużycia pamięci przez program

unsigned short int

int Liczba całkowita odpowiadająca podstawowemu roz-miarowi liczby całkowitej w danym komputerze Pod-stawowy typ dla liczb całkowitych

signed int signed

unsigned Liczba całkowita bez znaku unsigned intlong Długa liczba całkowita long int signed long

signed long intunsigned long Długa liczba całkowita bez znaku unsigned long intfloat Podstawowy typ do przechowywania liczb zmienno-

przecinkowych W nowszym standardzie zgodny jest znormą IEEE Nie można stosować go z modyfika-torem signed ani unsigned

double Liczba zmiennoprzecinkowa podwoacutejnej precyzji Po-dobnie jak float nie łączy się z modyfikatorem signedani unsigned

208 DODATEK D SKŁADNIA

long double Największa możliwa dokładność liczb zmiennoprzecin-kowych Nie łączy się z modyfikatorem signed aniunsigned

Typy dany według normy CBool Przechowuje wartości lub long long Nowy typ umożliwiający obliczeniach na bardzo du-

żych liczbach całkowitych bez użycia typu floatlong long int signedlong long signed longlong int

unsigned long long Długie liczby całkowite bez znaku unsigned long long intfloat Complex Słuzy do przechowywania liczb zespolonychdouble Complex Słuzy do przechowywania liczb zespolonychlong double Complex Słuzy do przechowywania liczb zespolonych

Typy dany definiowane przez użytkownikastruct Więcej o kompilowaniuunion Rozmiar typu jest taki jak rozmiar największego polatypedef Nowo zdefiniowany typ przyjmuje taki sam rozmiar

jak typ macierzystyenum Zwykle elementy mają taką samą długość jak typ int

Zależności rozmiaru typoacutew danych są następujące

sizeof(cokolwiek) = sizeof(signed cokolwiek) = sizeof(unsigned cokolwiek)

= sizeof(ar) le sizeof(short) le sizeof(int) le sizeof(long) le sizeof(long long)

sizeof(float) le sizeof(double) le sizeof(long double)

sizeof(cokolwiek Complex) = sizeof(cokolwiek)

sizeof(void ) = sizeof(ar ) ge sizeof(cokolwiek )

sizeof(cokolwiek ) = sizeof(signed cokolwiek ) = sizeof(unsigned cokolwiek )

sizeof(cokolwiek ) = sizeof(const cokolwiek )

Dodatkowo jeżeli przez V(typ) oznaczymy liczbę bitoacutew wykorzystywanych w typie to zachodzi

le V(ar) = V(signed ar) = V(unsigned ar)

le V(short) = V(unsigned short)

le V(int) = V(unsigned int)

le V(long) = V(unsigned long)

le V(long long) = V(unsigned long long)

V(ar) le V(short) le V(int) le V(long) le V(long long)

Dodatek E

Przykłady z komentarzem

E01 Liczby losowePoniższy program generuje wiersz po wierszu macierz o określonych przez użytkownika wymiarachzawierającą losowo wybrane liczby Każdy wygenerowany wiersz macierzy zapisywany jest w plikutekstowym o wprowadzonej przez użytkownika nazwie W pierwszym wierszu pliku wynikowego za-pisano wymiary utworzonej macierzy Program napisany i skompilowany został w środowisku GNU-Linux

include ltstdiohgtinclude ltstdlibhgt dla funkcji rand() oraz srand() include lttimehgt dla funkcji [time()

main()

int i j n mfloat reFILE fpchar fileName[128]

printf(Wprowadz nazwe pliku wynikowegon)scanf(sampfileName)

printf(Wprowadz po sobie liczbe wierszy i kolumn macierzy oddzielone spacjąn)scanf(d d ampn ampm)

jeżeli wystąpił błąd w otwieraniu pliku i go nie otwartowoacutewczas funkcja fclose(fp) wywołana na końcu programu zgłosi błądwykonania i wysypie nam program z działania stąd musimy umieścićwarunek ktoacutery w kontrolowany sposoacuteb zatrzyma program (funkcja exit)

if ( (fp = fopen(fileName w)) == NULL )

puts(Otwarcie pliku nie jest mozliwe)exit jeśli w procedurze glownej

to piszemy bez nawiasow

else puts(Plik otwarty prawidłowo)

209

210 DODATEK E PRZYKŁADY Z KOMENTARZEM

fprintf(fp d dn n m) w pierwszym wierszu umieszczono wymiary macierzy

srand( (unsigned int) time(0) )for (i=1 ilt=n ++i)

for (j=1 jlt=m ++j)re = ((rand() 200)-100) 100fprintf(fp1f re )if (j=m) fprintf(fp )

fprintf(fpn)fclose(fp)return 0

E02 Zamiana liczb dziesiętny na liczby w systemie dwoacutejkowym

Zajmijmy się teraz innym zagadnieniem Wiemy że komputer zapisuje wszystkie liczby w postacibinarnej (czyli za pomocą jedynek i zer) Sproacutebujmy zatem zamienić liczbę zapisaną w ldquonaszymrdquo dzie-siątkowym systemie na zapis binarny Uwaga Program działa jedynie dla liczb od do maksymalnejwartości ktoacuterą może przyjąć typ unsigned short int w twoim kompilatorze

include ltstdiohgtinclude ltlimitshgt

void dectobin (unsigned short a)

int licznik

CHAR_BIT to liczba bitoacutew w bajcie licznik = CHAR_BIT sizeof(a)while (--licznik gt= 0)

putchar(((a gtgt licznik) amp 1)) 1 0)

int main ()

unsigned short a

printf (Podaj liczbę od 0 do hd USHRT_MAX)scanf (hd ampa)printf (hd(10) = a)dectobin(a)printf ((2)n)

return 0

211

E03 Zalążek przeglądarki

Zajmiemy się tym razem inną kwestią a mianowicie programowaniem sieci Jest to zagadnienie bar-dzo ostatnio popularne Nasz program będzie miał za zadanie połączyć się z serwerem ktoacuterego adresużytkownik będzie podawał jako pierwszy parametr programu wysłać zapytanie HTTP i odebrać treśćktoacuterą wyśle do nas serwer Zacznijmy może od tego że obsługa sieci jest niemal identyczna w roacuteżnychsystemach operacyjnych Na przykład między systemami z rodziny Unix oraz Windowsem roacuteżnica po-lega tylko na dołączeniu innych plikoacutew nagłoacutewkowych (dla Windowsa mdash winsockh) Przeanalizujmyzatem poniższy kod

include ltstdiohgtinclude ltstdlibhgtinclude ltstringhgtinclude ltunistdhgtinclude ltarpainethgtinclude ltsystypeshgtinclude ltnetinetinhgtinclude ltsyssockethgt

define MAXRCVLEN 512define PORTNUM 80

char query = GET HTTP11nn

int main(int argc char argv[])

char buffer[MAXRCVLEN+1]int len mysocketstruct sockaddr_in destchar host_ip = NULLif (argc = 2)

printf (Podaj adres serweran)exit (1)

host_ip = argv[1]mysocket = socket(AF_INET SOCK_STREAM 0)

destsin_family = AF_INETdestsin_addrs_addr = inet_addr(host_ip) ustawiamy adres hosta destsin_port = htons (PORTNUM) numer portu przechowuje dwubajtowa zmienna -

musimy ustalić porządek sieciowy - Big Endian memset(amp(destsin_zero) 0 8) zerowanie reszty struktury

connect(mysocket (struct sockaddr )ampdestsizeof(struct sockaddr)) łączymy się z hostem write (mysocket query strlen(query)) wysyłamy zapytanie len=read(mysocket buffer MAXRCVLEN) i pobieramy odpowiedź

buffer[len]=0

printf(Rcvd sbuffer)close(mysocket) zamykamy gniazdo return EXIT_SUCCESS

212 DODATEK E PRZYKŁADY Z KOMENTARZEM

Powyższy przykład może być odrobinę niezrozumiały dlatego przyda się kilka słoacutew wyjaśnieniaPliki nagłoacutewkowe ktoacutere dołączamy zawierają deklarację nowych dla Ciebie funkcji mdash socket() con-nect() write() oraz read() Oproacutecz tego spotkałeś się z nową strukturą mdash sockaddr in Wszystkie teobiekty są niezbędne do stworzenia połączenia

Dodatek F

Informacje o pliku i historia

F1 HistoriaTa książka została stworzona na polskojęzycznej wersji projektu Wikibooks przez autoroacutew wymie-nionych poniżej w sekcji Autorzy Najnowsza wersja podręcznika jest dostępna pod adresem httpplwikibooksorgwikiC

F2 Informacje o pliku i historia został utworzony przez Derbetha dnia listopada na podstawie wersji z listopada podręcznika na Wikibooks Wykorzystany został poprawiony program WikiLaTeX autorstwa użyt-kownika angielskichWikibooks Hagindaza Wynikowy kod po ręcznych poprawkach został przekształ-cony w książkę za pomocą systemu składu XeLaTeX Wykorzystano wolną dostępną na licencjach i czcionkę Linux Libertine oraz wolną czcionkę DejaVu Sans Mono

Najnowsza wersja tego -u jest postępna pod adresem httpplwikibooksorgwikiImageCpdf

F3 AutorzyAdam majewski Adiblol Akira Albmont Ananas Arfrever BartekChom Bercik Bla Bociex CathyRichards Cnr CzarnyInaczej CzarnyZajaczek DaniXTeam Derbeth Equadus Faw GDR GangGk Gynvael Incuś Karol Ossowski Kazet Kj Lethern MTM Marcin MastiBot MeaglinMerdis Michael Migol Mina MonteChristof Mt Myki Mythov Narf Noisy Norill PawelkgPawlosck Peter de Sowaro Piotr Pkierski Ponton Przykuta RedRad Sasek Sblive Silbarad T zielWarszk Webprog Wentuq ZiomekPL Zjem ci chleb i anonimowi autorzy

F4 GrafikiAutorzy i licencje grafik

grafika na okładce Saint-Elme Gautier rycina z książki Le Corset agrave travers les acircges Paryż źroacutedło Wikimedia Commons public domain

logoWikibooks zastrzeżony znak towarowycopy ampAll rights reserved Wikimedia FoundationInc

grafika a (strona ) autor Claudio Rocchini źroacutedło Wikimedia Commons licencja

grafika b (strona ) autor Adam majewski źroacutedło Wikimedia Commons licencja CreativeCommons Aribution Unported

213

214 DODATEK F INFORMACJE O PLIKU

grafia (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

grafika (strona ) autor Daniel B źroacutedo Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Daniel B źroacutedło Wikimedia Commons licencja

grafika (strona ) autor Derrick Coetzee źroacutedło Wikimedia Commons public domain

grafika (strona ) autor Jarkko Piiroinen źroacutedo WikimediaCommons public domain

Indeks

adres alternatywa

biblioteka standardowa big endian blok

Cjęzyk

dekrementacja dynamiczna alokacja pamięci

enum

funkcja definicja deklaracja rekurencyjna

inkrementacja

komentarz kompilacja

warunkowa kompilator

lista używanie

koniunkcja konwersja

libc lile endian Porownaj big endian

main makefile

napis poroacutewnywanie

negacja

operatordekrementacji inkrementacji modulo

pobrania adresu sizeof wyrażenia warunkowego wyłuskania

plikczytanie i pisanie nagłowkowy

porządek bajtoacutew prawda i fałsz preprocesor procedury prototyp funkcji przekazywanie argumentoacutew do funkcji

przez wartość przez wskaźnik

przepełnienie bufora przesunięcie bitowe

rzutowanie

sizeof stała struktura słowa kluczowe

tablica wielowymiarowa znakoacutew

typ definiowanie wyliczeniowy

unia

Valgrind void

jako typ zwracany na liście argumentoacutew void

volatile

wejściewyjście wskaźnik wyciek pamięci

215

216 INDEKS

wyroacutewnywanie

zmienna globalna lokalna statyczna

znaki specjalne

  • O podręczniku
    • O czym moacutewi ten podręcznik
    • Co trzeba wiedzieć żeby skorzystać z niniejszego podręcznika
    • Konwencje przyjęte w tym podręczniku
    • Czy mogę pomoacutec
    • Autorzy
    • Źroacutedła
      • O języku C
        • Historia C
        • Zastosowania języka C
        • Przyszłość C
          • Czego potrzebujesz
            • Czego potrzebujesz
            • Zintegrowane Środowiska Programistyczne
            • Dodatkowe narzędzia
              • Używanie kompilatora
                • GCC
                • Borland
                • Czytanie komunikatoacutew o błędach
                  • Pierwszy program
                    • Twoacutej pierwszy program
                    • Rozwiązywanie problemoacutew
                      • Podstawy
                        • Kompilacja Jak działa C
                        • Co może C
                        • Struktura blokowa
                        • Zasięg
                        • Funkcje
                        • Biblioteki standardowe
                        • Komentarze i styl
                        • Preprocesor
                        • Nazwy zmiennych stałych i funkcji
                          • Zmienne
                            • Czym są zmienne
                            • Typy zmiennych
                            • Specyfikatory
                            • Modyfikatory
                            • Uwagi
                              • Operatory
                                • Przypisanie
                                • Rzutowanie
                                • Operatory arytmetyczne
                                • Operacje bitowe
                                • Poroacutewnanie
                                • Operatory logiczne
                                • Operator wyrażenia warunkowego
                                • Operator przecinek
                                • Operator sizeof
                                • Inne operatory
                                • Priorytety i kolejność obliczeń
                                • Kolejność wyliczania argumentoacutew operatora
                                • Uwagi
                                • Zobacz też
                                  • Instrukcje sterujące
                                    • Instrukcje warunkowe
                                    • Pętle
                                    • Instrukcja goto
                                    • Natychmiastowe kończenie programu --- funkcja exit
                                    • Uwagi
                                      • Podstawowe procedury wejścia i wyjścia
                                        • Wejściewyjście
                                        • Funkcje wyjścia
                                        • Funkcja puts
                                        • Funkcja fputs
                                        • Funkcje wejścia
                                          • Funkcje
                                            • Tworzenie funkcji
                                            • Wywoływanie
                                            • Zwracanie wartości
                                            • Zwracana wartość
                                            • Funkcja main()
                                            • Dalsze informacje
                                            • Zobacz też
                                              • Preprocesor
                                                • Wstęp
                                                • Dyrektywy preprocesora
                                                • Predefiniowane makra
                                                  • Biblioteka standardowa
                                                    • Czym jest biblioteka
                                                    • Po co nam biblioteka standardowa
                                                    • Gdzie są funkcje z biblioteki standardowej
                                                    • Opis funkcji biblioteki standardowej
                                                    • Uwagi
                                                      • Czytanie i pisanie do plikoacutew
                                                        • Pojęcie pliku
                                                        • Identyfikacja pliku
                                                        • Podstawowa obsługa plikoacutew
                                                        • Rozmiar pliku
                                                        • Przykład --- pliki graficzny
                                                        • Co z katalogami
                                                          • Ćwiczenia dla początkujących
                                                            • Ćwiczenia
                                                              • Tablice
                                                                • Wstęp
                                                                • Odczytzapis wartości do tablicy
                                                                • Tablice znakoacutew
                                                                • Tablice wielowymiarowe
                                                                • Ograniczenia tablic
                                                                • Ciekawostki
                                                                  • Wskaźniki
                                                                    • Co to jest wskaźnik
                                                                    • Operowanie na wskaźnikach
                                                                    • Arytmetyka wskaźnikoacutew
                                                                    • Tablice a wskaźniki
                                                                    • Gdy argument jest wskaźnikiem
                                                                    • Pułapki wskaźnikoacutew
                                                                    • Na co wskazuje NULL
                                                                    • Stałe wskaźniki
                                                                    • Dynamiczna alokacja pamięci
                                                                    • Wskaźniki na funkcje
                                                                    • Możliwe deklaracje wskaźnikoacutew
                                                                    • Popularne błędy
                                                                    • Ciekawostki
                                                                      • Napisy
                                                                        • Łańcuchy znakoacutew w języku C
                                                                        • Operacje na łańcuchach
                                                                        • Bezpieczeństwo kodu a łańcuchy
                                                                        • Konwersje
                                                                        • Operacje na znakach
                                                                        • Częste błędy
                                                                        • Unicode
                                                                          • Typy złożone
                                                                            • typedef
                                                                            • Typ wyliczeniowy
                                                                            • Struktury
                                                                            • Unie
                                                                            • Inicjalizacja struktur i unii
                                                                            • Wspoacutelne własności typoacutew wyliczeniowych unii i struktur
                                                                            • Studium przypadku --- implementacja listy wskaźnikowej
                                                                              • Biblioteki
                                                                                • Czym jest biblioteka
                                                                                • Jak zbudowana jest biblioteka
                                                                                  • Więcej o kompilowaniu
                                                                                    • Ciekawe opcje kompilatora GCC
                                                                                    • Program make
                                                                                    • Optymalizacje
                                                                                    • Kompilacja krzyżowa
                                                                                    • Inne narzędzia
                                                                                      • Zaawansowane operacje matematyczne
                                                                                        • Biblioteka matematyczna
                                                                                        • Prezentacja liczb rzeczywistych w pamięci komputera
                                                                                        • Liczby zespolone
                                                                                          • Powszechne praktyki
                                                                                            • Konstruktory i destruktory
                                                                                            • Zerowanie zwolnionych wskaźnikoacutew
                                                                                            • Konwencje pisania makr
                                                                                            • Jak dostać się do konkretnego bitu
                                                                                            • Skroacutety notacji
                                                                                              • Przenośność programoacutew
                                                                                                • Niezdefiniowane zachowanie i zachowanie zależne od implementacji
                                                                                                • Rozmiar zmiennych
                                                                                                • Porządek bajtoacutew i bitoacutew
                                                                                                • Biblioteczne problemy
                                                                                                • Kompilacja warunkowa
                                                                                                  • Łączenie z innymi językami
                                                                                                    • Język C i Asembler
                                                                                                    • C++
                                                                                                      • Indeks alfabetyczny
                                                                                                      • Indeks tematyczny
                                                                                                        • asserth
                                                                                                        • ctypeh
                                                                                                        • errnoh
                                                                                                        • floath
                                                                                                        • limitsh
                                                                                                        • localeh
                                                                                                        • mathh
                                                                                                        • setjmph
                                                                                                        • signalh
                                                                                                        • stdargh
                                                                                                        • stddefh
                                                                                                        • stdioh
                                                                                                        • stdlibh
                                                                                                        • stringh
                                                                                                        • timeh
                                                                                                          • Wybrane funkcje biblioteki standardowej
                                                                                                            • assert
                                                                                                            • atoi
                                                                                                            • isalnum
                                                                                                            • malloc
                                                                                                            • printf
                                                                                                            • scanf
                                                                                                              • Składnia
                                                                                                                • Symbole i słowa kluczowe
                                                                                                                • Polskie znaki
                                                                                                                • Operatory
                                                                                                                • Typy danych
                                                                                                                  • Przykłady z komentarzem
                                                                                                                  • Informacje o pliku
                                                                                                                    • Historia
                                                                                                                    • Informacje o pliku PDF i historia
                                                                                                                    • Autorzy
                                                                                                                    • Grafiki
                                                                                                                      • Skorowidz
Page 7: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 8: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 9: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 10: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 11: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 12: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 13: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 14: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 15: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 16: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 17: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 18: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 19: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 20: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 21: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 22: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 23: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 24: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 25: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 26: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 27: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 28: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 29: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 30: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 31: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 32: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 33: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 34: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 35: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 36: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 37: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 38: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 39: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 40: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 41: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 42: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 43: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 44: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 45: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 46: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 47: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 48: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 49: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 50: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 51: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 52: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 53: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 54: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 55: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 56: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 57: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 58: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 59: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 60: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 61: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 62: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 63: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 64: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 65: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 66: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 67: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 68: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 69: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 70: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 71: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 72: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 73: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 74: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 75: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 76: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 77: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 78: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 79: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 80: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 81: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 82: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 83: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 84: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 85: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 86: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 87: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 88: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 89: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 90: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 91: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 92: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 93: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 94: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 95: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 96: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 97: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 98: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 99: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 100: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 101: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 102: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 103: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 104: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 105: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 106: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 107: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 108: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 109: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 110: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 111: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 112: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 113: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 114: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 115: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 116: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 117: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 118: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 119: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 120: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 121: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 122: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 123: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 124: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 125: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 126: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 127: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 128: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 129: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 130: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 131: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 132: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 133: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 134: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 135: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 136: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 137: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 138: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 139: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 140: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 141: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 142: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 143: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 144: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 145: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 146: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 147: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 148: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 149: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 150: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 151: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 152: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 153: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 154: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 155: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 156: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 157: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 158: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 159: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 160: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 161: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 162: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 163: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 164: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 165: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 166: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 167: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 168: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 169: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 170: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 171: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 172: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 173: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 174: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 175: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 176: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 177: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 178: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 179: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 180: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 181: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 182: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 183: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 184: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 185: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 186: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 187: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 188: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 189: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 190: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 191: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 192: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 193: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 194: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 195: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 196: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 197: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 198: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 199: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 200: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 201: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 202: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 203: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 204: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 205: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 206: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 207: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 208: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 209: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 210: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 211: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 212: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 213: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 214: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ
Page 215: ProgramowaniewC12 ROZDZIAŁ1. OPODRĘCZNIKU Innegorodzajuprzykłady,dialogużytkownikazkonsoląiprogramem,wejście/wyjście programu,informacjeteoretycznebędąwyglądałytak: typ