You are on page 1of 223

Programowanie w C

Stworzone na Wikibooks, bibliotece wolnych podrcznikw.

Wydanie I z dnia 2 wrzenia 2008 Copyright c 2004-2008 uytkownicy Wikibooks. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled GNU Free Documentation License. Udziela si zezwolenia na kopiowanie, rozpowszechnianie i/lub modykacj treci artykuw polskich Wikibooks zgodnie z zasadami Licencji GNU Wolnej Dokumentacji (GNU Free Documentation License) w wersji 1.2 lub dowolnej pniejszej opublikowanej przez Free Software Foundation; bez Sekcji Niezmiennych, Tekstu na Przedniej Okadce i bez Tekstu na Tylnej Okadce. Kopia tekstu licencji znajduje si w czci zatytuowanej GNU Free Documentation License. Dodatkowe objanienia s podane w dodatku Dalsze wykorzystanie tej ksiki. Wikibooks nie udziela adnych gwarancji, zapewnie ani obietnic dotyczcych poprawnoci publikowanych treci. Nie udziela te adnych innych gwarancji, zarwno jednoznacznych, jak i dorozumianych.

Spis treci
1 O podrczniku O czym mwi ten podrcznik? . . . . . . . . . . . . . . . . . . Co trzeba wiedzie, eby skorzysta z niniejszego podrcznika? Konwencje przyjte w tym podrczniku . . . . . . . . . . . . . Czy mog pomc? . . . . . . . . . . . . . . . . . . . . . . . . . Autorzy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . rda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 11 11 12 12 12

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

2 O jzyku C 13 Historia C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Zastosowania jzyka C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Przyszo C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3 Czego potrzebujesz 17 Czego potrzebujesz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Zintegrowane rodowiska Programistyczne . . . . . . . . . . . . . . . . . . . . 18 Dodatkowe narzdzia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4 Uywanie kompilatora 19 GCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Borland . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Czytanie komunikatw o bdach . . . . . . . . . . . . . . . . . . . . . . . . . 20 5 Pierwszy program 23 Twj pierwszy program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Rozwizywanie problemw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 6 Podstawy Kompilacja: Jak dziaa C? Co moe C? . . . . . . . . Struktura blokowa . . . . Zasig . . . . . . . . . . . Funkcje . . . . . . . . . . Biblioteki standardowe . . Komentarze i styl . . . . . Preprocesor . . . . . . . . Nazwy zmiennych, staych 27 27 27 28 29 29 29 30 31 31

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i funkcji

. . . . . . . . .

. . . . . . . . .

. . . . . . . . . 3

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

7 Zmienne Czym s zmienne? Typy zmiennych . Specykatory . . . Modykatory . . . Uwagi . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

33 33 36 38 40 41 43 43 44 45 46 48 49 50 51 51 51 52 53 54 54 55 55 58 63 64 64 65 65 66 67 68 69 75 76 77 78 79 80 85 87 87 87 93

8 Operatory Przypisanie . . . . . . . . . . . . . . . . . . Rzutowanie . . . . . . . . . . . . . . . . . . Operatory arytmetyczne . . . . . . . . . . . Operacje bitowe . . . . . . . . . . . . . . . Porwnanie . . . . . . . . . . . . . . . . . . Operatory logiczne . . . . . . . . . . . . . . Operator wyraenia warunkowego . . . . . . Operator przecinek . . . . . . . . . . . . . . Operator sizeof . . . . . . . . . . . . . . . . Inne operatory . . . . . . . . . . . . . . . . Priorytety i kolejno oblicze . . . . . . . . Kolejno wyliczania argumentw operatora Uwagi . . . . . . . . . . . . . . . . . . . . . Zobacz te . . . . . . . . . . . . . . . . . . . 9 Instrukcje sterujce Instrukcje warunkowe . . Ptle . . . . . . . . . . . . Instrukcja goto . . . . . . Natychmiastowe koczenie Uwagi . . . . . . . . . . . 10 Podstawowe procedury Wejcie/wyjcie . . . . . . Funkcje wyjcia . . . . . . Funkcja puts . . . . . . . Funkcja fputs . . . . . . . Funkcje wejcia . . . . . . 11 Funkcje Tworzenie funkcji . Wywoywanie . . . Zwracanie wartoci Funkcja main() . . Dalsze informacje . Zobacz te . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . programu . . . . . . wejcia . . . . . . . . . . . . . . . . . . . . . . . . . i . . . . .

. . . . . . . . . . . . . . . . . . funkcja . . . . . . wyjcia . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . exit . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

12 Preprocesor Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dyrektywy preprocesora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Predeniowane makra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

13 Biblioteka standardowa Czym jest biblioteka? . . . . . . . . . . . . Po co nam biblioteka standardowa? . . . . . Gdzie s funkcje z biblioteki standardowej? Opis funkcji biblioteki standardowej . . . . Uwagi . . . . . . . . . . . . . . . . . . . . . 14 Czytanie i pisanie do plikw Pojcie pliku . . . . . . . . . . . Identykacja pliku . . . . . . . . Podstawowa obsuga plikw . . . Rozmiar pliku . . . . . . . . . . . Przykad pliki graczny . . . Co z katalogami? . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

95 95 95 96 96 96 97 97 97 97 101 101 102

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

15 wiczenia dla pocztkujcych 103 wiczenia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 16 Tablice Wstp . . . . . . . . . . . . . . . Odczyt/zapis wartoci do tablicy Tablice znakw . . . . . . . . . . Tablice wielowymiarowe . . . . . Ograniczenia tablic . . . . . . . . Ciekawostki . . . . . . . . . . . . 105 105 107 107 108 108 109 111 111 112 115 116 117 118 118 119 120 123 126 126 127 129 129 132 134 137 137 138 138

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

17 Wskaniki Co to jest wskanik? . . . . . . . . Operowanie na wskanikach . . . . Arytmetyka wskanikw . . . . . . Tablice a wskaniki . . . . . . . . . Gdy argument jest wskanikiem... . Puapki wskanikw . . . . . . . . Na co wskazuje NULL? . . . . . . Stae wskaniki . . . . . . . . . . . Dynamiczna alokacja pamici . . . Wskaniki na funkcje . . . . . . . . Moliwe deklaracje wskanikw . . Popularne bdy . . . . . . . . . . Ciekawostki . . . . . . . . . . . . . 18 Napisy acuchy znakw w jzyku C . . Operacje na acuchach . . . . . Bezpieczestwo kodu a acuchy Konwersje . . . . . . . . . . . . . Operacje na znakach . . . . . . . Czste bdy . . . . . . . . . . . Unicode . . . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . . 5

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

19 Typy zoone typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . Typ wyliczeniowy . . . . . . . . . . . . . . . . . . . . . . . Unie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Struktury . . . . . . . . . . . . . . . . . . . . . . . . . . . Wsplne wasnoci typw wyliczeniowych, unii i struktur Studium przypadku implementacja listy wskanikowej

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

141 141 141 142 143 143 145

20 Biblioteki 151 Czym jest biblioteka . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 Jak zbudowana jest biblioteka . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 21 Wicej o kompilowaniu Ciekawe opcje kompilatora GCC Program make . . . . . . . . . . Optymalizacje . . . . . . . . . . . Kompilacja skrona . . . . . . . . Inne narzdzia . . . . . . . . . . 155 155 155 157 158 159

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

22 Zaawansowane operacje matematyczne Biblioteka matematyczna . . . . . . . . . . . . . . . . . . . . . . . . . . . . Prezentacja liczb rzeczywistych w pamici komputera . . . . . . . . . . . . Liczby zespolone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Powszechne praktyki Konstruktory i destruktory . . . . . Zerowanie zwolnionych wskanikw . Konwencje pisania makr . . . . . . . Jak dosta si do konkretnego bitu? Skrty notacji . . . . . . . . . . . . .

161 . 161 . 162 . 162 165 165 166 167 167 169 171 171 172 173 175 175

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

24 Przenono programw Niezdeniowane zachowanie i zachowanie zalene od implementacji Rozmiar zmiennych . . . . . . . . . . . . . . . . . . . . . . . . . . . Porzdek bajtw i bitw . . . . . . . . . . . . . . . . . . . . . . . . Biblioteczne problemy . . . . . . . . . . . . . . . . . . . . . . . . . Kompilacja warunkowa . . . . . . . . . . . . . . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

25 czenie z innymi jzykami 177 Jzyk C i Asembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 A Indeks alfabetyczny B Indeks tematyczny assert.h . . . . . . . . ctype.h . . . . . . . . errno.h . . . . . . . . . oat.h . . . . . . . . . limits.h . . . . . . . . locale.h . . . . . . . . 181 183 183 183 183 183 183 184

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . . 6

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

math.h . setjmp.h signal.h stdarg.h stddef.h stdio.h . stdlib.h string.h time.h .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

184 185 185 185 185 185 186 186 186 189 189 190 191 193 195 199 203 203 205 205 207 209 213 213 213 213 213

C Wybrane assert . . atoi . . . isalnum . malloc . . printf . . scanf . . .

funkcje biblioteki standardowej . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

D Skadnia Symbole i sowa kluczowe Polskie znaki . . . . . . . Operatory . . . . . . . . . Typy danych . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

E Przykady z komentarzem F Informacje o pliku Historia . . . . . . . . . . . . . . Informacje o pliku PDF i historia Autorzy . . . . . . . . . . . . . . Graki . . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

G Dalsze wykorzystanie tej ksiki 215 Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 Status prawny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 Wykorzystywanie materiaw z Wikibooks . . . . . . . . . . . . . . . . . . . . 215 H GNU Free Documentation License Skorowidz 217 223

Spis tabel
8.1 Priorytety operatorw . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

D.1 Symbole i sowa kluczowe . . . . . . . . . . . . . . . . . . . . . . . . . . 204 D.2 Typy danych wedug rnych specykacji jzyka C . . . . . . . . . . . . 207

Rozdzia 1

O podrczniku
O czym mwi ten podrcznik?
Niniejszy podrcznik stanowi przewodnik dla pocztkujcych programistw po jzyku programowania C.

Co trzeba wiedzie, eby skorzysta z niniejszego podrcznika?


Ten podrcznik ma nauczy programowania w C od podstaw do poziomu zaawansowanego. Do zrozumienia rozdziau dla pocztkujcych wymagana jest jedynie znajomo podstawowych poj z zakresu algebry oraz terminw komputerowych. Dowiadczenie w programowaniu w innych jzykach bardzo pomaga, ale nie jest konieczne.

Konwencje przyjte w tym podrczniku


Informacje wane oznaczamy w nastpujcy sposb: Wana informacja! Dodatkowe informacje, ktre odrobin wykraczaj poza zakres podrcznika, a take wyjaniaj kwestie niezwizane bezporednio z jzykiem C oznaczamy tak: Wyjanienie Ponadto kod w jzyku C bdzie prezentowany w nastpujcy sposb: #include <stdio.h> int main (int argc, char *argv[]) { return 0; } 11

12

ROZDZIA 1. O PODRCZNIKU

Innego rodzaju przykady, dialog uytkownika z konsol i programem, wejcie / wyjcie programu, informacje teoretyczne bd wyglday tak: typ zmienna = warto;

Czy mog pomc?


Oczywicie e moesz. Mao tego, bdziemy zadowoleni z kadej pomocy moesz pisa rozdziay lub tumaczy je z angielskiej wersji tego podrcznika. Nie musisz pyta si nikogo o zgod jeli chcesz, moesz zacz ju teraz. Prosimy jedynie o zapoznanie si ze stylem podrcznika, uytymi w nim szablonami i zachowanie ukadu rozdziaw. Propozycje zmiany spisu treci naley zgasza na stronie dyskusji. Jeli znalaze jaki bd a nie umiesz go poprawi, koniecznie powiadom o tym fakcie autorw tego podrcznika za pomoc strony dyskusji danego moduu ksiki. Dziki temu przyczyniasz si do rozwoju tego podrcznika.

Autorzy
Istotny wkad w powstanie podrcznika maj: CzarnyZajaczek Derbeth Kj mina86 Dodatkowo w rozwoju podrcznika pomagali midzy innymi: Lrds Noisy

rda
podrcznik C Programming na anglojzycznej wersji Wikibooks, licencja GFDL Brian W. Kernighan, Dennis M. Ritchie, Jzyk ANSI C ISO C Committee Draft, 18 styczna 1999 Bruce Eckel, Thinking in C++. Rozdzia Jzyk C w programie C++.

Rozdzia 2

O jzyku C
Zobacz w Wikipedii: C (jC jest jzykiem programowania wysokiego poziomu. Jego nazw interpretuje si zyk programowania) jako nastpn liter po B (nazwa jego poprzednika), lub drug liter jzyka BCPL (poprzednik jzyka B).

Historia C
W 1947 roku trzej naukowcy z Bell Telephone Laboratories William Shockley, Walter Brattain i John Bardeen stworzyli pierwszy tranzystor; w 1956 roku, w MIT skonstruowano pierwszy komputer oparty wycznie na tranzystorach: TX-O; w 1958 roku Jack Kilby z Texas Instruments skonstruowa ukad scalony. Ale zanim powsta pierwszy ukad scalony, pierwszy jzyk wysokiego poziomu zosta ju napisany. W 1954 powsta Fortran (Formula Translator), ktry zapocztkowa napisanie jzyka Fortran I (1956). Pniej powstay kolejno: Algol 58 Algorithmic Language w 1958 r. Algol 60 (1960) CPL Combined Programming Language (1963) BCPL Basic CPL (1967) B (1969) i C w oparciu o B. B zosta stworzony przez Kena Thompsona z Bell Labs; by to jzyk interpretowany, uywany we wczesnych, wewntrznych wersjach systemu operacyjnego UNIX. Inni pracownicy Bell Labs, Thompson i Dennis Richie, rozwinli B, nazywajc go NB; dalszy rozwj NB da C jzyk kompilowany. Wiksza cz UNIXa zostaa ponownie napisana w NB, a nastpnie w C, co dao w efekcie bardziej przenony system operacyjny. W 1978 roku wydana zostaa ksika pt. The C Programming Language, ktra staa si pierwszym podrcznikiem do nauki jzyka C. Moliwo uruchamiania UNIX-a na rnych komputerach bya gwn przyczyn pocztkowej popularnoci zarwno UNIX-a, jak i C; zamiast tworzy nowy system operacyjny, programici mogli po prostu napisa tylko te czci systemu, ktrych wymaga inny sprzt, oraz napisa kompilator C dla nowego systemu. Odkd wiksza 13

14

ROZDZIA 2. O JZYKU C

cz narzdzi systemowych bya napisana w C, logiczne byo pisanie kolejnych w tym samym jzyku. Kilka z obecnie powszechnie stosowanych systemw operacyjnych takich jak Linux, Microsoft Windows zostay napisane w jzyku C.

Standaryzacje
W 1978 roku Ritchie i Kerninghan opublikowali pierwsz ksik nt. jzyka C The C Programming Language. Owa ksika przez wiele lat bya swoistym wyznacznikiem, jak programowa w jzyku C. Bya wic to niejako pierwsza standaryzacja, nazywana od nazwisk twrcw K&R. Oto nowoci, wprowadzone przez ni do jzyka C w stosunku do jego pierwszych wersji (pochodzcych z pocztku lat 70.): moliwo tworzenia struktur (sowo struct) dusze typy danych (modykator long) liczby cakowite bez znaku (modykator unsigned) zmieniono operator =+ na += Ponadto producenci kompilatorw (zwaszcza AT&T) wprowadzali swoje zmiany, nieobjte standardem: funkcje nie zwracajce wartoci (void) oraz typ void* funkcje zwracajce struktury i unie przypisywanie wartoci strukturom wprowadzenie sowa kluczowego const utworzenie biblioteki standardowej wprowadzenie sowa kluczowego enum Owe nieocjalne rozszerzenia zagroziy spjnoci jzyka, dlatego te powsta standard, regulujcy wprowadzone nowinki. Od 1983 roku trway prace standaryzacyjne, aby w 1989 roku wyda standard C89 (poprawna nazwa to: ANSI X3.159-1989). Niektre zmiany wprowadzono z jzyka C++, jednak rewolucj mia dopiero przynie standard C99, ktry wprowadzi m.in.: funkcje inline nowe typy danych (np. long long int) nowy sposb komentowania, zapoyczony od C++ (//) przechowywanie liczb zmiennoprzecinkowych zostao zaadaptowane do norm IEEE utworzono kilka nowych plikw nagwkowych (stdbool.h, inttypes.h) Na dzie dzisiejszy norm obowizujc jest norma C99.

ZASTOSOWANIA JZYKA C

15

Zastosowania jzyka C
Jzyk C zosta opracowany jako strukturalny jzyk programowania do celw oglnych. Przez ca sw histori (czyli ponad 30 lat) suy do tworzenia przernych programw od systemw operacyjnych po programy nadzorujce prac urzdze przemysowych. C, jako jzyk duo szybszy od jzykw interpretowanych (Perl, Python) oraz uruchamianych w maszynach wirtualnych (np. C#, Java) moe bez problemu wykonywa zoone operacje nawet wtedy, gdy naoone s do due limity czasu wykonywania pewnych operacji. Jest on przy tym bardzo przenony moe dziaa praktycznie na kadej architekturze sprztowej pod warunkiem opracowania odpowiedniego kompilatora. Czsto wykorzystywany jest take do oprogramowywania mikrokontrolerw i systemw wbudowanych. Jednak w niektrych sytuacjach jzyk C okazuje si by mao przydatny, zwaszcza chodzi tu o obliczenia matematyczne, wymagajce duej precyzji (w tej dziedzinie znakomicie spisuje si Fortran) lub te duej optymalizacji dla danego sprztu (wtedy niezastpiony jest jzyk asemblera). Kolejn zalet C jest jego dostpno waciwie kady system typu UNIX posiada kompilator C, w C pisane s funkcje systemowe. Problemem w przypadku C jest zarzdzanie pamici, ktre nie wybacza programicie bdw, niewygodne operowanie napisami i niestety pewna liczba kruczkw, ktre mog zaskakiwa nowicjuszy. Na tle modszych jzykw programowania, C jest jzykiem dosy niskiego poziomu wic wiele rzeczy trzeba w nim robi rcznie, jednak zarazem umoliwia to robienie rzeczy nieprzewidzianych w samym jzyku (np. implementacj liczb 128 bitowych), a take atwe czenie C z Asemblerem.

Przyszo C
Pomimo sdziwego ju wieku (C ma ponad 30 lat) nadal jest on jednym z najczciej stosowanych jzykw programowania. Doczeka si ju swoich nastpcw, z ktrymi w niektrych dziedzinach nadal udaje mu si wygrywa. Wida zatem, e pomimo pozornej prostoty i niewielkich moliwoci jzyk C nadal spenia stawiane przed nim wymagania. Warto zatem uczy si jzyka C, gdy nadal jest on wykorzystywany (i nic nie wskazuje na to, by miao si to zmieni), a wiedza ktr zdobdziesz uczc si C na pewno si nie zmarnuje. Skadnia jzyka C, pomimo e przez wielu uwaana za nieczyteln, staa si podstaw dla takich jzykw jak C++, C# czy te Java.

16

ROZDZIA 2. O JZYKU C

Rozdzia 3

Czego potrzebujesz
Czego potrzebujesz
Wbrew powszechnej opinii nauczenie si ktrego z jzykw programowania (w tym jzyka C) nie jest takie trudne. Do nauki wystarcz Ci: komputer z dowolnym systemem operacyjnym, takim jak FreeBSD, Linux, Windows; Jzyk C jest bardzo przenony, wic bdzie dziaa waciwie na kadej platformie sprztowej i w kadym nowoczesnym systemie operacyjnym. kompilator jzyka C Kompilator jzyka C jest programem, ktry tumaczy kod rdowy napisany przez nas do jzyka asembler, a nastpnie do postaci zrozumiaej dla komputera (maszyny cyfrowej) czyli do postaci cigu zer i jedynek ktre steruj prac poszczeglnych elementw komputera. Kompilator jzyka C mona dosta za darmo. Przykadem s: gcc pod systemy uniksowe, DJGPP pod systemy DOS, MinGW oraz lcc pod systemy typu Windows. Jako kompilator C moe dobrze suy kompilator jzyka C++ (rnice midzy tymi jzykami przy pisaniu prostych programw s nieistotne). Spokojnie moesz wic uy na przykad Microsoft Visual C++ R lub kompilatorw rmy Borland. Jeli lubisz eksperymentowa, wyprbuj Tiny C Compiler, bardzo szybki kompilator o ciekawych funkcjach. Moesz ponadto wyprbowa interpreter jzyka C. Wicej informacji na Wikipedii. Linker Linker jest to program ktry uruchamiany jest po etapie kompilacji jednego lub kilku plikw rdowych (pliki z rozszerzeniem *.c, *.cpp lub innym) skompilowanych dowolnym kompilatorem. Taki program czy wszystkie nasze skompilowane pliki rdowe i inne funkcje (np. printf, scanf) ktre byy uyte (doczone do naszego programu poprzez uycie dyrektywy #include) w naszym programie, a nie byy zdeniowane(napisane przez nas) w naszych plikach rdowych lub nagwkowych. Linker jest to czasami jeden program poczony z kompilatorem. Wywoywany jest on na og automatycznie przez kompilator, w wyniku czego dostajemy gotowy program do uruchomienia. 17

18 Debuger

ROZDZIA 3. CZEGO POTRZEBUJESZ

Debugger jest to program, ktry umoliwia przeledzenie(okrelenie wartoci poszczeglnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijce wykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu. Uywa si go w celu okrelenia czemu nasz program nie dziaa po naszej myli lub czemu program niespodziewanie koczy dziaanie bez powodu. Aby uy debuggera kompilator musi doczy kod rdowy do gotowego skompilowanego programu. Przykadowymi debuggerami s: gdb pod Linuksem, lub debugger rmy Borland pod Windowsa. edytora tekstowego; Systemy uniksowe oferuj wiele edytorw przydatnych dla programisty, jak choby vim i Emacs w trybie tekstowym, Kate w KDE czy gedit w GNOME. Windows ma edytor cakowicie wystarczajcy do pisania programw w C niemiertelny Notatnik ale z atwoci znajdziesz w Internecie wiele wygodniejszych narzdzi takich jak np. Notepad++. Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE. duo chci i dobrej motywacji.

Zintegrowane rodowiska Programistyczne


Zamiast osobnego kompilatora i edytora, moesz wybra Zintegrowane rodowisko Programistyczne (Integrated Development Environment, IDE). IDE jest zestawem wszystkich programw, ktre potrzebuje programista, najczciej z interfejsem gracznym. IDE zawiera kompilator, linker i edytor, z reguy rwnie debugger. Bardzo popularny IDE to patny Microsoft Visual C++ (MS VC++); popularne darmowe IDE to np.: Dev-C++ dla Windows, dostpny na stronie www.bloodshed.net, Code::Blocks dla Windows jak i Linux, dostpny na stronie www.codeblocks.org, KDevelop dla KDE Pelles C, www.smorgasbordet.com. Eclipse z wtyczk CDT (wsppracuje z MinGW i GCC) Czsto uywanym rodowiskiem jest te Borland C++ Builder (dostpny za darmo do uytku prywatnego).

Dodatkowe narzdzia
Wrd narzdzi, ktre nie s niezbdne, ale zasuguj na uwag, mona wymieni Valgrinda specjalnego rodzaju debugger. Valgrind kontroluje wykonanie programu i wykrywa nieprawidowe operacje w pamici oraz wycieki pamici. Uycie Valgrinda jest proste kompiluje si program tak, jak do debugowania i podaje jako argument Valgrindowi.

Rozdzia 4

Uywanie kompilatora
Jzyk C jest jzykiem kompilowanym, co oznacza, e potrzebuje specjalnego programu kompilatora ktry tumaczy kod rdowy, pisany przez czowieka, na jzyk rozkazw danego komputera. W skrcie dziaanie kompilatora sprowadza si do czytania tekstowego pliku z kodem programu, raportowania ewentualnych bdw i produkowania pliku wynikowego. Kompilator uruchamiamy ze Zintegrowanego rodowiska Programistycznego lub z konsoli (linii polece). Przej do konsoli mona dla systemw typu UNIX w trybie gracznym uy programw gterminal, konsole albo xterm, w Windows Wiersz polecenia (mona go znale w menu Akcesoria albo uruchomi wpisujc w Start -> Uruchom... cmd).

GCC
Zobacz w Wikipedii: GCC GCC jest to darmowy zestaw kompilatorw, m.in. jzyka C rozwijany w ramach projektu GNU. Dostpny jest on na du ilo platform sprztowych, obsugiwanych przez takie systemy operacyjne jak: AIX, *BSD, Linux, Mac OS X, SunOS, Windows. Aby skompilowa kod jzyka C za pomoc kompilatora GCC, napisany wczeniej w dowolnym edytorze tekstu, naley uruchomi program z odpowiednimi parametrami. Podstawowym parametrem, ktry jest wymagany, jest nazwa pliku zawierajcego kod programu ktry chcemy skompilowa. gcc kod.c Rezultatem kompilacji bdzie plik wykonywalny, z domyln nazw (w systemach Unix jest to a.out). Jest to metoda niezalecana poniewa jeeli skompilujemy w tym samym katalogu kilka plikw z kodem, kolejne pliki wykonywalne zostan nadpisane i w rezultacie otrzymamy tylko jeden (ten ostatni) skompilowany kod. Aby wymusi na GCC nazw pliku wykonywalnego musimy skorzysta z parametru -o <nazwa>: gcc -o program kod.c W rezultacie otrzymujemy plik wykonywalny o nazwie program. Jeeli chodzi o form nazwy podawanego pliku z kodem (w naszym przypadku kod.c), to kompilator sam rozpoznaje jzyk programowania wynikajcy z rozszerzenia pliku. Jednak jest moliwo skompilowania pliku o dowolnym rozszerzeniu, narzucajc 19

20

ROZDZIA 4. UYWANIE KOMPILATORA

kompilatorowi dany jzyk. Aby wymusi na GCC korzystanie z jzyka C, uywamy parametru -x <jzyk>: gcc -x c -o program kod W rezultacie kompilator potraktuje plik kod, jako plik z kodem rdowym w jzyku C. Pracujc nad zoonym programem skadajcym si z kilku plikw rdowych (.c), moemy skompilowa je niezalenie od siebie, tworzc tak zwane pliki typu obiekt (ang. Object File), s to pliki skompilowane ale nie poddane linkowaniu. Nastpnie stworzy z nich jednolity program. Jest to bardzo wygodne i praktyczne rozwizanie ze wzgldu na to, i nie jestemy zmuszeni kompilowa wszystkich plikw tworzce program za kadym razem na nowo a jedyne te w ktrych wprowadzilimy zmiany. Aby skompilowa plik bez linkowania uywamy parametru -c <plik>: gcc -o program1.o -c kod1.c gcc -o program2.o -c kod2.c Otrzymujemy w ten sposb pliki typu obiekt program1.o i program2.o. A nastpnie tworzymy z nich program gwny: gcc -o program program1.o program2.o Aby wczy dokadne, rygorystyczne sprawdzanie napisanego kodu, co moe by przydatne, jeli chcemy dy do perfekcji, uywamy przecznikw: gcc kod.c -o program -Werror -Wall -W -pedantic -ansi Wicej informacji na temat parametrw i dziaania kompilatora GCC mona znale na: Strona domowa projektu GNU GCC Krtki przekrojowy opis GCC po polsku Strona podrcznika systemu UNIX (man)

Borland
Zobacz podrcznik Borland C++ Compiler.

Czytanie komunikatw o bdach


Jedn z najbardziej podstawowych umiejtnoci, ktre musi posi pocztkujcy programista jest umiejtno rozumienia komunikatw o rnego rodzaju bdach, ktre sygnalizuje kompilator. Wszystkie te informacje pomog Ci szybko wychwyci ewentualne bdy (ktrych na pocztku zawsze jest bardzo duo). Nie martw si, e na pocztku do czsto bdziesz oglda wydruki bdw, zasygnalizowanych przez kompilator nawet zaawansowanym programistom si to zdarza. Kompilator ma za zadanie pomc Ci w szybkiej poprawie ewentualnych bdw, dlatego te umiejtno analizy komunikatw o bdach jest tak wana.

CZYTANIE KOMUNIKATW O BDACH

21

GCC
Kompilator jest w stanie wychwyci bdy skadniowe, ktre z pewnoci bdziesz popenia. Kompilator GCC wywietla je w nastpujcej formie: nazwa_pliku.c:numer_linijki:opis bdu Kompilator do czsto podaje take nazw funkcji, w ktrej wystpi bd. Przykadowo, bd deklaracji zmiennej w pliku test.c: #include <stdio.h> int main () { intr r; printf ("%d\n", r); } Spowoduje wygenerowanie nastpujcego komunikatu o bdzie: test.c: In function main: test.c:5: error: intr undeclared (first use in this function) test.c:5: error: (Each undeclared identifier is reported only once test.c:5: error: for each function it appears in.) test.c:5: error: syntax error before r test.c:6: error: r undeclared (first use in this function) Co widzimy w raporcie o bdach? W linii 5 uylimy nieznanego (undeclared) identykatora intr kompilator mwi, e nie zna tego identykatora, jest to pierwsze uycie w danej funkcji i e wicej nie ostrzee o uyciu tego identyfykatora w tej funkcji. Poniewa intr nie zosta rozpoznany jako aden znany typ, linijka intr r; nie zostaa rozpoznana jako deklaracja zmiennej i kompilator zgasza bd skadniowy (syntax error). W konsekwencji r nie zostao rozpoznane jako zmienna i kompilator zgosi to jeszcze w nastpnej linijce, gdzie uywamy r.

22

ROZDZIA 4. UYWANIE KOMPILATORA

Rozdzia 5

Pierwszy program
Twj pierwszy program
Przyjo si, e pierwszy program napisany w dowolnym jzyku programowania, powinien wywietli tekst np. Hello World! (Witaj wiecie!). Zauwa, e sam jzyk C nie ma adnych mechanizmw przeznaczonych do wprowadzania i wypisywania danych musimy zatem skorzysta ze specjalnie napisanych w tym celu funkcji w tym przypadku printf, zawartej w standardowej bibliotece C (ang. C Standard Library) (podobnie jak w Pascalu uywa si do tego procedur. Pascalowskim odpowiednikiem funkcji printf s procedury write/writeln). W jzyku C deklaracje funkcji zawarte s w plikach nagwkowych posiadajcych najczciej rozszerzenie .h, cho mona take spotka rozszerzenie .hpp, przy czym to drugie zwyko si stosowa w jzyku C++ (rozszerzenie nie ma swych technicznych korzeni jest to tylko pewna konwencja). eby wczy plik nagwkowy do swojego kodu, trzeba uy dyrektywy kompilacyjnej #include. Ta dyrektywa powoduje, e przed procesem kompilacji danego pliku rdowego, deklaracje funkcji z pliku nagwkowego zostaj doczone do twojego kodu celem zwerykowania poprawnoci wywoywanych funkcji. Poniej przykad, jak uy dyrektywy #include eby wklei denicj funkcji printf z pliku nagwkowego stdio.h (Standard Input/Output.Headerle): #include <stdio.h> W nawiasach trjktnych < > umieszcza si nazwy standardowych plikw nagwkowych. eby wczy inny plik nagwkowy (np. wasny), znajdujcy si w katalogu z kodem programu, trzeba go wpisa w cudzysw: #include "mj_plik_nagwkowy.h" Mamy wic funkcj printf, jak i wiele innych do wprowadzania i wypisywania danych, czas na pisanie programu. 1 Programy w C zaczyna si funkcj main, w ktrej umieszcza si waciwy kod programu. eby rozpocz t funkcj, naley wpisa:
1 Domylne pliki nagwkowe znajduj si w katalogu z plikami nagwkowymi kompilatora. W systemach z rodziny Unix bdzie to katalog /usr/include, natomiast w systemie Windows w katalog bdzie umieszczony w katalogu z kompilatorem.

23

24 int main (void) {

ROZDZIA 5. PIERWSZY PROGRAM

int oznacza, e funkcja zwrci (tzn. przyjmie warto po zakoczeniu) liczb cakowit w przypadku main bdzie to kod wyjciowy programu; main to nazwa funkcji, w nawiasach umieszczamy parametry programu. Na tym etapie parametry programu nie bd nam potrzebne (void oznacza brak parametrw). Uywa si ich do odczytywania argumentw linii polecenia, z jakimi program zosta uruchomiony. Kod funkcji umieszcza si w nawiasach klamrowych { i }. Wewntrz funkcji naley wpisa poniszy kod: printf("Hello World!"); return 0;

Wszystkie polecenia koczymy rednikiem. return 0; okrela warto jak zwrci funkcja (program); Liczba zero zwracana przez funkcj main() oznacza, e program zakoczy si bez bdw; bdne zakoczenie czsto (cho nie zawsze) okrelane jest przez liczb jeden2 . Funkcj main koczymy nawiasem klamrowym zamykajcym.

Twj kod powinien wyglda jak poniej: #include int main { printf return } <stdio.h> (void) ("Hello World!"); 0;

Teraz wystarczy go tylko skompilowa i uruchomi.

Rozwizywanie problemw
Jeli nie moesz skompilowa powyszego programu, to najprawdopodobniej popenie literwk przy rcznym przepisywaniu go. Zobacz te instrukcje na temat uywania kompilatora. Moe te si zdarzy, e program skompiluje si, uruchomi, ale jego efektu dziaania nie bdzie wida. Dzieje si tak, poniewa nasz pierwszy program po prostu wypisuje komunikat i od razu koczy dziaanie, nie czekajc na reakcj uytkownika. Nie jest to problemem, gdy program jest uruchamiany z konsoli tekstowej, ale w innych przypadkach nie widzimy efektw jego dziaania. Jeli korzystasz ze Zintegrowanego rodowiska Programistycznego (ang. IDE), moesz zaznaczy, by nie zamykao ono programu po zakoczeniu jego dziaania. Innym sposobem jest dodanie instrukcji, ktre wstrzymywayby zakoczenie programu. Mona to zrobi dodajc przed lini z return kod (wraz z okalajcymi klamrami):
2 Jeeli chcesz mie pewno, e twj program bdzie dziaa poprawnie rwnie na platformach, gdzie 1 oznacza poprawne zakoczenie (lub nie oznacza nic), moesz skorzysta z makr EXIT SUCCESS i EXIT FAILURE zdeniowanych w pliku nagwkowym stdlib.h.

ROZWIZYWANIE PROBLEMW { int chr; puts("Wcisnij ENTER..."); while ((chr = getchar())!=EOF && chr!=\n); }

25

Jest te prostszy (cho nieprzenony) sposb, mianowicie wywoanie polecenia systemowego. W zalenoci od uywanego systemu operacyjnego mamy do dyspozycji rne polecenia powodujce rne efekty. Do tego celu skorzystamy z funkcji system(), ktra jako parametr przyjmuje polecenie systemowe ktre chcemy wykona, np: Rodzina systemw Unix/Linux: system("sleep 10"); /* oczekiwanie 10 s */ system("read discard"); /* oczekiwanie na wpisanie tekstu */ Rodzina systemw DOS oraz MS Windows: system("pause"); /* oczekiwanie na wcinicie dowolnego klawisza */ Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnych Windows w ktrych to z regu pracuje si w trybie okienkowym a z konsoli korzystamy tylko podczas uruchamianiu programu. Z kolei w systemach Unix/Linux jest ona praktycznie w ogle nie uywana w tym celu, ze wzgldu na uruchamianie programu bezporednio z konsoli.

26

ROZDZIA 5. PIERWSZY PROGRAM

Rozdzia 6

Podstawy
Dla waciwego zrozumienia jzyka C nieodzowne jest przyswojenie sobie pewnych oglnych informacji.

Kompilacja: Jak dziaa C?


Jak kady jzyk programowania, C sam w sobie jest niezrozumiay dla procesora. Zosta on stworzony w celu umoliwienia ludziom atwego pisania kodu, ktry moe zosta przetworzony na kod maszynowy. Program, ktry zamienia kod C na wykonywalny kod binarny, to kompilator. Jeli pracujesz nad projektem, ktry wymaga kilku plikw kodu rdowego (np. pliki nagwkowe), wtedy jest uruchamiany kolejny program linker. Linker suy do poczenia rnych plikw i stworzenia jednej aplikacji lub biblioteki (library ). Biblioteka jest zestawem procedur, ktry sam w sobie nie jest wykonywalny, ale moe by uywana przez inne programy. Kompilacja i czenie plikw s ze sob bardzo cile powizane, std s przez wielu traktowane jako jeden proces. Jedn rzecz warto sobie uwiadomi kompilacja jest jednokierunkowa: przeksztacenie kodu rdowego C w kod maszynowy jest bardzo proste, natomiast odwrotnie nie. Dekompilatory co prawda istniej, ale rzadko tworz uyteczny kod C. Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collection, dostpny na stronie gcc.gnu.org.

Co moe C?
Pewnie zaskoczy Ci to, e tak naprawd czysty jzyk C nie moe zbyt wiele. Jzyk C w grupie jzykw programowania wysokiego poziomu jest stosunkowo nisko. Dziki temu kod napisany w jzyku C mona do atwo przetumaczy na kod asemblera. Bardzo atwo jest te czy ze sob kod napisany w jzyku asemblera z kodem napisanym w C. Dla bardzo wielu ludzi przeszkod jest take do dua liczba i czsta dwuznaczno operatorw. Pocztkujcy programista, czytajcy kod programu w C moe odnie bardzo nieprzyjemne wraenie, ktre mona opisa cytatem ja nigdy tego nie opanuj. Wszystkie te elementy jzyka C, ktre wydaj Ci si dziwne i nielogiczne w miar, jak bdziesz nabiera dowiadczenia nagle oka si cakiem przemylanie dobrane i takie, a nie inne konstrukcje przypadn Ci do gustu. Dalsza 27

28

ROZDZIA 6. PODSTAWY

lektura tego podrcznika oraz zaznajamianie si z funkcjami z rnych bibliotek uka Ci ca gam moliwoci, ktre daje jzyk C dowiadczonemu programicie.

Struktura blokowa
Teraz omwimy podstawow struktur programu napisanego w C. Jeli miae styczno z jzykiem Pascal, to pewnie syszae o nim, e jest to jzyk programowania strukturalny. W C nie ma tak cisej struktury blokowej, mimo to jest bardzo wane zrozumienie, co oznacza struktura blokowa. Blok jest grup instrukcji, poczonych w ten sposb, e s traktowane jak jedna cao. W C, blok zawiera si pomidzy nawiasami klamrowymi { }. Blok moe take zawiera kolejne bloki. Zawarto bloku. Generalnie, blok zawiera cig kolejno wykonywanych polece. Polecenia zawsze (z nielicznymi wyjtkami) kocz si rednikiem (;). W jednej linii moe znajdowa si wiele polece, cho dla zwikszenia czytelnoci kodu najczciej pisze si pojedyncz instrukcj w kadej linii. Jest kilka rodzajw polece, np. instrukcje przypisania, warunkowe czy ptli. W duej czci tego podrcznika bdziemy zajmowa si wanie instrukcjami. Pomidzy poleceniami s rwnie odstpy spacje, tabulacje, oraz przejcia do nastpnej linii, przy czym dla kompilatora te trzy rodzaje odstpw maj takie samo znaczenie. Dla przykadu, ponisze trzy fragmenty kodu rdowego, 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 wyjtek. Dotyczy on staych tekstowych. W powyszych przykadach sta tekstow jest Hello world. Gdy jednak rozbijemy ten napis, kompilator zasygnalizuje bd: printf("Hello world"); return 0; Naley tylko zapamita, e stae tekstowe powinny zaczyna si i koczy w tej samej lini (mona omin to ograniczenie wicej w rozdziale Napisy). Oprcz tego jednego przypadku dla kompilatora ma znaczenie samo istnienie odstpu, a nie jego wielko czy rodzaj. Jednak stosowanie odstpw jest bardzo wane, dla zwikszenia czytelnoci kodu dziki czemu moemy zaoszczdzi sporo czasu i nerww, poniewa znalezienie bdu (ktre si zdarzaj kademu) w nieczytelnym kodzie moe by bardzo trudne.

ZASIG

29

Zasig
Pojcie to dotyczy zmiennych (ktre przechowuj dane przetwarzane przez program). W kadym programie (oprcz tych najprostszych) s zarwno zmienne wykorzystywane przez cay czas dziaania programu, oraz takie ktre s uywane przez pojedynczy blok programu (np. funkcj). Na przykad, w pewnym programie w pewnym momencie jest wykonywane skomplikowane obliczenie, ktre wymaga zadeklarowania wielu zmiennych do przechowywania porednich wynikw. Ale przez wiksz cz tego dziaania, te zmienne s niepotrzebne, i zajmuj tylko miejsce w pamici najlepiej gdyby to miejsce zostao zarezerwowane tu przed wykonaniem wspomnianych oblicze, a zaraz po ich wykonaniu zwolnione. Dlatego w C istniej zmienne globalne, oraz lokalne. Zmienne globalne mog by uywane w kadym miejscu programu, natomiast lokalne tylko w okrelonym bloku czy funkcji (oraz blokach w nim zawartych). Generalnie zmienna zadeklarowana w danym bloku, jest dostpna tylko wewntrz niego.

Funkcje
Funkcje s cile zwizane ze struktur blokow funkcj jest po prostu blok instrukcji, ktry jest potem wywoywany w programie za pomoc pojedynczego polecenia. Zazwyczaj funkcja wykonuje pewne okrelone zadanie, np. we wspomnianym programie wykonujcym pewne skomplikowane obliczenie. Kada funkcja ma swoj nazw, za pomoc ktrej jest potem wywoywana w programie, oraz blok wykonywanych polece. Wiele funkcji pobiera pewne dane, czyli argumenty funkcji, wiele funkcji take zwraca pewn warto, po zakoczeniu wykonywania. Dobrym nawykiem jest dzielenie duego programu na zestaw mniejszych funkcji dziki temu bdziesz mg atwiej odnale bd w programie. Jeli chcesz uy jakiej funkcji, to powiniene wiedzie: jakie zadanie wykonuje dana funkcja rodzaj wczytywanych argumentw, i do czego s one potrzebne tej funkcji rodzaj zwrconych danych, i co one oznaczaj. W programach w jzyku C jedna funkcja ma szczeglne znaczenie jest to main(). Funkcj t, zwan funkcj gwn, musi zawiera kady program. W niej zawiera si gwny kod programu, przekazywane s do niej argumenty, z ktrymi wywoywany jest program (jako parametry argc i argv). Wicej o funkcji main() dowiesz si pniej w rozdziale Funkcje.

Biblioteki standardowe
Jzyk C, w przeciwiestwie do innych jzykw programowania (np. Fortranu czy Pascala) nie posiada absolutnie adnych sw kluczowych, ktre odpowiedzialne by byy za obsug wejcia i wyjcia. Moe si to wydawa dziwne jzyk, ktry sam w sobie nie posiada podstawowych funkcji, musi by jzykiem o ograniczonym zastosowaniu. Jednak brak podstawowych funkcji wejcia-wyjcia jest jedn z najwikszych zalet tego jzyka. Jego skadnia opracowana jest tak, by mona byo bardzo atwo przeoy j na kod maszynowy. To wanie dziki temu programy napisane w jzyku

30

ROZDZIA 6. PODSTAWY

C s takie szybkie. Pozostaje jednak pytanie jak umoliwi programom komunikacj z uytkownikiem ? W 1983 roku, kiedy zapocztkowano prace nad standaryzacj C, zdecydowano, e powinien by zestaw instrukcji identycznych w kadej implementacji C. Nazwano je Bibliotek Standardow (czasem nazywan libc). Zawiera ona podstawowe funkcje, ktre umoliwiaj wykonywanie takich zada jak wczytywanie i zwracanie danych, modykowanie zmiennych acuchowych, dziaania matematyczne, operacje na plikach, i wiele innych, jednak nie zawiera adnych funkcji, ktre mog by zalene od systemu operacyjnego czy sprztu, jak graka, dwik czy obsuga sieci. W programie Hello World uyto funkcji z biblioteki standardowej printf, ktra wywietla na ekranie sformatowany tekst.

Komentarze i styl
Komentarze to tekst wczony do kodu rdowego, ktry jest pomijany przez kompilator, i suy jedynie dokumentacji. W jzyku C, komentarze zaczynaj si od /* a kocz */ Dobre komentowanie ma due znaczenie dla rozwijania oprogramowania, nie tylko dlatego, e inni bd kiedy potrzebowali przeczyta napisany przez ciebie kod rdowy, ale take moesz chcie po duszym czasie powrci do swojego programu, i moesz zapomnie, do czego suy dany blok kodu, albo dlaczego akurat uye tego polecenia a nie innego. W chwili pisania programu, to moe by dla ciebie oczywiste, ale po duszym czasie moesz mie problemy ze zrozumieniem wasnego kodu. Jednak nie naley te wstawia zbyt duo komentarzy, poniewa wtedy kod moe sta si jeszcze mniej czytelny najlepiej komentowa fragmenty, ktre nie s oczywiste dla programisty, oraz te o szczeglnym znaczeniu. Ale tego nauczysz si ju w praktyce. Dobry styl pisania kodu jest o tyle wany, e powinien on by czytelny i zrozumiay; po to w kocu wymylono jzyki programowania wysokiego poziomu (w tym C), aby kod byo atwo zrozumie ;). I tak naley stosowa wcicia dla odrnienia blokw kolejnego poziomu (zawartych w innym bloku; podrzdnych), nawiasy klamrowe otwierajce i zamykajce blok powinny mie takie same wcicia, staraj si, aby nazwy funkcji i zmiennych kojarzyy si z zadaniem, jakie dana funkcja czy zmienna peni w programie. W dalszej czci podrcznika moesz napotka wicej zalece dotyczcych stylu pisania kodu. Staraj si stosowa do tych zalece dziki temu kod pisanych przez ciebie programw bdzie atwiejszy do czytania i zrozumienia. Jeli masz dowiadczenia z jzykiem C++ pamitaj, e w C nie powinno si stosowa komentarzy zaczynajcych si od dwch znakw slash: // tak nie komentujemy w C Jest to niezgodne ze standardem ANSI C i niektre kompilatory mog nie skompilowa kodu z komentarzami w stylu C++ (cho standard ISO C99 dopuszcza komentarze w stylu C++).

PREPROCESOR

31

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentw kodu. Jeli cz programu le dziaa i chcemy j chwilowo wyczy, albo fragment kodu jest nam ju niepotrzebny, ale mamy wtpliwoci, czy w przyszoci nie bdziemy chcieli go uy umieszczamy go po prostu wewntrz komentarza. Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uwaa na jedn subtelno. Ot komentarze /* * / w jzyku C nie mog by zagniedone. Trzeba na to uwaa, gdy chcemy obj komentarzem obszar w ktrym ju istnieje komentarz (naley wtedy usun wewntrzny komentarz). W nowszym standardzie C dopuszcza si, aby komentarz typu /* */ zawiera w sobie komentarz //.

Po polsku czy angielsku?


Jak ju wczeniej byo wspomniane, zmiennym i funkcjom powinno si nadawa nazwy, ktre odpowiadaj ich znaczeniu. Zdecydowanie atwiej jest czyta kod, gdy redni liczb przechowuje zmienna srednia ni a a znajdowaniem maksimum w cigu liczb zajmuje si funkcja max albo znajdz max ni nazwana f. Czsto nazwy funkcji to wanie czasowniki. Powstaje pytanie, w jakim jzyku naley pisa nazwy. Jeli chcemy, by nasz kod mogy czyta osoby nieznajce polskiego warto uy jzyka angielskiego. Jeli nie mona bez problemu uy polskiego. Bardzo istotne jest jednak, by nie miesza jzykw. Jeli zdecydowalimy si uywa polskiego, uywajmy go od pocztku do koca; przeplatanie ze sob dwch jzykw robi ze wraenie.

Preprocesor
Nie cay napisany przez ciebie kod bdzie przeksztacany przez kompilator bezporednio na kod wykonywalny programu. W wielu przypadkach bdziesz uywa polece skierowanych do kompilatora, tzw. dyrektyw kompilacyjnych. Na pocztku procesu kompilacji, specjalny podprogram, tzw. preprocesor, wyszukuje wszystkie dyrektywy kompilacyjne, i wykonuje odpowiednie akcje ktre polegaj notabene na edycji kodu rdowego (np. wstawieniu deklaracji funkcji, zamianie jednego cigu znakw na inny). Waciwy kompilator, zamieniajcy kod C na kod wykonywalny, nie napotka ju dyrektyw kompilacyjnych, poniewa zostay one przez preprocesor usunite, po wykonaniu odpowiednich akcji. W C dyrektywy kompilacyjne zaczynaj si od znaku hash (#). Przykadem najczciej uywanej dyrektywy, jest #include, ktra jest uyta nawet w tak prostym programie jak Hello, World!. #include nakazuje preprocesorowi wczy (ang. include) w tym miejscu zawarto podanego pliku, tzw. pliku nagwkowego; najczciej to bdzie plik zawierajcy funkcje z ktrej biblioteki standardowej (stdio.h STandard Input-Output, rozszerzenie .h oznacza plik nagwkowy C). Dziki temu, zamiast wkleja do kodu swojego programu deklaracje kilkunastu, a nawet kilkudziesiciu funkcji, wystarczy wpisa jedn magiczn linijk!

Nazwy zmiennych, staych i funkcji


Identykatory, czyli nazwy zmiennych, staych i funkcji mog skada si z liter (bez polskich znakw), cyfr i znaku podkrelenia z tym, e nazwa taka nie moe zaczyna si od cyfry. Nie mona uywa nazw zarezerwowanych (patrz: Skadnia). Przykady bdnych nazw:

32 2liczba moja funkcja $i if

ROZDZIA 6. PODSTAWY (nie mona zaczyna nazwy od cyfry) (nie mona uywa spacji) (nie mona uywa znaku $) (if to sowo kluczowe)

Aby kod by bardziej czytelny, przestrzegajmy poniszych (umownych) regu: nazwy zmiennych piszemy maymi literami: i, file nazwy staych (zadeklarowanych przy pomocy #dene) piszemy wielkimi literami: SIZE nazwy funkcji piszemy maymi literami: print wyrazy w nazwach oddzielamy znakiem podkrelenia: open file, close all files S to tylko konwencje aden kompilator nie zgosi bdu, jeli wprowadzimy swj wasny system nazewnictwa. Jednak warto pamita, e by moe nad naszym kodem bd pracowali take inni programici, ktrzy mog mie trudnoci z analiz kodu niespeniajcego pewnych zasad.

Rozdzia 7

Zmienne
Procesor komputera stworzony jest tak, aby przetwarza dane, znajdujce si w pamici komputera. Z punktu widzenia programu napisanego w jzyku C (ktry jak wiadomo jest jzykiem wysokiego poziomu) dane umieszczane s w postaci tzw. zmiennych. Zmienne uatwiaj programicie pisanie programu. Dziki nim programista nie musi si przejmowa gdzie w pamici owe zmienne si znajduj, tzn. nie operuje zycznymi adresami pamici, jak np. 0x14613467, tylko prost do zapamitania nazw zmiennej.

Czym s zmienne?
Zmienna jest to pewien fragment pamici o ustalonym rozmiarze, ktry posiada wasny identykator (nazw) oraz moe przechowywa pewn warto, zalen od typu zmiennej.

Deklaracja zmiennych
Aby mc skorzysta ze zmiennej naley j przed uyciem zadeklarowa, to znaczy poinformowa kompilator, jak zmienna bdzie si nazywa i jaki typ ma mie. Zmienne deklaruje si w sposb nastpujcy: typ nazwa_zmiennej; Oto deklaracja zmiennej o nazwie wiek typu int czyli liczby cakowitej: int wiek; Zmiennej w momencie zadeklarowania mona od razu przypisa warto: int wiek = 17; W jzyku C zmienne deklaruje si na samym pocztku bloku (czyli przed pierwsz instrukcj).

int wiek = 17; printf("%d\n", wiek); 33

34

ROZDZIA 7. ZMIENNE

int kopia_wieku; /* tu stary kompilator C zgosi bd */ /* deklaracja wystpuje po instrukcji (printf). */ kopia_wieku = wiek; Wedug nowszych standardw moliwe jest deklarowanie zmiennej w dowolnym miejscu programu, ale wtedy musimy pamita, aby zadeklarowa zmienn przed jej uyciem. To znaczy, e taki kod jest niepoprawny: printf ("Mam %d lat\n", wiek); int wiek = 17; Naley go zapisa tak: int wiek = 17; printf ("Mam %d lat\n", wiek); Jzyk C nie inicjalizuje zmiennych lokalnych. Oznacza to, e w nowo zadeklarowanej zmiennej znajduj si mieci - to, co wczeniej zawiera przydzielony zmiennej fragment pamici. Aby unikn cikich do wykrycia bdw, dobrze jest inicjalizowa (przypisywa warto) wszystkie zmienne w momencie zadeklarowania.

Zasig zmiennej
Zmienne mog by dostpne dla wszystkich funkcji programu nazywamy je wtedy zmiennymi globalnymi. Deklaruje si je przed wszystkimi funkcjami programu: #include <stdio.h> int a,b; /* nasze zmienne globalne */ void func1 () { /* instrukcje */ a=3; /* dalsze instrukcje */ } int main () { b=3; a=2; return 0; } Zmienne globalne, jeli programista nie przypisze im innej wartoci podczas deniowania, s inicjalizowane wartoci 0. Zmienne, ktre funkcja deklaruje do wasnych potrzeb nazywamy zmiennymi lokalnymi. Nasuwa si pytanie: czy bdzie bdem nazwanie t sam nazw zmiennej globalnej i lokalnej?. Ot odpowied moe by zaskakujca: nie. Natomiast w danej funkcji da si uywa tylko jej zmiennej lokalnej. Tej konstrukcji naley, z wiadomych wzgldw, unika.

CZYM S ZMIENNE? int a=1; /* zmienna globalna */ int main() { int a=2; /* to ju zmienna lokalna */ printf("%d", a); /* wypisze 2 */ }

35

Czas ycia
Czas ycia to czas od momentu przydzielenia dla zmiennej miejsca w pamici (stworzenie obiektu) do momentu zwolnienia miejsca w pamici (likwidacja obiektu). Zakres wanoci to cz programu, w ktrej nazwa znana jest kompilatorowi. main() { int a = 10; { int b = 10; printf("%d %d", a, b); } printf("%d %d", a, b); }

/* otwarcie lokalnego bloku */

/* zamknicie lokalnego bloku, zmienna b jest usuwana */ /* BD: b juz nie istnieje */ /* tu usuwana jest zmienna a */

Zdeniowalimy dwie zmienne typu int. Zarwno a i b istniej przez cay program (czas ycia). Nazwa zmiennej a jest znana kompilatorowi przez cay program. Nazwa zmiennej b jest znana tylko w lokalnym bloku, dlatego nastpi bd w ostatniej instrukcji. Niektre kompilatory (prawdopodobnie mona tu zaliczy Microsoft Visual C++ do wersji 2003) uznaj powyszy kod za poprawny! W dodatku mona ustawi w opcjach niektrych kompilatorw zachowanie w takiej sytuacji, wcznie z zachowaniami niezgodnymi ze standardem jzyka! Moemy wiadomie ograniczy wano zmiennej do kilku linijek programu (tak jak robilimy wyej) tworzc blok. Nazwa zmiennej jest znana tylko w tym bloku. { ... }

Stae
Staa, rni si od zmiennej tylko tym, e nie mona jej przypisa innej wartoci w trakcie dziaania programu. Warto staej ustala si w kodzie programu i nigdy ona nie ulega zmianie. Sta deklaruje si z uyciem sowa kluczowego const w sposb nastpujcy: const typ nazwa_staej=warto;

36

ROZDZIA 7. ZMIENNE

Dobrze jest uywa staych w programie, poniewa unikniemy wtedy przypadkowych pomyek a kompilator moe czsto zoptymalizowa ich uycie (np. od razu podstawiajc ich warto do kodu). const int WARTOSC_POCZATKOWA=5; int i=WARTOSC_POCZATKOWA; WARTOSC_POCZATKOWA=4; /* tu kompilator zaprotestuje */ int j=WARTOSC_POCZATKOWA; Przykad pokazuje dobry zwyczaj programistyczny, jakim jest zastpowanie umieszczonych na stae w kodzie liczb staymi. W ten sposb bdziemy mieli wiksz kontrol nad kodem stae umieszczone w jednym miejscu mona atwo modykowa, zamiast szuka po caym kodzie liczb, ktre chcemy zmieni. Nie mamy jednak penej gwarancji, e staa bdzie miaa t sam warto przez cay czas wykonania programu. Moliwe jest dostanie si do wartoci staej porednio za pomoc wskanikw. Mona zatem doj do wniosku, e sowo kluczowe const suy tylko do poinformowania kompilatora, aby ten nie zezwala na jawn zmian wartoci staej. Z drugiej strony, zgodnie ze standardem, prba modykacji wartoci staej ma niezdeniowane dziaanie (tzw. undened behaviour ) i w zwizku z tym moe si powie lub nie, ale moe te spowodowa jakie subtelne zmiany, ktre w efekcie spowoduj, e program bdzie le dziaa. Podobnie do zdeniowania staej moemy uy dyrektywy preprocesora #dene (opisanej w dalszej czci podrcznika). Tak zdeniowan sta nazywamy sta symboliczn. W przeciwiestwie do staej zadeklarowanej z uyciem sowa const staa zdeniowana przy uyciu #dene jest zastpowana dan wartoci w kadym miejscu, gdzie wystpuje, dlatego te moe by uywana w miejscach, gdzie normalna staa nie mogaby dobrze speni swej roli. W przeciwiestwie do jzyka C++, w C staa to cay czas zmienna, ktrej kompilator pilnuje, by nie zmienia si. Z tego powodu w C nie mona uy staej do okrelenia wielkoci tablicy1 i naley si w takim wypadku odwoa do wczeniej wspomnianej dyrektywy #define.

Typy zmiennych
Kady program w C operuje na zmiennych wydzielonych w pamici komputera obszarach, ktre mog reprezentowa obiekty nam znane, takie jak liczby, znaki, czy te bardziej zoone obiekty. Jednak dla komputera kady obszar w pamici jest taki sam to cig zer i jedynek, w takiej postaci zupenie nieprzydatny dla programisty i uytkownika. Podczas pisania programu musimy wskaza, w jaki sposb ten cig ma by interpretowany. Typ zmiennej wskazuje wanie sposb, w jaki pami, w ktrej znajduje si zmienna bdzie wykorzystywana. Okrelajc go przekazuje si kompilatorowi informacj, ile pamici trzeba zarezerwowa dla zmiennej, a take w jaki sposb wykonywa na nim operacje. Kada zmienna musi mie okrelony swj typ w miejscu deklaracji i tego typu nie moe ju zmieni. Lecz co jeli mamy zmienn jednego typu, ale potrzebujemy w pewnym miejscu programu innego typu danych? W takim wypadku stosujemy konwersj
1 Jest

to moliwe w standardzie C99

TYPY ZMIENNYCH

37

(rzutowanie) jednej zmiennej na inn zmienn. Rzutowanie zostanie opisane pniej, w rozdziale Operatory. Istniej wbudowane i zdeniowane przez uytkownika typy danych. Wbudowane typy danych to te, ktre zna kompilator, s one w nim bezporednio zaszyte. Mona te tworzy wasne typy danych, ale naley je kompilatorowi opisa. Wicej informacji znajduje si w rozdziale Typy zoone. W jzyku C wyrniamy 4 podstawowe typy zmiennych. S to: char jednobajtowe liczby cakowite, suy do przechowywania znakw; inttyp cakowity, o dugoci domylnej dla danej architektury komputera; oat typ zmiennopozycyjny (4 bajty 6 miejsc po przecinku); double typ zmiennopozycyjny podwjnej precyzji (8 bajtw 15 miejsc po przecinku); Typy zmiennoprzecinkowe zostay dokadnie opisane w IEEE 754.

int
Ten typ przeznaczony jest do liczb cakowitych. Liczby te moemy zapisa na kilka sposobw: System dziesitny 12 ; 13 ; 45 ; 35 itd System semkowy (oktalny) 010 016 019 czyli 8 czyli 8 + 6 = 14 BD

System ten operuje na cyfrach od 0 do 7. Tak wiec 9 jest niedozwolona. Jeeli chcemy uy takiego zapisu musimy zacz liczb od 0. System szesnastkowy (heksadycemalny) 0x10 0x12 0xff czyli 1*16 + 0 = 16 czyli 1*16 + 2 = 18 czyli 15*16 + 15 = 255

W tym systemie moliwe cyfry to 0...9 i dodatkowo a, b, c, d, e, f, ktre oznaczaj 10, 11, 12, 13, 14, 15. Aby uy takiego systemu musimy poprzedzi liczb cigiem 0x. Wielko znakw w takich literaach nie ma znaczenia.

oat
Ten typ oznacza liczby zmiennoprzecinkowe czyli uamki. Istniej dwa sposoby zapisu: System dziesitny 3.14 ; 45.644 ; 23.54 ; 3.21 itd System naukowy wykadniczy

38 6e2 czyli 6 * 102 czyli 600 1.5e3 czyli 1.5 * 103 czyli 1500 3.4e-3 czyli 3.4 * 103 czyli 0.0034

ROZDZIA 7. ZMIENNE

Naley wzi pod uwag, e reprezentacja liczb rzeczywistych w komputerze jest niedoskonaa i moemy otrzymywa wyniki o zauwaalnej niedokadnoci.

double
Double czyli podwjny oznacza liczby zmiennoprzecinkowe podwjnej precyzji. Oznacza to, e liczba taka zajmuje zazwyczaj w pamici dwa razy wicej miejsca ni oat (np. 64 bity wobec 32 dla oat), ale ma te dwa razy lepsz dokadno. Domylnie uamki wpisane w kodzie s typu double. Moemy to zmieni dodajc na kocu liter f: 1.5f 1.5 (float) (double)

char
Jest to typ znakowy, umoliwiajcy zapis znakw ASCII. Moe te by traktowany jako liczba z zakresu 0..255. Znaki zapisujemy w pojedynczych cudzysowach, by odrni je od acuchw tekstowych (pisanych w podwjnych cudzysowach). a ; 7 ; ! ; $ Cudzysw zapisujemy tak: \ a NULL (czyli zero, ktre midzy innymi koczy napisy) tak: \0. Wicej znakw specjalnych. Warto zauway, e typ char to zwyky typ liczbowy i mona go uywa tak samo jak typu int (zazwyczaj ma jednak mniejszy zakres). Co wicej literay znakowe (np. a) s traktowane jako liczby i w jzyku C s typu int (w jzyku C++ s typu char).

void
Sowa kluczowego void mona w okrelonych sytuacjach uy tam, gdzie oczekiwana jest nazwa typu. void nie jest waciwym typem, bo nie mona utworzy zmiennej takiego typu; jest to pusty typ (ang. void znaczy pusty). Typ void przydaje si do zaznaczania, e funkcja nie zwraca adnej wartoci lub e nie przyjmuje adnych parametrw (wicej o tym w rozdziale Funkcje). Mona te tworzy zmienne bdce typu wskanik na void

Specykatory
Specykatory to sowa kluczowe, ktre postawione przy typie danych zmieniaj jego znaczenie.

signed i unsigned
Na pocztku zastanwmy si, jak komputer moe przechowa liczb ujemn. Ot w przypadku przechowywania liczb ujemnych musimy w zmiennej przechowa jeszcze jej znak. Jak wiadomo, zmienna skada si z szeregu bitw. W przypadku uycia

SPECYFIKATORY

39

zmiennej pierwszy bit z lewej strony (nazywany take bitem najbardziej znaczcym) przechowuje znak liczby. Efektem tego jest spadek pojemnoci zmiennej, czyli zmniejszenie najwikszej wartoci, ktr moemy przechowa w zmiennej. Signed oznacza liczb ze znakiem, unsigned bez znaku (nieujemn). Mog by zastosowane do typw: char i int i czone ze specykatorami short i long (gdy ma to sens). Jeli przy signed lub unsigned nie napiszemy, o jaki typ nam chodzi, kompilator przyjmie warto domyln czyli int. Przykadowo dla zmiennej char(zajmujcej 8 bitw zapisanej w formacie uzupenie do dwch) wyglda to tak: signed char a; /* zmienna a przyjmuje wartoci od -128 do 127 */ unsigned char b; /* zmienna b przyjmuje wartoci od 0 do 255 */ unsigned short c; unsigned long int d; Jeeli nie podamy adnego ze specykatora wtedy liczba jest domylnie przyjmowana jako signed (nie dotyczy to typu char, dla ktrego jest to zalene od kompilatora). signed int i = 0; // jest rwnoznaczne z: int i = 0; Liczby bez znaku pozwalaj nam zapisa wiksze liczby przy tej samej wielkoci zmiennej ale trzeba uwaa, by nie zej z nimi poniej zera wtedy przewijaj si na sam koniec zakresu, co moe powodowa trudne do wykrycia bdy w programach.

short i long
Short i long s wskazwkami dla kompilatora, by zarezerwowa dla danego typu mniej (odpowiednio wicej) pamici. Mog by zastosowane do dwch typw: int i double (tylko long), majc rne znaczenie. Jeli przy short lub long nie napiszemy, o jaki typ nam chodzi, kompilator przyjmie warto domyln czyli int. Naley pamita, e to jedynie yczenie wobec kompilatora w wielu kompilatorach typy int i long int maj ten sam rozmiar. Standard jzyka C nakada jedynie na kompilatory nastpujce ograniczenia: int nie moe by krtszy ni 16 bitw; int musi by duszy lub rwny short a nie moe by duszy ni long; short int nie moe by krtszy ni 16 bitw; long int nie moe by krtszy ni 32 bity; Zazwyczaj typ int jest typem danych o dugoci odpowiadajcej wielkoci rejestrw procesora, czyli na procesorze szesnastobitowym ma 16 bitw, na trzydziestodwubitowym 32 itd.2 . Z tego powodu, jeli to tylko moliwe, do reprezentacji liczb cakowitych preferowane jest uycie typu int bez adnych specykatorw rozmiaru.
si to z pewnymi uwarunkowaniami historycznymi. Podrcznik do jzyka C duetu K&R zakada, e typ int mia si odnosi do typowej dla danego procesora dugoci liczby cakowitej. Natomiast, jeli procesor mg obsugiwa typy dusze lub krtsze stosownego znaczenia nabieray modykatory short i long. Dobrym przykadem moe by architektura i386, ktra umoliwia obliczenia na liczbach 16-bitowych. Dlatego te modykator short powoduje skrcenie zmiennej do 16 bitw.
2 Wie

40

ROZDZIA 7. ZMIENNE

Modykatory
volatile
volatile znaczy ulotny. Oznacza to, e kompilator wyczy dla takiej zmiennej optymalizacje typu zastpienia przez sta lub zawarto rejestru, za to wygeneruje kod, ktry bdzie odwoywa si zawsze do komrek pamici danego obiektu. Zapobiegnie to bdowi, gdy obiekt zostaje zmieniony przez cz programu, ktra nie ma zauwaalnego dla kompilatora zwizku z danym fragmentem kodu lub nawet przez zupenie inny proces. volatile float liczba1; float liczba2; { printf ("%d\n%d", liczba1, liczba2); /* instrukcje nie zwizane ze zmiennymi */ printf ("%d\n%d", liczba1, liczba2); } Jeeli zmienne liczba1 i liczba2 zmieni si niezauwaalnie dla kompilatora to odczytujc : liczba1 nastpi odwoanie do komrek pamici. Kompilator pobierze now warto zmiennej. liczba2 kompilator moe wypisa poprzedni warto, ktr przechowywa w rejestrze. Modykator volatile jest rzadko stosowany i przydaje si w wskich zastosowaniach, jak wspbieno i wspdzielenie zasobw oraz przerwania systemowe.

register
Jeeli utworzymy zmienn, ktrej bdziemy uywa w swoim programie bardzo czsto, moemy wykorzysta modykator register. Kompilator moe wtedy umieci zmienn w rejestrze, do ktrego ma szybki dostp, co przypieszy odwoania do tej zmiennej register int liczba ; W nowoczesnych kompilatorach ten modykator praktycznie nie ma wpywu na program. Optymalizator sam decyduje czy i co naley umieci w rejestrze. Nie mamy adnej gwarancji, e zmienna tak zadeklarowana rzeczywicie si tam znajdzie, chocia dostp do niej moe zosta przyspieszony w inny sposb. Raczej powinno si unika tego typu konstrukcji w programie.

static
Pozwala na zdeniowanie zmiennej statycznej. Statyczno polega na zachowaniu wartoci pomidzy kolejnymi denicjami tej samej zmiennej. Jest to przede wszystkim przydatne w funkcjach. Gdy zdeniujemy zmienn w ciele funkcji, to zmienna ta bdzie od nowa deniowana wraz z domyln wartoci (jeeli tak podano). W wypadku zmiennej okrelonej jako statyczna, jej warto si nie zmieni przy ponownym wywoaniu funkcji. Na przykad:

UWAGI void dodaj(int liczba) { int zmienna = 0; zmienna = zmienna + liczba; printf ("Wartosc zmiennej %d\n", zmienna); } Gdy wywoamy t funkcj np. 3 razy w ten sposb: dodaj(3); dodaj(5); dodaj(4); to ujrzymy na ekranie: Warto zmiennej Zmienna:3 Warto zmiennej Zmienna:5 Warto zmiennej Zmienna:4

41

jeeli jednak deklaracj zmiennej zmienimy na static int zmienna = 0, to warto zmiennej zostanie zachowana i po podobnym wykonaniu funkcji powinnymy ujrze: Warto zmiennej Zmienna:3 Warto zmiennej Zmienna:8 Warto zmiennej Zmienna:12 Zupenie co innego oznacza static zastosowane dla zmiennej globalnej. Jest ona wtedy widoczna tylko w jednym pliku. Zobacz te: rozdzia Biblioteki.

extern
Przez extern oznacza si zmienne globalne zadeklarowane w innych plikach informujemy w ten sposb kompilator, eby nie szuka jej w aktualnym pliku. Zobacz te: rozdzia Biblioteki.

auto
Zupenym archaizmem jest modykator auto, ktry oznacza tyle, e zmienna jest lokalna. Poniewa zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna, modykator ten nie ma obecnie adnego zastosowania praktycznego. auto jest spadkiem po wczeniejszych jzykach programowania, na ktrych oparty jest C (np. B).

Uwagi
Jzyk C++ pozwala na mieszanie deklaracji zmiennych z kodem. Wicej informacji w C++/Zmienne.

42

ROZDZIA 7. ZMIENNE

Rozdzia 8

Operatory
Przypisanie
Operator przypisania (=), jak sama nazwa wskazuje, przypisuje warto prawego argumentu lewemu, np.: int a = 5, b; b = a; printf("%d\n", b); /* wypisze 5 */ Operator ten ma czno prawostronn tzn. obliczanie przypisa nastpuje z prawa na lewo i zwraca on przypisan warto, dziki czemu moe by uyty kaskadowo: int a, b, c; a = b = c = 3; printf("%d %d %d\n", a, b, c);

/* wypisze "3 3 3" */

Skrcony zapis
C umoliwia te skrcony zapis postaci a #= b;, gdzie # jest jednym z operatorw: +, -, *, /, &, , , << lub >> (opisanych niej). Oglnie rzecz ujmujc zapis a #= b; jest rwnowany zapisowi a = a # (b);, np.: int a = 1; a += 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; */

Pocztkowo skrcona notacja miaa nastpujc skadni: a =# b, co czsto prowadzio do niejasnoci, np. i =-1 (i = -1 czy te i = i-1?). Dlatego te zdecydowano si zmieni kolejno operatorw.

43

44

ROZDZIA 8. OPERATORY

Rzutowanie
Zadaniem rzutowania jest konwersja danej jednego typu na dan innego typu. Konwersja moe by niejawna (domylna konwersja przyjta przez kompilator) lub jawna (podana explicite przez programist). Oto kilka przykadw konwersji niejawnej: int i = 42.7; float f = i; double d = f; unsigned u = i; f = 4.2; i = d; char *str = "foo"; const char *cstr = str; void *ptr = str; /* /* /* /* /* /* /* /* /* konwersja konwersja konwersja konwersja konwersja konwersja konwersja konwersja konwersja z z z z z z z z z double do int */ int do float */ float do double */ int do unsigned int */ double do float */ double do int */ const char* do char* [1] */ char* do const char* */ char* do void* */

Podczas konwersji zmiennych zawierajcych wiksze iloci danych do typw prostszych (np. double do int) musimy liczy si z utrat informacji, jak to miao miejsce w pierwszej linijce zmienna int nie moe przechowywa czci uamkowej tote zostaa ona odcita i w rezultacie zmiennej zostaa przypisana warto 42. Zaskakujca moe si wyda linijka oznaczona przez 1. Niejawna konwersja z typu const char* do typu char* nie jest dopuszczana przez standard C. Jednak literay napisowe (ktre s typu const char*) stanowi tutaj wyjtek. Wynika on z faktu, e byy one uywane na dugo przed wprowadzeniem swka const do jzyka i brak wspomnianego wyjtku spowodowaby, e dua cz kodu zostaaby nagle zakwalikowana jako niepoprawny kod. Do jawnego wymuszenia konwersji suy jednoargumentowy operator rzutowania, np.: double d = 3.14; int pi = (int)d; pi = (unsigned)pi >> 4;

/* 1 */ /* 2 */

W pierwszym przypadku operator zosta uyty, by zwrci uwag na utrat precyzji. W drugim, dlatego e bez niego operator przesunicia bitowego zachowuje si troch inaczej. Obie konwersje przedstawione powyej s dopuszczane przez standard jako jawne konwersje (tj. konwersja z double do int oraz z int do unsigned int), jednak niektre konwersje s bdne, np.: const char *cstr = "foo"; char *str = cstr; W takich sytuacjach mona uy operatora rzutowania by wymusi konwersj: const char *cstr = "foo"; char *str = (char*)cstr; Naley unika jednak takich sytuacji i nigdy nie stosowa rzutowania by uciszy kompilator. Zanim uyjemy operatora rzutowania naley si zastanowi co tak naprawd bdzie on robi i czy nie ma innego sposobu wykonania danej operacji, ktry nie wymagaby podejmowania tak drastycznych krokw.

OPERATORY ARYTMETYCZNE

45

Operatory arytmetyczne
Jzyk C deniuje nastpujce dwuargumentowe operatory arytmetyczne: dodawanie (+), odejmowanie (-), mnoenie (*), dzielenie (/), reszta z dzielenia (%) okrelona tylko dla liczb cakowitych (tzw. dzielenie modulo). int a=7, b=2, c; c = a % b; printf ("%d\n",c); /* wypisze "1" */ Naley pamita, e (w pewnym uproszczeniu) wynik operacji jest typu takiego jak najwikszy z argumentw. Oznacza to, e operacja wykonana na dwch liczbach cakowitych nadal ma typ cakowity nawet jeeli wynik przypiszemy do zmiennej rzeczywistej. Dla przykadu, poniszy kod: float a = 7 / 2; printf("%f\n", a); wypisze (wbrew oczekiwaniu pocztkujcych programistw) 3, a nie 3.5. Odnosi si to nie tylko do dzielenia, ale take mnoenia, np.: float a = 1000 * 1000 * 1000 * 1000 * 1000 * 1000; printf("%f\n", a); prawdopodobnie da o wiele mniejszy wynik ni bymy si spodziewali. Aby wymusi obliczenia rzeczywiste naley zmieni typ jednego z argumentw na liczb rzeczywist po prostu zmieniajc litera lub korzystajc z rzutowania, np.: float a = 7.0 / 2; float b = (float)1000 * 1000 * 1000 * 1000 * 1000 * 1000; printf("%f\n", a); printf("%f\n", b); Operatory dodawania i odejmowania s okrelone rwnie, gdy jednym z argumentw jest wskanik, a drugim liczba cakowita. Ten drugi jest take okrelony, gdy oba argumenty s wskanikami. O takim uyciu tych operatorw dowiesz si wicej C/Wskanikiw dalszej czci ksiki.

Inkrementacja i dekrementacja
Aby skrci zapis wprowadzono dodatkowe operatory: inkrementacji (++) i dekrementacji (), ktre dodatkowo mog by pre- lub postksowe. W rezultacie mamy wic cztery operatory: pre-inkrementacja (++i),

46 post-inkrementacja (i++), pre-dekrementacja (i) i post-dekrementacja (i).

ROZDZIA 8. OPERATORY

Operatory inkrementacji zwiksza, a dekrementacji zmniejsza argument o jeden. Ponadto operatory pre- zwracaj now warto argumentu, natomiast post- star warto argumentu. int a = b = c = a, b, c; 3; a--; /* po operacji b=3 a=2 */ --b; /* po operacji b=2 c=2 */

Czasami (szczeglnie w C++) uycie operatorw stawianych za argumentem jest nieco mniej efektywne (bo kompilator musi stworzy now zmienn by przechowa warto tymczasow). Bardzo wane jest, abymy poprawnie stosowali operatory dekrementacji i inkrementacji. Chodzi o to, aby w jednej instrukcji nie umieszcza kilku operatorw, ktre modykuj ten sam obiekt (zmienn). Jeeli taka sytuacja zaistnieje, to efekt dziaania instrukcji jest nieokrelony. Prostym przykadem mog by nastpujce instrukcje: int a = 1; a = a++; a = ++a; a = a++ + ++a; printf("%d %d\n", ++a, ++a); printf("%d %d\n", a++, a++); Kompilator GCC potra ostrzega przed takimi bdami - aby to czyni naley poda mu jako argument opcj -Wsequence-point.

Operacje bitowe
Oprcz operacji znanych z lekcji matematyki w podstawwce, jzyk C zosta wyposaony take w operatory bitowe, zdeniowane dla liczb cakowitych. S to: negacja bitowa ( ), koniunkcja bitowa (&), alternatywa bitowa () i alternatywa rozczna (XOR) (). Dziaaj one na poszczeglnych bitach przez co mog by szybsze od innych operacji. Dziaanie tych operatorw mona zdeniowa za pomoc poniszych tabel:

OPERACJE BITOWE "~" | 0 1 -----+----| 1 0 "&" | 0 1 -----+----0 | 0 0 1 | 0 1 = = 5 3 "|" | 0 1 -----+----0 | 0 1 1 | 1 1 "^" | 0 1 -----+----0 | 0 1 1 | 1 0

47

a | 0101 b | 0011 -------+-----~a | 1010 ~b | 1100 a & b | 0001 a | b | 0111 a ^ b | 0110

= 10 = 12 = 1 = 7 = 6

Lub bardziej opisowo: negacja bitowa daje w wyniku liczb, ktra ma bity rwne jeden tylko na tych pozycjach, na ktrych argument mia bity rwne zero; koniunkcja bitowa daje w wyniku liczb, ktra ma bity rwne jeden tylko na tych pozycjach, na ktrych oba argumenty miay bity rwne jeden (mnemonik: 1 gdy wszystkie 1); alternatywa bitowa daje w wyniku liczb, ktra ma bity rwne jeden na wszystkich tych pozycjach, na ktrych jeden z argumentw mia bit rwny jeden (mnemonik: 1 jeli jest 1); alternatywa rozczna daje w wyniku liczb, ktra ma bity rwne jeden tylko na tych pozycjach, na ktrych tylko jeden z argumentw mia bit rwny jeden (mnemonik: 1 gdy rne). Przy okazji warto zauway, e a ^ b ^ b to po prostu a. Waciwo ta zostaa wykorzystana w rnych algorytmach szyfrowania oraz funkcjach haszujcych. Alternatyw wyczn stosuje si np. do szyfrowania kodu wirusw polimorcznych.

Przesunicie bitowe
Dodatkowo, jzyk C wyposaony jest w operatory przesunicia bitowego w lewo (<<) i prawo (>>). Przesuwaj one w danym kierunku bity lewego argumentu o liczb pozycji podan jako prawy argument. Brzmi to moe strasznie, ale wcale takie nie jest. Rozwamy 4-bitowe liczby bez znaku (taki hipotetyczny unsigned int), wwczas: a | a<<1 | a<<2 | a>>1 | a>>2 ------+------+------+------+-----0001 | 0010 | 0100 | 0000 | 0000 0011 | 0110 | 1100 | 0001 | 0000 0101 | 1010 | 0100 | 0010 | 0001 1000 | 0000 | 0000 | 0100 | 0010 1111 | 1110 | 1100 | 0111 | 0011 1001 | 0010 | 0100 | 0100 | 0010

48

ROZDZIA 8. OPERATORY

Nie jest to zatem takie straszne na jakie wyglda. Wida, e bity bdce na skraju s tracone, a w puste miejsca wpisywane s zera. Inaczej rzecz si ma jeeli lewy argument jest liczb ze znakiem. Dla przesunicia bitowego w lewo a << b jeeli a jest nieujemna i warto a 2b mieci si w zakresie liczby to jest to wynikiem operacji. W przeciwnym wypadku dziaanie jest niezdeniowane1 . Dla przesunicia bitowego w lewo, jeeli lewy argument jest nieujemny to operacja zachowuje si tak jak w przypadku liczb bez znaku. Jeeli jest on ujemny to zachowanie jest zalene od implementacji. Zazwyczaj operacja przesunicia w lewo zachowuje si tak samo jak dla liczb bez znaku, natomiast przy przesuwaniu w prawo bit znaku nie zmienia si2 : a | a>>1 | a>>2 ------+------+-----0001 | 0000 | 0000 0011 | 0001 | 0000 0101 | 0010 | 0001 1000 | 1100 | 1110 1111 | 1111 | 1111 1001 | 1100 | 1110 Przesunicie bitowe w lewo odpowiada pomnoeniu, natomiast przesunicie bitowe w prawo podzieleniu liczby przez dwa do potgi jak wyznacza prawy argument. Jeeli prawy argument jest ujemny lub wikszy lub rwny liczbie bitw w typie, dziaanie jest niezdeniowane. #include <stdio.h> int main () { int a = 6; printf ("6 << 2 = %d\n", a<<2); printf ("6 >> 2 = %d\n", a>>2); return 0; }

/* wypisze 24 */ /* wypisze 1 */

Porwnanie
W jzyku C wystpuj nastpujce operatory porwnania: rwne (==), rne (!=), mniejsze (<), wiksze (>), mniejsze lub rwne (<=) i
1 Niezdeniowane w takim samym sensie jak niezdeniowane jest zachowanie programu, gdy prbujemy odwoa si do wartoci wskazywanej przez warto NULL czy do zmiennych poza tablic. 2 ale jeeli zaley Ci na przenonoci kodu nie moesz na tym polega

OPERATORY LOGICZNE wiksze lub rwne (>=).

49

Wykonuj one odpowiednie porwnanie swoich argumentw i zwracaj jedynk jeeli warunek jest speniony lub zero jeeli nie jest.

Czste bdy
Osoby, ktre poprzednio uczyy si innych jzykw programowania, czsto maj nawyk uywania w instrukcjach logicznych zamiast operatora porwnania ==, operatora przypisania =. Ma to czsto zgubne efekty, gdy przypisanie zwraca warto przypisan lewemu argumentowi. Porwnajmy ze sob dwa warunki: (a = 1) (a == 1) Pierwszy z nich zawsze bdzie prawdziwy, niezalenie od wartoci zmiennej a! Dzieje si tak, poniewa zostaje wykonane przypisanie do a wartoci 1 a nastpnie jako warto jest zwracane to, co zostao przypisane czyli jeden. Drugi natomiast bdzie prawdziwy tylko, gdy a jest rwne 1. W celu uniknicia takich bdw niektrzy programici zamiast pisa a == 1 pisz 1 == a, dziki czemu pomyka spowoduje, e kompilator zgosi bd. Warto zauway, e kompilator GCC potra w pewnych sytuacjach wychwyci taki bd. Aby zacz to robi naley poda mu argument -Wparentheses. Innym bdem jest uycie zwykych operatorw porwnania do sprawdzania relacji pomidzy liczbami rzeczywistymi. Poniewa operacje zmiennoprzecinkowe wykonywane s z pewnym przyblieniem rzadko kiedy dwie zmienne typu oat czy double s sobie rwne. Dla przykadu: #include <stdio.h> int main () { float a, b, c; a = 1e10; /* tj. 10 do potgi 10 */ b = 1e-10; /* tj. 10 do potgi -10 */ c = b; /* c = b */ c = c + a; /* c = b + a (teoretycznie) */ c = c - a; /* c = b + a - a = b (teoretycznie) */ printf("%d\n", c == b); /* wypisze 0 */ } Obejciem jest porwnywanie moduu rnicy liczb. Rwnie i takie bdy kompilator GCC potra wykrywa aby to robi naley poda mu argument -Wfloat-equal.

Operatory logiczne
Analogicznie do czci operatorw bitowych, w C deniuje si operatory logiczne, mianowicie: negacja (!),

50 koniunkcja (&&) i alternatywa (||).

ROZDZIA 8. OPERATORY

Dziaaj one bardzo podobnie do operatorw bitowych jednak zamiast operowa na poszczeglnych bitach bior pod uwag warto logiczn argumentw. Wyraenie ma warto logiczn 0 wtedy i tylko wtedy, gdy jest rwne 0. W przeciwnym wypadku ma warto 1. Operatory te w wyniku daj albo 0 albo 1.

Skrcone obliczanie wyrae logicznych


Jzyk C wykonuje skrcone obliczanie wyrae logicznych to znaczy, oblicza wyraenie tylko tak dugo, jak nie wie, jaka bdzie jego ostateczna warto. To znaczy, idzie od lewej do prawej obliczajc kolejne wyraenia (dodatkowo na kolejno wpyw maj nawiasy) i gdy bdzie mia na tyle informacji, by obliczy warto caoci, nie liczy reszty. Moe to wydawa si niejasne, ale przyjrzyjmy si wyraeniom logicznym: A && B A || B Jeli A jest faszywe to nie trzeba liczy B w pierwszym wyraeniu, bo fasz i dowolne wyraenie zawsze da fasz. Analogicznie, jeli A jest prawdziwe, to wyraenie 2 jest prawdziwe i warto B nie ma znaczenia. Poza zwikszon szybkoci zysk z takiego rozwizania polega na moliwoci stosowania efektw ubocznych. Idea efektu ubocznego opiera si na tym, e w wyraeniu mona wywoa funkcje, ktre bd robiy poza zwracaniem wyniku inne rzeczy, oraz uywa podstawie. Popatrzmy na poniszy przykad: ( (a > 0) || (a < 0) || (a = 1) ) Jeli a bdzie wiksze od 0 to obliczona zostanie tylko warto wyraenia (a > 0) da ono prawd, czyli reszta oblicze nie bdzie potrzebna. Jeli a bdzie mniejsze od zera, najpierw zostanie obliczone pierwsze podwyraenie a nastpnie drugie, ktre da prawd. Ciekawy bdzie jednak przypadek, gdy a bdzie rwne zero do a zostanie wtedy podstawiona jedynka i cao wyraenia zwrci prawd (bo 1 jest traktowane jak prawda). Efekty uboczne pozwalaj na rne szalestwa i wykonywanie zoonych operacji w samych warunkach logicznych, jednak przesadne uywanie tego typu konstrukcji powoduje, e kod staje si nieczytelny i jest uwaane za zy styl programistyczny.

Operator wyraenia warunkowego


C posiada szczeglny rodzaj operatora to operator ?: zwany te operatorem wyraenia warunkowego. Jest to jedyny operator w tym jzyku przyjmujcy trzy argumenty. a ? b : c Jego dziaanie wyglda nastpujco: najpierw oceniana jest warto logiczna wyraenia a; jeli jest ono prawdziwe, to zwracana jest warto b, jeli natomiast wyraenie a jest nieprawdziwe, zwracana jest warto c. Praktyczne zastosowanie znajdywanie wikszej z dwch liczb:

OPERATOR PRZECINEK a = (b>=c) ? b : c;

51 /* Jeli b jest wiksze bd rwne c, to zwr b. W przeciwnym wypadku zwr c. */

lub zwracanie moduu liczby: a = a < 0 ? -a : a; Wartoci wyrae s przy tym operatorze obliczane tylko jeeli zachodzi taka potrzeba, np. w wyraeniu 1 ? 1 : foo() funkcja foo() nie zostanie wywoana.

Operator przecinek
Operator przecinek jest do dziwnym operatorem. Powoduje on obliczanie wartoci wyrae od lewej do prawej po czym zwrcenie wartoci ostatniego wyraenia. W zasadzie, w normalnym kodzie programu ma on niewielkie zastosowanie, gdy zamiast niego lepiej rozdziela instrukcje zwykymi rednikami. Ma on jednak zastosowanie w instrukcji sterujcej for.

Operator sizeof
Operator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanego typu lub typu podanego wyraenia. Ma on dwa rodzaje: sizeof(typ) lub sizeof wyraenie. Przykadowo: #include <stdio.h> int main() { printf("sizeof(short ) printf("sizeof(int ) printf("sizeof(long ) printf("sizeof(float ) printf("sizeof(double) return 0; }

= = = = =

%d\n", %d\n", %d\n", %d\n", %d\n",

sizeof(short )); sizeof(int )); sizeof(long )); sizeof(float )); sizeof(double));

Operator ten jest czsto wykorzystywany przy dynamicznej alokacji pamici, co zostanie opisane w rozdziale powiconym wskanikom. Pomimo, e w swej budowie operator sizeof bardzo przypomina funkcj, to jednak ni nie jest. Wynika to z trudnoci w implementacji takowej funkcji jej specyka musiaaby odnosi si bezporednio do kompilatora. Ponadto jej argumentem musiayby by typy, a nie zmienne. W jzyku C nie jest moliwe przekazywanie typu jako argumentu. Ponadto czsto zdarza si, e rozmiar zmiennej musi by wiadomy jeszcze w czasie kompilacji to ewidentnie wyklucza implementacj sizeof() jako funkcji.

Inne operatory
Poza wyej opisanymi operatorami istniej jeszcze: operator opisany przy okazji opisywania tablic;

52

ROZDZIA 8. OPERATORY jednoargumentowe operatory * i & opisane przy okazji opisywania wskanikw; operatory . i -> opisywane przy okazji opisywania struktur i unii; operator () bdcy operatorem wywoania funkcji, operator () grupujcy wyraenia (np. w celu zmiany kolejnoci obliczania

Priorytety i kolejno oblicze


Jak w matematyce, rwnie i w jzyku C obowizuje pewna ustalona kolejno dziaa. Aby mc j okreli naley ustali dwa parametry danego operatora: jego priorytet oraz czno. Przykadowo operator mnoenia ma wyszy priorytet ni operator dodawania i z tego powodu w wyraeniu 2+22 najpierw wykonuje si mnoenie, a dopiero potem dodawanie. Drugim parametrem jest czno okrela ona od ktrej strony wykonywane s dziaania w przypadku poczenia operatorw o tym samym priorytecie. Na przykad odejmowanie ma czno lewostronn i 222 da w wyniku -2. Gdyby miao czno prawostronn w wynikiem byoby 2. Przykadem matematycznego operatora, ktry ma 2 czno prawostronn jest potgowanie, np. 32 jest rwne 81. W jzyku C wystpuje duo poziomw operatorw. Poniej przedstawiamy tabelk ze wszystkimi operatorami poczynajc od tych z najwyszym priorytetem (wykonywanych na pocztku). Tabela 8.1: Priorytety operatorw Operator nawiasy jednoargumentowe przyrostkowe: . -> woanie funkcji postinkrementacja postdekrementacja jednoargumentowe przedrostkowe:! + * & sizeof preinkrementacja predekrementacja rzutowanie */% + << >> < <= > >= == != & && || ?: operatory przypisania , czno nie dotyczy lewostronna prawostronna lewostronna lewostronna lewostronna lewostronna lewostronna lewostronna lewostronna lewostronna lewostronna lewostronna prawostronna prawostronna lewostronna

Dua liczba poziomw pozwala czasami zaoszczdzi troch milisekund w trakcie pisania programu i bajtw na dysku, gdy czsto nawiasy nie s potrzebne, nie naley

KOLEJNO WYLICZANIA ARGUMENTW OPERATORA

53

jednak z tym przesadza, gdy kod programu moe sta si mylcy nie tylko dla innych, ale po latach (czy nawet i dniach) rwnie dla nas. Warto take podkreli, e operator koniunkcji ma niszy priorytet ni operator porwnania3 . Oznacza to, e kod if (flags & FL_MASK == FL_FOO) zazwyczaj da rezultat inny od oczekiwanego. Najpierw bowiem wykona si porwnanie wartoci FL MASK z wartoci FL FOO, a dopiero potem koniunkcja bitowa. W takich sytuacjach naley pamita o uyciu nawiasw: if ((flags & FL_MASK) == FL_FOO) Kompilator GCC potra wykrywa takie bdy i aby to robi naley poda mu argument -Wparentheses.

Kolejno wyliczania argumentw operatora


W przypadku wikszoci operatorw (wyjtkami s tu &&, ||i przecinek) nie da si okreli, ktra warto argumentu zostanie obliczona najpierw. W wikszoci przypadkw nie ma to wikszego znaczenia, lecz w przypadku wyrae, ktre maj efekty uboczne wymuszenie konkretnej kolejnoci moe by potrzebne. Wemy dla przykadu program #include <stdio.h> int foo(int a) { printf("%d\n", a); return 0; } int main(void) { return foo(1) + foo(2); } Ot, nie wiemy czy najpierw zostanie wywoana funkcja foo z parametrem jeden, czy dwa. Jeeli ma to znaczenie naley uy zmiennych pomocniczych zmieniajc denicj funkcji main na: int main(void) { int tmp = foo(1); return tmp + foo(2); } Teraz ju na pewno najpierw zostanie wypisana jedynka, a potem dopiero dwjka. Sytuacja jeszcze bardziej si komplikuje, gdy uywamy wyrae z efektami ubocznymi jako argumentw funkcji, np.:
3 Jest to zaszo historyczna z czasw, gdy nie byo logicznych operatorw && oraz ||i zamiast nich stosowano operatory bitowe & oraz |.

54 #include <stdio.h> int foo(int a) { printf("%d\n", 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); }

ROZDZIA 8. OPERATORY

Teraz te nie wiemy, ktra z 24 permutacji liczb 1, 2, 3 i 4 zostanie wypisana i ponownie naley pomc sobie zmiennymi tymczasowymi jeeli zaley nam na konkretnej kolejnoci: int main(void) { int tmp = foo(1); tmp += foo(2); tmp += foo(3); return tmp + foo(4); }

Uwagi
W jzyku C++ wprowadzony zosta dodatkowo inny sposb zapisu rzutowania, ktry pozwala na atwiejsze znalezienie w kodzie miejsc, w ktrych dokonujemy rzutowania. Wicej na stronie C++/Zmienne.

Zobacz te
C/Skadnia#Operatory

Rozdzia 9

Instrukcje sterujce
C jest jzykiem imperatywnym oznacza to, e instrukcje wykonuj si jedna po drugiej w takiej kolejnoci w jakiej s napisane. Aby mc zmieni kolejno wykonywania instrukcji potrzebne s instrukcje sterujce. Na wstpie przypomnijmy jeszcze, e wyraenie jest prawdziwe wtedy i tylko wtedy, gdy jest rne od zera, a faszywe wtedy i tylko wtedy, gdy jest rwne zeru.

Instrukcje warunkowe
Instrukcja if
Uycie instrukcji if wyglda tak: if (wyraenie) { /* blok wykonany, jeli wyraenie jest prawdziwe */ } /* dalsze instrukcje */ Istnieje take moliwo reakcji na nieprawdziwo wyraenia wtedy naley zastosowa sowo kluczowe else: if (wyraenie) { /* blok wykonany, jeli wyraenie jest prawdziwe */ } else { /* blok wykonany, jeli wyraenie jest nieprawdziwe */ } /* dalsze instrukcje */ Przypatrzmy si bardziej yciowemu programowi, ktry porwnuje ze sob dwie liczby: #include <stdio.h> int main () { int a, b; a = 4; 55

56

ROZDZIA 9. INSTRUKCJE STERUJCE b = 6; if (a==b) { printf ("a jest rwne b\n"); } else { printf ("a nie jest rwne b\n"); } return 0;

} Czasami zamiast pisa instrukcj if moemy uy operatora wyboru (patrz Operatory): if (a != 0) b = 1/a; else b = 0; ma dokadnie taki sam efekt jak: b = (a !=0) ? 1/a : 0;

Instrukcja switch
Aby ograniczy wielokrotne stosowanie instrukcji if moemy uy switch. Jej uycie wyglda tak: switch (wyraenie) { case warto1: /* instrukcje, jeli wyraenie == warto1 */ break; case warto2: /* instrukcje, jeli wyraenie == warto2 */ break; /* ... */ default: /* instrukcje, jeli aden z wczeniejszych warunkw */ break; /* nie zosta speniony */ } Naley pamita o uyciu break po zakoczeniu listy instrukcji nastpujcych po case. Jeli tego nie zrobimy, program przejdzie do wykonywania instrukcji z nastpnego case. Moe mie to fatalne skutki: #include <stdio.h> int main () { int a, b; printf ("Podaj a: scanf ("%d", &a); printf ("Podaj b: scanf ("%d", &b); switch (b) { case 0: printf default: printf

"); ");

("Nie mona dzieli przez 0!\n"); /* tutaj zabrako break! */ ("a/b=%d\n", a/b);

INSTRUKCJE WARUNKOWE } return 0; }

57

A czasami moe by celowym zabiegiem (tzw. fall-through) wwczas warto zaznaczy to w komentarzu. Oto przykad:

#include <stdio.h> int main () { int a = 4; switch ((a%3)) { case 0: printf ("Liczba %d dzieli si przez 3\n", a); break; case -2: case -1: case 1: case 2: printf ("Liczba %d nie dzieli si przez 3\n", a); break; } return 0; } Przeanalizujmy teraz dziaajcy przykad:

#include <stdio.h> int main () { unsigned int dzieci = 3, podatek=1000; switch (dzieci) { case 0: break; /* brak dzieci - czyli brak ulgi */ case 1: /* ulga 2% */ podatek = podatek - (podatek/100* 2); break; case 2: /* ulga 5% */ podatek = podatek - (podatek/100* 5); break; default: /* ulga 10% */ podatek = podatek - (podatek/100*10); break; } printf ("Do zapaty: %d\n", podatek); }

58

ROZDZIA 9. INSTRUKCJE STERUJCE

Ptle
Instrukcja while
Czsto zdarza si, e nasz program musi wielokrotnie powtarza ten sam cig instrukcji. Aby nie przepisywa wiele razy tego samego kodu mona skorzysta z tzw. ptli. Ptla wykonuje si dotd, dopki prawdziwy jest warunek. while (warunek) { /* instrukcje do wykonania w ptli */ } /* dalsze instrukcje */ Ca zasad ptli zrozumiemy lepiej na jakim dziaajcym przykadzie. Zamy, e mamy obliczy kwadraty liczb od 1 do 10. Piszemy zatem program: #include <stdio.h> int main () { int a = 1; while (a <= 10) { /* dopki a nie przekracza 10 */ printf ("%d\n", a*a); /* wypisz a*a na ekran*/ ++a; /* zwikszamy a o jeden*/ } return 0; } Po analizie kodu mog nasun si dwa pytania: Po co zwiksza warto a o jeden? Ot gdybymy nie dodali instrukcji zwikszajcej a, to warunek zawsze byby speniony, a ptla krcia by si w nieskoczono. Dlaczego warunek to a <= 10 a nie a!=10? Odpowied jest do prosta. Ptla sprawdza warunek przed wykonaniem kolejnego obrotu. Dlatego te gdyby warunek brzmia a!=10 to dla a=10 jest on nieprawdziwy i ptla nie wykonaaby ostatniej iteracji, przez co program generowaby kwadraty liczb od 1 do 9, a nie do 10.

Instrukcja for
Od instrukcji while czasami wygodniejsza jest instrukcja for. Umoliwia ona wpisanie ustawiania zmiennej, sprawdzania warunku i inkrementowania zmiennej w jednej linijce co czsto zwiksza czytelno kodu. Instrukcj for stosuj si w nastpujcy sposb: for (wyraenie1; wyraenie2; wyraenie3) { /* instrukcje do wykonania w ptli */ } /* dalsze instrukcje */

PTLE

59

Jak wida, ptla for znacznie rni si od tego typu ptli, znanych w innych jzykach programowania. Opiszemy wic, co oznaczaj poszczeglne wyraenia: wyraenie1 jest to instrukcja, ktra bdzie wykonana przed pierwszym przebiegiem ptli. Zwykle jest to inicjalizacja zmiennej, ktra bdzie suya jako licznik przebiegw ptli. wyraenie2 jest warunkiem zakoczenia ptli. Ptla wykonuje si tak dugo, jak prawdziwy jest ten warunek. wyraenie3 jest to instrukcja, ktra wykonywana bdzie po kadym przejciu ptli. Zamieszczone s tu instrukcje, ktre zwikszaj licznik o odpowiedni warto. Jeeli wewntrz ptli nie ma adnych instrukcji continue (opisanych niej) to jest ona rwnowana z: { wyraenie1; while (wyraenie2) { /* instrukcje do wykonania w ptli */ wyraenie3; } } /* dalsze instrukcje */ Wan rzecz jest tutaj to, eby zrozumie i zapamita jak tak naprawd dziaa ptla for. Pocztkujcym programistom nieznajomo tego faktu sprawia wiele problemw. W pierwszej kolejnoci w ptli for wykonuje si wyraenie1. Wykonuje si ono zawsze, nawet jeeli warunek przebiegu ptli jest od samego pocztku faszywy. Po wykonaniu wyraenie1 ptla for sprawdza warunek zawarty w wyraenie2, jeeli jest on prawdziwy, to wykonywana jest tre ptli for, czyli najczciej to co znajduje si midzy klamrami, lub gdy ich nie ma, nastpna pojedyncza instrukcja. W szczeglnoci musimy pamita, e sam rednik te jest instrukcj instrukcj pust. Gdy ju zostanie wykonana tre ptli for, nastpuje wykonanie wyrazenie3. Naley zapamita, e wyraenie3 zostanie wykonane, nawet jeeli by to ju ostatni obieg ptli. Ponisze 3 przykady ptli for w rezultacie dadz ten sam wynik. Wypisz na ekran liczby od 1 do 10. for(i=1; i<=10; ++i){ printf("%d", i); } for(i=1; i<=10; ++i) printf("%d", i); for(i=1; i<=10; printf("%d", ++i ) ); Dwa pierwsze przykady korzystaj z wasnoci struktury blokowej, kolejny przykad jest ju bardziej wyranowany i korzysta z tego, e jako wyraenie3 moe zosta podane dowolne bardziej skomplikowane wyraenie, zawierajce w sobie inne podwyraenia. A oto kolejny program, ktry najpierw wywietla liczby w kolejnoci rosncej, a nastpnie wraca.

60 #include <stdio.h> int main() { int i; for(i=1; i<=5; ++i){ printf("%d", i); } for( ; i>=1; i--){ printf("%d", i); } return 0; }

ROZDZIA 9. INSTRUKCJE STERUJCE

Po analizie powyszego kodu, pocztkujcy programista moe stwierdzi, e ptla wypisze 123454321. Stanie si natomiast inaczej. Wynikiem dziaania powyszego programu bdzie cig cyfr 12345654321. Pierwsza ptla wypisze cyfry 12345, lecz po ostatnim swoim obiegu ptla for (tak jak zwykle) zinkrementuje zmienn i. Gdy druga ptla przystpi do pracy, zacznie ona odlicza poczwszy od liczby i=6, a nie 5. By spowodowa wywietlanie liczb o 1 do 5 i z powrotem wystarczy gdzie midzy ostatnim obiegiem pierwszej ptli for a pierwszym obiegiem drugiej ptli for zmniejszy warto zmiennej i o 1. Niech podsumowaniem bdzie jaki dziaajcy fragment kodu, ktry moe oblicza wartoci kwadratw liczb od 1 do 10.

#include <stdio.h> int main () { int a; for (a=1; a<=10; ++a) { printf ("%d\n", a*a); } return 0; }

W kodzie rdowym spotyka si czsto inkrementacj i++. Jest to zy zwyczaj, biorcy si z wzorowania si na nazwie jzyka C++. Post-inkrementacja i++ powoduje, e tworzony jest obiekt tymczasowy, ktry jest zwracany jako wynik operacji (cho wynik ten nie jest nigdzie czytany). Jedno kopiowanie liczby do zmiennej tymczasowej nie jest drogie, ale w ptli for takie kopiowanie odbywa si po kadym przebiegu ptli. Dodatkowo, w C++ podobn konstrukcj stosuje si do obiektw kopiowanie obiektu moe by ju czasochonn czynnoci. Dlatego w ptli for naley stosowa wycznie ++i.

PTLE

61

Instrukcja do..while
Ptle while i for maj jeden zasadniczy mankament moe si zdarzy, e nie wykonaj si ani raz. Aby mie pewno, e nasza ptla bdzie miaa co najmniej jeden przebieg musimy zastosowa ptl do while. Wyglda ona nastpujco: do { /* instrukcje do wykonania w ptli */ } while (warunek); /* dalsze instrukcje */ Zasadnicz rnic ptli do while jest fakt, i sprawdza ona warunek pod koniec swojego przebiegu. To wanie ta cecha decyduje o tym, e ptla wykona si co najmniej raz. A teraz przykad dziaajcego kodu, ktry tym razem bdzie oblicza trzeci potg liczb od 1 do 10. #include <stdio.h> int main () { int a = 1; do { printf ("%d\n", a*a*a); ++a; } while (a <= 10); return 0; } Moe si to wyda zaskakujce, ale rwnie przy tej ptli zamiast bloku instrukcji mona zastosowa pojedyncz instrukcj, np.: #include <stdio.h> int main () { int a = 1; do printf ("%d\n", a*a*a); while (++a <= 10); return 0; }

Instrukcja break
Instrukcja break pozwala na opuszczenie wykonywania ptli w dowolnym momencie. Przykad uycia: int a; for (a=1 ; a != 9 ; ++a) { if (a == 5) break; printf ("%d\n", a); } Program wykona tylko 4 przebiegi ptli, gdy przy 5 przebiegu instrukcja break spowoduje wyjcie z ptli.

62 Break i ptle nieskoczone

ROZDZIA 9. INSTRUKCJE STERUJCE

W przypadku ptli for nie trzeba podawa warunku. W takim przypadku kompilator przyjmie, e warunek jest stale speniony. Oznacza to, e ponisze ptle s rwnowane: for (;;) { /* ... */ } for (;1;) { /* ... */ } for (a;a;a) { /* ... */} /*gdzie a jest dowoln liczba rzeczywist rn od 0*/ while (1) { /* ... */ } do { /* ... */ } while (1); Takie ptle nazywamy ptlami nieskoczonymi, ktre przerwa moe jedynie instrukcja break1 (z racji tego, e warunek ptli zawsze jest prawdziwy) 2 . Wszystkie fragmenty kodu dziaaj identycznie: int i = 0; for (;i!=5;++i) { /* kod ... */ } int i = 0; for (;;++i) { if (i == 5) break; } int i = 0; for (;;) { if (i == 5) break; ++i; }

Instrukcja continue
W przeciwiestwie do break, ktra przerywa wykonywanie ptli instrukcja continue powoduje przejcie do nastpnej iteracji, o ile tylko warunek ptli jest speniony. Przykad: int i; for (i = 0 ; i < 100 ; ++i) { printf ("Poczatek\n"); if (i > 40) continue ; printf ("Koniec\n"); }
1 Tak naprawd podobn operacje, moemy wykona za pomoc polecenia goto. W praktyce jednak stosuje si zasad, e break stosuje si do przerwania dziaania ptli i wyjcia z niej, goto stosuje si natomiast wtedy, kiedy chce si wydosta si z kilku zagniedonych ptli za jednym zamachem. Do przerwania pracy ptli mog nam jeszcze posuy polecenia exit() lub return, ale wwczas zakoczymy nie tylko dziaanie ptli, ale i caego programu/funkcji. 2 artobliwie mona powiedzie, e stosujc ptl nieskoczon to najlepiej korzysta z ptli for(;;)\\, gdy wymaga ona napisania najmniejszej liczby znakw w porwnaniu do innych konstrukcji.

INSTRUKCJA GOTO

63

Dla wartoci i wikszej od 40 nie bdzie wywietlany komunikat Koniec. Ptla wykona pene 100 przej. Oto praktyczny przykad uycia tej instrukcji: #include <stdio.h> int main() { int i; for (i = 1 ; i <= 50 ; ++i) { if (i%4==0) continue ; printf ("%d, ", i); } return 0; } Powyszy program generuje liczby z zakresu od 1 do 50, ktre nie s podzielne przez 4.

Instrukcja goto
Istnieje take instrukcja, ktra dokonuje skoku do dowolnego miejsca programu, oznaczonego tzw. etykiet. etykieta: /* instrukcje */ goto etykieta; Uwaga!: kompilator GCC w wersji 4.0 i wyszych jest bardzo uczulony na etykiety zamieszczone przed nawiasem klamrowym, zamykajcym blok instrukcji. Innymi sowy: niedopuszczalne jest umieszczanie etykiety zaraz przed klamr, ktra koczy blok instrukcji, zawartych np. w ptli for. Mona natomiast stosowa etykiet przed klamr koczc dan funkcj. Instrukcja goto amie sekwencj instrukcji i powoduje skok do dowolnie odlegego miejsca w programie - co moe mie nieprzewidziane skutki. Zbyt czste uywanie goto moe prowadzi do trudnych do zlokalizowania bdw. Oprcz tego kompilatory maj kopoty z optymalizacj kodu, w ktrym wystpuj skoki. Z tego powodu zaleca si ograniczenie zastosowania tej instrukcji wycznie do opuszczania wielokrotnie zagniedonych ptli. Przykad uzasadnionego uycia: int i,j; for (i = 0; i < for (j = i; j if (i + j % } } koniec: /* dalsza czesc 10; ++i) { < i+10; ++j) { 21 == 0) goto koniec;

programu */

64

ROZDZIA 9. INSTRUKCJE STERUJCE

Natychmiastowe koczenie programu funkcja exit


Program moe zosta w kadej chwili zakoczony do tego wanie celu suy funkcja exit. Uywamy jej nastpujco: exit (kod_wyjcia); Liczba cakowita kod wyjcia jest przekazywana do procesu macierzystego, dziki czemu dostaje on informacj, czy program w ktrym wywoalimy t funkcj zakoczy si poprawnie lub czy si tak nie stao. Kody wyjcia s nieustandaryzowane i eby program by w peni przenony naley stosowa makra EXIT SUCCESS i EXIT FAILURE, cho na wielu systemach kod 0 oznacza poprawne zakoczenie, a kod rny od 0 bdne. W kadym przypadku, jeeli nasz program potra generowa wiele rnych kodw, warto je wszystkie udokumentowa w ew. dokumentacji. S one te czasem pomocne przy wykrywaniu bdw.

Uwagi
W jzyku C++ mona deklarowa zmienne w nagwku ptli for w nastpujcy sposb: for(int i=0; i<10; ++i) (wicej informacji w C++/Zmienne)

Rozdzia 10

Podstawowe procedury wejcia i wyjcia


Wejcie/wyjcie
Komputer byby cakowicie bezuyteczny, gdyby uytkownik nie mg si z nim porozumie (tj. wprowadzi danych lub otrzyma wynikw pracy programu). Programy komputerowe su w najwikszym uproszczeniu do obrbki danych wic musz te dane jako od nas otrzyma, przetworzy i przekaza nam wynik. Takie wczytywanie i wyrzucanie danych w terminologii komputerowej nazywamy wejciem (input) i wyjciem (output). Bardzo czsto mwi si o wejciu i wyjciu danych cznie input/output, albo po prostu I/O. W C do komunikacji z uytkownikiem su odpowiednie funkcje. Zreszt, do wielu zada w C su funkcje. Uywajc funkcji, nie musimy wiedzie, w jaki sposb komputer wykonuje jakie zadanie, interesuje nas tylko to, co ta funkcja robi. Funkcje niejako wykonuj za nas cz pracy, poniewa nie musimy pisa by moe dziesitek linijek kodu, eby np. wypisa tekst na ekranie (wbrew pozorom kod funkcji wywietlajcej tekst na ekranie jest do skomplikowany). Jeszcze taka uwaga gdy piszemy o jakiej funkcji, zazwyczaj podajc jej nazw dopisujemy na kocu nawias: printf() scanf() eby byo jasne, e chodzi o funkcj, a nie o co innego. Wyej wymienione funkcje to jedne z najczciej uywanych funkcji w C pierwsza suy do wypisywania danych na ekran, natomiast druga do wczytywania danych z klawiatury1 .
zasadzie standard C nie deniuje czego takiego jak ekran i klawiatura mowa w nim o standardowym wyjciu i standardowym wejciu. Zazwyczaj jest to wanie ekran i klawiatura, ale nie zawsze. W szczeglnoci uytkownicy Linuksa lub innych systemw uniksowych mog by przyzwyczajeniu do przekierowania wejcia/wyjcia z/do pliku czy czenie komend w potoki (ang. pipe). W takich sytuacjach dane nie s wywietlane na ekranie, ani odczytywane z klawiatury.
1W

65

66

ROZDZIA 10. PODSTAWOWE PROCEDURY WEJCIA I WYJCIA

Funkcje wyjcia
Funkcja printf
W przykadzie Hello World! uylimy ju jednej z dostpnych funkcji wyjcia, a mianowicie funkcji printf(). Z punktu widzenia swoich moliwoci jest to jedna z bardziej skomplikowanych funkcji, a jednoczenie jest jedn z najczciej uywanych. Przyjrzyjmy si ponownie kodowi programu Hello, World!. #include <stdio.h> int main(void) { printf("Hello world!\n"); return 0; } Po skompilowaniu i uruchomieniu, program wypisze na ekranie: Hello world! W naszym przykadowym programie, chcc by funkcja printf() wypisaa tekst na ekranie, umiecilimy go w cudzysowach wewntrz nawiasw. Oglnie, wywoanie funkcji printf() wyglda nastpujco: printf(format, argument1, argument2, ...); Przykadowo: int i = 500; printf("Liczbami cakowitymi s na przykad %i oraz %i.\n", 1, i); wypisze Liczbami cakowitymi s na przykad 1 oraz 500. Format to napis ujty w cudzysowy, ktry okrela oglny ksztat, schemat tego, co ma by wywietlone. Format jest drukowany tak, jak go napiszemy, jednak niektre znaki specjalne zostan w nim podmienione na co innego. Przykadowo, znak specjalny \n jest zamieniany na znak nowej linii 2 . Natomiast procent jest podmieniany na jeden z argumentw. Po procencie nastpuje specykacja, jak wywietli dany argument. W tym przykadzie %i (od int) oznacza, e argument ma by wywietlony jak liczba cakowita. W zwizku z tym, e \ i % maj specjalne znaczenie, aby wydrukowa je, naley uy ich podwjnie: printf("Procent: %% Backslash: \\"); drukuje: Procent: % Backslash: \
2 Zmiana ta nastpuje w momencie kompilacji programu i dotyczy wszystkich literaw napisowych. Nie jest to jaka szczeglna wasno funkcji printf(). Wicej o tego typu sekwencjach i cigach znakw w szczeglnoci opisane jest w rozdziale Napisy.

FUNKCJA PUTS

67

(bez przejcia do nowej linii). Na licie argumentw moemy miesza ze sob zmienne rnych typw, liczby, napisy itp. w dowolnej liczbie. Funkcja printf przyjmie ich tyle, ile tylko napiszemy. Naley uwaa, by nie pomyli si w formatowaniu: int i = 5; printf("%i %s %i", 5, 4, "napis"); /* powinno by: "%i %i %s" */ Przy wczeniu ostrzee (opcja -Wall lub -Wformat w GCC) kompilator powinien nas ostrzec, gdy format nie odpowiada podanym elementom. Najczstsze uycie printf(): printf(%i, i); gdy i jest typu int; zamiast %i mona uy %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 jzyka i acuch formatujcy moe by podany jako zmienna. W zwizku z tym moliwa jest np. taka konstrukcja: #include <stdio.h> int main(void) { char buf[100]; scanf("%99s", buf); /* funkcja wczytuje tekst do tablicy buf */ printf(buf); return 0; } Program wczytuje tekst, a nastpnie wypisuje go. Jednak poniewa znak procentu jest traktowany w specjalny sposb, tote jeeli na wejciu pojawi si cig znakw zawierajcy ten znak mog si sta rne dziwne rzeczy. Midzy innymi z tego powodu w takich sytuacjach lepiej uywa funkcji puts() lub fputs() opisanych niej lub wywoania: printf(%s, zmienna);. Wicej o funkcji printf()

Funkcja puts
Funkcja puts() przyjmuje jako swj argument cig znakw, ktry nastpnie bezmylnie wypisuje na ekran koczc go znakiem przejcia do nowej linii. W ten sposb, nasz pierwszy program mogli bymy napisa w ten sposb: #include <stdio.h> int main(void) { puts("Hello world!"); return 0; }

68

ROZDZIA 10. PODSTAWOWE PROCEDURY WEJCIA I WYJCIA

W swoim dziaaniu funkcja ta jest w zasadzie identyczna do wywoania: printf(%s\n, argument); jednak prawdopodobnie bdzie dziaa szybciej. Jedynym jej mankamentem moe by fakt, e zawsze na kocu podawany jest znak przejcia do nowej linii. Jeeli jest to efekt niepodany (nie zawsze tak jest) naley skorzysta z funkcji fputs() opisanej niej lub wywoania printf(%s, argument);. Wicej o funkcji puts()

Funkcja fputs
Opisujc funkcj fputs() wybiegamy ju troch w przyszo (a konkretnie do opisu operacji na plikach), ale warto o niej wspomnie ju teraz, gdy umoliwia ona wypisanie swojego argumentu bez wypisania na kocu znaku przejcia do nowej linii: #include <stdio.h> int main(void) { fputs("Hello world!\n", stdout); return 0; } W chwili obecnej moesz si nie przejmowa tym zagadkowym stdout wpisanym jako drugi argument funkcji. Jest to okrelenie strumienia wyjciowego (w naszym wypadku standardowe wyjcie standard output). Wicej o funkcji fputs()

Funkcja putchar
Funkcja putchar() suy do wypisywania pojedynczych znakw. Przykadowo jeeli chcielibymy napisa program wypisujcy w prostej tabelce wszystkie liczby od 0 do 99 moglibymy to zrobi tak: #include <stdio.h> int main(void) { int i = 0; for (; i<100; ++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; } Wicej o funkcji putchar()

FUNKCJE WEJCIA

69

Funkcje wejcia
Funkcja scanf()
Teraz pomylmy o sytuacji odwrotnej. Tym razem to uytkownik musi powiedzie co programowi. W poniszym przykadzie program podaje kwadrat liczby, podanej przez uytkownika: #include <stdio.h> int main () { int liczba = 0; printf ("Podaj liczb: "); scanf ("%d", &liczba); printf ("%d*%d=%d\n", liczba, liczba, liczba*liczba); return 0; } Zauwaye, e w tej funkcji przy zmiennej pojawi si nowy operator & (etka). Jest on wany, gdy bez niego funkcja scanf() nie skopiuje odczytanej wartoci liczby do odpowiedniej zmiennej! Waciwie oznacza przekazanie do funkcji adresu zmiennej, by funkcja moga zmieni jej warto. Nie musisz teraz rozumie, jak to si odbywa, wszystko zostanie wyjanione w rozdziale Wskaniki. Oznaczenia s podobne takie jak przy printf(), czyli scanf(%i, &liczba); wczytuje liczb typu int, scanf(%f, &liczba); liczb typu float, a scanf(%s, tablica znakw); cig znakw. Ale czemu w tym ostatnim przypadku nie ma etki? Ot, gdy podajemy jako argument do funkcji wyraenie typu tablicowego zamieniane jest ono automatycznie na adres pierwszego elementu tablicy. Bdzie to dokadniej opisane w rozdziale powiconym wskanikom. Brak etki jest czstym bdem szczeglnie wrd pocztkujcych programistw. Poniewa funkcja scanf() akceptuje zmienn liczb argumentw to nawet kompilator moe mie kopoty z wychwyceniem takich bdw (konkretnie chodzi o to, e standard nie wymaga od kompilatora wykrywania takich pomyek), cho kompilator GCC radzi sobie z tym jeeli podamy mu argument -Wformat. Naley jednak uwaa na to ostatnie uycie. Rozwamy na przykad poniszy kod: #include <stdio.h> int main(void) { char tablica[100]; scanf("%s", tablica); return 0; }

/* 1 */ /* 2 */

Robi on niewiele. W linijce 1 deklarujemy tablic 100 znakw czyli mogc przechowa napis dugoci 99 znakw. Nie przejmuj si jeeli nie do koca to wszystko

70

ROZDZIA 10. PODSTAWOWE PROCEDURY WEJCIA I WYJCIA

rozumiesz pojcia takie jak tablica czy cig znakw stan si dla Ciebie jasne w miar czytania kolejnych rozdziaw. W linijce 2 wywoujemy funkcj scanf(), ktra odczytuje tekst ze standardowego wejcia. Nie zna ona jednak rozmiaru tablicy i nie wie ile znakw moe ona przechowa przez co bdzie czyta tyle znakw, a napotka biay znak (format %s nakazuje czytanie pojedynczego sowa), co moe doprowadzi do przepenienia bufora. Niebezpieczne skutki czego takiego opisane s w rozdziale powiconym napisom. Na chwil obecn musisz zapamita, eby zaraz po znaku procentu podawa maksymaln liczb znakw, ktre moe przechowa bufor, czyli liczb o jeden mniejsz, ni rozmiar tablicy. Bezpieczna wersj powyszego kodu jest: #include <stdio.h> int main(void) { char tablica[100]; scanf("%99s", tablica); return 0; } Funkcja scanf() zwraca liczb poprawnie wczytanych zmiennych lub EOF jeeli nie ma ju danych w strumieniu lub nastpi bd. Zamy dla przykadu, e chcemy stworzy program, ktry odczytuje po kolei liczby i wypisuje ich 3 potgi. W pewnym momencie dane si kocz lub jest wprowadzana niepoprawna dana i wwczas nasz program powinien zakoczy dziaanie. Aby to zrobi, naley sprawdza warto zwracan przez funkcj scanf() w warunku ptli: #include <stdio.h> int main(void) { int n; while (scanf("%d", &n)==1) { printf("%d\n", n*n*n); } return 0; } Podobnie moemy napisa program, ktry wczytuje po dwie liczby i je sumuje: #include <stdio.h> int main(void) { int a, b; while (scanf("%d %d", &a, &b)==2) { printf("%d\n", a+b); } return 0; } Rozpatrzmy teraz troch bardziej skomplikowany przykad. Ot, ponownie jak poprzednio nasz program bdzie wypisywa 3 potg podanej liczby, ale tym razem

FUNKCJE WEJCIA

71

musi ignorowa bdne dane (tzn. pomija cigi znakw, ktre nie s liczbami) i koczy dziaanie tylko w momencie, gdy nastpi bd odczytu lub koniec pliku3 . #include <stdio.h> int main(void) { int result, n; do { result = scanf("%d", &n); if (result==1) { printf("%d\n", n*n*n); } else if (!result) { /* !result to to samo co result==0 */ result = scanf("%*s"); } } while (result!=EOF); return 0; } Zastanwmy si przez chwil co si dzieje w programie. Najpierw wywoywana jest funkcja scanf() i nastpuje prba odczytu liczby typu int. Jeeli funkcja zwrcia 1 to liczba zostaa poprawnie odczytana i nastpuje wypisanie jej trzeciej potgi. Jeeli funkcja zwrcia 0 to na wejciu byy jakie dane, ktre nie wyglday jak liczba. W tej sytuacji wywoujemy funkcj scanf() z formatem odczytujcym dowolny cig znakw nie bdcy biaymi znakami z jednoczesnym okreleniem, eby nie zapisywaa nigdzie wyniku. W ten sposb niepoprawnie wpisana dana jest omijana. Ptla gwna wykonuje si tak dugo jak dugo funkcja scanf() nie zwrci wartoci EOF. Wicej o funkcji scanf()

Funkcja gets
Funkcja gets suy do wczytania pojedynczej linii. Moe Ci si to wyda dziwne, ale: funkcji tej nie naley uywa pod adnym pozorem. Przyjmuje ona jeden argument adres pierwszego elementu tablicy, do ktrego naley zapisa odczytan lini i nic poza tym. Z tego powodu nie ma adnej moliwoci przekazania do tej funkcji rozmiaru bufora podanego jako argument. Podobnie jak w przypadku scanf() moe to doprowadzi do przepenienia bufora, co moe mie tragiczne skutki. Zamiast tej funkcji naley uywa funkcji fgets(). Wicej o funkcji gets()

Funkcja fgets
Funkcja fgets() jest bezpieczn wersj funkcji gets(), ktra dodatkowo moe operowa na dowolnych strumieniach wejciowych. Jej uycie jest nastpujce: fgets(tablica_znakw, rozmiar_tablicy_znakw, stdin); Na chwil obecn nie musisz si przejmowa ostatnim argumentem (jest to okrelenie strumienia, w naszym przypadku standardowe wejcie standard input). Funkcja
3 Jak

rozrnia te dwa zdarzenia dowiesz si w rozdziale Czytanie i pisanie do plikw.

72

ROZDZIA 10. PODSTAWOWE PROCEDURY WEJCIA I WYJCIA

czyta tekst a do napotkania znaku przejcia do nowej linii, ktry take zapisuje w wynikowej tablicy (funkcja gets() tego nie robi). Jeeli brakuje miejsca w tablicy to funkcja przerywa czytanie, w ten sposb, aby sprawdzi czy zostaa wczytana caa linia czy tylko jej cz naley sprawdzi czy ostatnim znakiem nie jest znak przejcia do nowej linii. Jeeli nastpi jaki bd lub na wejciu nie ma ju danych funkcja zwraca warto NULL. #include <stdio.h> int main(void) { char buffer[128], whole_line = 1, *ch; while (fgets(buffer, sizeof buffer, stdin)) { if (whole_line) { putchar(>); if (buffer[0]!=>) { putchar( ); } } fputs(buffer, stdout); for (ch = buffer; *ch && *ch!=\n; ++ch); whole_line = *ch == \n; } if (!whole_line) { putchar(\n); } return 0; }

/* 1 */ /* 2 */

/* 3 */ /* 4 */

Powyszy kod wczytuje dane ze standardowego wejcia linia po linii i dodaje na pocztku kadej linii znak wikszoci, po ktrym dodaje spacj jeeli pierwszym znakiem na linii nie jest znak wikszoci. W linijce 1 nastpuje odczytywanie linii. Jeeli nie ma ju wicej danych lub nastpi bd wejcia funkcja zwraca warto NULL, ktra ma logiczn warto 0 i wwczas ptla koczy dziaanie. W przeciwnym wypadku funkcja zwraca po prostu pierwszy argument, ktry ma warto logiczn 1. W linijce 2 sprawdzamy, czy poprzednie wywoanie funkcji wczytao ca lini, czy tylko jej cz jeeli ca to teraz jestemy na pocztku linii i naley doda znak wikszoci. W linii 3 najzwyczajniej w wiecie wypisujemy lini. W linii 4 przeszukujemy tablic znak po znaku, a do momentu, gdy znajdziemy znak o kodzie 0 koczcym cig znakw albo znak przejcia do nowej linii. Ten drugi przypadek oznacza, e funkcja fgets() wczytaa ca lini. Wicej o funkcji fgets()

Funkcja getchar()
Jest to bardzo prosta funkcja, wczytujca 1 znak z klawiatury. W wielu przypadkach dane mog by buforowane przez co wysyane s do programu dopiero, gdy bufor zostaje przepeniony lub na wejciu jest znak przejcia do nowej linii. Z tego powodu po wpisaniu danego znaku naley nacisn klawisz enter, aczkolwiek trzeba pamita, e w nastpnym wywoaniu zostanie zwrcony znak przejcia do nowej linii. Gdy nastpi bd lub nie ma ju wicej danych funkcja zwraca warto EOF (ktra ma

FUNKCJE WEJCIA

73

jednak warto logiczn 1 tote zwyka ptla while (getchar()) nie da oczekiwanego rezultatu): #include <stdio.h> int main(void) { int c; while ((c = getchar())!=EOF) { if (c== ) { c = _; } putchar(c); } return 0; } Ten prosty program wczytuje dane znak po znaku i zamienia wszystkie spacje na znaki podkrelenia. Moe wyda si dziwne, e zmienn c zdeniowalimy jako trzymajc typ int, a nie char. Wanie taki typ (tj. int) zwraca funkcja getchar() i jest to konieczne poniewa warto EOF wykracza poza zakres wartoci typu char (gdyby tak nie byo to nie byoby moliwoci rozrnienia wartoci EOF od poprawnie wczytanego znaku). Wicej o funkcji getchar()

74

ROZDZIA 10. PODSTAWOWE PROCEDURY WEJCIA I WYJCIA

Rozdzia 11

Funkcje
W matematyce pod pojciem funkcji rozumiemy twr, ktry pobiera pewn liczb argumentw i zwraca wynik. Jeli dla przykadu wemiemy funkcj sin(x) to x bdzie zmienn rzeczywist, ktra okrela kt, a w rezultacie otrzymamy inn liczb rzeczywist sinus tego kta. W C funkcja (czasami nazywana podprogramem, rzadziej procedur) to wydzielona cz programu, ktra przetwarza argumenty i ewentualnie zwraca warto, ktra nastpnie moe by wykorzystana jako argument w innych dziaaniach lub funkcjach. Funkcja moe posiada wasne zmienne lokalne. W odrnieniu od funkcji matematycznych, funkcje w C mog zwraca dla tych samych argumentw rne wartoci. Po lekturze poprzednich czci podrcznika zapewne mgby poda kilka przykadw funkcji, z ktrych korzystae. Byy to np. funkcja printf(), drukujca tekst na ekranie, czy funkcja main(), czyli gwna funkcja programu. Gwn motywacj tworzenia funkcji jest unikanie powtarzania kilka razy tego samego kodu. W poniszym fragmencie: for(i=1; i <= 5; ++i) { printf("%d ", i*i); } for(i=1; i <= 5; ++i) { printf("%d ", i*i*i); } for(i=1; i <= 5; ++i) { printf("%d ", i*i); } widzimy, e pierwsza i trzecia ptla for s takie same. Zamiast kopiowa fragment kodu kilka razy (co jest mao wygodne i moe powodowa bdy) lepszym rozwizaniem mogoby by wydzielenie tego fragmentu tak, by mona bo byo wywoywa kilka razy. Tak wanie dziaaj funkcje. Innym, niemniej wanym powodem uywania funkcji jest rozbicie programu na fragmenty wg ich funkcjonalnoci. Oznacza to, e z jeden duy program dzieli si na mniejsze funkcje, ktre s wyspecjalizowane w wykonywaniu okrelonych czynnoci. Dziki temu atwiej jest zlokalizowa bd. Ponadto takie funkcje mona potem przenie do innych programw. 75

76

ROZDZIA 11. FUNKCJE

Tworzenie funkcji
Dobrze jest uczy si na przykadach. Rozwamy nastpujcy kod: int iloczyn (int x, int y) { int iloczyn_xy; iloczyn_xy = x*y; return iloczyn_xy; } int iloczyn (int x, int y) to nagwek funkcji, ktry opisuje, jakie argumenty przyjmuje funkcja i jak warto zwraca (funkcja moe przyjmowa wiele argumentw, lecz moe zwraca tylko jedn warto)1 . Na pocztku podajemy typ zwracanej wartoci u nas int. Nastpnie mamy nazw funkcji i w nawiasach list argumentw. Ciao funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasach klamrowych. Pierwsz instrukcj jest deklaracja zmiennej jest to zmienna lokalna, czyli niewidoczna poza funkcj. Dalej przeprowadzamy odpowiednie dziaania i zwracamy rezultat za pomoc instrukcji return.

Oglnie
Funkcj w jzyku C tworzy si nastpujco: typ identyfikator (typ1 argument1, typ2 argument2, typn argumentn) { /* instrukcje */ } Oczywicie istnieje moliwo utworzenia funkcji, ktra nie posiada adnych argumentw. Deniuje si j tak samo, jak funkcj z argumentami z t tylko rnic, e midzy okrgymi nawiasami nie znajduje si aden argument lub pojedyncze swko void w denicji funkcji nie ma to znaczenia, jednak w deklaracji puste nawiasy oznaczaj, e prototyp nie informuje jakie argumenty przyjmuje funkcja, dlatego bezpieczniej jest stosowa swko void. Funkcje deniuje si poza gwn funkcj programu (main). W jzyku C nie mona tworzy zagniedonych funkcji (funkcji wewntrz innych funkcji).

Procedury
Przyjo si, e procedura od funkcji rni si tym, e ta pierwsza nie zwraca adnej wartoci. Zatem, aby stworzy procedur naley napisa: void identyfikator (argument1, argument2, argumentn) { /* instrukcje */ }
1 Bardziej precyzyjnie mona powiedzie, e funkcja moe zwrci tylko jeden adres do jakiego obiektu w pamici.

WYWOYWANIE

77

void (z ang. pusty, prny) jest sowem kluczowym majcym kilka znacze, w tym przypadku oznacza brak wartoci. Generalnie, w terminologii C pojcie procedura nie jest uywane, mwi si raczej funkcja zwracajca void. Jeli nie podamy typu danych zwracanych przez funkcj kompilator domylnie przyjmie typ int, cho ju w standardzie C99 nieokrelenie wartoci zwracanej jest bdem.

Stary sposb deniowania funkcji


Zanim powsta standard ANSI C, w licie parametrw nie podawao si typw argumentw, a jedynie ich nazwy. Rwnie z tamtych czasw wywodzi si oznaczenie, i puste nawiasy (w prototypie funkcji, nie w denicji) oznaczaj, e funkcja przyjmuje nieokrelon liczb argumentw. Tego archaicznego sposobu deniowania funkcji nie naley ju stosowa, ale poniewa w swojej przygodzie z jzykiem C Czytelnik moe si na ni natkn, a co wicej standard nadal (z powodu zgodnoci z wczeniejszymi wersjami) dopuszcza tak deklaracj to naley tutaj o niej wspomnie. Ot wyglda ona nastpujco: typ_zwracany nazwa_funkcji(argument1, argument2, argumentn) typ1 argumenty /*, ... */; typ2 argumenty /*, ... */; /* ... */ { /* instrukcje */ } Na przykad wczeniejsza funkcja iloczyn wygldaaby nastpujco: int iloczyn(x, y) int x, y; { int iloczyn_xy; iloczyn_xy = x*y; return iloczyn_xy; } Najpowaniejsz wad takiego sposobu jest fakt, e w prototypie funkcji nie ma podanych typw argumentw, przez co kompilator nie jest w stanie sprawdzi poprawnoci wywoania funkcji. Naprawiono to (wprowadzajc denicje takie jak je znamy obecnie) najpierw w jzyku C++, a potem rozwizanie zapoyczono w standardzie ANSI C z 1989 roku.

Wywoywanie
Funkcje wywouje si nastpujco: identyfikator (argument1, argument2, argumentn);

78

ROZDZIA 11. FUNKCJE

Jeli chcemy, aby przypisa zmiennej warto, ktr zwraca funkcja, naley napisa tak: zmienna = funkcja (argument1, argument2, argumentn); Programici majcy dowiadczenia np. z jzykiem Pascal mog popenia bd polegajcy na wywoywaniu funkcji bez nawiasw okrgych, gdy nie przyjmuje ona adnych argumentw. Przykadowo, mamy funkcj: void pisz_komunikat() { printf("To jest komunikat\n"); } Jeli teraz j wywoamy: pisz_komunikat; /* LE */ pisz_komunikat(); /* dobrze */ to pierwsze polecenie nie spowoduje wywoania funkcji. Dlaczego? Aby kompilator C zrozumia, e chodzi nam o wywoanie funkcji, musimy po jej nazwie doda nawiasy okrge, nawet, gdy funkcja nie ma argumentw. Uycie samej nazwy funkcji ma zupenie inne znaczenie oznacza pobranie jej adresu. W jakim celu? O tym bdzie mowa w rozdziale Wskaniki. Przykad A oto dziaajcy przykad, ktry demonstruje wiadomoci podane powyej: #include <stdio.h> int suma (int a, int b) { return a+b; } int main () { int m = suma (4, 5); printf ("4+5=%d\n", m); return 0; }

Zwracanie wartoci
return to prawdopodobnie pierwsze sowo kluczowe jzyka C, z ktrym zetkne si dotychczas. Suy ono do przerwania funkcji i zwrcenia wartoci lub te przerwania funkcji bez zwracania wartoci dzieje si tak np. w procedurach. Uycie tej instrukcji jest bardzo proste i wyglda tak:

FUNKCJA MAIN() return zwracana_warto; lub dla procedur: return;

79

Funkcja main()
Do tej pory we wszystkich programach istniaa funkcja main(). Po co tak waciwie ona jest? Ot jest to funkcja, ktra zostaje wywoana przez fragment kodu inicjujcego prac programu. Kod ten tworzony jest przez kompilator i nie mamy na niego wpywu. Istotne jest, e kady program w jzyku C musi zawiera funkcj main(). Istniej dwa moliwe prototypy (nagwki) omawianej funkcji: int main(void); lub int main(int argc, char **argv);2 . Argument argc jest liczba nieujemn okrelajc, ile cigw znakw przechowywanych jest w tablicy argv. Wyraenie argv[argc] ma zawsze warto NULL. Pierwszym elementem tablicy argv (o ile istnieje3 ) jest nazwa programu czy komenda, ktr program zosta uruchomiony. Pozostae przechowuj argumenty podane przy uruchamianiu programu. Zazwyczaj jeli program uruchomimy poleceniem program argument1 argument2 to argc bdzie rwne 3 (2 argumenty + nazwa programu) a argv bdzie zawiera napisy program, argument1, argument2 umieszczone w tablicy indeksowanej od 0 do 2. Wemy dla przykadu program, ktry wypisuje to, co otrzymuje w argumentach argc i argv: #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { while (*argv) { puts(*argv++); } /* Ewentualnie mona uyc: int i; for (i = 0; i<argc; ++i) { puts(argv[i]); } */ return EXIT_SUCCESS; } Uruchomiony w systemie typu UNIX poleceniem ./test foo bar baz powinien wypisa: ./test foo bar baz
2 Czasami mona si spotka z prototypem int main(int argc, char **argv, char **env);, ktry jest deniowany w standardzie POSIX, ale wykracza ju poza standard C. 3 Inne standardy mog wymusza istnienie tego elementu, jednak jeli chodzi o standard jzyka C to nic nie stoi na przeszkodzie, aby argument argc mia warto zero.

80

ROZDZIA 11. FUNKCJE

Na razie nie musisz rozumie powyszych kodw i opisw, gdy odwouj si do poj takich jak tablica oraz wskanik, ktre opisane zostan w dalszej czci podrcznika. Co ciekawe, funkcja main nie rni si zanadto od innych funkcji i tak jak inne moe woa sama siebie (patrz rekurencja niej), przykadowo powyszy program mona zapisa tak4 : #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { if (*argv) { puts(*argv); return main(argc-1, argv+1); } else { return EXIT_SUCCESS; } } Ostatni rzecz dotyczc funkcji main jest zwracana przez ni warto. Ju przy omawianiu pierwszego programu wspomniane zostao, e jedynymi wartociami, ktre znacz zawsze to samo we wszystkich implementacjach jzyka s 0, EXIT SUCCESS i EXIT FAILURE5 zdeniowane w pliku nagwkowym stdlib.h. Warto 0 i EXIT SUCCESS oznaczaj poprawne zakoczenie programu (co wcale nie oznacza, e makro EXIT SUCCESS ma warto zero), natomiast EXIT FAILURE zakoczenie bdne. Wszystkie inne wartoci s zalene od implementacji.

Dalsze informacje
Poniej przekaemy ci par bardziej zaawansowanych informacji o funkcjach w C, jeli nie masz ochoty wgbia si w szczegy, moesz spokojnie pomin t cz i wrci tu pniej.

Jak zwrci kilka wartoci?


Jeli chcesz zwrci z funkcji kilka wartoci, musisz zrobi to w troch inny sposb. Generalnie moliwe s dwa podejcia: jedno to upakowanie zwracanych wartoci mona stworzy tak zwan struktur, ktra bdzie przechowywaa kilka zmiennych (jest to opisane w rozdziale Typy zoone). Prostszym sposobem jest zwracanie jednej z wartoci w normalny sposb a pozostaych jako parametrw. Za chwil dowiesz si, jak to zrobi; jeli chcesz zobaczy przykad, moesz przyjrze si funkcji scanf() z biblioteki standardowej.
kto lubi ekstrawagancki kod ciao funkcji main mona zapisa jako return *argv ? puts(*argv), main(argc-1, argv+1) : EXIT SUCCESS;, ale nie radzimy stosowa tak skomplikowanych i, bd co bd, mao czytelnych konstrukcji. 5 Uwaga! Makra EXIT SUCCESS i EXIT FAILURE te su tylko i wycznie jako wartoci do zwracania przez funkcj main(). Nigdzie indziej nie maj one zastosowania.
4 Jeeli

DALSZE INFORMACJE

81

Przekazywanie parametrw
Gdy wywoujemy funkcj, warto argumentw, z ktrymi j wywoujemy, jest kopiowana do funkcji. Kopiowana to znaczy, e nie moemy normalnie zmieni wartoci zewntrznych dla funkcji zmiennych. Formalnie mwi si, e w C argumenty s przekazywane przez warto, czyli wewntrz funkcji operujemy tylko na ich kopiach. Moliwe jest modykowanie zmiennych przekazywanych do funkcji jako parametry ale do tego w C potrzebne s wskaniki.

Funkcje rekurencyjne
Jzyk C ma moliwo tworzenia tzw. funkcji rekurencyjnych. Jest to funkcja, ktra w swojej wasnej denicji (ciele) wywouje sam siebie. Najbardziej klasycznym przykadem moe tu by silnia. Napiszmy sobie zatem nasz funkcj rekurencyjn, ktra oblicza silni: int silnia (int liczba) { int sil; if (liczba<0) return 0; /* wywoanie jest bezsensowne, */ /* zwracamy 0 jako kod bdu */ if (liczba==0 || liczba==1) return 1; sil = liczba*silnia(liczba-1); return sil; } Musimy by ostroni przy funkcjach rekurencyjnych, gdy atwo za ich pomoc utworzy funkcj, ktra bdzie sama siebie wywoywaa w nieskoczono, a co za tym idzie bdzie zawieszaa program. Tutaj pierwszymi instrukcjami if ustalamy warunki stopu, gdzie koczy si wywoywanie funkcji przez sam siebie, a nastpnie okrelamy, jak funkcja bdzie wywoywa sam siebie (odjcie jedynki od argumentu, co do ktrego wiemy, e jest dodatni, gwarantuje, e dojdziemy do warunku stopu w skoczonej liczbie krokw). Warto te zauway, e funkcje rekurencyjne czasami mog by znacznie wolniejsze ni podejcie nierekurencyjne (iteracyjne, przy uyciu ptli). Flagowym przykadem moe tu by funkcja obliczajca wyrazy cigu Fibonacciego: #include <stdio.h> unsigned count; unsigned fib_rec(unsigned n) { ++count; return n<2 ? n : (fib_rec(n-2) + fib_rec(n-1)); } unsigned fib_it (unsigned n) { unsigned a = 0, b = 0, c = 1; ++count; if (!n) return 0;

82 while (--n) { ++count; a = b; b = c; c = a + b; } return c; }

ROZDZIA 11. FUNKCJE

int main(void) { unsigned n, result; printf("Ktory element ciagu Fibonacciego obliczyc? "); while (scanf("%d", &n)==1) { count = 0; result = fib_rec(n); printf("fib_ret(%3u) = %6u (wywolan: %5u)\n", n, result, count); count = 0; result = fib_it (n); printf("fib_it (%3u) = %6u } return 0; } W tym przypadku funkcja rekurencyjna, cho atwiejsza w napisaniu, jest bardzo nieefektywna.

(wywolan: %5u)\n", n, result, count);

Deklarowanie funkcji
Czasami moemy chcie przed napisaniem funkcji poinformowa kompilator, e dana funkcja istnieje. Niekiedy kompilator moe zaprotestowa, jeli uyjemy funkcji przed okreleniem, jaka to funkcja, na przykad: int a() { return b(0); } int b(int p) { if( p == 0 ) return 1; else return a(); } int main() { return b(1); }

DALSZE INFORMACJE

83

W tym przypadku nie jestemy w stanie zamieni a i b miejscami, bo obie funkcje korzystaj z siebie nawzajem. Rozwizaniem jest wczeniejsze zadeklarowanie funkcji. Deklaracja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji (przed otwierajcym nawiasem klamrowym) z dodatkowo dodanym rednikiem na kocu. W naszym przykadzie wystarczy na samym pocztku wstawi: int b(int p); W deklaracji mona pomin nazwy parametrw funkcji: int b(int); Bardzo czstym zwyczajem jest wypisanie przed funkcj main samych prototypw funkcji, by ich denicje umieci po denicji funkcji main, np.: int a(void); int b(int p); int main() { return b(1); } int a() { return b(0); } int b(int p) { if( p == 0 ) return 1; else return a(); } Z poprzednich rozdziaw pamitasz, e na pocztku programu doczalimy tzw. pliki nagwkowe. Zawieraj one wanie prototypy funkcji i uatwiaj pisanie duych programw. Dalsze informacje o plikach nagwkowych zawarte s w rozdziale Tworzenie bibliotek.

Zmienna liczba parametrw


Zauwaye zapewne, e uywajc funkcji printf() lub scanf() po argumencie zawierajcym tekst z odpowiednimi modykatorami moge poda praktycznie nieograniczon liczb argumentw. Zapewne deklaracja obu funkcji zadziwi Ci jeszcze bardziej: int printf(const char *format, ...); int scanf(const char *format, ...); Jak widzisz w deklaracji zostay uyte 3 kropki. Ot jzyk C ma moliwo przekazywania nieograniczonej liczby argumentw do funkcji (tzn. jedynym ograniczeniem

84

ROZDZIA 11. FUNKCJE

jest rozmiar stosu programu). Caa zabawa polega na tym, aby umie dosta si do odpowiedniego argumentu oraz pozna jego typ (uywajc funkcji printf, moglimy wpisa jako argument dowolny typ danych). Do tego celu moemy uy wszystkich ciekawostek, zawartych w pliku nagwkowym stdarg.h. Zamy, e chcemy napisa prost funkcj, ktra dajmy na to, mnoy wszystkie swoje argumenty (zakadamy, e argumenty s typu int). Przyjmujemy przy tym, e ostatni argument bdzie 0. Bdzie ona wygldaa tak: #include <stdarg.h> int mnoz (int pierwszy, ...) { va_list arg; int iloczyn = 1, t; va_start (arg, pierwszy); for (t = pierwszy; t; t = va_arg(arg, int)) { iloczyn *= t; } va_end (arg); return iloczyn; } va list oznacza specjalny typ danych, w ktrym przechowywane bd argumenty, przekazane do funkcji. Makropolecenie va arg odczytuje kolejne argumenty i przeksztaca je do odpowiedniego typu danych. Na zakoczenie uywane jest makro va end jest ono obowizkowe! Oczywicie, tak samo jak w przypadku funkcji printf() czy scanf(), argumenty nie musz by takich samych typw. Rozwamy dla przykadu funkcj, podobn do printf(), ale znacznie uproszczon: #include <stdarg.h> void wypisz(const char *format, ...) { va_list arg; va_start (arg, format); for (; *format; ++format) { switch (*format) { case i: printf("%d" , va_arg(arg, case I: printf("%u" , va_arg(arg, case l: printf("%ld", va_arg(arg, case L: printf("%lu", va_arg(arg, case f: printf("%f" , va_arg(arg, case x: printf("%x" , va_arg(arg, case X: printf("%X" , va_arg(arg, case s: printf("%s" , va_arg(arg, default : putc(*format); } } va_end (arg); }

int)); break; unsigned)); break; int)); break; unsigned long)); break; double)); break; unsigned)); break; unsigned)); break; const char *)); break;

ZOBACZ TE

85

Przyjmuje ona jako argument cig znakw, w ktrych niektre instruuj funkcj, by pobraa argument i go wypisaa. Nie przejmuj si jeeli nie rozumiesz wyrae *format i ++format. Istotne jest to, e ptla sprawdza po kolei wszystkie znaki formatu.

Ezoteryka C
C ma wiele niuansw, o ktrych wielu programistw nie wie lub atwo o nich zapomina: jeli nie podamy typu wartoci zwracanej w funkcji, zostanie przyjty typ int (wedug najnowszego standardu C99 nie podanie typu wartoci jest zwracane jako bd); jeli nie podamy adnych parametrw funkcji, to funkcja bdzie uywaa zmiennej iloci parametrw (inaczej ni w C++, gdzie przyjte zostanie, e funkcja nie przyjmuje argumentw). Aby wymusi pust list argumentw, naley napisa int funkcja(void) (dotyczy to jedynie prototypw czy deklaracji funkcji); jeli nie uyjemy w funkcji instrukcji return, warto zwracana bdzie przypadkowa (dostaniemy mieci z pamici). Kompilator C++ uyty do kompilacji kodu C najczciej zaprotestuje i ostrzee nas, jeli uyjemy powyszych konstrukcji. Natomiast czysty kompilator C z domylnymi ustawieniami nie napisze nic i bez mrugnicia okiem skompiluje taki kod.

Zobacz te
C++/Funkcje inline funkcje rozwijane w miejscu wywoywania (dostpne te w standardzie C99). C++/Przecianie funkcji

86

ROZDZIA 11. FUNKCJE

Rozdzia 12

Preprocesor
Wstp
W jzyku C wszystkie linijki, zaczynajce si od symbolu # nie podlegaj bezporednio procesowi kompilacji. S to natomiast instrukcje preprocesora elementu kompilatora, ktry analizuje plik rdowy w poszukiwaniu wszystkich wyrae, zaczynajcych si od #. Na podstawie tych instrukcji generuje on kod w czystym jzyku C, ktry nastpnie jest kompilowany przez kompilator. Poniewa za pomoc preprocesora mona niemal sterowa kompilatorem daje on niezwyke moliwoci, ktre nie byy dotd znane w innych jzykach programowania. Aby przekona si, jak wyglda kod przetworzony przez preprocesor, uyj (w kompilatorze gcc) przecznika -E: gcc test.c -E -o test.txt W pliku test.txt zostanie umieszczony cay kod w postaci, ktra zdatna jest do przetworzenia przez kompilator.

Dyrektywy preprocesora
Dyrektywy preprocesora s to wyraenia, ktre wystpuj zaraz za symbolem # i to wanie za ich pomoc moemy uywa preprocesora. Dyrektywa zaczyna si od znaku # i koczy si wraz z kocem linii. Aby przenie dalsz cz dyrektywy do nastpnej linii, naley zakoczy lini znakiem \: #define add(a,b) \ a+b Omwimy teraz kilka waniejszych dyrektyw.

#include
Najpopularniejsza dyrektywa, wstawiajca w swoje miejsce tre pliku podanego w nawiasach ostrych lub cudzysowie. Skadnia: 87

88 Przykad 1 #include <plik_naglowkowy_do_dolaczenia>

ROZDZIA 12. PREPROCESOR

Za pomoc #include moemy doczy dowolny plik niekoniecznie plik nagwkowy. Przykad 2 #include "plik_naglowkowy_do_dolaczenia" Jeeli nazwa pliku nagwkowego bdzie ujta w nawiasy ostre (przykad 1), to kompilator poszuka go wrd wasnych plikw nagwkowych (ktre najczciej si znajduj w podkatalogu includes w katalogu kompilatora). Jeli jednak nazwa ta bdzie ujta w podwjne cudzysowy(przykad 2), to kompilator poszuka jej w katalogu, w ktrym znajduje si kompilowany plik (mona zmieni to zachowanie w opcjach niektrych kompilatorw). Przy uyciu tej dyrektywy mona take wskaza dokadne pooenie plikw nagwkowych poprzez wpisanie bezwzgldnej lub wzgldnej cieki dostpu do tego pliku nagwkowego. Przykad 3 cieka bezwzgldna do pliku nagwkowego w Linuksie i w Windowsie Opis: W miejsce jednej i drugiej linijki zostanie wczytany plik umieszczony w danej lokalizacji #include "/usr/include/plik_nagwkowy.h" #include "C:\\borland\includes\plik_nagwkowy.h" Przykad 4 cieka wzgldna do pliku nagwkowego Opis: W miejsce linijki zostanie wczytany plik umieszczony w katalogu katalog1, a ten katalog jest w katalogu z plikiem rdowym. Inaczej mwic, jeli plik rdowy jest w katalogu /home/user/dokumenty/zrodla, to plik nagwkowy jest umieszczony w katalogu /home/user/dokumenty/zrodla/katalog1 #include "katalog1/plik_naglowkowy.h" Przykad 5 cieka wzgldna do pliku nagwkowego Opis: Jeli plik rdowy jest umieszczony w katalogu /home/user/dokumenty/zrodla, to plik nagwkowy znajduje si w katalogu /home/user/dokumenty/katalog1/katalog2/ #include "../katalog1/katalog2/plik_naglowkowy.h" Wicej informacji moesz uzyska w rozdziale Biblioteki.

#dene
Linia pozwalajca zdeniowa sta, funkcj lub sowo kluczowe, ktre bdzie potem podmienione w kodzie programu na odpowiedni warto lub moe zosta uyte w instrukcjach warunkowych dla preprocesora. Skadnia: #define NAZWA_STALEJ WARTOSC

DYREKTYWY PREPROCESORA lub #define NAZWA_STALEJ Przykad

89

#dene LICZBA 8 spowoduje ,e kade wystpienie sowa LICZBA w kodzie zostanie zastpione semk. #dene SUMA(a,b) (a+b) spowoduje, ze kade wystpienie wywoania funkcji SUMA zostanie zastpione przez sum argumentw

#undef
Ta instrukcja odwouje denicj wykonan instrukcj #dene. #undef STALA

instrukcje warunkowe
Preprocesor zawiera rwnie instrukcje warunkowe, pozwalajce na wybr tego co ma zosta skompilowane w zalenoci od tego, czy staa jest zdeniowana lub jak ma warto: #if #elif #else #endif Te instrukcje uzaleniaj kompilacje od warunkw. Ich dziaanie jest podobne do instrukcji warunkowych w samym jzyku C. I tak: #if wprowadza warunek, ktry jeli nie jest prawdziwy powoduje pominicie kompilowania kodu, a do napotkania jednej z poniszych instrukcji. #else spowoduje skompilowanie kodu jeeli warunek za #if jest nieprawdziwy, a do napotkania ktrego z poniszych instrukcji. #elif wprowadza nowy warunek, ktry bdzie sprawdzony jeeli poprzedni by nieprawdziwy. Stanowi poczenie instrukcji #if i #else. #endif zamyka blok ostatniej instrukcji warunkowej. Przykad: #if INSTRUKCJE == 2 printf ("Podaj liczb z przedziau 10 do 0\n"); /*1*/ #elif INSTRUKCJE == 1 printf ("Podaj liczb: "); /*2*/ #else printf ("Podaj parametr: "); /*3*/ #endif scanf ("%d\n", &liczba);/*4*/ wiersz nr 1 zostanie skompilowany jeeli staa INSTRUKCJE bdzie rwna 2 wiersz nr 2 zostanie skompilowany, gdy INSTRUKCJE bdzie rwna 1

90

ROZDZIA 12. PREPROCESOR wiersz nr 3 zostanie skompilowany w pozostaych wypadkach wiersz nr 4 bdzie kompilowany zawsze

#ifdef #ifndef #else #endif Te instrukcje warunkuj kompilacj od tego, czy odpowiednia staa zostaa zdeniowana. #ifdef spowoduje, e kompilator skompiluje poniszy kod tylko gdy zostaa zdeniowana odpowiednia staa. #ifndef ma odwrotne dziaanie do #ifdef, a mianowicie brak denicji odpowiedniej staej umoliwia kompilacje poniszego kodu. #else,#endif maj identyczne zastosowanie jak te z powyszej grupy Przykad: #define INFO /*definicja staej INFO*/ #ifdef INFO printf ("Twrc tego programu jest Jan Kowalski\n");/*1*/ #endif #ifndef INFO printf ("Twrc tego programu jest znany programista\n");/*2*/ #endif To czy dowiemy si kto jest twrc tego programu zaley czy instrukcja deniujca sta INFO bdzie istnie. W powyszym przypadku na ekranie powinno si wywietli Twrc tego programu jest Jan Kowalski

#error
Powoduje przerwanie kompilacji i wywietlenie tekstu, ktry znajduje si za t instrukcj. Przydatne gdy chcemy zabezpieczy si przed zdeniowaniem nieodpowiednich staych. Przykad: #if BLAD == 1 #error "Powany bd kompilacji" #endif Co jeeli zdeniujemy sta BLAD z wartoci 1? Spowoduje to wywietlenie w trakcie kompilacji komunikatu podobnego do poniszego: Fatal error program.c 6: Error directive: "Powany bd kompilacji" in function main() *** 1 errors in Compile *** wraz z przerwaniem kompilacji.

DYREKTYWY PREPROCESORA

91

#warning
Wywietla tekst, zawarty w cudzysowach, jako ostrzeenie. Jest czsto uywany do sygnalizacji programicie, e dana cz programu jest przestarzaa lub moe sprawia problemy. Przykad: #warning "To jest bardzo prosty program" Spowoduje to takie oto zachowanie kompilatora: test.c:3:2: warning: \#warning To jest bardzo prosty program Uycie dyrektywy #warning nie przerywa procesu kompilacji i suy tylko do wywietlania komunikatw dla programisty w czasie kompilacji programu.

#line
Powoduje wyzerowanie licznika linii kompilatora, ktry jest uywany przy wywietlaniu opisu bdw kompilacji. Pozwala to na szybkie znalezienie moliwej przyczyny bdu w rozbudowanym programie. Przykad: printf ("Podaj warto funkcji"); #line printf ("W przedziale od 10 do 0\n); /* tutaj jest bd - brak cudzysowu zamykajcego */ Jeeli teraz nastpi prba skompilowania tego kodu to kompilator poinformuje, e wystpi bd skadni w lini 1, a nie np. 258.

Makra
Preprocesor jzyka C umoliwia te tworzenie makr, czyli automatycznie wykonywanych czynnoci. Makra deklaruje si za pomoc dyrektywy #dene: #define MAKRO(arg1, arg2, ...) (wyraenie) W momencie wystpienia MAKRA w tekcie, preprocesor automatycznie zamieni makro na wyraenie. Makra mog by pewnego rodzaju alternatywami dla funkcji, ale powinno si ich uywa tylko w specjalnych przypadkach. Poniewa makro sprowadza si do prostego zastpienia przez preprocesor wywoania makra przez jego tekst, jest bardzo podatne na trudne do zlokalizowania bdy (kompilator bdzie podawa bdy w miejscach, w ktrych nic nie widzimy bo preprocesor wstawi tam tekst). Makra s szybsze (nie nastpuje wywoanie funkcji, ktre zawsze zajmuje troch czasu1 ), ale te mniej bezpieczne i elastyczne ni funkcje. Przeanalizujmy teraz fragment kodu:
1 Tak naprawd wg standardu C99 istnieje moliwo napisania funkcji, ktrej kod take bdzie wstawiany w miejscu wywoania. Odbywa si to dziki inline.

92 #include <stdio.h> #define KWADRAT(x) ((x)*(x))

ROZDZIA 12. PREPROCESOR

int main () { printf ("2 do kwadratu wynosi %d\n", KWADRAT(2)); return 0; } Preprocesor w miejsce wyraenia KWADRAT(2) wstawi ((2)*(2)). Zastanwmy si, co staoby si, gdybymy napisali KWADRAT("2"). Preprocesor po prostu wstawi napis do kodu, co da wyraenie (("2")*("2")), ktre jest nieprawidowe. Kompilator zgosi bd, ale programista widzi tylko w kodzie uycie makra a nie prawdziw przyczyn bdu. Wida tu, e bezpieczniejsze jest uycie funkcji, ktre daj moliwo wyspecykowania typw argumentw. Nawet jeeli program si skompiluje to makro moe dawa nieoczekiwany wynik. Jest tak w przypadku poniszego kodu: int x = 1; int y = KWADRAT(++x); Dzieje si tak dlatego, e makra rozwijane s przez preprocesor i kompilator widzi kod: int x = 1; int y = ((++x)*(++x)); Rwnie ponisze makra s bdne pomimo, e opisany problem w nich nie wystpuje: #define SUMA(a, b) a + b #define ILOCZYN(a, b) a * b Daj one nieoczekiwane wyniki dla wywoa: SUMA(2, 2) * 2; /* 6 zamiast 8 */ ILOCZYN(2 + 2, 2 + 2); /* 8 zamiast 16 */ Z tego powodu istotne jest uycie nawiasw: #define SUMA(a, b) ((a) + (b)) #define ILOCZYN(a, b) ((a) * (b))

# oraz ##
Do ciekawe moliwoci ma w makrach znak #. Zamienia on na napis stojcy za nim identykator. #include <stdio.h> #define wypisz(x) printf("%s=%i\n", #x, x); int main()

PREDEFINIOWANE MAKRA { int i=1; char a=5; wypisz(i); wypisz(a); return 0; } Program wypisze: i=1 a=5 Czyli wypisz(a) jest rozwijane w printf("%s=%i\n", "a", a);. Natomiast znaki ## cz dwie nazwy w jedn. Przykad: #include <stdio.h> #define abc(x) int zmienna ## x

93

int main() { abc(nasza); /* dziki temu zadeklarujemy zmienn o nazwie zmiennanasza */ zmiennanasza = 2; return 0; } Wicej o dobrych zwyczajach w tworzeniu makr mona si dowiedzie w rozdziale Powszechne praktyki.

Predeniowane makra
W jzyku wprowadzono rwnie serj predeniowanych makr, ktre maj uatwi ycie programicie. Oto one: DATE TIME data w momencie kompilacji godzina w momencie kompilacji

FILE acuch, ktry zawiera nazw pliku, ktry aktualnie jest kompilowany przez kompilator LINE deniuje numer linijki

STDC w kompilatorach zgodnych ze standardem ANSI lub nowszym makro to przyjmuje warto 1 STDC VERSION zalenie od poziomu zgodnoci kompilatora makro przyjmuje rne wartoci: jeeli kompilator jest zgodny z ANSI (rok 1989) makro nie jest zdeniowane, jeeli kompilator jest zgodny ze standardem z 1994 makro ma warto 199409L,

94

ROZDZIA 12. PREPROCESOR jeeli kompilator jest zgodny ze standardem z 1999 makro ma warto 199901L.

Warto rwnie wspomnie o identykatorze C99, ktrego warto to nazwa funkcji. Sprbujmy uy tych makr w praktyce: #include <stdio.h>

func

zdeniowanym w standardzie

#if __STDC_VERSION__ >= 199901L /* Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go. */ # define BUG(message) fprintf(stderr, "%s:%d: %s (w funkcji %s)\n", \ __FILE__, __LINE__, message, __func__) #else /* Jezeli __func__ nie ma no to trudno. */ # define BUG(message) fprintf(stderr, "%s:%d: %s\n", \ __FILE__, __LINE__, message) #endif int main(void) { printf("Program ABC,

data kompilacji: %s %s\n", __DATE__, __TIME__);

BUG("Przykladowy komunikat bledu"); return 0; } Efekt dziaania programu, gdy kompilowany jest kompilatorem C99: Program ABC, data kompilacji: Sep 1 2008 19:12:13 test.c:17: Przykladowy komunikat bledu (w funkcji main) Gdy kompilowany jest kompilatorem ANSI C: Program ABC, data kompilacji: Sep 1 2008 19:13:16 test.c:17: Przykladowy komunikat bledu

Rozdzia 13

Biblioteka standardowa
Czym jest biblioteka?
Bibliotek w jzyku C stanowi zbir skompilowanych wczeniej funkcji, ktry mona czy z programem. Biblioteki tworzy si, aby udostpni zbir pewnych wyspecjalizowanych funkcji do dyspozycji innych programw. Tworzenie bibliotek jest o tyle istotne, e takie podejcie znacznie uatwia tworzenie nowych programw. atwiej jest utworzy program w oparciu o istniejce biblioteki, ni pisa program wraz ze wszystkimi potrzebnymi funkcjami1 .

Po co nam biblioteka standardowa?


W ktrym z pocztkowych rozdziaw tego podrcznika napisane jest, e czysty jzyk C nie moe zbyt wiele. Tak naprawd, to jzyk C sam w sobie praktycznie nie ma mechanizmw do obsugi np. wejcia-wyjcia. Dlatego te wikszo systemw operacyjnych posiada tzw. bibliotek standardow zwan te bibliotek jzyka C. To wanie w niej zawarte s podstawowe funkcjonalnoci, dziki ktrym twj program moe np. napisa co na ekranie.

Jak skonstruowana jest biblioteka standardowa?


Zapytacie si zapewne jak biblioteka standardowa realizuje te funkcje, skoro sam jzyk C tego nie potra. Odpowied jest prosta biblioteka standardowa nie jest napisana w samym jzyku C. Poniewa C jest jzykiem tumaczonym do kodu maszynowego, to w praktyce nie ma adnych przeszkd, eby np. poczy go z jzykiem niskiego poziomu, jakim jest np. asembler. Dlatego biblioteka C z jednej strony udostpnia gotowe funkcje w jzyku C, a z drugiej za pomoc niskopoziomowych mechanizmw2 komunikuje si z systemem operacyjnym, ktry wykonuje odpowiednie czynnoci.
1 Pocztkujcy 2 Takich,

programista zapewne nie byby w stanie napisa nawet funkcji printf. jak np. wywoywanie przerwa programowych.

95

96

ROZDZIA 13. BIBLIOTEKA STANDARDOWA

Gdzie s funkcje z biblioteki standardowej?


Piszc program w jzyku C uywamy rnego rodzaju funkcji, takich jak np. printf. Nie jestemy jednak ich autorami, mao tego nie widzimy nawet deklaracji tych funkcji w naszym programie. Pamitacie program Hello world? Zaczyna on si od takiej oto linijki: #include <stdio.h> linijka ta oznacza: w tym miejscu wstaw zawarto pliku stdio.h. Nawiasy < i > oznaczaj, e plik stdio.h znajduje si w standardowym katalogu z plikami nagwkowymi. Wszystkie pliki z rozszerzeniem h s wanie plikami nagwkowymi. Wrmy teraz do tematu biblioteki standardowej. Kady system operacyjny ma za zadanie wykonywa pewne funkcje na rzecz programw. Wszystkie te funkcje zawarte s wanie w bibliotece standardowej. W systemach z rodziny UNIX nazywa si j LibC (biblioteka jzyka C). To tam wanie znajduje si funkcja printf, scanf, puts i inne. Oprcz podstawowych funkcji wejcia-wyjcia, biblioteka standardowa udostpnia te moliwo wykonywania funkcji matematycznych, komunikacji przez sie oraz wykonywania wielu innych rzeczy.

Jeli biblioteka nie jest potrzebna...


Czasami korzystanie z funkcji bibliotecznych oraz standardowych plikw nagwkowych jest niepodane np. wtedy, gdy programista pisze swj wasny system operacyjny oraz bibliotek do niego. Aby wyczy uywanie biblioteki C w opcjach kompilatora GCC moemy doda nastpujce argumenty: -nostdinc -fno-builtin

Opis funkcji biblioteki standardowej


Podrcznik C na Wikibooks zawiera opis duej czci biblioteki standardowej C: Indeks alfabetyczny Indeks tematyczny W systemach uniksowych moesz uzyska pomoc dziki narzdziu man, przykadowo piszc: man printf

Uwagi
Programy w jzyku C++ mog dokadnie w ten sam sposb korzysta z biblioteki standardowej, ale zalecane jest, by robi to raczej w troch odmienny sposb, waciwy dla C++. Szczegy w podrczniku C++.

Rozdzia 14

Czytanie i pisanie do plikw


Pojcie pliku
Na pocztku dobrze by byo, aby dowiedzia si, czym jest plik. Odpowiedni artyku dostpny jest w Wikipedii. Najprociej mwic, plik to pewne dane zapisane na dysku.

Identykacja pliku
Kady z nas, korzystajc na co dzie z komputera przyzwyczai si do tego, e plik ma okrelon nazw. Jednak w pisaniu programu posugiwanie si ca nazw nioso by ze sob co najmniej dwa problemy: pamicioerno przechowywanie caego (czasami nawet 255-bajtowego acucha) zajmuje niepotrzebnie pami ryzyko bdw (owe bdy szerzej omwione zostay w rozdziale Napisy) Aby uproci korzystanie z plikw programici wpadli na pomys, aby identykatorem pliku staa si liczba. Dziki temu kod programu sta si czytelniejszy oraz wyeliminowano konieczno cigego korzystania z acuchw. Jednak sam plik nadal jest identykowany po swojej nazwie. Aby przetworzy nazw pliku na odpowiedni liczb korzystamy z funkcji open lub fopen. Rnica wyjaniona jest poniej.

Podstawowa obsuga plikw


Istniej dwie metody obsugi czytania i pisania do plikw: wysoko- i niskopoziomowa. Nazwy funkcji z pierwszej grupy zaczynaj si od litery f (np. fopen(), fread(), fclose()), a identykatorem pliku jest wskanik na struktur typu FILE. Owa struktura to pewna grupa zmiennych, ktra przechowuje dane o danym pliku jak na przykad aktualn pozycj w nim. Szczegami nie musisz si przejmowa, funkcje biblioteki standardowej same zajmuj si wykorzystaniem struktury FILE, programista moe wic zapomnie, czym tak naprawd jest struktura FILE i traktowa tak zmienn jako uchwyt, identykator pliku. Druga grupa to funkcje typu read(), open(), write() i close(). Podstawowym identykatorem pliku jest liczba cakowita, ktra jednoznacznie 97

98

ROZDZIA 14. CZYTANIE I PISANIE DO PLIKW

identykuje dany plik w systemie operacyjnym. Liczba ta w systemach typu UNIX jest nazywana deskryptorem pliku. Naley pamita, e nie wolno nam uywa funkcji z obu tych grup jednoczenie w stosunku do jednego, otwartego pliku, tzn. nie mona najpierw otworzy pliku za pomoc fopen(), a nastpnie odczytywa danych z tego samego pliku za pomoc read(). Czym rni si oba podejcia do obsugi plikw? Ot metoda wysokopoziomowa ma swj wasny bufor, w ktrym znajduj si dane po odczytaniu z dysku a przed wysaniem ich do programu uytkownika. W przypadku funkcji niskopoziomowych dane kopiowane s bezporednio z pliku do pamici programu. W praktyce uywanie funkcji wysokopoziomowych jest prostsze a przy czytaniu danych maymi porcjami rwnie czsto szybsze i wanie ten model zostanie tutaj zaprezentowany.

Dane znakowe
Skupimy si teraz na najprostszym z moliwych zagadnie zapisie i odczycie pojedynczych znakw oraz caych acuchw. Napiszmy zatem nasz pierwszy program, ktry stworzy plik test.txt i umieci w nim tekst Hello world: #include <stdio.h> #include <stdlib.h> int main () { FILE *fp; /* uywamy metody wysokopoziomowej */ /* musimy mie zatem identyfikator pliku, uwaga na gwiazdk! */ char tekst[] = "Hello world"; if ((fp=fopen("test.txt", "w"))==NULL) { printf ("Nie mog otworzy pliku test.txt do zapisu!\n"); exit(1); } fprintf (fp, "%s", tekst); /* zapisz nasz acuch w pliku */ fclose (fp); /* zamknij plik */ return 0; } Teraz omwimy najwaniejsze elementy programu. Jak ju byo wspomniane wyej, do identykacji pliku uywa si wskanika na struktur FILE (czyli FILE *). Funkcja fopen zwraca w wskanik w przypadku poprawnego otwarcia pliku, bd te NULL, gdy plik nie moe zosta otwarty. Pierwszy argument funkcji to nazwa pliku, natomiast drugi to tryb dostpu w oznacza write (pisanie); zwrcony uchwyt do pliku bdzie mg by wykorzystany jedynie w funkcjach zapisujcych dane. I odwrotnie, gdy otworzymy plik podajc tryb r (read, czytanie), bdzie mona z niego jedynie czyta dane. Funkcja fopen zostaa dokadniej opisana w odpowiedniej czci rozdziau o bibliotece standardowej. Po zakoczeniu korzystania z pliku naley plik zamkn. Robi si to za pomoc funkcji fclose. Jeli zapomnimy o zamkniciu pliku, wszystkie dokonane w nim zmiany zostan utracone!

PODSTAWOWA OBSUGA PLIKW

99

Pliki a strumienie
Mona zauway, e do zapisu do pliku uywamy funkcji fprintf, ktra wyglda bardzo podobnie do printf jedyn rnic jest to, e w fprintf musimy jako pierwszy argument poda identykator pliku. Nie jest to przypadek obie funkcje tak naprawd robi tak samo. Uywana do wczytywania danych z klawiatury funkcja scanf te ma swj odpowiednik wrd funkcji operujcych na plikach jak nietrudno zgadn, nosi ona nazw fscanf. W rzeczywistoci jzyk C traktuje tak samo klawiatur i plik s to rda danych, podobnie jak ekran i plik, do ktrych mone dane kierowa. Jest to mylenie typowe dla systemw typu UNIX, jednak dla uytkownikw przyzwyczajonych do systemu Windows albo jzykw typu Pascal moe by to co najmniej dziwne. Nie da si ukry, e midzy klawiatur i plikiem na dysku zachodz podstawowe rnice i dostp do nich odbywa si inaczej jednak funkcje jzyka C pozwalaj nam o tym zapomnie i same zajmuj si szczegami technicznymi. Z punktu widzenia programisty, urzdzenia te sprowadzaj si do nadanego im identykatora. Uoglnione pliki nazywa si w C strumieniami. Kady program w momencie uruchomienia otrzymuje od razu trzy otwarte strumienie: stdin (wejcie) stdout (wyjcie) stderr (wyjcie bdw) (aby z nich korzysta naley doczy plik nagwkowy stdio.h) Pierwszy z tych plikw umoliwia odczytywanie danych wpisywanych przez uytkownika, natomiast pozostae dwa su do wyprowadzania informacji dla uytkownika oraz powiadamiania o bdach. Warto tutaj zauway, e konstrukcja: fprintf (stdout, "Hej, ja dziaam!") ; jest rwnowana konstrukcji printf ("Hej, ja dziaam!"); Podobnie jest z funkcj scanf(): fscanf (stdin, "%d", &zmienna); dziaa tak samo jak scanf("%d", &zmienna);

Obsuga bdw
Jeli nastpi bd, moemy si dowiedzie o jego przyczynie na podstawie zmiennej errno zadeklarowanej w pliku nagwkowym errno.h. Moliwe jest te wydrukowanie komunikatu o bedzie za pomoc funkcji perror. Na przykad uywajc:

100

ROZDZIA 14. CZYTANIE I PISANIE DO PLIKW

fp = fopen ("tego pliku nie ma", "r"); if( fp == NULL ) { perror("bd otwarcia pliku"); exit(-10); } dostaniemy komunikat: bd otwarcia pliku: No such file or directory

Zaawansowane operacje
Pora na kolejny, tym razem bardziej zoony przykad. Oto krtki program, ktry swoje wejcie zapisuje do pliku o nazwie podanej w linii polece: #include <stdio.h> #include <stdlib.h> /* program udajcy bardzo prymitywn wersj programu tee(1) */ int main (int argc, char *argv[]) { FILE *fp; int c; if (argc < 2) { fprintf (stderr, "Uzycie: %s nazwa_pliku\n", argv[0]); exit (-1); } fp = fopen (argv[1], "w"); if (!fp) { fprintf (stderr, "Nie moge otworzyc pliku %s\n", argv[1]); exit (-1); } printf("Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczyc\n"); while ( (c = fgetc(stdin)) != EOF) { fputc (c, stdout); fputc (c, fp); } fclose(fp); return 0; } Tym razem skorzystalimy ju z duo wikszego repertuaru funkcji. Midzy innymi mona zauway tutaj funkcj fputc(), ktra umieszcza pojedynczy znak w pliku. Ponadto w wyej zaprezentowanym programie zostaa uyta staa EOF, ktra reprezentuje koniec pliku (ang. End Of File). Powyszy program otwiera plik, ktrego nazwa przekazywana jest jako pierwszy argument programu, a nastpnie kopiuje dane z wejcia programu (stdin) na wyjcie (stdout) oraz do utworzonego pliku (identykowanego za pomoc fp). Program robi to dotd, a naciniemy kombinacj klawiszy Ctrl+D(w systemach Unixowych) lub Ctrl+Z(w Windows), ktra wyle do programu informacj, e skoczylimy wpisywa dane. Program wyjdzie wtedy z ptli i zamknie utworzony plik.

ROZMIAR PLIKU

101

Rozmiar pliku
Dziki standardowym funkcjom jzyka C moemy m.in. okreli dugo pliku. Do tego celu su funkcje fsetpos, fgetpos oraz fseek. Poniewa przy kadym odczycie/zapisie z/do pliku wskanik niejako przesuwa si o liczb przeczytanych/zapisanych bajtw. Moemy jednak ustawi wskanik w dowolnie wybranym miejscu. Do tego wanie su wyej wymienione funkcje. Aby odczyta rozmiar pliku powinnimy ustawi nasz wskanik na koniec pliku, po czym odczyta ile bajtw od pocztku pliku si znajdujemy. Wiem, brzmi to strasznie, ale dziaa wyjtkowo prosto i skutecznie. Uyjemy do tego tylko dwch funkcji: fseek oraz fgetpos. Pierwsza suy do ustawiania wskanika na odpowiedniej pozycji w pliku, a druga do odczytywania na ktrym bajcie pliku znajduje si wskanik. Kod, ktry okrela rozmiar pliku znajduje si tutaj: #include <stdio.h> int main (int argc, char **argv) { FILE *fp = NULL; fpos_t dlugosc; if (argc != 2) { printf ("Uycie: %s <nazwa pliku>\n", argv[0]); return 1; } if ((fp=fopen(argv[1], "rb"))==NULL) { printf ("Bd otwarcia pliku: %s!\n", argv[1]); return 1; } fseek (fp, 0, SEEK_END); /* ustawiamy wskanik na koniec pliku */ fgetpos (fp, &dlugosc); printf ("Rozmiar pliku: %d\n", dlugosc); fclose (fp); return 0; } Znajomo rozmiaru pliku przydaje si w wielu rnych sytuacjach, wic dobrze przeanalizuj przykad!

Przykad pliki graczny


Najprostszym przykadem rastrowego pliku gracznego jest plik PPM. Poniszy program pokazuje jak utworzy plik w katalogu roboczym programu. Do zapisu : nagwka pliku uywana jest funkcja fprintf, tablicy do pliku uywana jest funkcja fwrite. #include <stdio.h> int main() { const int dimx = 800; const int dimy = 800; int i, j;

102

ROZDZIA 14. CZYTANIE I PISANIE DO PLIKW FILE * fp = fopen("first.ppm", "wb"); /* b - tryb binarny */ fprintf(fp, "P6\n%d %d\n255\n", dimx, dimy); for(j=0; j<dimy; ++j){ for(i=0; i<dimx; ++i){ static unsigned char color[3]; color[0]=i % 255; /* red */ color[1]=j % 255; /* green */ color[2]=(i*j) % 255; /* blue */ fwrite(color,1,3,fp); } } fclose(fp); return 0;

} W powyszym przykadzie dostp do danych jest sekwencyjny. Jeli chcemy mie swobodny dostp do danych to : korzysta z funkcji: fsetpos, fgetpos oraz fseek, utworzy tablic (dla duych plikw dynamiczn), zapisa do niej wszystkie dane a nastpnie zapisa ca tablic do pliku. Ten sposb jest prostszy i szybszy. Naley zwrci uwag, e do obliczania rozmiaru caej tablicy nie moemy uy funkcji sizeof.

(a) Przykad uycia tej techniki, sekwen- (b) Przykad uycia tej techniki, swobodny cyjny dostp do danych (kod rdowy) dostp do danych (kod rdowy)

Co z katalogami?
Faktycznie, zapomnielimy o nich. Jednak wynika to z tego, e specykacja ANSI C nie uwzgldnia obsugi katalogw.

Rozdzia 15

wiczenia dla pocztkujcych


wiczenia
Wszystkie, zamieszczone tutaj wiczenia maj na celu pomc Ci w sprawdzeniu Twojej wiedzy oraz umoliwieniu Tobie wykorzystania nowo nabytych wiadomoci w praktyce. Pamitaj take, e ten podrcznik ma suy take innym, wic nie zamieszczaj tutaj Twoich rozwiza. Zachowaj je dla siebie.

wiczenie 1
Napisz program, ktry wywietli na ekranie twoje imi i nazwisko.

wiczenie 2
Napisz program, ktry poprosi o podanie dwch liczb rzeczywistych i wywietli wynik mnoenia obu zmiennych.

wiczenie 3
Napisz program, ktry pobierze jako argumenty z linii komend nazwy dwch plikw i przekopiuje zawarto pierwszego pliku do drugiego (tworzc lub zamazujc drugi).

wiczenie 4
Napisz program, ktry utworzy nowy plik (o dowolnie wybranej przez Ciebie nazwie) i zapisze tam: 1. Twoje imi 2. wiek 3. miasto, w ktrym mieszkasz Przykadowy plik powinien wyglda tak: Stanisaw 30 Krakw 103

104

ROZDZIA 15. WICZENIA DLA POCZTKUJCYCH

wiczenie 5
Napisz program generujcy tabliczk mnoenia 10 x 10 i wywietlajcy j na ekranie.

wiczenie 6
Napisz program znajdujcy pierwiastki trjmianu kwadratowego ax2 +bx+c=0, dla zadanych parametrw a, b, c.

Rozdzia 16

Tablice
W rozdziale Zmienne w C dowiedziae si, jak przechowywa pojedyncze liczby oraz znaki. Czasami zdarza si jednak, e potrzebujemy przechowa kilka, kilkanacie albo i wicej zmiennych jednego typu. Nie tworzymy wtedy np. dwudziestu osobnych zmiennych. W takich przypadkach z pomoc przychodzi nam tablica.

Rysunek 16.1: tablica 10-elementowa Tablica to cig zmiennych jednego typu. Cig taki posiada jedn nazw a do jego poszczeglnych elementw odnosimy si przez numer (indeks).

Wstp
Sposoby deklaracji tablic
Tablic deklaruje si w nastpujcy sposb: typ nazwa_tablicy[rozmiar]; gdzie rozmiar oznacza ile zmiennych danego typu moemy zmieci w tablicy. Zatem aby np. zadeklarowa tablic, mieszczc 20 liczb cakowitych moemy napisa tak: int tablica[20]; Podobnie jak przy deklaracji zmiennych, take tablicy moemy nada wartoci pocztkowe przy jej deklaracji. Odbywa si to przez umieszczenie wartoci kolejnych elementw oddzielonych przecinkami wewntrz nawiasw klamrowych: int tablica[3] = {1,2,3}; Moe to si wyda dziwne, ale po ostatnim elemencie tablicy moe wystpowa przecinek. Ponadto, jeeli poda si tylko cz wartoci, w pozostae wpisywane s zera: 105

106 int tablica[20] = {1,}; Niekoniecznie trzeba podawa rozmiar tablicy, np.: int tablica[] = {1, 2, 3, 4, 5};

ROZDZIA 16. TABLICE

W takim przypadku kompilator sam ustali rozmiar tablicy (w tym przypadku 5 elementw). Rozpatrzmy nastpujcy kod: #include <stdio.h> #define ROZMIAR 3 int main() { int tab[ROZMIAR] = {3,6,8}; int i; printf ("Druk tablicy tab:\n"); for (i=0; i<ROZMIAR; ++i) { printf ("Element numer %d = %d\n", i, tab[i]); } return 0; } Wynik: Druk tablicy tab: Element numer 0 = 3 Element numer 1 = 6 Element numer 2 = 8 Jak wida, wszystko si zgadza. W powyej zamieszczonym przykadzie uylimy staej do podania rozmiaru tablicy. Jest to o tyle podany zwyczaj, e w razie koniecznoci zmiany rozmiaru tablicy zmieniana jest tylko jedna linijka kodu przy staej, a nie kilkadziesit innych linijek, rozsianych po kodzie caego programu. W pierwotnym standardzie jzyka C rozmiar tablicy nie mg by okrelany przez zmienn lub nawet sta zadeklarowan przy uyciu sowa kluczowego const. Dopiero w pniejszej wersji standardu (tzw. C99) dopuszczono tak moliwo. Dlatego do deklarowania rozmiaru tablic czsto uywa si dyrektywy preprocesora #dene. Powinni na to zwrci uwag zwaszcza programici C++, gdy tam zawsze moliwe byy oba sposoby. Innym sposobem jest uycie operatora sizeof do poznania wielkoci tablicy. Poniszy kod robi to samo co przedstawiony: #include <stdio.h> int main() { int tab[3] = {3,6,8};

ODCZYT/ZAPIS WARTOCI DO TABLICY int i; printf ("Druk tablicy tab:\n"); for (i=0; i<(sizeof tab / sizeof *tab); ++i) { printf ("Element numer %d = %d\n", i, tab[i]); } return 0; }

107

Naley pamita, e dziaa on tylko dla tablic, a nie wskanikw (jak pniej si dowiesz wskanik te mona w pewnym stopniu traktowa jak tablic).

Odczyt/zapis wartoci do tablicy


Z tablicami posugujemy si tak samo jak ze zwykymi zmiennymi. Rnica polega jedynie na podaniu indeksu tablicy. Okrela on jednoznacznie, z ktrego elementu (wartoci) chcemy skorzysta. Indeksem jest liczba naturalna poczwszy od zera. To oznacza, e pierwszy element tablicy ma indeks rwny 0, drugi 1, trzeci 2, itd. Osoby, ktre wczeniej programoway w jzykach, takich jak Pascal, Basic czy Fortran musz przyzwyczai si do tego, e w jzyku C indeks numeruje si od 0. Sprbujmy przedstawi to na dziaajcym przykadzie. Przeanalizuj nastpujcy kod: int tablica[5] = {0}; int i = 0; tablica[2] = 3; tablica[3] = 7; for (i=0;i!=5;++i) { printf ("tablica[%d]=%d\n", i, tablica[i]); } Jak wida, na pocztku deklarujemy 5-elementow tablic, ktr od razu zerujemy. Nastpnie pod trzeci i czwarty element podstawiamy liczby 3 i 7. Ptla ma za zadanie wyprowadzi wynik naszych dziaa.

Tablice znakw
Tablice znakw tj. typu char oraz unsigned char posiadaj dwie oglnie przyjte nazwy, zalenie od ich przeznaczenia: bufory gdy wykorzystujemy je do przechowywania oglnie pojtych danych, gdy traktujemy je jako po prostu cigi bajtw (typ char ma rozmiar 1 bajta, wic jest elastyczny do przechowywania np. danych wczytanych z pliku przed ich przetworzeniem). napisy gdy zawarte w nich dane traktujemy jako cigi liter; jest im powicony osobny rozdzia Napisy.

108

ROZDZIA 16. TABLICE

Tablice wielowymiarowe
Rozwamy teraz konieczno przechowania w pamici komputera caej macierzy o wymiarach 10 x 10. Mona by tego dokona tworzc 10 osobnych tablic jednowymiarowych, reprezentujcych poszczeglne wiersze macierzy. Jednak jzyk C dostarcza nam duo wygodniejszej metody, ktra w dodatku jest bardzo atwa w uyciu. S to tablice wielowymiarowe, lub inaczej tablice tablic. Tablice wielowymiarowe deniujemy podajc przy zmiennej kilka wymiarw, np.: float macierz[10][10]; Tak samo wyglda dostp do poszczeglnych elementw tablicy: macierz[2][3] = 1.2; Rysunek 16.2: tablica dwuwymiaJak wida ten sposb jest duo wygodniejszy rowa (5x5) (i zapewne duo bardziej naturalny) ni deklarowanie 10 osobnych tablic jednowymiarowych. Aby zainicjowa tablic wielowymiarow naley zastosowa zagbianie klamer, np.: float macierz[3][4] = { { 1.6, 4.5, 2.4, 5.6 }, { 5.7, 4.3, 3.6, 4.3 }, { 8.8, 7.5, 4.3, 8.6 } };

/* pierwszy wiersz */ /* drugi wiersz */ /* trzeci wiersz */

Dodatkowo, pierwszego wymiaru nie musimy okrela (podobnie jak dla tablic jednowymiarowych) i wwczas kompilator sam ustali odpowiedni wielko, np.: float macierz[][4] { 1.6, 4.5, 2.4, { 5.7, 4.3, 3.6, { 8.8, 7.5, 4.3, { 6.3, 2.7, 5.7, }; = { 5.6 4.3 8.6 2.7

}, }, }, },

/* /* /* /*

pierwszy wiersz */ drugi wiersz */ trzeci wiersz */ czwarty wiersz */

Innym, bardziej elastycznym sposobem deklarowania tablic wielowymiarowych jest uycie wskanikw. Opisane to zostao w nastpnym rozdziale.

Ograniczenia tablic
Pomimo swej wygody tablice maj ograniczony, z gry zdeniowany rozmiar, ktrego nie mona zmieni w trakcie dziaania programu. Dlatego te w niektrych zastosowaniach tablice zostay wyparte przez dynamiczn alokacj pamici. Opisane to zostao w nastpnym rozdziale.

CIEKAWOSTKI

109

Przy uywaniu tablic trzeba by szczeglnie ostronym przy konstruowaniu ptli, poniewa ani kompilator, ani skompilowany program nie bd w stanie wychwyci przekroczenia przez indeks rozmiaru tablicy 1 . Efektem bdzie odczyt lub zapis pamici, znajdujcej si poza tablic. Wystarczy pomyli si o jedno miejsce (tzw. bd o by one) by spowodowa, e dziaanie programu zostanie nagle przerwane przez system operacyjny: int foo[100]; int i; for (i=0; i<=100; ++i) /* powinno by i<100 */ foo[i] = 0;

Ciekawostki
W pierwszej edycji konkursu IOCCC zwyciy program napisany w C, ktry wyglda do nietypowo: short main[] = { 277, 04735, -4129, 25, 0, 477, 1019, 0xbef, 0, 12800, -113, 21119, 0x52d7, -1006, -7151, 0, 0x4bc, 020004, 14880, 10541, 2056, 04010, 4548, 3044, -6716, 0x9, 4407, 6, 5568, 1, -30460, 0, 0x9, 5570, 512, -30419, 0x7e82, 0760, 6, 0, 4, 02400, 15, 0, 4, 1280, 4, 0, 4, 0, 0, 0, 0x8, 0, 4, 0, ,, 0, 12, 0, 4, 0, #, 0, 020, 0, 4, 0, 30, 0, 026, 0, 0x6176, 120, 25712, p, 072163, r, 29303, 29801, e }; Co ciekawe program ten bez przeszkd wykonywa si na komputerach VAX11 oraz PDP-11. Cay program to po prostu tablica z zawartym wewntrz kodem maszynowym! Tak naprawd jest to wykorzystanie pewnych waciwoci programu, ktry ostatecznie produkuje kod maszynowy. Linker (to o nim mowa) nie rozrnia na dobr spraw nazw funkcji od nazw zmiennych, wic bez problemu ustawi punkt wejcia programu na tablic wartoci, w ktrych zapisany by kod maszynowy. Tak przygotowany program zosta bez problemu wykonany przez komputer.

1 W zasadzie kompilatory maj moliwo dodania takiego sprawdzania, ale nie robi si tego, gdy znacznie spowolnioby to dziaanie programu. Takie postpowanie jest jednak podane w okresie testowania programu.

110

ROZDZIA 16. TABLICE

Rozdzia 17

Wskaniki
Zobacz w Wikipedii: Zmienne w komputerze s przechowywane w pamici. To wie kady programista, a Zmienna wskanikowa dobry programista potra kontrolowa zachowanie komputera w przydzielaniu i obsugi pamici dla zmiennych. W tym celu pomocne s wskaniki.

Co to jest wskanik?
Dla uatwienia przyjto poniej, e bajt ma 8 bitw, typ int skada si z dwch bajtw (16 bitw), typ long skada si z czterech bajtw (32 bitw) oraz liczby zapisane s w formacie big endian (tzn. bardziej znaczcy bajt na pocztku), co niekoniecznie musi by prawd na Twoim komputerze. Wskanik (ang. pointer) to specjalny rodzaj zmiennej, w ktrej zapisany jest adres w pamici komputera, tzn. wskanik wskazuje miejsce, gdzie zapisana jest jaka informacja. Oczywicie nic nie stoi na przeszkodzie aby wskazywan dan by inny wskanik do kolejnego miejsca w pamici. Obrazowo moemy wyobrazi sobie pami komputera jako bibliotek a zmienne jako ksiki. Zamiast bra ksik z pki samemu (analogicznie do korzystania wprost ze zwykych zmiennych) moemy poda bibliotekarzowi wypisany rewers z numerem katalogowym ksiki a on znajdzie j za nas. Analogia ta nie jest doskonaa, ale pozwala wyobrazi sobie niektre cechy wskanikw: kilka rewersw Rysunek 17.1: Wskanik a wskamoe dotyczy tej samej ksiki, numer w rewer- zujcy na zmienn b. Zauwamy, sie moemy skreli i uy go do zamwienia innej e b przechowuje liczb, podczas ksiki, jeli wpiszemy nieprawidowy numer kata- gdy a przechowuje adres b w palogowy to moemy dosta nie t ksik, ktr chce- mici (1462) my, albo te nie dosta nic. Warto te pozna w tym miejscu denicj adresu pamici. Moemy powiedzie, e adres to pewna liczba cakowita, jednoznacznie deniujca pooenie pewnego obiektu 111

112

ROZDZIA 17. WSKANIKI

(czyli np. znaku czy liczby) w pamici komputera. Dokadniejsz denicj moesz znale w Wikipedii.

Operowanie na wskanikach
By stworzy wskanik do zmiennej i mc si nim posugiwa naley przypisa mu odpowiedni warto (adres obiektu, na jaki ma wskazywa). Skd mamy zna ten adres? Wystarczy zapyta nasz komputer, jaki adres przydzieli zmiennej, ktr np. wczeniej gdzie stworzylimy. Robi si to za pomoc operatora & (operatora pobrania adresu). Przeanalizuj nastpujcy kod1 : #include <stdio.h> int main (void) { int liczba = 80; printf("Zmienna znajduje sie pod adresem: %p, i przechowuje wartosc: %d\n", (void*)&liczba, liczba); return 0; } Program ten wypisuje adres pamici, pod ktrym znajduje si zmienna oraz warto jak kryje zmienna przechowywana pod owym adresem. Aby mc zapisa gdzie taki adres naley zadeklarowa zmienn wskanikow. Robi si to poprzez dodanie * (gwiazdki) po typie na jaki zmienna ma wskazywa, np.: int *wskaznik1; char *wskaznik2; float*wskaznik3; Niektrzy programici mog nieco bdnie interpretowa wskanik do typu jako nowy typ i uwaa, e jeli napisz: int* a,b,c; to otrzymaj trzy wskaniki do liczby cakowitej. Tymczasem wskanikiem bdzie tylko zmienna a, natomiast b i c bd po prostu liczbami. Powodem jest to, e gwiazdka odnosi si do zmiennej a nie do typu. W tym przypadku trzy wskaniki otrzymamy piszc: int *a,*b,*c; Aby unikn pomyek, lepiej jest pisa gwiazdk tu przy zmiennej: int *a,b,c; albo jeszcze lepiej nie miesza deklaracji wskanikw i zmiennych: int *a; int b,c;
1 Warto zwrci uwag na rzutowanie do typu wskanik na void. Rzutowanie to jest wymagane przez funkcj printf, gdy ta oczekuje, e argumentem dla formatu %p bdzie wanie wskanik na void, gdy tymczasem w naszym przykadzie wyraenie &liczba jest typu wskanik na int.

OPEROWANIE NA WSKANIKACH

113

Aby dobra si do wartoci wskazywanej przez zmienn naley uy unarnego operatora * (gwiazdka), zwanego operatorem wyuskania: #include <stdio.h> int main (void) { int liczba = 80; int *wskaznik = &liczba; printf("Wartosc zmiennej: %d; jej adres: %p.\n", liczba, (void*)&liczba); printf("Adres zapisany we wskazniku: %p, wskazywana wartosc: %d.\n", (void*)wskaznik, *wskaznik); *wskaznik = 42; printf("Wartosc zmiennej: %d, wartosc wskazywana przez wskaznik: %d\n", liczba, *wskaznik); liczba = 0x42; printf("Wartosc zmiennej: %d, wartosc wskazywana przez wskaznik: %d\n", liczba, *wskaznik); return 0; }

O co chodzi z tym typem, na ktry ma wskazywa? Czemu to takie wane?


Jest to wane z kilku powodw. Rne typy zajmuj w pamici rn wielko. Przykadowo, jeeli w zmiennej typu unsigned int zapiszemy liczb 65 530, to w pamici bdzie istnie jako: +--------+--------+ |komrka1|komrka2| +--------+--------+ |11111111|11111010| = (unsigned int) 65530 +--------+--------+

Wskanik do takiej zmiennej (jak i do dowolnej innej) bdzie wskazywa na pierwsz komrk, w ktrej ta zmienna ma swoj warto. Jeeli teraz stworzymy drugi wskanik do tego adresu, tym razem typu unsigned char*, to wskanik przejmie ten adres prawidowo2 , lecz gdy sprbujemy odczyta warto na jak wskazuje ten wskanik to zostanie odczytana tylko pierwsza komrka i wynik bdzie rwny 255:
2 Tak naprawd nie zawsze mona przypisywa wartoci jednych wskanikw do innych. Standard C gwarantuje jedynie, e mona przypisa wskanikowi typu void* warto dowolnego wskanika, a nastpnie przypisa t warto do wskanika pierwotnego typu oraz, e dowolny wskanik mona przypisa do wskanika typu char*.

114 +--------+ |komrka1| +--------+ |11111111| = (unsigned char) 255 +--------+

ROZDZIA 17. WSKANIKI

Gdybymy natomiast stworzyli inny wskanik do tego adresu tym razem typu unsigned long* to przy prbie odczytu odczytane zostan dwa bajty z wartoci zapisan w zmiennej unsigned int oraz dodatkowe dwa bajty z niewiadom zawartoci i wwczas wynik bdzie rwny 65530 * 65536 + losowa warto : +--------+--------+--------+--------+ |komrka1|komrka2|komrka3|komrka4| +--------+--------+--------+--------+ |11111111|11111010|????????|????????| +--------+--------+--------+--------+ Ponadto, zapis czy odczyt poza przydzielonym obszarem pamici moe prowadzi do nieprzyjemnych skutkw takich jak zmiana wartoci innych zmiennych czy wrcz natychmiastowe przerwanie programu. Jako przykad mona poda ten (bdny) program3 : #include <stdio.h> int main(void) { unsigned char tab[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; unsigned short *ptr= (unsigned short*)&tab[2]; unsigned i; *ptr = 0xffff; for (i = 0; i < 9; ++i) { printf("%d ", tab[i]); } printf("%d\n", tab[9]); return 0; } Nie mona rwnie zapomina, e na niektrych architekturach dane wielobajtowe musz by odpowiednio wyrwnane w pamici. Np. zmienna dwubajtowa moe si znajdowa jedynie pod parzystymi adresami. Wwczas, gdybymy chcieli adres zmiennej jednobajtowej przypisa wskanikowi na zmienn dwubajtow mogoby doj do nieprzewidzianych bdw wynikajcych z prby odczytu niewyrwnanej danej. Zaskakujce moe si okaza, e rne wskaniki mog mie rny rozmiar. Np. wskanik na char moe by wikszy od wskanika na int, ale rwnie na odwrt. Co wicej, wskaniki rnych typw mog si rni reprezentacj adresw. Dla przykadu wskanik na char moe przechowywa adres do bajtu natomiast wskanik na int ten adres podzielony przez 2.
3 Moe

si okaza, e bd nie bdzie widoczny na Twoim komputerze.

ARYTMETYKA WSKANIKW

115

Podsumowujc, rne wskaniki to rne typy i nie naley beztrosko rzutowa wyrae pomidzy rnymi typami wskanikowymi, bo grozi to nieprzewidywalnymi bdami.

Do czego suy typ void*?


Czasami zdarza si, e nie wiemy, na jaki typ wskazuje dany wskanik. W takich przypadkach stosujemy typ void*. Sam void nie znaczy nic, natomiast void* oznacza wskanik na obiekt w pamici niewiadomego typu. Taki wskanik moemy potem odnie do konkretnego typu danych (w jzyku C++ wymagane jest do tego rzutowania). Na przykad, funkcja malloc zwraca wanie wskanik za pomoc void*.

Arytmetyka wskanikw
W jzyku C do wskanikw mona dodawa lub odejmowa liczby cakowite. Istotne jest jednak, e dodanie do wskanika liczby dwa nie spowoduje przesunicia si w pamici komputera o dwa bajty. Tak naprawd przesuniemy si o 2*rozmiar zmiennej. Jest to bardzo wana informacja! Pocztkujcy programici popeniaj czsto duo bdw, zwizanych z nieprawidow arytmetyk wskanikw. Zobaczmy na przykad: int *ptr; int a[] = {1, 2, 3, 5, 7}; ptr = &a[0];

Rysunek 17.2: Wskanik wskazuje na pierwsz komrk pamici Otrzymujemy nastpujc sytuacj: Gdy wykonamy ptr += 2;

Rysunek 17.3: Przesunicie wskanika na kolejne komrki wskanik ustawi si na trzecim elemencie tablicy.

116

ROZDZIA 17. WSKANIKI

Wskaniki mona rwnie od siebie odejmowa, czego wynikiem jest odlego dwch wskazywanych wartoci. Odlego zwracana jest jako liczba obiektw danego typu, a nie liczba bajtw. Np.: int a[] = {1, 2, 3, 5, 7}; int *ptr = &a[2]; int diff = ptr - a; /* diff ma warto 2 (a nie 2*sizeof(int)) */ Wynikiem moe by oczywicie liczba ujemna. Operacja jest przydatna do obliczania wielkoci tablicy (dugoci acucha znakw) jeeli mamy wskanik na jej pierwszy i ostatni element. Operacje arytmetyczne na wskanikach maj pewne ograniczenia. Przede wszystkim nie mona (tzn. standard tego nie deniuje) skonstruowa wskanika wskazujcego gdzie poza zadeklarowan tablic, chyba, e jest to obiekt zaraz za ostatnim (one past last), np.: int a[] = {1, int *ptr; ptr = a + 10; ptr = a - 10; ptr = a + 5; *ptr = 10; 2, 3, 5, 7}; /* /* /* /* niezdefiniowane */ niezdefiniowane */ zdefiniowane (element za ostatnim) */ to ju nie! */

Nie mona4 rwnie odejmowa od siebie wskanikw wskazujcych na obiekty znajdujce si w rnych tablicach, np.: int a[] = {1, 2, 3}, b[] = {5, 7}; int *ptr1 = a, *ptr2 = b; int diff = a - b; /* niezdefiniowane */

Tablice a wskaniki
Trzeba wiedzie, e tablice to te rodzaj zmiennej wskanikowej. Taki wskanik wskazuje na miejsce w pamici, gdzie przechowywany jest jej pierwszy element. Nastpne elementy znajduj si bezporednio w nastpnych komrkach pamici, w odstpie zgodnym z wielkoci odpowiedniego typu zmiennej. Na przykad tablica: int tab[] = {100,200,300}; wystpuje w pamici w szeciu komrkach 5 : +--------+--------+--------+--------+--------+--------+ |wartosc1| |wartosc2| |wartosc3| | +--------+--------+--------+--------+--------+--------+ |00000000|01100100|00000000|11001000|00000001|00101100| +--------+--------+--------+--------+--------+--------+
4 To znaczy standard nie deniuje co si wtedy stanie, aczkolwiek na wikszoci architektur odejmowanie dowolnych dwch wskanikw ma zdeniowane zachowanie. Piszc przenone programy nie mona jednak na tym polega, zwaszcza, e odejmowanie wskanikw wskazujcych na elementy rnych tablic zazwyczaj nie ma sensu. 5 Ponownie przyjmujc, e bajt ma 8 bitw, int dwa bajty i liczby zapisywane s w formacie little endian

GDY ARGUMENT JEST WSKANIKIEM...

117

Std do trzeciej wartoci mona si dosta tak (komrki w tablicy numeruje si od


zera): zmienna = tab[2]; albo wykorzystujc metod wskanikow: zmienna = *(tab + 2); Z denicji obie te metody s rwnowane. Z denicji (z wyjtkiem uycia operatora sizeof) wartoci zmiennej lub wyraenia typu tablicowego jest wskanik na jej pierwszy element (tab == &tab0). Co wicej, mona pj w drug stron i potraktowa wskanik jak tablic: int *wskaznik; wskaznik = &tab[1]; /* lub wskaznik = tab + 1; */ zmienna = wskaznik[1]; /* przypisze 300 */ Jako ciekawostk podamy, i w jzyku C mona odnosi si do elementw tablicy jeszcze w inny sposb: printf ("%d\n", 1[tab]); Skd ta dziwna notacja? Uzasadnienie jest proste: tab[1] = *(tab + 1) = *(1 + tab) = 1[tab] Podobn skadni stosuje m.in. asembler GNU.

Gdy argument jest wskanikiem...


Czasami zdarza si, e argumentem (lub argumentami) funkcji s wskaniki. W przypadku normalnych zmiennych nasza funkcja dziaa tylko na lokalnych kopiach tyche argumentw, natomiast nie zmienia zmiennych, ktre zostay podane jako argument. Natomiast w przypadku wskanika, kada operacja na wartoci wskazywanej powoduje zmian wartoci zmiennej zewntrznej. Sprbujmy rozpatrze poniszy przykad: #include <stdio.h> void func (int *zmienna) { *zmienna = 5; } int main () { int z=3; printf ("z=%d\n", z); /* wypisze 3 */ func(&z); printf ("z=%d\n", z); /* wypisze 5 */ } Widzimy, e funkcje w jzyku C nie tylko potra zwraca okrelon warto, lecz take zmienia dane, podane im jako argumenty. Ten sposb przekazywania argumentw do funkcji jest nazywany przekazywaniem przez wskanik (w przeciwiestwie do normalnego przekazywania przez warto).

118

ROZDZIA 17. WSKANIKI

Zwrmy uwag na wywoanie func(&z);. Naley pamita, by do funkcji przekaza adres zmiennej a nie sam zmienn. Jeli bymy napisali func(z); to funkcja staraaby si zmieni komrk pamici o numerze 3. Kompilator powinien ostrzec w takim przypadku o konwersji z typu int do wskanika, ale czsto kompiluje taki program pozostajc na ostrzeeniu.

Nie gra roli czy przy deklaracji funkcji jako argument funkcji podamy wskanik czy tablic (z podanym rozmiarem lub nie), np. ponisze deklaracje s identyczne: void func(int ptr[]); void func(int *ptr); Mona przyj konwencj, e deklaracja okrela czy funkcji przekazujemy wskanik do pojedynczy argument czy do sekwencji, ale rwnie dobrze mona za kadym razem stosowa gwiazdk.

Puapki wskanikw
Wane jest, aby przy posugiwaniu si wskanikami nigdy nie prbowa odwoywa si do komrki wskazywanej przez wskanik o wartoci NULL lub niezainicjowany wskanik! Przykadem nieprawidowego kodu, moe by np.: int *wsk; printf ("zawartosc komorki: %d\n", *(wsk)); /* Bd */ wsk = 0; /* 0 w kontekcie wskanikw oznacza wskanik NULL */ printf ("zawartosc komorki: %d\n", *(wsk)); /* Bd */ Naley rwnie uwaa, aby nie odwoywa si do komrek poza przydzielon pamici, np.: int tab[] = { 0, 1, 2 }; tab[3] = 3; /* Bd */ Pamitaj te, e moesz by rozczarowany uywajc operatora sizeof, podajc zmienn wskanikow. Uzyskana wielko bdzie wielkoci wskanika, a nie wielkoci typu uytego podczas deklarowania naszego wskanika. Wielko ta bdzie zawsze miaa taki sam rozmiar dla kadego wskanika, w zalenoci od kompilatora, a take docelowej platformy. Zamiast tego uywaj: sizeof(*wskanik). Przykad: char *zmienna; int a = sizeof zmienna; /* a wynosi np. 4, tj. sizeof(char*) */ a = sizeof(char*); /* robimy to samo, co wyej */ a = sizeof *zmienna; /* zmienna a ma teraz przypisany rozmiar pojedyczego znaku, tj. 1 */ a = sizeof(char); /* robimy to samo, co wyej */

Na co wskazuje NULL?
Analizujc kody rdowe programw czsto mona spotka taki oto zapis: void *wskaznik = NULL; /* lub = 0 */

STAE WSKANIKI

119

Wiesz ju, e nie moemy odwoa si pod komrk pamici wskazywan przez wskanik NULL. Po co zatem przypisywa wskanikowi 0? Odpowied moe by zaskakujca: wanie po to, aby unikn bdw! Wydaje si to zabawne, ale wikszo (jeli nie wszystkie) funkcje, ktre zwracaj wskanik w przypadku bdu zwrc wanie NULL, czyli zero. Tutaj rodzi si kolejna wskazwka: jeli w danej zmiennej przechowujemy wskanik, zwrcony wczeniej przez jak funkcj zawsze sprawdzajmy, czy nie jest on rwny 0 (NULL). Wtedy mamy pewno, e funkcja zadziaaa poprawnie. Dokadniej, NULL nie jest sowem kluczowym, lecz sta (makrem) zadeklarowan przez dyrektywy preprocesora. Deklaracja taka moe by albo wartoci 0 albo te wartoci 0 zrzutowan na void* (((void *)0)), ale te jakim sowem kluczowym deklarowanym przez kompilator. Warto zauway, e pomimo przypisywania wskanikowi zera, nie oznacza to, e wskanik NULL jest reprezentowany przez same zerowe bity. Co wicej, wskaniki NULL rnych typw mog mie rn warto! Z tego powodu poniszy kod jest niepoprawny: int **tablica_wskaznikow = calloc(100, sizeof *tablica_wskaznikow); Zakada on, e w reprezentacji wskanika NULL wystpuj same zera. Poprawnym zainicjowaniem dynamicznej tablicy wskanikw wartociami NULL jest (pomijamy sprawzdanie wartoci zwrconej przez malloc()): int **tablica_wskaznikow = malloc(100 * sizeof *tablica_wskaznikow); int i = 0; while (i<100) tablica_wskaznikow[i++] = 0;

Stae wskaniki
Tak, jak istniej zwyke stae, tak samo moemy mie stae wskaniki jednak s ich dwa rodzaje. Wskaniki na sta warto: const int *a; /* lub rwnowanie */ int const *a; oraz stae wskaniki: int * const b; Pierwszy to wskanik, ktrym nie mona zmieni wskazywanej wartoci. Drugi to wskanik, ktrego nie mona przestawi na inny adres. Dodatkowo, mona zadeklarowa stay wskanik, ktrym nie mona zmieni wartoci wskazywanej zmiennej, i rwnie mona zrobi to na dwa sposoby: const int * const c; /* alternatywnie */ int const * const c; int i=0; const int *a=&i; int * const b=&i; int const * const c=&i; *a = 1; /* kompilator zaprotestuje *b = 2; /* ok */ *c = 3 /* kompilator zaprotestuje a = b; /* ok */ b = a; /* kompilator zaprotestuje c = a; /* kompilator zaprotestuje

*/ */ */ */

120

ROZDZIA 17. WSKANIKI

Wskaniki na sta warto s przydatne midzy innymi w sytuacji gdy mamy duy obiekt (na przykad struktur z kilkoma polami). Jeli przypiszemy tak zmienn do innej zmiennej, kopiowanie moe potrwa duo czasu, a oprcz tego zostanie zajte duo pamici. Przekazanie takiej struktury do funkcji albo zwrcenie jej jako warto funkcji wie si z takim samym narzutem. W takim wypadku dobrze jest uy wskanika na sta warto. void funkcja(const duza_struktura *ds) { /* czytamy z ds i wykonujemy obliczenia */ } funkcja(&dane); /* mamy pewno, e zmienna dane nie zostanie zmieniona */

Dynamiczna alokacja pamici


Majc styczno z tablicami mona si zastanowi, czy nie daoby si mie tablic, ktrych rozmiar dostosowuje si do naszych potrzeb a nie jest na stae zaszyty w kodzie programu. Chcc pomieci wicej danych moemy po prostu zwikszy rozmiar tablicy ale gdy do przechowania bdzie mniej elementw okae si, e marnujemy pami. Jzyk C umoliwia dziki wskanikom i dynamicznej alokacji pamici tworzenie tablic takiej wielkoci, jakiej akurat potrzebujemy.

O co chodzi
Czym jest dynamiczna alokacja pamici? Normalnie zmienne programu przechowywane s na tzw. stosie (ang. stack ) powstaj, gdy program wchodzi do bloku, w ktrym zmienne s zadeklarowane a zwalniane w momencie, kiedy program opuszcza ten blok. Jeli deklarujemy tak tablice, to ich rozmiar musi by znany w momencie kompilacji eby kompilator wygenerowa kod rezerwujcy odpowiedni ilo pamici. Dostpny jest jednak drugi rodzaj rezerwacji (czyli alokacji) pamici. Jest to alokacja na stercie (ang. heap). Sterta to obszar pamici wsplny dla caego programu, przechowywane s w nim zmienne, ktrych czas ycia nie jest zwizany z poszczeglnymi blokami. Musimy sami rezerwowa dla nich miejsce i to miejsce zwalnia, ale dziki temu moemy to zrobi w dowolnym momencie dziaania programu. Naley pamita, e rezerwowanie i zwalnianie pamici na stercie zajmuje wicej czasu ni analogiczne dziaania na stosie. Dodatkowo, zmienna zajmuje na stercie wicej miejsca ni na stosie sterta utrzymuje specjaln struktur, w ktrej trzymane s wolne partie (moe to by np. lista). Tak wic uywajmy dynamicznej alokacji tam, gdzie jest potrzebna dla danych, ktrych rozmiaru nie jestemy w stanie przewidzie na etapie kompilacji lub ich ywotno ma by niezwizana z blokiem, w ktrym zostay zaalokowane.

Obsuga pamici
Podstawow funkcj do rezerwacji pamici jest funkcja malloc. Jest to niezbyt skomplikowana funkcja podajc jej rozmiar (w bajtach) potrzebnej pamici, dostajemy wskanik do zaalokowanego obszaru. Zamy, e chcemy stworzy tablic liczb typu oat: int rozmiar; float *tablica; rozmiar = 3; tablica = malloc(rozmiar * sizeof *tablica); tablica[0] = 0.1;

DYNAMICZNA ALOKACJA PAMICI

121

Przeanalizujmy teraz po kolei, co dzieje si w powyszym fragmencie. Najpierw deklarujemy zmienne rozmiar tablicy i wskanik, ktry bdzie wskazywa obszar w pamici, gdzie bdzie trzymana tablica. Do zmiennej rozmiar moemy w trakcie dziaania programu przypisa cokolwiek wczyta j z pliku, z klawiatury, obliczy, wylosowa nie jest to istotne. rozmiar * sizeof *tablica oblicza potrzebn wielko tablicy. Dla kadej zmiennej oat potrzebujemy tyle bajtw, ile zajmuje ten typ danych. Poniewa moe si to rni na rozmaitych maszynach, istnieje operator sizeof , zwracajcy dla danego wyraenia rozmiar jego typu w bajtach. W wielu ksikach (rwnie K&Rv2) i w Internecie stosuje si inny schemat uycia funkcji malloc a mianowicie: tablica = (float*)malloc(rozmiar * sizeof(float)). Takie uycie naley traktowa jako bdne, gdy nie sprzyja ono poprawnemu wykrywaniu bdw. Rozwamy sytuacj, gdy programista zapomni doda plik nagwkowy stdlib.h, wwczas kompilator (z braku deklaracji funkcji malloc) przyjmie, e zwraca ona typ int zatem do zmiennej tablica (ktra jest wskanikiem) bdzie przypisywana liczba cakowita, co od razu spowoduje bd kompilacji (a przynajmniej ostrzeenie), dziki czemu bdzie mona szybko poprawi kod programu. Rzutowanie jest konieczne tylko w jzyku C++, gdzie konwersja z void* na inne typy wskanikowe nie jest domylna, ale jzyk ten oferuje nowe sposoby alokacji pamici. Teraz rozwamy sytuacj, gdy zdecydujemy si zwikszy dokadno oblicze i zamiast typu oat uy typu double. Bdziemy musieli wyszuka wszystkie wywoania funkcji malloc, calloc i realloc odnoszce si do naszej tablicy i zmienia wszdzie sizeof(float) na sizeof(double). Aby temu zapobiec lepiej od razu uy sizeof *tablica (lub jeli kto woli z nawiasami: sizeof(*tablica)), wwczas zmiana typu zmiennej tablica na double* zostanie od razu uwzgldniona przy alokacji pamici. Dodatkowo, naley sprawdzi, czy funkcja malloc nie zwrcia wartoci NULL dzieje si tak, gdy zabrako pamici. Ale uwaga: moe si tak sta rwnie jeeli jako argument funkcji podano zero. Jeli dany obszar pamici nie bdzie ju nam wicej potrzebny powinnimy go zwolni, aby system operacyjny mg go przydzieli innym potrzebujcym procesom. Do zwolnienia obszaru pamici uywamy funkcji free(), ktra przyjmuje tylko jeden argument wskanik, ktry otrzymalimy w wyniku dziaania funkcji malloc(). free (addr); Naley pamita o zwalnianiu pamici inaczej dojdzie do tzw. wycieku pamici program bdzie rezerwowa now pami ale nie zwraca jej z powrotem i w kocu pamici moe mu zabrakn. Naley te uwaa, by nie zwalnia dwa razy tego samego miejsca. Po wywoaniu free wskanik nie zmienia wartoci, pami wskazywana przez niego moe te nie od razu ulec zmianie. Czasem moemy wic korzysta ze wskanika (zwaszcza czyta) po wywoaniu free nie orientujc si, e robimy co le i w pewnym momencie dosta komunikat o nieprawidowym dostpie do pamici. Z tego powodu zaraz po wywoaniu funkcji free mona przypisa wskanikowi warto 0. Czasami moemy potrzebowa zmieni rozmiar ju przydzielonego bloku pamici. Tu z pomoc przychodzi funkcja realloc: tablica = realloc(tablica, 2*rozmiar*sizeof *tablica); Funkcja ta zwraca wskanik do bloku pamici o podanej wielkoci (lub NULL gdy zabrako pamici). Uwaga moe to by inny wskanik. Jeli zadamy zwikszenia rozmiaru a za zaalokowanym aktualnie obszarem nie bdzie wystarczajco duo wolnego miejsca, funkcja znajdzie nowe miejsce i przekopiuje tam star zawarto. Jak wida, wywoanie tej funkcji moe by wic kosztowne pod wzgldem czasu.

122

ROZDZIA 17. WSKANIKI

Ostatni funkcj jest funkcja calloc(). Przyjmuje ona dwa argumenty: liczb elementw tablicy oraz wielko pojedynczego elementu. Podstawow rnic pomidzy funkcjami malloc() i calloc() jest to, e ta druga zeruje warto przydzielonej pamici (do wszystkich bajtw wpisuje warto 0).

Tablice wielowymiarowe

Rysunek 17.4: tablica dwuwymiarowa w rzeczywistoci tablica ze wskanikami do tablic


W rozdziale Tablice pokazalimy, jak tworzy tablice wielowymiarowe, gdy ich rozmiar jest znany w czasie kompilacji. Teraz zaprezentujemy, jak to wykona za pomoc wskanikw i to w sytuacji, gdy rozmiar moe si zmienia. Zamy, e chcemy stworzy tabliczk mnoenia: int rozmiar; int i; int **tabliczka; printf("Podaj rozmiar tabliczki mnozenia: "); scanf("%i", &rozmiar); /* dla prostoty nie bdziemy sprawdzali, czy uytkownik wpisa sensown warto */ tabliczka = malloc(rozmiar * sizeof *tabliczka); for (i = 0; i<rozmiar; ++i) { tabliczka[i] = malloc(rozmiar * sizeof **tabliczka); } for (i = 0; i<rozmiar; ++i) { int j; for (j = 0; j<rozmiar; ++j) { tabliczka[i][j] = (i+1)*(j+1); } } Najpierw musimy przydzieli pami najpierw dla tablicy tablic (1) a potem dla kadej z podtablic osobno (2-4). Poniewa tablica jest typu int* to nasza tablica tablic bdzie wskanikiem na int* czyli int**. Podobnie osobno, ale w odwrotnej kolejnoci bdziemy zwalnia tablic wielowymiarow: for (i = 0; i<rozmiar; ++i) { free(tabliczka[i]); } free(tabliczka); Naley nie pomyli kolejnoci: po wykonaniu free(tabliczka) nie bdziemy mieli prawa odwoywa si do tabliczka[i] (bo wczeniej dokonalimy zwolnienia tego obszaru pamici). /* /* /* /* 1 2 3 4 */ */ */ */

WSKANIKI NA FUNKCJE

123

Mona take zastosowa bardziej oszczdny sposb alokowania tablicy wielowymiarowej, a mianowicie: #define ROZMIAR 10 int i; int **tabliczka = malloc(ROZMIAR * sizeof *tabliczka); *tabliczka = malloc(ROZMIAR * ROZMIAR * sizeof **tabliczka); for (i = 1; i<ROZMIAR; ++i) { tabliczka[i] = tabliczka[0] + (i * ROZMIAR); } for (i = 0; i<ROZMIAR; ++i) { int j; for (j = 0; j<ROZMIAR; ++j) { tabliczka[i][j] = (i+1)*(j+1); } } free(*tabliczka); free(tabliczka); Powyszy kod dziaa w ten sposb, e zamiast dla poszczeglnych wierszy alokowa osobno pami alokuje pami dla wszystkich elementw tablicy i dopiero pniej przypisuje wskazania poszczeglnych wskanikw-wierszy na kolejne bloki po ROZMIAR elementw. Sposb ten jest bardziej oszczdny z dwch powodw: Po pierwsze wykonywanych jest mniej operacji przydzielania pamici (bo tylko dwie). Po drugie za kadym razem, gdy alokuje si pami troch miejsca si marnuje, gdy funkcja malloc musi w stogu przechowywa rne dodatkowe informacje na temat kadej zaalokowanej przestrzeni. Ponadto, czasami alokacja odbywa si blokami i gdy zada si niepeny blok to reszta bloku jest tracona. Zauwamy, e w ten sposb moemy uzyska nie tylko normaln, kwadratow tablic (dla dwch wymiarw). Moliwe jest np. uzyskanie tablicy trjktnej: 0123 012 01 0 lub tablicy o dowolnym innym rozkadzie dugoci wierszy, np.: const size_t wymiary[] = { 2, 4, 6, 8, 1, 3, 5, 7, 9 }; int i; int **tablica = malloc((sizeof wymiary / sizeof *wymiary) * sizeof *tablica); for (i = 0; i<10; ++i) { tablica[i] = malloc(wymiary[i] * sizeof **tablica); } Gdy nabierzesz wprawy w uywaniu wskanikw oraz innych funkcji malloc i realloc nauczysz si wykonywa rne inne operacje takie jak dodawanie kolejnych wierszy, usuwanie wierszy, zmiana rozmiaru wierszy, zamiana wierszy miejscami itp.

Wskaniki na funkcje
Dotychczas zajmowalimy si sytuacj, gdy wskanik wskazywa na jak zmienn. Jednak nie tylko zmienna ma swj adres w pamici. Oprcz zmiennej take i funkcja musi mie swoje

124

ROZDZIA 17. WSKANIKI

okrelone miejsce w pamici. A poniewa funkcja ma swj adres6 , to nie ma przeszkd, aby i na ni wskazywa jaki wskanik.

Deklaracja wskanika na funkcj


Tak naprawd kod maszynowy utworzony po skompilowaniu programu odnosi si wanie do adresu funkcji. Wskanik na funkcj rni si od innych rodzajw wskanikw. Jedn z gwnych rnic jest jego deklaracja. Zwykle wyglda ona tak: typ_zwracanej_wartoci (*nazwa_wskanika)(typ1 parametr1, typ2 parametr2); Oczywicie parametrw moe by wicej (albo te w ogle moe ich nie by). Oto przykad wykorzystania wskanika na funkcj: #include <stdio.h> int suma (int a, int b) { return a+b; } int main () { int (*wsk_suma)(int a, int b); wsk_suma = suma; printf("4+5=%d\n", wsk_suma(4,5)); return 0; } Zwrmy uwag na dwie rzeczy: 1. przypisujc nazw funkcji bez nawiasw do wskanika automatycznie informujemy kompilator, e chodzi nam o adres funkcji 2. wskanika uywamy tak, jak normalnej funkcji, na ktr on wskazuje

Do czego mona uy wskanikw na funkcje?


Jzyk C jest jzykiem strukturalnym, jednak dziki wskanikom istnieje w nim moliwo zaszczepienia pewnych obiektowych waciwoci. Wskanik na funkcj moe by np. elementem struktury wtedy mamy bardzo prymitywn namiastk klasy, ktr dobrze znaj programici, piszcy w jzyku C++. Ponadto dziki wskanikom moemy tworzy mechanizmy dziaajce na zasadzie funkcji zwrotnej7 . Dobrym przykadem moe by np. tworzenie sterownikw, gdzie musimy poinformowa rne podsystemy, jakie funkcje w naszym kodzie su do wykonywania okrelonych czynnoci. Przykad: struct urzadzenie { int (*otworz)(void); void (*zamknij)(void); }; int moje_urzadzenie_otworz (void) {
6 Tak naprawd kod maszynowy utworzony po skompilowaniu programu odnosi si wanie do adresu funkcji. 7 Funkcje zwrotne znalazy zastosowanie gwnie w programowaniu GUI

WSKANIKI NA FUNKCJE
/* kod...*/ } void moje_urzadzenie_zamknij (void) { /* kod... */ } int rejestruj_urzadzenie(struct urzadzenie &u) { /* kod... */ } int init (void) { struct urzadzenie moje_urzadzenie; moje_urzadzenie.otworz = moje_urzadzenie_otworz; moje_urzadzenie.zamknij = moje_urzadzenie_zamknij; rejestruj_urzadzenie(&moje_urzadzenie); }

125

W ten sposb w pamici kada klasa musi przechowywa wszystkie wskaniki do wszystkich metod. Innym rozwizaniem moe by stworzenie statycznej struktury ze wskanikami do funkcji i wwczas w strukturze bdzie przechowywany jedynie wskanik do tej struktury, np.: struct urzadzenie_metody { 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_metody moje_urzadzenie_metody = { moje_urzadzenie_otworz, moje_urzadzenie_zamknij }; int rejestruj_urzadzenie(struct urzadzenie &u) { /* kod... */ } int init (void)

126
{ struct urzadzenie moje_urzadzenie; moje_urzadzenie.m = &moje_urzadzenie_metody; rejestruj_urzadzenie(&moje_urzadzenie); }

ROZDZIA 17. WSKANIKI

Moliwe deklaracje wskanikw


Tutaj znajduje si krtkie kompendium jak deniowa wskaniki oraz co oznaczaj poszczeglne denicje:

i; *p; a[]; f(); **pp; (*pa)[]; (*pf)(); *ap[]; *fp(); ***ppp; (**ppa)[]; (**ppf)(); *(*pap)[]; *(*pfp)(); **app[]; (*apa[])[]; (*apf[])(); ***fpp(); (*fpa())[]; (*fpf())();

zmienna cakowita (typu int) i wskanik p wskazujcy na zmienn cakowit tablica a liczb cakowitych typu int funkcja f zwracajca liczb cakowit typu int wskanik pp na wskanik wskazujcy na liczb cakowit typu int wskanik pa wskazujcy na tablic liczb cakowitych typu int wskanik pf na funkcj zwracajc liczb cakowit typu int tablica ap wskanikw na liczby cakowite typu int funkcja fp, ktra zwraca wskanik na zmienn typu int wskanik ppp wskazujcy na wskanik wskazujcy na wskanik wskazujcy na liczb typu int wskanik ppa na wskanik wskazujcy na tablic liczb cakowitych typu int wskanik ppf wskazujcy na wskanik funkcji zwracajcej dane typu int wskanik pap wskazujcy na tablic wskanikw na typ int wskanik pfp na funkcj zwracajc wskanik na typ int tablica wskanikw app wskazujcych na typ int tablica wskanikw apa wskazujcych wskaniki na typ int tablica wskanikw apf na funkcj, ktre zwracaj wskaniki na typ int funkcja fpp, ktra zwraca wskanik na wskanik na wskanik, ktry wskazuje typ int funkcja fpa, ktra zwraca wskanik na tablic liczb typu int funkcja fpf, ktra zwraca wskanik na funkcj, ktra zwraca dane typu int

Popularne bdy
Jednym z najczstszych bdw, oprcz prb wykonania operacji na wskaniku NULL, s odwoania si do obszaru pamici po jego zwolnieniu. Po wykonaniu funkcji free() nie moemy ju wykonywa adnych odwoa do zwolnionego obszaru. Innym rodzajem bdw s: 1. odwoania do adresw pamici, ktre s poza obszarem przydzielonym funkcj malloc() 2. brak sprawdzania, czy dany wskanik nie ma wartoci NULL 3. wycieki pamici, czyli nie zwalnianie caej, przydzielonej wczeniej pamici

CIEKAWOSTKI

127

Ciekawostki
w rozdziale Zmienne pisalimy o staych. Normalnie nie mamy moliwoci zmiany ich wartoci, ale z uyciem wskanikw staje si to moliwe: const int CONST=0; int *c=&CONST; *c = 1; printf("%i\n",CONST); /* wypisuje 1 */ Konstrukcja taka moe jednak wywoa ostrzeenie kompilatora bd nawet jego bd wtedy moe pomc jawne rzutowanie z const int* na int*. jzyk C++ oferuje mechanizm podobny do wskanikw, ale nieco wygodniejszy referencje jzyk C++ dostarcza te innego sposobu dynamicznej alokacji i zwalniania pamici przez operatory new i delete w rozdziale Typy zoone znajduje si opis implementacji listy za pomoc wskanikw. Przykad ten moe by bardzo przydatny przy zrozumieniu po co istniej wskaniki, jak si nimi posugiwa oraz jak dobrze zarzdza pamici.

128

ROZDZIA 17. WSKANIKI

Rozdzia 18

Napisy
W dzisiejszych czasach komputer przesta by narzdziem tylko i wycznie do przetwarzania danych. Od programw komputerowych zaczto wymaga czego nowego program w wyniku swojego dziaania nie ma zwraca danych, rozumianych tylko przez autora programu, lecz powinien by na tyle komunikatywny, aby przecitny uytkownik komputera mg bez problemu tene komputer obsuy. Do przechowywania tyche komunikatw su tzw. acuchy (ang. string) czyli cigi znakw. Jzyk C nie jest wygodnym narzdziem do manipulacji napisami. Jak si wkrtce przekonamy, zestaw funkcji umoliwiajcych operacje na napisach w bibliotece standardowej C jest raczej skromny. Dodatkowo, problemem jest sposb, w jaki acuchy przechowywane s w pamici. Napisy w jzyku C mog by przyczyn wielu trudnych do wykrycia bdw w programach. Warto dobrze zrozumie, jak naley operowa na acuchach znakw i zachowa szczegln ostrono w tych miejscach, gdzie napisw uywamy.

acuchy znakw w jzyku C


Napis jest zapisywany w kodzie programu jako cig znakw zawarty pomidzy dwoma cudzysowami. printf ("Napis w jzyku C"); W pamici taki acuch jest nastpujcym po sobie cigiem znakw (char), ktry koczy si znakiem null (czyli po prostu liczb zero), zapisywanym jako \0. Jeli mamy napis, do poszczeglnych znakw odwoujemy si jak w tablicy: char *tekst = "Jaki tam tekst"; printf("%c\n", "przykad"[0]); /* wypisze p - znaki w napisach s numerowane od zera */ printf("%c\n", tekst[2]); /* wypisze k */ Poniewa napis w pamici koczy si zerem umieszczonym tu za jego zawartoci, odwoanie si do znaku o indeksie rwnym dugoci napisu zwrci zero: printf("%d", "test"[4]); /* wypisze 0 */

Napisy moemy wczytywa z klawiatury i wypisywa na ekran przy pomocy dobrze znanych funkcji scanf, printf i pokrewnych. Formatem uywanym dla napisw jest %s.

129

130
printf("%s", tekst);

ROZDZIA 18. NAPISY

Wikszo funkcji dziaajcych na napisach znajduje si w pliku nagwkowym string.h. Jeli acuch jest zbyt dugi, mona zapisa go w kilku linijkach, ale wtedy przechodzc do nastpnej linii musimy na kocu postawi znak \. printf("Ten napis zajmuje \ wicej ni jedn lini");

Instrukcja taka wydrukuje: Ten napis zajmuje wicej ni jedn lini Moemy zauway, e napis, ktry w programie zaj wicej ni jedn lini, na ekranie zaj tylko jedn. Jest tak, poniewa \ informuje kompilator, e acuch bdzie kontynuowany w nastpnej linii kodu nie ma wpywu na prezentacj acucha. Aby wydrukowa napis w kilku liniach naley wstawi do niego \n (n pochodzi tu od new line, czyli nowa linia). printf("Ten napis\nna ekranie\nzajmie wicej ni jedn lini."); W wyniku otrzymamy: Ten napis na ekranie zajmie wicej ni jedn lini.

Jak komputer przechowuje w pamici acuch?

Rysunek 18.1: Napis Merkkijono przechowywany w pamici


Zmienna, ktra przechowuje acuch znakw, jest tak naprawd wskanikiem do cigu znakw (bajtw) w pamici. Moemy te myle o napisie jako o tablicy znakw (jak wyjanialimy wczeniej, tablice to te wskaniki). Moemy 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[] = {J,a,k,i,s, ,t,a,m, ,t,e,k,s,t,\0}; /* Tekst to taka tablica jak kada inna */ Kompilator automatycznie przydziela wtedy odpowiedni ilo pamici (tyle bajtw, ile jest liter plus jeden dla koczcego nulla). Jeli natomiast wiemy, e dany acuch powinien przechowywa okrelon ilo znakw (nawet, jeli w deklaracji tego acucha podajemy mniej znakw) deklarujemy go w taki sam sposb, jak tablic jednowymiarow: char tekst[80] = "Ten tekst musi by krtszy ni 80 znakw";

ACUCHY ZNAKW W JZYKU C

131

Naley cay czas pamita, e napis jest tak naprawd tablic. Jeli zarezerwowalimy dla napisu 80 znakw, to przypisanie do niego duszego napisu spowoduje pisanie po pamici. Uwaga! Deklaracja char *tekst = cokolwiek; oraz char tekst = cokolwiek; pomimo, e wygldaj bardzo podobnie bardzo si od siebie rni. W przypadku pierwszej deklaracji prba zmodykowania napisu (np. tekst[0] = C;) moe mie nieprzyjemne skutki. Dzieje si tak dlatego, e char *tekst = cokolwiek; deklaruje wskanik na stay obszar pamici 1 . Pisanie po pamici moe czasami skoczy si bdem dostpu do pamici (segmentation fault w systemach UNIX) i zamkniciem programu, jednak moe zdarzy si jeszcze gorsza ewentualno moemy zmieni w ten sposb przypadkowo warto innych zmiennych. Program zacznie wtedy zachowywa si nieprzewidywalnie zmienne a nawet stae, co do ktrych zakadalimy, e ich warto bdzie cile ustalona, mog przyj tak warto, jaka absolutnie nie powinna mie miejsca. Warto wic stosowa zabezpieczenia typu makra assert. Kluczowy jest te koczcy napis znak null. W zasadzie wszystkie funkcje operujce na napisach opieraj wanie na nim. Na przykad, strlen szuka rozmiaru napisu idc od pocztku i zliczajc znaki, a nie natra na znak o kodzie zero. Jeli nasz napis nie koczy si znakiem null, funkcja bdzie sza dalej po pamici. Na szczcie, wszystkie operacje podstawienia typu tekst = Tekst powoduj zakoczenie napisu nullem (o ile jest na niego miejsce) 2 .

Znaki specjalne
Jak zapewne zauwaye w poprzednim przykadzie, w acuchu ostatnim znakiem jest znak o wartoci zero (\0). Jednak acuchy mog zawiera inne znaki specjalne(sekwencje sterujce), np.: \a - alarm (sygna akustyczny terminala) \b - backspace (usuwa poprzedzajcy znak) \f - wysuniecie strony (np. w drukarce) \r - powrt kursora (karetki) do pocztku wiersza \n - znak nowego wiersza \ - cudzysw \ - apostrof \\ - ukonik wsteczny (backslash) \t - tabulacja pozioma \v - tabulacja pionowa \? - znak zapytania (pytajnik) \ooo - liczba zapisana w systemie oktalnym (semkowym), gdzie ooo naley zastpi trzycyfrow liczb w tym systemie \xhh - liczba zapisana w systemie heksadecymalnym (szesnastkowym), gdzie hh naley zastpi dwucyfrow liczb w tym systemie \unnnn - uniwersalna nazwa znaku, gdzie nnnn naley zastpi czterocyfrowym identykatorem znaku w systemie szesnatkowym. nnnn odpowiada duszej formie w postaci 0000nnnn \unnnnnnnn - uniwersalna nazwa znaku, gdzie nnnnnnnn naley zastpi omiocyfrowym identykatorem znaku w systemie szesnatkowym.
si zatem zastanawia czemu kompilator dopuszcza przypisanie do zwykego wskanika wskazania na stay obszar, skoro kod const int *foo; int *bar = foo; generuje ostrzeenie lub wrcz si nie kompiluje. Jest to pewna zaszo historyczna wynikajca, z faktu, e swko const zostao wprowadzone do jzyka, gdy ju by on w powszechnym uyciu. 2 Nie naley myli znaku null (czyli znaku o kodzie zero) ze wskanikiem null (czy te NULL).
1 Mona

132

ROZDZIA 18. NAPISY

Warto zaznaczy, e znak nowej linii (\n) jest w rny sposb przechowywany w rnych systemach operacyjnych. Wie si to z pewnymi historycznymi uwarunkowaniami. W niektrych systemach uywa si do tego jednego znaku o kodzie 0x0A (Line Feed nowa linia). Do tej rodziny zaliczamy systemy z rodziny Unix: Linux, *BSD, Mac OS X inne. Drug konwencj jest zapisywanie \n za pomoc dwch znakw: LF (Line Feed) + CR (Carriage return powrt karetki). Znak CR reprezentowany jest przez warto 0x0D. Kombinacji tych dwch znakw uywaj m.in.: CP/M, DOS, OS/2, Microsoft Windows. Trzecia grupa systemw uywa do tego celu samego znaku CR. S to systemy dziaajce na komputerach Commodore, Apple II oraz Mac OS do wersji 9. W zwizku z tym plik utworzony w systemie Linux moe wyglda dziwnie pod systemem Windows.

Operacje na acuchach
Porwnywanie acuchw
Napisy to tak naprawd wskaniki. Tak wic uywajc zwykego operatora porwnania ==, otrzymamy wynik porwnania adresw a nie tekstw. Do porwnywania dwch cigw znakw naley uy funkcji strcmp zadeklarowanej w pliku nagwkowym string.h. Jako argument przyjmuje ona dwa napisy i zwraca warto ujemn jeeli napis pierwszy jest mniejszy od drugiego, 0 jeeli napisy s rwne lub warto dodatni jeeli napis pierwszy jest wikszy od drugiego. Cigi znakw porwnywalne s leksykalnie kody znakw, czyli np. (przyjmujc kodowanie ASCII) a jest mniejsze od b, ale jest wiksze od B. Np.: #include <stdio.h> #include <string.h> 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 (cmp<0) { puts("Pierwszy napis jest mniejszy."); } else if (cmp>0) { puts("Pierwszy napis jest wiekszy."); } else { puts("Napisy sa takie same."); } return 0; } Czasami moemy chcie porwna tylko fragment napisu, np. sprawdzi czy zaczyna si od jakiego cigu. W takich sytuacjach pomocna jest funkcja strncmp. W porwnaniu do strcmp() przyjmuje ona jeszcze jeden argument oznaczajcy maksymaln liczb znakw do porwnania: #include <stdio.h> #include <string.h>

OPERACJE NA ACUCHACH

133

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; }

Kopiowanie napisw
Do kopiowania cigw znakw suy funkcja strcpy, ktra kopiuje drugi napis w miejsce pierwszego. Musimy pamita, by w pierwszym acuchu byo wystarczajco duo miejsca. char napis[100]; strcpy(napis, "Ala ma kota."); Znacznie bezpieczniej jest uywa funkcji strncpy, ktra kopiuje co najwyej tyle bajtw ile podano jako trzeci parametr. Uwaga! Jeeli drugi napis jest za dugi funkcja nie kopiuje znaku null na koniec pierwszego napisu, dlatego zawsze trzeba to robi rcznie: char napis[100]; strncpy(napis, "Ala ma kota.", sizeof napis - 1); napis[sizeof napis - 1] = 0;

czenie napisw
Do czenia napisw suy funkcja strcat, ktra kopiuje drugi napis do pierwszego. Ponownie jak w przypadku strcpy musimy zagwarantowa, by w pierwszym acuchu byo wystarczajco duo miejsca. #include <stdio.h> #include <string.h> int main(void) { char napis1[80] = "hello "; char *napis2 = "world"; strcat(napis1, napis2); puts(napis1); return 0; } I ponownie jak w przypadku strcpy istnieje funkcja strncat, ktra skopiuje co najwyej tyle bajtw ile podano jako trzeci argument i dodatkowo dopisze znak null. Przykadowo powyszy kod bezpieczniej zapisa jako: #include <stdio.h> #include <string.h>

134
int main(void) { char napis1[80] = "hello "; char *napis2 = "world"; strncat(napis1, napis2, sizeof napis1 - 1); puts(napis1); return 0; }

ROZDZIA 18. NAPISY

Osoby, ktre programoway w jzykach skryptowych musz bardzo uwaa na czenie i kopiowanie napisw. Kompilator jzyka C nie wykryje nadpisania pamici za zmienn acuchow i nie przydzieli dodatkowego obszaru pamici. Moe si zdarzy, e program pomimo nadpisywania pamici za acuchem bdzie nadal dziaa, co bardzo utrudni wykrywanie tego typu bdw!

Bezpieczestwo kodu a acuchy


Przepenienie bufora
O co waciwie chodzi z tymi funkcjami strncpy i strncat? Ot, niewinnie wygldajce acuchy mog okaza si zabjcze dla bezpieczestwa programu, a przez to nawet dla systemu, w ktrym ten program dziaa. Moe brzmi to strasznie, lecz jest to prawda. Moe pojawi si tutaj pytanie: w jaki sposb acuch moe zaszkodzi programowi?. Ot moe i to cakiem atwo. Przeanalizujmy nastpujcy kod: #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { char haslo_poprawne = 0; char haslo[16]; if (argc!=2) { fprintf(stderr, "uzycie: %s haslo", argv[0]); return EXIT_FAILURE; } strcpy(haslo, argv[1]); /* tutaj nastpuje przepenienie bufora */ if (!strcmp(haslo, "poprawne")) { haslo_poprawne = 1; } if (!haslo_poprawne) { fputs("Podales bledne haslo.\n", stderr); return EXIT_FAILURE; } puts("Witaj, wprowadziles poprawne haslo."); return EXIT_SUCCESS; } Jest to bardzo prosty program, ktry wykonuje jak akcj, jeeli podane jako pierwszy argument haso jest poprawne. Sprawdmy czy dziaa:

BEZPIECZESTWO KODU A ACUCHY


$ ./a.out niepoprawne Podales bledne haslo. $ ./a.out poprawne Witaj, wprowadziles poprawne haslo.

135

Jednak okazuje si, e z powodu uycia funkcji strcpy wamywacz nie musi zna hasa, aby program uzna, e zna haso, np.: $ ./a.out 11111111111111111111111111111111 Witaj, wprowadziles poprawne haslo. Co si stao? Podalimy cig jedynek duszy ni miejsce przewidziane na haso. Funkcja strcpy() kopiujc znaki z argv1 do tablicy (bufora) haslo przekroczya przewidziane dla niego miejsce i sza dalej gdzie znajdowaa si zmienna haslo poprawne. strcpy() kopiowaa znaki ju tam, gdzie znajdoway si inne dane midzy innymi wpisaa jedynk do haslo poprawne. Podany przykad moe si rnie zachowywa w zalenoci od kompilatora, jakim zosta skompilowany, i systemu, na jakim dziaa, ale oglnie mamy do czynienia z powanym niebezpieczestwem. Tak sytuacj nazywamy przepenieniem bufora. Moe umoliwi dostp do komputera osobom nieuprzywilejowanym. Naley wystrzega si tego typu konstrukcji, a w miejsce niebezpiecznej funkcji strcpy stosowa bardziej bezpieczn strncpy.

Oto bezpieczna wersja poprzedniego programu: #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { char haslo_poprawne = 0; char 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] = 0; if (!strcmp(haslo, "poprawne")) { haslo_poprawne = 1; } if (!haslo_poprawne) { fputs("Podales bledne haslo.\n", stderr); return EXIT_FAILURE; } puts("Witaj, wprowadziles poprawne haslo."); return EXIT_SUCCESS; }

136

ROZDZIA 18. NAPISY

Bezpiecznymi alternatywami do strcpy i strcat s te funkcje strlcpy oraz strlcat opracowana przez projekt OpenBSD i dostpna do cignicia: strlcpy, strlcat. strlcpy() dziaa podobnie do strncpy: strlcpy (buf, argv[1], sizeof buf);, jednak jest szybsza (nie wypenia pustego miejsca zerami) i zawsze koczy napis nullem (czego nie gwarantuje strncpy). strlcat(dst, src, size) dziaa natomiast jak strncat(dst, src, size-1). Do innych niebezpiecznych funkcji naley np. gets zamiast ktrej naley uywa fgets. Zawsze moemy te alokowa napisy dynamicznie: #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { char haslo_poprawne = 0; char *haslo; if (argc!=2) { fprintf(stderr, "uzycie: %s haslo", argv[0]); return EXIT_FAILURE; } haslo = malloc(strlen(argv[1]) + 1); /* +1 dla znaku null */ if (!haslo) { fputs("Za malo pamieci.\n", stderr); return EXIT_FAILURE; } strcpy(haslo, argv[1]); if (!strcmp(haslo, "poprawne")) { haslo_poprawne = 1; } if (!haslo_poprawne) { fputs("Podales bledne haslo.\n", stderr); return EXIT_FAILURE; } puts("Witaj, wprowadziles poprawne haslo."); free(haslo) return EXIT_SUCCESS; }

Naduycia z udziaem cigw formatujcych


Jednak to nie koniec kopotw z napisami. Wielu programistw, niewiadomych zagroenia czsto uywa tego typu konstrukcji: #include <stdio.h> int main (int argc, char *argv[]) { printf (argv[1]); } Z punktu widzenia bezpieczestwa jest to bardzo powany bd programu, ktry moe nie ze sob katastrofalne skutki! Prawidowo napisany kod powinien wyglda nastpujco:

KONWERSJE
#include <stdio.h> int main (int argc, char *argv[]) { printf ("%s", argv[1]); } lub: #include <stdio.h> int main (int argc, char *argv[]) { fputs (argv[1], stdout); }

137

rdo problemu ley w konstrukcji funkcji printf. Przyjmuje ona bowiem za pierwszy parametr acuch, ktry nastpnie przetwarza. Jeli w pierwszym parametrze wstawimy jak zmienn, to funkcja printf potraktuje j jako cig znakw razem ze znakami formatujcymi. Zatem wane, aby wczenie wyrobi sobie nawyk stosowania funkcji printf z co najmniej dwoma parametrami, nawet w przypadku wywietlenia samego tekstu.

Konwersje
Czasami zdarza si, e acuch mona interpretowa nie tylko jako cig znakw, lecz np. jako liczb. Jednak, aby dao si tak liczb przetworzy musimy skopiowa j do pewnej zmiennej. Aby uatwi programistom tego typu zamiany powsta zestaw funkcji bibliotecznych. Nale do nich: atol, strtol zamienia acuch na liczb cakowit typu long atoi zamienia acuch na liczb cakowit typu int atoll, strtoll zamienia acuch na liczb cakowit typu long long (64 bity); dodatkowo istnieje przestarzaa funkcja atoq bdca rozszerzeniem GNU, atof, strtod przeksztaca acuch na liczb typu double Oglnie rzecz ujmujc funkcje z serii ato* nie pozwalaj na wykrycie bdw przy konwersji i dlatego, gdy jest to potrzebne, naley stosowa funkcje strto*. Czasami przydaje si te konwersja w drug stron, tzn. z liczby na acuch. Do tego celu moe posuy funkcja sprintf lub snprintf. sprintf jest bardzo podobna do printf, tyle, e wyniki jej prac zwracane s do pewnego acucha, a nie wywietlane np. na ekranie monitora. Naley jednak uwaa przy jej uyciu (patrz Bezpieczestwo kodu a acuchy). snprintf (zdeniowana w nowszym standardzie) dodatkowo przyjmuje jako argument wielko bufora docelowego.

Operacje na znakach
Warto te powiedzie w tym miejscu o operacjach na samych znakach. Spjrzmy na poniszy program: #include <stdio.h> #include <ctype.h> #include <string.h> int main() { int znak;

138
while ((znak = getchar())!=EOF) { if( islower(znak) ) { znak = toupper(znak); } else if( isupper](znak) ) { znak = tolower(znak); } putchar(znak); } return 0; }

ROZDZIA 18. NAPISY

Program ten zmienia we wczytywanym tekcie wielkie litery na mae i odwrotnie. Wykorzystujemy funkcje operujce na znakach z pliku nagwkowego ctype.h. isupper sprawdza, czy znak jest wielk liter, natomiast toupper zmienia znak (o ile jest liter) na wielk liter. Analogicznie jest dla funkcji islower i tolower. Jako wiczenie, moesz tak zmodykowa program, eby odczytywa dane z pliku podanego jako argument lub wprowadzonego z klawiatury.

Czste bdy
pisanie do niezaalokowanego miejsca char *tekst; scanf("%s", tekst); zapominanie o koczcym napis nullu char test[4] = "test"; /* nie zmieci si null koczcy napis */ nieprawidowe porwnywanie acuchw

char tekst1[] = "jakis tekst"; char tekst2[] = "jakis tekst"; if( tekst1 == tekst2 ) { /* tu zawsze bdzie fasz bo == porwnuje adresy, naley uy strcmp() */ ... }

Zobacz w Wikipedii: Unicode

Unicode
W dzisiejszych czasach brak obsugi wielu jzykw praktycznie marginalizowaoby jzyk. Dlatego te C99 wprowadza moliwo zapisu znakw wg norm Unicode.

Jaki typ?
Do przechowywania znakw zakodowanych w Unicode powinno si korzysta z typu wchar t. Jego domylny rozmiar jest zaleny od uytego kompilatora, lecz w wikszoci zaktualizowanych kompilatorw powinny to by 2 bajty. Typ ten jest czci jzyka C++, natomiast w C znajduje si w pliku nagwkowym stddef.h. Alternatyw jest wykorzystanie gotowych bibliotek dla Unicode (wikszo jest dostpnych jedynie dla C++, nie wsppracuje z C), ktre czsto maj zdeniowane wasne typy, jednak zmuszeni jestemy wtedy do przejcia ze znanych nam ju funkcji jak np. strcpy, strcmp na funkcje dostarczane przez bibliotek, co jest do niewygodne. My zajmiemy si pierwszym wyjciem.

UNICODE

139

Jaki rozmiar i jakie kodowanie?


Unicode okrela jedynie jakiej liczbie odpowiada jaki znak, nie mwi za nic o sposobie dekodowania (tzn. jaka sekwencja znakw odpowiada jakiemu znaku/znakom). Jako e Unicode obejmuje 918 tys. znakw, zmienna zdolna pomieci go w caoci musi mie przynajmniej 3 bajty. Niestety procesory nie funkcjonuj na zmiennych o tym rozmiarze, pracuj jedynie na zmiennych o wielkociach: 1, 2, 4 oraz 8 bajtw (kolejne potgi liczby 2). Dlatego te jeli wci uparcie chcemy by dokadni i zastosowa przejrzyste kodowanie musimy skorzysta ze zmiennej 4-bajtowej (32 bity). Tak do sprawy podeszli twrcy kodowania Unicode nazwanego UTF-32/UCS-4. Ten typ kodowania po prostu przydziela kademu znakowi Unicode kolejne Zobacz w Wikipedii: UTFliczby. Jest to najbardziej intuicyjny i wygodny typ kodowania, ale jak wida cigi znakw 32 zakodowane w nim s bardzo obszerne, co zajmuje dostpn pami, spowalnia dziaanie programu oraz drastycznie pogarsza wydajno podczas transferu przez sie. Poza UTF-32 istnieje jeszcze wiele innych kodowa. Najpopularniejsze z nich to: UTF-8 od 1 do 6 bajtw (dla znakw poniej 65536 do 3 bajtw) na znak przez co jest skrajnie niewygodny, gdy chcemy przeprowadza jakiekolwiek operacje na tekcie bez korzystania z gotowych funkcji UTF-16 2 lub 4 bajty na znak; rczne modykacje acucha s bardziej skomplikowane ni przy UTF-32 UCS-2 2 bajty na znak przez co znaki z numerami powyej 65 535 nie s uwzgldnione; rwnie wygodny w uytkowaniu co UTF-32. Rczne operacje na cigach zakodowanych w UTF-8 i UTF-16 s utrudnione, poniewa w przeciwiestwie do UTF-32, gdzie mona okreli, i powiedzmy 2. znak cigu zajmuje bajty od 4. do 7. (gdy z gry wiemy, e 1. znak zaj bajty od 0. do 3.), w tych kodowaniach musimy najpierw okreli rozmiar 1. znaku. Ponadto, gdy korzystamy z nich nie dziaaj wtedy funkcje udostpniane przez biblioteki C do operowania na cigach znakw.

Priorytet may rozmiar atwa i wydajna edycja przenono oglna szybko

Proponowane kodowania UTF-8 UTF-32 lub UCS-2 UTF-83 UCS-2 lub UTF-8

Co naley zrobi, by zacz korzysta z kodowania UCS-2 (domylne kodowanie dla C): powinnimy korzysta z typu wchar t (ang. wide character), jednak jeli chcemy udostpnia kod rdowy programu do kompilacji na innych platformach, powinnimy ustawi odpowiednie parametry dla kompilatorw, by rozmiar by identyczny niezalenie od platformy. korzystamy z odpowiednikw funkcji operujcych na typie char pracujcych na wchar t (z reguy skadnia jest identyczna z t rnic, e w nazwach funkcji zastpujemy str na wcs np. strcpy wcscpy; strcmp wcscmp) jeli przyzwyczajeni jestemy do korzystania z klasy string, powinnimy zamiast niej korzysta z wstring, ktra posiada zblion skadni, ale pracuje na typie wchar t. Co naley zrobi, by zacz korzysta z Unicode: gdy korzystamy z kodowa innych ni UTF-16 i UCS-2, powinnimy zdeniowa wasny typ w wykorzystywanych przez nas bibliotekach podajemy typ wykorzystanego kodowania. gdy chcemy rcznie modykowa cig musimy przeczyta specykacj danego kodowania; s one wyczerpujco opisane na siostrzanym projekcie Wikibooks Wikipedii. Przykad uycia kodowania UCS-2:

140

ROZDZIA 18. NAPISY

#include <stddef.h> /* jeli uywamy C++, moemy opuci t linijk */ #include <stdio.h> #include <string.h> int main() { wchar_t* wcs1 = L"Ala ma kota."; wchar_t* wcs2 = L"Kot ma Ale."; wchar_t calosc[25]; wcscpy(calosc, wcs1); *(calosc + wcslen(wcs1)) = L ; wcscpy(calosc + wcslen(wcs1) + 1, wcs2); printf("lancuch wyjsciowy: %ls\n", calosc); return 0; }

Rozdzia 19

Typy zoone
typedef
Jest to sowo kluczowe, ktre suy do deniowania typw pochodnych np.: typedef stara_nazwa nowa_nazwa; typedef int mojInt; typedef int* WskNaInt; od tej pory mozna uywa typw mojInt i WskNaInt.

Typ wyliczeniowy
Suy do tworzenia zmiennych, ktre powinny przechowywa tylko pewne z gry ustalone wartoci: enum Nazwa {WARTOSC_1, WARTOSC_2, WARTOSC_N }; Na przykad mona w ten sposb stworzy zmienn przechowujc kierunek: enum Kierunek {W_GORE, W_DOL, W_LEWO, W_PRAWO}; enum Kierunek kierunek = W_GORE; ktr mona na przykad wykorzysta w instrukcji switch switch(kierunek) { case W_GORE: printf("w gr\n"); break; case W_DOL: printf("w d\n"); break; default: printf("gdzie w bok\n"); } Tradycyjnie przechowywane wielkoci zapisuje si wielkimi literami (W GORE, W DOL). Tak naprawd C przechowuje wartoci typu wyliczeniowego jako liczby cakowite, o czym mona si atwo przekona:

141

142
kierunek = W_DOL; printf("%i\n", kierunek); /* wypisze 1 */

ROZDZIA 19. TYPY ZOONE

Kolejne wartoci to po prostu liczby naturalne: domylnie pierwsza to zero, druga jeden itp. Moemy przy deklarowaniu typu wyliczeniowego zmieni domylne przyporzdkowanie: enum Kierunek { W_GORE, W_DOL = 8, W_LEWO, W_PRAWO }; printf("%i %i\n", W_DOL, W_LEWO); /* wypisze 8 9 */ Co wicej liczby mog si powtarza i wcale nie musz by ustawione w kolejnoci rosncej: enum Kierunek { W_GORE = 5, W_DOL = 5, W_LEWO = 2, W_PRAWO = 1 }; printf("%i %i\n", W_DOL, W_LEWO); /* wypisze 5 2 */ Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajn ich obsug, ale stwarza niebezpieczestwa mona przypisywa pod typ wyliczeniowy liczby, nawet nie majce odpowiednika w wartociach, a kompilator moe o tym nawet nie ostrzec: kierunek = 40;

Unie
S to twory deklarowane w nastpujcy sposb: union Nazwa { typ1 nazwa1; typ2 nazwa2; /* ... */ }; Na przykad: union LiczbaLubZnak { int calkowita; char znak; double rzeczywista; }; Pola w unii nakadaj si na siebie w ten sposb, e w danej chwili mona w niej przechowywa warto tylko jednego typu. Unia zajmuje w pamici tyle miejsca, ile zajmuje najwiksza z jej skadowych. W powyszym przypadku unia bdzie miaa prawdopodobnie rozmiar typu double czyli czsto 64 bity, a cakowita i znak bd wskazyway odpowiednio na pierwsze cztery bajty lub na pierwszy bajt unii (cho nie musi tak by zawsze). Do konkretnych wartoci pl unii odwoujemy si przy pomocy operatorem wyboru skadnika kropki: union LiczbaLubZnak liczba; liczba.calkowita = 10; printf("%d\n", liczba.calkowita); Zazwyczaj uycie unii ma na celu zmniejszenie zapotrzebowania na pami, gdy naraz bdzie wykorzystywane tylko jedno pole i jest czsto czone z uyciem struktur. yciowy przykad uycia zamieniamy kolejnosc bajtow w p.p1 np. w kodzie oprogramowania sieciowego Big Endian -> Little Endian, gdypotrzeba zmiany numeru portu podanego w nie sieciowej kolejnosci bajtw:

STRUKTURY

143

char t; union ZamianaKolejnosci { unsigned short p1; unsigned char p2[2]; }; union ZamianaKolejnosci p; p.p1=GetPortNr(); /* w wyniku czego z np. klawiatury w p.p1 bedzie 0x3412 */ /* Teraz zamienmy kolejnosc bajtow */ t=p.p2[1]; p.p2[1]=p.p2[0]; p.p2[0]=t; /*a teraz mozemy wyslac pakiet UDP np. w systemie Ethernut (port RTOS dla urzadzen wbudowanych) */ r=NutUdpSendTo(sock, ip, p.p1, "ALA MA KOTA", strlen("ALA MA KOTA")); Na koniec w zmiennej p.p1 mamy 0x1234.

Struktury
Struktury to specjalny typ danych mogcy przechowywa wiele wartoci w jednej zmiennej. Od tablic jednake rni si tym, i te wartoci mog by rnych typw. Struktury deniuje si podobnie jak unie, ale zamiast sowa kluczowego union uywa si struct, np.: struct Struktura { int pole1; int pole2; char pole3; }; Zmienn posiadajc struktur tworzy si podajc jako jej typ nazw struktury. struct Struktura zmiennaS; Dostp do poszczeglnych pl, tak samo jak w przypadku unii, uzyskuje si przy pomocy operatora kropki: zmiennaS.pole1 = 60; zmiennaS.pole2 = 2; zmiennaS.pole3 = a; /* przypisanie liczb do pl */ /* a teraz znaku */

Wsplne wasnoci typw wyliczeniowych, unii i struktur


Warto w zwrci uwag, e jzyk C++ przy deklaracji zmiennych typw wyliczeniowych, unii lub struktur nie wymaga przed nazw typu odpowiedniego sowa kluczowego. Na przykad poniszy kod jest poprawnym programem C++: enum Enum { A, B, C }; union Union { int a; float b; }; struct Struct { int a; float b; }; int main() { Enum e; Union u; Struct s;

144
e = A; u.a = 0; s.a = 0; return e + u.a + s.a; }

ROZDZIA 19. TYPY ZOONE

Nie jest to jednak poprawny kod C i naley o tym pamita szczeglnie jeeli uczysz si jzyka C korzystajc z kompilatora C++.

Naley rwnie pamita, e po klamrze zamykajcej denicje musi nastpowa rednik. Brak tego rednika jest czstym bdem powodujcym czasami niezrozumiae komunikaty bdw. Jedynym wyjtkiem jest natychmiastowa denicja zmiennych danego typu, na przykad: struct Struktura { int pole; } s1, s2, s3; Denicja typw wyliczeniowych, unii i struktur jest lokalna do bloku. To znaczy, moemy zdeniowa struktur wewntrz jednej z funkcji (czy wrcz wewntrz jakiego bloku funkcji) i tylko tam bdzie mona uywa tego typu. Czstym idiomem w C jest uycie typedef od razu z denicj typu, by unikn pisania enum, union czy struct przy deklaracji zmiennych danego typu. typedef struct struktura { int pole; } Struktura; Struktura s1; struct struktura s2; W tym przypadku zmienne s1 i s2 s tego samego typu. Moemy te zrezygnowa z nazywania samej struktury: typedef struct { int pole; } Struktura; Struktura s1;

Wskanik na uni i struktur


Podobnie, jak na kad inn zmienna, wskanik moe wskazywa take na uni lub struktur. Oto przykad: typedef struct { int p1, p2; } Struktura; int main () { Struktura Struktura wsk->p1 = wsk->p2 = return 0; }

s = { 0, 0 }; *wsk = &s; 2; 3;

Zapis wsk->p1 jest (z denicji) rwnowany (*wsk).p1, ale bardziej przejrzysty i powszechnie stosowany. Wyraenie wsk.p1 spowoduje bd kompilacji (struktur jest *wsk a nie wsk).

STUDIUM PRZYPADKU IMPLEMENTACJA LISTY WSKANIKOWEJ

145

Zobacz te
Powszechne praktyki konstruktory i destruktory

Pola bitowe
Struktury maj pewne dodatkowe moliwoci w stosunku do zmiennych. Mowa tutaj o rozmiarze elementu struktury. W przeciwiestwie do zmiennej moe on mie nawet 1 bit!. Aby mc zdeniowa tak zmienn musimy uy tzw. pola bitowego. Wyglda ono tak: struct moja { unsigned int a1:4, a2:8, a3:1, a4:3; };

/* /* /* /*

4 8 1 3

bity */ bitw (czsto 1 bajt) */ bit */ bity */

Wszystkie pola tej struktury maj w sumie rozmiar 16 bitw, jednak moemy odwoywa si do nich w taki sam sposb, jak do innych elementw struktury. W ten sposb efektywniej wykorzystujemy pami, jednak istniej pewne zjawiska, ktrych musimy by wiadomi przy stosowaniu pl bitowych. Wicej na ten temat w rozdziale przenono programw. Pola bitowe znalazy zastosowanie gwnie w implementacjach protokow sieciowych.

Studium przypadku implementacja listy wskanikowej


Zobacz w Wikipedii: Lista
Rozwamy teraz co, co kady z nas moe spotka w codziennym yciu. Kady z nas widzia kiedy jaki przykad listy (czy to zakupw, czy te list wierzycieli). Jzyk C te oferuje listy, jednak w programowaniu listy bd suyy do czego innego. Wyobramy sobie sytuacj, w ktrej jestemy autorami genialnego programu, ktry znajduje kolejne liczby pierwsze. Oczywicie kad kolejn liczb pierwsz moe wywietla na ekran, jednak z matematyki wiemy, e dana liczba jest liczb pierwsz, jeli nie dzieli si przez adn liczb pierwsz j poprzedzajc, mniejsz od pierwiastka z badanej liczby. U, mniej wicej chodzi o to, e moglibymy wykorzysta znalezione wczeniej liczby do przyspieszenia dziaania naszego programu. Jednak nasze liczby trzeba jako mdrze przechowa w pamici. Tablice maj ograniczenie musimy z gry zna ich rozmiar. Jeli zapenilibymy tablic, to przy znalezieniu kadej kolejnej liczby musielibymy: 1. przydziela nowy obszar pamici o rozmiarze poprzedniego rozmiaru + rozmiar zmiennej, przechowujcej nowo znalezion liczb 2. kopiowa zawarto starego obszaru do nowego 3. zwalnia stary, nieuywany obszar pamici 4. w ostatnim elemencie nowej tablicy zapisa znalezion liczb. C, troch tutaj roboty jest, a kopiowanie caej zawartoci jednego obszaru w drugi jest czasochonne. W takim przypadku moemy uy listy. Tworzc list moemy w prosty sposb przechowa nowo znalezione liczby. Przy uyciu listy nasze postpowanie ograniczy si do: 1. przydzielenia obszaru pamici, aby przechowa warto oblicze 2. doda do listy nowy element Prawda, e proste? Dodatkowo, lista zajmuje w pamici tylko tyle pamici, ile potrzeba na aktualn liczb elementw. Pusta tablica zajmuje natomiast tyle samo miejsca co pena tablica.

146

ROZDZIA 19. TYPY ZOONE

Implementacja listy
W jzyku C aby stworzy list musimy uy struktur. Dlaczego? Poniewa musimy przechowa co najmniej dwie wartoci: 1. pewn zmienn (np. liczb pierwsz z przykadu) 2. wskanik na kolejny element listy Przyjmijmy, e szukajc liczb pierwszych nie przekroczymy moliwoci typu unsigned long: typedef struct element { struct element *next; /* wskanik na kolejny element listy */ unsigned long val; /* przechowywana warto */ } el_listy; Zacznijmy zatem pisa nasz eksperymentalny program, do wyszukiwania liczb pierwszych. Pierwsz liczb pierwsz jest liczba 2 Pierwszym elementem naszej listy bdzie zatem struktura, ktra bdzie przechowywaa liczb 2. Na co bdzie wskazywao pole next? Poniewa na pocztku dziaania programu bdziemy mie tylko jeden element listy, pole next powinno wskazywa na NULL. Umwmy si zatem, e pole next ostatniego elementu listy bdzie wskazywao NULL po tym poznamy, e lista si skoczya. #include <stdio.h> #include <stdlib.h> typedef struct element { struct element *next; unsigned 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 = 1000; first = malloc (sizeof(el_listy)); first->val = 2; first->next = NULL; for (;i<=END;++i) { /* tutaj powinien znajdowa si kod, ktry sprawdza podzielno sprawdzanej liczby przez poprzednio znalezione liczby pierwsze oraz dodaje liczb do listy w przypadku stwierdzenia, e jest ona liczb pierwsz. */ } wypisz_liste(first); return 0; } Na pocztek zajmiemy si wypisywaniem listy. W tym celu bdziemy musieli odwiedzi kady element listy. Elementy listy s poczone polem next, aby przegldn list uyjemy nastpujcego algorytmu: 1. Ustaw wskanik roboczy na pierwszym elemencie listy 2. Jeli wskanik ma warto NULL, przerwij 3. Wypisz element wskazywany przez wskanik 4. Przesu wskanik na element, ktry jest wskazywany przez pole next

STUDIUM PRZYPADKU IMPLEMENTACJA LISTY WSKANIKOWEJ


5. Wr do punktu 2 void wypisz_liste(el_listy *lista) { el_listy *wsk=lista; /* 1 */ while( wsk != NULL ) /* 2 */ { printf ("%lu\n", wsk->val); /* 3 */ wsk = wsk->next; /* 4 */ } /* 5 */ }

147

Zastanwmy si teraz, jak powinien wyglda kod, ktry dodaje do listy nastpny element. Taka funkcja powinna: 1. znale ostatni element (tj. element, ktrego pole next == NULL) 2. przydzieli odpowiedni obszar pamici 3. skopiowa w pole val w nowo przydzielonym obszarze znalezion liczb pierwsz 4. nada polu next ostatniego elementu listy warto NULL 5. w pole next ostatniego elementu listy wpisa adres nowo przydzielonego obszaru Napiszmy zatem odpowiedni funkcj: void dodaj_do_listy (el_listy *lista, unsigned long liczba) { el_listy *wsk, *nowy; wsk = lista; while (wsk->next != NULL) /* 1 */ { wsk = wsk->next; /* przesuwamy wsk a znajdziemy ostatni element */ } nowy = malloc (sizeof(el_listy)); /* 2 */ nowy->val = liczba; /* 3 */ nowy->next = NULL; /* 4 */ wsk->next = nowy; /* 5 */ } I... to ju waciwie koniec naszej funkcji (warto zwrci uwag, e funkcja w tej wersji zakada, e na licie jest ju przynajmniej jeden element). Wstaw j do kodu przed funkcj main. Zosta nam jeszcze jeden problem: w ptli for musimy doda kod, ktry odpowiednio bdzie bada liczby oraz w przypadku stwierdzenia pierwszestwa liczby, bdzie dodawa j do listy. Ten kod powinien wyglda mniej wicej tak: int jest_pierwsza(el_listy *lista, int liczba) { el_listy *wsk; wsk = first; while (wsk != NULL) { if ((liczba % wsk->val)==0) return 0; /* jeli reszta z dzielenia liczby przez ktrkolwiek z poprzednio znalezionych liczb pierwszych jest rwna zero, to znaczy, e liczba ta nie jest liczb pierwsz */ wsk = wsk->next; } /* natomiast jeli sprawdzimy wszystkie poprzednio znalezione liczby

148

ROZDZIA 19. TYPY ZOONE

i adna z nich nie bdzie dzielia liczby i, moemy liczb i doda do listy liczb pierwszych */ return 1; } ... for (;i<=END;++i) { if (jest_pierwsza(first, i)) dodaj_do_listy (first,i); } Podsumujmy teraz efekty naszej pracy. Oto cay kod naszego programu: #include <stdio.h> #include <stdlib.h> typedef struct element { struct element *next; unsigned long val; } el_listy; el_listy *first; void dodaj_do_listy (el_listy *lista, unsigned long liczba) { el_listy *wsk, *nowy; wsk = lista; while (wsk->next != NULL) { wsk = wsk->next; /* przesuwamy wsk a znajdziemy ostatni element */ } nowy = malloc (sizeof(el_listy)); nowy->val = liczba; nowy->next = NULL; wsk->next = nowy; /* podczepiamy nowy element do ostatniego z listy */ } void wypisz_liste(el_listy *lista) { el_listy *wsk=lista; while( wsk != NULL ) { printf ("%lu\n", wsk->val); wsk = wsk->next; } } int jest_pierwsza(el_listy *lista, int liczba) { el_listy *wsk; wsk = first; while (wsk != NULL) { if ((liczba%wsk->val)==0) return 0; wsk = wsk->next; } return 1;

STUDIUM PRZYPADKU IMPLEMENTACJA LISTY WSKANIKOWEJ


}

149

int main () { unsigned long i = 3; /* szukamy liczb pierwszych w zakresie od 3 do 1000 */ const unsigned long END = 1000; first = malloc (sizeof(el_listy)); first->val = 2; first->next = NULL; for (;i!=END;++i) { if (jest_pierwsza(first, i)) dodaj_do_listy (first, i); } wypisz_liste(first); return 0; } Moemy jeszcze pomyle, jak mona by wykona usuwanie elementu z listy. Najprociej byoby zrobi: wsk->next = wsk->next->next ale wtedy element, na ktry wskazywa wczeniej wsk->next przestaje by dostpny i zamieca pami. Trzeba go usun. Zauwamy, e aby usun element potrzebujemy wskanika do elementu go poprzedzajcego (po to, by nie rozerwa listy). Popatrzmy na ponisz funkcj: void usun_z_listy(el_listy *lista, { el_listy *wsk=lista; while (wsk->next != NULL) { if (wsk->next->val == element) { el_listy *usuwany=wsk->next; wsk->next = usuwany->next; free(usuwany); } else { wsk = wsk->next; } } } int element)

/* musimy mie wskanik do elementu poprzedzajcego */ /* zapamitujemy usuwany element */ /* przestawiamy wskanik next by omija usuwany element */ /* usuwamy z pamici */

/* idziemy dalej tylko wtedy kiedy nie usuwalimy */ /* bo nie chcemy zostawi duplikatw */

Funkcja ta jest tak napisana, by usuwaa z listy wszystkie wystpienia danego elementu (w naszym programie nie ma to miejsca, ale lista jest zrobiona tak, e moe trzyma dowolne liczby). Zauwamy, e wskanik wsk jest przesuwany tylko wtedy, gdy nie kasowalimy. Gdybymy zawsze go przesuwali, przegapilibymy element gdyby wystpowa kilka razy pod rzd. Funkcja ta dziaa poprawnie tylko wtedy, gdy nie chcemy usuwa pierwszego elementu. Mona to poprawi dodajc instrukcj warunkow do funkcji lub dodajc do listy gow pierwszy element nie przechowujcy niczego, ale upraszczajcy operacje na licie. Zostawiamy to do samodzielnej pracy. Cay powyszy przykad omawia tylko jeden przypadek listy list jednokierunkow. Jednak istniej jeszcze inne typy list, np. lista jednokierunkowa cykliczna, lista dwukierunkowa oraz dwukierunkowa cykliczna. Rni si one od siebie tylko tym, e:

150

ROZDZIA 19. TYPY ZOONE


w przypadku list dwukierunkowych w strukturze el listy znajduje si jeszcze pole, ktre wskazuje na element poprzedni w przypadku list cyklicznych ostatni element wskazuje na pierwszy (nie rozrnia si wtedy elementu pierwszego, ani ostatniego)

Rozdzia 20

Biblioteki
Czym jest biblioteka
Biblioteka jest to zbir funkcji, ktre zostay wydzielone po to, aby dao si z nich korzysta w wielu programach. Uatwia to programowanie nie musimy np. sami tworzy funkcji printf. Kada biblioteka posiada swoje pliki nagwkowe, ktre zawieraj deklaracje funkcji bibliotecznych oraz czsto zawarte s w nich komentarze, jak uywa danej funkcji. W tej czci podrcznika nauczymy si tworzy nasze wasne biblioteki.

Jak zbudowana jest biblioteka


Kada biblioteka skada si z co najmniej dwch czci: pliku nagwkowego z deklaracjami funkcji (plik z rozszerzeniem .h) pliku rdowego, zawierajcego ciaa funkcji (plik z rozszerzeniem .c)

Budowa pliku nagwkowego


Oto najprostszy moliwy plik nagwkowy:

#ifndef PLIK_H #define PLIK_H /* tutaj s wpisane deklaracje funkcji */ #endif /* PLIK_H */ Zapewne zapytasz si na co komu instrukcje #ifndef, #define oraz #endif. Ot czsto si zdarza, e w programie korzystamy z plikw nagwkowych, ktre doczaj si wzajemnie. Oznaczaoby to, e w kodzie programu kilka razy pojawia by si zawarto tego samego pliku nagwkowego. Instrukcja #ifndef i #define temu zapobiega. Dziki temu kompilator nie musi kilkakrotnie kompilowa tego samego kodu. W plikach nagwkowych czsto umieszcza si te denicje typw, z ktrych korzysta biblioteka albo np. makr.

Budowa najprostszej biblioteki


Zamy, e nasza biblioteka bdzie zawieraa jedn funkcj, ktra wypisuje na ekran tekst pl.Wikibooks. Utwrzmy zatem nasz plik nagwkowy:

151

152
#ifndef WIKI_H #define WIKI_H void wiki (void); #endif

ROZDZIA 20. BIBLIOTEKI

Naley pamita, o podaniu void w licie argumentw funkcji nie przyjmujcych argumentw. O ile przy denicji funkcji nie trzeba tego robi (jak to czsto czynilimy w przypadku funkcji main) o tyle w prototypie brak swka void oznacza, e w prototypie nie ma informacji na temat tego jakie argumenty funkcja przyjmuje. Plik nagwkowy zapisujemy jako wiki.h. Teraz napiszmy ciao tej funkcji: #include "wiki.h" #include <stdio.h> void wiki (void) { printf ("pl.Wikibooks\n"); } Wane jest doczenie na pocztku pliku nagwkowego. Dlaczego? Plik nagwkowy zawiera deklaracje naszych funkcji jeli popenilimy bd i deklaracja nie zgadza si z denicj, kompilator od razu nas o tym powiadomi. Oprcz tego plik nagwkowy moe zawiera denicje istotnych typw lub makr. Zapiszmy nasz bibliotek jako plik wiki.c. Teraz naley j skompilowa. Robi si to troch inaczej, ni normalny program. Naley po prostu do opcji kompilatora gcc doda opcj -c: gcc wiki.c -c -o wiki.o Rozszerzenie .o jest domylnym rozszerzeniem dla bibliotek statycznych (typowych bibliotek czonych z reszt programu na etapie kompilacji). Teraz moemy spokojnie skorzysta z naszej nowej biblioteki. Napiszmy nasz program: #include "wiki.h" int main () { wiki(); return 0; } Zapiszmy program jako main.c Teraz musimy odpowiednio skompilowa nasz program: gcc main.c wiki.o -o main Uruchamiamy nasz program: ./main pl.Wikibooks Jak wida nasza pierwsza biblioteka dziaa. Zauwamy, e kompilatorowi podajemy i pliki z kodem rdowym (main.c) i pliki ze skompilowanymi bibliotekami (wiki.o) by uzyska plik wykonywalny (main). Jeli nie podalibymy plikw z bibliotekami, main.c co prawda skompilowaby si, ale bd zostaby zgoszony przez linker cz kompilatora odpowiedzialna za wstawienie w miejsce wywoa funkcji ich adresw (takiego adresu linker nie mgby znale).

JAK ZBUDOWANA JEST BIBLIOTEKA

153

Zmiana dostpu do funkcji i zmiennych (static i extern)


Jzyk C, w przeciwiestwie do swego modszego krewnego C++ nie posiada praktycznie adnych mechanizmw ochrony kodu biblioteki przed modykacjami. C++ ma w swoim asortymencie m.in. sterowanie uprawnieniami rnych elementw klasy. Jednak programista, piszcy program w C nie jest tak do koca bezradny. Autorzy C dali mu do rki dwa narzdzia: extern oraz static. Pierwsze z tych sw kluczowych informuje kompilator, e dana funkcja lub zmienna istnieje, ale w innym miejscu, i zostanie doczona do kodu programu w czasie czenia go z bibliotek. extern przydaje si, gdy zmienna lub funkcja jest zadeklarowana w bibliotece, ale nie jest udostpniona na zewntrz (nie pojawia si w pliku nagwkowym). Przykadowo: /* biblioteka.h */ extern char zmienna_dzielona[]; /* biblioteka.c */ #include "biblioteka.h" char zmienna_dzielona[] = "Zawartosc"; /* main.c */ #include <stdio.h> #include "biblioteka.h" int main() { printf("%s\n", zmienna_dzielona); return 0; } Gdybymy tu nie zastosowali extern, kompilator (nie linker) zaprotestowaby, e nie zna zmiennej zmienna dzielona. Prba dopisania deklaracji char zmienna dzielona; stworzyaby now zmienn i utracilibymy dostp do interesujcej nas zawartoci. Odwrotne dziaanie ma sowo kluczowe static uyte w tym kontekcie (uyte wewntrz bloku tworzy zmienn statyczn, wicej informacji w rozdziale Zmienne). Moe ono odnosi si zarwno do zmiennych jak i do funkcji globalnych. Powoduje, e dana zmienna lub funkcja jest niedostpna na zewntrz biblioteki1 . Moemy dziki temu ukry np. funkcje, ktre uywane s przez sam bibliotek, by nie dao si ich wykorzysta przez extern.

1 Tak naprawd cae ukrycie funkcji polega na zmianie niektrych danych w pliku z kodem binarnym danej biblioteki (pliku .o), przez co linker powoduje wygenerowanie komunikatu o bdzie w czasie czenia biblioteki z programem.

154

ROZDZIA 20. BIBLIOTEKI

Rozdzia 21

Wicej o kompilowaniu
Ciekawe opcje kompilatora GCC
E powoduje wygenerowanie kodu programu ze zmianami, wprowadzonymi przez preprocesor S zamiana kodu w jzyku C na kod asemblera (komenda: gcc -S plik.c spowoduje utworzenie pliku o nazwie plik.s, w ktrym znajdzie si kod asemblera) c kompilacja bez czenia z bibliotekami Ikatalog ustawienie domylnego katalogu z plikami nagwkowymi na katalog lbiblioteka wymusza czenie programu z podan bibliotek (np. -lGL)

Program make
Do czsto moe si zdarzy, e nasz program skada si z kilku plikw rdowych. Jeli tych plikw jest mao (np. 3-5) moemy jeszcze prbowa rcznie kompilowa kady z nich. Jednak jeli tych plikw jest duo, lub chcemy pokaza nasz program innym uytkownikom musimy stworzy elegancki sposb kompilacji naszego programu. Wanie po to, aby zautomatyzowa proces kompilacji powsta program make. Program make analizuje pliki Makele i na ich podstawie wykonuje okrelone czynnoci.

Budowa pliku Makele


Uwaga: poniej zosta umwiony Makele dla GNU Make. Istniej inne programy make i mog uywa innej skadni. Na Wikibooks zosta te obszernie opisany program make rmy Borland. Najwaniejszym elementem pliku Makele s zalenoci oraz reguy przetwarzania. Zalenoci polegaj na tym, e np. jeli nasz program ma by zbudowany z 4 plikw, to najpierw naley skompilowa kady z tych 4 plikw, a dopiero pniej poczy je w jeden cay program. Zatem zalenoci okrelaj kolejno wykonywanych czynnoci. Natomiast reguy okrelaj jak skompilowa dany plik. Zalenoci tworzy si tak: co: od_czego reguy... Dziki temu program make zna ju kolejno wykonywanych dziaa oraz czynnoci, jakie ma wykona. Aby zbudowa co naley wykona polecenie: make co. Pierwsza regua w pliku Makele jest regu domyln. Jeli wydamy polecenie make bez parametrw, zostanie

155

156

ROZDZIA 21. WICEJ O KOMPILOWANIU

zbudowana wanie regua domylna. Tak wic dobrze jest jako pierwsz regu wstawi regu budujc kocowy plik wykonywalny; zwyczajowo regu t nazywa si all. Naley pamita, by sekcji co nie wcina, natomiast reguy wcina dwoma spacjami. Cz od czego moe by pusta. Plik Makele umoliwia te deniowanie pewnych zmiennych. Nie trzeba tutaj si ju troszczy o typ zmiennej, wystarczy napisa: nazwa_zmiennej = warto W ten sposb moemy zadeklarowa dowolnie duo zmiennych. Zmienne mog by rne nazwa kompilatora, jego parametry i wiele innych. Zmiennej uywamy w nastpujcy sposb: $(nazwa zmiennej). Komentarze w pliku Makele tworzymy zaczynajc lini od znaku hash (#).

Przykadowy plik Makele


Do tej teorii, teraz zajmiemy si dziaajcym przykadem. Zamy, e nasz przykadowy program nazywa si test oraz skada si z czterech plikw: pierwszy.c drugi.c trzeci.c czwarty.c Odpowiedni plik Makele powinien wyglda mniej wicej tak: # Mj plik makefile - wpisz make all aby skompilowa cay program CC = gcc all: pierwszy.o drugi.o trzeci.o czwarty.o $(CC) pierwszy.o drugi.o trzeci.o czwarty.o -o test pierwszy.o: pierwszy.c $(CC) pierwszy.c -c -o pierwszy.o drugi.o: drugi.c $(CC) drugi.c -c -o drugi.o trzeci.o: trzeci.c $(CC) trzeci.c -c -o trzeci.o czwarty.o: czwarty.c $(CC) czwarty.c -c -o czwarty.o Widzimy, e nasz program zaley od 4 plikw z rozszerzeniem .o (pierwszy.o itd.), potem kady z tych plikw zaley od plikw .c, ktre program make skompiluje w pierwszej kolejnoci, a nastpnie poczy w jeden program (test). Nazw kompilatora zapisalimy jako zmienn, poniewa powtarza si i zmienna jest sposobem, by zmieni j wszdzie za jednym zamachem. Zatem jak wida uywanie pliku Makele jest bardzo proste. Warto na koniec naszego przykadu doda regu, ktra wyczyci katalog z plikw .o: clean: rm -f *.o test Ta regua spowoduje usunicie wszystkich plikw .o oraz naszego programu jeli napiszemy make clean. Moemy te ukry wykonywane komendy albo dopisa wasny opis czynnoci: clean: @echo Usuwam gotowe pliki @rm -f *.o test

OPTYMALIZACJE
Ten sam plik Makele mgby wyglda inaczej: CFLAGS = -g -O # tutaj mona dodawa inne flagi kompilatora LIBS = -lm # tutaj mona dodawa biblioteki OBJ =\ pierwszy.o \ drugi.o \ trzeci.o \ czwarty.o all: main clean: rm -f *.o test .c.o: $(CC) -c $(INCLUDES) $(CFLAGS) $< main: $(OBJ) $(CC) $(OBJ) $(LIBS) -o test

157

Tak naprawd jest to dopiero bardzo podstawowe wprowadzenie do uywania programu make, jednak jest ono wystarczajce, by zacz z niego korzysta. Wyczerpujce omwienie caego programu niestety przekracza zakres tego podrcznika.

Optymalizacje
Kompilator GCC umoliwia generacj kodu zoptymalizowanego dla konkretnej architektury. Su do tego opcje -march= i -mtune=. Stopie optymalizacji ustalamy za pomoc opcji -Ox, gdzie x jest numerem stopnia optymalizacji (od 1 do 3). Moliwe jest te uycie opcji -Os, ktra powoduje generowanie kodu o jak najmniejszym rozmiarze. Aby skompilowa dany plik z optymalizacjami dla procesora Athlon XP, naley napisa tak: gcc program.c -o program -march=athlon-xp -O3 Z optymalizacjami naley uwaa, gdy czsto zdarza si, e kod skompilowany bez optymalizacji dziaa zupenie inaczej, ni ten, ktry zosta skompilowany z optymalizacjami.

Wyrwnywanie
Wyrwnywanie jest pewnym zjawiskiem, na ktre w bardzo wielu podrcznikach, mwicych o C w ogle si nie wspomina. Ten rozdzia ma za zadanie wyjanienie tego zjawiska oraz uprzedzenie programisty o pewnych faktach, ktre w pniejszej jego twrczoci mog zminimalizowa czas na znalezienie pewnych informacji, ktre mog wpywa na to, e jego program nie bdzie dziaa poprawnie.

Czsto zdarza si, e kompilator w ramach optymalizacji wyrwnuje elementy struktury tak, aby procesor mg atwiej odczyta i przetworzy dane. Przyjrzyjmy si bliej nastpujcemu fragmentowi kodu: typedef struct { unsigned char wiek; /* 8 bitw */ unsigned short dochod; /* 16 bitw */ unsigned char plec; /* 8 bitw */ } nasza_str;

158

ROZDZIA 21. WICEJ O KOMPILOWANIU

Aby procesor mg atwiej przetworzy dane kompilator moe doda do tej struktury jedno, omiobitowe pole. Wtedy struktura bdzie wygldaa tak: typedef struct { unsigned char wiek; /*8 bitw */ unsigned char fill[1]; /* 8 bitw */ unsigned short dochod; /* 16 bitw */ unsigned char plec; /* 8 bitw */ } nasza_str; Wtedy rozmiar zmiennych przechowujcych wiek, pe, oraz dochd bdzie wynosi 64 bity bdzie zatem potg liczby dwa i procesorowi duo atwiej bdzie tak uoon struktur przechowywa w pamici cache. Jednak taka sytuacja nie zawsze jest podana. Aby jej zapobiec w kompilatorze GNU GCC moemy uy takiej oto linijki: __attribute__ ((packed)) Dziki uyciu tego atrybutu, kompilator zostanie zmuszony do braku ingerencji w nasz struktur. Jest jednak jeszcze jeden, by moe bardziej elegancki sposb na obejcie dopeniania. Zauwaye, e dopenienie, dodane przez kompilator pojawio si midzy polem o dugoci 8 bitw (plec) oraz polem o dugoci 32 bitw (dochod). Wyrwnywanie polega na tym, e dana zmienna powinna by umieszczona pod adresem bdcym wielokrotnoci jej rozmiaru. Oznacza to, e jeli np. mamy w strukturze na pocztku dwie zmienne, o rozmiarze jednego bajta, a potem jedn zmienn, o rozmiarze 4 bajtw, to pomidzy polami o rozmiarze 2 bajtw, a polem czterobajtowym pojawi si dwubajtowe dopenienie. Moe Ci si wydawa, e jest to tylko niepotrzebne mcenie w gowie, jednak niektre architektury (zwaszcza typu RISC) mog nie wykona kodu, ktry nie zosta wyrwnany. Dlatego, nasz struktur powinnimy zapisa mniej wicej tak: typedef struct { unsigned short dochod; /* 16 bitw */ unsigned char wiek; /* 8 bitw */ unsigned char plec; /* 8 bitw */ } nasza_str; W ten sposb wyrwnana struktura nie bdzie podlegaa modykacjom przez kompilator oraz bdzie przenona pomidzy rnymi kompilatorami. Wyrwnywanie dziaa take na pojedynczych zmiennych w programie, jednak ten problem nie powoduje tyle zamieszania, co ingerencja kompilatora w ukad pl struktury. Wyrwnywanie zmiennych polega tylko na tym, e kompilator umieszcza je pod adresami, ktre s wielokrotnoci ich rozmiaru

Kompilacja skrona
Majc w domu dwa komputery, o odmiennych architekturach (np. i386 oraz Sparc) moemy potrzebowa stworzy program dla jednej maszyny, majc do dyspozycji tylko drugi komputer. Nie musimy wtedy lata do znajomego, posiadajcego odpowiedni sprzt. Moemy skorzysta z tzw. kompilacji skronej (ang. cross-compile). Polega ona na tym, e program nie jest kompilowany pod procesor, na ktrym dziaa kompilator, lecz na inn, zdeniowan wczeniej maszyn. Efekt bdzie taki sam, a skompilowany program moemy bez problemu uruchomi na drugim komputerze.

INNE NARZDZIA

159

Inne narzdzia
Wrd przydatnych narzdzi, warto wymieni rwnie program objdump (zarwno pod Unix jak i pod Windows) oraz readelf (tylko Unix). Objdump suy do deasemblacji i analizy skompilowanych programw. Readelf suy do analizy pliku wykonywalnego w formacie ELF (uywanego w wikszoci systemw z rodziny Unix). Wicej informacji moesz uzyska, piszc (w systemach Unix): man 1 objdump man 1 readelf

160

ROZDZIA 21. WICEJ O KOMPILOWANIU

Rozdzia 22

Zaawansowane operacje matematyczne


Biblioteka matematyczna
Aby mc korzysta z wszystkich dobrodziejstw funkcji matematycznych musimy na pocztku doczy plik math.h: #include <math.h> A w procesie kompilacji (dotyczy kompilatora GCC) musimy niekiedy doda ag -lm: gcc plik.c -o plik -lm Funkcje matematyczne, ktre znajduj si w bibliotece standardowej moesz znale tutaj. Przy korzystaniu z nich musisz wzi pod uwag m.in. to, e biblioteka matematyczna prowadzi kalkulacj w oparciu o radiany a nie stopnie.

Stae matematyczne
W pliku math.h zdeniowane s pewne stae, ktre mog by przydatne do oblicze. S to m.in.: M E podstawa logarytmu naturalnego M LOG2E logarytm o podstawie 2 z liczby e M LOG10E logarytm o podstawie 10 z liczby e M LN2 logarytm naturalny z liczby 2 M LN10 logarytm naturalny z liczby 10 M PI liczba M PI 2 liczba /2 M PI 4 liczba /4 M 1 PI liczba 1/ M 2 PI liczba 2/

161

162

ROZDZIA 22. ZAAWANSOWANE OPERACJE MATEMATYCZNE

Prezentacja liczb rzeczywistych w pamici komputera


By moe ten temat moe wyda Ci si niepotrzebnym, lecz w wielu ksikach nie ma w ogle tego tematu. Dziki niemu zrozumiesz, jak komputer radzi sobie z przecinkiem oraz dlaczego niektre obliczenia daj niezbyt dokadne wyniki. Na pocztek troch teorii: do przechowywania liczb rzeczywistych przeznaczone s 3 typy: float, double oraz long double. Zajmuj one odpowiednio 32, 64 oraz 80 bitw. Wiemy te, e komputer nie ma zycznej moliwoci zapisania przecinka. Sprbujmy teraz zapisa jak liczb wymiern w formie liczb binarnych. Nasza liczba to powiedzmy 4.25. Sprbujmy j rozbi na sum potg dwjki: 4 = 1*22 + 0*21 +0*20 . Dobra rozpisalimy liczb 4, ale co z czci dziesitn? Skorzystajmy z zasad matematyki 0.25 = 22 . Zatem nasza liczba powinna wyglda tak: 100.01 Poniewa komputer nie jest w stanie przechowa pozycji przecinka, kto wpad na prosty ale sprytny pomys ustawienia przecinka jak najbliej pocztku liczby i tylko mnoenia jej przez odpowiedni potg dwjki. Taki sposb przechowywania liczb nazywamy zmiennoprzecinkowym, a proces przeksztacania naszej liczby z postaci czytelnej przez czowieka na format zmiennoprzecinkowy nazywamy normalizacj. Wrmy do naszej liczby 4.25. W postaci binarnej wyglda ona tak: 100.01, natomiast po normalizacji bdzie wygldaa tak: 1.0001*22 . W ten sposb w pamici komputera znajd si dwie informacje: liczba zakodowana w pamici z wirtualnym przecinkiem oraz numer potgi dwjki. Te dwie informacje wystarczaj do przechowania wartoci liczby. Jednak pojawia si inny problem co si stanie, jeli np. bdziemy chcieli przeoy liczb typu 1 ? Ot tutaj wychodz na wierzch pewne nie3 docignicia komputera w dziedzinie samej matematyki. 1/3 daje w rozwiniciu dziesitnym 0.(3). Jak zatem zapisa tak liczb? Ot nie moemy przechowa caego jej rozwinicia (wynika to z ogranicze typu danych ma on niestety skoczon liczb bitw). Dlatego przechowuje si tylko pewne przyblienie liczby. Jest ono tym bardziej dokadne im dany typ ma wicej bitw. Zatem do oblicze wymagajcych dokadnych danych powinnimy uy typu double lub long double. Na szczcie w wikszoci przecitnych programw tego typu problemy zwykle nie wystpuj. A poniewa pocztkujcy programista nie odpowiada za tworzenie programw sterujcych np. lotem statku kosmicznego, wic drobne przekamania na odlegych miejscach po przecinku nie stanowi wikszego problemu.

Liczby zespolone
Operacje na liczbach zespolonych s czci uaktualnionego standardu jzyka C o nazwie C99, ktry jest obsugiwany jedynie przez cz kompilatorw Podane tutaj informacje zostay sprawdzone na systemie Gentoo Linux z bibliotek GNU libc w wersji 2.3.5 i kompilatorem GCC w wersji 4.0.2

Dotychczas korzystalimy tylko z liczb rzeczywistych, lecz najnowsze standardy jzyka C umoliwiaj korzystanie take z innych liczb np. z liczb zespolonych. Aby mc korzysta z liczb zespolonych w naszym programie naley w nagwku programu umieci nastpujc linijk: #include <complex.h> Wiemy, e liczba zespolona zdeklarowana jest nastpujco: z = a+b*i, gdzie a, b s liczbami rzeczywistymi, a i*i=(-1). W pliku complex.h liczba i zdeniowana jest jako I. Zatem wyprbujmy moliwoci liczb zespolonych:

LICZBY ZESPOLONE
#include <math.h> #include <complex.h> #include <stdio.h> int main () { float _Complex z = 4+2.5*I; printf ("Liczba z: %f+%fi\n", creal(z), cimag (z)); return 0; } nastpnie kompilujemy nasz program: gcc plik1.c -o plik1 -lm Po wykonaniu naszego programu powinnimy otrzyma: Liczba z: 4.00+2.50i W programie zamieszczonym powyej uylimy dwch funkcji creal i cimag. creal zwraca cz rzeczywist liczby zespolonej cimag zwraca cz urojon liczby zespolonej

163

164

ROZDZIA 22. ZAAWANSOWANE OPERACJE MATEMATYCZNE

Rozdzia 23

Powszechne praktyki
Rozdzia ten ma za zadanie pokaza powszechnie stosowane metody programowania w C. Nie bdziemy tu uczy, jak naley stawia nawiasy klamrowe ani ktry sposb nazewnictwa zmiennych jest najlepszy prowadzone s o to spory, z ktrych niewiele wynika. Zaprezentowane tu rozwizania maj konkretny wpyw na jako tworzonych programw.

Konstruktory i destruktory
W wikszoci obiektowych jzykw programowania obiekty nie mog by tworzone bezporednio obiekty otrzymuje si wywoujc specjaln metod danej klasy, zwan konstruktorem. Konstruktory s wane, poniewa pozwalaj zapewni obiektowi odpowiedni stan pocztkowy. Destruktory, wywoywane na kocu czasu ycia obiektu, s istotne, gdy obiekt ma wyczny dostp do pewnych zasobw i konieczne jest upewnienie si, czy te zasoby zostan zwolnione. Poniewa C nie jest jzykiem obiektowym, nie ma wbudowanego wsparcia dla konstruktorw i destruktorw. Czsto programici bezporednio modykuj tworzone obiekty i struktury. Jednake prowadzi to do potencjalnych bdw, poniewa operacje na obiekcie mog si nie powie lub zachowa si nieprzewidywalnie, jeli obiekt nie zosta prawidowo zainicjalizowany. Lepszym podejciem jest stworzenie funkcji, ktra tworzy instancj obiektu, ewentualnie przyjmujc pewne parametry: struct string { size_t size; char *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->size = strlen(initial); new_string->data = strdup(initial); } return new_string; } Podobnie, bezporednie usuwanie obiektw moe nie do koca si uda, prowadzc do wycieku zasobw. Lepiej jest uy destruktora:

165

166

ROZDZIA 23. POWSZECHNE PRAKTYKI

void free_string(struct string *s) { assert (s != NULL); free(s->data); /* zwalniamy pami zajmowan przez struktur */ free(s); /* usuwamy sam struktur */ } Czsto czy si destruktory z zerowaniem zwolnionych wskanikw. Czasami dobrze jest ukry denicj obiektu, eby mie pewno, e uytkownicy nie utworz go rcznie. Aby to zapewni struktura jest deniowana w pliku rdowym (lub prywatnym nagwku niedostpnym dla uytkownikw) zamiast w pliku nagwkowym, a deklaracja wyprzedzajca jest umieszczona w pliku nagwkowym: struct string; struct string *create_string(const char *initial); void free_string(struct string *s);

Zerowanie zwolnionych wskanikw


Jak powiedziano ju wczeniej, po wywoaniu free() dla wskanika, staje si on wiszcym wskanikiem. Co gorsze, wikszo nowoczesnych platform nie potra wykry, kiedy taki wskanik jest uywany zanim zostanie ponownie przypisany. Jednym z prostych rozwiza tego problemu jest zapewnienie, e kady wskanik jest zerowany natychmiast po zwolnieniu: free(p); p = NULL; Inaczej ni w przypadku wiszcych wskanikw, na wielu nowoczesnych architekturach przy prbie uycia wyzerowanego wskanika pojawi si sprztowy wyjtek. Dodatkowo, programy mog zawiera sprawdzanie bdw dla zerowych wartoci, ale nie dla wiszcych wskanikw. Aby zapewni, e jest to wykonywane dla kadego wskanika, moemy uy makra: #define FREE(p) do { free(p); (p) = NULL; } while(0)

(aby zobaczy, dlaczego makro jest napisane w ten sposb, zobacz Konwencje pisania makr) Przy wykorzystaniu tej techniki destruktory powinny zerowa wskanik, ktry przekazuje si do nich, wic argument musi by do nich przekazywany przez referencj. Na przykad, oto zaktualizowany destruktor z sekcji Konstruktory i destruktory: void free_string(struct { assert(s != NULL && FREE((*s)->data); /* FREE(*s); /* } string **s) *s != NULL); zwalniamy pami zajmowan przez struktur */ usuwamy struktur */

Niestety, ten idiom nie jest wstanie pomc w wypadku wskazywania przez inne wskaniki zwolnionej pamici. Z tego powodu niektrzy eksperci C uwaaj go za niebezpieczny, jako kreujcy faszywe poczucie bezpieczestwa.

KONWENCJE PISANIA MAKR

167

Konwencje pisania makr


Poniewa makra preprocesora dziaaj na zasadzie zwykego zastpowania napisw, s podatne na wiele kopotliwych bdw, z ktrych czci mona unikn przez stosowanie si do poniszych regu: 1. Umieszczaj nawiasy dookoa argumentw makra kiedy to tylko moliwe. Zapewnia to, e gdy s wyraeniami kolejno dziaa nie zostanie zmieniona. Na przykad: le: #define kwadrat(x) (x*x) Dobrze: #define kwadrat(x) ( (x)*(x) ) Przykad: Zamy, e w programie makro kwadrat() zdeniowane bez nawiasw zostao wywoane nastpujco: kwadrat(a+b). Wtedy zostanie ono zamienione przez preprocesor na: a+b*a+b. Z kolejnoci dziaa wiemy, e najpierw zostanie wykonane mnoenie, wic warto wyraenia kwadrat(a+b) bdzie rna od kwadratu wyraenia a+b. 2. Umieszczaj nawiasy dookoa caego makra, jeli jest pojedynczym wyraeniem. Ponownie, chroni to przed zaburzeniem kolejnoci dziaa. le: #define kwadrat(x) (x)*(x) Dobrze: #define kwadrat(x) ( (x)*(x) ) Przykad: Deniujemy makro #define suma(a, b) (a)+(b) i wywoujemy je w kodzie wynik = suma(3, 4) * 5. Makro zostanie rozwinite jako wynik = 3+4*5, co z powodu kolejnoci dziaa da wynik inny ni podany. 3. Jeli makro skada si z wielu instrukcji lub deklaruje zmienne, powinno by umieszczone w ptli do { ... } while(0), bez koczcego rednika. Pozwala to na uycie makra jak pojedynczej instrukcji w kadym miejscu, jak ciao innego wyraenia, pozwalajc jednoczenie na umieszczenie rednika po makrze bez tworzenia zerowego wyraenia. Naley uwaa, by zmienne w makrze potencjalnie nie kolidoway z argumentami makra. le: #define FREE(p) free(p); p = NULL; Dobrze: #define FREE(p) do { free(p); p = NULL; } while(0) 4. Unikaj uywania argumentw makra wicej ni raz wewntrz makra. Moe to spowodowa kopoty, gdy argument makra ma efekty uboczne (np. zawiera operator inkrementacji). Przykad: #define kwadrat(x) ((x)*(x)) nie powinno by wywoywane z operatorem inkrementacji kwadrat(a++) poniewa zostanie to rozwinite jako ((a++) * (a++)), co jest niezgodne ze specykacj jzyka i zachowanie takiego wyraenia jest niezdeniowane (dwukrotna inkrementacja w tym samym wyraeniu). 5. Jeli makro moe by w przyszoci zastpione przez funkcj, rozwa uycie w nazwie maych liter, jak w funkcji.

Jak dosta si do konkretnego bitu?


Wiemy, e komputer to maszyna, ktrej najmniejsz jednostk pamici jest bit, jednak w C najmniejsza zmienna ma rozmiar 8 bitw (czyli jednego bajtu). Jak zatem mona odczyta warto pojedynczych bitw? W bardzo prosty sposb w zestawie operatorw jzyka C znajduj si tzw. operatory bitowe. S to m. in.: & logiczne i logiczne lub

168
logiczne nie

ROZDZIA 23. POWSZECHNE PRAKTYKI

Oprcz tego s take przesunicia ( oraz ). Zastanwmy si teraz, jak je wykorzysta w praktyce. Zamy, e zajmujemy si jednobajtow zmienn. unsigned char i = 2; Z matematyki wiemy, e zapis binarny tej liczby wyglda tak (w omiobitowej zmiennej): 00000010. Jeli teraz np. chcielibymy zapali drugi bit od lewej (tj. bit, ktrego zapalenie niejako doda do liczby warto 26 ) powinnimy uy logicznego lub: unsigned char i = 2; i |= 64; Gdzie 64=26 . Odczytywanie wykonuje si za pomoc tzw. maski bitowej. Polega to na: 1. wyzerowaniu bitw, ktre s nam w danej chwili niepotrzebne 2. odpowiedniemu przesuniciu bitw, dziki czemu szukany bit znajdzie si na pozycji pierwszego bitu od prawej Do wyuskania odpowiedniego bitu moemy posuy si operacj i czyli operatorem &. Wyglda to analogicznie do posugiwania si operatorem lub: unsigned char i = 3; /* bitowo: 00000011 */ unsigned char temp = 0; temp = i & 1; /* sprawdzamy najmniej znaczcy bit - czyli pierwszy z prawej */ if (temp) { printf ("bit zapalony"); else { printf ("bit zgaszony"); } Jeli nie wadasz biegle kodem binarnym, tworzenie masek bitowych uatwi ci przesunicia bitowe. Aby uzyska liczb ktra ma zapalony bit o numerze n (bity s liczone od zera), przesuwamy bitowo w lewo jedynk o n pozycji: 1 << n Jeli chcemy uzyska liczb, w ktrej zapalone s bity na pozycjach l, m, n uywamy sumy logicznej (lub): (1 << l) | (1 << m) | (1 << n) Jeli z kolei chcemy uzyska liczb gdzie zapalone s wszystkie bity poza n, odwracamy j za pomoc operatora logicznej negacji ~(1 << n) Warto wada biegle operacjami na bitach, ale pocztkujcy mog (po uprzednim przeanalizowaniu) zdeniowa nastpujce makra i ich uywa: /* Sprawdzenie czy w liczbie k jest zapalony bit n */ #define IS_BIT_SET(k, n) ((k) & (1 << (n))) /* Zapalenie bitu n w zmiennej k */ #define SET_BIT(k, n) (k |= (1 << (n))) /* Zgaszenie bitu n w zmiennej k */ #define RESET_BIT(k, n) (k &= ~(1 << (n)))

SKRTY NOTACJI

169

Skrty notacji
Istniej pewne sposoby ograniczenia iloci niepotrzebnego kodu. Przykadem moe by wykonywanie jednej operacji w razie wystpienia jakiego warunku, np. zamiast pisa: if (warunek) { printf ("Warunek prawdziwy\n"); } moesz skrci notacj do: if (warunek) printf ("Warunek prawdziwy\n"); Podobnie jest w przypadku ptli for: for (;warunek;) printf ("Wywietlam si w ptli!\n"); Niestety ograniczeniem w tym wypadku jest to, e mona w ten sposb zapisa tylko jedn instrukcj.

170

ROZDZIA 23. POWSZECHNE PRAKTYKI

Rozdzia 24

Przenono programw
Jak dowiedziae si z poprzednich rozdziaw tego podrcznika, jzyk C umoliwia tworzenie programw, ktre mog by uruchamiane na rnych platformach sprztowych pod warunkiem ich powtrnej kompilacji. Jzyk C naley do grupy jzykw wysokiego poziomu, ktre tumaczone s do poziomu kodu maszynowego (tzn. kod rdowy jest kompilowany). Z jednej strony jest to korzystne posunicie, gdy programy s szybsze i mniejsze ni programy napisane w jzykach interpretowanych (takich, w ktrych kod rdowy nie jest kompilowany do kodu maszynowego, tylko na bieco interpretowany przez tzw. interpreter). Jednak istnieje take druga strona medalu pewne zawioci sprztu, ktre ograniczaj przenono programw. Ten rozdzia ma wyjani Ci mechanizmy dziaania sprztu w taki sposb, aby bez problemu mg tworzy poprawne i cakowicie przenone programy.

Niezdeniowane zachowanie i zachowanie zalene od implementacji


W trakcie czytania kolejnych rozdziaw mona byo si natkn na zwroty takie jak zachowanie niezdeniowane (ang. undened behaviour ) czy zachowanie zalene od implementacji (ang. implementation-dened behaviour ). C one tak waciwie oznaczaj? Zacznijmy od tego drugiego. Autorzy standardu jzyka C czuli, e wymuszanie jakiego konkretnego dziaania danego wyraenia byoby zbytnim obcieniem dla osb piszcych kompilatory, gdy dany wymg mgby by bardzo trudny do zrealizowania na konkretnej architekturze. Dla przykadu, gdyby standard wymaga, e typ unsigned char ma dokadnie 8 bitw to napisanie kompilatora dla architektury, na ktrej bajt ma 9 bitw byoby cokolwiek kopotliwe, a z pewnoci wynikowy program dziaaby o wiele wolniej niby to byo moliwe. Z tego wanie powodu, niektre aspekty jzyka nie s okrelone bezporednio w standardzie i s pozostawione do decyzji zespou (osoby) piszcego konkretn implementacj. W ten sposb, nie ma adnych przeciwwskaza (ze strony standardu), aby na architekturze, gdzie bajty maj 9 bitw, typ char rwnie mia tyle bitw. Dokonany wybr musi by jednak opisany w dokumentacji kompilatora, tak eby osoba piszca program w C moga sprawdzi jak dana konstrukcja zadziaa. Naley zatem pamita, e poleganie na jakim konkretnym dziaaniu programu w przypadkach zachowania zalenego od implementacji drastycznie zmniejsza przenono kodu rdowego. Zachowania niezdeniowane s o wiele groniejsze, gdy zaistnienie takowego moe spowodowa dowolny efekt, ktry nie musi by nigdzie udokumentowany. Przykadem moe tutaj by prba odwoania si do wartoci wskazywanej przez wskanik o wartoci NULL. Jeeli gdzie w naszym programie zaistnieje sytuacja niezdeniowanego zachowania, to nie jest ju to kwestia przenonoci kodu, ale po prostu bdu w kodzie, chyba e wiado-

171

172

ROZDZIA 24. PRZENONO PROGRAMW

mie korzystamy z rozszerzenia naszego kompilatora. Rozwamy odwoywanie si do wartoci wskazywanej przez wskanik o wartoci NULL. Poniewa wedug standardu operacja taka ma niezdeniowany skutek to w szczeglnoci moe wywoa jak z gry okrelon funkcj kompilator moe co takiego zrealizowa sprawdzajc warto wskanika przed kad dereferencj, w ten sposb niezdeniowane zachowanie dla konkretnego kompilatora stanie si jak najbardziej zdeniowane. Sytuacj wzit z ycia s operatory przesuni bitowych, gdy dziaaj na liczbach ze znakiem. Konkretnie przesuwanie w lewo liczb jest dla wielu przypadkw niezdeniowane. Bardzo czsto jednak, w dokumentacji kompilatora dziaanie przesuni bitowych jest dokadnie opisane. Jest to o tyle interesujcy fakt, i wielu programistw nie zdaje sobie z niego sprawy i niewiadomie korzysta z rozszerze kompilatora. Istnieje jeszcze trzecia klasa zachowa. Zachowania nieokrelone (ang. unspecied behaviour ). S to sytuacje, gdy standard okrela kilka moliwych sposobw w jaki dane wyraenie moe dziaa i pozostawia kompilatorowi decyzj co z tym dalej zrobi. Co takiego nie musi by nigdzie opisane w dokumentacji i znowu poleganie na konkretnym zachowaniu jest bdem. Klasycznym przykadem moe by kolejno obliczania argumentw wywoania funkcji.

Rozmiar zmiennych
Rozmiar poszczeglnych typw danych (np. char, int czy long) jest rna na rnych platformach, gdy nie jest deniowany w sztywny sposb, jak np. long int zawsze powinien mie 64 bity (takie okrelenie wizaoby si z wyej opisanymi trudnociami), lecz w na zasadzie zalenoci typu long powinien by nie krtszy ni int, short nie powinien by duszy od int. Pierwsza standaryzacja jzyka C zakadaa, e typ int bdzie mia taki rozmiar, jak domylna dugo liczb cakowitych na danym komputerze, natomiast modykatory short oraz long zmieniay dugo tego typu tylko wtedy, gdy dana maszyna obsugiwaa typy o mniejszej lub wikszej dugoci1 . Z tego powodu, nigdy nie zakadaj, e dany typ bdzie mia okrelony rozmiar. Jeeli potrzebujesz typu o konkretnym rozmiarze (a dokadnej konkretnej liczbie bitw wartoci) moesz skorzysta z pliku nagwkowego stdint.h wprowadzonego do jzyka przez standard ISO C z 1999 roku. Deniuje on typy int8 t, int16 t, int32 t, int64 t, uint8 t, uint16 t, uint32 t i uint64 t (o ile w danej architekturze wystpuj typy o konkretnej liczbie bitw). Jednak moemy posiada implementacj, ktra nie posiada tego pliku nagwkowego. W takiej sytuacji nie pozostaje nam nic innego jak tworzy wasny plik nagwkowy, w ktrym za pomoc swka typedef sami zdeniujemy potrzebne nam typy. Np.:

typedef typedef typedef typedef typedef typedef typedef typedef

unsigned signed unsigned signed unsigned signed unsigned signed

char char short short long long long long long long

u8; s8; u16; s16; u32; s32; u64; s64;

Aczkolwiek naley pamita, e taki plik bdzie trzeba pisa od nowa dla kadej architektury na jakiej chcemy kompilowa nasz program.
1 Dokadniejszy

opis rozmiarw dostpny jest w rozdziale Skadnia.

PORZDEK BAJTW I BITW

173

Porzdek bajtw i bitw


Bajty i sowa
Wiesz zapewne, e podstawow jednostk danych jest bit, ktry moe mie warto 0 lub 1. Kilka kolejnych bitw 2 stanowi bajt (dla skupienia uwagi, przyjmijmy, e bajt skada si z 8 bitw). Czsto typ short ma wielko dwch bajtw i wwczas pojawia si pytanie w jaki sposb s one zapisane w pamici czy najpierw ten bardziej znaczcy big-endian, czy najpierw ten mniej znaczcy little-endian. Skd takie nazwy? Ot pochodz one z ksiki Podre Guliwera, w ktrej liliputy kciy si o stron, od ktrej naley rozbija jajko na twardo. Jedni uwaali, e trzeba je rozbija od grubszego koca (big-endian) a drudzy, e od cieszego (little-endian). Nazwy te s o tyle trafne, e w wypadku procesorw wybr kolejnoci bajtw jest spraw czysto polityczn, ktra jest technicznie neutralna. Sprawa si jeszcze bardziej komplikuje w przypadku typw, ktre skadaj si np. z 4 bajtw. Wwczas s a 24 (4 silnia) sposoby zapisania kolejnych fragmentw takiego typu. W praktyce zapewne spotkasz si jedynie z kolejnociami big-endian lub little-endian, co nie zmienia faktu, e inne moliwoci take istniej i przy pisaniu programw, ktre maj by przenone naley to bra pod uwag. Poniszy przykad dobrze obrazuje oba sposoby przechowywania zawartoci zmiennych w pamici komputera (przyjmujemy CHAR BIT == 8 oraz sizeof(long) == 4, bez bitw wypenienia (ang. padding bits)): unsigned long zmienna = 0x01020304; w pamici komputera bdzie przechowywana tak: adres | 0 | 1 | 2 | 3 | big-endian |0x01|0x02|0x03|0x04| little-endian |0x04|0x03|0x02|0x01|

Konwersja z jednego porzdku do innego


Czasami zdarza si, e napisany przez nas program musi si komunikowa z innym programem (moe te przez nas napisanym), ktry dziaa na komputerze o (potencjalnie) innym porzdku bajtw. Czsto najprociej jest przesya liczby jako tekst, gdy jest on niezaleny od innych czynnikw, jednak taki format zajmuje wicej miejsca, a nie zawsze moemy sobie pozwoli na tak rozrzutno. Przykadem moe by komunikacja sieciowa, w ktrej przyjo si, e dane przesyane s w porzdku big-endian. Aby mc atwo operowa na takich danych, w standardzie POSIX zdeniowano nastpujce funkcje (w zasadzie zazwyczaj s to makra): #include uint32_t uint16_t uint32_t uint16_t <arpa/inet.h> htonl(uint32_t htons(uint16_t ntohl(uint32_t ntohs(uint16_t

hostlong); hostshort); netlong); netshort);

Pierwsze dwie konwertuj liczb z reprezentacji lokalnej na reprezentacj big-endian (host to network ), natomiast kolejne dwie dokonuj konwersji w drug stron (network to host). Mona rwnie skorzysta z pliku nagwkowego endian.h, w ktrym deniowane s makra pozwalajce okreli porzdek bajtw: #include <endian.h> #include <stdio.h>
2 Standard wymaga aby byo ich co najmniej 8 i liczba bitw w bajcie w konkretnej implementacji jest okrelona przez makro CHAR BIT zdeniowane w pliku nagwkowym limits.h

174

ROZDZIA 24. PRZENONO PROGRAMW

int main() { #if __BYTE_ORDER == __BIG_ENDIAN printf("Porzdek big-endian (4321)\n"); #elif __BYTE_ORDER == __LITTLE_ENDIAN printf("Porzdek little-endian (1234)\n"); #elif defined __PDP_ENDIAN && __BYTE_ORDER == __PDP_ENDIAN printf("Porzdek PDP (3412)\n"); #else printf("Inny porzdek (%d)\n", __BYTE_ORDER); #endif return 0; } Na podstawie makra BYTE ORDER mona skonstruowa funkcj, ktra bdzie konwertowa liczby pomidzy porzdkiem rnymi porzdkami: #include <endian.h> #include <stdio.h> #include <stdint.h> uint32_t convert_order32(uint32_t val, unsigned from, unsigned to) { if (from==to) { return val; } else { uint32_t ret = 0; unsigned char tmp[5] = { 0, 0, 0, 0, 0 }; unsigned char *ptr = (unsigned char*)&val; unsigned div = 1000; do tmp[from / div % 10] = *ptr++; while ((div /= 10)); ptr = (unsigned char*)&ret; div = 1000; do *ptr++ = tmp[to / div % 10]; while ((div /= 10)); return ret; } } #define #define #define #define #define #define LE_TO_H(val) H_TO_LE(val) BE_TO_H(val) H_TO_BE(val) PDP_TO_H(val) H_TO_PDP(val) convert_order32((val), convert_order32((val), convert_order32((val), convert_order32((val), convert_order32((val), convert_order32((val), 1234, __BYTE_ORDER) __BYTE_ORDER, 1234) 4321, __BYTE_ORDER) __BYTE_ORDER, 4321) 3412, __BYTE_ORDER) __BYTE_ORDER, 3412)

int main () { printf("%08x\n", printf("%08x\n", printf("%08x\n", printf("%08x\n", printf("%08x\n", printf("%08x\n", return 0; }

LE_TO_H(0x01020304)); H_TO_LE(0x01020304)); BE_TO_H(0x01020304)); H_TO_BE(0x01020304)); PDP_TO_H(0x01020304)); H_TO_PDP(0x01020304));

Cigle jednak polegamy na niestandardowym pliku nagwkowym endian.h. Mona go wyeliminowa sprawdzajc porzdek bajtw w czasie wykonywania programu:

BIBLIOTECZNE PROBLEMY
#include <stdio.h> #include <stdint.h> int main() { uint32_t val = 0x04030201; unsigned char *v = (unsigned char *)&val; int byte_order = v[0] * 1000 + v[1] * 100 + v[2] * 10 + v[3]; if (byte_order == 4321) { printf("Porzdek big-endian (4321)\n"); } else if (byte_order == 1234) { printf("Porzdek little-endian (1234)\n"); } else if (byte_order == 3412) { printf("Porzdek PDP (3412)\n"); } else { printf("Inny porzdek (%d)\n", byte_order); } return 0; }

175

Powysze przykady opisuj jedynie cz problemw jakie mog wynika z prby przenoszenia binarnych danych pomidzy wieloma platformami. Wszystkie co wicej zakadaj, e bajt ma 8 bitw, co wcale nie musi by prawd dla konkretnej architektury, na ktr piszemy aplikacj. Co wicej liczby mog posiada w swojej reprezentacje bity wypenienia (ang. padding bits), ktre nie bior udziay w przechowywaniu wartoci liczby. Te wszystkie rnice mog dodatkowo skomplikowa kod. Tote naley by wiadomym, i przenoszc dane binarnie musimy uwaa na rne reprezentacje liczb.

Biblioteczne problemy
Piszc programy nieraz bdziemy musieli korzysta z rnych bibliotek. Problem polega na tym, e nie zawsze bd one dostpne na komputerze, na ktrym inny uytkownik naszego programu bdzie prbowa go kompilowa. Dlatego te wane jest, abymy korzystali z atwo dostpnych bibliotek, ktre dostpne s na wiele rnych systemw i platform sprztowych. Zapamitaj: Twj program jest na tyle przenony na ile przenone s biblioteki z ktrych korzysta!

Kompilacja warunkowa
Przy zwikszaniu przenonoci kodu moe pomc preprocessor. Przyjmijmy np., e chcemy korzysta ze swka kluczowego inline wprowadzonego w standardzie C99, ale rwnoczenie chcemy, aby nasz program by rozumiany przez kompilatory ANSI C. Wwczas, moemy skorzysta z nastpujcego kodu: #ifndef __inline__ # if __STDC_VERSION__ >= 199901L # define __inline__ inline # else # define __inline__ # endif #endif

176

ROZDZIA 24. PRZENONO PROGRAMW

a w kodzie programu zamiast swka inline stosowa inline . Co wicej, kompilator GCC rozumie swka kluczowe tak tworzone i w jego przypadku warto nie redeniowa ich wartoci: #ifndef __GNUC__ # ifndef __inline__ # if __STDC_VERSION__ >= 199901L # define __inline__ inline # else # define __inline__ # endif # endif #endif Korzystajc z kompilacji warunkowej mona take korzysta z rnego kodu zalenie od (np.) systemu operacyjnego. Przykadowo, przed kompilacj na konkretnej platformie tworzymy odpowiedni plik cong.h, ktry nastpnie doczamy do wszystkich plikw rdowych, w ktrych podejmujemy decyzje na podstawie zdeniowanych makr. Dla przykadu, plik cong.h: #ifndef CONFIG_H #define CONFIG_H /* Uncomment if using Windows */ /* #define USE_WINDOWS */ /* Uncomment if using Linux */ /* #define USE_LINUX */ #error You must edit config.h file #error Edit it and remove those error lines #endif Jaki plik rdowy: #include "config.h" /* ... */ #ifdef USE_WINDOWS rob_cos_wersja_dla_windows(); #else rob_cos_wersja_dla_linux(); #endif Istniej rne narzdzia, ktre pozwalaj na automatyczne tworzenie takich plikw cong.h, dziki czemu uytkownik przed skompilowaniem programu nie musi si trudzi i edytowa ich rcznie, a jedynie uruchomi odpowiednie polecenie. Przykadem jest zestaw autoconf i automake.

Rozdzia 25

czenie z innymi jzykami


Programista, piszc jaki program ma problem z wyborem najbardziej odpowiedniego jzyka do utworzenia tego programu. Niekiedy zdarza si, e najlepiej byoby pisa program, korzystajc z rnych jzykw. Jzyk C moe by z atwoci czony z innymi jzykami programowania, ktre podlegaj kompilacji bezporednio do kodu maszynowego (Asembler, Fortran czy te C++). Ponadto dziki specjalnym bibliotekom mona go czy z jzykami bardzo wysokiego poziomu (takimi jak np. Python czy te Ruby). Ten rozdzia ma za zadanie wytumaczy Ci, w jaki sposb mona miesza rne jzyki programowania w jednym programie.

Jzyk C i Asembler
Informacje zawarte w tym rozdziale odnosz si do komputerw z procesorem i386 i pokrewnych. czenie jzyka C i jzyka asemblera jest do powszechnym zjawiskiem. Dziki moliwoci poczenia obu tych jzykw programowania mona byo utworzy bibliotek dla jzyka C, ktra niskopoziomowo komunikuje si z jdrem systemu operacyjnego komputera. Poniewa zarwno asembler jak i C s jzykami tumaczonymi do poziomu kodu maszynowego, za ich czenie odpowiada program zwany linkerem (popularny ld). Ponadto niektrzy producenci kompilatorw umoliwiaj stosowanie tzw. wstawek asemblerowych, ktre umieszcza si bezporednio w kodzie programu, napisanego w jzyku C. Kompilator, kompilujc taki kod wstawi w miejsce tyche wstawek odpowiedni kod maszynowy, ktry jest efektem przetumaczenia kodu asemblera, zawartego w takiej wstawce. Opisz tu oba sposoby czenia obydwu jzykw.

czenie na poziomie kodu maszynowego


W naszym przykadzie zaoymy, e w pliku f1.S zawarty bdzie kod, napisany w asemblerze, a f2.c to kod z programem w jzyku C. Program w jzyku C bdzie wykorzystywa jedn funkcj, napisan w jzyku asemblera, ktra wywietli prosty napis Hello world. Z powodu ogranicze technicznych zakadamy, e program uruchomiony zostanie w rodowisku POSIX na platformie i386 i skompilowany kompilatorem gcc. Uywan skadni asemblera bdzie AT&T (domylna dla asemblera GNU) Oto plik f1.S: .text .globl _f1

177

178

ROZDZIA 25. CZENIE Z INNYMI JZYKAMI

_f1: pushl %ebp movl %esp, %ebp movl $4, %eax /* 4 to funkcja systemowa "write" */ movl $1, %ebx /* 1 to stdout */ movl $tekst, %ecx /* adres naszego napisu */ movl $len, %edx /* dugo napisu w bajtach */ int $0x80 /* wywoanie przerwania systemowego */ popl %ebp ret .data tekst: .string "Hello world\n" len = . - tekst W systemach z rodziny UNIX naley pomin znak przed nazw funkcji f1 Teraz kolej na f2.c: extern void f1 (void); /* musimy uy sowa extern */ int main () { f1(); return 0; } Teraz moemy skompilowa oba programy: as f1.S -o f1.o gcc f2.c -c -o f2.o gcc f2.o f1.o -o program W ten sposb uzyskujemy plik wykonywalny o nazwie program. Efekt dziaania programu powinien by nastpujcy: Hello world Na razie utworzylimy bardzo prost funkcj, ktra w zasadzie nie komunikuje si z jzykiem C, czyli nie zwraca adnej wartoci ani nie pobiera argumentw. Jednak, aby zacz pisa obsug funkcji, ktra bdzie pobieraa argumenty i zwracaa wyniki musimy pozna dziaanie jzyka C od troch niszego poziomu.

Argumenty
Do komunikacji z funkcj jzyk C korzysta ze stosu. Argumenty odkadane s w kolejnoci od ostatniego do pierwszego. Ponadto na kocu odkadany jest tzw. adres powrotu, dziki czemu po wykonaniu funkcji program wie, w ktrym miejscu ma kontynuowa dziaanie. Ponadto, pocztek funkcji w asemblerze wyglda tak: pushl %ebp movl %esp, %ebp Zatem na stosie znajduj si kolejno: zawarto rejestru EBP, adres powrotu a nastpnie argumenty od pierwszego do n-tego.

JZYK C I ASEMBLER Zwracanie wartoci

179

Na architekturze i386 do zwracania wynikw pracy programu uywa si rejestru EAX, bd jego mniejszych odpowiednikw, tj. AX i AH/AL. Zatem aby funkcja, napisana w asemblerze zwrcia 1 przed rozkazem ret naley napisa: movl $1, %eax

Nazewnictwo
Kompilatory jzyka C/C++ dodaj podkrelnik na pocztku kadej nazwy. Dla przykadu funkcja: void funkcja(); W pliku wyjciowym bdzie posiada nazw funkcja. Dlatego, aby korzysta z poziomu jzyka C z funkcji zakodowanych w asemblerze, musz one mie przy denicji w pliku asemblera wspomniany dodatkowy podkrelnik na pocztku.

czymy wszystko w cao


Pora, abymy napisali jak funkcj, ktra pobierze argumenty i zwrci jaki konkretny wynik. Oto kod f1.S: .text .globl _funkcja _funkcja: pushl %ebp movl %esp, %ebp movl 8(%esp), %eax /* kopiujemy pierwszy argument do %eax */ addl 12(%esp), %eax /* do pierwszego argumentu w %eax dodajemy drugi argument */ popl %ebp ret /* ... i zwracamy wynik dodawania... */ oraz f2.c: #include <stdio.h> extern int funkcja (int a, int b); int main () { printf ("2+3=%d\n", funkcja(2,3)); return 0; } Po skompilowaniu i uruchomieniu programu powinnimy otrzyma wydruk: 2+3=5

Wstawki asemblerowe
Oprcz moliwoci wstpnie skompilowanych moduw moesz posuy si take tzw. wstawkami asemblerowymi. Ich uycie powoduje wstawienie w miejsce wystpienia wstawki odpowiedniego kodu maszynowego, ktry powstanie po przetumaczeniu kodu asemblerowego. Poniewa jednak wstawki asemblerowe nie s standardowym elementem jzyka C, kady kompilator ma cakowicie odmienn lozo ich stosowania (lub nie ma ich w ogle). Poniewa w tym podrczniku uywamy gwnie kompilatora GNU, wic w tym rozdziale zostanie omwiona lozoa stosowania wstawek asemblera wedug programistw GNU. Ze wstawek asemblerowych korzysta si tak:

180
int main () { asm ("nop"); }

ROZDZIA 25. CZENIE Z INNYMI JZYKAMI

W tym wypadku wstawiona zostanie instrukcja nop (no operation), ktra tak naprawd suy tylko i wycznie do konstruowania ptli opniajcych.

C++
Jzyk C++ z racji swojego podobiestwa do C bdzie wyjtkowo atwy do czenia. Pewnym utrudnieniem moe by obiektowo jzyka C++ oraz wystpowanie w nim przestrzeni nazw oraz moliwo przeciania funkcji. Oczywicie nadal zakadamy, e gwny program piszemy w C, natomiast korzystamy tylko z pojedynczych funkcji, napisanych w C++. Poniewa jzyk C nie oferuje tego wszystkiego, co daje programicie jzyk C++, to musimy zmusi C++ do wyczenia pewnych swoich moliwoci, aby mona byo poczy ze sob elementy programu, napisane w dwch rnych jzykach. Uywa si do tego nastpujcej konstrukcji: extern "C" { /* funkcje, zmienne i wszystko to, co bdziemy czy z programem w C */ } W zrozumieniu teorii pomoe Ci prosty przykad: plik f1.c: #include <stdio.h> extern int f2(int a); int main () { printf ("%d\n", f2(2)); return 0; } oraz plik f2.cpp: #include <iostream> using namespace std; extern "C" { int f2 (int a) { cout << "a=" << a << endl; return a*2; } } Teraz oba pliki kompilujemy: gcc f1.c -c -o f1.o g++ f2.cpp -c -o f2.o Przy czeniu obu tych plikw musimy pamita, e jzyk C++ take korzysta ze swojej biblioteki. Zatem poprawna posta polecenia kompilacji powinna wyglda: gcc f1.o f2.o -o program -lstdc++ (stdc++ biblioteka standardowa jzyka C++). Bardzo istotne jest tutaj to, abymy zawsze pamitali o extern C, gdy w przeciwnym razie funkcje napisane w C++ bd dla programu w C cakowicie niewidoczne.

Dodatek A

Indeks alfabetyczny
Alfabetyczny spis funkcji biblioteki standardowej ANSI C (tzw. libc) w wersji C89.

A
abort() abs() acos() asctime() asin() assert() atan() atan2() atexit() atof() atoi() atol()

D
ditime() div()

frexp() fscanf() fseek() fsetpos()

E
errno (zmienna) exit() exp()

ftell() fwrite()

G
getc() getchar()

F
fabs() fclose() feof() ferror() ush()

getenv() gets() gmtime()

I
isalnum() isalpha() iscntrl() isdigit() isgraph() islower() isprint() ispunct() isspace() isupper() isxdigit()

B
bsearch()

fgetc() fgetpos() fgets() oor() calloc() ceil() clearerr() clock() cos() cosh() ctime() fmod() fopen() fprintf() fputc() fputs() fread() free() freopen()

181

182

DODATEK A. INDEKS ALFABETYCZNY

L
labs() ldexp() ldiv() localeconv() localtime() log() log10() longjmp()

R
raise() rand() realloc() remove() rename() rewind()

strspn() strstr() strtod() strtok() strtol() strtoul() strxfrm() system() scanf() setbuf()

S T

M
malloc() mblen() mbstowcs() mbtowc() memchr() memcmp() memcpy() memmove() memset() mktime() modf()

setjmp() setlocale() setvbuf() signal() sin() sinh() sprintf() sqrt() srand() sscanf() strcat() strchr()

tan() tanh() time() tm (struktura) tmple() tmpnam() tolower() toupper()

U
ungetc()

O
osetof()

strcmp() strcoll() strcpy()

V
va arg() va end() va start() vfprintf() vprintf() vsprintf()

P
perror() pow() printf() putc() putchar() puts()

strcspn() strerror() strftime() strlen() strncat() strncmp() strncpy()

W
wcstombs() wctomb()

Q
qsort()

strpbrk() strrchr()

Dodatek B

Indeks tematyczny
Spis plikw nagwkowych oraz zawartych w nich funkcji i makr biblioteki standardowej C. Funkcje, makra i typy wprowadzone dopiero w standardzie C99 zostay oznaczone poprzez [C99] po nazwie.

assert.h
Makro asercji. assert()

ctype.h
Klasykowanie znakw. isalnum() isalpha() isblank() [C99] iscntrl() isdigit() isgraph() islower() isprint() ispunct() isspace() tolower() toupper() isupper() isxdigit()

errno.h
Deklaracje kodw bdw. EDOM (makro) EILSEQ (makro) [C99] ERANGE (makro) errno (zmienna)

oat.h
Waciwoci typw zmiennoprzecinkowych zalene od implementacji.

limits.h
Waciwoci typw cakowitych zalene od implementacji.

183

184

DODATEK B. INDEKS TEMATYCZNY

locale.h
Ustawienia midzynarodowe. localeconv() setlocale()

math.h
Funkcje matematyczne. FP FAST FMAF (makro) [C99] FP FAST FMAL (makro) [C99] FP FAST FMA (makro) [C99] FP ILOGB0 (makro) [C99] FP ILOGBNAN (makro) [C99] FP INFINITE (makro) [C99] FP NAN (makro) [C99] FP NORMAL (makro) [C99] FP SUBNORMAL (makro) [C99] FP ZERO (makro) [C99] HUGE VALF (makro) [C99] HUGE VALL (makro) [C99] HUGE VAL (makro) INFINITY (makro) [C99] MATH ERREXCEPT (makro) [C99] MATH ERRNO (makro) [C99] NAN (makro) [C99] acosh() acos() asinh() asin() atan2() atanh() atan() cbrt() [C99] ceil() copysign() [C99] cosh() cos() double t (typ) [C99] erfc() [C99] erf() [C99] exp2() [C99] expm1() [C99] exp() fabs() fdim() [C99] aot t (typ) [C99] oor() fmax() [C99] fma() [C99] fmin() [C99] fmod() fpclassify() [C99] frexp() hypot() [C99] ilogb() [C99] isnite() [C99] isgreaterequal() [C99] isgreater() [C99] isinf() [C99] islessequal() [C99] islessgreater() [C99] isless() [C99] isnan() [C99] isnormal() [C99] isunordered() [C99] ldexp() lgamma() [C99] llrint() [C99] llround() [C99] log10() log1p() [C99] log2() [C99] logb() [C99] log()

SETJMP.H
lrint() [C99] lround() [C99] math errhandling (makro) [C99] modf() nan() [C99] nearbyint() [C99] nextafter() [C99] nexttoward() [C99] pow() remainder() [C99] remquo() [C99] rint() [C99] round() [C99] scalbln() [C99] scalbn() [C99] signbit() [C99] sinh() sin() sqrt() tanh() tan() tgamma() [C99] trunc() [C99]

185

setjmp.h
Obsuga nielokalnych skokw. longjmp() setjmp()

signal.h
Obsuga sygnaw. raise() signal()

stdarg.h
Narzdzia dla funkcji ze zmienn liczb argumentw. va arg() va end() va start()

stddef.h
Standardowe denicje. osetof()

stdio.h
Standard Input/Output, czyli standardowe wejcie-wyjcie. clearerr() fclose() feof() ferror() ush() fgetc() fgetpos() fgets() fopen()

186
fprintf() fputc() fputs() fread() freopen() fscanf() fseek() fsetpos() ftell() fwrite() getc()

DODATEK B. INDEKS TEMATYCZNY


getchar() gets() perror() printf() putc() putchar() puts() remove() rename() rewind() scanf() setbuf() setvbuf() sprintf() sscanf() tmple() tmpnam() ungetc() vfprintf() vprintf() vsprintf()

stdlib.h
Najbardziej 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()

string.h
Operacje na acuchach znakw memchr() memcmp() memcpy() memmove() memset() strcat() strchr() strcmp() strcoll() strcpy() strcspn() strerror() strlen() strncat() strncmp() strncpy() strpbrk() strrchr() strspn() strstr() strtok() strxfrm() strdup()

time.h
Funkcje obsugi czasu.

TIME.H
asctime() clock() ctime() ditime() gmtime() localtime() mktime() strftime() time() tm (struktura)

187

188

DODATEK B. INDEKS TEMATYCZNY

Dodatek C

Wybrane funkcje biblioteki standardowej


assert
Deklaracja
#define assert(expr)

Plik nagwkowy
assert.h

Opis
Makro przypominajce w uyciu funkcj, suy do debuggowania programw. Gdy testowany warunek logiczny expr przyjmuje warto fasz, na standardowe wyjcie bdw wypisywany jest komunikat o bdzie (zawierajce m.in. argument wywoania makra; nazw funkcji, w ktrej zostao wywoane; nazw pliku rdowego oraz numer linii w formacie zalenym od implementacji) i program jest przerywany poprzez wywoanie funkcji abort. W ten sposb moemy oznaczy w programie niezmienniki, czyli warunki, ktre niezalenie od wartoci zmiennych musz pozosta prawdziwe. Jeli asercja zawiedzie, oznacza to, e popenilimy bd w algorytmie, piszemy sobie po pamici (nadajc zmiennym wartoci, ktrych nigdy nie powinny mie) albo nastpia po drodze sytuacja wyjtkowa, na przykad zwizana z obsug operacji wejcia-wyjcia. Mona atwo pozby si asercji, uwalniajc kod od spowalniajcych obcie a jednoczenie nie muszc kasowa wystpie assert i zachowujc je na przyszo. Aby to zrobi, naley przed doczeniem pliku nagwkowego assert.h zdeniowa makro NDEBUG, wwczas makro assert przyjmuje posta: #define assert(ignore) ((void)0) Makro assert jest redeniowane za kadym doczeniem pliku nagwkowego assert.h.

Warto zwracana
Makro nie zwraca adnej wartoci.

189

190

DODATEK C. WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Przykad
#include <assert.h> int main() { int err=1; assert(err==0); return 0; } Program wypisze komunikat podobny do: Assertion failed: err==0, file test.c, line 6 Natomiast jeli uruchomimy: #define NDEBUG #include <assert.h> int main() { int err=1; assert(err==0); return 0; } nie pojawi si aden komunikat o bdach.

atoi
Deklaracja
int atoi (const char * string)

Plik nagwkowy
stdlib.h

Opis
Funkcja jako argument pobiera liczb w postaci cigu znakw ASCII, a nastpnie zwraca jej warto w formacie int. Liczb moe poprzedza dowolona ilo biaych znakw (spacje, tabulatory, itp.), oraz jej znak (plus (+) lub minus (-)). Funkcja atoi() koczy wczytywa znaki w momencie napotkania jakiegokowiek znaku ktry nie jest cyfr.

Warto zwracana
Przeksztacona liczba, w przypadku gdy cig nie zawiera cyfr zwracana jest warto 0.

Uwagi
Znak musi bezporednio poprzedza liczb, czyli moliwy jest zapis -2, natomiast prba potraktowania funkcj atoi cigu - 2 skutkuje zwracan wartoci 0.

ISALNUM

191

Przykad
#include <stdio.h> #include <stdlib.h> int main(void) { char * c_Numer = "\n\t 2004u"; int i_Numer; i_Numer = atoi(c_Numer); printf("\n Liczba typu int: %d, oraz jako cig znakw: return 0; }

%s \n", i_Numer, c_Numer);

isalnum
Deklaracja
#include <ctype.h> int int int int int int int int int int int int isalnum(int c); isalpha(int c); isblank(int c); iscntrl(int c); isdigit(int c); isgraph(int c); islower(int c); isprint(int c); ispuntc(int c); isspace(int c); isupper(int c); isxdigit(int c);

Argumenty
c warto znaku reprezentowana w jako typ unsigned char lub warto makra EOF. Z tego powodu, przed przekazaniem funkcji argumentu typu char lub signed char naley go zrzutowa na typ unsigned char lub unsigned int.

Opis
Funkcje sprawdzaj czy podany znak spenia jaki konkretny warunek. Bior pod uwag ustawienia jzyka i dla rnych znakw w rnych localeach mog zwraca rne wartoci. isalnum sprawdza czy znak jest liczb lub liter, isalpha sprawdza czy znak jest liter, isblank sprawdza czy znak jest znakiem odstpu sucym do oddzielania wyrazw (standardowymi znakami odstpu s spacja i znak tabulacji), iscntrl sprawdza czy znak jest znakiem sterujcym, isdigit sprawdza czy znak jest cyfr dziesitna, isgraph sprawdza czy znak jest znakiem drukowalnym rnym od spacji, islower sprawdza czy znak jest ma liter, isprint sprawdza czy znak jest znakiem drukowalnym (wczajc w to spacj),

192

DODATEK C. WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

ispunct sprawdza czy znak jest znakiem przestankowym, dla ktrego ani isspace ani isalnum nie s prawdziwe (standardowo s to wszystkie znaki drukowalne, dla ktrych te funkcje zwracaj zero), isspace sprawdza czy znak jest tzw. biaym znakiem (standardowymi biaymi znakami s: spacja, wysunicie strony \f, znak przejcia do nowej linii \n, znak powrotu karetki \r, tabulacja pozioma \t i tabulacja pionowa \v), isupper sprawdza czy znak jest du liter, isxdigit sprawdza czy znak jest cyfr szesnastkow, tj. cyfr dziesitn lub liter od a do f niezalenie od wielkoci. Funkcja isblank nie wystpowaa w oryginalnym standardzie ANSI C z 1989 roku (tzw. C89) i zostaa dodana dopiero w nowszym standardzie z 1999 roku (tzw. C99).

Warto zwracana
Liczba niezerowa gdy podany argument spenia konkretny warunek, w przeciwnym wypadku zero.

Przykad uycia
#include <ctype.h> /* funkcje is* */ #include <locale.h> /* setlocale */ #include <stdio.h> /* printf i scanf */ void identify_char(int c) { printf(" Litera lub cyfra: #if __STDC_VERSION__ >= 199901L printf(" Odstp: #endif printf(" Znak sterujcy: printf(" Cyfra dziesitna: printf(" Graficzny: printf(" Maa litera: printf(" Drukowalny: printf(" Przestankowy: printf(" Biay znak: printf(" Wielka litera: printf(" Cyfra szesnastkowa: }

%s\n", isalnum (c) ? "tak" : "nie"); %s\n", isblank (c) ? "tak" : "nie"); %s\n", %s\n", %s\n", %s\n", %s\n", %s\n", %s\n", %s\n", %s\n", iscntrl (c) isdigit (c) isgraph (c) islower (c) isprint (c) ispunct (c) isspace (c) isupper (c) isxdigit(c) ? ? ? ? ? ? ? ? ? "tak" "tak" "tak" "tak" "tak" "tak" "tak" "tak" "tak" : : : : : : : : : "nie"); "nie"); "nie"); "nie"); "nie"); "nie"); "nie"); "nie"); "nie");

int main() { unsigned char c; printf("Nacinij jaki klawisz.\n"); if (scanf("%c", &c)==1) { identify_char(c); setlocale(LC_ALL, "pl_PL"); /* przystosowanie do warunkw polskich */ puts("Po zmianie ustawie jzyka:"); identify_char(c); } return 0; }

MALLOC

193

Zobacz te
tolower, toupper

malloc
Deklaracja
#include <stdlib.h> void void void void *calloc(size_t nmeb, size_t size); *malloc(size_t size); free(void *ptr); *realloc(void *ptr, size_t size);

Argumenty
nmeb liczba elementw, dla ktrych ma by przydzielona pami size rozmiar (w bajtach) pamici do zarezerwowania bd rozmiar pojedynczego elementu ptr wskanik zwrcony przez poprzednie wywoanie jednej z funkcji lub NULL

Opis
Funkcja calloc przydziela pami dla nmeb elementw o rozmiarze size kady i zeruje przydzielon pami. Funkcja malloc przydziela pami o wielkoci size bajtw. Funkcja free zwalnia blok pamici wskazywany przez ptr wczeniej przydzielony przez jedn z funkcji malloc, calloc lub realloc. Jeeli ptr ma warto NULL funkcja nie robi nic. Funkcja realloc zmienia rozmiar przydzielonego wczeniej bloku pamici wskazywanego przez ptr do size bajtw. Pierwsze n bajtw bloku nie ulegnie zmianie gdzie n jest minimum z rozmiaru starego bloku i size. Jeeli ptr jest rwny zero (tj. NULL), funkcja zachowuje si tak samo jako malloc.

Warto zwracana
Jeeli przydzielanie pamici si powiodo, funkcje calloc, malloc i realloc zwracaj wskanik do nowo przydzielonego bloku pamici. W przypadku funkcji realloc moe to by warto inna ni ptr. Jeli jako size, nmeb podano zero, zwracany jest albo wskanik NULL albo prawidowy wskanik, ktry mona poda do funkcji free (zauwamy, e NULL jest te prawidowym argumentem free). Jeli dziaanie funkcji nie powiedzie si, zwracany jest NULL i odpowiedni kod bdu jest wpisywany do zmiennej errno. Dzieje si tak zazwyczaj, gdy nie ma wystarczajco duo miejsca w pamici.

Przykad
#include <stdio.h> #include <stdlib.h> int main(void) { size_t size, num = 0; float *tab, tmp;

194

DODATEK C. WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

/* Przydzielenie pocztkowego bloku pamici */ size = 64; tab = malloc(size * sizeof *tab); if (!tab) { perror("malloc"); return EXIT_FAILURE; } /* Odczyt liczb */ while (scanf("%f", &tmp)==1) { /* Jeeli zapeniono ca tablic, trzeba j zwikszy */ 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("%f\n", tab[--num]); } /* Zwolnienie pamieci i zakonczenie programu */ free(tab); return EXIT_SUCCESS; }

Uwagi
Uycie rzutowania przy wywoaniach funkcji malloc, realloc oraz calloc w jzyku C jest zbdne i szkodliwe. W przypadku braku deklaracji tych funkcji (np. gdy programista zapomni doda plik nagwkowy stdlib.h) kompilator przyjmuje domyln deklaracje, w ktrej funkcja zwraca int. Przy braku rzutowania spowoduje to bd kompilacji (z powodu niemonoci skonwertowania liczby na wskanik) co pozwoli na szybkie wychwycenie bdu w programie. Rzutowanie powoduje, e kompilator zostaje zmuszony do przeprowadzenia konwersji typw i nie wywietla adnych bdw. W przypadku jzyka C++ rzutowanie jest konieczne. Zastosowanie operatora sizeof z wyraeniem (np. sizeof *tablica), a nie typem (np. sizeof float) uatwia pniejsz modykacj programw. Gdyby w pewnym momencie programista zdecydowa si zmieni tablic z tablicy oatw na tablice doublei, musiaby wyszukiwa wszystkie wywoania funkcji malloc, realloc i calloc, co nie jest konieczne przy uyciu operatora sizeof z wyraeniem. Warto zauway, e w przypadku standardowych konguracji systemu GNU/Linux funkcje przydzielajce pami nigdy nie zawodz i nie zwracaj wartoci NULL (dla wartoci parametru size wikszego od zera). Poniewa dla parametru size rwnego zero funkcja moe zwrci albo wskanik rny od wartoci NULL albo jej rwny, zwyke sprawdzanie poprawnoci wywoania poprzez przyrwnanie zwrconej wartoci do zera moe nie da prawidowego wyniku.

PRINTF

195

Zobacz te
Wskaniki (dokadne omwienie zastosowania)

printf
Deklaracja
#include <stdio.h> int int int int printf(const char *format, ...); fprintf(FILE *stream, const char *format, ...); sprintf(char *str, const char *format, ...); snprintf(char *str, size_t size, const char *format, ...)

#include <stdarg.h> int int int int vprintf(const char *format, va_list ap); vfprintf(FILE *stream, const char *format, va_list ap); vsprintf(char *str, const char *format, va_list ap); vsnprintf(char *str, size_t size, const char *format, va_list ap);

Opis
Funkcje formatuj tekst zgodnie z podanym formatem opisanym poniej. Funkcje printf i vprintf wypisuj tekst na standardowe wyjcie (tj. do stdout); fprintf i vfprintf do strumienia podanego jako argument; a sprintf, vsprintf, snprintf i vsnprintf zapisuj go w podanej jako argument tablicy znakw. Funkcje vprintf, vfprintf, vsprintf i vsnprintf rni si od odpowiadajcych im funkcjom printf, fprintf, sprintf i snprintf tym, e zamiast zmiennej liczby argumentw przyjmuj argument typu va list. Funkcje snprintf i vsnprintf rni si od sprintf i vsprintf tym, e nie zapisuje do tablicy nie wicej ni size znakw (wliczajc koczcy znak \0). Oznacza to, e mona je uywa bez obawy o wystpienie przepenienia bufora.

Argumenty
format format, w jakim zostan wypisane nastpne argumenty stream strumie wyjciowy, do ktrego maj by zapisane dane str tablica znakw, do ktrej ma by zapisany sformatowany tekst size rozmiar tablicy znakw ap wskanik na pierwszy argument z listy zmiennej liczby argumentw

Format
Format skada si ze zwykych znakw (innych ni znak %), ktre s kopiowane bez zmian na wyjcie oraz sekwencji sterujcych, zaczynajcych si od symbolu procenta, po ktrym nastpuje: dowolna liczba ag, opcjonalne okrelenie minimalnej szerokoci pola, opcjonalne okrelenie precyzji, opcjonalne okrelenie rozmiaru argumentu,

196

DODATEK C. WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ


okrelenie formatu.

Jeeli po znaku procenta wystpuje od razu drugi procent to caa sekwencja traktowana jest jak zwyky znak procenta (tzn. jest on wypisywany na wyjcie).

Flagi
W sekwencji moliwe s nastpujce agi: - (minus) oznacza, e pole ma by wyrwnane do lewej, a nie do prawej. + (plus) oznacza, e dane liczbowe zawsze poprzedzone s znakiem (plusem dla liczb nieujemnych lub minusem dla ujemnych). spacja oznacza, e liczby nieujemne poprzedzone s dodatkow spacj; jeeli aga plus i spacja s uyte jednoczenie to spacja jest ignorowana. # (hash) powoduje, e wynik jest przedstawiony w alternatywnej postaci: dla formatu o powoduje to zwikszenie precyzji, jeeli jest to konieczne, aby na pocztku wyniku byo zero; dla formatw x i X niezerowa liczba poprzedzona jest cigiem 0x lub 0X; dla formatw a, A, e, E, f, F, g i G wynik zawsze zawiera kropk nawet jeeli nie ma za ni adnych cyfr; dla formatw g i G kocowe zera nie s usuwane. 0 (zero) dla formatw d, i, o, u, x, X, a, A, e, E, f, F, g i G do wyrwnania pola wykorzystywane s zera zamiast spacji za wyjtkiem wypisywania wartoci nieskoczono i NaN. Jeeli obie agi 0 i s obecne to aga zero jest ignorowana. Dla formatw d, i, o, u, x i X jeeli okrelona jest precyzja aga ta jest ignorowana.

Szeroko pola i precyzja


Minimalna szeroko pola oznacza ile najmniej znakw ma zaj dane pole. Jeeli warto po formatowaniu zajmuje mniej miejsca jest ona wyrwnywana spacjami z lewej strony (chyba, e podano agi, ktre modykuj to zachowanie). Domylna warto tego pola to 0. Precyzja dla formatw: d, i, o, u, x i X okrela minimaln liczb cyfr, ktre maj by wywietlone i ma domyln warto 1; a, A, e, E, f i F liczb cyfr, ktre maj by wywietlone po kropce i ma domyln warto 6; g i G okrela liczb cyfr znaczcych i ma domyln warto 1; dla formatu s maksymaln liczb znakw, ktre maj by wypisane. Szeroko pola moe by albo dodatni liczb zaczynajc si od cyfry rnej od zera albo gwiazdk. Podobnie precyzja z t rnic, e jest jeszcze poprzedzona kropk. Gwiazdka oznacza, e brany jest kolejny z argumentw, ktry musi by typu int. Warto ujemna przy okreleniu szerokoci jest traktowana tak jakby podano ag - (minus).

Rozmiar argumentu
Dla formatw d i i mona uy jednego ze modykator rozmiaru: hh oznacza, e format odnosi si do argumentu typu signed char, h oznacza, e format odnosi si do argumentu typu short, l (el) oznacza, e format odnosi si do argumentu typu long, ll (el el) oznacza, e format odnosi si do argumentu typu long long,

PRINTF
j oznacza, e format odnosi si do argumentu typu intmax t,

197

z oznacza, e e format odnosi si do argumentu typu bdcego odpowiednikiem typu size t ze znakiem, t oznacza, e e format odnosi si do argumentu typu ptrdi t. Dla formatw o, u, x i X mona uy takich samych modykatorw rozmiaru jak dla formatu d i oznaczaj one, e format odnosi si do argumentu odpowiedniego typu bez znaku. Dla formatu n mona uy takich samych modykatorw rozmiaru jak dla formatu d i oznaczaj one, e format odnosi si do argumentu bdcego wskanikiem na dany typ. Dla formatw a, A, e, E, f, F, g i G mona uy modykatorw rozmiaru L, ktry oznacza, e format odnosi si do argumentu typu long double. Dodatkowo, modykator l (el) dla formatu c oznacza, e odnosi si on do argumentu typu wint t, a dla formatu s, e odnosi si on do argumenty typu wskanik na wchar t.

Format
Funkcje z rodziny printf obsuguj nastpujce formaty: d, i argument typu int jest przedstawiany jako liczba cakowita ze znakiem w postaci [-]ddd. o, u, x, X argument typu unsigned int jest przedstawiany jako nieujemna liczba cakowita zapisana w systemie oktalnym (o), dziesitnym (u) lub heksadecymalnym (x i X). f, F argument typu double jest przedstawiany w postaci [-]ddd.ddd. e, E argument typu double jest reprezentowany w postaci [i]d.ddde+dd, gdzie liczba przed kropk dziesitn jest rna od zera, jeeli liczba jest rna od zera, a + oznacza znak wykadnika. Format E uywa wielkiej litery E zamiast maej. g, G argument typu double jest reprezentowany w formacie takim jak f lub e (odpowiednio F lub E) zalenie od liczby znaczcych cyfr w liczbie oraz okrelonej precyzji. a, A argument typu double przedstawiany jest w formacie [-]0xh.hhhp+d czyli analogicznie jak dla e i E, tyle e liczba zapisana jest w systemie heksadecymalnym. c argument typu int jest konwertowany do unsigned char i wynikowy znak jest wypisywany. Jeeli podano modykator rozmiaru l argument typu wint t konwertowany jest do wielobajtowej sekwencji i wypisywany. s argument powinien by typu wskanik na char (lub wchar t). Wszystkie znaki z podanej tablicy, a do i z wyczeniem znaku null s wypisywane. p argument powinien by typu wskanik na void. Jest to konwertowany na seri drukowalnych znakw w sposb zaleny od implementacji. n argument powinien by wskanikiem na liczb cakowit ze znakiem, do ktrego zapisana jest liczba zapisanych znakw. W przypadku formatw f, F, e, E, g, G, a i A warto nieskoczono jest przedstawiana w formacie [-]inf lub [-]innity zalenie od implementacji. Warto NaN jest przedstawiana w postaci [-]nan lub [i]nan(sekwencja), gdzie sekwencja jest zalena od implementacji. W przypadku formatw okrelonych wielk liter rwnie wynikowy cig znakw jest wypisywany wielk liter.

198

DODATEK C. WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Warto zwracana
Jeeli funkcje zakocz si sukcesem zwracaj liczb znakw w tekcie (wypisanym na standardowe wyjcie, do podanego strumienia lub tablicy znakw) nie wliczajc koczcego \0. W przeciwnym wypadku zwracana jest liczba ujemna. Wyjtkami s funkcje snprintf i vsnprintf, ktre zwracaj liczb znakw, ktre zostayby zapisane do tablicy znakw, gdyby bya wystarczajco dua.

Przykad uycia
#include <stdio.h> int main() { int i = 4; float f = 3.1415; char *s = "Monty Python"; printf("i = %i\nf = %.1f\nWskanik s wskazuje na napis: %s\n", i, f, s); return 0; } Wywietli: i = 4 f = 3.1 Wskanik s wskazuje na napis: Monty Python Funkcja formatujca cig znakw i alokujca odpowiedni ilo pamici: #include <stdarg.h> #include <stdlib.h> char *sprintfalloc(const char *format, ...) { int ret; size_t size = 100; char *str = malloc(size); if (!str) { return 0; } for(;;){ va_list ap; char *tmp; va_start(ap, format); ret = vsnprintf(str, size, format, ap); va_end(ap); if (ret<size) { break; } tmp = realloc(str, (size_t)ret + 1); if (!tmp) { ret = -1; break; } else {

SCANF
str = tmp; size = (size_t)ret + 1; } } if (ret<0) { free(str); str = 0; } else if (size-1>ret) { char *tmp = realloc(str, (size_t)ret + 1); if (tmp) { str = tmp; } } return str; }

199

Uwagi
Funkcje snprintf i vsnprintf nie byy zdeniowane w standardzie C89. Zostay one dodane dopiero w standardzie C99. Biblioteka glibc do wersji 2.0.6 wcznie posiadaa implementacje funkcji snprintf oraz vsnprintf, ktre byy niezgodne ze standardem, gdy zwracay -1 w przypadku, gdy wynikowy tekst nie mieci si w podanej tablicy znakw.

scanf
Deklaracja
W pliku nagwkowym stdio.h: int scanf(const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int sscanf(const char *str, const char *format, ...); W pliku nagwkowym stdarg.h: 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);

Opis
Funkcje odczytuj dane zgodnie z podanym formatem opisanym niej. Funkcje scanf i vscanf odczytuj dane ze standardowego wejcia (tj. stdin); fscanf i vfscanf ze strumienia podanego jako argument; a sscanf i vsscanf z podanego cigu znakw. Funkcje vscanf, vfscanf i vsscanf rni si od odpowiadajcych im funkcjom scanf, fscanf i sscanf tym, e zamiast zmiennej liczby argumentw przyjmuj argument typu va list.

Argumenty
format format odczytu danych stream strumie wejciowy, z ktrego maj by odczytane dane

200

DODATEK C. WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

str tablica znakw, z ktrej maj by odczytane dane ap wskanik na pierwszy argument z listy zmiennej liczby argumentw

Format
Format skada si ze zwykych znakw (innych ni znak %) oraz sekwencji sterujcych, zaczynajcych si od symbolu procenta, po ktrym nastpuje: opcjonalna gwiazdka, opcjonalne maksymalna szeroko pola, opcjonalne okrelenie rozmiaru argumentu, okrelenie formatu. Jeeli po znaku procenta wystpuje od razu drugi procent to caa sekwencja traktowana jest jak zwyky znak procenta (tzn. jest on wypisywany na wyjcie). Wystpienie w formacie biaego znaku powoduje, e funkcje z rodziny scanf bd odczytywa i odrzuca znaki, a do napotkania pierwszego znaku nie bdcego biaym znakiem. Wszystkie inne znaki (tj. nie biae znaki oraz nie sekwencje sterujce) musz dokadnie pasowa do danych wejciowych. Wszystkie biae znaki z wejcia s ignorowane, chyba e sekwencja sterujca okrela format [, c lub n. Jeeli w sekwencji sterujcej wystpuje gwiazdka to dane z wejcia zostan pobrane zgodnie z formatem, ale wynik konwersji nie zostanie nigdzie zapisany. W ten sposb mona pomija cz danych. Maksymalna szeroko pola przyjmuje posta dodatniej liczby cakowitej zaczynajcej si od cyfry rnej od zera. Okrela ona ile maksymalnie znakw dany format moe odczyta. Jest to szczeglnie przydatne przy odczytywaniu cigu znakw, gdy dziki temu mona poda wielko tablicy (minus jeden) i tym samym unikn bdw przepenienia bufora.

Rozmiar argumentu
Dla formatw d, i, o, u, x i n mona uy jednego ze modykator rozmiaru: hh oznacza, e format odnosi si do argumentu typu wskanik na signed char lub unsigned char, h oznacza, e format odnosi si do argumentu typu wskanik na short lub wskanik na unsigned short, l (el) oznacza, e format odnosi si do argumentu typu wskanik na long lub wskanik na unsigned long, ll (el el) oznacza, e format odnosi si do argumentu typu wskanik na long long lub wskanik na unsigned long long, j oznacza, e format odnosi si do argumentu typu wskanik na intmax t lub wskanik na uintmax t, z oznacza, e e format odnosi si do argumentu typu wskanik na size t lub odpowiedni typ ze znakiem, t oznacza, e e format odnosi si do argumentu typu wskanik na ptrdi t lub odpowiedni typ bez znaku. Dla formatw a, e, f i g mona uy modykatorw rozmiaru l, ktry oznacza, e format odnosi si do argumenty typu wskanik na double lub L, ktry oznacza, e format odnosi si do argumentu typu wskanik na long double. Dla formatw c, s i [ modykator l oznacza, e format odnosi si do argumentu typu wskanik na wchar t.

SCANF Format
Funkcje z rodziny scanf obsuguj nastpujce formaty:

201

d, i odczytuje liczb cakowit, ktrej format jest taki sam jak oczekiwany format przy wywoaniu funkcji strtol z argumentem base rwnym odpowiednio 10 dla d lub 0 dla i, argument powinien by wskanikiem na int; o, u, x odczytuje liczb cakowit, ktrej format jest taki sam jak oczekiwany format przy wywoaniu funkcji strtoul z argumentem base rwnym odpowiednio 8 dla o, 10 dla u lub 16 dla x, argument powinien by wskanikiem na unsigned int; a, e, f, g odczytuje liczb rzeczywist, nieskoczono lub NaN, ktrych format jest taki sam jak oczekiwany przy wywoaniu funkcji strtod, argument powinien by wskanikiem na aot; c odczytuje dokadnie tyle znakw ile okrelono w maksymalnym rozmiarze pola (domylnie 1), argument powinien by wskanikiem na char; s odczytuje sekwencje znakw nie bdcych biaymi znakami, argument powinien by wskanikiem na char; [ odczytuje niepusty cig znakw, z ktrych kady musi nalee do okrelonego zbioru, argument powinien by wskanikiem na char; p odczytuje sekwencje znakw zalen od implementacji odpowiadajc cigowi wypisywanemu przez funkcj printf, gdy podano sekwencj %p, argument powinien by typu wskanik na wskanik na void; n nie odczytuje adnych znakw, ale zamiast tego zapisuje do podanej zmiennej liczb odczytanych do tej pory znakw, argument powinien by typu wskanik na int. Swko wicej o formacie [. Po otwierajcym nawiasie nastpuje cig okrelajcy znaki jakie mog wystpowa w odczytanym napisie i koczy si on nawiasem zamykajcym tj. ]. Znaki pomidzy nawiasami (tzw. scanlist) okrelaj moliwe znaki, chyba e pierwszym znakiem jest wwczas w odczytanym cigu znakw mog wystpowa znaki nie wystpujce w scanlist. Jeeli sekwencja zaczyna si od [] lub [] to ten pierwszy nawias zamykajcy nie jest traktowany jako koniec sekwencji tylko jak zwyky znak. Jeeli wewntrz sekwencji wystpuje znak - (minus), ktry nie jest pierwszym lub drugim jeeli pierwszym jest ani ostatnim znakiem zachowanie jest zalene od implementacji. Formaty A, E, F, G i X s rwnie dopuszczalne i maj takie same dziaanie jak a, e, f, g i x.

Warto zwracana
Funkcja zwraca EOF jeeli nastpi koniec danych lub bd odczytu zanim jakiekolwiek konwersje zostan dokonane lub liczb poprawnie wczytanych pl (ktra moe by rwna zero).

202

DODATEK C. WYBRANE FUNKCJE BIBLIOTEKI STANDARDOWEJ

Dodatek D

Skadnia
Uwaga: przedstawione tutaj informacje nie s w stanie zastpi treci caego podrcznika.

Symbole i sowa kluczowe


Jzyk C deniuje pewn ilo sw, za pomoc ktrych tworzy si np. ptle itp. S to tzw. sowa kluczowe, tzn. nie mona uy ich jako nazwy zmiennej, czy te staej (o nich poniej). Oto lista sw kluczowych jzyka C (wedug norm ANSI C z roku 1989 oraz ISO C z roku 1990):

203

204

DODATEK D. SKADNIA

Sowo auto break case char const continue default do double else enum extern oat for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

Tabela D.1: Symbole i sowa kluczowe Opis w tym podrczniku Zmienne Instrukcje sterujce Instrukcje sterujce Zmienne Zmienne Instrukcje sterujce Instrukcje sterujce Instrukcje sterujce Zmienne Instrukcje sterujce Typy zoone Biblioteki Zmienne Instrukcje sterujce Instrukcje sterujce Instrukcje sterujce Zmienne Zmienne Zmienne Procedury i funkcje Zmienne Zmienne Zmienne Biblioteki, Zmienne Typy zoone Instrukcje sterujce Typy zoone Typy zoone Zmienne Wskaniki Zmienne Instrukcje sterujce

POLSKIE ZNAKI
Specykacja ISO C z roku 1999 dodaje nastpujce sowa: Bool Complex Imaginary inline restrict

205

Polskie znaki
Piszc program, moemy stosowa polskie litery (tj. ) tylko w: komentarzach cigach znakw (acuchach) Niedopuszczalne jest stosowanie polskich znakw w innych miejscach.

Operatory
Operatory arytmetyczne
S to operatory wykonujce znane wszystkim dodawanie,odejmowanie itp.:

operator + * / % =

znaczenie dodawanie odejmowanie mnoenie dzielenie dzielenie modulo daje w wyniku sam reszt z dzielenia operator przypisania wykonuje dziaanie po prawej stronie i wynik przypisuje obiektowi po lewej

Operatory logiczne
Su porwnaniu zawartoci dwch zmiennych wedug okrelonych kryteriw:

Operator == > >= < <= !=

Rodzaj porwnania czy rwne wikszy wikszy bd rwny mniejszy mniejszy bd rwny czy rny(nierwny)

S jeszcze operatory, suce do grupowania porwna (Patrz te:logika w Wikipedi):

|| && !

lub(OR) i,oraz(AND) negacja(NOT)

206

DODATEK D. SKADNIA

Operatory binarne
S to operatory, ktre dziaaj na bitach.

operator | & ~ >> << ^

funkcja suma bitowa(OR) iloczyn bitowy negacja bitowa przesunicie bitw o X w prawo przesunicie bitw o X w lewo alternatywa wyczna

przykad 5 2 da w wyniku 7 ( 00000101 OR 00000010 = 00000111 ) 7 & 2 da w wyniku 2 ( 00000111 AND 00000010 = 00000010 ) 2 da w wyniku 253 ( NOT 00000010 = 11111101 ) 7 2 da w wyniku 1 ( 00000111 >> 2 = 00000001 ) 7 2 da w wyniku 28 ( 00000111 << 2 = 00011100 ) 7 2 da w wyniku 5 ( 00000111 00000010 = 00000101 )

Operatory inkrementacji/dekrementacji
Su do dodawania/odejmowania od liczby wartoci jeden. Przykady:

Operacja x++ ++x x x

Opis operacji zwikszy warto w x o jeden zwikszy warto w x o jeden zmniejszy warto w x o jeden zmniejszy warto w x o jeden

Warto wyraenia warto zmiennej x przed zmian warto zmiennej x powikszona o jeden warto zmiennej x przed zmian warto zmiennej x pomniejszona o jeden

Par przykadw dla zrozumienia:

int a=7; if ((a++)==7) /* najpierw porwnuje, potem dodaje */ printf ("%d\n",a); /* wypisze 8 */ if ((++a)==9) /* najpierw dodaje, potem porwnuje */ printf ("%d\n", a); /* wypisze 9 */

Analogicznie ma si sytuacja z operatorami dekrementacji.

TYPY DANYCH

207

Pozostae
Operacja *x Opis operacji operator wyuskania dla wskanika Warto wyraenia warto trzymana w pamici pod adresem przechowywanym we wskaniku zwraca adres zmiennej zwraca element tablicy o indeksie a (numerowanym od zera) wybiera skadnik ze struktury lub unii wybiera skadnik ze struktury, gdy uywamy wskanika do struktury zamiast zwykej zmiennej zwraca rozmiar typu w bajtach

&x x[a] x.a x->a

operator pobrania adresu operator wybrania elementu tablicy operator wyboru skadnika a ze zmiennej x operator wyboru skadnika a przez wskanik do zmiennej x operator pobrania rozmiaru typu

sizeof (typ)

Operator ternarny
Istnieje jeden operator przyjmujcy trzy argumenty jest to operator wyraenia warunkowego: a ? b : c. Zwraca on b gdy a jest prawd lub c w przeciwnym wypadku.

Typy danych
Tabela D.2: Typy danych wedug rnych specykacji jzyka C Typ char Opis Typy danych wg norm C89 i C90 Suy gwnie do przechowywania znakw. Od kompilatora zaley, czy jest to liczba ze znakiem czy bez; w wikszoci kompilatorw jest liczb ze znakiem Typ char ze znakiem Typ char bez znaku Wystpuje, gdy docelowa maszyna wyszczeglnia krtki typ danych cakowitych, w przeciwnym wypadku jest tosamy z typem int. Czsto ma rozmiar jednego sowa maszynowego Liczba typu short bez znaku Podobnie jak short uywana do zredukowania zuycia pamici przez program Liczba cakowita, odpowiadajca podstawowemu rozmiarowi liczby cakowitej w danym komputerze. Podstawowy typ dla liczb cakowitych Liczba cakowita bez znaku * Duga liczba cakowita Duga liczba cakowita Inne nazwy

signed char unsigned char short

short int, signed short, signed short int unsigned short int

unsigned short

int

signed int, signed

unsigned long unsigned long

unsigned int long int, signed long, signed long int

oat

Podstawowy typ do przechowywania liczb zmiennoprzecinkowych. W nowszym standardzie zgodny jest z norm IEEE 754. Nie mona stosowa go z modykatorem signed ani unsigned

208

DODATEK D. SKADNIA

double

long double

Bool long long

Liczba zmiennoprzecinkowa podwjnej precyzji. Podobnie jak oat nie czy si z modykatorem signed ani unsigned Najwiksza moliwa dokadno liczb zmiennoprzecinkowych. Nie czy si z modykatorem signed ani unsigned. Typy danych wedug normy C99 Przechowuje wartoci 0 lub 1 Nowy typ, umoliwiajcy obliczeniach na bardzo duych liczbach cakowitych bez uycia typu oat Dugie liczby cakowite bez znaku Suzy do przechowywania liczb zespolonych Suzy do przechowywania liczb zespolonych Suzy do przechowywania liczb zespolonych Typy danych deniowane przez uytkownika Wicej o kompilowaniu. Rozmiar typu jest taki jak rozmiar najwikszego pola Nowo zdeniowany typ przyjmuje taki sam rozmiar, jak typ macierzysty Zwykle elementy maj tak sam dugo, jak typ int.

unsigned long long oat Complex double Complex long double Complex struct union typedef enum

long long int, signed long long, signed long long int unsigned long long int

Zalenoci rozmiaru typw danych s nastpujce: sizeof(cokolwiek ) = sizeof(signed cokolwiek ) = sizeof(unsigned cokolwiek ); 1 = sizeof(char) sizeof(oat) sizeof(short) sizeof(int) sizeof(long) sizeof(long long); sizeof(double) sizeof(long double); sizeof(cokolwiek *);

sizeof(cokolwiek Complex) = 2 * sizeof(cokolwiek ) sizeof(void *) = sizeof(char *) sizeof(cokolwiek *) = sizeof(signed cokolwiek *) = sizeof(unsigned cokolwiek *); sizeof(cokolwiek *) = sizeof(const cokolwiek *). Dodatkowo, jeeli przez V(typ) oznaczymy liczb bitw wykorzystywanych w typie to zachodzi: 8 16 16 32 64 V(char) = V(signed char) = V(unsigned char); V(short) = V(unsigned short); V(int) = V(unsigned int); V(long) = V(unsigned long); V(long long) = V(unsigned long long); V(short) V(int) V(long) V(long long).

V(char)

Dodatek E

Przykady z komentarzem
Liczby losowe
Poniszy program generuje wiersz po wierszu macierz o okrelonych przez uytkownika wymiarach, zawierajc losowo wybrane liczby. Kady wygenerowany wiersz macierzy zapisywany jest w pliku tekstowym o wprowadzonej przez uytkownika nazwie. W pierwszym wierszu pliku wynikowego zapisano wymiary utworzonej macierzy. Program napisany i skompilowany zosta w rodowisku GNU/Linux. #include <stdio.h> #include <stdlib.h> #include <time.h> main() { int i, j, n, m; float re; FILE *fp; char fileName[128]; printf("Wprowadz nazwe pliku wynikowego..\n"); scanf("%s",&fileName); printf("Wprowadz po sobie liczbe wierszy i kolumn macierzy oddzielone spacj..\n"); scanf("%d %d", &n, &m); /* jeeli wystpi bd w otwieraniu pliku i go nie otwarto, wwczas funkcja fclose(fp) wywoana na kocu programu zgosi bd wykonania i wysypie nam program z dziaania, std musimy umieci warunek, ktry w kontrolowany sposb zatrzyma program (funkcja exit;) */ if ( (fp = fopen(fileName, "w")) == NULL ) { puts("Otwarcie pliku nie jest mozliwe!"); exit; /* jeli w procedurze glownej to piszemy bez nawiasow */ } else { puts("Plik otwarty prawidowo.."); } /* dla funkcji rand() oraz srand() */ /* dla funkcji [time() */

209

210

DODATEK E. PRZYKADY Z KOMENTARZEM

fprintf(fp, "%d %d\n", n, m); /* w pierwszym wierszu umieszczono wymiary macierzy */ srand( (unsigned int) time(0) ); for (i=1; i<=n; ++i) { for (j=1; j<=m; ++j) { re = ((rand() % 200)-100)/ 10.0; fprintf(fp,"%.1f", re ); if (j!=m) fprintf(fp," "); } fprintf(fp,"\n"); } fclose(fp); return 0; }

Zamiana liczb dziesitnych na liczby w systemie dwjkowym


Zajmijmy si teraz innym zagadnieniem. Wiemy, e komputer zapisuje wszystkie liczby w postaci binarnej (czyli za pomoc jedynek i zer). Sprbujmy zatem zamieni liczb, zapisan w naszym dziesitkowym systemie na zapis binarny. Uwaga: Program dziaa jedynie dla liczb od 0 do maksymalnej wartoci ktr moe przyj typ unsigned short int w twoim kompilatorze.

#include <stdio.h> #include <limits.h> void dectobin (unsigned short a) { int licznik; /* CHAR_BIT to liczba bitw w bajcie */ licznik = CHAR_BIT * sizeof(a); while (--licznik >= 0) { putchar(((a >> licznik) & 1)) ? 1 : 0); } } int main () { unsigned short a; printf ("Podaj liczb od 0 do %hd: ", USHRT_MAX); scanf ("%hd", &a); printf ("%hd(10) = ", a); dectobin(a); printf ("(2)\n"); return 0; }

211

Zalek przegldarki
Zajmiemy si tym razem inn kwesti, a mianowicie programowaniem sieci. Jest to zagadnienie bardzo ostatnio popularne. Nasz program bdzie mia za zadanie poczy si z serwerem, ktrego adres uytkownik bdzie podawa jako pierwszy parametr programu, wysa zapytanie HTTP i odebra tre, ktr wyle do nas serwer. Zacznijmy moe od tego, e obsuga sieci jest niemal identyczna w rnych systemach operacyjnych. Na przykad midzy systemami z rodziny Unix oraz Windowsem rnica polega tylko na doczeniu innych plikw nagwkowych (dla Windowsa winsock2.h). Przeanalizujmy zatem poniszy kod: #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <unistd.h> <arpa/inet.h> <sys/types.h> <netinet/in.h> <sys/socket.h>

#define MAXRCVLEN 512 #define PORTNUM 80 char *query = "GET / HTTP1.1\n\n"; int main(int argc, char *argv[]) { char buffer[MAXRCVLEN+1]; int len, mysocket; struct sockaddr_in dest; char *host_ip = NULL; if (argc != 2) { printf ("Podaj adres serwera!\n"); exit (1); } host_ip = argv[1]; mysocket = socket(AF_INET, SOCK_STREAM, 0); dest.sin_family = AF_INET; dest.sin_addr.s_addr = inet_addr(host_ip); /* ustawiamy adres hosta */ dest.sin_port = htons (PORTNUM); /* numer portu przechowuje dwubajtowa zmienna musimy ustali porzdek sieciowy - Big Endian */ memset(&(dest.sin_zero), \0, 8); /* zerowanie reszty struktury */ connect(mysocket, (struct sockaddr *)&dest,sizeof(struct sockaddr)); /* czymy si z hostem */ write (mysocket, query, strlen(query)); /* wysyamy zapytanie */ len=read(mysocket, buffer, MAXRCVLEN); /* i pobieramy odpowied */ buffer[len]=\0; printf("Rcvd: %s",buffer); close(mysocket); /* zamykamy gniazdo */ return EXIT_SUCCESS; }

212

DODATEK E. PRZYKADY Z KOMENTARZEM

Powyszy przykad moe by odrobin niezrozumiay, dlatego przyda si kilka sw wyjanienia. Pliki nagwkowe, ktre doczamy zawieraj deklaracj nowych dla Ciebie funkcji socket(), connect(), write() oraz read(). Oprcz tego spotkae si z now struktur sockaddr in. Wszystkie te obiekty s niezbdne do stworzenia poczenia.

Dodatek F

Informacje o pliku i historia


Historia
Ta ksika zostaa stworzona na polskojzycznej wersji projektu Wikibooks przez autorw wymienionych poniej w sekcji Autorzy. Najnowsza wersja podrcznika jest dostpna pod adresem http://pl.wikibooks.org/wiki/C.

Informacje o pliku PDF i historia


PDF zosta utworzony przez Derbetha dnia 2 wrzenia 2008 na podstawie wersji z 2 wrzenia 2008 podrcznika na Wikibooks. Wykorzystany zosta poprawiony program Wiki2LaTeX autorstwa uytkownika angielskich Wikibooks, Hagindaza. Wynikowy kod po rcznych poA prawkach zosta przeksztacony w ksik za pomoc systemu skadu L TEX. Najnowsza wersja tego PDF-u jest postpna pod adresem http://pl.wikibooks.org/ wiki/Image:C.pdf.

Autorzy
Adam majewski, Akira, Arfrever, Bercik, Bla, Bociex, Cnr, CzarnyZajaczek, Darek86, Derbeth, Equadus, Evil, Faw, GDR!, Gk180984, Incu, Karol Ossowski, Kj, Lewico, MTM, Michael, Migol, Mina86, Mtfk, Myki, Mythov, Noisy, Pawelkg, Pawlosck, Peter de Sowaro, Piotr, Piotrala, Ponton, Sasek, Sblive, Silbarad, Stefan, T ziel, Warszk, Webprog i anonimowi autorzy.

Graki
Autorzy i licencje grak: graka na okadce: Saint-Elme Gautier, rycina z ksiki Le Corset ` travers les ges, a a Pary 1893; rdo Wikimedia Commons; public domain logo Wikibooks: zastrzeony znak towarowy, c & Foundation, Inc.
TM

All rights reserved, Wikimedia

graka 14.1a (strona 102): autor Claudio Rocchini, rdo Wikimedia Commons, licencja GFDL graka 14.1b (strona 102): autor Adam majewski, rdo Wikimedia Commons, licencja Creative Commons Attribution 3.0 Unported

213

214

DODATEK F. INFORMACJE O PLIKU


graa 16.1 (strona 105): autor Jarkko Piiroinen, rdo Wikimedia Commons, public domain graka 16.2 (strona 108): autor Jarkko Piiroinen, rdo Wikimedia Commons, public domain graka 17 (strona 111): autor Daniel B, rdo Wikimedia Commons, licencja GFDL graka 17.2 (strona 115): autor Daniel B, rdo Wikimedia Commons, licencja GFDL graka 17.3 (strona 115): autor Daniel B, rdo Wikimedia Commons, licencja GFDL graka 17.4 (strona 122): autor Derrick Coetzee, rdo Wikimedia Commons, public domain graka 18.1 (strona 130): autor Jarkko Piiroinen, rdo Wikimedia Commons, public domain

Dodatek G

Dalsze wykorzystanie tej ksiki


Wstp
Ide Wikibooks jest swobodne dzielenie si wiedz, dlatego te uregulowania Wikibooks maj na celu jak najmniejsze ograniczanie moliwoci osb korzystajcych z serwisu i zapewnienie, e treci dostpne tutaj bd mogy by atwo kopiowane i wykorzystywane dalej. Prosimy wszystkich odwiedzajcych, aby powicili chwil na zaznajomienie si z poniszymi zasadami, by unikn w przyszoci nieporozumie.

Status prawny
Caa zawarto Wikibooks (o ile w danym miejscu nie jest zaznaczone inaczej; szczegy niej) jest udostpniona na nastpujcych warunkach: Udziela si zezwolenia na kopiowanie, rozpowszechnianie i/lub modykacj treci artykuw polskich Wikibooks zgodnie z zasadami Licencji GNU Wolnej Dokumentacji (GNU Free Documentation License) w wersji 1.2 lub dowolnej pniejszej opublikowanej przez Free Software Foundation; bez Sekcji Niezmiennych, Tekstu na Przedniej Okadce i bez Tekstu na Tylnej Okadce. Kopia tekstu licencji znajduje si na stronie GNU Free Documentation License. Zasadniczo oznacza to, e artykuy pozostan na zawsze dostpne na zasadach open source i mog by uywane przez kadego z obwarowaniami wyszczeglnionymi poniej, ktre zapewniaj, e artykuy te pozostan wolne. Graki i pliku multimedialne wykorzystane na Wikibooks mog by udostpnione na warunkach innych ni GNU FDL. Aby sprawdzi warunki korzystania z graki, naley przej do jej strony opisu, klikajc na grace.

Wykorzystywanie materiaw z Wikibooks


Jeli uytkownik polskich Wikibooks chce wykorzysta materiay w niej zawarte, musi to zrobi zgodnie z GNU FDL. Warunki te to w skrcie i uproszczeniu:

Publikacja w Internecie
1. dobrze jest wymieni Wikibooks jako rdo (jest to nieobowizkowe, lecz jest dobrym zwyczajem)

215

216

DODATEK G. DALSZE WYKORZYSTANIE TEJ KSIKI

2. naley poda list autorw lub wyranie opisany i funkcjonujcy link do oryginalnej treci na Wikibooks lub historii edycji (wypenia to obowizek podania autorw oryginalnego dziea) 3. trzeba jasno napisa, e tre publikowanego dziea jest objta licencj GNU FDL 4. naley poda link to tekstu licencji (najlepiej zachowanej na wasnym serwerze) 5. posta tekstu nie moe ogranicza moliwoci jego kopiowania

Druk
1. dobrze jest wymieni Wikibooks jako rdo (jest to nieobowizkowe, lecz jest dobrym zwyczajem) 2. naley wymieni 5 autorw z listy w rozdziale Autorzy danego podrcznika (w przypadku braku podobnego rozdziau lista autorw jest dostpna pod odnonikiem historia na grze strony). Gdy podrcznik ma mniej ni 5 autorw, naley wymieni wszystkich. 3. trzeba jasno napisa na przynajmniej jednej stronie, e tre publikowanego dziea jest objta licencj GNU FDL 4. peny tekst licencji, w oryginale i bez zmian, musi by zawarty w ksice 5. jeli zostanie wykonanych wicej niz 100 kopii ksiki konieczne jest: (a) dostarczenie pyt CD, DVD, dyskw lub innych nonikw danych z trecia ksiki w formie moliw do komputerowego przetwarzania; lub: (b) dostarczenie linku do strony z czyteln dla komputera form ksiki (link musi by aktywny przynajmniej przez rok od publikacji; mona uy linku do spisu treci danego podrcznika na Wikibooks) Nie ma wymogu pytania o zgod na wykorzystanie tekstu jakichkolwiek osb z Wikibooks. Autorzy nie mog zabroni nikomu wykorzystywania ich tekstw zgodnie z licencj GNU FDL. Mona korzysta z ksiek jako caoci albo z ich fragmentw. Materiay bazujce na treci z Wikibooks mog by bez przeszkd sprzedawane; zyskami nie trzeba dzieli si z autorami oryginalnego dziea. Jeeli w wykorzystanych materiaach z polskich Wikibooks s treci objte innymi ni GNU FDL licencjami, uytkownik musi speni wymagania zawarte w tych licencjach (dotyczy to szczeglnie grak, ktre mog mie rne licencje).

Dodatek H

GNU Free Documentation License


Version 1.2, November 2002 Copyright c 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

Preamble
The purpose of this License is to make a manual, textbook, or other functional and useful document free in the sense of freedom: to assure everyone the eective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modications made by others. This License is a kind of copyleft, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.

1. APPLICABILITY AND DEFINITIONS


This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The Document, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as you. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A Modied Version of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modications and/or translated into another language.

217

218

DODATEK H. GNU FREE DOCUMENTATION LICENSE

A Secondary Section is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Documents overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The Invariant Sections are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not t the above denition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The Cover Texts are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A Transparent copy of the Document means a machine-readable copy, represented in a format whose specication is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent le format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modication by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not Transparent is called Opaque. Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modication. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The Title Page means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, Title Page means the text near the most prominent appearance of the works title, preceding the beginning of the body of the text. A section Entitled XYZ means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specic section name mentioned below, such as Acknowledgements, Dedications, Endorsements, or History.) To Preserve the Title of such a section when you modify the Document means that it remains a section Entitled XYZ according to this denition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no eect on the meaning of this License.

2. VERBATIM COPYING
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying

219
this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies.

3. COPYING IN QUANTITY
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Documents license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to t legibly, you should put the rst ones listed (as many as t reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.

4. MODIFICATIONS
You may copy and distribute a Modied Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modied Version under precisely this License, with the Modied Version lling the role of the Document, thus licensing distribution and modication of the Modied Version to whoever possesses a copy of it. In addition, you must do these things in the Modied Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modications in the Modied Version, together with at least ve of the principal authors of the Document (all of its principal authors, if it has fewer than ve), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modied Version, as the publisher.

220

DODATEK H. GNU FREE DOCUMENTATION LICENSE

D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modied Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Documents license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled History, Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modied Version as given on the Title Page. If there is no section Entitled History in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modied Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the History section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled Acknowledgements or Dedications, Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled Endorsements. Such a section may not be included in the Modied Version. N. Do not retitle any existing section to be Entitled Endorsements or to conict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modied Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modied Versions license notice. These titles must be distinct from any other section titles. You may add a section Entitled Endorsements, provided it contains nothing but endorsements of your Modied Version by various partiesfor example, statements of peer review or that the text has been approved by an organization as the authoritative denition of a standard. You may add a passage of up to ve words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modied Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modied Version.

5. COMBINING DOCUMENTS

221
You may combine the Document with other documents released under this License, under the terms dened in section 4 above for modied versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodied, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but dierent contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled History in the various original documents, forming one section Entitled History; likewise combine any sections Entitled Acknowledgements, and any sections Entitled Dedications. You must delete all sections Entitled Endorsements.

6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.

7. AGGREGATION WITH INDEPENDENT WORKS


A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an aggregate if the copyright resulting from the compilation is not used to limit the legal rights of the compilations users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Documents Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.

8. TRANSLATION
Translation is considered a kind of modication, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.

222

DODATEK H. GNU FREE DOCUMENTATION LICENSE

If a section in the Document is Entitled Acknowledgements, Dedications, or History, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.

9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

10. FUTURE REVISIONS OF THIS LICENSE


The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may dier in detail to address new problems or concerns. See http://www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document species that a particular numbered version of this License or any later version applies to it, you have the option of following the terms and conditions either of that specied version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.

ADDENDUM: How to use this License for your documents


To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright c YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled GNU Free Documentation License. If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the with . . . Texts. line with this: with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.

Skorowidz
adres, 111 argc, 79 argv, 79 biblioteka standardowa, 95 blok, 28 C jzyk, 13 dekrementacja, 46 dynamiczna alokacja pamici, 120 enum, 141 funkcja, 75 denicja, 76 deklaracja, 82 rekurencyjna, 81 inkrementacja, 46 komentarz, 30 kompilator lista, 17 uywanie, 19 konwersja, 44 libc, 95 main, 79 makele, 155 napis, 129 porwnywanie, 132 NULL, 118 operator dekrementacji, 46 inkrementacji, 46 modulo, 45 pobrania adresu, 112 sizeof, 121 wyuskania, 113 wyboru skadnika, 143 wyraenia warunkowego, 50 plik czytanie i pisanie, 97 nagowkowy, 151 preprocesor, 87 prototyp funkcji, 83 przekazywanie argumentw do funkcji przez warto, 81 przez wskanik, 117 przepenienie bufora, 134 przesunicie bitowe, 47 rzutowanie, 44 sowa kluczowe, 203 sizeof, 121 staa, 35 struktura, 143 tablica, 105 wielowymiarowa, 122 znakw, 129 typ, 36 deniowanie, 141 wyliczeniowy, 141 unia, 142 void, 38 jako typ zwracany, 76 na licie argumentw, 76 void*, 115 volatile, 40 wejcie/wyjcie, 65 wskanik, 111 wyciek pamici, 121 zmienna, 33 globalna, 34 lokalna, 34 statyczna, 40 znaki specjalne, 131

223

You might also like