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

Post on 23-Mar-2021

1 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

Transcript

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

    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

      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

        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

          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

            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

              top related