C. Rusz głową! [PDF]

W obecnych czasach triumfy święcą platforma .NET, Java oraz HTML5 i JavaScript. Mogłoby się wydawać, że język C i inne p

134 78 22MB

Polish Pages 616 Year 2013

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Spis treści......Page 9
Wprowadzenie......Page 27
Dla kogo jest przeznaczona ta książka?......Page 28
Wiemy, co sobie myślisz......Page 29
Zmuś swój mózg do posłuszeństwa......Page 31
Przeczytaj to......Page 34
Zespół recenzentów technicznych......Page 36
Podziękowania......Page 37
1. Dajmy nurka......Page 39
C to język do pisania małych, szybkich programów......Page 40
Ale jak wygląda skompilowany program napisany w C?......Page 43
A jak można uruchomić program?......Page 47
Dwa rodzaje poleceń......Page 52
Oto kod, jakim aktualnie dysponujemy......Page 53
Liczenie kart? W języku C?......Page 55
Wartości logiczne to nie tylko sprawdzanie równości......Page 56
Jak aktualnie wygląda nasz kod?......Page 63
Pociąg do Switcherado......Page 64
Czasami jeden raz nie wystarcza…......Page 67
Pętle często mają taką samą strukturę…......Page 68
Instrukcji break używamy, by wydostać się z pętli…......Page 69
Twój niezbędnik C......Page 78
2. Na co wskazujesz?......Page 79
Kod C zawiera wskaźniki......Page 80
Grzebiemy w pamięci......Page 81
Stawiamy żagle ze wskaźnikami......Page 82
Spróbujmy przekazać wskaźnik do zmiennej......Page 85
Stosowanie wskaźników......Page 86
Jak przekazać łańcuch znaków do funkcji?......Page 91
Zmienne tablicowe są jak wskaźniki…......Page 92
Co myśli komputer, wykonując nasz kod?......Page 93
Jednak zmienne tablicowe nie są tak do końca wskaźnikami......Page 97
Dlaczego tablice naprawdę zaczynają się od 0?......Page 99
Dlaczego wskaźniki mają typ?......Page 100
Stosowanie wskaźników do wprowadzania danych......Page 103
Używając funkcji scanf(), uważaj!......Page 104
Alternatywą dla scanf() jest fgets()......Page 105
Literały łańcuchowe nie mogą być nigdy modyfikowane......Page 110
Jeśli chcesz zmienić łańcuch — skopiuj go......Page 112
Ściąga z pamięci......Page 118
Twój niezbędnik C......Page 119
2,5. Teoria łańcuchów......Page 121
Desperacko poszukuję Zuzy......Page 122
Utwórz tablicę tablic......Page 123
Odnajdywanie łańcucha zawierającego określony tekst......Page 124
Stosowanie funkcji strstr()......Page 127
Czas na przegląd kodu......Page 132
Tablica tablic czy tablica wskaźników?......Page 136
Twój niezbędnik C......Page 137
3. Rób jedną rzecz, ale rób ją dobrze......Page 139
Małe programy narzędziowe mogą rozwiązywać wielkie problemy......Page 140
Oto sposób wykorzystania programu......Page 144
Ale my nie używamy plików…......Page 145
Możesz skorzystać z przekierowania......Page 146
Przedstawiamy standardowy strumień błędów......Page 156
Domyślnie strumień błędów jest wyświetlany na ekranie......Page 157
fprintf() zapisuje dane w strumieniu......Page 158
Zaktualizujmy kod, by korzystał z funkcji fprintf()......Page 159
Niewielkie programy narzędziowe są elastyczne......Page 164
Nie zmieniaj programu geo2json......Page 165
Różne zadania wymagają różnych narzędzi......Page 166
Połącz wejście i wyjście przy użyciu potoku......Page 167
Program narzędziowy bermuda......Page 168
A jeśli chcemy przekazywać wyniki do więcej niż jednego pliku?......Page 173
Stwórz swoje własne strumienie danych......Page 174
Nieco więcej o funkcji main()......Page 177
Niech biblioteka wykona pracę za nas......Page 185
Twój niezbędnik C......Page 192
4. Podziel go, rozbuduj go......Page 193
Krótki przewodnik po typach danych......Page 198
Nie umieszczaj czegoś dużego w czymś małym......Page 199
Użyj rzutowania, by zapisać wartość zmiennoprzecinkową w zmiennej całkowitej......Page 200
O nie… to bezrobotni aktorzy…......Page 204
Zobaczmy, co się stało z kodem......Page 205
Kompilatory nie lubią niespodzianek......Page 207
Oddziel deklaracje od definicji......Page 209
Tworzenie pierwszego pliku nagłówkowego......Page 210
Jeśli oprogramowałeś często używane operacje…......Page 218
Możesz rozdzielić kod, umieszczając go w osobnych plikach......Page 219
Za kulisami kompilacji......Page 220
Współdzielony kod trzeba umieścić w osobnym pliku źródłowym......Page 222
To nie jest kosmiczna technologia… a może jest?......Page 225
Nie rekompiluj każdego pliku......Page 226
Najpierw skompiluj źródła do plików obiektowych......Page 227
Ciągłe śledzenie zmian w plikach jest trudne......Page 232
Zautomatyzuj kompilacje, używając narzędzia make......Page 234
Jak działa make......Page 235
Przekaż informacje o kodzie, używając pliku makefile......Page 236
Startujemy!......Page 241
Twój niezbędnik C......Page 242
1. Laboratorium C. Arduino......Page 243
5. Wytocz swoje własne struktury......Page 253
Czasami musisz rozdawać wiele danych......Page 254
Rozmowa trójstronna......Page 255
Twórz swoje własne strukturalne typy danych przy użyciu struct......Page 256
Po prostu daj im rybkę......Page 257
Odczytuj pola struktur, używając operatora "."......Page 258
Czy można umieścić jedną strukturę wewnątrz drugiej?......Page 263
Jak zaktualizować zawartość struktury?......Page 272
Ten kod klonuje żółwie......Page 274
Potrzebny będzie wskaźnik na strukturę......Page 275
(*t).age kontra *t.age......Page 276
Czasami rzeczy podobnego typu wymagają danych różnych typów......Page 282
Unie pozwalają używać bloku pamięci na różne sposoby......Page 283
Jak stosować unie?......Page 284
Zmienna typu enum przechowuje symbol......Page 291
Czasami potrzebujemy kontroli na poziomie bitów......Page 297
Pola bitowe zawierają dowolną liczbę bitów......Page 298
Twój niezbędnik C......Page 302
6. Budowanie mostów......Page 303
Czy potrzebujesz elastycznego sposobu przechowywania danych?......Page 304
Listy połączone przypominają łańcuchy danych......Page 305
Listy połączone pozwalają na dodawanie elementów......Page 306
Tworzenie struktur rekurencyjnych......Page 307
Tworzenie wysp w języku C…......Page 308
Wstawianie elementów pośrodku listy......Page 309
Pamięć dynamiczną rezerwuj na stercie......Page 314
Zwróć pamięć, kiedy nie będzie już potrzebna......Page 315
Proś o pamięć, wywołując malloc()…......Page 316
Popraw kod, używając funkcji strdup()......Page 322
Zwalniaj pamięć, gdy jej nie potrzebujesz......Page 326
Przegląd systemu SPIES......Page 336
Detektywi oprogramowania: stosowanie valgrind......Page 338
Skorzystaj z programu valgrind kilkakrotnie, by zebrać więcej dowodów......Page 339
Przeanalizuj dowody......Page 340
Sprawdzenie poprawek......Page 343
Twój niezbędnik C......Page 345
7. Odpicuj swoje funkcje na maksa!......Page 347
Szukając Pana Doskonałego…......Page 348
Przekaż kod do funkcji......Page 352
Musisz przekazać funkcji find() nazwę funkcji testującej......Page 353
Każda nazwa funkcji jest wskaźnikiem do tej funkcji…......Page 354
…ale nie ma żadnego typu danych reprezentującego funkcję......Page 355
Jak utworzyć wskaźnik do funkcji......Page 356
Posortuj to, używając standardowej biblioteki C......Page 361
Użyj wskaźnika do funkcji, by określić porządek sortowania......Page 362
Automatyzacja generowania listów do Jana......Page 370
Stwórz tablicę wskaźników do funkcji......Page 374
Zapewnij swoim funkcjom elastyyyyyczność......Page 379
Twój niezbędnik C......Page 386
8. Wymienialny kod......Page 387
Kod, który możesz zabrać do banku......Page 388
Nawiasy kątowe dołączają standardowe pliki nagłówkowe......Page 390
A co zrobić, jeśli będziesz chciał współużytkować jakiś kod?......Page 391
Współużytkowanie plików nagłówkowych......Page 392
Współużytkowanie plików .o poprzez określanie pełnej ścieżki dostępu......Page 393
Archiwum zawiera pliki .o......Page 394
Utwórz archiwum, używając polecenia ar…......Page 395
I w końcu kompiluj inne programy......Page 396
Siłownia Rusz Głową wchodzi na scenę globalną......Page 401
Obliczanie spalonych kalorii......Page 402
Ale zagadnienie jest nieco bardziej skomplikowane…......Page 405
Programy składają się z wielu fragmentów…......Page 406
Łączenie dynamiczne następuje podczas działania programu......Page 408
Czy pliki .a można łączyć podczas działania programu?......Page 409
Najpierw utwórz plik obiektowy......Page 410
Nazewnictwo bibliotek dynamicznych zależy od platformy systemowej......Page 411
Twój niezbędnik C......Page 423
2. Laboratorium C. OpenCV......Page 425
9. Przekraczanie granic......Page 433
Wywołania systemowe są Twoją gorącą linią z systemem operacyjnym......Page 434
Wtem ktoś włamał się do systemu…......Page 438
Bezpieczeństwo nie jest jedynym problemem......Page 439
Funkcja exec() zapewnia większą kontrolę......Page 440
Istnieje wiele funkcji exec()......Page 441
Funkcje z tablicą argumentów: execv(), execvp() oraz execve()......Page 442
Przekazywanie zmiennych środowiskowych......Page 443
Większość wywołań systemowych zawodzi w taki sam sposób......Page 444
Czytaj doniesienia, używając RSS......Page 452
exec() jest końcem rodu naszego programu......Page 456
Uruchamianie procesu potomnego przy użyciu funkcji fork() i exec()......Page 457
Twój niezbędnik C......Page 463
10. Dobrze jest porozmawiać......Page 465
Przekierowania strumieni wejściowych......Page 466
Zajrzyjmy do wnętrza standardowego procesu......Page 467
Przekierowanie zastępuje deskryptor......Page 468
Funkcja fileno() zwraca deskryptor......Page 469
Czasami trzeba poczekać…......Page 474
Bądź w kontakcie ze swymi potomkami......Page 478
Połącz swoje procesy potokami......Page 479
Studium przypadku: otwieranie doniesień w przeglądarce......Page 480
W procesie rodzicielskim......Page 481
Otwieranie strony w przeglądarce......Page 482
Śmierć procesu......Page 487
Przechwytywanie sygnałów i wykonywanie własnego kodu......Page 488
Struktury sigaction są rejestrowane przy użyciu funkcji sigaction()......Page 489
Modyfikacja kodu i wykorzystanie procedury obsługi sygnałów......Page 490
Używaj polecenia kill, by wysyłać sygnały......Page 493
Wysyłanie do procesu sygnału pobudki......Page 494
Twój niezbędnik C......Page 502
11. Nie ma drugiego takiego miejsca jak 127.0.0.1......Page 503
Internetowy serwer puk-puk......Page 504
Prezentacja serwera puk-puk......Page 505
PNAR — jak serwery komunikują się z internetem......Page 506
Gniazdo nie jest typowym strumieniem danych......Page 508
Czasami serwer nie uruchamia się prawidłowo......Page 512
Dlaczego mama zawsze powtarzała Ci, byś sprawdzał błędy......Page 513
Odczyt danych przesyłanych przez klienta......Page 514
Serwer może rozmawiać tylko z jednym klientem naraz......Page 521
Możesz użyć fork(), by obsłużyć oba klienty jednocześnie......Page 522
Pisanie klienta WWW......Page 526
To zadanie klienta......Page 527
Utwórz gniazdo dla adresu IP......Page 528
Funkcja getaddrinfo() pobiera adresy domen......Page 529
Twój niezbędnik C......Page 536
12. To równoległy świat......Page 537
Zadania są sekwencyjne… lub nie…......Page 538
…a procesy nie zawsze są właściwą odpowiedzią......Page 539
Proste procesy robią po jednej rzeczy naraz......Page 540
Zatrudnij dodatkowych pracowników: skorzystaj z wątków......Page 541
Jak się tworzy wątki?......Page 542
Utwórz wątki, używając funkcji pthread_create()......Page 543
Ten kod nie jest wielobieżny......Page 548
Potrzebujesz sygnalizacji świetlnej......Page 549
Użyj muteksu jako sygnalizacji świetlnej......Page 550
Twój niezbędnik C......Page 557
3. Laboratorium C. Blasteroidy......Page 559
A. Dziesięć najważniejszych rzeczy (których nie opisaliśmy)......Page 575
1. Operatory......Page 576
2. Dyrektywy preprocesora......Page 578
3. Słowo kluczowe static......Page 579
4. Jak duże to jest?......Page 580
5. Automatyczne testowanie......Page 581
6. Więcej o gcc......Page 582
7. Więcej o programie make......Page 584
8. Narzędzia programistyczne......Page 586
9. Tworzenie graficznego interfejsu użytkownika......Page 587
Witryny WWW......Page 588
B. Powtórka z całego materiału......Page 589
Skorowidz......Page 611

C. Rusz głową! [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

Tytuł oryginału: Head First C Tłumaczenie: Piotr Rajca ISBN: 978-83-246-5235-8 © 2013 Helion S.A. Authorized Polish translation of the English edition of Head First C, 1st edition, ISBN 9781449399917 © David Griffiths and Dawn Griffiths. This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Wydawnictwo HELION dołożyło wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie bierze jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Wydawnictwo HELION nie ponosi również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/cruszg.zip Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/cruszg_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Poleć książkę na Facebook.com • Kup w wersji papierowej • Oceń książkę

• Księgarnia internetowa • Lubię to! » Nasza społeczność

Opinie o książce C. Rusz głową! „Całkiem możliwe, że C. Rusz głową! mogłoby się okazać najlepszą książką o C wszech czasów. I wcale nie mówię tego lekkim tonem. Bez trudu mógłbym sobie wyobrazić, że ta książka staje się obowiązkową lekturą na wszystkich akademickich kursach programowania w języku C. Większość książek poświęconych programowaniu prowadzi całkowicie przewidywalną ścieżką, prezentując kolejno słowa kluczowe, struktury sterujące, składnię, operatory, typy danych, procedury. Mogą one służyć jako bardzo użyteczne źródło informacji encyklopedycznych oraz jako dobre wprowadzenie do języka, mające charakter nieco akademicki. Jednak ta książka przedstawia podejście całkowicie odmienne. Ona uczy, jak zostać prawdziwym programistą C. Bardzo żałuję, że nie miałem jej 15 lat temu!” — Dave Kitabjian, kierownik działu rozwoju oprogramowania, NetCarrier Telecom „C. Rusz głową! jest przystępnym, lekkim wprowadzeniem do programowania w języku C, napisanym w klasycznym stylu serii Rusz głową!. Obrazki, żarty, ćwiczenia i laboratoria delikatnie, choć konsekwentnie prezentują Czytelnikowi podstawy języka C — tablice, wskaźniki, struktury oraz funkcje — a następnie przechodzą do bardziej zaawansowanych zagadnień związanych ze standardem POSIX oraz programowaniem w systemie operacyjnym Linux, takich jak procesy oraz wątki”. — Vince Milner, programista

Opinie o innych książkach z serii Rusz głową! „Książka Java. Rusz głową! Kathy i Berta upodabnia drukowane strony książki do graficznego interfejsu użytkownika w stopniu, jakiego jeszcze nigdy nie widziałeś. W lekko drwiący, nowoczesny sposób autorzy tej książki sprawiają, że nauka Javy staje się absorbująca, a Czytelnik wciąż zastanawia się: »Co też oni nowego wymyślą?«”. — Warren Keuffel, „Software Development Magazine” „Oprócz zajmującego stylu, który zmienia Czytelnika z nowicjusza w zachwyconego wojownika Javy, książka Java. Rusz głową! oferuje całą masę praktycznych zagadnień, które w innych książkach są pozostawione jako przerażające »zagadnienia do opracowania we własnym zakresie«. To mądra, lekko kpiąca, nowoczesna i praktyczna pozycja — nie ma wielu książek, które mogą rościć sobie prawa do takiej oceny, sprostać jej, a jednocześnie uczyć Czytelników serializacji obiektów i protokołów sieciowego uruchamiania aplikacji”. — Dr Dan Russell, kierownik badań nad nauką i doświadczeniami użytkowników w IBM Almaden Research Center (uczy także sztucznej inteligencji na Uniwersytecie Stanforda) „Ta książka jest szybka, nonszalancka, zabawna i absorbująca. Uważaj — przez przypadek możesz się jeszcze czegoś nauczyć!” — Ken Arnold, zatrudniony wcześniej na stanowisku starszego inżyniera w Sun Microsystems; współautor (wraz z Jamesem Goslingiem, twórcą języka Java) książki The Java Programming Language „Czuję się, jakby właśnie zdjęto z mojej głowy tysiące kilogramów książek”. — Ward Cunningham, twórca wiki oraz założyciel Hillside Group „To właściwy ton dla przeciętnego, niedbale wyluzowanego programistycznego guru drzemiącego w każdym z nas. Doskonała książka do poznania praktycznych strategii programowania — zmusza mój mózg do działania bez konieczności przebijania się przez wyświechtane i nieświeże profesorskie gadki”. — Travis Kalanick, założyciel sieci Scour i witryny Red Swoosh; umieszczony na liście TR100 „Są książki, które się kupuje, które się zatrzymuje, które się trzyma na biurku, a teraz — dzięki wydawnictwu O’Reilly i zespołowi Head First — jest jeszcze przedostatnia kategoria: książki z serii Rusz głową!. Te książki są zużyte, wymięte, noszone zawsze przy sobie. SQL. Rusz głową! jest zawsze na samym wierzchu sterty moich książek. Rany… ta książka jest u mnie, nawet w wydaniu PDF, wystrzępiona i podarta”. — Bill Sawyer, ATG Curriculum Manager, Oracle „Zachwycająca przejrzystość, humor oraz znacząca doza mądrości zawarte w tej książce sprawiają, że nawet osoby niemające nic wspólnego z programowaniem zaczynają dobrze myśleć o rozwiązywaniu problemów”. — Cory Doctorow, współredaktor Boing Boing; autor książek Down and Out in the Magic Kingdom oraz Someone Comes to Town, Someone Leaves Town

Opinie o innych książkach z serii Rusz głową! „Dostałem tę książkę wczoraj, zacząłem ją czytać i… nie mogłem przestać. Bez dwóch zdań — jest odlotowa. Jest zabawna, lecz opisuje wiele zagadnień, które zostały przedstawione w bardzo trafny sposób. Naprawdę jestem pod wrażeniem”. — Erich Gamma, IBM Distinguished Engineer oraz współautor książki Wzorce projektowe. Rusz głową! „Jedna z najbardziej zabawnych i najdowcipniejszych książek o projektowaniu oprogramowania, jakie kiedykolwiek czytałem”. — Aaron LaBerge, VP Technology, ESPN.com „To, co kiedyś było długim procesem nauki pełnym prób i błędów, zostało zgrabnie zredukowane do postaci zajmującej książki”. — Mike Davidson, prezes Newsvine, Inc. „Elegancki projekt jest wizytówką każdego rozdziału tej książki, a każde pojęcie jest prezentowane z równą dozą pragmatyzmu i dowcipu”. — Ken Goldstein, wiceprezes Disney Online „Ja ♥ Head First HTML with CSS and XHTML — ta książka uczy wszystkiego, co powinieneś wiedzieć, przedstawiając informacje w zabawny sposób”. — Sally Applin, projektantka interfejsów użytkownika i artystka „Zwykle czytając książkę lub artykuł na temat wzorców projektowych, muszę raz na jakiś czas zatrzymać się, aby sprawdzić, czy zwracam uwagę. Nie w przypadku tej książki. Nieważne, jak głupio to zabrzmi, ale dzięki tej książce nauka wzorców projektowych to sama przyjemność. Podczas gdy inne książki na temat wzorców projektowych są nudne jak »bla, bla, bla«, ta książka jest jak śpiew na platformie »Bawmy się, dziewczyno!«”. — Eric Wuehler „Dosłownie kocham tę książkę. W rzeczywistości pocałowałem tę książkę na oczach mojej żony”. — Satish Kumar

Inne książki wydawnictwa O’Reilly o tej samej tematyce: C in a Nutshell Język C. Programowanie C. Leksykon kieszonkowy Algorytmy w C C i C++. Bezpieczne programowanie. Receptury

Inne książki z serii Rusz głową! Analiza i projektowanie obiektowe. Rusz głową! C#. Rusz głową! Excel. Rusz głową! Head First Ajax. Edycja polska Head First Algebra. Edycja polska Head First Design Patterns. Edycja polska Head First EJB. Edycja polska Head First HTML with CSS & XHTML. Edycja polska Head First JavaScript. Edycja polska Head First Object-Oriented Analysis and Design. Edycja polska Head First PHP & MySQL. Edycja polska Head First Ruby on Rails. Edycja polska Head First Servlets & JSP. Edycja polska. Wydanie II Head First Software Development. Edycja polska Head First Web Design. Edycja polska Head First. Fizyka. Edycja polska Head First. Sieci komputerowe. Edycja polska Head First. Statystyka. Edycja polska HTML5. Rusz głową! Java. Rusz głową! Wydanie II jQuery. Rusz głową! Mobile Web. Rusz głową! SQL. Rusz głową! Wzorce projektowe. Rusz głową!

Dennisowi Ritchiemu (1941–2011), ojcu języka C

O autorach

Autorzy C. Rusz głową!

iths David Griff

Dawn Griffiths

David Griffiths zaczął programować w wieku 12 lat, kiedy obejrzał film dokumentalny poświęcony pracom Seymour Papert. W wieku 15 lat napisał implementację języka LOGO opracowanego przez Papert. Po zakończeniu studiów matematycznych na uniwersytecie zaczął pisać kod przeznaczony dla komputerów oraz artykuły w czasopismach dla ludzi. Pracował jako instruktor zwinnych metod programowania, programista, parkingowy, ale nie w takiej kolejności. Potrafi programować w ponad 10 językach i pisać prozę tylko w jednym, a kiedy nie pisze ani nie zajmuje się doradztwem, spędza większość czasu ze swoją uroczą żoną — i współautorką tej książki — Dawn. Przed C. Rusz głową! David napisał także dwie inne książki tej serii: Head First Rails oraz Head First Programming. Można go śledzić na Twitterze: http://twitter.com/dogriffiths.

viii

Wprowadzenie

Dawn Griffiths zaczynała jako matematyk na jednym z czołowych angielskich uniwersytetów, gdzie ukończyła studia matematyczne z wyróżnieniem. Później rozpoczęła karierę w branży produkcji oprogramowania i dysponuje już 15-letnim doświadczeniem w branży IT. Przed połączeniem sił z Davidem przy pracy nad książką C. Rusz głową! Dawn napisała dwie inne książki z tej serii (Head First. Statystyka. Edycja polska oraz Head First 2D Geometry) i brała udział w pracach nad wieloma innymi. Kiedy nie pracuje nad żadną z książek Rusz głową!, spędza czas, poprawiając swoje umiejętności z zakresu tai-chi, biegając, robiąc koronki i gotując. Uwielbia także podróżować i spędzać czas ze swym mężem, Davidem.

Spis treści

Spis treści (skrócony) Wprowadzenie

xxvii

1

Zaczynamy poznawać C. Dajmy nurka

1

2

Pamięć i wskaźniki. Na co wskazujesz?

41

2,5 Łańcuchy znaków. Teoria łańcuchów

83

3

Tworzenie małych programów narzędziowych. Rób jedną rzecz, ale rób ją dobrze

101

4

Stosowanie wielu plików źródłowych. Podziel go, rozbuduj go

155

5

1. laboratorium C. Arduino

205

Struktury, unie i pola bitowe. Wytocz swoje własne struktury

215

6

Struktury danych i pamięć dynamiczna. Budowanie mostów

265

7

Zaawansowane funkcje. Odpicuj swoje funkcje na maksa!

309

8

Biblioteki statyczne i dynamiczne. Wymienialny kod

349

2. laboratorium C. OpenCV

387

9

Procesy i wywołania systemowe. Przekraczanie granic

395

10

Komunikacja pomiędzy procesami. Dobrze jest porozmawiać

427

11

Gniazda i komunikacja sieciowa. Nie ma drugiego takiego miejsca jak 127.0.0.1

465

12

Wątki. To równoległy świat

499

3. laboratorium C. Blasteroidy

521

A

Pozostałości. Dziesięć najważniejszych rzeczy (których nie opisaliśmy)

537

B

Zagadnienia programowania w C. Powtórka z całego materiału

551

Spis treści (z prawdziwego zdarzenia)

W

Wprowadzenie Twój mózg jest skoncentrowany na C. Kiedy Ty starasz się czegoś nauczyć, Twój mózg robi Ci przysługę i stara się, by ta wiedza nie została utrwalona. Twój mózg myśli sobie: „Lepiej zostawić miejsce na naprawdę ważne rzeczy, takie jak dzikie zwierzęta, których należy unikać, albo rozważania, czy jeżdżenie na snowboardzie w stroju Adama to dobry pomysł”. Jak zatem możesz oszukać swój mózg i przekonać go, że Twoje życie zależy od znajomości C? Dla kogo jest przeznaczona ta książka? Wiemy, co sobie myślisz Metapoznanie

xxviii xxix xxxi

Zmuś swój mózg do posłuszeństwa

xxxiii

Przeczytaj to

xxxiv

Zespół recenzentów technicznych

xxxvi

Podziękowania

xxxvii

ix

Spis treści

Zaczynamy poznawać C

1

Dajmy nurka Czy chcesz zajrzeć do głowy komputera? Musisz napisać kod działający naprawdę szybko, na przykład na potrzeby nowej gry? A może program na Arduino? Albo we własnej aplikacji na iPhone’a użyć biblioteki napisanej przez kogoś innego? Jeśli tak, to skorzystaj z pomocy bohaterskiego C. C działa na znacznie niższym poziomie niż większość innych języków programowania, a zatem zrozumienie go daje nam znacznie większe pojęcie o tym, co się naprawdę dzieje w programie. C pozwala także lepiej zrozumieć inne języki programowania. A zatem bierz się do pracy, przygotuj kompilator, a już niedługo zaczniesz poznawać C.

C to język do pisania małych, szybkich programów

2

Ale jak wygląda skompilowany program napisany w C?

5

A jak można uruchomić program?

x

9

Dwa rodzaje poleceń

14

Oto kod, jakim aktualnie dysponujemy

15

Liczenie kart? W języku C?

17

Wartości logiczne to nie tylko sprawdzanie równości

18

Jak aktualnie wygląda nasz kod?

25

Pociąg do Switcherado

26

Czasami jeden raz nie wystarcza…

29

Pętle często mają taką samą strukturę…

30

Instrukcji break używamy, by wydostać się z pętli…

31

Twój niezbędnik C

40

Spis treści

Pamięć i wskaźniki

2

Na co wskazujesz? Jeśli naprawdę chcesz zrobić coś odlotowego w języku C, musisz się dowiedzieć, w jaki sposób zarządza on pamięcią. Język C daje nam bardzo dużą kontrolę nad tym, jak programy korzystają z pamięci komputera. W tym rozdziale zajrzysz za kulisy i zobaczysz, co dokładnie dzieje się podczas zapisywania i odczytywania zmiennych. Dowiesz się, jak działają tablice, jak unikać paskudnych błędów w trakcie wykonywania operacji na pamięci, a przede wszystkim przekonasz się, że opanowanie operacji na wskaźnikach i adresowania pozwoli Ci stać się rewelacyjnym programistą C.

Złapaliśmy wiatr w żagle, kapitanie!

Kod C zawiera wskaźniki

42

Grzebiemy w pamięci

43

Stawiamy żagle ze wskaźnikami

44

Spróbujmy przekazać wskaźnik do zmiennej

47

Stosowanie wskaźników

48

Jak przekazać łańcuch znaków do funkcji?

53

Zmienne tablicowe są jak wskaźniki…

54

Co myśli komputer, wykonując nasz kod?

55

Jednak zmienne tablicowe nie są tak do końca wskaźnikami

59

Dlaczego tablice naprawdę zaczynają się od 0?

61

Dlaczego wskaźniki mają typ?

62

Stosowanie wskaźników do wprowadzania danych

65

Używając funkcji scanf(), uważaj!

66

Alternatywą dla scanf() jest fgets()

67

Literały łańcuchowe nie mogą być nigdy modyfikowane

72

Jeśli chcesz zmienić łańcuch — skopiuj go

74

Ściąga z pamięci

80

Twój niezbędnik C

81

Kurs na Cancún!

latitude Arr! Wiosenne wakacje!

32 31 4 100 000 xi

Spis treści

Łańcuchy znaków

2,5

Teoria łańcuchów Korzystanie z łańcuchów nie ogranicza się do ich odczytywania. Przekonałeś się już, że w języku C łańcuchy znaków są w rzeczywistości tablicami danych typu char. Pozostaje jednak pytanie, co język C pozwala nam z nimi robić. W tym właśnie momencie na scenę wkracza string.h. To część standardowej biblioteki języka C, przeznaczona do wykonywania operacji na łańcuchach znaków. Jeśli chcemy połączyć ze sobą dwa łańcuchy, skopiować jeden łańcuch do drugiego bądź też porównać dwa łańcuchy znaków, to wszystkie te operacje można wykonać przy użyciu funkcji zadeklarowanych w pliku nagłówkowym string.h. W tym rozdziale dowiesz się, jak można stworzyć tablicę łańcuchów, a następnie przyjrzysz się nieco bliżej przeszukiwaniu zawartości łańcuchów przy użyciu funkcji strstr().

Desperacko poszukuję Franka

84

Utwórz tablicę tablic

85

Odnajdywanie łańcucha zawierającego określony tekst

86

Stosowanie funkcji strstr()

89

Czas na przegląd kodu

94

Tablica tablic czy tablica wskaźników?

98

Twój niezbędnik C

99

Szukaj łańcucha

Porównaj ze sobą dwa łańcuchy zn akó

w

łańcucha Utwórz kopię

xii

r st

ing

.h

znaków

Podziel łańcuch na małe fragmen

ty

Spis treści

Tworzenie małych programów narzędziowych

3

Rób jedną rzecz, ale rób ją dobrze Każdy system operacyjny udostępnia niewielkie programy narzędziowe. Niewielkie programy narzędziowe pisane w języku C wykonują wyspecjalizowane zadania, takie jak odczytywanie i zapisywanie plików czy też filtrowanie danych. Jeśli chcemy wykonać bardziej złożone zadanie, można nawet połączyć ze sobą kilka takich programów. W jaki jednak sposób tworzy się takie małe programy narzędziowe? W tym rozdziale przyjrzymy się elementom używanym podczas ich tworzenia. Dowiesz się, jak korzystać z opcji wiersza poleceń, jak zarządzać strumieniami informacji, czym są przekierowania, i błyskawicznie zdobędziesz nowe narzędzia.

Małe programy narzędziowe mogą rozwiązywać wielkie problemy

102

Oto sposób wykorzystania programu

106

Ale my nie używamy plików…

107

Możesz skorzystać z przekierowania

108

Przedstawiamy standardowy strumień błędów

118

Domyślnie strumień błędów jest wyświetlany na ekranie

119

fprintf() zapisuje dane w strumieniu

120

Zaktualizujmy kod, by korzystał z funkcji fprintf()

121

Niewielkie programy narzędziowe są elastyczne

126

Nie zmieniaj programu geo2json

127

Różne zadania wymagają różnych narzędzi

128

Połącz wejście i wyjście przy użyciu potoku

129

Program narzędziowy bermuda

130

A jeśli chcemy przekazywać wyniki do więcej niż jednego pliku?

135

Stwórz swoje własne strumienie danych

136

Nieco więcej o funkcji main()

139

Niech biblioteka wykona pracę za nas

147

Twój niezbędnik C

154 Standardowy strumień błędów jest podłączony do ekranu.

Standardowy strumień wejściow y jest podłączony do klawiatury.

Standardowy strumień wyjściowy jest podłączony do ekranu.

xiii

Spis treści

Stosowanie wielu plików źródłowych

4

Podziel go, rozbuduj go Jeśli piszesz duże programy, nie chcesz mieć równie dużych plików źródłowych. Czy jesteś sobie w stanie wyobrazić, jak trudna i czasochłonna byłaby pielęgnacja dużego programu korporacyjnego napisanego w formie jednego pliku źródłowego? W tym rozdziale dowiesz się, jak C pozwala na dzielenie kodu źródłowego na małe, poręczne fragmenty oraz jak potem utworzyć z nich jeden duży program. W międzyczasie dowiesz się także nieco o niuansach związanych z typami danych i poznasz swojego nowego najlepszego przyjaciela — program make.

gcc -c

gcc -o

xiv

Krótki przewodnik po typach danych

160

Nie umieszczaj czegoś dużego w czymś małym

161

Użyj rzutowania, by zapisać wartość zmiennoprzecinkową w zmiennej całkowitej

162

O nie… to bezrobotni aktorzy…

166

Zobaczmy, co się stało z kodem

167

Kompilatory nie lubią niespodzianek

169

Oddziel deklaracje od definicji

171

Tworzenie pierwszego pliku nagłówkowego

172

Jeśli oprogramowałeś często używane operacje…

180

Możesz rozdzielić kod, umieszczając go w osobnych plikach

181

Za kulisami kompilacji

182

Współdzielony kod trzeba umieścić w osobnym pliku źródłowym

184

To nie jest kosmiczna technologia… a może jest?

187

Nie rekompiluj każdego pliku

188

Najpierw skompiluj źródła do plików obiektowych

189

Ciągłe śledzenie zmian w plikach jest trudne

194

Zautomatyzuj kompilacje, używając narzędzia make

196

Jak działa make

197

Przekaż informacje o kodzie, używając pliku makefile

198

Startujemy!

203

Twój niezbędnik C

204

pis is tr Spis treści

1. laboratorium C

Arduino                   !     "  #$        %  % !        % % &

xv

Spis treści

Struktury, unie i pola bitowe

5

Wytocz swoje własne struktury Większość rzeczy w realnym życiu jest nieco bardziej złożona niż proste liczby. Do tej pory poznałeś jedynie podstawowe typy danych dostępne w języku C, ale co można zrobić, by wykroczyć poza to, co dają nam liczby i łańcuchy znaków — gdybyśmy chcieli odwzorować przedmioty z realnego świata? W języku C złożoności realnego świata można odwzorowywać przy użyciu struktur — typu struct. W tym rozdziale dowiesz się, jak łączyć podstawowe typy danych przy użyciu struktur, a nawet jak radzić sobie z niewiadomymi życia przy użyciu unii. Jeśli z kolei wolisz proste odpowiedzi typu „tak” lub „nie”, to mogą Ci się przydać pola bitowe.

To jest Marceli…

…ale do funkcji zostaj e przekazany jego klon.

Czasami musisz rozdawać wiele danych

216

Rozmowa trójstronna

217

Twórz swoje własne strukturalne typy danych przy użyciu struct

218

Po prostu daj im rybkę

219

Odczytuj pola struktur, używając operatora "."

220

Czy można umieścić jedną strukturę wewnątrz drugiej?

225

Jak zaktualizować zawartość struktury?

234

Ten kod klonuje żółwie

236

Potrzebny będzie wskaźnik na strukturę

237

(*t).age kontra *t.age

238

Czasami rzeczy podobnego typu wymagają danych różnych typów

244

Unie pozwalają używać bloku pamięci na różne sposoby

245

Jak stosować unie?

246

Zmienna typu enum przechowuje symbol

253

Czasami potrzebujemy kontroli na poziomie bitów

259

Pola bitowe zawierają dowolną liczbę bitów

260

Twój niezbędnik C

264

Oto żółw „t”.

xvi

Spis treści

Struktury danych i pamięć dynamiczna

6

Budowanie mostów Czasami prosta struktura nie wystarcza. Aby zamodelować bardzo złożone dane, często będziemy musieli łączyć ze sobą struktury. W tym rozdziale zobaczysz, jak korzystać ze wskaźników na dane typu struct, by łączyć niestandardowe typy danych w duże i złożone struktury danych. Poznasz kluczowe zasady, ucząc się tworzyć listy połączone. Dowiesz się także, jak sprawić, by Twoje struktury danych radziły sobie ze zmienną liczbą informacji, wykorzystując do tego celu dynamiczne przydzielanie pamięci na stercie i zwalniając używaną pamięć, kiedy nie będzie już potrzebna. A gdy porządkowanie pamięci stanie się zbyt trudne, dowiesz się, jak może nam pomóc program valgrind. Czy potrzebujesz elastycznego sposobu przechowywania danych?

266

Listy połączone przypominają łańcuchy danych

267

Listy połączone pozwalają na dodawanie elementów

268

Tworzenie struktur rekurencyjnych

269

Tworzenie wysp w języku C…

270

Wstawianie elementów pośrodku listy

271

Pamięć dynamiczną rezerwuj na stercie

276

Zwróć pamięć, kiedy nie będzie już potrzebna

277

Proś o pamięć, wywołując malloc()…

278

Popraw kod, używając funkcji strdup()

284

Zwalniaj pamięć, gdy jej nie potrzebujesz

288

Przegląd systemu SPIES

298

Detektywi oprogramowania: stosowanie valgrind

300

Skorzystaj z programu valgrind kilkakrotnie, by zebrać więcej dowodów

301

Przeanalizuj dowody

302

Sprawdzenie poprawek

305

Twój niezbędnik C

307

Wyspa Skalista

Wyspa Zjaw

32 bajty danych na stercie w miejscu o adresie 4204853

Wyspa Chmur xvii

Spis treści

Zaawansowane funkcje

7

Odpicuj swoje funkcje na maksa! Proste funkcje są w porządku, jednak czasami możemy potrzebować czegoś więcej. Do tej pory koncentrowaliśmy się na sprawach podstawowych, ale co zrobić, kiedy do osiągnięcia celu będziemy potrzebowali większych możliwości i większej elastyczności? W tym rozdziale zobaczysz, jak podnieść IQ swojego kodu, przekazując funkcje jako parametry. Dowiesz się, jak można sortować, wykorzystując funkcje — komparatory. A na samym końcu nauczysz się, jak dzięki zastosowaniu zmiennej liczby argumentów tworzyć superelastyczne funkcje.

Szukając Pana Doskonałego…

310

Przekaż kod do funkcji

314

Musisz przekazać funkcji find() nazwę funkcji testującej

315

Każda nazwa funkcji jest wskaźnikiem do tej funkcji…

316

…ale nie ma żadnego typu danych reprezentującego funkcję

317

Jak utworzyć wskaźnik do funkcji

318

Posortuj to, używając standardowej biblioteki C

323

Użyj wskaźnika do funkcji, by określić porządek sortowania

324

Automatyzacja generowania listów do Jana

332

Stwórz tablicę wskaźników do funkcji

336

Zapewnij swoim funkcjom elastyyyyyczność

341

Twój niezbędnik C

348

Maszyna 

xviii

Spis treści

Biblioteki statyczne i dynamiczne

8

Wymienialny kod Poznałeś już ogromne możliwości bibliotek standardowych. Nadszedł czas, byś użył mocy swojego własnego kodu. W tym rozdziale dowiesz się, jak tworzyć swoje własne biblioteki oraz jak wielokrotnie używać tego samego kodu w różnych programach. Co więcej, nauczysz się współużytkowania kodu w trakcie działania programu, co jest możliwe dzięki bibliotekom łączonym dynamicznie. Poznasz sekrety mistrzów kodowania. A pod koniec tego rozdziału będziesz już potrafił pisać kod, który w łatwy i efektywny sposób będzie można skalować oraz którym będzie można równie łatwo zarządzać.

Rodzynki, mąka, masło, anchois…

Kod, który możesz zabrać do banku

350

Nawiasy kątowe dołączają standardowe pliki nagłówkowe

352

A co zrobić, jeśli będziesz chciał współużytkować jakiś kod?

353

Współużytkowanie plików nagłówkowych

354

Współużytkowanie plików .o poprzez określanie pełnej ścieżki dostępu

355

Archiwum zawiera pliki .o

356

Utwórz archiwum, używając polecenia ar…

357

I w końcu kompiluj inne programy

358

Siłownia Rusz Głową wchodzi na scenę globalną

363

Obliczanie spalonych kalorii

364

Ale zagadnienie jest nieco bardziej skomplikowane…

367

Programy składają się z wielu fragmentów…

368

Łączenie dynamiczne następuje podczas działania programu

370

Czy pliki .a można łączyć podczas działania programu?

371

Najpierw utwórz plik obiektowy

372

Nazewnictwo bibliotek dynamicznych zależy od platformy systemowej

373

Twój niezbędnik C

385

Czy to ptak? Czy samolot? Nie, to przenaszalny plik obiektowy z metadanymi.

xix

Spis treści

2. laboratorium C

OpenCV $ ' "

 (  )  

  *      %&$            % %   "  %  +,&

xx

Spis treści

Procesy i wywołania systemowe

9

Przekraczanie granic Czas, by zacząć myśleć kreatywnie. Dowiedziałeś się już, że można tworzyć złożone aplikacje, łącząc niewielkie programy narzędziowe wywoływane z poziomu wiersza poleceń. Ale co zrobić, gdy będziemy chcieli korzystać z innych programów we własnym kodzie? W tym rozdziale dowiesz się, jak korzystać z usług systemowych, by tworzyć i kontrolować działanie procesów. Dzięki temu Twoje programy uzyskają dostęp do poczty elektronicznej, WWW oraz wszelkich innych narzędzi zainstalowanych na komputerze. Po przeczytaniu tego rozdziału zyskasz moc pozwalającą wykraczać poza język C.

Wywołania systemowe są Twoją gorącą linią z systemem operacyjnym

396

Wtem ktoś włamał się do systemu…

400

Bezpieczeństwo nie jest jedynym problemem

401

Funkcja exec() zapewnia większą kontrolę

402

Istnieje wiele funkcji exec()

403

Funkcje z tablicą argumentów: execv(), execvp() oraz execve()

404

Przekazywanie zmiennych środowiskowych

405

Większość wywołań systemowych zawodzi w taki sam sposób

406

Czytaj doniesienia, używając RSS

414

exec() jest końcem rodu naszego programu

418

Uruchamianie procesu potomnego przy użyciu funkcji fork() i exec()

419

Twój niezbędnik C

425

To jest Twój proces newshound.

y Uruchamia on odrębn proces dla każdego z trzech kanałów RSS.

newshound

Wszystkie procesy potomne działają jednocześnie.

xxi

Spis treści

Komunikacja pomiędzy procesami

10

Dobrze jest porozmawiać Tworzenie procesów to tylko połowa sukcesu. Co zrobić, gdy chcemy kontrolować proces po jego uruchomieniu? Albo przesłać do niego jakieś dane? Albo odczytać generowane przez niego wyniki? Komunikacja pomiędzy procesami zapewnia procesom możliwość podejmowania wspólnych działań w celu wykonania zadania. W tym rozdziale pokażemy Ci, jak możesz zwiększyć możliwości swojego kodu, pozwalając mu na komunikowanie się z innymi programami w systemie.

#include int main() { char name[30];      fgets(name, 30, stdin); printf(“Witaj, %s\n”, name); return 0; } Plik Edycja Okno Pomoc

> ./greetings      > Kiedy naciśniesz kombinację klawiszy Ctrl+C, program przestaje działać. Ale dlaczego?

xxii

Przekierowania strumieni wejściowych

428

Zajrzyjmy do wnętrza standardowego procesu

429

Przekierowanie zastępuje deskryptor

430

Funkcja fileno() zwraca deskryptor

431

Czasami trzeba poczekać…

436

Bądź w kontakcie ze swymi potomkami

440

Połącz swoje procesy potokami

441

Studium przypadku: otwieranie doniesień w przeglądarce

442

W procesie potomnym

443

W procesie rodzicielskim

443

Otwieranie strony w przeglądarce

444

Śmierć procesu

449

Przechwytywanie sygnałów i wykonywanie własnego kodu

450

Struktury sigaction są rejestrowane przy użyciu funkcji sigaction()

451

Modyfikacja kodu i wykorzystanie procedury obsługi sygnałów

452

Używaj polecenia kill, by wysyłać sygnały

455

Wysyłanie do procesu sygnału pobudki

456

Twój niezbędnik C

464

Spis treści

Gniazda i komunikacja sieciowa

11

Nie ma drugiego takiego miejsca jak 127.0.0.1 Programy działające na różnych komputerach muszą się ze sobą komunikować. Dowiedziałeś się już, jak można używać operacji wejścia-wyjścia, by korzystać z plików, oraz w jaki sposób mogą się ze sobą porozumiewać programy działające na tym samym komputerze. Teraz jednak masz zamiar sięgnąć po resztę świata i nauczyć się pisać w języku C programy, które będą mogły komunikować się z innymi programami działającymi w tej samej sieci oraz na całym świecie. Po przeczytaniu tego rozdziału będziesz potrafił pisać programy działające jako serwery oraz programy pracujące jako klienty.

Internetowy serwer puk-puk

466

Prezentacja serwera puk-puk

467

PNAR — jak serwery komunikują się z internetem

468

Gniazdo nie jest typowym strumieniem danych

470

Czasami serwer nie uruchamia się prawidłowo

474

Dlaczego mama zawsze powtarzała Ci, byś sprawdzał błędy

475

Odczyt danych przesyłanych przez klienta

476

Serwer może rozmawiać tylko z jednym klientem naraz

483

Możesz użyć fork(), by obsłużyć oba klienty jednocześnie

484

Pisanie klienta WWW

488

To zadanie klienta

489

Utwórz gniazdo dla adresu IP

490

Funkcja getaddrinfo() pobiera adresy domen

491

Twój niezbędnik C

498

Serwer Klient oraz serwer prowadzą konwersację o ściśle określonej strukturze, nazywanej protokołem.

Klient telnet

Serwer będzie rozmawiał z kilkoma klientami jednocześnie.

Klient telnet Klient telnet

xxiii

Spis treści

Wątki

12

To równoległy świat Programy często muszą robić kilka rzeczy naraz. Wątki POSIX mogą sprawić, że nasz kod będzie sprawniej reagował na poczynania użytkownika, a to dzięki wydzieleniu kilku fragmentów, które będą działać jednocześnie. Ale uważaj! Wątki są potężnym narzędziem, jednak na pewno byś nie chciał, żeby sobie wzajemnie przeszkadzały. W tym rozdziale dowiesz się, jak zainstalować sygnalizację świetlną i wytyczyć linie, które zapobiegną programistycznym karambolom w Twoim kodzie. Po przeczytaniu tego rozdziału będziesz wiedział, jak tworzyć wątki POSIX oraz jak używać mechanizmów synchronizacji, by chronić integralność ważnych danych.

Zadania są sekwencyjne… lub nie…

500

…a procesy nie zawsze są właściwą odpowiedzią

501

Proste procesy robią po jednej rzeczy naraz

502

Zatrudnij dodatkowych pracowników: skorzystaj z wątków

503

Jak się tworzy wątki?

504

Utwórz wątki, używając funkcji pthread_create()

505

Ten kod nie jest wielobieżny

510

Potrzeba Ci sygnalizacji świetlnej

511

Użyj muteksu jako sygnalizacji świetlnej

512

Twój niezbędnik C

519

A Zmienna   Te dwa auta reprez dwa wątki. Oba chcentują skorzystać z tej sa ą mej współdzielonej zmien nej.

B xxiv

lna uniemożliwia Sygnalizacja świet tej samej do wątkom dostęp nej w tym współdzielonej zmien . sie cza m samy

pis tr ttreści Spis

3. laboratorium C

Blasteroidy $      -       %-  %%-    %- %( &.    /   #

xxv

Spis treści

Pozostałości

A

Dziesięć najważniejszych rzeczy (których nie opisaliśmy) Nawet po tym wszystkim, co już napisaliśmy, wciąż jeszcze pozostaje coś, czego nie wyjaśniliśmy. Jest jeszcze parę zagadnień, o których według nas powinieneś się dowiedzieć. Nie czulibyśmy się dobrze, gdybyśmy zupełnie je zignorowali. Wymagają one choć krótkiego wyjaśnienia, a my naprawdę nie chcemy oddawać Ci do rąk książki, której nie byłbyś w stanie podnieść bez solidnego treningu na lokalnej siłowni. A zatem zanim odłożysz tę książkę na półkę, przeczytaj zamieszczone tu informacje.

gcc

1. Operatory

538

2. Dyrektywy preprocesora

540

3. Słowo kluczowe static

541

4. Jak duże to jest?

542

5. Automatyczne testowanie

543

6. Więcej o gcc

544

7. Więcej o programie make

546

8. Narzędzia programistyczne

548

9. Tworzenie graficznego interfejsu użytkownika

549

10. Materiały

550

Zagadnienia programowania w C

B

Procesy i komunikacja

  568

 

 

Funkcja exit() powoduje natychmiastowe zakończenie programu.

Dodatek B

xxvi

 

Procesy mogą wymieniać ze sobą dane przy użyciu potoków.

Funkcja fork() tworzy kopię bieżącego procesu.

execl() = lista argumentów. execle() = lista argumentów oraz zmienne środowiskowe. execlp() = lista argumentów oraz przeszukanie ścieżki. execv() = tablica argumentów. execve() = tablica argumentów oraz zmienne środowiskowe. execvp() = tablica argumentów oraz przeszukanie ścieżki.

 

 

Wywołania funkcji fork() oraz exec() tworzą proces potomny.

 

Funkcja system() wykona polecenie zapisane w łańcuchu znaków, tak jakby zostało ono wpisane w wierszu poleceń.

 

Procesy i komunikacja

Funkcja pipe() tworzy potok komunikacyjny.

Funkcja waitpid() pozwala poczekać na zakończenie procesu.

Powtórka z całego materiału Czy kiedykolwiek pomyślałeś, że fajnie by było, gdyby te wszystkie wspaniałe fakty dotyczące C zostały zebrane w jednym miejscu? W tym dodatku zostały zebrane wszystkie zagadnienia oraz zasady związane z pisaniem w języku C, które przedstawiliśmy w całej książce. Przejrzyj je wszystkie i przekonaj się, czy je zapamiętałeś. Przy każdym fakcie został podany numer rozdziału, z którego on pochodzi, a zatem nie będziesz miał problemów z odnalezieniem dodatkowych informacji, gdybyś ich potrzebował. Możesz nawet wyciąć te strony z książki i przykleić je sobie na ścianie.

Skorowidz

573

     

Wprowadzenie Nie mogę uwierzyć, że umieścili te wszystkie rzeczy w książce o programowaniu w C.

odpowiemy na palące W tej części książki orzy umieścili aut ego acz pytanie: „Dl e niezwykłe rzeczy?”. w książce te wszystki

jesteś tutaj  xxvii

Jak korzystać z tej książki

Dla kogo jest przeznaczona ta książka? Jeśli możesz odpowiedzieć twierdząco na wszystkie z poniższych pytań:

1 Czy znasz już jakiś inny język programowania? 2 Czy chcesz doskonale opanować język C, napisać coś wspaniałego, zarobić fortunę, a na emeryturę przenieść się na swoją własną wyspę?

No dobrze, być może te plany są cież nieco zbyt dalekosiężne. Ale prze od czegoś trzeba zacząć, prawda?

3 Czy wolisz coś robić i wykorzystywać zdobytą wiedzę, czy też słuchać kogoś na niekończących się wykładach? to jest to książka dla Ciebie.

Kto prawdopodobnie nie powinien sięgać po tę książkę? Jeśli możesz odpowiedzieć twierdząco na przynajmniej jedno z poniższych pytań:

1 Czy szukasz szybkiego wprowadzenia lub podręcznika do pisania w języku C? 2 Czy wolisz, by 15 wrzeszczących małp wyrwało Ci paznokcie z palców u stóp, niż nauczyć się czegoś nowego? Czy uważasz, że książka o programowaniu w C powinna opisywać każde możliwe zagadnienie, a przy okazji jej lektura powinna być śmiertelnie nurząca, i to im bardziej, tym lepiej? to ta książka nie jest dla Ciebie.

[Notatka z działu marketingu: ta książka jest dla każdego, kto ma kartę kredytową… zresztą czeki też przyjmujemy].

xxviii Wprowadzenie

Wprowadzenie

Wiemy, co sobie myślisz „Jakim cudem może to być poważna książka o programowaniu w C?” „Po co te wszystkie obrazki?” „Czy w taki sposób można się czegokolwiek nauczyć?” Twój mózg myśli, że właśnie TO jest istotne.

Wiemy, co sobie myśli Twój mózg Twój mózg pragnie nowości. Zawsze szuka, przegląda i wyczekuje czegoś niezwykłego. W taki sposób został stworzony i to pomaga Ci przetrwać. Zatem co Twój mózg robi z tymi wszystkimi rutynowymi, zwyczajnymi, normalnymi informacjami, jakie do niego docierają? Otóż robi wszystko, co tylko może, aby nie przeszkadzały w jego najważniejszym zadaniu — zapamiętywaniu rzeczy, które mają prawdziwe znaczenie. Twój mózg nie traci czasu i energii na zapamiętywanie nudnych informacji; one nigdy nie przechodzą przez filtr „to jest w oczywisty sposób nieważne”. W jaki sposób Twój mózg wie, co jest istotne? Załóżmy, że jesteś na codziennej przechadzce i nagle przed Tobą staje tygrys. Co się dzieje w Twojej głowie?

Wspaniale. Pozostało jeszcze jedynie 600 głupich, nudnych i drętwych stron.

Neurony płoną. Emocje szaleją. Adrenalina napływa falami. I właśnie dlatego Twój mózg wie, że…

To musi być ważne! Nie zapominaj o tym!

ózg Twój m, że uważa nie warto TEGO iętywać. zapam

Ale wyobraź sobie, że jesteś w domu albo bibliotece. Jesteś w bezpiecznym miejscu — przytulnym i pozbawionym tygrysów. Uczysz się. Przygotowujesz się do egzaminu. Albo rozgryzasz jakiś trudny problem techniczny, którego rozwiązanie według szefa powinno zająć Ci tydzień, a najdalej dziesięć dni. Jest tylko jeden drobny problem. Twój mózg stara Ci się pomóc. Próbuje zapewnić, że te w oczywisty sposób nieistotne informacje nie zajmą cennych zasobów w Twojej głowie. Zasobów, które powinny zostać wykorzystane na zapamiętanie naprawdę ważnych rzeczy. Takich jak tygrysy. Takich jak zagrożenie, jakie niesie ze sobą pożar. Takich jak to, że nigdy nie powinieneś publikować na Facebooku tych zdjęć z imprezy. Co gorsza, nie ma żadnego sposobu, aby powiedzieć mózgowi: „Hej, mój mózgu, dziękuję ci bardzo, ale niezależnie od tego, jak nudna jest ta książka i jak nieznaczne są emocje, których aktualnie doznaję, to jednak naprawdę chciałbym zapamiętać wszystkie te informacje”.

jesteś tutaj  xxix

Jak korzystać z tej książki

Wyobrażamy sobie, że Czytelnik tej książki jest uczniem neś to coś poznać, Czego zatem trzeba, żeby się czegoś nauczyć? W pierwszej kolejności powinie nie do wtłocze o a następnie postarać się tego czegoś nie zapomnieć. I nie chodzi tu jedynie cji, informa jania przyswa ie dziedzin w głowy suchych faktów. Najnowsze badania prowadzone tylko niż więcej czegoś wymaga się uczenie że ą, neurobiologii i psychologii nauczania pokazuj a. czytania tekstu. My wiemy, co potrafi pobudzić nasze mózgi do działani Oto niektóre z głównych założeń niniejszej książki:

ą, sobie inaniem że uczenie się staje się zdecydowanie bardziej efektywne (studia nad przypom ność zapamiętywania i przekazywaniem informacji wykazują, że użycie rysunków poprawia efektyw zrozumiałe. Wystarczy bardziej znacznie się stają je o 89%). Co więcej, rysunki sprawiają, że informac następnej stronie, na nie a okolicy), jego w (lub u rysunk na rednio umieścić słowa bezpoś którego te słowa , problem ć rozwiąza stanie w będą się uczące osoby że stwo, odobień a prawdop dotyczą, wzrośnie niemal dwukrotnie. końcowych studenci Stosuj dialogi i personifikuj. Według najnowszych badań w testach dni, w pierwszej bezpośre sposób w wana uzyskiwali wyniki o 40% lepsze, jeśli treść była przekazy historyjki. Używaj są lepsze w wykładó Od . formalny sposób w osobie i w konwencji rozmowy, a nie podczas stymulującej — uważny bardziej byłbyś Kiedy e. poważni zbyt się traktuj Nie języka potocznego. rozmowy przy obiedzie czy podczas wykładu? neuronów do Zmuś ucznia do głębszego zastanowienia. Innymi słowy, jeśli nie zmusisz zmotywowany, być musi Czytelnik . aktywnego wysiłku, w Twojej głowie nie zdarzy się nic wielkiego iem wniosków wyciągan ów, problem waniem rozwiązy zaangażowany, zaciekawiony i podekscytowany e wyzwań, stawiani poprzez możliwe jest ego wszystki tego ie osiągnięc i zdobywaniem nowej wiedzy. A poprzez oraz ienia zapraszanie do rozwiązywania ćwiczeń i zadawanie pytań zmuszających do zastanow . zmysłów wielu i ych mózgow nakłanianie do działań, które wymagają zaangażowania obu półkul

niż same słowa i sprawiaj Zwizualizuj to sobie. Rysunki są znacznie łatwiejsze do zapamiętania

znalazł się kiedyś Musisz zdobyć uwagę Czytelnika i zachować ją na dłużej. Każdy

pierwszej strony. Mózg w sytuacji, gdy bardzo chciał się czegoś nauczyć, lecz zasypiał po przeczytaniu nieoczekiwane. Jednak wzrok, ające przykuw dziwne, ące, zwraca uwagę na rzeczy niezwykłe, interesuj będzie to zagadnienie Jeśli nudne. być musi nie wcale nego technicz nia poznawanie nowego zagadnie interesujące, Twój mózg przyswoi je znacznie szybciej. ji są w znacznej mierze Wyzwól emocje. Teraz już wiemy, że zdolności do zapamiętywania informac Zapamiętujemy zależy. nam zależne od ich zawartości emocjonalnej. Zapamiętujemy to, na czym jących historii wzrusza myśli na tu mamy nie w sytuacjach, w których coś odczuwamy. Oczywiście podekscytowanie, radosne ść, ciekawo nie, zaskocze jak emocje takie o chłopcu i jego psie. Chodzi nam o ym rozwiązaniu „a niech to…” i uczucie satysfakcji — „Jestem wielki!” — jakie odczuwamy po poprawn że znamy sprawy, sobie zdaniu zagadki, nauczeniu się czegoś, co powszechnie uchodzi za trudne, lub i. więcej szczegółów technicznych niż Zenek z działu inżynieri

xxx

Wprowadzenie

Wprowadzenie

Metapoznanie — myślenie o myśleniu Jeśli naprawdę chcesz się czegoś nauczyć i jeśli chcesz się tego nauczyć szybciej i dokładniej, to zwracaj uwagę na to, jak Ci na tym zależy. Myśl o tym, jak myślisz. Poznawaj sposób, w jaki się uczysz. Większość z nas w okresie dorastania nie uczestniczyła w kursach metapoznania ani teorii nauczania. Oczekiwano od nas, że będziemy się uczyć, jednak nie uczono nas, jak mamy to robić.

Zastanawiam się, jak zmusić mózg do zapamiętania tych informacji…

Zakładamy jednak, że jeśli trzymasz w ręku tę książkę, to chcesz nauczyć się programowania w C. I prawdopodobnie nie chcesz na to tracić zbyt wiele czasu. Jeśli chcesz wykorzystać to, co przeczytałeś w tej książce, musisz to zapamiętać. A oprócz tego musisz to zrozumieć. Aby w jak największym stopniu wykorzystać zarówno tę, jak i dowolną inną książkę lub jakikolwiek inny sposób uczenia się, musisz wziąć odpowiedzialność za swój mózg. Myśl o tym, czego się uczysz. Sztuczka polega na tym, aby przekonać mózg, że poznawany materiał jest naprawdę ważny. Kluczowy dla Twojego dobrego samopoczucia. Tak ważny jak tygrys stojący przed Tobą. W przeciwnym razie będziesz nieustannie prowadzić wojnę z własnym mózgiem, który ze wszystkich sił będzie się starać, aby nowe informacje nie zostały utrwalone.

Jak w takim razie ZMUSIĆ mózg do traktowania programowania jak głodnego tygrysa? Można to zrobić w sposób wolny i męczący lub szybki i bardziej efektywny. Wolny sposób polega na wielokrotnym powtarzaniu. Oczywiście wiesz, że jesteś w stanie nauczyć się i zapamiętać nawet najnudniejsze zagadnienie, mozolnie je wkuwając. Po odpowiedniej ilości powtórzeń Twój mózg stwierdzi: „Wydaje się, że to nie jest dla niego szczególnie ważne, lecz w kółko to czyta i powtarza, więc przypuszczam, że jakąś wartość to jednak musi mieć”. Szybszy sposób polega na zrobieniu czegokolwiek, co zwiększy aktywność mózgu, zwłaszcza jeśli czynność ta wyzwoli kilka różnych typów aktywności. Wszystkie zagadnienia, o jakich pisaliśmy na poprzedniej stronie, są kluczowymi elementami rozwiązania i udowodniono, że wszystkie z nich potrafią pomóc w zmuszeniu mózgu do tego, aby pracował na Twoją korzyść. Na przykład badania wykazują, że umieszczenie słów na opisywanych rysunkach (a nie w innych miejscach tekstu na stronie, na przykład w nagłówku lub wewnątrz akapitu) sprawia, że mózg stara się zrozumieć relację pomiędzy słowami a rysunkiem, a to z kolei zwiększa aktywność neuronów. Większa aktywność neuronów to większa szansa, że mózg uzna informacje za warte zainteresowania i, ewentualnie, zapamiętania. Prezentowanie informacji w formie konwersacji pomaga, gdyż ludzie zwykle wykazują większe zainteresowanie w sytuacjach, gdy uważają, że biorą udział w rozmowie, gdyż oczekuje się od nich, że będą śledzić jej przebieg i brać w niej czynny udział. Zadziwiające jest to, iż mózg zdaje się nie zważać na to, że rozmowa jest prowadzona z książką! Z kolei jeśli sposób przedstawiania informacji jest formalny i suchy, mózg postrzega to tak samo jak w sytuacji, gdy uczestniczysz w wykładzie na sali pełnej sennych studentów. Nie ma potrzeby wykazywania jakiejkolwiek aktywności. Jednak rysunki i przedstawianie informacji w formie rozmowy to jedynie początek…

jesteś tutaj  xxxi

Jak korzystać z tej książki

Oto, co zrobiliśmy: Używaliśmy rysunków, ponieważ Twój mózg zwraca większą uwagę na obrazy niż na tekst. Jeśli chodzi o mózg, to faktycznie jeden obraz jest wart tysiąca słów. W sytuacjach gdy pojawiał się zarówno tekst, jak i rysunek, umieszczaliśmy tekst na rysunku, gdyż mózg działa bardziej efektywnie, gdy tekst jest wewnątrz czegoś, co opisuje, niż kiedy jest umieszczony w innym miejscu i stanowi część większego fragmentu tekstu. Stosowaliśmy powtórzenia, wielokrotnie podając tę samą informację na różne sposoby i przy wykorzystaniu różnych środków przekazu oraz odwołując się do różnych zmysłów. Wszystko po to, aby zwiększyć szansę, że informacja zostanie zakodowana w większej liczbie obszarów Twojego mózgu. Korzystaliśmy z pomysłów i rysunków w nieoczekiwany sposób, ponieważ Twój mózg liczy na nowości i ich pragnie. Poza tym staraliśmy się zawrzeć w nich chociaż trochę emocji, gdyż mózg jest skonstruowany w taki sposób, że zwraca uwagę na biochemię związaną z emocjami. Prawdopodobieństwo zapamiętania czegoś jest większe, jeśli „to coś” sprawia, że cokolwiek poczujemy, nawet jeśli to uczucie nie jest niczym więcej jak lekkim rozbawieniem, zaskoczeniem lub zainteresowaniem. Używaliśmy bezpośrednich zwrotów i przekazywaliśmy treści w formie konwersacji, gdyż mózg zwraca większą uwagę, jeśli sądzi, że prowadzisz rozmowę, niż gdy jesteś jedynie biernym słuchaczem prezentacji. Mózg działa w ten sposób nawet wtedy, gdy czytasz zapis rozmowy. Zamieściliśmy w książce ponad 80 ćwiczeń, ponieważ mózg uczy się i pamięta więcej, gdy coś robi, niż gdy o czymś czyta. Poza tym podane ćwiczenia stanowią wyzwania, choć nie są przesadnie trudne, gdyż właśnie takie preferuje większość osób. Stosowaliśmy wiele stylów nauczania, gdyż Ty możesz preferować instrukcje opisujące krok po kroku sposób postępowania, ktoś inny analizowanie zagadnienia opisanego w ogólny sposób, a jeszcze inne osoby — przejrzenie przykładowego fragmentu kodu. Jednak niezależnie od ulubionego sposobu nauki każdy skorzysta na tym, że te same informacje będą przedstawiane kilkakrotnie na różne sposoby. Podawaliśmy informacje przeznaczone dla obu półkul Twojego mózgu, gdyż im bardziej mózg będzie zaangażowany, tym większe jest prawdopodobieństwo nauczenia się i zapamiętania podawanych informacji i tym dłużej możesz się koncentrować na nauce. Ponieważ angażowanie tylko jednej półkuli mózgu często oznacza, że druga będzie mogła odpocząć, będziesz mógł się uczyć bardziej produktywnie przez dłuższy czas. Dodatkowo zamieszczaliśmy opowiadania i ćwiczenia prezentujące więcej niż jeden punkt widzenia, ponieważ mózg uczy się łatwiej, gdy jest zmuszony do przetwarzania i podawania własnej opinii. Stawialiśmy przed Tobą wyzwania — zarówno poprzez włączenie ćwiczeń, jak i stawianie pytań, na które nie zawsze można odpowiedzieć w prosty sposób, a to dlatego, że mózg uczy się i pamięta, gdy musi nad czymś popracować. Pomyśl sam — nie można zdobyć dobrej kondycji, obserwując ćwiczenia w telewizji. Jednak dołożyliśmy wszelkich starań, aby zapewnić, że gdy pracujesz, to robisz dokładnie to, co trzeba, aby ani jeden dendryt nie musiał przetwarzać trudnego przykładu ani analizować tekstu zbyt lapidarnego lub napisanego niezrozumiałym żargonem. Personifikowaliśmy tekst. W historyjkach, przykładach, rysunkach i we wszelkich innych możliwych miejscach tekstu staraliśmy się personifikować tekst, gdyż jesteś osobą, a Twój mózg zwraca większą uwagę na osoby niż na rzeczy.

xxxii Wprowadzenie

Wprowadzenie

Oto, co możesz zrobić, aby zmusić swój mózg do posłuszeństwa Wytnij te porady A zatem zrobiliśmy, co było w naszej mocy. Reszta zależy od Ciebie. Możesz zacząć i przyklej na lodówce. od poniższych porad. Posłuchaj swojego mózgu i określ, które sprawdzają się w Twoim

przypadku, a które nie dają pozytywnych rezultatów.

1 Zwolnij. Im więcej rozumiesz, tym mniej musisz

6

zapamiętać. Nie ograniczaj się jedynie do czytania. Przerwij na chwilę lekturę i pomyśl. Kiedy znajdziesz w tekście pytanie, nie zaglądaj od razu na stronę z odpowiedzią. Wyobraź sobie, że ktoś faktycznie zadaje Ci pytanie. Im bardziej zmusisz swój mózg do myślenia, tym większa będzie szansa, że się czegoś nauczysz i coś zapamiętasz.

Twój mózg pracuje najlepiej, gdy dostarcza mu się dużo płynów. Odwodnienie (które może następować, nawet zanim poczujesz pragnienie) obniża zdolność percepcji.

7

8

„Nie ma głupich pytań”.

4 Niech lektura tej książki będzie ostatnią rzeczą, jaką robisz przed pójściem spać. A przynajmniej ostatnią rzeczą stanowiącą wyzwanie intelektualne. Pewne elementy procesu uczenia się (a w szczególności przenoszenie informacji do pamięci długoterminowej) następują po odłożeniu książki. Twój mózg potrzebuje trochę czasu dla siebie i musi dodatkowo przetworzyć dostarczone informacje. Jeśli w czasie niezbędnym do tego dodatkowego „przetwarzania” zmusisz go do innej działalności, część z przyswojonych informacji może zostać utracona.

5 Rozmawiaj o zdobywanych informacjach. Na głos. Mówienie aktywuje odmienne fragmenty mózgu. Jeśli próbujesz coś zrozumieć lub zwiększyć szansę na zapamiętanie informacji na dłużej, powtarzaj je na głos. Jeszcze lepiej, jeśli będziesz się starać je na głos komuś wytłumaczyć. W ten sposób nauczysz się szybciej, a oprócz tego będziesz mógł odkryć kwestie, o których nie wiedziałeś podczas czytania tekstu książki.

Poczuj coś! Twój mózg musi wiedzieć, że to, czego się uczysz, ma znaczenie. Z zaangażowaniem śledź zamieszczane w tekście opowiadania. Nadawaj własne tytuły zdjęciom. Zalewanie się łzami ze śmiechu po przeczytaniu głupiego dowcipu i tak jest lepsze od braku jakiejkolwiek reakcji.

3 Czytaj fragmenty oznaczone jako Chodzi tu o wszystkie fragmenty umieszczone z boku tekstu. Nie są to fragmenty opcjonalne — stanowią one część podstawowej zawartości książki! Nie pomijaj ich.

Posłuchaj swojego mózgu. Uważaj, kiedy Twój mózg staje się przeciążony. Jeśli spostrzeżesz, że zaczynasz czytać pobieżnie i zapominać to, o czym przeczytałeś przed chwilą, to najwyższy czas, żeby sobie zrobić przerwę. Po przekroczeniu pewnego punktu nie będziesz się uczył szybciej, „wciskając” do głowy więcej informacji, a co gorsza, może to zaszkodzić całemu procesowi nauki.

2 Wykonuj ćwiczenia. Rób notatki. Umieszczaliśmy je w tekście, jednak jeśli zrobilibyśmy je za Ciebie, to niczym nie różniłoby się to od sytuacji, w której ktoś za Ciebie wykonywałby ćwiczenia fizyczne. I nie ograniczaj się jedynie do czytania ćwiczeń. Używaj ołówka. Można znaleźć wiele dowodów na to, że fizyczna aktywność podczas nauki może poprawić jej wyniki.

Pij wodę. Dużo wody.

9

Pisz jak najwięcej kodu. Istnieje tylko jeden sposób, by nauczyć się programowania w C: pisanie kodu — im będzie go więcej, tym lepiej. I właśnie to będziesz robił podczas lektury tej książki. Pisanie programów jest umiejętnością, a jedynym sposobem jej nabycia jest ciągłe praktykowanie. Mamy zamiar dać Ci ku temu wiele okazji: w każdym rozdziale zostały umieszczone ćwiczenia stawiające przed Tobą problemy, które możesz rozwiązać. Nie pomijaj ich — podczas rozwiązywania ćwiczeń możesz się bardzo wiele nauczyć. Zamieściliśmy także rozwiązania wszystkich ćwiczeń — nie obawiaj się zerknąć na rozwiązanie, jeśli utkniesz! (Łatwo jest utknąć na jakiejś drobnostce). Staraj się jednak rozwiązać problem, zanim zajrzysz do rozwiązania. I koniecznie, zanim przejdziesz do dalszej części książki, postaraj się uruchomić programy, nad którymi pracujesz.

jesteś tutaj  xxxiii

Jak korzystać z tej książki

Przeczytaj to Ta książka jest doznaniem poznawczym, a nie podręcznikiem. Celowo usunęliśmy z niej wszystko, co mogłoby Ci przeszkadzać w uczeniu się i poznawaniu materiału zamieszczonego w danym miejscu książki. Podczas pierwszej lektury tej książki powinieneś zaczynać od samego początku, gdyż jej dalsze fragmenty bazują na wiedzy, którą powinieneś zdobyć wcześniej.

Zakładamy, że nowością jest dla Ciebie język C, ale nie programowanie. Zakładamy, że miałeś już wcześniej kontakt z programowaniem. Mamy nadzieję, że może w niewielkim stopniu, ale jednak zapoznałeś się już wcześniej z innymi językami, takimi jak JavaScript, oraz takimi rzeczami jak pętle czy zmienne. C jest w rzeczywistości całkiem zaawansowanym językiem, dlatego jeśli wcześniej nie miałeś żadnego kontaktu z programowaniem, to być może, zanim zaczniesz lekturę tej książki, powinieneś sięgnąć po jakąś inną. Doskonała będzie na przykład Head First Programming.

Na swoim komputerze musisz zainstalować kompilator C. W tej książce będziemy używać kompilatora Gnu Compiler Collection (gcc), ponieważ jest on dostępny bezpłatnie, a poza tym uważamy, że jest naprawdę dobry. Musisz się zatem upewnić, że będzie on dostępny na Twoim komputerze. Jeśli masz komputer z systemem Linux, to na szczęście będziesz miał także gcc. Jeśli używasz komputera Mac, to powinieneś zainstalować narzędzia Xcode/Developer tools. Można je pobrać bądź to z App Store, bądź też z witryny firmy Apple. Jeśli natomiast używasz komputera z systemem Windows, to masz do wyboru dwie możliwości. Pierwszą z nich jest środowisko Cygwin (http://www.cygwin.com), udostępniające pełną symulację systemu operacyjnego UNIX, włącznie z kompilatorem gcc. Jeśli jednak zależy Ci jedynie na tym, by w jak najprostszy sposób tworzyć programy działające w systemie Windows, to być może będziesz wolał zainstalować środowisko Minimalist GNU for Windows (MinGW), które można pobrać ze strony http://www.mingw.org. Wszystkie kody zamieszczone w tej książce mogą działać we wszystkich systemach operacyjnych — bardzo się staraliśmy, by żaden z napisanych programów nie działał wyłącznie na jednej platformie systemowej. Od czasu do czasu pojawią się jakieś różnice, jednak na pewno zostaną one wyraźnie opisane w treści książki.

Najpierw przedstawiamy proste zagadnienia związane z pisaniem w C, a potem od razu zaczynamy wykorzystywać zdobytą wiedzę. Podstawowe zagadnienia związane z językiem C zostały opisane w rozdziale 1. Dzięki temu, kiedy dotrzesz do rozdziału 2., będziesz już tworzył programy potrafiące robić coś realnego, użytecznego oraz — a jakże! — zabawnego. Dalsza część książki bazuje na tych zdobywanych umiejętnościach posługiwania się językiem C, zmieniając Cię błyskawicznie z nieopierzonego kurczaka C w programistycznego mistrza ninja.

xxxiv Wprowadzenie

Wprowadzenie

Aktywności NIE są opcjonalne. Ćwiczenia i aktywności nie są jedynie dodatkami; są one elementem treści tej książki. Niektóre z nich mają wspomóc Twoją pamięć, inne — ułatwić zrozumienie, a jeszcze inne — pomóc w wykorzystaniu nabytej wiedzy i umiejętności. Nie pomijaj tych ćwiczeń!

Powtórzenia są celowe i ważne. Jedną z cech, która wyróżnia książki z serii Rusz głową!, jest to, że nam naprawdę zależy, żebyś wszystko zrozumiał. Chcemy także, byś kończąc lekturę tej książki, pamiętał wszystko, co w niej przeczytałeś. Większość książek informacyjnych i encyklopedycznych nie zwraca uwagi na przyswojenie i zapamiętanie informacji, jednak w tej chodzi o naukę, dlatego znajdziesz w niej wiele pojęć, które pojawiają się kilka razy.

Przykładowe kody są jak najbardziej zwięzłe. Czytelnicy niejednokrotnie zwracali nam uwagę, że przeglądanie 200 wierszy kodu w poszukiwaniu dwóch linijek, które należy zrozumieć, może być frustrujące. W większości przykładów zamieszczonych w tej książce ich kod został w jak największym stopniu skrócony, dzięki czemu fragmenty, których musisz się nauczyć, są przejrzyste i proste. Nie należy zatem oczekiwać, że podawane przykłady będą solidne ani nawet że będą kompletne — zostały one opracowane wyłącznie pod kątem nauki, a nie zapewnienia pełnej funkcjonalności.

Do ćwiczeń z cyklu „Wysil szare komórki” nie podawaliśmy odpowiedzi. Do niektórych w ogóle nie można podać jednej dobrej odpowiedzi, w innych przypadkach to doświadczenie, które zdobywasz, rozwiązując ćwiczenia, ma dać Ci możliwość określenia, czy i kiedy podana odpowiedź będzie poprawna. W niektórych ćwiczeniach z tej serii znajdziesz także podpowiedzi, które ułatwią Ci znalezienie rozwiązania.

jesteś tutaj  xxxv

Zespół recenzentów

Zespół recenzentów technicznych Dave Kitabjian

Vince Milner

Recenzenci techniczni książki: Dave Kitabjian posiada dwa dyplomy z elektroniki oraz projektowania komputerów, jak również 20-letnie doświadczenie w konsultowaniu, integracji, projektowaniu oraz tworzeniu systemów informatycznych przeróżnych klientów, zaczynając od firmy z grupy Fortune 500, a kończąc na technologicznych start-upach. Prywatnie Dave lubi grać na gitarze i pianinie oraz spędzać czas z żoną i trójką dzieci. Vince Milner od ponad 20 lat pisze w C (oraz innych językach) oprogramowanie przeznaczone na wiele różnych platform. Kiedy nie studiuje, żeby przygotować się do obrony dyplomu z matematyki, można go zobaczyć przegrywającego w gry planszowe z 6-latkami i unikającego przeprowadzki.

xxxvi Wprowadzenie

Wprowadzenie

Podziękowania Dla naszego redaktora: Chcieliśmy bardzo podziękować Brianowi Sawyerowi — przede wszystkim za to, że poprosił nas o napisanie tej książki. Brian wierzył w nas w trakcie pracy nad tym projektem, zapewnił możliwość wypróbowania nowych pomysłów i nie panikował za bardzo, gdy przekraczaliśmy kolejne terminy.

Brian Sawyer

Zespół wydawnictwa O’Reilly: Wielkie podziękowania należą się następującym osobom, które pomagały nam podczas pracy nad tą książką: Karen Shaner za jej doskonałe umiejętności wyszukiwania obrazków oraz, ogólnie, za zapewnienie gładkiego postępu prac; Laurie Petrycki za zapewnienie nam dobrego wyżywienia i odpowiednio wysokiej motywacji podczas pobytu w Bostonie; Brianowi Jepsonowi za wprowadzenie nas we wspaniały świat Arduino, a także zespołowi wydania wstępnego za udostępnienie do pobrania wczesnej wersji tej książki. Wreszcie, chcieliśmy także podziękować Rachel Monaghan oraz zespołowi produkcyjnemu za doskonałe przeprowadzenie książki przez cały proces produkcji oraz za tak ciężką, choć niezauważalną pracę. Wszyscy jesteście wspaniali. Rodzina, przyjaciele i koledzy: W czasie naszej podróży do świata serii Rusz głową! poznaliśmy wielu nowych przyjaciół. W szczególności chcieliśmy podziękować Lou Barr, Brettowi McLaughlinowi oraz Sandersowi Kleinfeldowi za to, że tak wiele nas nauczyli. David: Chciałem podziękować Andy’emu Parkerowi, Joemu Broughtonowi, Carlowi Jacquesowi oraz Simonowi Jonesowi, a także wielu innym przyjaciołom, którzy podczas gdy pisałem tę książkę, mieli ze mną tak ograniczony kontakt. Dawn: Praca nad tą książką byłaby znacznie trudniejsza bez mojej niesamowitej sieci wsparcia tworzonej przez rodzinę i przyjaciół. W szczególności chciałam podziękować Mamie i Tacie, Carlowi, Steve’owi, Gill, Jacqui, Joycowi oraz Paulowi. Jestem wam bardzo wdzięczna za wsparcie i zachętę. Lista pozostałych osób: Nasz zespół recenzentów technicznych wykonał doskonałą robotę, utrzymując nas w ryzach i zapewniając, że opisywane zagadnienia są dokładne i prawidłowe. Jesteśmy także niezmiernie wdzięczni wszystkim osobom, które przekazywały nam swoje uwagi po ukazaniu się pierwszych wersji niniejszej książki. Uważamy, że dzięki wam ta książka stała się znacznie, znacznie lepsza. I w końcu chcieliśmy podziękować Kathy Sierra oraz Bertowi Batesowi za stworzenie tej niesamowitej serii książek.

jesteś tutaj xxxvii

xxxviii Wprowadzenie

1.      !

Dajmy nurka Czy nie ubóstwiasz niebieskiego oCeanu? Wskakuj — woda jest cudowna!

Czy chcesz zajrzeć do głowy komputera? Musisz napisać kod działający naprawdę szybko, na przykład na potrzeby nowej gry? A może program na Arduino? Albo we własnej aplikacji na iPhone’a użyć biblioteki napisanej przez kogoś innego? Jeśli tak, to skorzystaj z pomocy bohaterskiego C. C działa na znacznie niższym poziomie niż większość innych języków programowania, a zatem zrozumienie go daje nam znacznie większe pojęcie o tym, co się naprawdę dzieje w programie. C pozwala także lepiej zrozumieć inne języki programowania. A zatem bierz się do pracy, przygotuj kompilator, a już niedługo zaczniesz poznawać C.

to jest nowy rozdział 

1

Jak działa C

C to język do pisania małych, szybkich programów Język C został stworzony do pisania małych i szybkich programów. Działa na znacznie niższym poziomie niż większość innych języków programowania, a to oznacza, że tworzy kod znacznie bliższy temu, co komputery naprawdę są w stanie zrozumieć.

Sposób działania C Tak naprawdę komputery rozumieją tylko jeden język: kod maszynowy — binarny strumień składający się jedynie z zer i jedynek. Kod napisany w języku C konwertujemy na kod maszynowy za pomocą kompilatora.

#include

indows W systemie W nosił ie dz bę ik pl n te exe, nazwę rocks. s. ck ro a nie

int main() {

Plik Edycja Okno Pomoc Kompilacja

puts(“C jest czaderskie!”); return 0;

> gcc rocks.c -o rocks >

} rocks.c

rocks

1

Kod źródłowy Zaczynasz od utworzenia pliku źródłowego. Plik źródłowy zawiera kod napisany w języku C, który jest zrozumiały dla człowieka.

2

Kompilacja

Kod wynikowy

Plik źródłowy jest następnie przetwarzany przy użyciu kompilatora. Kompilator sprawdza kod w poszukiwaniu błędów, a kiedy uzna, że wszystko jest w porządku, kompiluje go.

Kompilator tworzy nowy plik, nazywany plikiem wykonywalnym. Zawiera on kod maszynowy — strumień jedynek i zer, które jest w stanie zrozumieć komputer. I to właśnie jest program, który możesz wykonać.

Język C jest używany, w przypadku gdy duże znaczenie mają szybkość działania, niewielkie rozmiary oraz możliwość przenoszenia. Większość systemów operacyjnych została napisana w języku C. Także większość innych języków programowania została napisana w C. Dodatkowo przeważająca większość gier jest pisana w C.

2

Rozdział 1.

3

Istnieją trzy standardy języka C, z którymi można się zetknąć. ANSI C pochodzi z późnych lat 80. i jest używany w najstarszym kodzie. W nowym standardzie — C99, istniejącym od 1999 roku, poprawiono sporo różnych rzeczy. Natomiast w najnowszym standardzie — C11, opracowanym w 2011 roku, dodano kilka nowych, świetnych możliwości. Różnice pomiędzy tymi trzema standardami nie są wielkie, jednak będziemy o nich wspominać.

Zaczynamy poznawać C

Zaostrz ołówek Spróbuj odgadnąć, co robi każdy z tych fragmentów kodu. fragment kodu. Opisz, co według Ciebie robi ten

int card_count = 11;

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

if (card_count > 10)

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

puts(“Nowe rozdanie. Licytujemy.”); int c = 10; while (c > 0) {    "$ c = c - 1;

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

} &'*+/22245 /"'&

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

678945:

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

 ?67+ 

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

6 @AB $8

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

 @ "DDD*26"D$8

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

char suit = ‘K’;

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

switch(suit) {

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

6 EFG

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

puts(“Trefle”); break; 6 EHG puts(“Kara”); break; 6 E?G

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

puts(“Piki”);

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

break;

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

 puts(“Serca”); }

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

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

jesteś tutaj 

3

Fragmenty bez tajemnic

Zaostrz ołówek Rozwiązanie

int card_count = 11;

Nie przejmuj się, jeśli jeszcze nie rozumiesz wszystkiego. Zostanie to szczegółowo wyjaśnione w dalszej części książki. int oznacza liczbę całkowitą.

Tworzy zmienną całkowitą i przypisuje jej wartość 11. ........................................................... Czy wartość zmiennej jest większa od 10? ...........................................................

if (card_count > 10) puts(“Nowe rozdanie. Licytujemy.”); To wyświetla łańcuch znaków w wierszu poleceń lub na terminalu. Nawiasy klamrowe definiują int c = 10; instrukcję blokową.

Jeśli jest, to wyświetlamy komunikat. ...........................................................

Tworzy zmienną całkowitą i przypisuje jej wartość 10. ...........................................................

while (c > 0) {

Jak długo wartość jest większa od zera… ...........................................................

   "$

…wyświetlamy komunikat… ........................................................... …i dekrementujemy wartość zmiennej. ...........................................................

c = c - 1; }

To koniec powtarzanego bloku kodu. ...........................................................

&'*+/22245 /"'&

To jest komentarz. ...........................................................

678945:

Tworzymy tablicę 20 znaków. ...........................................................

 ?67+  6 @AB $8

To oznacza: „Wszystko, co użytkownik wpisze, zapisz w tablicy ex”.

 @ "DDD*26"D$8

Wyświetlamy komunikat na ekranie. ........................................................... Zapisujemy w tablicy to, co wpisze użytkownik. ........................................................... Wyświetlamy komunikat zawierający wpisany tekst. ...........................................................

To wstawi łańcuch znaków w miej sce %s Tworzymy zmienną znakową i zapisujemy w niej K. ...........................................................

char suit = ‘K’; switch(suit) { 6 EFG

Instrukcja switch sprawdza zmienną, porównując ją do różnych wartości.

Czy jest nią „T”? ...........................................................

puts(“Trefle”);

Jeśli tak, to wyświetlamy słowo „Trefle”. ...........................................................

break;

Następnie pomijamy pozostałe testy. ...........................................................

6 EHG

Czy jest nią „K”? ...........................................................

puts(“Kara”);

Jeśli tak, to wyświetlamy słowo „Kara”. ...........................................................

break;

Następnie pomijamy pozostałe testy. ...........................................................

6 E?G

Czy jest nią „P”? ...........................................................

puts(“Piki”);

Jeśli tak, to wyświetlamy słowo „Piki”. ...........................................................

break;

Następnie pomijamy pozostałe testy.

 puts(“Serca”); }

4

Sprawdzamy wartość zmiennej. ...........................................................

Rozdział 1.

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

W przeciwnym razie…

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

...wyświetlamy słowo „Serca”.

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

To już koniec sprawdzania.

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

Zaczynamy poznawać C

Ale jak wygląda skompilowany program napisany w C? Aby stworzyć pełny program, musisz zapisać jego kod w pliku źródłowym. Pliki źródłowe programów pisanych w języku C można utworzyć w dowolnym edytorze tekstów, a ich nazwy zazwyczaj mają rozszerzenie .c. Przyjrzyjmy się typowemu plikowi źródłowemu programu w języku C.

1

To jest tylko konwencja, jednak ać. powinieneś się do niej zastosow

            

Komentarz opisuje przeznaczenie kodu umieszczonego w tym pliku, a dodatkowo może także zawierać informacje o licencji oraz prawach autorskich. Nie ma potrzeby umieszczania komentarza — ani tu, ani w żadnym innym miejscu pliku — jednak jest to dobrym zwyczajem i większość programistów używających języka C będzie takiego komentarza oczekiwać. /*

Komentarz zaczyna się od znaków /*.

'F +2M6 "

Te znaki * są opcjonalne. Poprawiają tylko wygląd komentarza.

'H  M67O ?Q6R6 " ' 645AUV6HQX6 MY6 "

Komentarz kończy się znakami */.

2

*/

     

#include

C jest językiem o bardzo, bardzo małych możliwościach… bez zastosowania bibliotek zewnętrznych nie da się w nim zrobić praktycznie niczego. Będziesz musiał poinformować kompilator o tym, jakiego kodu zewnętrznego chcesz używać, podając pliki nagłówkowe odpowiednich bibliotek. Plikiem nagłówkowym, którego będziemy używać zdecydowanie najczęściej, jest stdio.h. Biblioteka stdio zawiera kod pozwalający odczytywać dane z terminala i wyświetlać je w nim.

int main() { int decks;  ?6 Q$  6 @$Z6  if (decks < 1) {  F +6 Q$ return 1; }  ^`6  67@D$ 6 'q4 return 0; }

3          !   " #$"  %  Cały kod pisany w języku C jest umieszczany wewnątrz funkcji. Najważniejsza funkcja, którą znajdziemy w każdym programie pisanym w tym języku, nosi nazwę main(). Stanowi ona punkt początkowy, od którego zaczyna się wykonywanie całego kodu programu.

A zatem przyjrzyjmy się funkcji main() nieco bardziej szczegółowo.

jesteś tutaj 

5

Funkcja main()

"#       Komputer zacznie wykonywać nasz program od funkcji main(). Jej nazwa ma znaczenie: jeśli w kodzie programu nie będzie funkcji main(), nie będzie go można wykonać. Funkcja main() zwraca wartość typu int. Co to oznacza? Cóż, kiedy komputer wykonuje program, musi dysponować jakimś sposobem określenia, czy został on wykonany prawidłowo, czy też nie. Robi to, sprawdzając wartość wynikową zwracaną przez funkcję main(). Jeśli każemy jej zwrócić wartość 0, będzie to oznaczało, że program został wykonany prawidłowo. Jeśli natomiast zwrócimy inną wartość, będzie to oznaczało, że pojawiły się jakieś problemy. To typ wyniku. W przypadku funkcji main() zawsze powinien to być typ int.

int main()

Ponieważ funkcja nosi nazwę „main”, to właśnie od niej rozpocznie się wykonywanie programu. Gdyby funkcja miała jakieś parametry, zostałyby one tu wymienione.

{ int decks;

 ?6 Q$  6 @$Z6  Zawartość funkcji jest zawsze umieszczana pomiędzy nawiasami klamrowymi.

if (decks < 1) {  F +6 Q$ return 1; }  ^`6  67@D$ 6 'q4 return 0; }

Nazwa funkcji jest podawana za typem wyniku. Za nazwą mogą natomiast zostać podane parametry funkcji, oczywiście jeśli w ogóle takie są. Ostatnim elementem funkcji jest jej zawartość. Zawartość funkcji musi być zapisana wewnątrz pary nawiasów klamrowych.

Dla maniaków Funkcja printf() służy do wyświetlania sformatowanych danych wynikowych. Zastępuje ona znaki formatujące wartościami zmiennych. Oto przykład: wiony Pierwszy parametr zostanie wsta ów. znak uch łańc jako

Pierwszy parametr

   

!" ##$

 #%&'* Drugi parametr zostanie wstawiony jako liczba całkowita.

Drugi parametr

Wywołując funkcję printf(), można w niej podać dowolnie wiele parametrów, musimy się jednak przy tym upewnić, że użyliśmy tyle samo znaków formatujących %.

6

Rozdział 1.

ć Jeśli chcesz sprawdzi status wykonania programu, to w systemie Windows wpisz:

!+,01 natomiast w systemach Linux lub Mac OS wpisz:

!+23

Zaczynamy poznawać C

Magnesiki z kodem                                 !   !    "  !     # $   %  & /* 'F +2M6 " 'H  M67O ?Q6R6 " ' 645AUV6HQX6 MY6 " */ ...................................................... ...................................................... .......... main() { 676x9|: Wpisz dwuliterowy symbol    MQM 6x  określający nazwę karty.  6 @4 $6x int val = 0;  6x95:~~GHG val = 10; €  6x95:~~GG ............................................ €  6x95:~~"""""""""""" val = 10; €""""""""""""" 6x95:~~"""""""""""

*

97":+; 1 5&&

*

int

............................................ } else { Ta funkcja konwertuje tekst na liczbę. val = atoi(card_name); }  ‚M@D$ƒ

4$4

............. 0;





6 !7

if

1 5&


Nasz kod działa dobrze. Dzięki połączeniu dwóch warunków przy użyciu operatora logicznego możemy sprawdzać całe zakresy, a nie konkretne wartości. Teraz dysponujemy już podstawową strukturą programu liczącego karty.

Komputer twierdzi, że to była niska karta. Liczba się powiększyła! Warto robić większe zakłady! Dużo większe!

Niewidoczne urządzeni e komunikacyjne

jesteś tutaj 

21

Wywiad z gcc

Kompilator bez tajemnic W tym tygodniu tematem wywiadu jest:

Czy gcc kiedykolwiek coś dla nas zrobił?

 Zacznę od podziękowań, że w swoim napiętym harmonogramie znalazłeś nieco czasu na wywiad z nami.

 Stary, to naprawdę żaden problem. Cała przyjemność po mojej stronie.  gcc, powiedz, czy to prawda, że potrafisz mówić w wielu językach?

 Płynnie posługuję się ponad sześcioma milionami form komunikacji…  Naprawdę?

 Nie, żartuję. Ale faktycznie znam kilka języków. Oczywiście jest to C, lecz oprócz niego także C++ oraz Objective-C. Radzę sobie także z Pascalem, Fortranem, PL/I i kilkoma innymi. A… no i liznąłem także nieco Go.  A jeśli chodzi o stronę sprzętową, to jesteś w stanie generować kod maszynowy na bardzo wiele różnych platform, czy tak?

 Tak, praktycznie na każdy procesor. Właściwie, kiedy projektanci tworzą nowy rodzaj procesora, to pierwszą rzeczą, na jakiej im zależy, jest uruchomienie na nim mnie w jakiejś postaci.

 Wspominałeś o dwóch stronach swojej osobowości.

 Mam także stronę wewnętrzną: system służący do konwertowania kodu pośredniego na kod maszynowy, który jest rozumiany przez wiele różnych platform sprzętowych. Trzeba jeszcze do tego dodać znajomość konkretnych formatów plików wykonywalnych stosowanych w niemal wszystkich systemach operacyjnych, o których kiedykolwiek słyszałeś…  A jednak, pomimo tego wszystkiego, czasami uważają cię za zwyczajny translator. Czy uważasz, że to jest sprawiedliwe? Przecież bez wątpienia to nie wszystko, czym jesteś.

 No cóż, oczywiście, że robię znacznie więcej niż zwyczajne tłumaczenie. Potrafię na przykład wykrywać błędy w kodzie.  Takie jak?

 Z rzeczy oczywistych — potrafię wykrywać błędy w nazwach zmiennych. Jednak wykrywam także błędy o bardziej subtelnej naturze, takie jak powtórzona definicja jakiejś zmiennej. Umiem także ostrzegać programistów, by nie nadawali jakiejś zmiennej takiej samej nazwy, jaką ma już istniejąca funkcja i tak dalej.

 W jaki sposób udało ci się osiągnąć tak niesamowitą elastyczność?

 A zatem potrafisz także dbać o jakość kodu?

 Przypuszczam, że tajemnicą mojego sukcesu są dwie strony mojej osobowości. Strona zewnętrzna rozumie pewne rodzaje kodu źródłowego.

 O, tak. I nie tylko o jakość, lecz także o wydajność jego działania. Jeśli wykryję, że fragment kodu umieszczony wewnątrz pętli mógłby równie dobrze działać poza nią, mogę go błyskawicznie tam przenieść.

 Napisanego w takim języku jak C?

 Właśnie tak. Ta zewnętrzna strona mojej osobowości potrafi konwertować ten język na kod pośredni. Wszystkie moje osobowości rozumiejące różne języki generują ten sam rodzaj kodu.

22

Rozdział 1.

 Czyli w rzeczywistości robisz całkiem sporo!

 Tak właśnie uważam. Ale robię to po cichu.  gcc, dziękuję ci bardzo.

Zaczynamy poznawać C

BĄDŹ kompilatorem Każdy z fragmentów kodu, które są przedstawione na tej stronie, reprezentuje kompletny plik źródłowy programu napisanego w języku C. Twoim zadaniem jest wcielić się w rolę kompilatora i określić, czy każdy z nich uda się skompilować, a jeśli nie, to dlaczego. Aby zdobyć dodatkowe punkty, powiedz, jakie według Ciebie będą wyniki wygenerowane przez skompilowany program oraz czy działają one zgodnie z przeznaczeniem. A #include int main() { int card = 1; if (card > 1) card = card - 1; if (card < 7) puts(“Niska karta”); else { puts(“As!”); } return 0; }

B #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts(“Niska karta”); else puts(“As!”); } return 0; }

 #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts(“Niska karta”); } else puts(“As!”); return 0; }

D #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts(“Niska karta”); else puts(“As!”); return 0; }

jesteś tutaj 

23

Kod skompilowany

BĄDŹ kompilatorem. Rozwiązanie Każdy z fragmentów kodu, które są przedstawione na tej stronie, reprezentuje kompletny plik źródłowy programu napisanego w języku C. Twoim zadaniem jest wcielić się w rolę kompilatora i określić, czy każdy z nich uda się skompilować, a jeśli nie, to dlaczego. Aby zdobyć dodatkowe punkty, powiedz, jakie według Ciebie będą wyniki wygenerowane przez skompilowany program oraz czy działają one zgodnie z przeznaczeniem. A #include

Kod można skompilować. Program wyświetla komunikat „Niska karta”. Kod nie działa jednak prawidłowo, gdyż klauzula else jest połączona z nieodpowiednią instrukcją if.

int main() { int card = 1; if (card > 1) card = card - 1; if (card < 7) puts(“Niska karta”); else { puts(“As!”); } return 0; }

B #include

Kod można skompilować. Program nic nie wyświetla i nie działa prawidłowo, gdyż klauzula else jest dołączona do niewłaściwej instrukcji if.

int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts(“Niska karta”); else puts(“As!”); } return 0; }

24

Rozdział 1.

 #include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts(“Niska karta”); } else puts(“As!”); return 0; }

D

Kod można skompilować. Program wyświetla komunikat „As!” i jest prawidłowo napisany.

#include int main() { int card = 1; if (card > 1) { card = card - 1; if (card < 7) puts(“Niska karta”); else puts(“As!”); return 0; }

Tego programu nie uda się skompilować, gdyż brakuje jednego nawiasu klamrowego.

Zaczynamy poznawać C

Jak aktualnie wygląda nasz kod? int main() { 676x9|:    MQM 6x   6 @4 $6x int val = 0;  6x95:~~GHG val = 10; €  6x95:~~GG val = 10; €  6x95:~~G‰G val = 10; €  6x95:~~GŠG val = 11; } else { val = atoi(card_name); } &'V M6 M‚‚6   |‡'&  ƒ‘4ZZ ƒ’“  R6 Q  + $ &'‰‚  M6 MA5‰QH'& else if (val == 10)  R6 Q  +  $ return 0; }

Hm… A czy można coś zrobić z tą sekwencją instrukcji if? Wszystkie one sprawdzają tę samą wartość, konkretnie card_name[0], a większość z nich także robi to samo — przypisuje zmiennej val wartość 10. Zastanawiam się, czy w języku C można wyrazić to w jakiś bardziej efektywny sposób.

Często zdarza się, że programy pisane w języku C muszą kilkakrotnie sprawdzać tę samą wartość, a następnie dla każdego przypadku wykonywać bardzo podobne fragmenty kodu. Teraz możesz używać sekwencji instrukcji if i najprawdopodobniej wszystko będzie działało bardzo dobrze. Jednak język C pozwala na zapisanie takiej logiki działania także w innej postaci.

W języku C testy logiczne można także wykonywać, używając instrukcji switch.

jesteś tutaj 

25

Instrukcja switch

Pociąg do Switcherado Czasami, kiedy tworzymy kod z logiką warunkową, konieczne jest wielokrotne sprawdzanie wartości tej samej zmiennej. Aby uniknąć konieczności pisania wielu instrukcji if, w języku C wprowadzono alternatywne rozwiązanie: instrukcję !+. Instrukcja switch jest nieco podobna do if, jednak w odróżnieniu od niej pozwala na testowanie wielu wartości jednej zmiennej: !+  'L

Jeśli wartość zmiennej train == 37, to do zmiennej winnings dodaj 50 i przeskocz na koniec instrukcji.

! OJ  ~ ”q5 " *

Jeśli wartość zmiennej train == 65, to do zmiennej winnings dodaj 80, A NASTĘPNIE dodaj do tej samej zmiennej 20 i przeskocz na koniec instrukcji.

! S[  •+–$ winnings = winnings + 80; ! &%  ~ ”45 " *

Jeśli wartość zmiennej train == 12, to do zmiennej winnings dodaj 20 i przeskocz na koniec instrukcji.

7  winnings = 0; }

Jeśli zmienna train ma jakąkolwiek inną wartość, to przypisz zmiennej winnings wartość ZERO.

Kiedy komputer dotrze do instrukcji switch, sprawdza wartość zmiennej, jaka została w niej podana, a następnie próbuje odnaleźć pasującą do tej wartości klauzulę case. Jeśli uda się ją znaleźć, wykonuje cały kod aż do napotkania pierwszej instrukcji break. Komputer będzie wykonywał kod aż do momentu, gdy każemy mu przerwać wykonywanie instrukcji !+.

'"  %

Brakujące instrukcje break mogą być przyczyną błędnego działania kodu.

W większości programów w języku C każda klauzula case kończy się instrukcją break. Ułatwia to zrozumienie kodu, choć może nieco pogarszać efektywność jego działania.

26

Rozdział 1.

Zaczynamy poznawać C

Zaostrz ołówek Przyjrzyjmy się jeszcze raz poniższemu fragmentowi naszego programu cards: int val = 0;  6x95:~~GHG val = 10; €  6x95:~~GG val = 10; €  6x95:~~G‰G val = 10; €  6x95:~~GŠG val = 11; } else { val = atoi(card_name); }

Czy sądzisz, że byłbyś w stanie przepisać ten kod, używając instrukcji switch? Odpowiedź zapisz poniżej:

jesteś tutaj 

27

Kod z instrukcją switch

Zaostrz ołówek Rozwiązanie

Twoim zadaniem było przepisanie kodu z użyciem instrukcji switch.

int val = 0;

int val = 0;

switch(card_name[0]) {

 6x95:~~GHG val = 10;

case ’K’:

€  6x95:~~GG

case ’Q’:

val = 10;

case ’J’:

€  6x95:~~G‰G

val = 10;

val = 10;

break;

€  6x95:~~GŠG

case ’A’:

val = 11;

val = 11;

} else {

break;

val = atoi(card_name);

default:

}

val = atoi(card_name); }

Nie istnieją

CELNE SPOSTRZEŻENIA Q

Instrukcją switch można zastąpić sekwencję instrukcji if.

głupie pytania

P: Dlaczego miałbym używać P: Czy w instrukcjach !+ instrukcji !+ zamiast if?

O

Q

Instrukcja switch sprawdza pojedynczą wartość.

Q

Komputer rozpocznie wykonywanie kodu od pierwszej pasującej klauzuli case.

: Na użycie instrukcji switch możesz się zdecydować, jeśli musisz wykonać wiele testów operujących na tej samej zmiennej.

Q

Kod będzie wykonywany do pierwszej napotkanej instrukcji break bądź aż do końca instrukcji switch.

P: Jakie są zalety korzystania

Upewnij się, że umieściłeś instrukcje break we wszystkich właściwych miejscach, w przeciwnym razie instrukcje switch mogą działać nieprawidłowo.

O: Jest ich kilka. Przede wszystkim:

Q

28

Rozdział 1.

z instrukcji !+?

przejrzystość. Dzięki niej od razu wiadomo, że cały blok kodu operuje na jednej zmiennej. W przypadku sekwencji instrukcji if wcale nie jest to aż tak oczywiste. Poza tym można skorzystać z przechodzenia kolejnych sekcji case, by wielokrotnie używać tego samego kodu.

mogą być używane wyłącznie zmienne? Nie można w nich używać wartości?

O: Owszem, można. Instrukcja

switch może sprawdzać, czy dwie wartości są sobie równe.

P

: Czy w instrukcjach !+ można sprawdzać łańcuchy znaków?

O: Nie, nie można używać

instrukcji switch do porównywania ani łańcuchów znaków, ani jakichkolwiek tablic. Instrukcje switch mogą operować wyłącznie na pojedynczych wartościach.

Zaczynamy poznawać C

Czasami jeden raz nie wystarcza… Nauczyłeś się już całkiem sporo o języku C, jednak wciąż pozostaje jeszcze wiele ważnych rzeczy, o których musisz się dowiedzieć. Zobaczyłeś już, jak pisać programy określające sposób działania w zależności od wielu różnych sytuacji, istnieje jednak pewne kluczowe zagadnienie, którym się jeszcze nie zajmowaliśmy. Co zrobić, kiedy chcemy, by nasz program wykonywał jakąś czynność wiele, wiele, wiele razy?

Dwie karty??? O kurczę…

Stosowanie pętli w języku C Pętle są szczególnym rodzajem instrukcji sterujących. Instrukcje sterujące określają, czy dany blok kodu zostanie wykonany, czy nie. Natomiast pętle określają, ile razy dany blok kodu zostanie wykonany. Najprostszym rodzajem pętli dostępnym w języku C jest pętla while. Wykonuje ona umieszczony wewnątrz niej blok kodu dopóty, dopóki określony w niej warunek logiczny jest spełniony.

Ten warunek jest sprawdzany przed wykonaniem zawartości pętli.

+9 G=   ;'L Zawartość pętli jest umieszczana pomiędzy nawiasami klamrowymi.

"""&'FMM‚6"'& }

Jeśli zawartość pętli składa się z jednego wiersza kodu, nawiasy klamrowe nie są potrzebne.

Po wykonaniu całego bloku kodu umieszczonego wewnątrz pętli komputer sprawdza, czy warunek podany na jej początku wciąż jest spełniony, a jeśli jest, to zawartość pętli jest wykonywana jeszcze raz.

while (more_balls) keep_juggling();

Przynajmniej jeden do while   &  (! while4&,    &   po& &       ! $3  4 & !    &przynamniej jeden raz$  ˜7 do { &'HM'& while (have_not_won);

jesteś tutaj 

29

Pętle for

Pętle często mają taką samą strukturę… Pętli while możemy używać zawsze wtedy, gdy konieczne jest wielokrotne wykonanie jakiegoś fragmentu kodu. Jednak w bardzo wielu przypadkach nasze pętle będą miały taką samą strukturę:  Wykonać jakąś prostą operację przed rozpoczęciem pętli, Ì na przykład ustawić wartość licznika.  Wykonać prosty test na początku pętli. Ì  Wykonać jakąś operację na końcu pętli, na przykład zmienić Ì wartość licznika. Na przykład poniższa pętla while liczy od 1 do 10: To jest kod aktualizujący, który zmienia wartość licznika i jest wykonywany na samym końcu pętli.

To jest kod początkowy pętli.

int counter = 1; while (counter < 11) {

To jest warunek pętli.

 @ M67Q ‚6`D$6 counter++; }

Pamiętaj: counter++ oznacza „pow wartość zmiennej counter o jede iększ n”.

Takie pętle składają się z kodu, który przygotowuje zmienne używane w danej pętli, jakiejś logiki warunkowej sprawdzanej przed każdym wykonaniem pętli oraz jakiegoś kodu umieszczonego na końcu pętli, który aktualizuje licznik lub wykonuje jakąś inną, podobną operację.

…a dzięki instrukcji for tworzenie takich pętli jest łatwe Ponieważ pętle tworzone według takiego wzorca występują bardzo często, projektanci języka C stworzyli instrukcję for, dzięki której kod takich pętli może być nieco krótszy i bardziej zwarty. Oto kod realizujący dokładnie to samo zadanie, ale napisany z użyciem pętli for: Ten fragment inicjalizuje zmienną używaną w pętli.

 ! *

To jest warunek sprawdzany przed każdym wykonaniem pętli.

! 5&*! 9&&*! RR'L

To kod, który zostanie zrealizowany po każdym wykonaniu zawartości pętli.

 @ M67Q ‚6`D$6 }

Ponieważ nasza pętla zawiera tylko jeden wiersz kodu, moglibyśmy pominąć nawiasy klamrowe.

Pętle for są bardzo często używane w programach pisanych w języku C — równie często, a może nawet częściej niż pętle while. Nie tylko umożliwiają one nieznaczne skrócenie kodu, lecz także ułatwiają jego zrozumienie, gdyż cały kod używany do sterowania działaniem pętli — czyli związany ze sprawdzaniem i aktualizacją zmiennej counter — jest umieszczony w samej instrukcji for, a nie rozsiany w kodzie umieszczonym wewnątrz niej.

30

Rozdział 1.

Każda pętla for musi mieć jakąś zawartość.

Zaczynamy poznawać C

Instrukcji break używamy, by wydostać się z pętli… W języku C możemy tworzyć pętle sprawdzające warunek logiczny na samym jej początku lub końcu. Ale co zrobić, jeśli będziemy chcieli przerwać działanie pętli gdzieś w środku kodu umieszczonego wewnątrz pętli? Zawsze można odpowiednio zmienić strukturę kodu, jednak czasami łatwiej jest wyjść z pętli natychmiast, w dowolnym jej miejscu, używając do tego instrukcji " :

'"  %

Instrukcja break jest używana do przerywania działania pętli oraz instrukcji switch.

Używając instrukcji break, upewnij się, że wiesz, z której instrukcji chcesz wyjść.

+ C=+ CF'L eat_cake(); if (feeling_queasy) { &' M67 M 7'& " * }

Instrukcja „break” powoduje natychmiastowe wyjście z pętli.

drink_coffee(); }

Użycie instrukcji break spowoduje natychmiastowe wyjście z aktualnie wykonywanej pętli i pominięcie całej reszty umieszczonego w niej kodu. Stosowanie tych instrukcji jest bardzo przydatne, gdyż czasami stanowią one najszybszy i najprostszy sposób zakończenia działania pętli. Warto jednak unikać stosowania zbyt wielu instrukcji break, gdyż mogą one utrudniać zrozumienie działania kodu.

…a instrukcji continue, by ją kontynuować Jeśli chcemy pominąć dalszą część kodu umieszczonego wewnątrz pętli i wrócić do jej początku, możemy skorzystać z pomocy instrukcji continue: + C=+ CF'L if (not_lunch_yet) { &' 6M6 `'& !  * } eat_cake(); }

Instrukcja continue przenosi nas z powrotem na początek pętli.

Opowieści z krypty Break nie przerywa instrukcji if. 15 stycznia 1990 roku uległ awarii system rozmów długodystansowych firmy AT&T, przez co 60 tysięcy osób nie było w stanie korzystać z usług telefonicznych. A co było przyczyną tej awarii? Otóż programista, który pisał w języku C kod odpowiadający za wymianę połączeń, spróbował użyć instrukcji break, by zakończyć wykonywanie instrukcji if. Jednak instrukcje break nie działają w instrukcjach if. W efekcie program pomijał całą sekcję kodu, co było przyczyną błędu, który doprowadził do przerwania 70 milionów rozmów telefonicznych w ciągu dziewięciu godzin.

jesteś tutaj 

31

Pisanie funkcji

( ) # Zanim wypróbujemy magiczne moce nowo poznanych pętli, udajmy się na krótki objazd, by przyjrzeć się funkcjom. Do tej pory w każdym tworzonym programie musiałeś zdefiniować jedną funkcję — main(): Ta funkcja zwraca wartość typu int.

To jest nazwa funkcji.

int main()

Wewnątrz tych nawiasów nic nie ma.

{ Zawartość funkcji jest zapisana pomiędzy nawiasami klamrowymi.

To jest zawartość funkcji — to właśnie ona robi to, co trzeba.

 *QM+   QMQM2M$ return 0; Po zakończeniu funkcja zwraca wartość.

}

Niemal wszystkie funkcje w języku C wyglądają podobnie. Na przykład poniższy program zawiera napisaną przez nas niestandardową funkcję, która jest wywoływana przez funkcję main(): #include Zwracana jest wartość typu int.

 Q { if (a > b) return a;

Ta funkcja przyjmuje dwa argumenty: a oraz b. Oba są wartościami typu int.

return b; } int main()

W tym miejscu wywołujemy funkcję.

{  ~ A55A555  @  –D$  return 0; }

Funkcja larger() nieznacznie różni się od funkcji main(), gdyż pobiera argumenty, nazywane także parametrami. Argument jest zmienną lokalną, której wartość jest przekazywana przez kod wywołujący funkcję. Funkcja larger() pobiera dwa argumenty — a oraz b — i zwraca większą z przekazanych liczb.

32

Rozdział 1.

Uprzejmy przewodnik po standardach %  "   "&  main() #  &int, zatem na jej &5  !  +(  &return$6   &   4& & )! & ( 7(&      (   $  &  kompilatora C99 w razie   !   & return

    $ )&   ##   **4    #  ( parametr -std=99$

Zaczynamy poznawać C

*+   #

Nie istnieją

głupie pytania

Większość funkcji pisanych w języku C zwraca jakąś wartość. Jednak od czasu do czasu zdarzają się funkcje niedysponujące żadnymi użytecznymi informacjami, które mogłyby zwrócić. Takie funkcje zazwyczaj coś robią, a nie wyliczają. Funkcje zawsze powinny zawierać instrukcję return, wyjątkiem są te spośród nich, w których typem wyniku jest 17. Typ void oznacza, że funkcja nic nie zwraca.

void complain() {  ›‰  6 ‚M–› }

Instrukcja return nie jest potrzebna, gdyż funkcja nic nie zwraca.

W języku C słowo kluczowe void oznacza, że coś nie ma znaczenia. Jeśli tylko poinformujemy kompilator, że nie interesuje nas zwracanie wyniku z funkcji, to nie będziemy musieli umieszczać w niej instrukcji return.

Łańcuchy przypisań Niemal wszystko w języku C zwraca jakąś wartość; nie dotyczy to wyłącznie funkcji. W rzeczywistości nawet przypisania mają swoją wartość wynikową. Przyjrzyjmy się na przykład poniższej instrukcji:

Przypisanie „x = 4” ma wartość 4.

P

: Czy zastosowanie typu 17 w definicji funkcji oznacza, że wewnątrz niej nie można używać instrukcji  ?

O: W takiej funkcji wciąż można

używać instrukcji return, jednak może to spowodować wygenerowanie ostrzeżenia przez kompilator. Poza tym umieszczanie instrukcji return w takiej funkcji jest bezcelowe.

P: Naprawdę? Dlaczego? O: Gdyż próba pobrania wartości z funkcji typu void spowoduje zgłoszenie błędu przez kompilator.

A zatem także zmiennej y zostanie przypisana wartość 4.

y = (x = 4);

Powyższy wiersz kodu przypisze wartość U zarówno zmiennej x, jak i y. Okazuje się, że możemy nieco skrócić ten kod, usuwając z niego nawiasy:

x = 4; y = x = 4; Instrukcja ta przypisuje zmiennej liczbę U. Interesujące jest to, że samo wyrażenie 8~U ma wartość równą wartości przypisywanej, czyli U. A dlaczego to ma znaczenie? Ponieważ oznacza to, że możemy stosować ciekawe sztuczki, takie jak tworzenie łańcuchów przypisań:

Takie łańcuchowe przypisania można bardzo często zobaczyć w kodzie, w którym kilku zmiennym trzeba przypisać tę samą wartość.

jesteś tutaj 

33

Pomieszane wiadomości Poniżej został przedstawiony kod prostego programu napisanego w C. Brakuje w nim jednego fragmentu. Twoim zadaniem jest dopasowanie proponowanych bloków kodu (widocznych poniżej z lewej strony) do wyników, które zostałyby wygenerowane przez program w razie umieszczenia w nim danego kodu. Nie wszystkie wiersze wyników zostaną użyte, a niektóre z nich mogą zostać wykorzystane więcej niż jeden raz. Narysuj linie łączące proponowane bloki kodu z generowanymi przez nie wynikami.

(     ,

#include int main() { int x = 0; int y = 0; 7 8’q Tutaj umieść u. proponowany blok kod

 ›@@›8M x = x + 1; } return 0; }

      

Dopasuj proponowane bloki kodu do generowanych przez nie wyników.

    

F5TDF*

%%PS

F5FRT*

&&OP[\

F5FR%* F;P'

Zamiast wyświetlić pełną długość łańcucha, powyższa funkcja poinformuje, że łańcuch ma 4 lub 8 bajtów długości. Co się stało? Dlaczego funkcja uważa, że przekazany przez nas łańcuch jest krótszy?

WYSIL

SZARE KOMÓRKI Jak sądzisz, dlaczego operator sizeof(msg) zwrócił wartość mniejszą od faktycznej długości łańcucha? Dlaczego miałby zwracać różne długości na różnych komputerach?

jesteś tutaj 

53

Zmienne tablicowe

Zmienne tablicowe są jak wskaźniki… Kiedy tworzymy tablicę, zmienna tablicowa będzie używana jako wskaźnik do miejsca pamięci, w którym zaczyna się tablica. Kiedy wewnątrz funkcji język C zobaczy następujący wiersz kodu: 67¦9:~$‰ `66 6 M –$

Zmienna quote będzie reprezentować adres pierwszego znaku łańcucha.

J

e

d

z



c

... \0

komputer zarezerwuje na stosie miejsce dla każdego znaku łańcucha oraz dodatkowe — dla znaku \0. Jednak dodatkowo skojarzy on adres pierwszego znaku ze zmienną quote. Za każdym razem, gdy zmienna ta zostanie zastosowana w kodzie, komputer zastąpi ją adresem pierwszego znaku łańcucha. W rzeczywistości zmienna tablicowa jest bardzo podobna do wskaźnika:  $*¦  67M @D$¦

Zmiennej „quote” można użyć jako zmiennej wskaźnikowej, choć w rzeczywistości jest ona tablicą.

% &/3&%; <  9

Jeśli napiszesz testowy program do wyświetlania adresu, to zobaczysz wyniki takie jak te.

> ./where_is_quote U 

† !+F

7 7  ./skip 7ˆ7  >

Dlaczego wskaźniki mają typ? Skoro wskaźniki są tylko adresami, to dlaczego zmienne wskaźnikowe mają typy? Dlaczego nie możemy zapisywać wszystkich wskaźników w jakiejś jednej, ogólnej zmiennej wskaźnikowej? Wynika to z faktu, że arytmetyka wskaźników jest podstępna. Jeśli dodamy & do wskaźnika typu char, to wskaźnik ten wskaże sąsiednią komórkę pamięci. Ale dzieje się tak dlatego, że wartość typu char zajmuje tylko jeden bajt pamięci. A co, jeśli będziemy mieli wskaźnik typu int? Dane typu int zazwyczaj zajmują 4 bajty pamięci, a zatem jeśli dodamy 1 do wskaźnika typu int, to skompilowany kod będzie w rzeczywistości powiększał adres o 4.

int*

long*

char* short*

e Zmienne wskaźnikowe mają różn typy, zależnie od danych, na jakie wskazują.

 9:~A4|€ printf(”nums ma adres %p\n”, nums); Pamiętaj, że te adresy są wyświetlane w postaci liczb szesnastkowych.

printf(”nums + 1 ma adres %p\n”, nums + 1);

Kiedy wykonamy ten kod, dwa wyświetlone adresy będą oddalone od siebie o więcej niż 1 bajt. A zatem typy wskaźników istnieją po to, by kompilator wiedział, o ile modyfikować wskaźniki podczas wykonywania na nich operacji arytmetycznych. Wskaźni k (nums + 1) jest oddalony od wskaźnika nums o 4 bajty.

62

Rozdział 2.

Plik Edycja Okno Pomoc

> ./print_nums

   7 gcc monte.c -o monte && ./monte monte.exe has stopped working

FaulSeg!

Wpadka!

Błąd magistrali!

Co jest nie w porządku z tym kodem?

70

Rozdział 2.

Błąd s

egmen

tacji!

Pamięć i wskaźniki : 7

?

7

CO O TYM SĄDZISZ?

Czas zaprząc do pracy naszą intuicję. Nie staraj się zbytnio analizować. Po prostu zgaduj. Przeczytaj poniższe odpowiedzi i wybierz tylko jedną z nich — tę, którą uważasz za prawidłową. Jak sądzisz, na czym polega problem?

Łańcucha znaków nie można zmieniać.

Zamieniamy znaki, umieszczając je poza łańcuchem.

Łańcuch nie znajduje się w pamięci.

Przyczyną jest coś innego.

jesteś tutaj 

71

Dobra intuicja : 7

?

7

CO O TYM SĄDZISZ? ROZWIĄZANIE

Miałeś skorzystać ze swojej intuicji. Twoim zadaniem było przeczytanie poniższych odpowiedzi i wybranie tylko jednej z nich — tej, którą uważasz za prawidłową. Na czym według Ciebie polegał problem?

Łańcucha znaków nie można zmieniać.

Zamieniamy znaki, umieszczając je poza łańcuchem.

Łańcuch nie znajduje się w pamięci.

Przyczyną jest coś innego.

Literały łańcuchowe nie mogą być nigdy modyfikowane Zmiennej wskazującej na literał łańcuchowy nie można użyć do zmiany zawartości łańcucha: 67'6 ~$‰H$

Za pomocą tej zmiennej nie można zmieniać tego łańcucha.

Gdybyśmy jednak utworzyli łańcuch jako tablicę, to w takim przypadku moglibyśmy go modyfikować: 676 9:~$‰H$

Wszystko sprowadza się do tego, w jaki sposób język C korzysta z pamięci…

72

Rozdział 2.

Pamięć i wskaźniki

W pamięci: char *cards=”JQK” Aby zrozumieć, dlaczego powyższy wiersz kodu powoduje błędy, musimy pogrzebać nieco w pamięci komputera i przekonać się, co dokładnie komputer będzie w niej robić. Najwyższy adres

1 2    $ $*" Kiedy komputer wczytuje program do pamięci, umieszcza wszystkie wartości stałe, takie jak literał łańcuchowy $‰H$, w specjalnym obszarze pamięci służącym do przechowywania stałych. Ten blok pamięci jest przeznaczony tylko do odczytu.

STOS 2

2       

  

cards

Stos jest tym obszarem pamięci, którego komputer używa do przechowywania zmiennych lokalnych, czyli zmiennych stosowanych wewnątrz funkcji. To właśnie w tym obszarze zostanie umieszczona zmienna cards.

STERTA

3 .  

        $*" 2= .............) {  $@6$' t t = ...........

.........

1 .........;

Wyliczanie adresów w taki spos ób jest nazywane „arytmetyką wskaźnik ów”.

} puts(””); }

Tablica tablic czy tablica wskaźników? Dowiedziałeś się już, jak utworzyć tablicę tablic, by przechować grupę łańcuchów znaków. Jednak można to także zrobić w inny sposób — tworząc tablicę wskaźników. Tablica wskaźników jest dokładnie tym, czego się można spodziewać: listą adresów zapisaną w tablicy. Jest ona bardzo przydatna, jeśli chcemy szybko i wygodnie stworzyć listę literałów łańcuchowych. 67' xx9:~$•7$$Y$$µ8$€

To jest tablica przechowująca wskaźniki.

W tablicy znajdą się wskaźniki, z których każdy wskazuje na jeden z tych literałów łańcuchowych.

Do tablicy wskaźników można się odwoływać dokładnie tak samo jak do tablicy tablic.

98

Rozdział 2,5

Łańcuchy znaków

 178

Twój niezbędnik C Przeczytałeś już rozdział 2,5, a do swojego niezbędnika dodałeś wiadomości dotyczące wskaźników i pamięci. Pełną listę porad i wskazówek znajdziesz w dodatku B. Tablica łańcuchów znaków je s tablicą ta t blic.

kowy Plik nagłów ra ie w za string.h ne cz te uży funkcje do operacji na łańcuchach znaków.

Funkcja strstr(a, b) zwraca adres łańcucha b wewnątrz łańcucha a.

ic Tablicę tabl tworzy się, isu p używając za [...][...]. s char string

Funkcja strcmp() porównuje dwa łańcu chy znaków.

Funkcja strchr() określa położenie znaku wewnątrz łańcucha.

Funkcja czy łą strcat() dwa ą b o ze s y łańcuch w ó znak .

Funkcja strcpy() kopiuje jeden łańcuch znaków do drugiego.

Funkcja strlen() określ a długość łańcucha znaków.

jesteś tutaj 

99

100

Rozdział 2,5

3.9   4 / $  -  4

Rób jedną rzecz, ale rób ją dobrze Wszystko sprowadza się do wyboru odpowiedniego narzędzia do konkretnego zadania…

Każdy system operacyjny udostępnia niewielkie programy narzędziowe. Niewielkie programy narzędziowe pisane w języku C wykonują wyspecjalizowane zadania, takie jak odczytywanie i zapisywanie plików czy też filtrowanie danych. Jeśli chcemy wykonać bardziej złożone zadanie, można nawet połączyć ze sobą kilka takich programów. W jaki jednak sposób tworzy się takie małe programy narzędziowe? W tym rozdziale przyjrzymy się elementom używanym podczas ich tworzenia. Dowiesz się, jak korzystać z opcji wiersza poleceń, jak zarządzać strumieniami informacji, czym są przekierowania, i błyskawicznie zdobędziesz nowe narzędzia.

to jest nowy rozdział 

101

Małe programy narzędziowe

Małe programy narzędziowe mogą rozwiązywać wielkie problemy Mały program narzędziowy to program napisany w języku C, który wykonuje jedno zadanie i robi to bardzo dobrze. Może on wyświetlać zawartość pliku na ekranie komputera lub generować listę aktualnie działających procesów. Może też wyświetlać bądź drukować 10 pierwszych wierszy pliku. Większość systemów operacyjnych jest dostarczanych z bogatym zestawem takich programów, które można wykonywać z poziomu wiersza poleceń lub okna terminala. Czasami, kiedy mamy do rozwiązania duży problem, możemy go podzielić na sekwencję małych problemów, a następnie napisać narzędzia do rozwiązania każdego z nich.

Mały program narzędziowy realizuje jedno zadanie i robi to dobrze. ie jak Linux Systemy operacyjne takją się z setek ada skł i ośc cał w l niema narzędziowych. niewielkich programów

Znajomy napisał mi aplikację z internetową mapą. Uwielbiam publikować na niej dane z moich wycieczek rowerowych. Mam jednak problem, polega on na nieprawidłowym formacie danych zapisywanych przez GPS. h To jest format danych zapisywanyc są przez odbiornik GPS. Informacje cinkami. w nim oddzielane od siebie prze To jest szerokość geograficzna.

To jest długość geograficzna.

42.363400,-71.098465,Speed = 21 42.363327,-71.097588,Speed = 23 42.363255,-71.096710,Speed = 17

To jest format danych wymagany przez mapę. Nosi on nazwę JavaScript Object Notation, w skrócie JSON.

data=[ {latitude: 42.363400, longitude: -71.098465, info: 'Speed = 21'}, {latitude: 42.363327, longitude: -71.097588, info: 'Speed = 23'}, {latitude: 42.363255, longitude: -71.096710, info: 'Speed = 17'}, ... ]

Jeśli jakaś część programu wymaga przekonwertowania danych z jednego formatu na drugi, to jest to zadanie, które w prosty sposób można wykonać właśnie przy użyciu małego programu narzędziowego.

102

Rozdział 3.

Dane są takie same, jednak ich format jest trochę inny.

Tworzenie małych programów narzędziowych

Hej, kto z nas nie zabrał na wyjazd długiego wydruku kodu tylko po to, by przekonać się, że szybko stanie się on zupełnie… nieczytelny? Oczywiście wszystkich nas to kiedyś spotkało. Jednak po chwili zastanowienia powinieneś być w stanie poukładać kod w początkowej kolejności.

:   

Ten program potrafi odczytać rozdzielone przecinkami dane, a następnie wyświetlić je w formacie JSON. Sprawdź, czy będziesz w stanie określić, jakiego kodu brakuje.

#include

int main() { float latitude; float longitude; char info[80]; int started = .........;

puts(”data=[”);

Używamy funkcji scanf(), by wczytać kilka wartości wpisywanych przez użytkownika.

Jakie to będą wartości? Pamiętaj: funkcja scanf() zawsze używa wskaźników.

Funkcja scanf() zwraca liczbę wartości, jakie udało się jej odczytać.

while (scanf(”%f,%f,%79[^\n]”, ........., ........., .........) == 3) { if (started) printf(”,\n”); else

To sposób, by powiedzieć: „Zwróć wszystkie znaki aż do końca wiersza”. Uważaj na wartość przypisywaną zmiennej „started”.

started = ............. ; printf(”{latitude: %f, longitude: %f, info: ’%s’}”, ........, ........., .........); } puts(”\n]”);

Jakie wartości należy wyświetlić?

return 0; }

jesteś tutaj  103

Hej, kto z nas nie zabrał na wyjazd długiego wydruku kodu tylko po to, by przekonać się, że szybko stanie się on zupełnie… nieczytelny? Oczywiście wszystkich nas to kiedyś spotkało. Jednak po chwili zastanowienia powinieneś być w stanie poukładać kod w początkowej kolejności. Ten program potrafi odczytać rozdzielone przecinkami dane, a następnie wyświetlić je w formacie JSON. Miałeś sprawdzić, czy będziesz w stanie określić, jakiego kodu brakuje.

:   

  #include

int main() { float latitude; float longitude; char info[80];

Na początku zmiennej „started” musi być przypisana wartość 0, odpowiadająca logicznemu fałszowi. Czy pamiętałeś o dodaniu operatora „&” przed zmiennymi liczbowymi? Do funkcji scanf() należy przekazywać wskaźniki.

0 int started = .........;

puts(”data=[”); &latitude &longitude info while (scanf(”%f,%f,%79[^\n]”, ........., ........., .........) == 3) { if (started) printf(”,\n”);

Będziesz wyświetlał przecinek wyłącznie wtedy, gdy wcześniejszy wiersz został już wyświetlony.

else 1 started = ............. ;

Po rozpoczęciu pętli powinieneś przypisać zmiennej „started” wartość 1, reprezentującą logiczną prawdę.

longitude info printf(”{latitude: %f, longitude: %f, info: ’%s’}”, latitude ........, ........., .........); } puts(”\n]”); return 0; }

104

Rozdział 3.

ów &, Tutaj nie potrzebujesz operator ości, wart a używ tf() prin cja funk gdyż h. nnyc zmie sów a nie adre

Tworzenie małych programów narzędziowych

Jazda próbna A zatem co się stanie, kiedy skompilujesz ten kod i go uruchomisz? Co on zrobi? To są dane, które wpisałeś.

To są wyświetlane dane.

Dane wejściowe i wyjściowe są wymieszane.

Plik Edycja Okno Pomoc JSON

>./geo2json data=[ 42.363400,-71.098465,Speed = 21 {latitude: 42.363400, longitude: , {latitude: 42.363327, longitude: , {latitude: 42.363255, longitude: , ... ... ... {latitude: 42.363182, longitude: , {latitude: 42.362385, longitude: ] >

-71.098465, info: ’Speed = 21’}42.363327,-71.097588,Speed = 23 -71.097588, info: ’Speed = 23’}42.363255,-71.096710,Speed = 17 -71.096710, info: ’Speed = 17’}42.363182,-71.095833,Speed = 22

-71.095833, info: ’Speed = 22’}42.362385,-71.086182,Speed = 21 -71.086182, info: ’Speed = 21’}^D

Po kilku kolejnych godzinach wpisywania…

Program pozwala wpisać dane GPS z klawiatury, a następnie wyświetla je w formacie JSON na ekranie. Problem z tym programem polega jednak na tym, że dane wejściowe i wyjściowe są wymieszane. Co więcej, tych wszystkich danych na ekranie jest bardzo dużo. Pisząc mały program narzędziowy, raczej nie chcemy wpisywać danych ręcznie; zwłaszcza duże ilości danych będziemy chcieli pobierać, wczytując je z pliku. Poza tym w jaki sposób będą używane dane w formacie JSON? Bez wątpienia wyświetlenie ich na ekranie nie będzie szczególnie użytecznym rozwiązaniem.

W końcu, aby zatrzymać program, musisz nacisnąć kombinację klawiszy Ctrl+D.

Naprawdę nie są nam potrzebne wyniki wyświetlane na ekranie komputera. Potrzebujemy ich w pliku, abyśmy mogli wykorzystać je w internetowej aplikacji prezentującej mapę wycieczek. O tu… zobaczcie, pokażę Wam…

Czy zatem program działa dobrze? Czy robi to, co powinien? Czy musisz coś zmienić w jego kodzie?

jesteś tutaj  105

Jak to działa

Oto sposób wykorzystania programu Wczytujemy plik z danymi z rowerowego odbiornika GPS.

1

Odbiornik GPS tworzy plik o nazwie gpsdata.csv, w którym dane każdej z zarejestrowanych lokalizacji są zapisywane w osobnym wierszu.

To jest odbiornik służący do rejes GPS trowania lokalizacji na ro werze.

Dane są zapisywane w tym pliku.

gpsdata.csv

2

Program geo2json musi ! 1!   gpsdata.csv wiersz po wierszu…

Wczytujemy ten plik.

To jest nasz program narzędziowy geo2json.

geo2json

3 4  

  !   %  JSON w pliku output.json.

4 Zapisujemy ten plik.

Program narzędziowy zapisze dane w tym pliku.

106

Rozdział 3.

Strona WWW, na której jest prezentowana mapa, wczytuje 1!      

Aplikacja z mapą wczytuje dane z pliku output.json, a następnie wyświetla je na mapie prezentowanej na stronie WWW.

output.json

Tworzenie małych programów narzędziowych

Ale my nie używamy plików… Problem polega na tym, że program, który napisałeś, nie odczytuje ani nie zapisuje plików — aktualnie wczytuje on dane z klawiatury i wyświetla je na ekranie komputera.

Dane są wczytywane z klawiatury.

Nasz program narzędziowy konwertuje je na nowy format.

geo2json

na ekranie, Dane są wyświetlane u. plik w ne wa isy zap nie a JSON Okno Pomoc Plik Edycja >./geo2json data=[ ed = 21 1.098465,Spe 42.363400,-7

,Speed = 23 7,-71.097588 = 21'}42.36332 info: 'Speed -71.098465, 400, longitude: {latitude: 42.363 ,Speed = 17 5,-71.096710 = 23'}42.36325 info: 'Speed -71.097588, 327, longitude: {latitude: 42.363

,

,Speed = 22 2,-71.095833 = 17'}42.36318 info: 'Speed -71.096710, 255, longitude: {latitude: 42.363

,

, ... ... ...

A takie rozwiązanie nie wystarcza. Użytkownik nie będzie chciał wpisywać wszystkich danych, jeśli są one zapisane w jakimś pliku. A jeśli dane w formacie JSON będą wyświetlane na ekranie, to mapa prezentowana na stronie WWW nie będzie miała jak z nich skorzystać. Musisz zatem napisać program, który będzie operował na plikach. Ale jak to zrobić? Jeśli chcesz używać plików zamiast klawiatury i ekranu, to jakie fragmenty kodu trzeba będzie zmienić? Czy w ogóle trzeba będzie coś zmieniać w kodzie?

WYSIL

SZARE KOMÓRKI Czy nasz program może zacząć korzystać z plików bez wprowadzania w nim żadnych zmian? A nawet bez rekompilacji kodu?

Dla maniaków Programy narzędziowe wczytujące dane wiersz po wierszu, przetwarzające je i wyświetlające są nazywane filtrami. Jeśli używasz komputera z systemem UNIX lub zainstalowałeś Cygwin na komputerze z systemem Windows, to możesz skorzystać z kilku takich filtrów: head — ten program wyświetla kilka pierwszych wierszy pliku; tail — ten filtr wyświetla wiersze umieszczone na końcu pliku; sed — to edytor strumieniowy pozwalający na wykonywanie takich operacji jak wyszukiwanie i zastępowanie łańcuchów znaków. W dalszej części rozdziału dowiesz się, jak można łączyć filtry ze sobą, tworząc tak zwane łańcuchy filtrów.

jesteś tutaj  107

Przekieruj dane

Możesz skorzystać z przekierowania W pisanym programie używasz funkcji scanf() oraz printf(), by czytać dane z klawiatury i wyświetlać je na ekranie. Jednak w rzeczywistości funkcje te nie komunikują się bezpośrednio ani z klawiaturą, ani z ekranem monitora. Zamiast tego korzystają ze standardowego strumienia wejściowego oraz standardowego strumienia wyjściowego. Standardowy strumień wejściowy oraz standardowy strumień wyjściowy są tworzone przez system operacyjny podczas uruchamiania programu.

e za Program otrzymuje dan wego rdo pośrednictwem standa . strumienia wejściowego

System operacyjny kontroluje, w jaki sposób dane trafiają do standardowego strumienia wejściowego i wydostają się ze standardowego strumienia wyjściowego. Jeśli uruchamiasz program z poziomu wiersza poleceń lub w oknie terminala, to system będzie przekazywał wszystkie informacje o naciskanych klawiszach do standardowego strumienia wejściowego. Jeśli system operacyjny odczyta jakieś dane ze standardowego strumienia wyjściowego, to domyślnie wyświetli je na ekranie. Funkcje scanf() oraz printf() ani nie wiedzą, ani nie przejmują się tym, skąd pochodzą dane oraz gdzie później trafiają. Po prostu odczytują je ze standardowego strumienia wejściowego i zapisują w standardowym strumieniu wyjściowym. Może Ci się teraz wydawać, że to wszystko jest bardzo skomplikowane. W końcu dlaczego pisany program nie może odczytywać danych prosto z klawiatury i wyświetlać ich na ekranie? Czy tak nie byłoby prościej? Cóż, istnieje jeden bardzo dobry powód, dla którego systemy operacyjne komunikują się z programami za pośrednictwem standardowego strumienia wejściowego i standardowego strumienia wyjściowego:

Można przekierować standardowy strumień wejściowy oraz standardowy strumień wyjściowy tak, by odczytywały i zapisywały dane gdzieś indziej, na przykład w plikach.

108

Rozdział 3.

Program wyświetla dane, korzystając ze standardowego strumienia wyjściowego

Tworzenie małych programów narzędziowych

Standardowy strumień wejściowy można przekierować, używając

Operator < informuje system operacyjny, że standardowy strumień wejściowy programu powinien zostać podłączony do pliku gpsdata.csv, a nie do klawiatury. A zatem możemy przesłać do programu dane z pliku. Teraz pozostaje jeszcze przekierowanie wyników.

-71.098465, -71.097588, -71.096710, -71.095833, -71.094955, -71.094078,

info: info: info: info: info: info:

’Speed ’Speed ’Speed ’Speed ’Speed ’Speed

= = = = = =

21’}, 23’}, 17’}, 22’}, 14’}, 16’},

-71.086182, info: ’Speed = 21’}

geo2json gpsdata.csv

jesteś tutaj  109

Przekierowanie wyjścia

…a standardowy strumień wyjściowy, używając > Aby przekierować standardowy strumień wyjściowy do pliku, musisz skorzystać z operatora >. Teraz przekierowujesz zarówno standardowy strumień wejściowy, jak i standardowy strumień wyjściowy. % &/3&% % &  (   

> ./geo2json < gpsdata.csv > output.json > Wyniki programu zostaną zapisane w pliku output.json.

Teraz na ekranie komputera nic się nie pojawi; wszystkie wyniki trafią do pliku output.json.

data=[ {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: {latitude: ]

42.363400, 42.363327, 42.363255, 42.363182, 42.363110, 42.363037, 42.362965, 42.362892, 42.362820, 42.362747, 42.362675, 42.362602, 42.362530, 42.362457, 42.362385,

longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude: longitude:

-71.098465, -71.097588, -71.096710, -71.095833, -71.094955, -71.094078, -71.093201, -71.092323, -71.091446, -71.090569, -71.089691, -71.088814, -71.087936, -71.087059, -71.086182,

info: info: info: info: info: info: info: info: info: info: info: info: info: info: info:

’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed ’Speed

= = = = = = = = = = = = = = =

21’}, 23’}, 17’}, 22’}, 14’}, 16’}, 18’}, 22’}, 17’}, 23’}, 14’}, 19’}, 16’}, 16’}, 21’}

output.json

Ponieważ przekierowujesz standardowy strumień wyjściowy, na ekranie nie pojawią się żadne wyniki generowane przez program. Jednak tym razem program utworzył plik o nazwie output.json.

geo2json

Plik output.json jest właśnie tym, czego potrzebuje aplikacja prezentująca mapę wycieczek rowerowych. Zobaczmy, jak to wszystko działa.

output.json

110

Rozdział 3.

Tworzenie małych programów narzędziowych

Jazda próbna Teraz nadszedł doskonały moment, by sprawdzić, czy nowy plik z danymi wygenerowany przez program może zostać wykorzystany do wyświetlenia lokalizacji na mapie. W tym celu zrobisz kopię strony z internetową mapą i umieścisz ją w tym samym katalogu, w którym znajduje się już plik output.json. Następnie spróbuj otworzyć tę stronę w przeglądarce.

$"  %

Kod dokumentu znajduje się w przykładach dołączonych do książki, które można pobrać z serwera FTP wydawnictwa Helion pod adresem ftp://ftp.helion.pl/przyklady/ CRUSZG.zip.

gpsapp

To strona WWW prezentująca mapę.

map.html To jest plik utworzony przez nasz program.

Mapa działa.

output.json

Mapa na stronie WWW jest w stanie odczytać dane z pliku wynikowego.

Super! Teraz mogę publikować swoje wycieczki na WWW.

jesteś tutaj 

111

Błędne dane

Jednak pojawił się problem z niektórymi danymi… Twój program najwyraźniej jest w stanie odczytywać dane z odbiornika GPS i zapisywać je w formacie wymaganym przez internetową mapę. Jednak po kilku dniach pojawił się problem…

Podczas jazdy odbiornik GPS spadł mi kilka razy na ziemię, a teraz mapa nie chce wyświetlać punktów.

Co się stało? Problem polega na tym, że w pliku z odbiornika GPS pojawiły się błędne dane:

{latitude: 42.363255, longitude: -71.096710, info: ’Speed = 17’}, {latitude: 423.63182, longitude: -71.095833, info: ’Speed = 22’},

W tej liczbie kropka dziesiętna jest w niewłaściwym miejscu.

Jednak program geo2json w żaden sposób nie sprawdza odczytywanych danych, po prostu zapisuje je w innym formacie i wyświetla na wyjściu.

Wydaje się, że naprawienie tego błędu nie będzie problemem. Trzeba jedynie sprawdzać poprawność danych.

112

Rozdział 3.

Tworzenie małych programów narzędziowych

Ćwiczenie

Do programu geo2json musisz dodać nowy fragment kodu, który będzie sprawdzał poprawność zarejestrowanych szerokości i długości geograficznych. Nie musi to być nic wymyślnego. Jeśli szerokość lub długość geograficzna przekroczą zadane granice wartości oczekiwanych, powinieneś wyświetlić komunikat o błędzie i zakończyć program z kodem statusu o wartości 2:

#include int main() { float latitude; float longitude; char info[80]; int started = 0; puts(”data=[”); while (scanf(”%f,%f,%79[^\n]”, &latitude, &longitude, info) == 3) { if (started) printf(”,\n”); else started = 1;

będzie mniejsza od Jeśli wartość zmiennej latitude zasygnalizować błąd, –90 lub większa od 90, to masz 2. Tak samo należy zwracając kod statusu o wartościlongitude będzie postąpić, jeśli wartość zmiennej 180. mniejsza od –180 lub większa od

................................................................ ................................................................ ................................................................ ................................................................ ................................................................ ................................................................ ................................................................ ................................................................ ................................................................ printf(”{latitude: %f, longitude: %f, info: ’%s’}”, latitude, longitude, info); } puts(”\n]”); return 0; }

jesteś tutaj 

113

Szerokość i długość

Rozwiązanie ćwiczenia

Do programu geo2json miałeś dodać nowy fragment kodu, który będzie sprawdzał poprawność zarejestrowanych szerokości i długości geograficznych. Nie miało to być nic wymyślnego. Jeśli szerokość lub długość geograficzna przekroczą zadane granice wartości oczekiwanych, miałeś wyświetlić komunikat o błędzie i zakończyć program z kodem statusu o wartości 2:

#include int main() { float latitude; float longitude; char info[80]; int started = 0; puts(”data=[”); while (scanf(”%f,%f,%79[^\n]”, &latitude, &longitude, info) == 3) { if (started) printf(”,\n”); else started = 1; if ((latitude < –90.0) || (latitude > 90.0)) { Te wiersze spowodują zakończenie wykonywania funkcji main(), zwracając przy tym kod błędu 2.

printf(”Nieprawidłowa wartość szerokości geograficznej: %f\n”, latitude); return 2; } if ((longitude < –180.0) || (longitude > 180.0)) { printf(”Nieprawidłowa wartość długości geograficznej: %f\n”, longitude);

Te wiersze sprawdzają, czy wartości szerokości i długości geograficznej mieszczą się w dopuszczalnych granicach. Te wiersze oste wyświetlają prbłędach. komunikaty o

return 2; }

printf(”{latitude: %f, longitude: %f, info: ’%s’}”, latitude, longitude, info); } puts(”\n]”); return 0; }

114

Rozdział 3.

Tworzenie małych programów narzędziowych

Jazda próbna No dobrze, zatem kod sprawdzający poprawność szerokości i długości geograficznej jest na miejscu. Ale czy to wystarczy, by nasz program poradził sobie z nieprawidłowymi danymi? Przekonajmy się. Skompiluj kod, a następnie uruchom program, przekazując do niego błędne dane: To polecenie spowoduje skompilowanie programu. Następnie uruchomisz program, przekazując do niego błędne dane.

% &/3&% % &  (   

> gcc geo2json.c -o geo2json

Wyniki zapiszesz w pliku output.json.

> ./geo2json < gpsdata.csv > output.json >

WTF? A gdzie jest komunikat o błędzie? To znaczy „Wrażenie Totalnie Frustrujące”.

I gdzie podziały się wszystkie punkty?

Hm… Dziwne. Dodałeś przecież kod sprawdzający poprawność danych, ale wydaje się, że po uruchomieniu programu nic się nie zmieniło. Jednak teraz na mapie nie są wyświetlane żadne punkty. O co chodzi?

WYSIL

SZARE KOMÓRKI Przeanalizuj uważnie kod. Co się według Ciebie stało? Czy kod robi to, o co nam chodziło? Dlaczego nie pojawiły się żadne komunikaty o błędach? Dlaczego aplikacja z mapą uznała, że cały plik output.json jest nieprawidłowy?

jesteś tutaj 

115

Analiza kodu

Analiza kodu Internetowa mapa zgłasza problemy związane z plikiem output.json, a zatem otwórz go i sprawdź jego zawartość. To jest zawartość pliku output.json.

data=[ {latitude: 42.363400, longitude: -71.098465, info: ’Speed = 21’}, {latitude: 42.363327, longitude: -71.097588, info: ’Speed = 23’}, {latitude: 42.363255, longitude: -71.096710, info: ’Speed = 17’}, +‚ ‚66 U4|"‡|A´5q Och… także komunikat o błędzie został umieszczony w pliku wynikowym.

Kiedy otworzysz plik, wyraźnie zobaczysz, co się stało. Program odnalazł błąd w danych i natychmiast zakończył działanie. Nie przetworzył żadnych dalszych danych, a co więcej, wyświetlił komunikat o błędzie. Problem polega na tym, że ponieważ standardowy strumień wyjściowy został przekierowany do pliku output.json, w pliku tym pojawił się także komunikat o błędzie. Dlatego program nic nie wyświetlił, a my nie zobaczyliśmy żadnego komunikatu o błędzie. Oczywiście, moglibyśmy sprawdzać kod statusu po zakończeniu programu, jednak zależy nam na tym, by komunikaty o błędach były wyświetlane.

Ale w jaki sposób można wyświetlać komunikaty o błędach, jeśli standardowy strumień wyjściowy jest przekierowywany?

Dla maniaków Jeśli program napotka jakieś problemy związane z danymi, kończy się, zwracając kod statusu o wartości 2. Ale w jaki sposób możesz sprawdzić ten kod po zakończeniu programu? Cóż, to zależy od używanego systemu operacyjnego. W przypadku systemów Mac OS, Linux, niektórych systemów UNIX oraz w przypadku korzystania z Cygwin w systemie Windows ten kod statusu można wyświetlić w następujący sposób: Plik Edycja Okno Pomoc

$ echo $? 2

W przypadku uruchamiania programu w wierszu poleceń w systemie Windows wyświetlenie wartości kodu statusu wygląda nieco inaczej: Plik Edycja Okno Pomoc

C:\> echo %ERRORLEVEL% 2

Oba powyższe polecenia robią dokładnie to samo: wyświetlają liczbę zwracaną w momencie kończenia działania programu.

116

Rozdział 3.

Tworzenie małych programów narzędziowych

Czyż nie byłoby cudownie, gdyby istniał jakiś specjalny strumień wyjściowy dla błędów, tak żeby nie trzeba ich było mieszać z wynikami zapisywanymi w standardowym strumieniu wyjściowym? Wiem jednak, że to tylko fantazje…

jesteś tutaj 

117

Standardowy strumień błędów

Przedstawiamy standardowy strumień błędów Standardowy strumień wyjściowy jest domyślnym sposobem wyświetlania danych przez programy. Ale co zrobić, gdy zdarzy się coś wyjątkowego, takiego jak błąd? Można przypuszczać, że z takimi rzeczami jak komunikaty o błędach będziemy chcieli postępować nieco inaczej. I to właśnie z tego powodu wymyślono standardowy strumień błędów. Jest to drugi strumień wyjściowy tworzony specjalnie w celu wyświetlania komunikatów o błędach. Ludzie mają zazwyczaj dwoje uszu i jedne usta, jednak procesy komputerowe są poskładane nieco inaczej. Każdy proces ma jedno ucho (standardowy strumień wejściowy) oraz dwoje ust (standardowy strumień wyjściowy oraz standardowy strumień błędów).

Człowiek

To jest jedno ucho.

ucho. A to jest drugie

Jedne usta. Wiele zas tosowań.

Proces

To jest standardowy strumień wejściowy. Jedyne ucho procesu.

To jest standardowy strumień wyjściowy.

Zobaczmy, w jaki sposób system operacyjny używa tych wszystkich strumieni.

118

Rozdział 3.

ucha. Nie ma drugiego

To jest standardowy str umień błędów.

Tworzenie małych programów narzędziowych

Domyślnie strumień błędów jest wyświetlany na ekranie Czy pamiętasz, jak wspominaliśmy, że w momencie uruchamiania nowego programu system operacyjny kojarzy standardowy strumień wejściowy z klawiaturą, a standardowy strumień wyjściowy z ekranem monitora? Właśnie… Dokładnie w tym samym momencie system operacyjny tworzy także standardowy strumień błędów, który — podobnie jak standardowy strumień wyjściowy — jest domyślnie kojarzony z ekranem monitora. Standardowy strumień wejściow y jest podłączony do klawiatury.

Standardowy strumień błędów jest podłączony do ekranu.

Standardowy strumień wyjściowy jest podłączony do ekranu.

Oznacza to, że jeśli standardowy strumień wejściowy oraz standardowy strumień wyjściowy zostaną przekierowane do plików, to standardowy strumień błędów wciąż będzie wyświetlał zapisywane w nim komunikaty na ekranie monitora. Standardowy strumień wejściowy pobiera dane z pliku.

Standardowy strumień wyjściowy zapisuje dane w pliku.

Standardowy strumień błędów wyświetla komunikaty na ekranie.

I to jest naprawdę świetne rozwiązanie, gdyż oznacza, że nawet gdy standardowy strumień wyjściowy zostanie gdzieś przekierowany, to domyślnie wszystkie komunikaty zapisywane w standardowym strumieniu błędów będą widoczne na ekranie. A zatem możesz rozwiązać nasz problem niewidocznego komunikatu o błędzie w bardzo prosty sposób — wystarczy zapisać go w standardowym strumieniu błędów.

Ale jak to zrobić?

jesteś tutaj 

119

fprintf()

fprintf() zapisuje dane w strumieniu Widziałeś już, że funkcja printf() wyświetla dane, zapisując je w standardowym strumieniu wyjściowym. Nie wiedziałeś natomiast, że jest to wersja bardziej ogólnej funkcji o nazwie fprintf():

esz Kiedy wywołuj ę cj funk printf(), ona ści w rzeczywisto cję wywołuje funk fprintf().

To wywołanie przekaże dane do strumienia.

 $RQM–$ Te dwa wywołania dają identyczne wyniki.

 7#0" FV#'* A to są dane, które zostaną wysłane do strumienia.

stdout to standardowy strumień wyjściowy.

Funkcja fprintf() pozwala nam określić, gdzie mają zostać wysłane dane. Możemy jej kazać wysłać dane do stdout (standardowego strumienia wyjściowego) lub stderr (standardowego strumienia błędów).

Nie istnieją

głupie pytania

P

P: A zatem czy wywołanie!

7 :::'

O: Owszem, jest. I jak się zapewne domyślasz, jest to

O: Tak. Oba wywołania dają identyczne rezultaty.

: Dostępne są strumienie stdout oraz stderr. Czy jest zatem także strumień stdin?

standardowy strumień wejściowy.

P: Czy mogę zapisywać w nim dane? O: Nie. W standardowym strumieniu wejściowym nie można niczego zapisywać.

P: A czy mogę z niego czytać? O: Tak, korzystając z funkcji fscanf(), która jest bardzo podobna do funkcji scanf(), przy czym wymaga określenia używanego strumienia.

robi dokładnie to samo co !

:::'?

W rzeczywistości, za kulisami, funkcja scanf(...) po prostu wywołuje funkcję fscanf(stdin,...).

P: Czy mogę przekierować standardowy strumień błędów?

O: Tak; operator > przekierowuje standardowy strumień wyjściowy, natomiast operator 2> przekierowuje standardowy strumień błędów.

P: A zatem mogę użyć polecenia geo2json 2> errors.txt?

O: Tak. 120

Rozdział 3.

Tworzenie małych programów narzędziowych

Zaktualizujmy kod, by korzystał z funkcji fprintf() Wprowadzając dosłownie kilka niewielkich zmian, możesz sprawić, że komunikaty o błędach będą wyświetlane w standardowym strumieniu błędów.

#include int main() { float latitude; float longitude; char info[80]; int started = 0; puts(”data=[”); while (scanf(”%f,%f,%79[^\n]”, &latitude, &longitude, info) == 3) { if (started) printf(”,\n”); else started = 1; if ((latitude < -90.0) || (latitude > 90.0)) {  $+‚ ‚66 @D$  7#` 7Y  GH G!CC !  ° # 7'* return 2; } if ((longitude < -180.0) || (longitude > 180.0)) {

Zamiast funkcji printf() wywołujemy funkcję fprintf().

 $+‚+‚66 @D$  7#` 7Y  GH7YCG!CC !  ° # C7'* return 2; }

Jako pierwszy parametr wywołania funkcji musimy podać stderr.

printf(”{latitude: %f, longitude: %f, info: ’%s’}”, latitude, longitude, info); } puts(”\n]”); return 0; }

Oznacza to, że kod powinien działać w dokładnie taki sam sposób jak wcześniej, z tym wyjątkiem, że komunikaty o błędach powinny się pojawiać w standardowym strumieniu błędów, a nie standardowym strumieniu wyjściowym.

Uruchommy program i przekonajmy się.

jesteś tutaj 

121

Jazda próbna

Jazda próbna Jeśli ponownie skompilujesz program i wykonasz go, przekazując przy tym do niego nieprawidłowe dane, to uzyskasz następujące efekty:

% &/3&%> ?#!

> gcc geo2json.c -o geo2json > ./geo2json < gpsdata.csv > output.json ` 7Y  GH G!CC !  P%O:SO&].

Q

Funkcja scanf() wczytuje dane ze standardowego strumienia wejściowego.

Q

Domyślnie standardowy strumień wejściowy odczytuje dane z klawiatury.

Q

Istnieje możliwość przekierowania standardowego strumienia wejściowego w taki sposób, by odczytywał dane zapisane w pliku. Służy do tego operator < używany w wierszu poleceń.

Q

Standardowy strumień błędów jest zarezerwowany do przekazywania informacji o błędach.

Q

Standardowy strumień błędów można przekierować, używając operatora 2>.

Tworzenie małych programów narzędziowych

¥FLĂOHWDMQH Mamy powody, by sądzić, że poniższy program został użyty podczas przekazywania tajnych wiadomości: #include

i % 2 oznacza „reszta z dzielenia wartości zmiennej i przez 2”.

int main() { char word[10]; int i = 0; while (scanf(”%9s”, word) == 1) { i = i + 1; if (i % 2) fprintf(stdout, ”%s\n”, word); else fprintf(stderr, ”%s\n”, word); } return 0; }

Przechwyciliśmy plik o nazwie secret.txt oraz skrawek papieru z instrukcjami:

Uruchamiać poleceniem: secret_messages < secret.txt > message1.txt 2> message2.txt

^¹º»Hª??Yº YºŠ V*¼½¾ ®ª­*®‰Š‰¼H V©¿©YžªF¼RH¿ º*©¼ ©ÀF¼‰¬R¼HŠ ©¼•*Y­¼¬

secret.txt

Operator > przekierowuje standardowy strumień wyjściowy.

Operator 2> przekierowuje ów. standardowy strumień błęd

Twoim zadaniem jest odszyfrowanie dwóch tajnych wiadomości. Zapisz je poniżej.

   

   !

jesteś tutaj  123

Ściśle tajne rozwiązanie

¥FLĂOHWDMQHļUR]ZLÈ]DQLH Mamy powody, by sądzić, że poniższy program został użyty podczas przekazywania tajnych wiadomości: #include int main() { char word[10]; int i = 0; while (scanf(”%9s”, word) == 1) { i = i + 1; if (i % 2) fprintf(stdout, ”%s\n”, word); else fprintf(stderr, ”%s\n”, word); } return 0; }

Przechwyciliśmy plik o nazwie secret.txt oraz skrawek papieru z instrukcjami: ^¹º»Hª??Yº YºŠ V*¼½¾ ®ª­*®‰Š‰¼H V©¿©YžªF¼RH¿ º*©¼ ©ÀF¼‰¬R¼HŠ ©¼•*Y­¼¬

Uruchamiać poleceniem: secret_messages < secret.txt > message1.txt 2> message2.txt

secret.txt

Twoim zadaniem było odszyfrowanie dwóch tajnych wiadomości. Zapisz je poniżej.

    ŁÓDŹ

KUP

PODWODNA

SZEŚĆ

WYNURZY

JAJEK

SIĘ

I

O

BUTELKĘ

DZIEWIĄTEJ

MLEKA

WIECZOREM

124

   !

Rozdział 3.

Tworzenie małych programów narzędziowych

System operacyjny bez tajemnic Tematem dzisiejszego wywiadu jest: Czy System Operacyjny ma znaczenie?  Systemie operacyjny, jest nam naprawdę bardzo miło, że znalazłeś dziś dla nas trochę czasu.

 A, rozumiem. A ty masz dużo do czynienia z programami narzędziowymi?

 Podział czasu — w tym jestem mistrzem.

 Czy nie taki właśnie jest mój los? To zależy od systemu operacyjnego. Systemy uniksowe wykonują zadania, korzystając z wielu programów narzędziowych. W systemie Windows są one używane rzadziej, jednak wciąż mają duże znaczenie.

 Zgodziłeś się wystąpić, pod warunkiem że pozostaniesz anonimowy; czy to prawda?  Wy nie pytacie, ja nie odpowiadam. Nazywajcie mnie po prostu SO.  Czy to ma znaczenie, jakim SO jesteś?  Wiele osób przykłada bardzo dużą wagę do tego, jakiego systemu operacyjnego używa. Patrząc jednak z punktu widzenia nas, czyli prostych programów pisanych w języku C, wszystkie zachowujemy się i działamy bardzo podobnie.

 Tworzenie niewielkich programów narzędziowych, które potrafią ze sobą współpracować, jest niemal filozofią, prawda?  O tak, to droga życia. Czasami, kiedy postawią przed tobą wielki problem do rozwiązania, łatwiej jest podzielić go na wiele prostszych zadań.

 Czy to dzięki standardowej bibliotece języka C?

 A potem napisać odpowiedni program narzędziowy do rozwiązania każdego z nich?

 Ech… Jeśli piszecie w C, to podstawowe możliwości i sposoby wszędzie są takie same. Ja zawsze powtarzam: kiedy wyłączą światło, wszystkie jesteśmy takie same. Wiecie, o co mi chodzi?

 Właśnie tak. A potem skorzystać z systemu operacyjnego — czyli mnie — by połączyć je ze sobą.

 Och, oczywiście. Powiedz nam teraz, czy odpowiadasz za wczytywanie programów do pamięci?  Zgadza się, to ja robię z nich procesy.  Czy to ważne zadanie?  Lubię tak o tym myśleć. W końcu wiecie… nie wystarczy wrzucić programu do pamięci i pozostawić go na pastwę procesora. Trzeba go jeszcze odpowiednio przygotować. Dlatego oprócz tego muszę jeszcze przydzielić pamięć dla programu i podłączyć program do standardowych strumieni danych, aby mogły robić różne ważne rzeczy, takie jak wyświetlanie na ekranie i wczytywanie z klawiatury.  Czyli tak jak zrobiłeś w przypadku programiku geo2json?  Ten gość to prawdziwy program narzędziowy.  Och, przepraszam.  Spoko. Chodzi mi o to, że on faktycznie jest jak program narzędziowy: prosty program operujący na danych tekstowych.

 Czy takie rozwiązanie ma jakieś zalety?  Jedną z jego ogromnych zalet jest prostota. Jeśli dysponujecie zbiorem niewielkich programów, możecie je łatwiej przetestować. A poza tym, kiedy już napiszecie program narzędziowy, będziecie mogli go użyć także w innych projektach.  A co z wadami?  No cóż… programy narzędziowe nie wyglądają tak fajnie. Zazwyczaj są używane z poziomu wiersza poleceń, zatem nie są, jakby to ująć, atrakcyjne wizualnie.  Czy to ma jakieś znaczenie?  Nie tak duże, jak można by przypuszczać. Jeśli będziecie dysponować grupą dobrych narzędzi do rozwiązywania jakiegoś problemu, to zawsze będziecie mogli podłączyć je czy to do jakiegoś atrakcyjnego interfejsu, jakiejś aplikacji na komputery biurkowe, czy też aplikacji internetowej. A teraz przepraszam, ale muszę was wywłaszczyć.  Oczywiście, SO, było nam naprawdę bardzo przyj… zzzzzzzz...

jesteś tutaj  125

Narzędzia wielokrotnego użytku

Niewielkie programy narzędziowe są elastyczne Jedną ze wspaniałych cech niewielkich programów narzędziowych jest ich elastyczność. Jeśli napiszemy program, który robi jedną rzecz, lecz robi to naprawdę dobrze, to istnieje bardzo duże prawdopodobieństwo, że będziemy w stanie używać go w bardzo wielu kontekstach. Jeśli napiszemy program, który potrafi wyszukiwać teksty wewnątrz pliku, to najprawdopodobniej okaże się on przydatny przy wielu okazjach. Zastanówmy się na przykład nad naszym programem geo2json. Napisałeś go, by wyświetlać na mapie dane rejestrowane podczas wycieczek rowerowych, prawda? Ale czy oznacza to, że nie możemy używać go w jakichś innych celach… takich jak badania… chociażby…

To jest szerokość geograficzna 34°.

To jest szerokość geograficzna 26°.

. To jest długość geograficzna -76°

. To jest długość geograficzna -64°

Aby się przekonać, jak elastyczny jest nasz program narzędziowy, zastosujemy go do rozwiązania zupełnie innego problemu. Zamiast do zwyczajnego wyświetlania danych na internetowej mapie wykorzystamy go do rozwiązania nieco bardziej złożonego problemu. Załóżmy, że — podobnie jak wcześniej — wczytujemy cały zestaw danych z odbiornika GPS, jednak zamiast wyświetlać je wszystkie, chcemy wyświetlić tylko te dane, które zawierają się w prostokącie bermudzkim. Oznacza to, że wyświetlane mają być wyłącznie dane spełniające następujące warunki: ((latitude > 26) && (latitude < 34)) ((longitude > -76) && (longitude < -64))

Od czego masz zatem zacząć?

126

Rozdział 3.

Tworzenie małych programów narzędziowych

Nie zmieniaj programu geo2json Nasz program geo2json wyświetla wszystkie przekazane do niego dane. Co zatem powinniśmy zrobić? Czy mamy go zmodyfikować, tak by nie tylko eksportował dane, lecz także je sprawdzał? Oczywiście moglibyśmy tak zrobić, trzeba jednak pamiętać, że małe programy narzędziowe:

robią jedną rzecz i robią ją bardzo dobrze A zatem tak naprawdę nie mamy zamiaru modyfikować programu geo2json, gdyż chcemy, by realizował on tylko jedno zadanie. Jeśli program będzie robić coś bardziej złożonego, przysporzy to problemów użytkownikom oczekującym, że będzie on działał w dokładnie taki sam sposób jak wcześniej.

Ja naprawdę nie chcę filtrować danych. Chciałabym wyświetlać je wszystkie.

A zatem, skoro nie chcemy zmieniać programu geo2json, to co możemy zrobić?

0 $       / $  -  4 Niewielkie programy narzędziowe, takie jak geo2json, są tworzone zgodnie z poniższymi zasadami:

* Mogą wczytywać dane ze standardowego strumienia wejściowego. * Mogą wyświetlać dane, korzystając ze standardowego strumienia wyjściowego. * Operują raczej na danych tekstowych, a nie na nieczytelnych formatach binarnych. * Każdy z nich wykonuje jedno proste zadanie.

jesteś tutaj  127

Dwa programy narzędziowe

Różne zadania wymagają różnych narzędzi Jeśli chcemy pominąć dane, które nie leżą wewnątrz prostokąta bermudzkiego, musimy stworzyć odrębne narzędzie, które będzie właśnie do tego służyło. A zatem będziemy mieli dwa programy narzędziowe: nowy program — bermuda — będzie filtrował dane, pozostawiając tylko te z nich, które leżą wewnątrz prostokąta bermudzkiego, a następnie nasz stary program geo2json będzie konwertował je na format wymagany przez internetową mapę. Oto, w jaki sposób połączymy oba te programy: Do programu bermuda będą przekazywane wszystkie dane. Te dane zawierają zdarzenia, które odbyły się zarówno wewnątr z prostokąta bermudzkiego, jak i poza nim.

bermuda

Ten program narzędziowy będzie przekazywał wyłącznie dane leżące wewnątrz prostokąta bermudzkiego.

A zatem do programu geo2json trafią wyłącznie dane leżące w prostokącie bermudzkim.

Program geo2json będzie działał dokładnie tak samo jak wcześniej.

geo2json

Rozdzielając problem na dwa zadania, możemy uniknąć konieczności modyfikowania programu geo2json. Oznacza to, że jego aktualni użytkownicy wciąż będą mogli z niego korzystać. Pozostaje jednak pytanie:

Jak połączyć ze sobą oba programy?

128

Rozdział 3.

Wygenerujesz mapę zawierającą wyłącznie dane leżące w prostokącie bermudzkim.

Tworzenie małych programów narzędziowych

Połącz wejście i wyjście przy użyciu potoku Dowiedziałeś się już, jak można skorzystać z przekierowań, by połączyć standardowy strumień wejściowy oraz standardowy strumień wyjściowy z plikiem. Jednak teraz chodzi nam o połączenie standardowego strumienia wyjściowego programu bermuda ze standardowym strumieniem wejściowym programu geo2json, mniej więcej w następujący sposób:

Symbol | jest potokiem łączącym standardowy strumień wyjściowy jednego procesu ze standardowym strumieniem wejściowym innego procesu.

bermuda

Dane pojawiające się na wyjściu programu bermuda… Potok może posłużyć do połączenia standardowego strumienia wyjściowego jednego procesu ze standardowym strumieniem wejściowym innego procesu.

To jest potok.

W ten sposób zawsze gdy program bermuda odnajdzie dane leżące w obszarze prostokąta bermudzkiego, zapisze je w standardowym strumieniu wyjściowym. Potok przekaże te dane ze standardowego strumienia wyjściowego programu bermuda do standardowego strumienia wejściowego programu geo2json.

geo2json. jście programu …trafiają na we

geo2json

Nad samym sposobem działania potoku czuwa system operacyjny. Wszystko, co musimy zrobić, by całość zadziałała, to wydanie odpowiedniego polecenia, takiego jak to przedstawione poniżej: To jest potok. System operacyjny wykona oba programy w tym samym momencie.

bermuda | geo2json Dane generowane na wyjściu prog ramu bermuda stają się danymi wejściowymi programu geo2 json.

A zatem nadszedł czas, byś napisał program narzędziowy bermuda.

jesteś tutaj  129

Notatki narzędziowe

Program narzędziowy bermuda Program narzędziowy bermuda będzie działał w bardzo podobny sposób jak program geo2json: będzie odczytywał wiersz po wierszu przekazywane dane GPS i przesyłał je do standardowego strumienia wyjściowego. Pojawią się jednak dwie ważne różnice. Przede wszystkim nie wszystkie dane będą przekazywane do standardowego strumienia wyjściowego — trafią tam jedynie dane leżące wewnątrz prostokąta bermudzkiego. Druga różnica polega na tym, że program bermuda zawsze będzie wyświetlał dane w tym samym formacie, którego używa odbiornik GPS, czyli w formacie CSV. Oto, jak wygląda pseudokod tego nowego programu:

Wczytaj

szerokoś ć i długo ść geogr oraz wsz aficzną elkie inn e dane z wiersza pliku: jeśli sze rokość z awiera s od 26 do ię w prz 34, to: edziale jeśli dłu gość geo graficzna w przedz mieści s iale od – ię 64 do –7 6, to: wyświet l szeroko ść i dług geografic ość zną oraz inne dan e

Przełóżmy ten pseudokod na C.

130

Rozdział 3.

Tworzenie małych programów narzędziowych

 /  "  Twoim zadaniem jest uzupełnienie kodu programu bermuda. Wyciągnij fragmenty kodu z basenu i umieść je w pustych miejscach kodu poniżej. Nie będziesz musiał użyć wszystkich fragmentów kodu z basenu.

#include

int main() { float latitude; float longitude; char info[80]; while (scanf(”%f,%f,%79[^\n]”, ........., ........., .........) == 3) if ((......... > .........) ......... (......... < .........)) if ((......... > .........) ......... (......... < .........)) printf(”%f,%f,%s\n”, ........., ........., .........); return 0; }

Uwaga: każdy fragment kodu z basenu może zostać użyty tylko raz!

&longitude

&&

-76

info info latitude latitude

&latitude

longitude 26 -64

&info

longitude || yeti

longitude

34

&&

latitude

||

jesteś tutaj 

131

Kod z basenu

 /  "    Twoim zadaniem było uzupełnienie kodu programu bermuda. Miałeś wyciągnąć fragmenty kodu z basenu i umieścić je w pustych miejscach kodu poniżej.

#include

int main() { float latitude; float longitude; char info[80]; &longitude .........) &latitude ..........., info while (scanf(”%f,%f,%79[^\n]”, ........., == 3) latitude > .........) 26 latitude < .........)) && 34 if ((......... ......... (......... -64 && longitude > .........) -76 longitude < .........)) if ((......... ......... (......... latitude longitude .........); info printf(”%f,%f,%s\n”, ........., ........., return 0; }

Uwaga: każdy fragment kodu z basenu może zostać użyty tylko raz!

&info || yeti ||

132

Rozdział 3.

Tworzenie małych programów narzędziowych

Jazda próbna Skoro już napisałeś program bermuda, nadszedł czas, by wypróbować go wraz z programem geo2json i sprawdzić, czy pojawiły się jakieś doniesienia o tajemniczych zdarzeniach występujących w obszarze prostokąta bermudzkiego. Po skompilowaniu obu programów wywołaj okno konsoli, a następnie uruchom oba programy, używając poniższego polecenia: Pamiętaj: jeśli korzystasz z systemu Windows, to nie musisz używać sekwencji znaków „./”.

To jest potok łączący oba procesy.

$"  % Plik spooky.csv znajduje się w przykładach dołączonych do książki, które można pobrać z serwera FTP wydawnictwa Helion pod adresem ftp://ftp.helion.pl/przyklady/CRUSZG.zip. To jest plik zawierający wszystkie zdarzenia.

Kiedy łączymy ze sobą "&QÁ"&4 ’ M"6 ƒ‘"  dwa programy, możemy je potraktować jako jeden program. Program bermuda filtruje zdarzeni Program geo2json przekonwertuje a, które chcemy zignorować. dane o zdarzeniach na format JSON.

Wygenerowane wyniki zapiszemy w tym pliku.

Łącząc ze sobą dwa odrębne programy przy użyciu potoku, można je potraktować, jak gdyby były jednym programem, i przekierowywać ich standardowy strumień wejściowy oraz standardowy strumień wyjściowy tak samo jak wcześniej. % &/3&%;,>

‘ "&QÁ"&4 ’ M"6 ƒ‘" 

  

jesteś tutaj  133

Nie ma głupich pytań Nie istnieją

głupie pytania

P: Dlaczego ważne jest, by

P: Jeśli zatem dwa programy są

O: Gdyż dzięki temu łatwiej jest łączyć

: Nie. Oba programy będą działały w tym samym czasie — kiedy pierwszy będzie generował dane, drugi może je odczytywać i ich używać.

niewielkie programy narzędziowe korzystały ze standardowego strumienia wejściowego oraz standardowego strumienia wyjściowego?

programy ze sobą przy użyciu potoków.

P: Dlaczego to ma znaczenie? O: Niewielkie programy narzędziowe

zazwyczaj nie potrafią samodzielnie rozwiązać całego problemu, a jedynie niewielkie problemy techniczne, takie jak konwersja danych z jednego formatu na drugi. Jednak łącząc je ze sobą, można rozwiązywać za ich pomocą duże problemy.

P: Czym tak naprawdę jest potok? O: Szczegóły zależą od systemu operacyjnego. Potoki mogą być fragmentami pamięci bądź plikami tymczasowymi. Najważniejsze jednak jest to, że z jednej strony przyjmują dane, a z drugiej strony, w odpowiedniej kolejności, wysyłają te same dane.

połączone potokiem, to czy pierwszy z nich musi zakończyć działanie, zanim drugi będzie mógł zostać uruchomiony?

O

P: Dlaczego małe programy

narzędziowe korzystają z danych tekstowych?

O: Gdyż jest to najbardziej otwarty format.

Jeśli mały program narzędziowy będzie korzystał z danych tekstowych, oznacza to, że dowolny inny programista może odczytać i przeanalizować generowane przez niego wyniki — wystarczy w tym celu otworzyć plik w dowolnym edytorze tekstów. Formaty binarne są zazwyczaj bardziej zagmatwane i trudne do zrozumienia.

P: Jeśli kilka procesów będzie

ze sobą połączonych przy użyciu potoków, a następnie użyję operatorów > oraz przechwyci zawartość standardowego strumienia wyjściowego ostatniego procesu tworzącego potok.

P: Czy podczas uruchamiania

programów bermuda i geo2json te nawiasy są naprawdę konieczne?

O: Tak. To właśnie dzięki nim zawartość pliku trafi do standardowego strumienia wejściowego programu bermuda.

P: Czy przy użyciu potoków można połączyć ze sobą kilka programów?

O: Tak, wystarczy pomiędzy każdą

ich parą umieścić znaki |. Seria takich połączonych ze sobą programów nosi nazwę potoku.

CELNE SPOSTRZEŻENIA Q

Q

134

Rozdział 3.

Jeśli chcesz wykonywać inne zadania, to rozważ napisanie innego małego programu narzędziowego. Projektuj programy narzędziowe korzystające ze standardowego strumienia wejściowego oraz standardowego strumienia wyjściowego.

Q

Małe programy narzędziowe zazwyczaj odczytują i zapisują dane tekstowe.

Q

Przy użyciu potoku można połączyć standardowy strumień wyjściowy jednego procesu ze standardowym strumieniem wejściowym innego procesu.

Tworzenie małych programów narzędziowych

A jeśli chcemy przekazywać wyniki do więcej niż jednego pliku? Dowiedzieliśmy się już, jak można odczytywać dane z jednego pliku i zapisywać je w innym, korzystając przy tym z przekierowań. Jak jednak postąpić, gdy program ma zrobić coś bardziej złożonego, na przykład wysyłać dane do więcej niż jednego pliku? Wyobraźmy sobie, że musimy stworzyć kolejny program narzędziowy, który będzie odczytywał dane z pliku, a następnie rozdzielał je i zapisywał w kilku innych plikach.

ufos.csv

 4%  4%

categorize spooky.csv

Na czym zatem polega nasz problem? Nie możemy zapisywać w plikach, prawda? Problem polega na tym, że korzystając z przekierowań, możemy zapisywać dane co najwyżej w dwóch plikach — w jednym, korzystając ze standardowego strumienia wyjściowego, oraz w drugim, korzystając ze standardowego strumienia błędów. Co zatem możemy zrobić?

disappearances.csv

other.csv

jesteś tutaj  135

Strumienie danych wedle potrzeb

Stwórz swoje własne strumienie danych Kiedy program jest uruchamiany, system operacyjny tworzy dla niego trzy plikowe strumienie danych: standardowy strumień wejściowy, standardowy strumień wyjściowy oraz standardowy strumień błędów. Jednak czasami pojawia się konieczność tworzenia innych strumieni danych w trakcie działania programu. Na szczęście system operacyjny nie ogranicza nas wyłącznie do tych strumieni, które otrzymaliśmy podczas uruchamiania programu. Możemy tworzyć własne już w czasie działania programu. Każdy strumień danych jest reprezentowany przez wskaźnik do pliku, a można je tworzyć przy użyciu funkcji  ': To wywołanie utworzy strumień danych pozwalający czytać z pliku. To wywołanie utworzy strumień danych pozwalający zapisywać w pliku.

To jest nazwa pliku.

To jest tryb „r” oznaczający „odczytywanie” (ang. read).

³‹0,‡ =5 # :T###'* To jest nazwa pliku.

³‹0,‡=5 #:T###'*

Funkcja fopen() pobiera dwa parametry: nazwę pliku oraz tryb. Trybem może być w, jeśli chcemy zapisywać w pliku, r, jeśli chcemy z niego odczytywać, oraz a, jeśli mamy zamiar dodawać dane na końcu pliku. Po utworzeniu strumienia danych można zapisywać w nim dane przy użyciu funkcji  ', zupełnie tak samo jak wcześniej. A co w przypadku, gdy trzeba odczytać jakieś dane ze strumienia? Wówczas można skorzystać z funkcji !

':

To jest tryb „w” oznaczający „zapisywanie” (ang. write).

Dostępne tryby to: „w” = zapisywanie, „r” = odczytywanie, „a” = dopisywanie.

 =#`  :##! C## F #'* !

 =#J\?° @° #  !'* Kiedy już skończymy używać strumienia danych, należy go zamknąć. Co prawda wszystkie strumienie danych są automatycznie zamykane podczas kończenia działania programu, niemniej samodzielne zamykanie strumieni zawsze jest dobrym pomysłem:

! ='* !='* A zatem spróbujmy użyć strumieni.

136

Rozdział 3.

Tworzenie małych programów narzędziowych

Zaostrz ołówek To jest kod programu, którego zadaniem jest odczytanie wszystkich danych z pliku wygenerowanego przez odbiornik GPS i zapisanie ich do jednego z trzech innych plików. Sprawdź, czy będziesz w stanie uzupełnić brakujące miejsca. #include #include #include int main() { char line[80]; µ©R¼'~ $ M"6 ƒ$"""""""""" µ©R¼'A~ $ "6 ƒ$"""""""""" µ©R¼'4~ $ 6 "6 ƒ$"""""""""" µ©R¼'|~ $7"6 ƒ$"""""""""" while (..........(in, ”%79[^\n]\n”, line) == 1) {    $ªµY$ ..........(file1, ”%s\n”, line);     $*6$ ..........(file2, ”%s\n”, line); else ..........(file3, ”%s\n”, line); } ..........(file1); ..........(file2); ..........(file3); return 0; }

Nie istnieją

głupie pytania

P: Ilu strumieni danych mogę używać? O: To zależy od systemu operacyjnego, jednak

zazwyczaj można używać do 256 strumieni. Kluczowe znaczenie ma tu ograniczona liczba dostępnych strumieni, zatem warto dbać o to, by zawsze zamykać strumienie, kiedy nie są już potrzebne.

P

: Dlaczego typ FILE jest zapisywany wielkimi literami?

O: Ze względów historycznych. Kiedyś był on

definiowany jako makro, a nazwy makr są zazwyczaj zapisywane wielkimi literami. O makrach dowiesz się więcej w dalszej części książki.

jesteś tutaj  137

Zapis i odczyt

Zaostrz ołówek Rozwiązanie

To jest kod programu, którego zadaniem jest odczytanie wszystkich danych z pliku wygenerowanego przez odbiornik GPS i zapisanie ich do jednego z trzech innych plików. Miałeś sprawdzić, czy będziesz w stanie uzupełnić brakujące miejsca.

#include #include #include int main() { char line[80]; "r" µ©R¼'~ $ M"6 ƒ$"""""""""" "w" µ©R¼'A~ $ "6 ƒ$"""""""""" "w" µ©R¼'4~ $ 6 "6 ƒ$"""""""""" "w" µ©R¼'|~ $7"6 ƒ$"""""""""" fscanf while (..........(in, ”%79[^\n]\n”, line) == 1) {    $ªµY$ fprintf ..........(file1, ”%s\n”, line);     $*6$ fprintf ..........(file2, ”%s\n”, line); else fprintf ..........(file3, ”%s\n”, line); } fclose ..........(file1); fclose ..........(file2); fclose ..........(file3); return 0; }

Program działa, ale…

ufos.csv

Jeśli skompilujesz i uruchomisz ten program, używając następującego polecenia:

C!!! C:!¶! CWW:E! C disappearances.csv

to program wczyta wiersz po wierszu dane zapisane w pliku spooky.csv i rozdzieli je, zapisując w trzech innych plikach: ufos.csv, disappearances.csv oraz other.csv. To super; co jednak zrobić, w przypadku gdy użytkownik będzie chciał rozdzielić dane w inny sposób? Jeśli na przykład będzie chciał wyszukiwać inne słowa lub zapisywać dane w plikach o innych nazwach? Czy będzie w stanie to zrobić bez ponownej kompilacji programu?

138

Rozdział 3.

other.csv

Tworzenie małych programów narzędziowych

Nieco więcej o funkcji main() Chodzi o to, że każdy program, który będziesz pisał, powinien dawać użytkownikowi możliwość modyfikowania sposobu jego działania. Jeśli jest to program dysponujący graficznym interfejsem użytkownika, to zapewne powinieneś go wyposażyć w okno ustawień. Jeśli natomiast jest to program wykonywany z poziomu wiersza poleceń, taki jak nasz program 6 , to powinien on zapewniać możliwość przekazywania argumentów wiersza poleceń. To jest pierwsze słowo, którego chcemy poszukiwać.

Wszystkie dane o syrenach będą zapisywane w tym pliku.

To oznacza, że chcemy poszukiwać Elvisa.

:E! CF   7:!1,11:!1+=:!1

Wszystkie inne dane trafią do tego pliku.

Wszystkie przypadki zauważenia . Elvisa będą trafiały do tego pliku

Ale w jaki sposób można odczytywać argumenty wiersza poleceń wewnątrz programu? Na razie za każdym razem, gdy pisaliśmy funkcję  ', była ona definiowana bez żadnych argumentów. Okazuje się jednak, że w rzeczywistości istnieją dwie postacie tej funkcji. Oto ta druga z nich:      C!!+ ‡ C1?@' { """"MM6"""" }

Funkcja main() może odczytywać argumenty wiersza poleceń jako tablicę łańcuchów znaków. Oczywiście język C nie udostępnia wbudowanego typu reprezentującego łańcuchy znaków, zatem w rzeczywistości jest to tablica wskaźników typu char. Oto przykład:

·:E! C··F ··  7:!1··,1··1:!1··+=:!1· To jest argv[0].

To jest argv[1].

To jest argv[2]. To jest argv[3]. To jest argv[4].

To jest argv[5].

Pierwszym argumentem jest . nazwa uruchamianego programu

Podobnie jak we wszystkich tablicach w języku C, także i w tym przypadku konieczna jest znajomość jej długości. To właśnie dlatego funkcja main() ma dwa argumenty. Pierwszy z nich, argc, jest liczbą określającą liczbę elementów w tablicy. Argumenty wiersza poleceń zapewniają naszym programom ogromną elastyczność i warto pomyśleć o tym, w jaki sposób użytkownicy mogliby chcieć zmodyfikować działanie naszego programu. Dzięki temu program może się dla nich stać znacznie bardziej użyteczny i cenny.

'"  %

Pierwszy argument zawiera nazwę programu uruchomionego przez użytkownika.

To oznacza, że pierwszym właściwym argumentem jest ƒ9A:.

No dobrze, zobaczmy, jak można przydać nieco elastyczności naszemu programowi categorize.

jesteś tutaj  139

Magnesiki z kodem

Magnesiki z kodem To jest zmodyfikowana wersja programu categorize, który pobiera              -( )   ,  ,      ! #       ! $  $( )   $   %   ,     1

:E! CF   7:!1,11:!1+=:!1 #include #include #include  667'ƒ9: { char line[80];  """""""""""–~"""""""""""  $¬  q/D$ return 1; } µ©R¼'~ $ M"6 ƒ$$$ FILE *file1 = fopen( ........... , ”w”); FILE *file2 = fopen( ........... , ”w”); FILE *file3 = fopen( ........... , ”w”);

140

Rozdział 3.

Tworzenie małych programów narzędziowych

while (fscanf(in, ”%79[^\n]\n”, line) == 1) { if (strstr(line, ...........)) fprintf(file1, ”%s\n”, line); else if (strstr(line, ...........)) fprintf(file2, ”%s\n”, line); else fprintf(file3, ”%s\n”, line); } fclose(file1); fclose(file2); fclose(file3); return 0; }

6

5 argv[5] argv[2]

argc

argv[4]

argv[1] argv[3]

jesteś tutaj 

141

Magnesiki z kodem. Rozwiązanie

Magnesiki z kodem. Rozwiązanie To jest zmodyfikowana wersja programu categorize, który pobiera poszukiwane            -( .  !  ,  #    !     ! #       ! $  $( )   $   %   ,     1

:E! CF   7:!1,11:!1+=:!1 #include #include #include  667'ƒ9: { char line[80];

6 argc  """""""""""–~"""""""""""  $¬  q/D$ return 1; } µ©R¼'~ $ M"6 ƒ$$$

argv[2] FILE *file1 = fopen( ........... , ”w”); argv[4] FILE *file2 = fopen( ........... , ”w”);

argv[5] FILE *file3 = fopen( ........... , ”w”);

142

Rozdział 3.

Tworzenie małych programów narzędziowych

while (fscanf(in, ”%79[^\n]\n”, line) == 1) { argv[1] if (strstr(line, ..............)) fprintf(file1, ”%s\n”, line); argv[3] else if (strstr(line, .............)) fprintf(file2, ”%s\n”, line); else fprintf(file3, ”%s\n”, line); } fclose(file1); fclose(file2); fclose(file3); return 0; }

5

jesteś tutaj  143

Jazda próbna

Jazda próbna W porządku, wypróbujmy nową wersję naszego kodu. Będziesz do tego potrzebował testowego pliku z danymi, noszącego nazwę spooky.csv.

|5"‡´qA‡|¤‡´"A|“45“FM~® 4´"|5U|´5¤“U"q“qABqFM~ªµY 4B"A|4B“A¤“A"A|‡U“qFM~V 4´"|U|5‡q¤‡4"“q|B5‡FM~¼ƒ 4“"´‡´4A“¤‡´"55q|“AFM~R |5"UB‡5A“¤“|"|||“U5FM~*6 4‡"44UUU“¤“A"U““5qAFM~ªµY 4B"U5A|45¤‡‡"54“´|4FM~V |“"´“Bq|‡¤‡B"U““q|BFM~¼ƒ 44"“5q4q‡¤‡´"AB4A|BFM~¼ƒ 4“"A‡‡‡Bq¤´“"U´UA|AFM~¼ƒ

spooky.csv

Teraz wykonaj program 6 , przekazując do niego kilka argumentów wiersza poleceń określających poszukiwane teksty oraz nazwy plików:

% &/3&%B !&!?  

;! CŒ³…  :!1,11:!1+=:!1

W efekcie wykonania programu zostaną utworzone następujące pliki:

144

Rozdział 3.

Tworzenie małych programów narzędziowych

4´"|5U|´5¤“U"q“qABqFM~ªµY

Jeśli przetworzysz plik elvises.csv przy użyciu programu geo2json, to będziesz mógł wyświetlić dane na mapie.

4‡"44UUU“¤“A"U““5qAFM~ªµY

aliens.csv |5"‡´qA‡|¤‡´"A|“45“FM~® 4B"A|4B“A¤“A"A|‡U“qFM~V 4“"´‡´4A“¤‡´"55q|“AFM~R |5"UB‡5A“¤“|"|||“U5FM~*6 4B"U5A|45¤‡‡"54“´|4FM~V

the_rest.csv 4´"|U|5‡q¤‡4"“q|B5‡FM~¼ƒ |“"´“Bq|‡¤‡B"U““q|BFM~¼ƒ 44"“5q4q‡¤‡´"AB4A|BFM~¼ƒ 4“"A‡‡‡Bq¤´“"U´UA|AFM~¼ƒ Elvis opuścił budynek.

elvises.csv

:  # "  3 Choć w laboratoriach Rusz Głową nigdy nie popełniamy błędów (hm...), to jednak w programach używanych w realnych zastosowaniach należy sprawdzać, czy nie pojawią się jakieś problemy podczas otwierania plików do zapisu i odczytu. Na szczęście, jeśli podczas otwierania strumienia danych wystąpi jakiś problem, funkcja fopen() zwróci wartość 0. Oznacza to, że chcąc sprawdzić wystąpienie błędu, powinieneś zmienić poniższy wiersz kodu: FILE *in = fopen(”nie_istnieje.txt”, ”r”);

na następujący fragment: FILE *in;  – ~ $x "8$$$  $2 M"D$ return 1; }

jesteś tutaj  145

Opcje wiersza poleceń

Zasłyszane w Pizzerii Rusz Głową Z anchois i ananasem, na grubym cieście. Zróbcie ją migiem — potrzebuję jej do natychmiastowej dostawy.

Całkiem możliwe, że każdy program, który napiszesz, będzie potrzebował opcji. Jeśli będzie to program do internetowych pogawędek, to będzie potrzebował ustawień użytkownika. Jeśli będzie to gra, to użytkownik może chcieć zmienić kształt krwawych rozprysków. Jeśli natomiast będzie to narzędzie uruchamiane z poziomu konsoli, to zapewne będzie musiało korzystać z opcji wiersza poleceń. Opcje wiersza poleceń są niewielkimi przełącznikami, które bardzo często pojawiają się w programach narzędziowych:

znie Wyświetla wszystkie procesy włąc z informacjami o ich środowisku.

ps –ae tail –f logfile.out

146

Rozdział 3.

Wyświetla końcówkę pliku, lecz czeka, aż na końcu pliku zostaną dopisane nowe dane.

Tworzenie małych programów narzędziowych

Niech biblioteka wykona pracę za nas Wielu programistów używa opcji wiersza poleceń, a w bibliotece języka C dostępna jest funkcja, która nieco ułatwia ich obsługę. Nosi ona nazwę C', a każde jej wywołanie zwraca kolejną opcję podaną w wierszu wywołania programu. Zobaczmy, jak działa ta funkcja. Wyobraźmy sobie, że piszesz program, który może przyjmować kilka różnych opcji: Używać 4 silników.

Włączony tryb odlotowości.

! =¶P¶ ^  “ 0 7F Ten program wymaga jednej opcji posiadającej dodatkową wartość (-e — określającej liczbę używanych silników) oraz drugiej (-a — tryb odlotowości), która jest zwyczajnym przełącznikiem typu włączony/ wyłączony. Opcje te można obsługiwać, wywołując funkcję getopt() w pętli, jak pokazaliśmy na poniższym przykładzie: Musisz dodać ten plik nagłówkowy.

;        4 % &#,&unistd.h   & !   ) )  &  ! &$8        !  &,  ,) )  & %3F$%3F # ,)!    )  "& 4&,  (  &        $

#include To oznacza, że „opcja a jest prawidłowa, podobnie jak opcja e”.

... Tutaj jest umieszczony kod obsługujący poszczególne opcje.

Wczytujemy argument dla opcji „e”.

dbają Ostatnie dwa wiersze yli ocz esk prz y śm aby to, o je. poza przeczytane opc

+!+5C C! C1# #''V5,…³' !+!+'L Ten dwukropek oznacza, że opcja e potrzebuje argumentu. ... case ’e’:  C =! 5 C* ... owuje liczbę optind przech ów odczytanych ak zn łańcuchów } leceń, której z wiersza po przeskoczyć za opcje.

C!D5 7* używamy, by

C1R5 7*

Wewnątrz pętli umieściłeś instrukcję switch, która obsługuje każdą z prawidłowych opcji. Łańcuch znaków ae: informuje funkcję getopt(), że prawidłowymi opcjami są a oraz e. Za opcją e został poza tym zapisany dwukropek, informujący funkcję getopt(), że po opcji -e musi być podany dodatkowy argument. Funkcja zapewnia dostęp do tego argumentu za pośrednictwem zmiennej optarg. Po zakończeniu pętli modyfikujesz wartości zmiennych ƒ oraz argc, aby przeskoczyć za opcje i pobrać pozostałe parametry podane w wierszu poleceń. Zmiana ta sprawi, że tablica ƒ będzie mieć następującą postać:

'"  %

Po przetworzeniu argumentów zerowym argumentem nie będzie już nazwa programu.

ƒ95: będzie wskazywać na pierwszy argument wiersza poleceń, podany za opcjami.

ž FRM To jest argv[0].

To jest argv[1].

To jest argv[2].

jesteś tutaj  147

Zagadkowa pizza

Kawałki pizzy Wygląda na to, że ktoś poodgryzał kawałki kodu pizzy. Przekonaj się, czy będziesz potrafił zastąpić kawałki pizzy i odtworzyć program x

.

#include #include  667'ƒ9: { 67'ƒM~$$ 76~5 int count = 0; char ch; 7 67~ 6ƒ$""""""""""""""""""""""""""""$–~¼Yµ switch (ch) { case ’d’: ............... = ..............; Q case ’t’: ............... = ..............; Q default:  $ 6G@ GD$ return ...............; }

148

Rozdział 3.

Tworzenie małych programów narzędziowych

argc -= optind; ƒ”~  76 puts(”Grube ciasto.”);  ƒM95:  $º @ "D$ƒM  $V+$ for (count = ...........; count < ..........; count++)  ƒ96: return 0; }

71F

argc optarg

+!

1 0

1 t

:

jesteś tutaj  149

Pizza wyjaśniona

Zagadkowa pizza. Rozwiązanie Wygląda na to, że ktoś poodgryzał kawałki kodu pizzy. Miałeś zastąpić kawałki pizzy i odtworzyć program x

.

#include #include  667'ƒ9: { 67'ƒM~$$ 76~5 int count = 0;

Za opcją „d” jest umieszczony dwu gdyż wymaga ona podania argumen kropek, tu.

char ch;

t : 7 67~ 6ƒ$""""""""""""""""""""""""""""""""""""$–~¼Yµ switch (ch) { case ’d’:

71F

optarg

............... = ...................;

Zmienna delivery będzie wskazywać na argument opcji „d”.

Q case ’t’:

+!

1

............... = ....................;

Pamiętaj: w języku C przypisaniu zmiennej wartości 1 odpowiada przypisanie jej wartości reprezentującej logiczną prawdę.

Q default:  $ 6G@ GD$

return .....................; 1 }

150

Rozdział 3.

Tworzenie małych programów narzędziowych

argc -= optind; ƒ”~  76 puts(”Grube ciasto.”);  ƒM95:  $º @ "D$ƒM składnik pizzy Po przetworzeniu opcji pierwszy a argv[0]. żeni wyra iu użyc przy ać można pobr

 $V+$

0

argc

for (count = ..................; count < .................; count++)  ƒ96: return 0; }

Pętla będzie działać tak długo, jak długo wartość count będzie mniejsza od argc.

jesteś tutaj 

151

Jazda próbna

Jazda próbna A teraz wypróbuj poprawiony program do zamawiania pizzy:

Skompiluj program. Podczas pierwszych dwóch prób wykonania programu nie będziesz używał żadnych opcji.

Następnie zastosuj opcję „d”, a jako jej argument podaj „zaraz”.

Teraz wypróbuj opcję „t”. Pamiętaj: ta opcja nie lubi żadnych argumentów.

I w końcu spróbuj pominąć argument opcji „d” — w efekcie zostanie wyświetlony błąd.

% &/3&%  9

;C!!7= :!D7=

;:E7= 

!+ K Y 7   anchois ;:E7= 

!+

 K Y 7   anchois ananas ;:E7= D7  

!+

 ¸    : K Y 7   anchois ananas ;:E7= D7  D

!+

 Grube ciasto. ¸    : K Y 7   anchois ananas ;:E7= D7 7=  †

 C  DD7 `

! M 'M >

Program działa! W tym rozdziale dowiedziałeś się całkiem sporo. Poznałeś tajniki standardowego strumienia wejściowego, standardowego strumienia wyjściowego oraz standardowego strumienia błędów. Dowiedziałeś się, jak rozmawiać z plikami przy użyciu przekierowań oraz samodzielnie tworzonych strumieni danych. I wreszcie, na samym końcu rozdziału dowiedziałeś się, jak obsługiwać argumenty i opcje wiersza poleceń. Wielu programistów korzystających z języka C spędza wiele czasu na pisaniu niewielkich, prostych programów narzędziowych, a większość programów tego typu, jakie można znaleźć w systemach operacyjnych, takich jak Linux, zostało napisanych właśnie w C. Jeśli zachowasz odpowiednią uwagę podczas projektowania programów narzędziowych i zadbasz o to, by programy te robiły tylko jedną rzecz i robiły ją bardzo dobrze, to znajdziesz się na prostej drodze, by stać się doskonałym programistą C.

152

Rozdział 3.

Tworzenie małych programów narzędziowych Nie istnieją

głupie pytania

P: Czy mogę łączyć opcje, zapisując

je jako -td    zamiast -d    -t?

O

: Tak, możesz. Funkcja getopt() poradzi sobie z takim zapisem.

P: A co ze zmianą kolejności opcji? O: Ze względu na sposób odczytywania

P: Jeśli zatem program zauważy

w wierszu poleceń wartość rozpoczynającą się od znaku minusa, to potraktuje ją jako opcję?

O: Jeśli odczyta ją, zanim dotrze do

głównych argumentów wiersza poleceń, to tak — potraktuje ją jako opcję.

opcji nie ma znaczenia, czy zapiszesz je jako: ¤  ¤, ¤¤  , czy też jako ¤  .

P: A co, jeśli będę chciał przekazać

argument mający wartość ujemną, na przykład set_temperature -c -4? Czy funkcja nie pomyśli, że 4 jest opcją, a nie wartością argumentu?

O: Aby uniknąć niejednoznaczności,

można oddzielić główne argumenty wywołania programu od jego opcji przy użyciu dwóch znaków minusa: --. A zatem podane wywołanie możesz zapisać jako: set_temperature -c -- -4. Funkcja getopt() zakończy odczytywanie opcji, gdy napotka sekwencję dwóch znaków minusa, czyli dalsza część wiersza poleceń zostanie potraktowana jako zwyczajne argumenty.

CELNE SPOSTRZEŻENIA Q

Istnieją dwie wersje funkcji main() — jedna z argumentami wiersza poleceń oraz druga — bez żadnych argumentów.

Q

Argumenty wiersza poleceń są przekazywane do funkcji main() jako liczba argumentów oraz tablica wskaźników na łańcuch znaków zawierająca podane argumenty.

Q

Opcje wiersza poleceń to argumenty poprzedzone znakiem minusa (-).

Q

W obsłudze opcji wiersza poleceń może nam pomóc funkcja getopt().

Q

Prawidłowe opcje definiuje się, przekazując je do funkcji getopt() w formie łańcucha znaków, takiego jak ae:.

Q

Umieszczenie dwukropka (:) za opcją w tym łańcuchu oznacza, że opcja pobiera dodatkowy argument.

Q

Funkcja getopt() zapewnia dostęp do argumentu opcji za pośrednictwem zmiennej optarg.

Q

Po odczytaniu wszystkich opcji należy przeskoczyć za nie, korzystając ze zmiennej optind.

jesteś tutaj  153

Niezbędnik C

 

C jest bardzo małym językiem programowania. Oto wszystkie jego zarezerwowane słowa kluczowe (ich kolejność nie ma znaczenia). Każdy program napisany w języku C można podzielić na te słowa kluczowe oraz kilka symboli. Jeśli któregoś z tych słów spróbujesz użyć jako nazwy, kompilator będzie bardzo, ale to bardzo zły.

auto int char return do static  F F7 for + const

if case register default  else !+ float unsigned enum signed

break long continue short double struct T union goto 17 1 

jesteś tutaj  179

Udostępnianie kodu file_hider

Jeśli oprogramowałeś często używane operacje… Istnieją spore szanse, że kiedy zaczniesz pisać więcej programów w C, pojawią się pewne funkcje i możliwości, które chciałbyś wykorzystywać wielokrotnie w różnych programach. Spójrzmy na przykład na specyfikację dwóch programów przedstawione po prawej stronie.

Wczytać zawartość pliku i utworzyć jego nową wersję zaszyfrowaną prz y użyciu operacji XOR.

Szyfrowanie przy użyciu operacji XOR jest bardzo prostym sposobem ukrywania tekstu poprzez określenie bitowej alternatywy wykluczającej każdego znaku tego tekstu oraz jakiejś wartości. Nie jest to metoda szczególnie bezpieczna, lecz wyjątkowo łatwa do zaimplementowania. Co więcej, ten sam kod, który jest używany do zaszyfrowania tekstu, może także posłużyć do jego odszyfrowania. Oto funkcja służąca do szyfrowania przekazanego łańcucha znaków: void oznacza, że funkcja nic nie zwraca.

17 !F!+ ‡  C'

y Do funkcji przekazujem licy tab do nik wskaź znaków. ą !+ !* cał ąda egl Pętla prz To oznacza, że licy tab zawartość będziemy obliczać +‡  C'L i zamienia każdy alternatywę jej element na jego wykluczającą każdego nik. zaszyfrowany odpowied ‡  C5‡  CO&* znaku i liczby 31.

{

ider message_h ów rię łańcuch Wczytać se ego standardow znaków ze o wejścioweg strumienia owym rd a d na stan ić tl ie św y iw ich wyjściowym strumieniu wane iki zaszyfro odpowiedn R. operacji XO przy użyciu

  CRR* } }

Obliczenia matematyczne na znakach? Możemy tak robić, gdyż char jest liczbowym typem danych.

…dobrze byłoby je udostępnić Wygląda na to, że oba powyższe programy będą musiały korzystać z tej samej funkcji szyfrującej — 6M . Wystarczyłoby zatem skopiować jej kod z jednego programu do drugiego, prawda? Takie rozwiązanie nie jest nawet takie złe, zakładając, że kopiowany kod nie jest zbyt duży; ale co zrobić, jeśli trzeba by skopiować naprawdę bardzo dużo kodu? Albo co trzeba by zrobić, gdyby sposób działania funkcji 6M  sprawiał, że w przyszłości trzeba by ją zmienić? Gdyby istniały dwie kopie kodu funkcji, to zmiany trzeba by wprowadzać w dwóch miejscach. Aby nasz kod dobrze się skalował, koniecznie musimy znaleźć jakiś sposób pozwalający na wielokrotne stosowanie tego samego fragmentu kodu — sposób na udostępnienie zbioru funkcji w wielu różnych programach.

Jak możesz to zrobić?

180

Rozdział 4.

WYSIL

SZARE KOMÓRKI Wyobraź sobie, że masz grupę funkcji, których chciałbyś używać w różnych programach. Gdybyś to Ty zaprojektował język C, to w jaki sposób zapewniłbyś możliwość takiego współdzielenia kodu?

Stosowanie wielu plików źródłowych

Możesz rozdzielić kod, umieszczając go w osobnych plikach Jeśli dysponujesz fragmentem kodu, którego chciałbyś używać w kilku różnych plikach, to sensownym rozwiązaniem byłoby umieszczenie go w osobnym pliku .c. Gdyby kompilator potrafił w jakiś sposób automatycznie dołączać taki współdzielony kod podczas kompilacji programu, to można by takiego kodu używać jednocześnie w wielu aplikacjach. Dzięki temu, jeśli kiedyś pojawi się konieczność dokonania zmian w tym współdzielonym kodzie, wystarczy je wprowadzić w jednym miejscu. ny To jest współużytkowa fragment kodu.

Odczytuje zawartość pliku, zapisuje plik.

Szyfruje tekst.

Odczytuje tekst ze standardowego strumienia wejściowego, wyświetla tekst.

Kompilator wkompiluje współużytkowany fragment kodu do każdego programu.

file_hider

Musimy znaleźć jakiś sposób, by powiedzieć kompilatorowi, że ma stworzyć program na podstawie wielu plików źródłowych.

message_hider

Jeśli chcemy skorzystać z osobnego pliku .c zawierającego współużytkowany kod, to pojawia się pewien problem. Do tej pory tworzyliśmy wyłącznie programy na podstawie tylko jednego pliku źródłowego. A zatem jeśli mieliśmy program blitz_hack, to był on tworzony na podstawie jednego pliku źródłowego o nazwie blitz_hack.c. Jednak teraz chcemy znaleźć jakiś sposób, by przekazać kompilatorowi grupę plików źródłowych i powiedzieć mu: „Słuchaj, masz skompilować program na podstawie tych wszystkich plików”. Jak to zrobić? Jakiej składni wywołania programu gcc użyć w takim przypadku? A przede wszystkim, co — z punktu widzenia kompilatora — oznacza wygenerowanie jednego programu wykonywalnego na podstawie wielu plików źródłowych? Jak taki program miałby działać? W jaki sposób połączyć poszczególne pliki?

Aby zrozumieć, w jaki sposób kompilator może stworzyć jeden program na podstawie wielu plików źródłowych, trzeba się przyjrzeć, jak działa kompilacja…

jesteś tutaj 

181

Jak działa kompilacja

Za kulisami kompilacji Aby zrozumieć, w jaki sposób kompilator może skompilować kilka plików źródłowych i wygenerować na ich podstawie jeden program wykonywalny, musimy zajrzeć za kulisy i przekonać się, jak w rzeczywistości wygląda proces kompilacji.

1

Hm… A zatem mam skompilować te wszystkie pliki źródłowe i zrobić z nich jeden program? Zobaczmy, co uda mi się z tego przyrządzić…

   

, ?   #$

Pierwszą operacją, jaką musi przeprowadzić kompilator, jest modyfikacja kodu źródłowego. Musi on dodać do kodu zawartość wszystkich plików nagłówkowych, o których został poinformowany przy użyciu dyrektyw #include. Oprócz tego może się także zdarzyć, że kompilator będzie musiał rozwinąć lub pominąć niektóre fragmenty programu. Po wykonaniu tych operacji kod źródłowy będzie już gotowy do faktycznej kompilacji. Może to robi ć, korzystając z #define oraz poleceń #i Zobaczysz, ja fdef. korzystać, w k z nich części książk dalszej i.

„Dyrektywa” to takie wymyślne określenie „polecenia”.

W pierwszej kolejności umieszczę w kodzie źródłowym kilka dodatkowych składników…

2 2 , $     + Może się wydawać, że C jest językiem programowania całkiem niskiego poziomu, jednak prawda jest taka, że jego poziom nie jest aż tak niski, by mógł go zrozumieć komputer. Tak naprawdę komputer może zrozumieć wyłącznie kod maszynowy bardzo niskiego poziomu, a jedynym sposobem wygenerowania takiego kodu jest przetłumaczenie kodu źródłowego w C na symbole w języku asemblera, takie jak te: ƒ¦¤4U @Q@8 ƒ Q @8@8 ƒ@8@8

Wygląda tajemniczo? Język asemblera opisuje pojedyncze instrukcje, które będzie musiał wykonać procesor podczas wykonywania programu. Kompilator C posiada cały zestaw przepisów dotyczących sposobu tłumaczenia poszczególnych elementów języka. Przepisy te informują kompilator, w jaki sposób przekształcić instrukcję if lub wywołanie funkcji na sekwencję instrukcji w języku asemblera. Jednak nawet język asemblera nie ma odpowiednio niskiego poziomu, by mógł go zrozumieć komputer. Właśnie dlatego niezbędny jest…

182

Rozdział 4.

A zatem w przypadku tej instrukcji „if” muszę zacząć od umieszczenia na stosie…

Stosowanie wielu plików źródłowych Czas zrobić z tego kodu symbolicznego coś strawnego.

3

$   +  ,     + 

Kompilator będzie musiał przekształcić kody symboliczne na kod maszynowy nazywany także kodem obiektowym. Jest to rzeczywisty kod binarny, który będzie wykonywany przez układy scalone procesora. To naprawdę pikantny kawał zapisany w kodzie maszynowym.

&description);

CELNE SPOSTRZEŻENIA Q

Q

Podczas wywoływania funkcji wartości są kopiowane do zmiennych będących jej parametrami. Można tworzyć wskaźniki na struktury, podobnie jak na dane innych typów.

Q

Wyrażenie   ¤‘ ma to samo znaczenie co '  ".

Q

Zapis ¤‘ niweluje konieczność stosowania nawiasów i sprawia, że kod staje się bardziej czytelny.

jesteś tutaj  243

Inne typy danych

Czasami rzeczy podobnego typu wymagają danych różnych typów Struktury pozwalają nam modelować bardziej skomplikowane obiekty z realnego świata. Istnieją jednak takie informacje, których nie można zapisać przy użyciu jednego typu danych:

Dziś w promocji: Liczba całkowita Liczba zmiennoprzecinkowa

6 jabłek 1,5 kg truskawek 0,5 l soku z pomarańczy

Liczba zmiennoprzecinkowa

A zatem, gdybyśmy chcieli zapisać, dajmy na to, ilość czegoś, a tą ilością mogą być liczba, waga lub objętość, to w jaki sposób moglibyśmy to zrobić? No cóż, zawsze można by utworzyć strukturę zawierającą kilka pól, taką jak ta przedstawiona poniżej: M 6 """  76 7 ƒ """ €

Jednak nie jest to dobre rozwiązanie, i to z kilku powodów:

Ì

7  + !         

Ì

21 #$+  !        1!

Ì

3      #    $+ H 1 Q

Naprawdę byłoby bardzo użyteczne, gdybyśmy mogli stworzyć jeden typ danych reprezentujący ilość, a dopiero potem, dla konkretnej informacji, zdecydować, czym ta ilość będzie — liczbą, wagą czy objętością.

W języku C można to zrobić, korzystając z unii.

244

Rozdział 5.

ą ilość. Wszystkie dane opisuj

Struktury, unie i pola bitowe

Unie pozwalają używać bloku pamięci na różne sposoby Zawsze gdy tworzymy strukturę, komputer umieści jej pola w pamięci, jedno za drugim: To jest miejsce na wiek, wyrażony jako liczba typu int. To jest wskaźnik na znaki określające nazwę.

char *name

int age

float weight

To jest liczba zmiennoprzecinko do przechowywanwa ia wagi.

º~›ž›4B´"q€ Unie, tworzone przy użyciu słowa kluczowego union, są inne. Unia będzie wykorzystywać przydzielony jej obszar pamięci do przechowywania wartości tylko jednego z pól podanych w jej definicji. A zatem gdybyśmy mieli unię o nazwie ¦M ze zdefiniowanymi polami 6, 7 oraz ƒ (reprezentującymi odpowiednio: liczbę, wagę oraz objętość), to komputer przydzieliłby jej wystarczająco dużo miejsca w pamięci, by można było zapisać wartość największego z pól, a następnie pozostawił nam możliwość wyboru, którego z pól chcemy użyć. Niezależnie od tego, czy użylibyśmy pola 6, 7, czy ƒ, dane zostałyby zapisane w tym samym miejscu pamięci. ¦M      

Unia wygląda zupełnie jak struktura, przy czym jest definiowana przy użyciu słowa kluczowego union.

typedef union {

Jeśli wartość typu float zajmuje 4 bajty, a wartość typu short 2 bajty, to unia będzie mieć 4 bajty wielkości.

short count; float weight;

Każde z tych pól będziesamym przechowywane w tym miejscu.

float volume; } quantity; ów, To są dane różnych typ ają ilość. jednak wszystkie wyraż

Objętość soku Liczba pomarańczy

Waga winogron

jesteś tutaj  245

Unie

Jak stosować unie?

Nie istnieją

głupie pytania

Po zadeklarowaniu zmiennej typu  możemy przypisać jej wartość na kilka różnych sposobów.

P: Dlaczego unia zawsze ma rozmiar

Określanie wartości pierwszego pola według standardu C89

O: Komputer musi zapewnić, by unia zawsze

Jeśli w zmiennej typu  mamy zamiar zapisać wartość pierwszego pola, to możemy w tym celu użyć zapisu stosowanego w standardzie C89. Według tego zapisu, by przypisać wartość pierwszemu polu unii, wystarczy umieścić ją w nawiasach klamrowych: quantity q = {4};

W tym przypadku polu count została przypisana wartość 4.

Wyznaczone inicjalizatory określają wartości innych pól Wyznaczone inicjalizatory określają wartości pól unii na podstawie ich nazwy; oto przykład:

†

F†5L:C+5&:[N*

W tym przypadku w unii zostanie zapisana zmiennoprzecinkowa wartość pola weight.

największego pola?

miała tę samą wielkość. Jedynym sposobem, w jaki może to zrobić, jest zapewnienie, że będzie ona na tyle duża, by można w niej było zapisać wartość dowolnego z jej pól.

P: Dlaczego używając zapisu ze standardu C89, można ustawić wyłącznie wartość pierwszego pola? Dlaczego nie można by ustawić wartości pierwszego pola zmiennoprzecinkowego w razie przekazania wartości typu float?

O: Aby uniknąć niejednoznaczności. Gdyby

w unii dostępne było pole typu  oraz drugie typu Q, to czy wyrażenie 4"A€ powinno zapisać wartość typu , czy typu Q? Dzięki temu, że zapisywana jest wyłącznie wartość pierwszego pola, dokładnie wiemy, w jaki sposób dane zostaną zainicjowane.

Określenie wartości przy użyciu zapisu z kropką Trzecim sposobem określania wartości unii jest utworzenie zmiennej w jednym wierszu kodu, a w drugim określenie wartości wybranego pola: quantity q; †:1 5O:J*

Pamiętaj: niezależnie od wybranego sposobu ustawiania wartości unii zawsze będzie w niej przechowywana tylko jedna informacja. Unie zapewniają nam jedynie możliwość tworzenia zmiennych, które mogą przechowywać wartości kilku różnych typów.

246

Rozdział 5.

Uprzejmy przewodnik po standardach Wyznaczone inicjalizatory  & (  ,  " &   $;  (  , &  4&  & 4 #       ** ! &$ )#     !& ( &  ,4  & &  niektórych wersji ! & )   ( ($  &#! &Objective C!         4  ^^nie$

Struktury, unie i pola bitowe Wygląda na to, że te wyznaczone inicjalizatory mogłyby być przydatne także w przypadku korzystania ze struktur. Zastanawiam się, czy także tam mogę ich używać.

Tak, wyznaczonych inicjalizatorów można używać także do określania początkowych wartości pól struktur. Mogą się one okazać niezwykle użyteczne, gdy korzystamy ze struktur zawierających bardzo dużo pól i początkowo chcemy określić wartości jedynie kilku z nich. Poza tym stanowią one także doskonały sposób poprawiania czytelności kodu: M 6 6 67'6   77

Ten inicjalizator określi wartości pól gears oraz height, lecz nie ustawi wartości pola color.

€Q " "5L:+C+5&J:C 5%&N*

Unie są często stosowane razem ze strukturami Tworząc unię, deklarujemy jednocześnie nowy typ danych. Oznacza to, że wartości tego nowego typu możemy używać, gdzie tylko zechcemy, tak samo jak danych dowolnych innych typów, takich jak  lub 6. Na przykład możemy użyć ich w strukturze: typedef struct { const char *name; const char *country; quantity amount; } fruit_order;

Do wartości zapisanych w takim połączeniu struktury i unii odwołujemy się, korzystając z operatorów " (kropki) oraz ¤‘, w sposób opisany już wcześniej: j, gdyż jest to nazwa Fragment .amount pojawił się tuta m struktury. pole cej będą tity, zmiennej typu quan

W tym miejscu korzystamy z podwójnego wyznaczonego identyfikatora. .amount odnosi się do pola struktury, a .weight do unii zapisanej w polu .amount.

=7 5L# "Y ##‰ #:  :C+5P:%N*  #U   7%:% C° # :  :C+ : '* To wywołanie wyświetli tekst: „Zamówiono jabłka o wadze 4.2 kg”.

jesteś tutaj  247

Pomieszane drinki

Pomieszane drinki Nadeszła Noc z Margaritą w Salonie Rusz Głową, jednak po następnej kolejce okazało się, że barmanowi najwyraźniej pomieszały się receptury drinków. Sprawdź, czy będziesz potrafił znaleźć pasujące fragmenty kodu dla różnych rodzajów margarity. Oto podstawowe składniki:

M  x6  €x

M 6 ¦ 6 x6  € A oto różne rodzaje margarity: C   5L%:closes i->opens żaden dodatkowy } kod.

}

Nie istnieją

głupie pytania

P: Inne języki programowania, takie jak Java, udostępniają wbudowane listy połączone. Czy w C są jakieś wbudowane struktury danych?

O: W rzeczywistości w języku C nie ma wbudowanych żadnych typów reprezentujących struktury danych. Trzeba je tworzyć samemu.

P: Co musiałbym zrobić, chcąc

skorzystać z siedemsetnego elementu listy? Czy musiałbym zacząć od samego początku i odczytać kolejno wszystkie elementy aż do tego, który mnie interesuje?

O: Właśnie tak.

272

Rozdział 6.

P: To niezbyt fajnie. Sądziłem, że P: Pokazaliście typ struct, który listy połączone są lepsze od tablic.

O: Nie należy myśleć o strukturach

zawiera wskaźnik do kolejnej struktury tego samego typu. Czy typ struct może rekurencyjnie zawierać sam siebie?

P: Jeśli zatem potrzebuję

O: Nie. P: Dlaczego nie? O: Język C musi znać dokładną

danych w kategoriach „lesze” i „gorsze”. Są one odpowiednie lub nieodpowiednie do konkretnego zastosowania.

struktury danych, która pozwala na szybkie wstawianie elementów, to powinienem użyć listy połączonej, a jeśli szukam możliwości bezpośredniego dostępu, to tablicy?

O: Właśnie.

wielkość pamięci, jaką typ struct będzie zajmował w pamięci. Gdyby możliwe było tworzenie rekurencyjnych kopii tego samego typu wewnątrz niego samego, to jedna struktura tego typu miałaby inną wielkość od innej.

Struktury danych i pamięć dynamiczna

Jazda próbna Wykorzystajmy funkcję display(), by wyświetlić wyspy na trasie wycieczki. Dodaj ją do kodu i skompiluj go jako program o nazwie tour.  M~$ M ? M«$$5B55$$A“55$ªRR€  6M~$ M V $$5B55$$A“55$ªRR€   xQ~$ M •7$$5B55$$A“55$ªRR€   7~$ M *$$5B55$$A“55$ªRR€ amity.next = &craggy; craggy.next = &isla_nublar; isla_nublar.next = &shutter;   ~$ M •  $$5B55$$A“55$ªRR€ isla_nublar.next = &skull; skull.next = &shutter; display(&amity);

Doskonale. Kod tworzy listę połączoną struktur typu island, a wstawianie do niej kolejnych elementów jest bardzo łatwe i nie wymaga wiele pracy. No dobrze, dysponujesz już zatem znajomością podstawowych zagadnień związanych ze strukturami rekurencyjnymi oraz listami i możesz zająć się pracą nad głównym programem. Tym razem Twoim zadaniem jest odczytanie zawartości pliku tekstowego o następującej postaci: Wyspa Delfinów Anielska Wyspa Tu dalej znajduje się jeszcze więcej wysp.

Wyspa Dzikiego Kota M  X+

Pracownicy linii lotniczej wciąż dopisują do listy nowe wyspy, dlatego póki nie uruchomisz programu, nie będziesz wiedział, jaka jest długa. W każdym wierszu pliku została zapisana nazwa jednej wyspy. Przekształcenie zawartości tego pliku na listę połączoną nie powinno być większym problemem, prawda?

% &/3&%8)+ !&  &

‘66"6¤ZZ"&   M ? M«   675B55¤A“55   M V    675B55¤A“55   M •7   675B55¤A“55   M •     675B55¤A“55   M *   675B55¤A“55 >

;        4 &       4  # 4  &     — skull$H&      

(#   q **  vv! &$   &     &  )( &    &"& $

jesteś tutaj  273

Pamięć dynamiczna

Hm… Do tej pory każdy element listy zapisywaliśmy w osobnej zmiennej. Skoro jednak nie wiemy, ile wysp zostało zapisanych w pliku, to skąd dowiemy się, ilu zmiennych nam potrzeba? Zastanawiam się, czy istnieje możliwość zarezerwowania nowego miejsca w pamięci wtedy, gdy pojawi się taka potrzeba.

Owszem, potrzebujesz jakiegoś sposobu dynamicznego rezerwowania pamięci. Wszystkie programy, które pisaliśmy do tej pory, korzystały z pamięci statycznej. Za każdym razem, kiedy musieliśmy przechować jakąś informację, dodawaliśmy do kodu odpowiednią zmienną. Zmienne te zazwyczaj były przechowywane na stosie. Pamiętaj: stos jest obszarem pamięci przeznaczonym do przechowywania zmiennych lokalnych. A zatem, kiedy miałeś utworzyć cztery wyspy, zrobiłeś to w następujący sposób:  M~$ M ? M«$$5B55$$A“55$ªRR€  6M~$ M V $$5B55$$A“55$ªRR€   xQ~$ M •7$$5B55$$A“55$ªRR€   7~$ M *$$5B55$$A“55$ªRR€

Do przechowania każdej struktury island potrzebna była odrębna zmienna. Ten fragment kodu zawsze utworzy dokładnie cztery struktury typu island. Gdybyś jednak chciał utworzyć więcej niż cztery wyspy, to potrzebowałbyś kolejnych zmiennych. Takie rozwiązanie jest w porządku, jeśli już w czasie kompilacji wiemy, ile danych będziemy musieli przechowywać; jednak całkiem często zdarza się, że program dopiero w momencie działania dowiaduje się, ile miejsca na dane potrzebuje. Gdybyś pisał na przykład przeglądarkę WWW, to nie wiedziałbyś, ile miejsca na stronę masz zarezerwować, aż do momentu jej odczytania. Właśnie z tego względu programy pisane w języku C muszą dysponować jakimś sposobem poinformowania systemu operacyjnego o tym, że potrzebują dodatkowego miejsca w pamięci, i to w momencie kiedy pojawi się taka potrzeba.

Programy potrzebują pamięci dynamicznej.

274

Rozdział 6.

Struktury danych i pamięć dynamiczna

Czyż nie byłoby cudownie, gdyby już w trakcie działania programu istniał sposób zarezerwowania obszaru pamięci o takiej wielkości, jaka jest nam potrzebna? Wiem jednak, że to tylko moje fantazje…

jesteś tutaj  275

malloc()

Pamięć dynamiczną rezerwuj na stercie Większość pamięci, jakiej używaliśmy do tej pory, była przechowywana na stosie. Stos jest obszarem pamięci przeznaczonym do przechowywania zmiennych lokalnych. Każda informacja jest przechowywana w zmiennej, a każda zmienna znika, gdy tylko zakończy się wykonywanie funkcji. Problem polega na tym, że przydzielanie dodatkowego miejsca na stosie w trakcie działania programu jest dosyć trudne. I właśnie w tym momencie na scenę wkracza sterta. Jest ona tym obszarem pamięci używanym przez programy do przechowywania danych, których będą używały przez dłuższy czas. Sterta nie zostanie automatycznie oczyszczona, zatem doskonale nadaje się do przechowywania struktur danych, takich jak nasza lista połączona. Możesz ją sobie wyobrazić jako bankowy sejf, w którym można wynajmować skrytki.

Rezerwowanie pamięci na stercie jest jak przechowywanie kosztowności w bankowej skrytce.

W pierwszej kolejności zarezerwuj pamięć, używając funkcji malloc() Wyobraź sobie, że Twój program w trakcie działania dowiaduje się, że musi przechować znaczne ilości danych. To trochę tak, jakby prosić o dużą skrytkę na dane, a w języku C robimy to, wywołując funkcję malloc(). Funkcji tej przekazujemy dokładną wielkość potrzebnego nam obszaru pamięci, a ona z kolei prosi system operacyjny o zarezerwowanie takiego obszaru na stercie. Funkcja malloc() zwraca wskaźnik na nowy obszar pamięci przydzielony na stercie, mniej więcej tak, jak gdyby wręczała nam klucz do skrytki bankowej. Wskaźnik ten zapewnia dostęp do przydzielonego obszaru pamięci i możemy go używać, by w przyszłości móc korzystać z naszej skrytki.

32 bajty danych na stercie w miejsc u o adresie 4204853

Funkcja malloc() zwraca wskaźnik na obszar przydzielony na stercie.

276

Rozdział 6.

Sterta

Struktury danych i pamięć dynamiczna

Zwróć pamięć, kiedy nie będzie już potrzebna Bardzo dobrą informacją dotyczącą pamięci przydzielanej na stercie jest ta, że możemy z niej korzystać przez bardzo długi czas. Niestety jest też nieco gorsza wiadomość… możemy jej używać przez bardzo długi czas. W przypadku korzystania ze stosu nie musimy się przejmować zwracaniem używanej pamięci — będzie to robione automatycznie. Zawsze gdy funkcja kończy działanie, wszystkie jej dane lokalne zostają usunięte ze stosu. Jednak sterta działa inaczej. Kiedy już poprosimy o miejsce na stercie, to nie będzie można z niego ponownie skorzystać aż do momentu, gdy jawnie poinformujemy standardową bibliotekę C, że skończyliśmy go używać. Ilość miejsca na stercie jest ograniczona, jeśli więc nasz program będzie o nie bezustannie prosił, to szybko zaczną powstawać tak zwane wycieki pamięci.

Ilość miejsca na stercie jest ograniczona, pamiętaj zatem, by korzystać z niego mądrze.

Wycieki pamięci powstają, gdy program bezustannie prosi o nowe obszary pamięci, nie zwalniając tych, które już mu nie są potrzebne. Wycieki należą do najczęstszych błędów występujących w programach pisanych w języku C, a wyśledzenie ich i poprawienie może być naprawdę bardzo trudne.

Zwalniaj pamięć, wywołując funkcję free() Funkcja malloc() przydziela obszar pamięci na stercie i zwraca wskaźnik do niego. Posługując się tym wskaźnikiem, możemy korzystać z pamięci, a później, kiedy już nie będzie nam ona potrzebna, możemy ją zwolnić, przekazując ten wskaźnik w wywołaniu funkcji free(). To tak jak byśmy oddawali klucz do skrytki pracownikowi banku, by mógł z niej skorzystać ktoś inny.

Dzięki za miejsce w pamięci. Nie jest mi już dłużej potrzebne.

Zawsze gdy jakiś fragment naszego programu prosi o przydzielenie miejsca na stercie, wywołując funkcję malloc(), to jakiś inny jego fragment powinien zwalniać to miejsce za pomocą funkcji free(). Kiedy program zakończy działanie, cała pamięć zajmowana przez jego stertę zostanie automatycznie zwolniona, niemniej jednak dobra praktyka programistyczna nakazuje, by każdy dynamicznie przydzielony fragment pamięci zwracać, wywołując funkcję free().

danych 32 bajty ew na sterci miejscu 4204853 o adresie

Przekonajmy się, jak funkcje malloc() i free() wyglądają w praktyce.

jesteś tutaj  277

free()

Proś o pamięć, wywołując malloc()… Funkcja, która prosi o przydzielenie pamięci, nosi nazwę malloc()— od angielskich słów memory allocation (przydzielanie pamięci). Funkcja ta ma tylko jeden parametr — określa on liczbę bajtów, jaką należy zarezerwować w pamięci. W większości przypadków nie wiemy dokładnie, ile bajtów pamięci nam potrzeba, dlatego też w wywołaniach funkcji malloc() zazwyczaj pojawia się operator sizeof: #include

Dodanie pliku nagłówkowego stdlib.h jest niezbędne, by móc korzystać z funkcji malloc() oraz free().

... malloc(sizeof(island));

To znaczy: „Przydzielcie mi tyle pamięci, by zmieściła się w niej struktur a typu island”.

Operator sizeof określa, ile bajtów zajmuje w pamięci — w używanym systemie operacyjnym — wartość konkretnego typu. Może to być typ struct bądź jakikolwiek inny typ danych, taki jak int czy double. Funkcja malloc() rezerwuje dla nas odpowiedni blok pamięci, a następnie zwraca wskaźnik zawierający jego adres. A jakiego typu będzie ten wskaźnik? Otóż funkcja malloc() zwraca wskaźniki ogólnego przeznaczenia typu void*. island *p = malloc(sizeof(island));

To znaczy: „Zarezerwuj obszar pamięci wystarczający, by zmieścić strukturę typu island, a jego adres zapisz w zmiennej p”.

…i zwalniaj ją, wywołując free() Po zarezerwowaniu pamięci na stercie możemy jej używać dowolnie długo. Jednak kiedy już nie będzie nam potrzebna, powinniśmy ją zwolnić, wywołując funkcję free(). W wywołaniu funkcji free() należy przekazać wskaźnik do obszaru pamięci zarezerwowanego przez funkcję malloc(). O ile tylko przekażemy bibliotece C informację o tym, gdzie się znajduje zwalniany obszar pamięci, to będzie ona w stanie sprawdzić swoje notatki i określić jego wielkość. Aby zatem zwolnić pamięć zarezerwowaną przez powyższą instrukcję, powinniśmy użyć wywołania: free(p);

To oznacza: „Zwolnij pamięć, którą przydzieliłeś na stercie dla zmiennej p”.

No dobrze; skoro już dowiedziałeś się nieco więcej o pamięci dynamicznej, możesz wykorzystać tę wiedzę do rozbudowy swojego programu.

278

Rozdział 6.

Pamiętaj: jeśli w jednej części programu rezerwujesz pamięć przy użyciu funkcji malloc(), to zawsze powinieneś ją później zwolnić, wywołując funkcję free().

Struktury danych i pamięć dynamiczna

O nie! To chwilowo bezrobotni aktorzy… Początkujący aktorzy chwilowo nie mają żadnego angażu, dlatego też znaleźli nieco wolnego czasu w swoich napiętych terminarzach, by pomóc Ci przy kodowaniu. Napisali funkcję narzędziową służącą do tworzenia nowych wysp — danych typu island — o podanych nazwach. Poniżej przedstawiliśmy kod tej funkcji: Nazwa wyspy jest przekazywana jako wskaźnik na łańcuch znaków.

To jest nowa funkcja.

island* create(char *name) To wywołanie utworzy na stercie nową strukturę typu island.

Do przydzielenia obszaru na stercie jest używana funkcja malloc().

{ island *i = malloc(sizeof(island)); ¤‘~

Te wiersze określają ¤‘ ~$5B55$ wartości pól nowej ¤‘6  ~$A“55$ struktury.

Operator sizeof określa wielkość potrzebnego obszaru pamięci wyrażoną w bajtach.

¤‘8~ªRR return i; } Funkcja zwraca adres nowej struktury.

Ta funkcja wygląda naprawdę fajnie. Aktorzy zauważyli, że większość lotnisk na wyspach jest zamykana i otwierana o tych samych godzinach, i dlatego przypisują polom opens i closes odpowiednie wartości domyślne. Funkcja zwraca wskaźnik na nowo utworzoną strukturę typu island.

WYSIL

SZARE KOMÓRKI Uważnie przeanalizuj kod powyższej funkcji create(). Czy myślisz, że może ona przysparzać jakichś problemów? Kiedy już to dokładnie przemyślisz, odwróć kartkę.

jesteś tutaj  279

Rozpoczyna się gra

    Dziennik kapitana. 11:00. Pogoda doskonała. Napisano funkcję create() korzystającą z dynamicznego przydzielania pamięci i zespół programistów zadecydował, że jest ona gotowa do lotów próbnych. island* create(char *name) { island *i = malloc(sizeof(island));

Zagadka na -

¤‘~ ¤‘ ~$5B55$ ¤‘6  ~$A“55$ ¤‘8~ªRR return i; }

14:15. Pochmurno. Wieje od przodu z kierunku północno-zachodniego z siłą 15 węzłów. Lądujemy przy pierwszej sposobności. Zespół programistów na pokładzie zapewnia niezbędny kod. Nazwa wyspy została wpisana z poziomu wiersza poleceń. Tworzymy tablicę do zapisania nazwy wyspy.

679´5:  ´5 

nazwy wyspy. Prosimy użytkownika o podanie

 'x 5~6  Plik Edycja Okno Pomoc

> ./test_flight Atlantyda

14:45. Startujemy. Pas startowy niebezpieczny, dużo kamieni naniesionych przez trzęsienia ziemi. Zespół programistów wciąż jest na pokładzie. Zapasy Red Bulla zaczynają się kończyć…

280

Rozdział 6.

Struktury danych i pamięć dynamiczna

15:35. Dolatujemy do drugiej wyspy. Pogoda dobra. Brak wiatru. Wprowadzam szczegółowe informacje do nowego programu. Prosimy użytkownika o podanie nazwy drugiej wyspy.

 ´5   'x A~6 

wyspę Łączymy pierwszą z drugą.

Ta instrukcja tworzy drugą wyspę.

x 5¤‘8~x A Plik Edycja Okno Pomoc

Wyspa Liliputów

17:50. Z powrotem w kwaterze głównej; odwalam papierkową robotę. Dziwne. Dziennik lotu wygenerowany przez program testowy ma jakiś błąd. Po wyświetleniu szczegółów dzisiejszego lotu okazało się, że w tajemniczy sposób została zmieniona nazwa pierwszej wyspy, na której lądowaliśmy. Proszę zespół programistów o rozeznanie problemu.  M x 5 W ten sposób, korzystając z napisanej wcześniej funkcji, wyświetlimy szczegółowe informacje zapisane na liście odwiedzonych wysp. ??? A co się stało z Atlantydą?

Plik Edycja Okno Pomoc

Nazwa: Wyspa Liliputów, lotnisko otwarte w godzinach: 09:00 — 17:00 Nazwa: Wyspa Liliputów,

Teraz pierwsza ma taką samą wyspa nazwę jak druga!!!

lotnisko otwarte w godzinach: 09:00 — 17:00

Co się stało z nazwą pierwszej wyspy? Czy w kodzie funkcji create() jest jakiś błąd? Czy wskazówką może być sposób jej wywoływania?

jesteś tutaj  281

Zagadka rozwiązana

    Co się stało z nazwą pierwszej wyspy? Spójrzmy jeszcze raz na kod funkcji create(): island* create(char *name) { island *i = malloc(sizeof(island)); ¤‘~ ¤‘ ~$5B55$ ¤‘6  ~$A“55$ ¤‘8~ªRR return i; }

Kiedy kod funkcji zapisuje nazwę wyspy, nie robi kopii przekazanego łańcucha znaków name — zapisuje jedynie wskaźnik do miejsca, jakie on zajmuje w pamięci. Czy to ma jakieś znaczenie? Gdzie jest przechowywany łańcuch znaków zawierający nazwę wyspy? Możemy to sprawdzić, analizując kod wywołujący funkcję create(): 679´5:  ´5   'x 5~6   ´5   'x A~6 

Program prosi użytkownika o podanie nazwy każdej z wysp, jednak za każdym razem nazwa ta jest zapisywana w tej samej lokalnej tablicy znaków o nazwie name. A to oznacza, że obie wyspy będą korzystały z tego samego łańcucha znaków zawierającego nazwę. Kiedy tylko zmienna lokalna name zostanie zaktualizowana — czyli gdy zapiszemy w niej nazwę drugiej wyspy — zmieni się także nazwa pierwszej wyspy.

282

Rozdział 6.

Zagadka na -   

Struktury danych i pamięć dynamiczna

:    34$  $ # W języku C całkiem często trzeba robić kopie łańcuchów znaków. Można to zrobić, wywołując funkcję malloc(), by przydzielić nieznaczny obszar pamięci na stercie, a następnie ręcznie kopiując kolejne znaki z łańcucha do przydzielonego miejsca na stercie. Ale wiesz co? Programiści już przed nami wpadli na lepszy pomysł. Stworzyli funkcję strdup() i udostępnili ją w pliku nagłówkowym string.h. Załóżmy, że dysponujemy wskaźnikiem na tablicę znaków, którą chcemy skopiować: 67' ~$¬YŠR©VŠ$

M

O

N

A

L

I

S

A

\0

Funkcja strdup() jest w stanie utworzyć kompletną kopię łańcucha znaków przechowywaną gdzieś na stercie: char *copy = strdup(s);

1

Z   [\ 1  $   $*"  #   

 $ %  [\ + !         +    

W łańcuchu s jest dziesięć znaków, zaczynając od samego początku i kończąc na ostatnim znaku \0. Wywołanie malloc(10) właśnie mnie poinformowało, że udało się zarezerwować potrzebne miejsce na stercie, zaczynając od adresu 2500000.

2

3 

 %       $*"        

W komórce 2500000 znajduje się M, w komórce 2500001 znajduje się O…

Oznacza to, że funkcja strdup() zawsze tworzy kopię łańcucha na stercie. Nie może tworzyć jej na stosie, gdyż ten obszar jest przeznaczony dla zmiennych lokalnych, a zmienne lokalne są usuwane zdecydowanie zbyt często. Niemniej fakt, że funkcja strdup() umieszcza kopie łańcuchów na stercie, oznacza, że zawsze trzeba zwalniać zarezerwowaną przez nią pamięć, wywołując funkcję free().

jesteś tutaj  283

Zastosowanie funkcji strdup()

Popraw kod, używając funkcji strdup() Teraz możesz poprawić kod programu, używając funkcji strdup(). Oto, jak należy to zrobić: island* create(char *name) { island *i = malloc(sizeof(island)); Nie istnieją

¤‘~strdup(name); ¤‘ ~$5B55$ ¤‘6  ~$A“55$ ¤‘8~ªRR return i; }

Zauważ, że funkcji tej trzeba użyć wyłącznie do zapisania wartości pola name. Czy potrafisz powiedzieć, dlaczego tak się dzieje? Otóż wynika to z faktu, że w polach opens oraz closes są zapisywane literały łańcuchowe. Czy pamiętasz, jak dawno, dawno temu opisywaliśmy, w jakich obszarach pamięci są przechowywane różnego rodzaju dane? Literały łańcuchowe są przechowywane w obszarze przeznaczonym dla stałych, który jest obszarem tylko do odczytu. Ponieważ w obu polach są zapisywane wartości stałe, zatem nie musisz ich zabezpieczać, tworząc kopie ich zawartości, gdyż i tak nigdy nie będą się one zmieniać. Musisz jednak zrobić taką kopię bezpieczeństwa tablicy name, gdyż w przyszłości jej zawartość może zostać zmodyfikowana.

Czy to zatem rozwiązuje nasz problem?

głupie pytania

P

: Gdyby w strukturze island zamiast wskaźnika na znaki była używana tablica znaków, to czy także musiałbym korzystać z funkcji strdup()?

O: Nie. W takim przypadku każda struktura

typu island posiadałaby własną kopię tablicy, więc samodzielne tworzenie jeszcze jednej kopii łańcucha nie byłoby potrzebne.

P: Dlaczego zatem w strukturze

miałbym używać wskaźnika na znaki, a nie tablicy znaków?

O: Wskaźnik na znaki w żaden sposób nie

narzuca wielkości pamięci, jaką trzeba będzie zarezerwować w celu przechowania łańcucha znaków. Gdybyś natomiast użył tablicy znaków, to z góry musiałbyś dokładnie określić dopuszczalną długość łańcucha.

Aby sprawdzić, czy zmiany wprowadzone w funkcji create() rozwiązały problem znikającej nazwy wyspy, musisz ponownie uruchomić program: % &/3&%]  ]   

‘"& x7 Atlantyda M R/  ŠM   675B55ÊA“55   M R/   675B55ÊA“55

Teraz kod działa jak należy. Za każdym razem, gdy użytkownik wpisuje nazwę wyspy, funkcja create() zapisuje ją w zupełnie nowym łańcuchu znaków.

No dobrze, skoro już dysponujesz funkcją do tworzenia danych wysp, użyj jej do skonstruowania listy połączonej wysp zapisanych w pliku.

284

Rozdział 6.

Struktury danych i pamięć dynamiczna

 /  "  Katastrofa! Kod służący do tworzenia wycieczki po wyspach wpadł do basenu! Twoim zadaniem jest wyłowić poszczególne fragmenty kodu z basenu i wstawić je w odpowiednie, puste miejsca programu. Z kolei Twoim celem jest odtworzenie programu, by mógł on odczytywać listę nazw ze standardowego strumienia wejściowego, a następnie przekształcał te nazwy w listę wysp. Każdego fragmentu kodu możesz użyć tylko raz, a do odtworzenia programu nie będziesz potrzebował wszystkich fragmentów pływających w basenie.

 ' ~ªRR  '~ªRR  '8~ªRR 679´5: for(; ........... != ............; i = ...............) { next = create(name);  ~~ªRR start = .................;  –~ªRR i ...........

.......... = next;

} display(start); Uwaga: każdy fragment kodu z basenu może zostać użyty tylko raz!

fgets(name, 80, stdin) next NULL NULL

next

-> .

next

jesteś tutaj  285

Kod wyciągnięty z basenu

 /  "    Katastrofa! Kod służący do tworzenia wycieczki po wyspach wpadł do basenu! Twoim zadaniem było wyłowić poszczególne fragmenty kodu z basenu i wstawić je w odpowiednie, puste miejsca programu. Z kolei Twoim celem było odtworzenie programu, by mógł on odczytywać listę nazw ze standardowego strumienia wejściowego, a następnie przekształcał te nazwy w listę wysp.

 ' ~ªRR  '~ªRR  '8~ªRR

ze Wczytujemy łańcuch znakówwejściowego. standardowego strumienia

679´5: for(;

fgets(name, 80, stdin)

next = create(name); Ta instrukcja tworzy nową wyspę.  ~~ªRR next

start =

!=

NULL

; i =

->

} display(start);

next

286

Rozdział 6.

= next;

Nie zapominaj: i jest wskaźnikiem, zatem należy tu użyć operatora ->.

Uwaga: każdy fragment kodu z basenu może zostać użyty tylko raz!

NULL

next

można Pętla będzie działać tak długo, jak będzie odczytywać kolejne łańcuchy znaków. Podczas pierwszej iteracji pętli zmienna start ma wartość NULL, należy w niej ; zatem zapisać pierwszą wyspę.

 –~ªRR i

Pod koniec działania każdej iteracji pętli w zmiennej i zapisujemy kolejną utworzoną wyspę.

.

)

{

Struktury danych i pamięć dynamiczna

Zaostrz ołówek Chwileczkę! Jeszcze nie skończyłeś. Nie zapominaj, że zawsze gdy rezerwujesz pamięć, używając funkcji malloc(), musisz ją także zwolnić za pomocą funkcji free(). Twój program tworzy listę wysp, korzystając przy tym z funkcji malloc(); nadszedł zatem właściwy moment, byś uzupełnił go o fragment, który będzie zwalniał tę pamięć, kiedy już nie będzie potrzebna. Poniżej przedstawiliśmy początek kodu funkcji o nazwie release(), służącej do zwalniania pamięci używanej przez listę połączoną i wymagającej przekazania wskaźnika na jej pierwszy element:

void release(island *start) { island *i = start;  '8~ªRR  –~ªRR~8 next = ..............; ................; ................; } }

Zastanów się bardzo uważnie. Kiedy będziesz zwalniał pamięć zajmowaną przez wyspę, to co tak naprawdę będziesz musiał zwolnić? Tylko wartość typu island? A może coś jeszcze? W jakiej kolejności powinieneś zwalniać te dane?

jesteś tutaj  287

Zaostrz ołówek

Zaostrz ołówek. Rozwiązanie

Chwileczkę! Jeszcze nie skończyłeś. Nie zapominaj, że zawsze gdy rezerwujesz pamięć, używając funkcji malloc(), musisz ją także zwolnić za pomocą funkcji free(). Twój program tworzy listę wysp, korzystając przy tym z funkcji malloc(); nadszedł zatem właściwy moment, byś uzupełnił go o fragment, który będzie zwalniał tę pamięć, kiedy już nie będzie potrzebna. Poniżej przedstawiliśmy początek kodu funkcji o nazwie release(), służącej do zwalniania pamięci używanej przez listę połączoną i wymagającej przekazania wskaźnika na jej pierwszy element:

void release(island *start) { island *i = start;  '8~ªRR  –~ªRR~8 W pierwszej kolejności musisz zwolnić łańcuch znaków zawierający nazwę, skopiowany przy użyciu funkcji strdup().

W zmiennej next musisz zapisać wskaźnik na następną wyspę.

i->next next = ................; free(i->name)

................; free(i) ................;

}

Dopiero po zwolnieniu pamięci zajmowanej przez nazwę możesz zwolnić samą strukturę island.

}

Gdybyś najpierw zwolnił strukturę, to nie miałbyś jak odwołać się i zwolnić pamięci zajmowanej przez nazwę.

Zastanów się bardzo uważnie. Kiedy będziesz zwalniał pamięć zajmowaną przez wyspę, to co tak naprawdę będziesz musiał zwolnić? Tylko wartość typu island? A może coś jeszcze? W jakiej kolejności powinieneś zwalniać te dane?

Zwalniaj pamięć, gdy jej nie potrzebujesz Skoro dysponujesz już funkcją do zwalniania pamięci, powinieneś ją wywołać, gdy lista wysp nie będzie Ci już potrzebna. Twój program musi jedynie wyświetlić zawartość listy, a zatem kiedy już to zrobi, będziesz mógł ją zwolnić: display(start); release(start);

Kiedy to zrobisz, możesz sprawdzić program.

288

Rozdział 6.

Struktury danych i pamięć dynamiczna

Jazda próbna Co się zatem stanie, kiedy skompilujesz kod i uruchomisz program? % &/3&% ; >,  % )

‘"&’A"8   M º/   675B55ÊA“55  Š  M    675B55ÊA“55   M º H   675B55ÊA“55   M    675B55ÊA“55   X+    675B55ÊA“55  X+` ‰/   675B55ÊA“55   M Q 67º/   675B55ÊA“55   M µ    675B55ÊA“55  µ   675B55ÊA“55   M ½6   675B55ÊA“55   M žQ/   675B55ÊA“55    M    675B55ÊA“55  R‚`6 M    675B55ÊA“55

Zadziałało. Pamiętaj: nie mogłeś wiedzieć, ile wysp znajdzie się w pliku. W tym przypadku jedynie wyświetlasz nazwy pobierane z pliku, zatem mógłbyś to zrobić bez zapisywania ich w pamięci. Ponieważ jednak umieściłeś je w pamięci, możesz na nich dowolnie operować. Mógłbyś dodać do wycieczki nowe etapy bądź też usunąć któreś z wysp znajdujących się na liście. Mógłbyś też zmienić ich kolejność. Dzięki pamięci dynamicznej możesz rezerwować pamięć i korzystać z niej w odpowiedzi na potrzeby pojawiające się W TRAKCIE DZIAŁANIA PROGRAMU. Możliwość korzystania z takiej dynamicznej pamięci przydzielanej na stercie zapewniają dwie funkcje: malloc() oraz free().

jesteś tutaj  289

Stos i sterta

Pogawędki przy kominku Temat dzisiejszej dyskusji:    

     

 



Sterto, jesteś tam? Wróciłem. Niezbyt często widuję cię o tej porze. Niewiele się dziś dzieje? Głęboka regresja. Ups… przepraszam… Tylko to posprzątam… Co robisz? Kod właśnie skończył wykonywać funkcję. Muszę więc posprzątać pamięć zajmowaną przez te zmienne lokalne. Powinieneś nieco luźniej podchodzić do życia. Trochę się odprężyć… Może masz rację. Nie masz nic przeciwko, bym sobie usiadł? Piwa? Nie przejmuj się tą czapką, połóż ją byle gdzie. To… czy ona jest twoja? Hej, znalazłeś pizzę! Super! Szukałam jej przez cały tydzień. Naprawdę powinnaś zastanowić się nad wynajęciem kogoś, kto zadba o to miejsce. Nie przejmuj się. To aplikacja do zamawiania przez internet ją tu zostawiła. Pewnie kiedyś po nią wróci. Skąd możesz to wiedzieć? Chodzi mi o to, skąd wiesz, że o niej nie zapomniała? Skontaktowałaby się. Wywołałaby free(). Hm? Jesteś pewna? Czy nie napisała jej ta sama kobieta, która jest autorką przerażającej gry Rozdepcz-króliczka? Wszędzie te wycieki pamięci. Ledwie co udało mi się dostać do struktur tych króliczków. Wszędzie te pozostałości. To było coś okropnego.

290

Rozdział 6.

Struktury danych i pamięć dynamiczna

 

 Słuchaj, czyszczenie pamięci nie należy do zakresu moich obowiązków. Ktoś prosi mnie o pamięć, ja mu ją przydzielam. Potem ją zostawiam w spokoju, aż mnie poproszą o jej zwolnienie.

To dosyć nieodpowiedzialne podejście. Hm… Może. Ale za to jestem łatwa w obsłudze. Nie tak jak ty i to twoje… zawracanie głowy. Zawracanie głowy? Ja nie zawracam nikomu głowy! Może chcesz chusteczkę? Proszę? Chodzi mi o to, że trudno za tobą nadążyć. Ja po prostu uważam, że pamięcią należy odpowiednio zarządzać. Nieważne. Ja mam zasadę: żyj i daj żyć innym. Jeśli program lubi bałaganić, to sprzątanie po nim nie jest moim problemem. Bałaganiara. Jestem po prostu wyluzowana. Dlaczego nie zatrudniłaś mechanizmu oczyszczania pamięci? Aha… znowu się zaczyna… Chodzi mi tylko o… trochę porządku. Ty przecież nie robisz nic!!! Wyluzuj, stary. Przepraszam. Ale nie jestem w stanie poradzić sobie z takim stopniem nieuporządkowania. Hej… przepełniasz się. Weź to… Dziękuję. Chwila, a co to jest? To najlepszy wynik z gry Rozdepcz-króliczka. Nie przejmuj się; nie przypuszczam, żeby ten program go jeszcze potrzebował.

jesteś tutaj  291

Nie ma głupich pytań Nie istnieją

głupie pytania

P: Dlaczego sterta jest nazywana stertą?

O: Ponieważ komputer nie zarządza

automatycznie jej zawartością. To po prostu wielka sterta danych.

P: Rozumiem, dlaczego muszę

kopiować pole name struktury island w przedstawionym wcześniej przykładzie, ale dlaczego nie muszę kopiować wartości pól opens oraz closes?

P: Czym jest odzyskiwanie pamięci? O: Wartościami tych dwóch pól są literały znakowe. A ponieważ literałów O: Niektóre języki programowania łańcuchowych nie można zmieniać, zatem gromadzą informacje o danych zapisywanych na stercie, a następnie, kiedy dane te nie są już potrzebne, zwalniają zajmowaną przez nie pamięć.

P: Dlaczego mechanizm ten nie jest dostępny w języku C?

O: C jest dosyć starym językiem

programowania; w czasach gdy był on opracowywany, większość języków nie dysponowała możliwościami automatycznego odzyskiwania pamięci.

P: Czy muszę zwolnić wszystkie przydzielone dane przed zakończeniem programu?

O: Nie musisz; system operacyjny zwolni całą pamięć w momencie kończenia pracy programu. Niemniej zwalnianie całej pamięci, którą samodzielnie zarezerwowaliśmy, jest dobrą praktyką programistyczną.

nie ma znaczenia, czy kilka struktur będzie używać tego samego literału, czy nie.

P

: Czy funkcja strdup() korzysta z funkcji malloc()?

O: To zależy od implementacji

standardowej biblioteki C, jednak w większości przypadków tak właśnie się dzieje.

CELNE SPOSTRZEŻENIA

292

Q

Dynamiczne struktury danych pozwalają na przechowywanie różnej liczby elementów.

Q

Lista połączona jest strukturą danych pozwalającą na łatwe wstawianie nowych elementów.

Q

Dynamiczne struktury danych są zazwyczaj definiowane w języku C przy wykorzystaniu rekurencyjnych typów struct.

Q

Rekurencyjne typy struct zawierają jeden lub większą liczbę wskaźników do podobnych typów struct.

Rozdział 6.

Q

Stos jest używany do przechowywania zmiennych lokalnych i w całości zarządza nim komputer.

Q

Sterta służy do długotrwałego przechowywania danych. Obszar na stercie rezerwuje się przy użyciu funkcji malloc().

Q

Operator sizeof pozwala określić wielkość obszaru pamięci niezbędnego do zapisania danej typu struct.

Q

Dane będą przechowywane na stercie tak długo, aż zwolnimy je, wywołując funkcję free().

Struktury danych i pamięć dynamiczna :

?

7

JAKĄ STRUKTURĄ DANYCH JESTEM? 7

7

Dowiedziałeś się już, w jaki sposób można utworzyć w języku C listę połączoną. Jednak listy połączone nie są jedynymi strukturami danych, których możemy potrzebować. Poniżej przedstawiliśmy przykłady kilku innych. Przekonajmy się, czy będziesz potrafił dopasować te struktury danych do opisów sposobów ich wykorzystania.

Struktura danych

Opis

Można mnie używać do przechowywania sekwencji elementów, przy czym gwarantuję łatwość dodawania kolejnych. Moje elementy można przetwarzać tylko w jednym kierunku.

Każdy z moich elementów może się łączyć co najwyżej z dwoma innymi. Doskonale nadaję się do przechowywania informacji hierarchicznych.

Można mnie używać do kojarzenia ze sobą dwóch różnych typów danych. Na przykład mógłbyś mnie użyć do skojarzenia imion osób z ich numerami telefonów.

Każdy z moich elementów jest połączony co najwyżej z dwoma innymi. Moje elementy można przetwarzać w dwóch kierunkach.

jesteś tutaj  293

Taka jest moja struktura danych :

?

7

JAKĄ STRUKTURĄ DANYCH JESTEM? 7

7

Rozwiązanie

Dowiedziałeś się już, w jaki sposób można utworzyć w języku C listę połączoną. Jednak listy połączone nie są jedynymi strukturami danych, których możemy potrzebować. Poniżej przedstawiliśmy przykłady kilku innych. Miałeś dopasować te struktury danych do opisów sposobów ich wykorzystania.

Tablica skojarzeniowa lub mapa

Opis Ta struktura danych kojarzy klucze z wartościami.

Lista dwukierunkowa

Można mnie używać do przechowywania sekwencji elementów, przy czym gwarantuję łatwość dodawania kolejnych. Moje elementy można przetwarzać tylko w jednym kierunku.

Każdy z moich elementów może się łączyć co najwyżej z dwoma innymi. Doskonale nadaję się do przechowywania informacji hierarchicznych.

Ta struktura danych przypomina zwyczajną listę połączoną, jednak połączenia pomiędzy poszczególnymi elementami są dwukierunkowe.

Lista połączona

Można mnie używać do kojarzenia ze sobą dwóch różnych typów danych. Na przykład mógłbyś mnie użyć do skojarzenia imion osób z ich numerami telefonów.

Drzewo binarne Każdy z moich elementów jest połączony co najwyżej z dwoma innymi. Moje elementy można przetwarzać w dwóch kierunkach.

Struktury danych są użyteczne, jednak trzeba na nie uważać!

294

Rozdział 6.

Tworząc takie struktury danych w języku C, musimy zachować dużą ostrożność. Jeśli nie będziemy właściwie śledzić naszych danych, to istnieje ryzyko, że stare, niepotrzebne już informacje zostaną na stercie. Może to doprowadzić do stopniowego zajmowania pamięci komputera, a wraz z upływem czasu także do awarii programu spowodowanej błędami obsługi pamięci. Oznacza to, że naprawdę duże znaczenie ma to, byś nauczył się wykrywać i poprawiać pojawiające się w kodzie błędy związane z wyciekami pamięci…

Struktury danych i pamięć dynamiczna

¥FLĂOHWDMQH                ! " # $% &

.#/ * )*0 #1 -'#23 '! ( #")*#+,- - #/(#)/.#1

 5 6     , 4   4  2+  

#8  9(.#:        (  .  7    , = 7

5 ;        4?  4 4 ;

>  ;  . 7 &  

   5  4 &  & &  7  75 &

  6 

@4 4    

4   

       6  & 5 7&  6 & ;&&  5& AB  ? &   

 & = 

, 5  5 ? &C

 4  &  

 &&  (.#   5 .

    

 4  ?  & ; 4 5

<  & 



5       = 

?  &> >    

 ?  ;? 64   &



,    ;  ? * 6 

jesteś tutaj  295

Ściśle tajne

Dowód rzeczowy A: kod źródłowy Poniżej został przedstawiony kod źródłowy systemu Suspicious Persons Identification Expert System (SPIES). System ten może być używany do rejestrowania i identyfikacji wybranych osób. Nie musisz analizować go dokładnie już teraz, zachowaj jednak jego kopię, byś mógł z niej skorzystać na dalszych etapach śledztwa. É6’ "7‘ É6’ Q"7‘ É6’ "7‘ typedef struct node { char *question; struct node *no; struct node *yes; } node; int yes_no(char *question) { char answer[3];  $@ § &$¦    |   95:~~ËG } node* create(char *question) { node *n = malloc(sizeof(node)); ¤‘¦ ~  ¦  ¤‘~ªRR ¤‘M ~ªRR return n; } void release(node *n) { if (n) {  ¤‘   ¤‘  ¤‘M    ¤‘M   ¤‘¦   ¤‘¦  free(n); } }

296

Rozdział 6.

Struktury danych i pamięć dynamiczna

int main() { 67¦ 9´5: 67  6945: ' x~6 $• M M` M$  x¤‘~6 $R* $  x¤‘M ~6 $º ^M26 $ node *current; do { current = start_node; 7 A  M x 6¤‘¦  {  6¤‘M  6~6¤‘M  } else {  $?Yº¼‰­*Š®*©º¼F®µ©HY Š®D$ break; } €  6¤‘ 6~6¤‘ } else { &'* «$M $  '&  $H  M§$   645  node *yes_node = create(suspect); 6¤‘M ~M x &'* «$$M'& 'x~6 6¤‘¦  6¤‘~x &'* `MM'&  $?M?­Š º*© ¼@ 6 ©¼?­Š º*© ¼@ §$   66¤‘¦   ¦ ´5  6¤‘¦ ~  ¦  break; } } €7 M x $ M$ release(start_node); 5 }

jesteś tutaj  297

Ściśle tajne

Przegląd systemu SPIES Program SPIES jest systemem eksperckim, który uczy się identyfikować osoby na podstawie ich cech szczególnych. Im więcej osób zostanie wpisanych do systemu, tym więcej wie i tym mądrzejszy się staje.

Program tworzy drzewo podejrzanych Program zapamiętuje informacje, wykorzystując strukturę danych nazywaną drzewem binarnym. Drzewo binarne pozwala, by każdy jego element był powiązany z dwoma innymi, tak jak pokazaliśmy na poniższym rysunku: e. To jest pierwsze pytani

  D Tak, Denis ma wąsy.

Nie, Laura nie ma wą sów.

F  * 

  E6&

Tak wyglądają dane zaraz po uruchomieniu programu. Pierwszy element (nazywany też węzłem) drzewa zawiera pytanie: „Czy podejrzany ma wąsy?”. Element ten jest połączony z dwoma innymi: pierwszym — reprezentującym odpowiedź twierdzącą (yes), oraz drugim — reprezentującym odpowiedź przeczącą (no). Węzły yes oraz no przechowują pseudonimy podejrzanych. Program korzysta z tego drzewa, by zadać użytkownikowi serię pytań w celu dokonania identyfikacji podejrzanego. Jeśli programowi nie uda się odnaleźć pasującej osoby, poprosi użytkownika o podanie jej pseudonimu oraz dodatkowych, szczegółowych informacji, które pomogą w jej identyfikacji. Program zapisze te informacje w drzewie, które stopniowo będzie rosło, odzwierciedlając coraz większą wiedzę systemu.

  D

*> 4D

 F&  (

Program będzie gromadził nowe informacje w drzewie w taki sposób.

  E6&

Zobaczmy, jak program prezentuje się w działaniu.

298

Rozdział 6.

   

D

$ & ; 

F  * 

ych zawsze Pseudonimy podejrzan e drzewa. są umieszczane na dol

Struktury danych i pamięć dynamiczna

Jazda próbna Oto, co się stanie, gdy jakiś agent skompiluje program i go uruchomi: % &/3&% & L"

‘66  "6¤  ZZ"&  • M M` M§ & R* § & H  M§±µ67 ?M?­Š º*© ¼±µ676 ©¼?­Š º*© ¼R* § ¬  M M§ & • M M` M§ & ¬  M§ & ±µ67§ & ?Yº¼‰­*Š®*©º¼F®µ©HY Š® M§ & >

Podczas pierwszego wykonania programowi nie udało się zidentyfikować podejrzanego Heńka Fachury. Jednak po podaniu dodatkowych informacji podczas drugiego wykonania program wiedział już wystarczająco dużo, by pana Fachurę zidentyfikować.

Chytre. Na czym zatem polega problem? Ktoś w laboratorium używał systemu przez kilka godzin i zauważył, że choć program zdawał się pracować prawidłowo, to jednak zużywał dwa razy więcej pamięci, niż powinien. To właśnie dlatego zadzwonili po Ciebie. Gdzieś głęboko w kodzie źródłowym jakiś fragment przydziela pamięć na stercie, lecz nigdy jej nie zwalnia. Teraz możesz już usiąść i przeanalizować cały kod, mając nadzieję, że uda Ci się dostrzec przyczynę problemów. Jednak wyśledzenie wycieków pamięci może być wyjątkowo trudne.

Może zatem warto zrobić sobie wycieczkę do laboratorium oprogramowania…

jesteś tutaj  299

valgrind

Detektywi oprogramowania: stosowanie valgrind Wyśledzenie błędów w dużym, złożonym programie, takim jak system SPIES, może zajmować boleśnie dużo czasu. Dlatego też specjaliści od języka C napisali narzędzie, które może nam w tym pomóc. Jednym z takich narzędzi, działającym w systemie operacyjnym Linux, jest program valgrind. Program ten potrafi monitorować dane zapisywane na stercie. Aby móc działać, valgrind tworzy swoją własną wersję funkcji malloc(). Kiedy nasz program chce zarezerwować jakiś fragment pamięci na stercie, valgrind przechwytuje wywołanie funkcji malloc() oraz free() i zamiast nich wywoła ich odpowiedniki. Wersja funkcji malloc() stosowana przez program valgrind zapamięta informację o tym, jaki fragment kodu wywołał tę funkcję oraz jaki fragment pamięci został mu przydzielony. Kiedy nasz program zakończy działanie, valgrind wyświetli informacje o wszelkich danych, które pozostały na stercie, oraz o tym, w których miejscach kodu została im przydzielona pamięć.

Przygotowanie kodu: dodanie informacji do debugowania Przed skorzystaniem z programu valgrind nie musimy nic robić. Nie musimy nawet ponownie kompilować naszego programu. Aby jednak naprawdę w pełnym stopniu skorzystać z możliwości programu valgrind, musimy się upewnić, że nasz program wykonywalny zawiera informacje do debugowania. Informacje do debugowania to dodatkowe dane dodawane do programu podczas jego kompilacji; między innymi należą do nich numery wierszy pliku źródłowego, w jakim był zapisany dany fragment kodu. Jeśli te informacje będą dostępne, valgrind będzie w stanie udzielać nam znacznie bardziej szczegółowych informacji o źródle wycieków pamięci.

9: valgrind gromadzi informacje o danych, dla których przydzielamy pamięć, lecz jej nie zwalniamy.

 

valgrind przechwytuje wywołania funkcji malloc() oraz free().

gcc –g spies.c –o spies Opcja -g informuje kompilator o tym, by zapisywał numery wierszy kompilowanego kodu źródłowego.

Aby dodać informacje do debugowania do pliku wykonywalnego, należy skompilować kod z opcją –g:

Takie są fakty: sprawdzaj swój kod Aby się przekonać, jak działa program valgrind, musisz go uruchomić na komputerze z systemem Linux, a następnie użyć do kilkukrotnego sprawdzenia programu SPIES. Za pierwszym razem użyjesz programu valgrind do sprawdzenia jednego z domyślnych podejrzanych: Denisa Łyżeczki. Musisz go uruchomić z poziomu wiersza poleceń, używając przy tym opcji ¤¤¤676~, a następnie określając program, który ma zostać uruchomiony:

Informacje o tym, czy program valgrind jest dostępny w wersji dla danego systemu operacyjnego oraz jak go zainstalować, można znaleźć na stronie http://valgrind.org.

% &/3&%x 6 

> valgrind --leak-check=full ./spies ==1754== Copyright (C) 2002-2010, and GNU GPL’d, by Julian Seward et al. F7 

F F3E ' ¸ ÇF! 3E ' PODEJRZANY ZIDENTYFIKOWANY F  H  3E ' ==1754== All heap blocks were freed -- no leaks are possible

300

Rozdział 6.

Struktury danych i pamięć dynamiczna

Skorzystaj z programu valgrind kilkakrotnie, by zebrać więcej dowodów W poprzednim przypadku w momencie kończenia programu SPIES na jego stercie nie zostały żadne dane. Ale co się stanie, kiedy uruchomisz program drugi raz i spróbujesz nauczyć go rozpoznawania nowego podejrzanego — Heńka Fachury? % &/3&%x 6 

> valgrind --leak-check=full ./spies ==1754== Copyright (C) 2002-2010, and GNU GPL’d, by Julian Seward et al. F7 

F F3E ' 0  U  3E ' Š 7 

F3Ì  ³ !+

‰7 F

‰’¸U‹,7 Ì  ³ !+ !`‹,‰’¸U‹,7 0  U  3‘   na twarzy Kod zarezerwował bloki F  H  3E ' Na stercie pozostało 16 bajtów. pamięci na stercie 11 razy, 55%J[

Wyciek został zatamowany Wprowadziłeś do programu dokładnie te same dane, jednak tym razem program usunął ze sterty wszystkie zapisane na niej informacje. Jak to zrobiłeś? W jaki sposób poradziłeś sobie z tą sprawą? Nie przejmuj się, jeśli tym razem nie udało Ci się wykryć i poprawić błędu. Wycieki pamięci są jednymi z najtrudniejszych błędów występujących w programach pisanych w C. Prawda wygląda tak, że sporo dostępnych i używanych programów napisanych w C prawdopodobnie ma jakieś błędy tego typu; ale to właśnie z tego powodu programy takie jak valgrind są tak ważne.

Ì

-        

Ì

1      "   

Ì

)   +    !      $

jesteś tutaj  305

Nie ma głupich pytań Nie istnieją

głupie pytania

P

: valgrind stwierdził, że wyciek występuje w wierszu 46., ale został poprawiony zupełnie inny wiersz kodu. Jak to?

O: Łańcuch znaków „Laura…” został

P

: W jaki sposób valgrind przechwytuje wywołania funkcji malloc() i free()?

O: Obie funkcje, malloc() oraz free(),

zapisany na stercie w wierszu 46., jednak wyciek nastąpił, gdy zmieniła się wartość zmiennej (6¤‘¦ ), w której był przechowywany wskaźnik na ten łańcuch. Wycieki nie następują w momencie tworzenia danych, tylko wtedy, gdy program traci wszelkie odwołania do nich.

należą do standardowej biblioteki języka C. Jednak valgrind zawiera bibliotekę z własnymi wersjami obu tych funkcji. Kiedy uruchamiamy nasz program za pośrednictwem programu valgrind, to oba te programy będą korzystały z niestandardowych wersji funkcji malloc() oraz free().

P: Czy mogę korzystać z programu

P: Dlaczego kompilator, generując

valgrind na swoim systemie — Mac OS/Windows/FreeBSD?

O: Wszelkie informacje na temat

dostępnych wersji programu valgrind znajdziesz na stronie http://valgrind.org.

P: Skąd pochodzi nazwa valgrind? O: Valgrind to nazwa wejścia do Walhalli. valgrind (program) zapewnia dostęp do sterty programu w pamięci komputera.

kod, nie dodaje do niego informacji do debugowania zawsze, a jedynie wtedy, kiedy tego zażądamy?

O: Ponieważ dołączanie informacji do

debugowania powoduje powiększenie programu, a może także sprawić, że będzie on działał nieco wolniej.

CELNE SPOSTRZEŻENIA Q

Program valgrind sprawdza występowanie wycieków pamięci.

Q

Q

Q

306

Q

Działanie tego programu polega na przechwytywaniu wywołań funkcji malloc() oraz free().

Wykonując program kilka razy, możemy znacznie zawęzić obszar podejrzewany o występowanie przecieku.

Q

Kiedy sprawdzany program kończy działanie, valgrind wyświetla szczegółowe informacje o tym, co pozostało na stercie.

Program valgrind może nam powiedzieć, w jakim wierszu kodu źródłowego dana została umieszczona na stercie.

Q

Program valgrind może nam także powiedzieć, czy problem wycieków pamięci został rozwiązany.

Jeśli skompilujemy nasz program, dodając do niego informacje do debugowania, to valgrind będzie w stanie podać znacznie więcej informacji.

Rozdział 6.

Struktury danych i pamięć dynamiczna

 I

Twój niezbędnik C Masz już za sobą rozdział 6., a do swojego niezbędnika dodałeś struktury danych i pamięć dynamiczną. Pełną listę podpowiedzi możesz znaleźć w dodatku B. Dynamiczne struktury danych używają rekurencyjnych typów struct.

Wstawianie nowych danych do listy połączonej jest bardzo łatwe.

a ołączon Lista p ziej d jest bar lna za rozszer a. lic b a t iż n

Lista jest połączona ą n z ic m a n dy strukturą danych.

Rekurencyjne typy struct zawierają jedno bądź więcej odwołań do danych tego samego typu.

Funkcja malloc() przydziela pamięć na stercie.

Funkcja free() zwalnia e z pamięć sterty. Funkcja strdup() tworzy kopię łańcucha znaków na stercie.

Wyciek pamięci to obszar pamięci, do którego nie możemy się już dostać.

W odróżnieniu od stosu, pamięć przydzielona na stercie nie jest automatycznie zwalniana. Stos jest używany do przechowyw ania zmiennych lokalnych.

W odnajdywaniu wycieków pamięci może nam pomóc program valgrind.

jesteś tutaj  307

308

Rozdział 6.

7. Zaawansowane funkcje

Odpicuj swoje funkcje na maksa!

Od kiedy odkryłam funkcje o zmiennej liczbie argumentów, moja funkcja go_on_date() stała się wprost niesamowita.

Proste funkcje są w porządku, jednak czasami możemy potrzebować czegoś więcej. Do tej pory koncentrowaliśmy się na sprawach podstawowych, ale co zrobić, kiedy do osiągnięcia celu będziemy potrzebowali większych możliwości i większej elastyczności? W tym rozdziale zobaczysz, jak podnieść IQ swojego kodu, przekazując funkcje jako parametry. Dowiesz się, jak można sortować, wykorzystując funkcje — komparatory. A na samym końcu nauczysz się, jak dzięki zastosowaniu zmiennej liczby argumentów tworzyć superelastyczne funkcje.

to jest nowy rozdział  309

Prawdziwa miłość

Szukając Pana Doskonałego… We wcześniejszych rozdziałach tej książki używałeś już całkiem sporej liczby funkcji; niemniej w rzeczywistości wciąż istnieje kilka sposobów, by nasze funkcje pisane w języku C były jeszcze potężniejsze. Dzięki znajomości odpowiednich sposobów korzystania z funkcji pisanych w C możemy robić znacznie więcej bez konieczności pisania znacznie bardziej rozbudowanego kodu. Aby się przekonać, jak to działa, przeanalizujmy przykład. Załóżmy, że dysponujesz tablicą łańcuchów znaków, które chcesz przefiltrować, wyświetlając tylko niektóre z nich, a pomijając pozostałe: int NUM_ADS = 7; char *ADS[] = { ”Wilhelm: SBM GSOH lubi sport, TV, dobre jedzenie”, $¬6V ¬?Q M$ $¬ VR¬ºQ `2 $ $¬67+º ¬ºVQ62/ žQ$ $?VŠ¬Q 67M $ $‰/ V‰¬Q M$ $‰ºž¬Q `2Q $ };

Chciałabym kogoś, kto lubi sport i definitywnie nie lubi Biebera…

Napiszmy zatem program, który przefiltruje tę tablicę, używając do tego celu funkcji operujących na łańcuchach znaków.

310

Rozdział 7.

Zaawansowane funkcje

Magnesiki z kodem 8    *                        !    !  9  ;  ( Uwaga: # %      *  ,    %#   $ *  (

ƒ  { int i;  $ MM $  $¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤$

 ~5"""""""""""""""""””  """""""""" """"""""""""""""""""""""" """"""""""""""""""""""""""""""""" """""""""""""""""""""""  $@ D$ŠºV9: } }  $¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤$ }

strcmp strstr

ADS[i]

ADS[i]

"sport"

NUM_ADS
b, to ta różnica będzie wartością większą od zera. Jeśli a < b, to będzie ona wartością mniejszą od zera. I wreszcie jeśli a będzie równe b, to różnica wyniesie 0.

A oto, w jaki sposób możesz posortować tablicę przy użyciu funkcji ¦  : qsort(scores, 7, sizeof(int), compare_scores);

jesteś tutaj  325

Ćwiczenie Długie ćwiczenie Teraz Twoja kolej. Przyjrzyj się przedstawionym poniżej opisom różnych sposobów sortowania. Sprawdź, czy będziesz potrafił dla każdego z nich napisać odpowiednią funkcję komparatora. Aby ułatwić Ci wykonanie ćwiczenia, pierwszą z nich napisaliśmy sami.

Posortuj tablicę liczb całkowitych, zaczynając do najmniejszych wartości.

6x 6 6 ƒ' 6x6 ƒ' 6xQ { ~' ' 6x Q~' ' 6xQ ¤Q }

Posortuj liczby całkowite, zaczynając od największych wartości.

6x 6 x 6 6 ƒ' 6x6 ƒ' 6xQ {

}

M 6

Posortuj ty prostoką i od śc o w kolejn zego najmniejs szego. k do najwię

7 77

To jest typ rectangle reprezentujący prostokąt.

€6

6x 6 ƒ'6 ƒ'Q {

}

326

Rozdział 7.

Zaawansowane funkcje

Ostrzeżenie: to jest naprawdę trudne zadanie.

Posortuj alfabetycznie listę imion, uwzględniając przy tym wielkość liter.

6x 6 ƒ'6 ƒ'Q {

} Jeśli łańcuch znaków jest wskaźnikiem na znaki, to czym będzie wskaźnik na ten wskaźnik ?

Oto wskazówka: strcmp(“Abc”, “Def”) < 0

I ostatnie zadanie: jeśli już poradziłeś sobie z funkcjami 6x  oraz 6x , to w jaki sposób napisałbyś dwie poniższe funkcje komparatorów?

Posortuj prostokąty pod względem pola powierzchni, zaczynając od największego.

Posortuj listę imion w odwrotnej kolejności alfabetycznej, uwzględniając wielkość liter.

6x x 6 6 ƒ'ƒ'Q {

}

6x x 6 6 ƒ'6 ƒ'Q {

}

jesteś tutaj  327

Rozwiązanie ćwiczenia Rozwiązanie długiego ćwiczenia Teraz Twoja kolej. Miałeś przyjrzeć się przedstawionym opisom różnych sposobów sortowania i dla każdego z nich napisać odpowiednią funkcję komparatora.

Posortuj tablicę liczb całkowitych, zaczynając do najmniejszych wartości.

6x 6 6 ƒ' 6x6 ƒ' 6xQ { ~' ' 6x

śniej. Tę funkcję napisaliśmy już wcze

Q~' ' 6xQ ¤Q }

Posortuj liczby całkowite, zaczynając od największych wartości.

6x 6 x 6 6 ƒ' 6x6 ƒ' 6xQ { int a = *(int*)score_a; int b = *(int*)score_b; return b – a; Jeśli odwrócisz kolejność, w jakiej liczby są od siebie odejmowane, to zmienisz w ten sposób kolejność sortowania.

}

M 6

Posortuj ty prostoką i od śc o w kolejn zego najmniejs szego. k do najwię

7 77

To jest typ rectangle reprezentujący prostokąt.

€6

6x 6 ƒ'6 ƒ'Q { W pierwszej kolejności skonwertuj wskaźniki na odpowiedni typ.

rectangle* ra = (rectangle*)a; rectangle* rb = (rectangle*)b; int area_a = (ra->width * ra->height);

Następnie oblicz pole powierzchni.

I w końcu użyj sztuczki z odejmowaniem.

328

Rozdział 7.

int area_b = (rb->width * rb->height); return area_a – area_b; }

Zaawansowane funkcje

Posortuj alfabetycznie listę imion, uwzględniając przy tym wielkość liter.

6x 6 ƒ'6 ƒ'Q {

Łańcuch znaków jest wskaźnikiem na znaki, zatem do funkcji komparatora jest przekazywany wskaźnik na wskaźnik.

char** sa = (char**)a; char** sb = (char**)b; return strcmp(*sa, *sb); }

By dotrzeć do łańcuchów znaków, musimy użyć operatora *.

Oto wskazówka: strcmp(”Abc”, ”Def”) < 0

I ostatnie zadanie: jeśli już poradziłeś sobie z funkcjami 6x  oraz 6x , to w jaki sposób napisałbyś dwie poniższe funkcje komparatorów?

Posortuj prostokąty pod względem pola powierzchni, zaczynając od największego.

Posortuj listę imion w odwrotnej kolejności alfabetycznej, uwzględniając wielkość liter.

6x x 6 6 ƒ'ƒ'Q { return compare_areas(b, a); }

Mógłbyś także użyć wywołania o postaci: -compare_areas(a, b).

6x x 6 6 ƒ'6 ƒ'Q { return compare_names(b, a); }

Mógłbyś także użyć wywołania o postaci: -compare_names(a, b).

Spokojnie

Nie przejmuj się, jeśli te ćwiczenia przysporzyły Ci pewnych problemów.

W końcu wymagały one od Ciebie umiejętności korzystania ze wskaźników, wskaźników do funkcji, a nawet pewnych operacji matematycznych. Jeśli były one dla Ciebie trudne, to zrób sobie przerwę, napij się trochę wody i za godzinę lub dwie spróbuj rozwiązać je jeszcze raz.

jesteś tutaj  329

Jazda próbna

Jazda próbna Niektóre z przedstawionych wcześniej funkcji komparatorów były naprawdę pokręcone; dobrze zatem byłoby sprawdzić, jak sobie dają radę w praktyce. Poniżej przedstawiliśmy kod, którego potrzebujesz, by skorzystać z tych funkcji.

É6’ "7‘ É6’ "7‘ É6’ Q"7‘

Tutaj umieść funkcje komparatorów.

  {  6 9:~qU||4||4qqUAA|AA4€ To jest wiersz, który sortuje tablicę scores.

int i; ¦  6 “   6x 6 x 6  $Y 6 QM$  ~5’“””

Ta pętla wyświetli tablicę, gdy już zostanie ona posortowana. To wywołanie sortuje tablicę imion. Pamiętaj: tablica imion to w rzeczywistości tablica wskaźników na znaki, a zatem wielkość każdego z jej elementów wynosi sizeof(char*).

330

Rozdział 7.

 $ M~@D$ 6 9:

Funkcja qsort() zmienia kolejność elementów tablicy.

} 67' 9:~$HM $$¬$$ž$$¬$€ ¦   U   67'6x   $Y $  ~5’U””  $@ D$ 9: } 5 }

Ta instrukcja wyświetla posortowane imiona.

Zaawansowane funkcje

Oto wynik, jaki uzyskasz, kiedy skompilujesz i uruchomisz powyższy kod: Plik Edycja Okno Pomoc Posortowane

> ./test_drive Oto posortowane liczby: Wynik = 554 Wynik = 543 Wynik = 323 Wynik = 112 Wynik = 32 Wynik = 11 Wynik = 3 Oto posortowane imiona: Bartek Krysia Marek Mirka >

Super. Działa! A teraz spróbuj napisać swój własny kod. Funkcje sortujące mogą być niezwykle przydatne, jednak napisanie potrzebnych do ich działania funkcji komparatorów może być dosyć trudne. Na szczęście wraz ze zdobywaniem praktyki będzie to coraz łatwiejsze.

$"  %

Nie istnieją

głupie pytania

P: Nie rozumiem funkcji komparatora P: No dobrze, ale kiedy korzystam do sortowania tablicy łańcuchów znaków. Co oznacza zapis char**?

O: Każdy element tablicy jest wskaźnikiem na znaki (char *). Kiedy ¦   wywołuje funkcję komparatora, przekazuje do niej wskaźniki na dwa elementy tablicy. Oznacza to, że do funkcji komparatora są przekazywane dwa wskaźniki na wskaźniki na znaki. W zapisie stosowanym w języku C każdy z tych wskaźników jest deklarowany jako char**.

z funkcji strcmp(), dlaczego używam wywołania w postaci strcmp(*a, *b)? Dlaczego nie strcmp(a, b)?

O: Parametry a i b są typu char**.

A do funkcji 6  należy przekazywać argumenty typu char*.

P: Czy funkcja qsort() tworzy posortowaną wersję tablicy?

O: ¦   nie tworzy kopii tablicy — modyfikuje oryginalną.

P: Dlaczego to wszystko przyprawia mnie o ból głowy?

O: Nie przejmuj się. Stosowanie

wskaźników bywa naprawdę trudne. Jeśli to wszystko nie wydaje Ci się trudne i złożone, to zapewne dlatego, że dostatecznie długo zastanawiałeś się nad wskaźnikami.

jesteś tutaj  331

drogi Janie

Automatyzacja generowania listów do Jana Wyobraźmy sobie, że piszesz program do rozsyłania korespondencji — będzie on wysyłał różne typy wiadomości do różnych osób. Jednym ze sposobów utworzenia danych dla takiego programu byłoby zastosowanie poniższego typu struct:   xMDUMP, SECOND_CHANCE, MARRIAGE};

To są trzy typy wiadomości, które będą rozsyłane.

M 6 char *name; enum response_type type; } response;

We wszystkich danych o odpowiedzi będziesz zapisywał jej typ.

Typ wyliczeniowy daje Ci możliwość określenia nazwy każdego z trzech rodzajów odpowiedzi, które będą wysyłane i które możesz zapisać w strukturze każdej odpowiedzi. Struktur typu response będziesz używał, przekazując je w wywołaniach trzech poniższych funkcji przeznaczonych do generowania poszczególnych typów wiadomości: void dump(response r) {  $º@ D$"  $ M Q /` QM+‚6 +  $  $ +QM  226  6"$ } void second_chance(response r) {  $º@ D$"  $MQ‚6 Q /` QM+‚6 +$  $   "V   MQ6"$ } void marriage(response r) {  $º@ D$"  $M–Y Q /` QM+‚6 +QM  $  $• M6+2¨ "$ }

A zatem skoro już wiesz, jak wyglądają dane, i dysponujesz funkcjami do generowania odpowiedzi, zobaczmy, jak skomplikowany będzie kod służący do wysyłania odpowiedzi na podstawie tablicy danych.

332

Rozdział 7.

Zaawansowane funkcje

 /  "  Wyciągnij fragmenty kodu z basenu i umieść je w odpowiednich, pustych miejscach poniższego programu. Twoim zadaniem jest uzupełnienie funkcji  , tak by generowała zbiór odpowiedzi na podstawie tablicy danych typu response. Żadnego z fragmentów kodu z basenu nie możesz użyć więcej niż raz.   { response r[] = { $¬67$ºª¬?€$^ $V¼•Yºx•±Š•¼€ $¬6$V¼•Yºx•±Š•¼€$ 7$¬Š­­©ŠX¼€ }; int i;  ~5’U””  67 """"""""""" 6 """"""""""""""  """""""""""" break; 6 """"""""""""""  6x676 """"""""""""" break;   """""""""""""" } } 5 }

Uwaga: każdy kawałeczek z basenu może zostać użyty tylko raz! r[i].type DUMP

r[i].name

r[i] r[i].name

r[i] dump

r[i].name r[i]

SECOND_CHANCE second_chance

jesteś tutaj  333

Wyciągnięte z basenu

 /  "    Wyciągnij fragmenty kodu z basenu i umieść je w odpowiednich, pustych miejscach poniższego programu. Twoim celem było uzupełnienie funkcji  , tak by generowała zbiór odpowiedzi na podstawie tablicy danych typu response.

  { response r[] = { $¬67$ºª¬?€$^ $V¼•Yºx•±Š•¼€ $¬6$V¼•Yºx•±Š•¼€$ 7$¬Š­­©ŠX¼€ }; int i; Wszystkie elementy tablicy są przetwarzane w pętli.  ~5’U”” r[i].type  67 """"""""""" DUMP 6 """""""""""""" Za każdym razem r[i]  """""""""""" sprawdzasz pole type. Dla każdego break; typu wywołujesz SECOND CHANCE odpowiednią funkcję. 6 """"""""""""""""" r[i]  6x676 """"""""""""" break;  r[i]  """""""""""""" } } 5 }

Uwaga: każdy kawałeczek z basenu może zostać użyty tylko raz!

r[i].name r[i].name dump

334

Rozdział 7.

r[i].name

second_chance

Zaawansowane funkcje

Jazda próbna Kiedy uruchomisz program, wygeneruje on poprawne, bo jakżeby inaczej, odpowiedzi dla poszczególnych osób: % &/3&%8 %,& ; 

./send_dear_johns Drogi Michale,

F"    "FYG 

7!    Y   Y "F  H !   !: ¸CÇ  F7"G!"    "FYG 

7!Y  C



  C

 :K       F"! : Drogi Macieju, F7"G!"    "FYG 

7!Y  C



  C

 :K       F"! : Drogi Wilhelmie, C   FV…"    "FYG 

7!Y "F  H !F!  Yˆ : >

Cóż, fajnie, że program działa, jednak jest on całkiem długi, zważywszy, że jedynie wywołuje funkcję dla każdej ze struktur response. Za każdym razem gdy chcesz wywołać funkcję odpowiadającą typowi danej odpowiedzi, program musi wykonać poniższą instrukcję 67: 67 "M case DUMP:   break; 6 V¼•Yºx•±Š•¼  6x676  break;    }

Powiedzieli mi, że programista zapomniał wstawić instrukcji break, a przez to ja skończyłam z tym gościem…

A poza tym co by się stało, gdybyśmy wprowadzili czwarty typ odpowiedzi? Musiałbyś zmienić każdy fragment programu podobny do powyższej instrukcji. Szybko okazałoby się, że musisz zajmować się całkiem rozbudowanym kodem, a wtedy łatwo o błąd. Na szczęście w języku C jest pewna sztuczka, której można użyć w takich sytuacjach, i ma ona związek z tablicami…

jesteś tutaj  335

Tablice wskaźników do funkcji

Stwórz tablicę wskaźników do funkcji Cała sztuczka polega na stworzeniu tablicy wskaźników do funkcji, które będą odpowiadać poszczególnym typom odpowiedzi. Zanim zobaczymy, jak to działa, przekonajmy się, jak można utworzyć tablice wskaźników do funkcji. Gdybyś dysponował zmienną tablicową, w której mógłbyś umieścić wskaźniki do funkcji, to taką tablicę mógłbyś zdefiniować w następujący sposób:  9:~ 6x676€

Jednak w języku C takiej składni nie możesz użyć. Musisz dokładnie wskazać kompilatorowi, jak będą wyglądały funkcje, które chcesz przechowywać w tablicy, czyli podać ich typ wartości wynikowej oraz typy parametrów. Oznacza to, że musisz zastosować znacznie bardziej złożoną składnię:

Każda funkcja w tablicy będzie funkcją typu void.

Zmienna będzie nosić nazwę „replies”.

I nie jest to zwykły wskaźnik do funkcji — to cała tablica takich wskaźników.

void (*replies[])(response)~ 6x676€ Tylko jeden parametr typu ”response”.

     (*

   

Deklarujesz wskaźnik do funkcji (tablicę).

)(

typy

 

)

Tu kończysz określanie zmiennej, teraz nadszedł czas na podanie, jak będą wyglądać parametry każdej z funkcji.

Ale w czym nam pomoże tablica? Przyjrzyj się tablicy. Zawiera ona grupę nazw funkcji, które zostały zapisane w dokładnie takiej samej kolejności, w jakiej są podane wartości typu wyliczeniowego:   xMDUMP, SECOND_CHANCE, MARRIAGE};

To jest bardzo ważne, gdyż tworząc typ wyliczeniowy, kompilator C nadaje jego poszczególnym symbolom wartości całkowite (zaczyna od 0). A zatem DUMP to 5, V¼•Yºx•±Š•¼ to A, a ¬Š­­©ŠX¼ to 4. A to naprawdę bardzo korzystna okoliczność, gdyż oznacza ona, że możesz pobrać wskaźnik do odpowiedniej funkcji, używając wartości typu wyliczeniowego response_type: To twoja tablica funkcji „replies”.

replies[SECOND_CHANCE] == second_chance

Całe to wyrażenie to wskaźnik do funkcji second_chance.

SECOND_CHANCE ma wartość 1.

Przekonajmy się, czy potrafisz wykorzystać tę tablicę funkcji, by uprościć kod funkcji main().

336

Rozdział 7.

Zaawansowane funkcje

Zaostrz ołówek No dobrze, to będzie naprawdę trudne ćwiczenie. Nie spiesz się, a wszystko powinno się udać. Dysponujesz już wszystkimi informacjami, których potrzebujesz, by uzupełnić kod. W nowej wersji funkcji   cała używana wcześniej instrukcja 67 została usunięta i masz ją zastąpić jednym wierszem kodu. Ten wiersz kodu będzie odnajdywał odpowiednią funkcję w tablicy replies i wywoływał ją.   { response r[] = { $¬67$ºª¬?€$^ $V¼•Yºx•±Š•¼€ $¬6$V¼•Yºx•±Š•¼€$ 7$¬Š­­©ŠX¼€ }; int i;  ~5’U”” """""""""""""""""""""""""""""""""""""""""""""""""""""""""" } 5 }

jesteś tutaj  337

Funkcja main() zaktualizowana

Zaostrz ołówek. Rozwiązanie

No dobrze, to było naprawdę trudne ćwiczenie. W nowej wersji funkcji   cała używana wcześniej instrukcja 67 została usunięta, a Ty miałeś ją zastąpić jednym wierszem kodu. Ten wiersz kodu odnajduje odpowiednią funkcję w tablicy replies i wywołuje ją.

  { response r[] = { $¬67$ºª¬?€$^ $V¼•Yºx•±Š•¼€ $¬6$V¼•Yºx•±Š•¼€$ 7$¬Š­­©ŠX¼€ }; int i;  ~5’U”” (replies[r[i].type])(r[i]); """""""""""""""""""""""""""""""""""""""""""""""""""""""""" Gdybyś chciał, mógłbyś dodać * za nawiasem otwierającym; w obu przypadkach wyrażenie działałoby jednak dokładnie w taki sam sposób.

} 5 }

Rozłóżmy to na czynniki pierwsze.

To całe wyrażenie reprezentuje funkcję, taką jak „dump” lub „marriage”.

(replies[r[i].type])(r[i]); To jest tablica nazw funkcji.

338

Rozdział 7.

To jest wartość, np. 0 dla symbolu DUMP lub 2 dla symbolu MARRIAGE.

Wywołujesz funkcję, przekazując do niej dane odpowiedzi — r[i].

Zaawansowane funkcje

Jazda próbna Jeśli teraz spróbujesz wykonać nową wersję programu, uzyskasz dokładnie takie same wyniki co wcześniej: % &/3&%> 6 6

"& xx7 º¬67  M Q /` QM+‚6 +    +QM  226  6" º^  MQ‚6 Q /` QM+‚6 +    "V    MQ6" º¬6 MQ‚6 Q /` QM+‚6 +    "V    MQ6" º 7 M–Y Q /` QM+‚6 +QM   6 M6+2¨ " >

A na czym polega różnica? Zamiast całej instrukcji 67 mamy następujące wywołanie: (replies[r[i].type])(r[i]);

Jeśli chcesz wywołać funkcję generującą odpowiedź w kilku miejscach programu, to nie musisz kopiować całego rozbudowanego kodu. A jeśli w przyszłości zdecydujesz się dodać nowy typ odpowiedzi i nową funkcję generującą, to wystarczy dodać odpowiednie dane do tablicy:

ę Nowy typ oraz funkcj możesz dodać w ten sposób.

  xMºª¬?V¼•Yºx•±Š•¼¬Š­­©ŠX¼LAW_SUIT}; ƒ ' 9:   ~ 6x676law_suit};

Tablice wskaźników do funkcji mogą znacznie uprościć pielęgnację kodu. Stosuje się je, by zapewnić skalowalność kodu poprzez jego skrócenie i ułatwienie jego rozbudowy. Choć początkowo jest to rozwiązanie stosunkowo trudne do zrozumienia, to jednak tablice wskaźników do funkcji naprawdę mogą podnieść Twoje umiejętności programistyczne.

jesteś tutaj  339

Nie ma głupich pytań

CELNE SPOSTRZEŻENIA Q

Wskaźniki do funkcji przechowują adres funkcji.

Q

Nazwa każdej funkcji jest w rzeczywistości wskaźnikiem.

Q

Jeśli dysponujemy funkcją 7 , to zarówno shoot, jak i Z 7 są wskaźnikami do tej funkcji.

Q

Nowy wskaźnik do funkcji deklaruje się w następujący sposób: M¤M ' ¤  MM¤ /.

Q

Jeśli  jest wskaźnikiem do funkcji, to funkcję tę można wywołać, używając wyrażenia  M""";

Q

Również użycie wyrażenia ' M""" spowoduje wywołanie funkcji.

Q

Standardowa biblioteka C udostępnia funkcję sortującą o nazwie ¦  .

Q

Funkcja ¦   wymaga podania wskaźnika do funkcji komparatora sprawdzającej równość bądź relację pomiędzy danymi.

Q

Do funkcji komparatora są przekazywane wskaźniki do dwóch elementów sortowanej tablicy.

Q

Jeśli dysponujemy tablicą danych, to korzystając z tablicy wskaźników do funkcji, możemy skojarzyć z tymi danymi funkcje.

Nie istnieją

głupie pytania

P: Dlaczego składnia

deklarowania tablicy wskaźników do funkcji jest tak złożona?

P: To wygląda podobnie

do kodu obiektowego pisanego w innych językach programowania, prawda?

O: Ponieważ podczas deklarowania O: Owszem, całkiem podobnie. wskaźnika do funkcji konieczne jest określenie typu jej wartości wynikowej oraz typów parametrów. To właśnie dlatego używanych jest przy tym tyle nawiasów.

340

Rozdział 7.

Języki obiektowe kojarzą grupę funkcji (nazywanych metodami) z elementami danych. Dokładnie w taki sam sposób, w jaki można użyć wskaźników do funkcji, by skojarzyć funkcję z danymi.

P: Hm, czy to oznacza,

że C jest językiem obiektowym? Rany, ale super.

O: Nie. C nie jest językiem

obiektowym, jednak inne języki, takie jak Objective-C oraz C++, stworzone na bazie C, zapewniają wiele ze swych obiektowych możliwości, korzystając właśnie ze wskaźników do funkcji (choć robią to w sposób niezauważalny dla programistów).

Zaawansowane funkcje

Zapewnij swoim funkcjom elastyyyyyczność Czasami będziemy chcieć, by nasze funkcje zapewniały naprawdę duże możliwości, takie jak przedstawiona wcześniej funkcja  , która dzięki wykorzystaniu wskaźników do funkcji pozwalała na filtrowanie łańcuchów znaków. Jednak w innych okolicznościach może nam zależeć na napisaniu funkcji, które będą łatwe w użyciu. W ramach przykładu przyjrzyjmy się funkcji  . Dysponuje ona jedną naprawdę rewelacyjną cechą, z której już korzystaliśmy: posiada zmienną liczbę argumentów:  $@26/@2Q¨ ` +QQD$‡A  $ž6  +D$  $@  +` M+D$$/+$

żemy Do funkcji printf() mo ów, ent przekazać tyle argum ile chcemy wyświetlić.

Ale jak TY możesz coś takiego zrobić? Właśnie stanąłeś przed problemem, którego rozwiązanie wymaga takiej możliwości. Okazuje się bowiem, że personel Salonu Rusz Głową ma pewne problemy z rejestracją rachunków za drinki. Jedna osoba z personelu próbowała ułatwić sobie i innym życie i stworzyła typ wyliczeniowy z nazwami wszystkich dostępnych drinków oraz funkcję, która zwraca ich ceny: enum drink { ¬ªºVR©º¼µª**®xŠO¼R¬YH¼®xXRŠº*Y¬ž©¼ }; Q6  {  67  6 ¬ªºVR©º¼ ‡"“B 6 µª**®xŠO¼R q"|A 6 ¬YH¼®xXRŠº U"´4 6 *Y¬ž©¼ q"´B } 5 }

No i super, o ile tylko pracownikom Salonu Rusz Głową chodziłoby o określenie ceny pojedynczego drinka. Ale im chodzi o wyliczenie wartości całego zamówienia: Liczba drinków

To proste

6 *Y¬ž©¼ |*Y¬ž©¼¬YH¼®xXRŠºµª**®xŠO¼R

Pracownicy Salonu Rusz Głową chcieliby dysponować funkcją  , do której byłaby przekazywana liczba wszystkich drinków oraz lista ich nazw.

To już nie jest takie proste

Lista zamówionych drinków

jesteś tutaj  341

Zmienna lista argumentów

*  # " /$ # Język C udostępnia możliwość tworzenia funkcji o zmiennej liczbie argumentów. Standardowa biblioteka C zawiera kilka makr ułatwiających ich tworzenie. Aby się przekonać, jak one działają, napiszesz funkcję pozwalającą na wyświetlenie grupy liczb całkowitych:

Makro można sobie wyobrazić jako funkcję specjalnego typu, pozwalającą na modyfikowanie kodu źródłowego.

x |“BA5A|4 Liczba liczb, jakie należy wyświetlić.

Liczby, która mają zostać wyświetlone.

A oto i kod:

To jest zwyczajny argument, który zawsze będzie przekazywany do funkcji.

Zmienna lista argumentów rozpoczyna się tutaj.

Zmienne argumenty rozpoczynają się za parametrem args.

É6’ "7‘

ƒx  """ {

va_start informuje, gdzie rozpoczynają się zmienne argumenty. Ta pętla pobierze wszystkie pozostałe argumenty przekazane do funkcji.

args zawiera wartość całkowitą określającą liczbę przekazanych argumentów.

va_list ap; ƒx    int i;  ~5’ ””  $@D$ƒx  } ƒx  }

Podzielmy ten kod na fragmenty i przeanalizujmy po kolei każdy z nich.

342

Rozdział 7.

Zaawansowane funkcje

1

@$   $# "

Cały kod potrzebny do obsługi funkcji o zmiennej liczbie argumentów jest dostępny w pliku nagłówkowym stdarg.h.

2

@  ! %    +     #4

Czy pamiętasz te wszystkie książki, w których bohaterka ciągnie ukochanego przez sypialnię, a rozdział kończy się „"""”? Te trzy kropki to tak zwany wielokropek; informuje ona, że pojawi się jakiś ciąg dalszy. W języku C te trzy kropki umieszczone po argumencie funkcji oznaczają, że w jej wywołaniu mogą się pojawić dodatkowe argumenty.

3

Nie, my też nie czytamy takich książek.

-  fL 

Zmienna tworzona przy użyciu makra va_list będzie zawierać dodatkowe argumenty przekazane w wywołaniu funkcji.

4

1       

  

Język C musi znać nazwę ostatniego ustalonego argumentu. W przypadku naszej funkcji jest to parametr  .

5

3 

        

Teraz, gdy przekazane argumenty są zapisane w zmiennej va_list, możesz je odczytać, korzystając z makra ƒx. Wymaga ono przekazania dwóch wartości: pierwszą jest va_list, a drugą typ kolejnego argumentu. W naszym przypadku wszystkie dodatkowe argumenty są liczbami całkowitymi typu int.

6

 *  

Po zakończeniu odczytywania argumentów należy poinformować o tym kompilator C. Do tego celu służy makro va_end.

7

    $! % 

Skoro funkcja jest już gotowa, pozostaje ją wywołać: x |“BA5A|4 To wywołanie wyświetli liczby 79, 101 i 32.

jesteś tutaj  343

Nie ma głupich pytań

Porady maniaka

Funkcje kontra makra Makra służą do przepisania kodu źródłowego, zanim zostanie on skompilowany. Makra, których używałeś w tym rozdziale (va_start, ƒx oraz va_end), mogą wyglądać jak funkcje, jednak w rzeczywistości ukrywają tajemne instrukcje informujące preprocesor, jak ma wygenerować całkiem rozbudowany i inteligentny kod, który przed kompilacją zostanie dodany do Twojego programu.

Nie istnieją

głupie pytania

P

: Chwila, dlaczego va_end oraz va_start nazywacie makrami? Czy to nie są zwyczajne funkcje?

O: Nie. Zostały one zaprojektowane

tak, by wyglądać jak zwyczajne funkcje, jednak w rzeczywistości preprocesor zastępuje je innym kodem.

P: A co to takiego ten preprocesor?

O: Preprocesor jest uruchamiany przed etapem kompilacji kodu źródłowego. Między innymi dodaje on do kodu zawartość plików nagłówkowych.

344

Rozdział 7.

P: Czy można stworzyć funkcję zawierającą wyłącznie zmienną listę argumentów, bez żadnych argumentów ustalonych?

O: Nie. Funkcja musi posiadać

przynajmniej jeden argument ustalony, którego nazwa zostanie przekazana do makra va_start.

P: A co się stanie, kiedy

spróbuję odczytać z va_arg więcej argumentów, niż zostało przekazanych do funkcji?

O: Mogą się pojawić losowe błędy.

P: To nie brzmi najlepiej. O: Niewątpliwie nie. P: A co by się stało, gdybym

spróbował odczytać argument typu int jako argument typu double lub jakiegoś innego?

O: Mogłyby się pojawić losowe błędy.

Zaawansowane funkcje

No dobrze, teraz Twoja kolej. Pracownicy Salonu Rusz Głową chcą, byś napisał im funkcję, która będzie zwracać sumaryczną kwotę zamawianej kolejki drinków, według poniższego przykładu:

Ćwiczenie

 $•M @"4 +D$ |¬YH¼®xXRŠº¬ªºVR©º¼µª**®xŠO¼R Ta instrukcja wyświetli tekst: „Cena wynosi: 16.92 zł”.

Korzystając z funkcji 6  przedstawionej kilka stron wcześniej, uzupełnij kod funkcji  :

Q  """ { Q~5 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" return total; }

jesteś tutaj  345

Kto płaci?

No dobrze, teraz Twoja kolej. Pracownicy Salonu Rusz Głową chcieli, byś napisał im funkcję, która będzie zwracać sumaryczną kwotę zamawianej kolejki drinków, według poniższego przykładu:

Rozwiązanie ćwiczenia

 $•M @"4 +D$ |¬YH¼®xXRŠº¬ªºVR©º¼µª**®xŠO¼R Ta instrukcja wyświetli tekst: ”Cena wynosi: 16.92 zł”.

Korzystając z funkcji 6  przedstawionej kilka stron wcześniej, miałeś uzupełnić kod funkcji  :

Q  """ { Q~5 Nie przejmuj się, jeśli Twój kod nie będzie wyglądał dokładnie tak jak ten. Można go zapisać na kilka różnych sposobów.

va_list ap; """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" va_start(ap, args); """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" int i; """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" for(i = 0; i < args; i++) { """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" enum drink d = va_arg(ap, enum drink); """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" total = total + price(d); """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" } """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" va_end(ap); """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" return total; }

346

Rozdział 7.

Zaawansowane funkcje

Jazda próbna Jeśli napiszesz prosty kod testowy służący do wywoływania funkcji z poprzedniej strony, to będziesz mógł skompilować program i zobaczyć, co się stanie: To jest kod testowy.

   $•M @"4 +D$ 4¬YH¼®xXRŠº¬ªºVR©º¼  $•M @"4 +D$ |¬YH¼®xXRŠº¬ªºVR©º¼µª**®xŠO¼R  $•M @"4 +D$ A*Y¬ž©¼ 5 }

Plik Edycja Okno Pomoc Siemka

A oto i wyniki.

> ./price_drinks  F &&:S&Y  F &S:\%Y  F [:]\Y >

Twój kod działa! Teraz już wiesz, jak tworzyć funkcje o zmiennej liście argumentów, które upraszczają kod i sprawiają, że staje się on bardziej intuicyjny.

Tak, dziecinko! Mógłbym to zapamiętać, nawet gdybym wypił o jednego Monkey Glanda za dużo…

CELNE SPOSTRZEŻENIA Q

Język C pozwala na tworzenie funkcji o zmiennej liczbie argumentów.

Q

Aby stworzyć funkcję o zmiennej liczbie argumentów, trzeba dołączyć plik nagłówkowy stdarg.h.

Q

Zmienne argumenty zostaną zapisane w zmiennej typu va_list.

Q

Dane zapisane w va_list można kontrolować przy użyciu makr: ƒx  , ƒx  oraz ƒx .

Q

Konieczne jest zadeklarowanie przynajmniej jednego argumentu ustalonego.

Q

Uważaj, by nie odczytać większej liczby parametrów, niż faktycznie zostało przekazanych.

Q

Zawsze musisz znać typ każdego z odczytywanych parametrów.

jesteś tutaj  347

Niezbędnik C

 K

Twój niezbędnik C Masz już za sobą rozdział 7., a do swojego niezbędnika dodałeś tworzenie zaawansowanych funkcji. Pełną listę porad i wskazówek możesz znaleźć w dodatku B. iki do Wskaźn o jedyne t funkcji i, które ik n ź wska agają nie wym ia n a w o stos rów * o t a r e p o oraz &…

Wskaźniki do funkcji pozwalają na przekazywanie funkcji, tak jak gdyby były one zwyczajnymi danymi.

ażdej Nazwa k t s je funkcji m do ie ik n ź a k ws . ji c k n tej fu

Funkcja qsort() sortuje tablicę.

Każda funkcja sortująca wymaga przekazania wskaźnika do funkcji komparatora. Funkcja komparat ora określa, w jakie j kolejności mają być umieszczone dw ie dane.

348

Rozdział 7.

…choć, jeśli tylko chcesz, możesz ich używać.

Tablice wskaźników do funkcji mogą nam pomóc w wykonywaniu różnych funkcji w zależności od typu danych.

Język C pozwala na cji tworzenie funk ie zb lic ej nn ie o zm argumentów. Aby tworzyć takie funkcje, należy dołączyć plik stdarg.h.

8. Biblioteki statyczne i dynamiczne

Wymienialny kod Kości śródstopia są statycznie połączone z kośćmi stopy, a te z kolei są statycznie połączone ze stawem skokowym…

Poznałeś już ogromne możliwości bibliotek standardowych. Nadszedł czas, byś użył mocy swojego własnego kodu. W tym rozdziale dowiesz się, jak tworzyć swoje własne biblioteki oraz jak wielokrotnie używać tego samego kodu w różnych programach. Co więcej, nauczysz się współużytkowania kodu w trakcie działania programu, co jest możliwe dzięki bibliotekom łączonym dynamicznie. Poznasz sekrety mistrzów kodowania. A pod koniec tego rozdziału będziesz już potrafił pisać kod, który w łatwy i efektywny sposób będzie można skalować oraz którym będzie można równie łatwo zarządzać.

to jest nowy rozdział  349

Biblioteka bezpieczeństwa

Kod, który możesz zabrać do banku Czy pamiętasz funkcję encrypt(), którą napisałeś już jakiś czas temu i która szyfrowała zawartość łańcucha znaków? Umieściłeś ją w osobnym pliku źródłowym, który można było wykorzystywać w różnych programach: #include “encrypt.h” void encrypt(char *message) { void encrypt(char *message);

while (*message) { *message = *message ^ 31; message++;

encrypt.h

} }

encrypt.c

Ktoś inny napisał już funkcję o nazwie checksum(), która potrafi sprawdzać, czy zawartość łańcucha znaków została zmodyfikowana. Zarówno szyfrowanie, jak i sprawdzanie, czy dane zostały zmodyfikowane, są ważnymi aspektami bezpieczeństwa. Obie te funkcje z osobna są całkiem przydatne, jednak wspólnie mogą się stać podstawą biblioteki bezpieczeństwa.

#include “checksum.h” int checksum(char *message)

Ta funkcja zwraca liczby określane na podstawie zawartości łańcucha znaków.

{ int c = 0;

int checksum(char *message);

while (*message) { c += c ^ (int)(*message); checksum.h

message++; } return c; }

checksum.c

350

Rozdział 8.

Biblioteki statyczne i dynamiczne Biblioteka bezpieczeństwa? Hej, właśnie czegoś takiego szukam! Poziom bezpieczeństwa w naszym banku jest… dość mizerny.

Zaostrz ołówek

Szef do spraw bezpieczeństwa Pierwszego Banku Rusz Głową. Dorabia sobie, czyszcząc baseny.

Pracownik banku napisał program testowy, by przekonać się, jak działają te dwie funkcje. Umieścił wszystkie pliki źródłowe w tym samym katalogu na swoim komputerze i zaczął je kompilować. Skompilował dwa pliki z funkcjami bezpieczeństwa do plików obiektowych, a następnie napisał program testowy:

#include #include #include

Plik Edycja Okno Pomoc

> gcc -c encrypt.c -o encrypt.o > gcc -c checksum.c -o checksum.o >

int main() { 67 9:~ ?  M6«$ encrypt(s); printf(“Zakodowano do postaci ’%s’\n”, s); printf(“Suma kontrolna wynosi %u\n”, checksum(s));

Funkcja encrypt() zaszyfruje przekazany łańcuch znaków. Jej drugie wywołanie spowoduje jego odszyfrowanie.

encrypt(s); printf(“Odkodowano do postaci ’%s’\n”, s); printf(“Suma kontrolna wynosi %u\n”, checksum(s)); return 0; }

I właśnie wtedy zaczęły się problemy. Kiedy skompilował program, coś poszło koszmarnie źle… Plik Edycja Okno Pomoc

> gcc test_code.c encrypt.o checksum.o -o test_code test_code.c:2:21: error: encrypt.h: No such file or directory test_code.c:3:22: error: checksum.h: No such file or directory >

Weź ołówek i zakreśl polecenie lub kod, który sprawia, że kompilacja się nie powiodła.

jesteś tutaj  351

Stosuj dla standardowych plików nagłówkowych

Zaostrz ołówek Rozwiązanie

Problem tkwił w programie testowym. Wszystkie pliki źródłowe są umieszczone w tym samym katalogu, jednak program ten próbuje dołączyć pliki nagłówkowe encrypt.h oraz checksum.h, podając ich nazwy w nawiasach kątowych (< >).

#include #include #include int main() { 67 9:~ ?  M6«$ encrypt(s); printf(“Zakodowano do postaci ’%s’\n”, s); printf(“Suma kontrolna wynosi %u\n”, checksum(s)); encrypt(s); printf(“Odkodowano do postaci ’%s’\n”, s); printf(“Suma kontrolna wynosi %u\n”, checksum(s)); return 0; }

Nawiasy kątowe dołączają standardowe pliki nagłówkowe Jeśli w dyrektywie #include użyjemy nawiasów kątowych, to kompilator nie będzie poszukiwał plików nagłówkowych w bieżącym katalogu, lecz w standardowych katalogach plików nagłówkowych. Aby nasz testowy program skompilował się i skorzystał z lokalnych plików nagłówkowych, musisz zamienić nawiasy kątowe na znaki cudzysłowu (“ “).

Plik stdio.h jest przechowywany w jednym ze standardowych katalogów plików nagłówkowych.

Teraz program się prawidłowo kompiluje. Szyfruje testowy łańcuch znaków do postaci, której nie da się odczytać.

#include #include “encrypt.h” #include “checksum.h”

Pliki encrypt.h oraz checksum.h są natomiast przechowywane w tym samym katalogu co program.

Plik Edycja Okno Pomoc

> gcc test_code.c encrypt.o checksum.o -o test_code > ./test_code Zakodowano do postaci ’Ophvz{e3?omefu~|vzsj3?v?hzu{ M Suma kontrolna wynosi 4294967253 …7 7

7 !M‰7F ! 7M Suma kontrolna wynosi 4294967236 > Funkcja checksum() zwraca różne wartości dla różnych łańcuchów znaków.

352

Rozdział 8.

Powtórne wywołanie funkcji encrypt() powoduje przywrócenie oryginalnej postaci łańcucha znaków.

Biblioteki statyczne i dynamiczne

N -      ""# #$ /$ 4& A zatem gdzie kompilator będzie poszukiwał plików nagłówkowych, których nazwy zapisujesz w nawiasach kątowych? By się tego dowiedzieć, powinieneś sprawdzić dokumentację używanego kompilatora C, jednak zazwyczaj w systemach uniksowych, takich jak Linux lub komputery Mac, kompilator będzie ich poszukiwał w następujących katalogach: /usr/local/include /usr/include

Katalog /usr/local/include jest często używany do przechowywania plików nagłówkowych dodatkowych bibliotek.

A jeśli używasz wersji gcc dostępnej w MinGW, to kompilator będzie ich szukał w katalogu: C:\MinGW\include

W pierwszej kolejności kompilator sprawdzi katalog /usr/local/include.

Katalog /usr/include zaw zazwyczaj pliki nagłów iera systemu operacyjnego kowe .

A co zrobić, jeśli będziesz chciał współużytkować jakiś kod? Czasami będziesz chciał napisać kod przeznaczony do wykorzystania w wielu programach przechowywanych w różnych katalogach na całym dysku komputera. Co można zrobić w takiej sytuacji?

No pewnie. Mam zamiar dodać kod bezpieczeństwa do tych wszystkich programów. Nie chciałbym, żeby w każdym była używana osobna kopia tego samego kodu.

Istnieją dwie grupy plików, które będą użytkowane we wszystkich programach: pliki nagłówkowe .h oraz pliki obiektowe .o. Zobaczmy, w jaki sposób można współużytkować obie te grupy plików.

jesteś tutaj  353

Współużytkowanie plików nagłówkowych

Współużytkowanie plików nagłówkowych Istnieje kilka sposobów współdzielenia plików nagłówkowych pomiędzy kilkoma projektami:

1

& !     

Jeśli skopiujesz swoje pliki nagłówkowe do jednego z katalogów standardowych, takich jak /usr/local/include, to będziesz mógł je dołączać, zapisując ich nazwy w nawiasach kątowych.

#include

2

Nawiasów kątowych możesz używać, jeśli Twoje pliki nagłówkowe są umieszczone w jednym z katalogów standardowych.

.   h  ! $  1    

Jeśli chcesz przechowywać swoje pliki nagłówkowe w innym miejscu, na przykład w katalogu /my_header_files, to możesz dodać nazwę katalogu do nazwy pliku nagłówkowego podanego w dyrektywie #include:

/

% && 

my_header_files

#include "/my_header_files/encrypt.h" encrypt.h

checksum.h

3

. !          !

Ostatnim rozwiązaniem jest poinformowanie kompilatora, gdzie może znaleźć Twoje pliki nagłówkowe. Można to zrobić, używając opcji -I kompilatora gcc: gcc -I/my_header_files test_code.c ... -o test_code

Opcja -I informuje kompilator gcc o tym, że istnieje jeszcze inne miejsce, w którym może szukać plików nagłówkowych. Kompilator wciąż będzie ich poszukiwał we wszystkich katalogach standardowych, jednak w pierwszej kolejności sprawdzi katalogi podane w opcji -I.

354

Rozdział 8.

pilator, że ma Ta opcja informuje komłówkowych nie tylko nag ów plik ć iwa zuk pos wych, lecz także w katalogach standardo _files. der hea y_ /m gu w katalo

Biblioteki statyczne i dynamiczne

Współużytkowanie plików .o poprzez określanie pełnej ścieżki dostępu Także pliki obiektowe — z rozszerzeniem .o — możesz umieszczać w podobnym wspólnym katalogu. Dzięki temu podczas kompilowania programu, który z nich korzysta, będziesz mógł podać pełną ścieżkę dostępu do nich:

/

% && 

my_object_files

gcc -I/my_header_files test_code.c /my_object_files/encrypt.o /my_object_files/checksum.o -o test_code Podawanie pełnej ścieżki dostępu do plików obiektowych oznacza, że nie będziesz musiał tworzyć ich odrębnych kopii dla poszczególnych projektów.

encrypt.o rolę Katalog /my_object_files pełni w plikó ch Twoi u azyn mag go centralne obiektowych.

checksum.o

Jeśli skompilujesz swój kod, podając pełne ścieżki dostępu do plików obiektowych, których chcesz używać, to wszystkie pisane programy będą mogły korzystać z tych samych plików encrypt.o oraz checksum.o.

Hm… Takie rozwiązanie może być w porządku, jeśli mam jeden lub dwa współużytkowane pliki obiektowe, ale co zrobić, jeśli będę ich miał znacznie więcej? Zastanawiam się, czy jest jakiś sposób, by przekazać kompilatorowi informacje o całej grupie takich plików…

Owszem, jeśli utworzysz archiwum plików obiektowych, to będziesz mógł powiedzieć kompilatorowi o nich wszystkich za jednym zamachem. Archiwum to grupa plików obiektowych zapisanych w jednym pliku. Tworząc pojedynczy plik archiwum zawierający wszystkie pliki bezpieczeństwa, znacznie ułatwisz współużytkowanie ich w wielu różnych projektach.

Zobaczmy, jak to zrobić…

jesteś tutaj  355

Archiwa

Archiwum zawiera pliki .o Czy kiedykolwiek używałeś plików .zip lub .tar? Zatem wiesz, jak łatwo można utworzyć plik zawierający inne pliki. I dokładnie tym samym jest archiwum .a — jest to plik zawierający inne pliki.

libl.a

Otwórz okno terminala lub wiersza poleceń i przejdź do jednego z katalogów library. Są to takie katalogi jak /usr/lib lub C:\MinGW\ lib; zawierają one kod bibliotek. W katalogach tych możesz znaleźć bardzo wiele archiwów .a. Dostępne jest także polecenie nm pozwalające na przeglądanie zawartości tych archiwów:

libmain.o

'

Być może na swoim komputerze nie będziesz miał biblioteki libl.a, jednak polecenie to możes z wypróbować na dowolnym pliku .a.

To jest archiwum o nazwie libl.a. libmain.o

libyywrap.o

% &/3&%%  3  !

> nm libl.a libl.a(libmain.o): 00000000000003a8 s EH_frame0 U _exit 0000000000000000 T _main 00000000000003c0 S _main.eh U _yylex libl.a(libyywrap.o): 0000000000000350 s EH_frame0 0000000000000000 T _yywrap 0000000000000368 S _yywrap.eh >

Polecenie nm generuje listę nazw dostępnych w podanym archiwum. Przedstawione na powyższym przykładzie archiwum libl.a zawiera dwa pliki obiektowe: libmain.o oraz libyywrap.o. To, do czego oba te pliki są używane, nie ma w tym przypadku większego znaczenia; najważniejsze jest to, że całą grupę plików obiektowych można przekształcić w jeden plik archiwalny i używać go podczas korzystania z kompilatora gcc. Zanim jednak zobaczysz, jak kompilować programy, korzystając przy tym z plików .a, zerknij, jak można umieścić pliki encrypt.o oraz checksum.o w archiwum.

356

Rozdział 8.

”T _main” oznacza, że libmain.o zawiera funkcję main().

Biblioteki statyczne i dynamiczne

Utwórz archiwum, używając polecenia ar… Polecenie archiwizowania (ar) pozwala umieszczać pliki obiektowe w archiwach: Przełącznik r oznacza, że jeśli plik .a istnieje, to zostanie on zaktualizowany.

Przełącznik s nakazuje utworzenie indeksu na samym początku pliku .a.

To są pliki obiektowe, które zost aną umieszczone w archiwum.

ar –rcs libhfsecurity.a encrypt.o checksum.o

Przełącznik c oznacza, archiwum zostanie utw że bez żadnych komunikat orzone ów.

To jest nazwa tworzonego pliku .a.

'"  % Czy zauważyłeś, że wszystkie pliki .a mają nazwy podobne do lib.a? To standardowy sposób określania nazw archiwów. Nazwy zaczynające się od lib oznaczają, że są to biblioteki statyczne. W dalszej części rozdziału dowiesz się, co to oznacza.

Pamiętaj, by swoim archiwom zawsze nadawać nazwy w postaci lib.a.

Jeśli będziesz stosował inne nazwy, to kompilator może mieć problemy ze znajdowaniem Twoich archiwów.

…a następnie umieść plik .a w katalogu bibliotek Kiedy już utworzysz plik archiwum, powinieneś go umieścić w katalogu bibliotek. A który katalog to ma być? To już zależy od Ciebie, ale do wyboru masz dwie możliwości:

Ì

'   1 ! #           q qq +

Niektórzy programiści, kiedy już wiedzą, że ich archiwa działają prawidłowo, lubią je umieszczać w standardowych katalogach. W przypadku systemu Linux, komputerów Mac lub środowiska Cygwin dobrym wyborem będzie katalog /usr/local/lib, gdyż jest on przeznaczony do przechowywania Twoich własnych niestandardowych bibliotek.

Ì

'   1 !      1

 

Jeśli jeszcze pracujesz nad swoim kodem bądź też jeśli nie odpowiada Ci umieszczanie własnego kodu w katalogu systemowym, to możesz zapisać swoje archiwum w dowolnym innym katalogu, takim jak /my_lib.

W większości systemów umieszczanie plików w katalogu /usr/local/lib wymaga posiadania uprawnień administracyjnych.

jesteś tutaj  357

Kompiluj, używając przełącznika -l

I w końcu kompiluj inne programy Tworzenie archiwum biblioteki miało na celu zapewnienie Ci możliwości używania go podczas tworzenia innych programów. Jeśli zainstalowałeś swoje archiwum w bibliotece standardowej, to będziesz mógł kompilować kod, używając przy tym opcji -l: hfsecurity informuje komputer, że należy poszukać archiwum o nazwie libhfsecurity.a.

Pamiętaj, by nazwy plików podać przed nazwami bibliotek dołączanych przy użyciu opcji -l.

gcc test_code.c –lhfsecurity –o test_code hiwów, Jeśli korzystasz z kilku arc ji -l. opc u par ąc waj uży podaj je,

Czy teraz już rozumiesz, dlaczego tak ważne jest nadawanie swoim archiwom nazw typu lib.a? Nazwa podawana za opcją -l musi pasować do fragmentu nazwy pliku archiwum. A zatem jeśli archiwum nosi nazwę libawesome.a, to program będziesz mógł skompilować, używając opcji -lawesome.

Czy potrzebujesz opcji -I? Wszystko zależy od tego, gdzie umieściłeś swoje pliki nagłówkowe.

A zatem muszę poszukać pliku libhfsecurity.a w katalogu /my_lib.

Ale co zrobić, jeśli archiwa zostaną umieszczone w jakimś innym miejscu, takim jak katalog /my_lib? W takim przypadku należy posłużyć się opcją -L, umożliwiającą określenie katalogu, w którym znajdują się archiwa: gcc test_code.c –L/my_lib –lhfsecurity –o test_code

Dla maniaków Zawartość katalogów bibliotek na różnych komputerach może się bardzo różnić. A dlaczego? Ponieważ różne systemy operacyjne udostępniają różne usługi. Każdy z plików .a jest odrębną biblioteką. Będą istniały biblioteki służące do nawiązywania połączeń z siecią oraz do tworzenia aplikacji z graficznym interfejsem użytkownika. Spróbuj wykonać polecenie nm na kilku różnych plikach .a. Bardzo wiele z wyświetlonych w ten sposób nazw będzie odpowiadać skompilowanym funkcjom, których możesz używać: T oznacza „Text”, a to z kolei oznacza funkcję.

0000000000000000 T _yywrap

Funkcja ta ma nazwę yywrap().

Polecenie nm wyświetli nazwy wszystkich plików obiektowych (.o) oraz wszystkie nazwy dostępne wewnątrz tych plików. Jeśli obok nazwy ujrzysz literkę T, będzie to oznaczać, że dana nazwa jest funkcją dostępną w danym pliku obiektowym.

358

Rozdział 8.

Biblioteki statyczne i dynamiczne

Magnesiki make )    ,       -             $   %$          - ( 6   5  =       * encrypt checksum =  !      ( ./elliptical

Mac

Waga: 52.18 kg Dystans: 18.18 km Spalono kalorii: 882.22 cal >

W systemie Linux To nie całkiem to samo, co faktycznie dzieje się w systemie Linux. W systemie Linux oraz większości wersji systemu UNIX kompilator zapisuje jedynie nazwę pliku libhfcal.so, bez dołączania pełnej ścieżki dostępu do niego. Oznacza to, że jeśli biblioteka nie będzie umieszczona w jednym ze standardowych katalogów (takich jak /usr/lib), to program nie będzie w stanie jej odszukać. Aby rozwiązać ten problem, system Linux sprawdza także dodatkowe katalogi, które zostały określone w zmiennej środowiskowej LD_LIBRARY_PATH. Jeśli zatem zadbasz o to, by nazwa używanego katalogu z bibliotekami została podana w tej zmiennej środowiskowej, i jeśli ją wyeksportujesz — to program elliptical odnajdzie bibliotekę libhfcal.so. W systemie Linux musisz odpowiednio określić zawartość zmiennej środowiskowej LD_LIBRARY_PATH, by program był w stanie odnaleźć bibliotekę. Nie trzeba tego robić, jeśli biblioteka zostanie umieszczona w jednym ze standardowych katalogów, takich jak /usr/lib.

374

Rozdział 8.

Nie możesz zapomnieć o wyeksportowaniu zmiennej. Plik Edycja Okno Pomoc

JestemLinux

> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/libs > ./elliptical Waga: 52.18 kg Dystans: 18.18 km Spalono kalorii: 882.22 cal >

Linux

Biblioteki statyczne i dynamiczne

W systemie Windows Przyjrzyjmy się teraz, w jaki sposób możesz uruchomić kod skompilowany przez wersje kompilatora gcc dostępne w MinGW oraz Cygwin. Oba te kompilatory tworzą biblioteki DLL oraz programy wykonywalne dostosowane do systemu Windows. I podobnie jak w systemie Linux, także i w tym przypadku program wykonywalny zawiera wyłącznie nazwę biblioteki — hfcal — bez ścieżki dostępu do katalogu, w którym jest ona umieszczona. Jednak system Windows nie korzysta ze zmiennej środowiskowej LD_LIBRARY_PATH podczas poszukiwania bibliotek. Zamiast tego Windows szuka biblioteki w aktualnym katalogu, a jeśli jej tam nie znajdzie, to przeszuka katalogi podane w zmiennej środowiskowej PATH.

W przypadku korzystania z Cygwin Jeśli program skompilowałeś, używając Cygwin, to będziesz mógł go uruchomić z poziomu interpretera poleceń bash w następujący sposób: Plik Edycja Okno Pomoc

JestemCygwin

> PATH=”$PATH:/libs” > ./elliptical Waga: 52.18 kg Dystans: 18.18 km Spalono kalorii: 882.22 cal >

Cygwin w systemie Windows

W przypadku korzystania z MinGW Jeśli program skompilowałeś, używając kompilatora dostępnego w MinGW, to będziesz mógł go uruchomić z poziomu wiersza poleceń w następujący sposób: Plik Edycja Okno Pomoc

JestemMinGW

C:\code> PATH=”%PATH%;C:\libs” C:\code> ./elliptical Waga: 52.18 kg Dystans: 18.18 km Spalono kalorii: 882.22 cal C:\code>

Czy uważasz, że to wszystko jest dosyć złożone? Owszem, jest, i właśnie dlatego większość programów używających bibliotek dynamicznych przechowuje je w jednym ze standardowych katalogów. Oznacza to, że w systemie Linux oraz na komputerach Mac biblioteki będą się zwykle znajdować w takich katalogach jak /usr/lib lub /usr/local/lib; natomiast w systemie Windows programiści zazwyczaj umieszczają biblioteki DLL w tym samym katalogu, w którym znajduje się plik wykonywalny programu.

MinGW w systemie Windows

jesteś tutaj  375

Ćwiczenie Długie ćwiczenie Ekipa Siłowni Rusz Głową ma zamiar wysłać bieżnie treningowe do USA. Wbudowany serwer działa pod kontrolą systemu Linux i jest już na nim zainstalowana polska wersja kodu. Pracownicy techniczni umieścili bibliotekę w katalogu /usr/local/lib. lib. To jest katalog /usr/local/

/usr/local/lib common-lisp python2.7 python4.2 site_ruby libfluxcap.a libfluxcap.la To właśnie w nim została zainstalowana biblioteka hfcal.

libhfcal.so Znajduje się w nim tak że wiele innych plików.

Na tym samym komputerze, w katalogu /usr/local/include, został także umieszczony plik nagłówkowy biblioteki hfcal.

libmrfusion.so

/usr/local/include python2.7

. To jest katalog usr/local/include

python4.2 fluxcap.h hfcal.h

l.h. To jest plik nagłówkowy hfca

mrfusion.h Znajduje się w nim także wiele innych plików.

   '(

Pracownicy techniczni preferują instalowanie bibliotek w tych katalogach, gdyż jest to rozwiązanie najbardziej zbliżone do przyjętych standardów. Komputer jest przygotowany do pracy w polskich warunkach, jednak teraz trzeba to zmienić.

376

Rozdział 8.

Biblioteki statyczne i dynamiczne

System należy zaktualizować, by mógł być używany w siłowni w kraju, do którego jest wysyłany, czyli w USA. Oznacza to, że kod obsługujący prezentację danych na wyświetlaczach bieżni musi zostać zmieniony na wersję z tekstami w języku angielskim oraz używającą innych jednostek — mil i funtów. To jest kod do zastosowania w siłowniach w USA.

#include #include

void display_calories(float weight, float distance, float coeff) { printf(“Weight: %3.2f lbs\n”, weight * 2.2046); printf(“Distance: %3.2f miles\n”, distance / 1.609344);

Ten kod wyświetla informacje w milach i funtach, a komunikaty są w języku angielskim.

printf(“Calories burned: %4.2f cal\n”, coeff * weight * distance); }

hfcal_UK.c

Ten plik jest umieszczony w katalogu /home/ebrown.

Oprogramowanie, które jest już zainstalowane na komputerze, musi używać tej nowej wersji kodu. Ponieważ aplikacja korzysta z tego kodu w formie biblioteki dynamicznej, jedyne, co musisz zrobić, to skompilować go w katalogu /usr/local/lib. Załóżmy też, że przeszedłeś do tego samego katalogu, w którym znajduje się plik hfcal_USA.c, oraz że dysponujesz uprawnieniami niezbędnymi do zapisu plików we wszystkich katalogach. Poniżej zapisz polecenia, które musisz wydać, by skompilować nową wersję biblioteki. .......................................................................................................................... .......................................................................................................................... .......................................................................................................................... Jeśli główna aplikacja do obsługi bieżni treningowych nosi nazwę /opt/apps/treadmill, to jakie polecenie musisz wydać, by ją uruchomić? .......................................................................................................................... .......................................................................................................................... ..........................................................................................................................

jesteś tutaj  377

Ćwiczenie rozwiązane Rozwiązanie długiego ćwiczenia Ekipa Siłowni Rusz Głową ma zamiar wysłać bieżnie treningowe do USA. Wbudowany serwer działa pod kontrolą systemu Linux i jest już na nim zainstalowana polska wersja kodu. Pracownicy techniczni umieścili bibliotekę w katalogu /usr/local/lib. lib. To jest katalog /usr/local/

/usr/local/lib common-lisp python2.7 python4.2 site_ruby libfluxcap.a libfluxcap.la To właśnie w nim została zainstalowana biblioteka hfcal.

libhfcal.so Znajduje się w nim tak że wiele innych plików.

Na tym samym komputerze, w katalogu /usr/local/include, został także umieszczony plik nagłówkowy biblioteki hfcal.

libmrfusion.so

/usr/local/include python2.7

. To jest katalog usr/local/include

python4.2 fluxcap.h hfcal.h

l.h. To jest plik nagłówkowy hfca

mrfusion.h Znajduje się w nim także wiele innych plików.

   '(

Pracownicy techniczni preferują instalowanie bibliotek w tych katalogach, gdyż jest to rozwiązanie najbardziej zbliżone do przyjętych standardów. Komputer jest przygotowany do pracy w polskich warunkach, jednak teraz trzeba to zmienić.

378

Rozdział 8.

Biblioteki statyczne i dynamiczne

System należy zaktualizować, by mógł być używany w siłowni w kraju, do którego jest wysyłany, czyli w USA. Oznacza to, że kod obsługujący prezentację danych na wyświetlaczach bieżni musi zostać zmieniony na wersję z tekstami w języku angielskim oraz używającą innych jednostek — mil i funtów.

#include #include

void display_calories(float weight, float distance, float coeff) { printf(“Weight: %3.2f lbs\n”, weight * 2.2046); printf(“Distance: %3.2f miles\n”, distance / 1.609344); printf(“Calories burned: %4.2f cal\n”, coeff * weight * distance); }

hfcal_UK.c

Oprogramowanie, które już jest zainstalowane na komputerze, musi używać tej nowej wersji kodu. Ponieważ aplikacja korzysta z tego kodu w formie biblioteki dynamicznej, jedyne, co musisz zrobić, to skompilować go w katalogu /usr/local/lib. Załóżmy też, że przeszedłeś do tego samego katalogu, w którym znajduje się plik hfcal_USA.c, oraz że dysponujesz uprawnieniami niezbędnymi do zapisu plików we wszystkich katalogach. Poniżej zapisz polecenia, które musisz wydać, by skompilować nową wersję biblioteki. Musisz skompilować kod źródłowy do postaci kodu obiektowego. A następnie przekonwertować plik obiektowy na obiekt współużytkowany.

Nie musisz korzystać z opcji -I, gdyż plik nagłówkowy został umieszczony w standardowym katalogu.

gcc -c -fPIC hfcal_USA.c -o hfcal.o ................................................................................................................ gcc -shared hfcal.o -o /usr/local/lib/libhfcal.so ................................................................................................................

Jeśli główna aplikacja do obsługi bieżni treningowych nosi nazwę /opt/apps/treadmill, to jakie polecenie musisz wydać, by ją uruchomić?

Nie musisz określać wartości zmiennej środowiskowej LD_LIBRARY_PATH, ponieważ biblioteka jest umieszczona w katalogu standardowym.

/opt/apps/treadmill .......................................................................................................................... Czy zwróciłeś uwagę na to, że zarówno plik nagłówkowy, jak i biblioteka zostały zainstalowane w katalogach standardowych? Dzięki temu nie musiałeś korzystać z flagi -I podczas kompilowania programu ani określać wartości zmiennej środowiskowej LD_LIBRARY_PATH podczas jego uruchamiania.

jesteś tutaj  379

Jazda próbna

Jazda próbna Skoro już zaktualizowałeś bibliotekę przeznaczoną dla programu działającego na bieżniach treningowych używanych w siłowniach w USA, wypróbujmy ją na urządzeniu przeznaczonym do użytkowania w Polsce. To jest jedna ze zmodyfikowanych polskich bieżni, korzystająca z oryginalnej wersji biblioteki libhfcal.so: To jest polska wersja bieżni treningowej.

Aplikacja treadmill rozpoczyna działanie w momencie włączenia urządzenia, a zatem po pewnym czasie jej wyświetlacz może przedstawiać następujące wyniki:

Waga: 53.25 kg Dystans: 15.13 km Spalono kalorii: 749.27 cal

Program treadmill na polskiej bieżni treningowej dynamicznie łączy się z wersją biblioteki libhfcal.so skompilowanej na podstawie polskiej wersji programu hfcal.

A co z bieżniami przeznaczonymi do użytku w USA? 380

Rozdział 8.

Biblioteka 4) #7  (T

9 ##

Biblioteki statyczne i dynamiczne

W bieżniach przeznaczonych na rynek amerykański instalowany jest ten sam program treadmill, jednak w ich przypadku ponownie skompilowałeś bibliotekę libhfcal.so, używając przy tym kodu źródłowego umieszczonego w pliku hfcal_USA.c.

To jest bieżnia przeznaczona do użytku w siłowniach w USA.

To jest dokładnie ten sam program treadmill.

9 ##

Ta wersja programu jest łączona z amerykańską wersją biblioteki hfcal.

Biblioteka 4) #7  ;=V

W tym przypadku po przebiegnięciu podobnego dystansu na ekranie urządzenia będą widoczne następujące wyniki: Waga jest wyświetlana w funtach.

Dystans jest . wyświetlany w milach

Weight: 117.54 lbs Distance: 9.40 miles Calories burned: 749.27 cal

świetlane Komunikaty są wyim. lsk gie an u yk jęz w

Spalone kalorie są wciąż wyświetlane jako kalorie.

Udało się. Chociaż program treadmill nigdy nie był ponownie kompilowany, to jednak potrafił dynamicznie użyć kodu z nowej biblioteki. Biblioteki dynamiczne ułatwiają zmienianie kodu w trakcie działania programu. Możemy zatem aktualizować aplikację bez konieczności jej ponownego kompilowania. Gdybyśmy mieli kilka programów, wspólnie używających tego samego fragmentu kodu, to moglibyśmy zaktualizować je wszystkie za jednym zamachem. Teraz, kiedy już wiesz, jak tworzyć biblioteki dynamiczne, Twoje możliwości jako programisty C stały się zdecydowanie większe.

jesteś tutaj  381

Statyczne i dynamiczne

Pogawędki przy kominku Temat dzisiejszej dyskusji: Dwóch doskonale znanych zwolenników programowania modularnego         



$

Cóż, jak sądzę, oboje zgodzimy się, że tworzenie kodu z mniejszych fragmentów jest dobrym rozwiązaniem. Oczywiście. To jest naprawdę sensowne rozwiązanie, prawda? Oczywiście. Sprawia, że łatwiej można zarządzać kodem. Tak. Miłe i duże programy. Duże? Tak. Miłe DUŻE programy ze ściśle określonymi zależnościami. To chyba nie jest najlepszy pomysł. Co masz na myśli, stary druhu? Uważam, że programy powinny się składać z wielu małych plików łączonych ze sobą wyłącznie podczas działania programu. Cóż… … to jest bardzo… nie, chyba nie mówisz poważnie? Mówię serio. Co? Dużo niezależnych plików? Łączonych ze sobą niezależnie od nas? Ja to nazywam łączeniem dynamicznym. Ale to przecież… przecież… to prosta droga do chaosu! To oznacza, że później można zmienić zdanie. Wszystko powinno być poprawnie określone od samego początku. Jednak to nie zawsze jest możliwe. Wszystkie duże programy powinny korzystać z łączenia dynamicznego.

382

Rozdział 8.

Biblioteki statyczne i dynamiczne



$

Wszystkie programy? Tak uważam. A co z jądrem systemu Linux? Czy ono jest wystarczająco duże? A przecież jest ono stworzone z użyciem… Łączenia statycznego. Tak, wiem o tym. W tym przypadku jesteś górą. Łączenie statyczne może nie jest takie luźne i swobodne, ale wiesz co? Programy łączone statycznie są przynajmniej łatwe w użyciu. To pojedyncze pliki. Chcesz taki program zainstalować? Wystarczy skopiować plik wykonywalny. Żadnego horroru związanego z zarządzaniem mnóstwem bibliotek DLL. Cóż, mamy nieco odmienne poglądy. Czy mogę jakoś wpłynąć na ciebie, byś zmienił zdanie? Nie. Hm… czyżbyś chciał mi powiedzieć, że twoja opinia w tym względnie jest statyczna?

CELNE SPOSTRZEŻENIA Q

Biblioteki dynamiczne są dołączane do programów podczas ich działania.

Q

Biblioteki dynamiczne są tworzone z jednego lub większej liczby plików obiektowych.

Q

Na niektórych komputerach tworzenie bibliotek dynamicznych wymaga zastosowania opcji -fPIC.

Q

Opcja -fPIC sprawia, że kompilator generuje kod przesuwny.

Q

W wielu systemach opcję –fPIC można pominąć.

Q

Także opcja kompilatora -shared powoduje utworzenie biblioteki dynamicznej.

Q

Biblioteki dynamiczne mają różne nazwy w różnych systemach operacyjnych.

Q

Możemy znacznie ułatwić sobie życie, umieszczając biblioteki dynamiczne w katalogach standardowych.

Q

W przeciwnym razie może się okazać, że konieczne będzie odpowiednie ustawienie zmiennej środowiskowej PATH lub LD_LIBRARY_PATH.

jesteś tutaj  383

Nie ma głupich pytań Nie istnieją

głupie pytania

P: Dlaczego występują tak znaczne różnice pomiędzy bibliotekami dynamicznymi w poszczególnych systemach operacyjnych?

O: Systemy operacyjne lubią

optymalizować wykorzystywane sposoby wczytywania bibliotek dynamicznych, dlatego też każdy z nich ma swoje własne wymagania dotyczące tych bibliotek.

P: Próbowałem zmienić nazwę

biblioteki, modyfikując nazwę jej pliku, ale kompilator nie był już w stanie jej odnaleźć. Dlaczego tak nie można?

O: Kiedy kompilator generuje bibliotekę

dynamiczną, umieszcza jej nazwę wewnątrz jej pliku. Jeśli zmienisz nazwę pliku biblioteki, to nazwa zapisana wewnątrz niego nie będzie pasowała do jego faktycznej nazwy, co zmyli kompilator. Jeśli chcesz zmienić nazwę biblioteki, musisz ją ponownie skompilować.

P: Dlaczego w Cygwin można

stosować tak wiele różnych konwencji nazewnictwa bibliotek dynamicznych?

O: Cygwin umożliwia kompilowanie

oprogramowania pisanego dla systemu UNIX na komputerach działających w systemie Windows. Ponieważ Cygwin tworzy środowisko uniksowe, wykorzystuje wiele konwencji stosowanych w tym systemie operacyjnym. Na przykład preferuje nadawanie bibliotekom rozszerzeń .a, nawet jeśli są to dynamiczne biblioteki DLL.

384

Rozdział 8.

P: Czy dynamiczne biblioteki

tworzone z użyciem Cygwin są prawdziwymi bibliotekami DLL?

O: Tak. Ponieważ jednak zależą one

od systemu Cygwin, umożliwienie zastosowania ich w kodzie, który z Cygwin nie korzysta, będzie wymagało nieco pracy.

P: Dlaczego kompilator MinGW

obsługuje ten sam format nazw bibliotek dynamicznych co Cygwin?

O: Ponieważ oba te projekty są ze sobą

ściśle powiązane i znaczna część ich kodu jest wspólna. Podstawowa różnica polega na tym, że programy tworzone przy użyciu MinGW mogą działać na komputerach, na których Cygwin nie jest zainstalowany.

P: Dlaczego w systemie Linux

w plikach wykonywalnych nie są zapisywane ścieżki dostępu do bibliotek? Dzięki temu nie trzeba by było korzystać ze zmiennej środowiskowej LD_LIBRARY_PATH.

O: To był wybór projektowy twórców

systemu. Rezygnacja z zapisywania ścieżki dostępu zapewnia bowiem znacznie większą kontrolę nad tym, z której wersji biblioteki program będzie korzystał — a to jest bardzo przydatne podczas pisania nowych bibliotek.

P: Dlaczego Cygwin nie korzysta ze zmiennej LD_LIBRARY_PATH podczas poszukiwania bibliotek?

O: Ponieważ musi korzystać z bibliotek

DLL systemu Windows. A te są poszukiwane z wykorzystaniem katalogów podanych w zmiennej środowiskowej PATH.

P: Które rozwiązanie jest lepsze?

Łączenie statyczne czy dynamiczne?

O: To zależy. Łączenie statyczne oznacza niewielkie, szybkie pliki wykonywalne programów, które łatwo można przenosić z komputera na komputer. Z kolei łączenie dynamiczne daje większe możliwości konfigurowania programu podczas jego działania.

P: Jeśli kilka programów korzysta z tej samej biblioteki dynamicznej, to czy jest ona wczytywana więcej niż jeden raz, czy też jest współużytkowana w pamięci?

O: To zależy od systemu operacyjnego.

Niektóre z systemów wczytują odrębne wersje bibliotek dla każdego wykonywanego procesu. Inne wczytują tylko jedną kopię biblioteki i współużytkują ją, by zaoszczędzić pamięć.

P: Czy biblioteki dynamiczne są

najlepszym sposobem konfigurowania aplikacji?

O: Zazwyczaj łatwiejszym rozwiązaniem

jest skorzystanie z plików konfiguracyjnych. Jeśli jednak mamy zamiar korzystać z jakichś urządzeń zewnętrznych, to zazwyczaj będziemy musieli używać przy tym bibliotek dynamicznych, spełniających rolę sterowników.

Biblioteki statyczne i dynamiczne

 X

Twój niezbędnik C Masz już za sobą rozdział 8., a do swojego niezbędnika dodałeś biblioteki statyczne i dynamiczne. Kompletną listę porad można znaleźć w dodatku B.

wa Dyrekty e d lu c in w # je plikó poszuku wych o k nagłów ach g w katalo wych, do r a d n a t s k takich ja de. clu /usr/in

Polecenie ar tworzy archiwum biblioteki zawierające pliki obiektowe.

Opcja -I lo listy standa g do rdowych katalogów, w których są poszukiwan e pliki nagłów kowe.

Opcja -l konsoliduje program z plikami znajdującymi się w katalogach standardowych, takich jak /usr/lib. Opcja -L dodaje wskazany katalog do listy standardowych katalogów zawierających biblioteki.

Polecenie gcc -shared konwertuje plik obiektowy na bibliotekę dynamiczną.

Biblioteki dynamiczne są łączone w trakcie działania programu.

Archiwa są k bibliote wane o d li o s n o k ie. n z c y stat

Biblioteki dynamiczne mają rozszerzenia .so, .dylib, .dll lub .dll.a.

Biblioteki e dynamiczn e n ż ró mają różnych w y w z a n h c a m syste ych. operacyjn

Archiwa bibliotek mają nazwy podobne do libcostam.a.

jesteś tutaj  385

386

Rozdział 8.

Nazwisko:

Data:

2. laboratorium C

OpenCV )      %0% %   (   %   (  %   %-  (  "& )    %-  ( (   &4   "%   % *  %  % %   &!      5         "    %    "    "      (% ('&         Ciebie;

  " %- ( &

C Lab

387

C# Lab

387

OpenCV Specyfikacja: zmień komputer w wykrywacz włamań Wyobraź sobie, że komputer mógłby pilnować Twojego domu, kiedy Cię nie ma, i informować Cię, kto się po nim kręcił. Okazuje się, że dzięki wbudowanej kamerze internetowej oraz bibliotece OpenCV jest to możliwe! Oto, co masz zamiar stworzyć.

Wykrywacz włamań Twój komputer będzie bezustannie kontrolował swoje otoczenie, korzystając przy tym z wbudowanej kamery internetowej. Kiedy wykryje jakiś ruch, zapisze na dysku aktualny obraz rejestrowany przez kamerę. A jeśli zapiszesz ten plik na dysku sieciowym bądź skorzystasz z usługi synchronizacji danych, takiej jak Dropbox, to będziesz dysponował rejestrem włamań z natychmiastowymi powiadomieniami.

Włamywacz

Kamera internetowa

O! Włamywacz ulatniający się z zapasami kawy! Muszę to zarejestrować…

  

Kiedy komputer za pośrednictwe m kamery internetowej wykryje ruch wbudowanej …

388

…zapisze w pliku to, co zobaczył.

OpenCV OpenCV OpenCV jest biblioteką o otwartym kodzie źródłowym, służącą do komputerowego przetwarzania obrazu. Pozwala ona pobierać dane z kamery podłączonej do komputera, przetwarzać je, analizować w czasie rzeczywistym i podejmować decyzje w zależności od tego, co komputer zobaczy. Co więcej, to wszystko można zrobić, pisząc kod w języku C. Biblioteka OpenCV jest dostępna w wersjach przeznaczonych dla systemów Windows, Linux oraz komputerów Mac. Stronę wiki biblioteki OpenCV możesz znaleźć na tej stronie: http://opencv.willowgarage.com/wiki/FullOpenCVWiki

Instalacja OpenCV Bibliotekę OpenCV można zainstalować w systemie Windows, Linux oraz na komputerach Mac. Instrukcję instalacji można znaleźć na stronie o podanym adresie wraz z odnośnikami do strony pozwalającej na pobranie najnowszych stabilnych wersji biblioteki: http://opencv.willowgarage.com/wiki/InstallGuide Po zainstalowaniu OpenCV na komputerze powinien się pojawić katalog o nazwie samples. Warto do niego zajrzeć. Dodatkowo można także znaleźć odnośniki do poradników umieszczonych na stronie wiki projektu OpenCV. W celu wykonania tego laboratorium będziesz musiał trochę poczytać i dokładniej zapoznać się z tą biblioteką. Jeśli chcesz dokładniej poznać bibliotekę OpenCV, polecamy, byś sięgnął po książkę Learning OpenCV, napisaną przez Gary’ego Bradskiego oraz Adriana Kaehlera (wydaną przez wydawnictwo O’Reilly).

Według nas książka Learning OpenCV jest inspirująca.

389

OpenCV Co powinien robić Twój kod Twój kod powinien realizować następujące zadania:

Pobierać dane wejściowe z kamery podłączonej do komputera Musisz operować na danych pobieranych w czasie rzeczywistym, przesyłanych do komputera z kamery. Dlatego pierwszą rzeczą, którą powinieneś się zająć, jest pobieranie tych danych. Biblioteka OpenCV udostępnia funkcję, która pomoże Ci wykonać to zadanie. Ta funkcja to cvCreateCameraCapture(0). Zwraca ona wskaźnik do struktury typu CvCapture. Wskaźnik ten stanowi Twoją „gorącą linię” z kamerą i będziesz go używał do pobierania obrazów. Pamiętaj, by sprawdzać błędy, na wypadek gdyby Twój komputer nie był w stanie odnaleźć podłączonej do niego kamery. Jeśli nawiązanie połączenia z kamerą nie będzie możliwe, wywołanie cvCreateCameraCapture(0) zwróci NULL.

Przechwyć obraz z kamery Ostatni obraz zarejestrowany przez kamerę możesz odczytać, używając funkcji cvQueryFrame(). Wymaga ona przekazania wskaźnika typu CvCapture. Funkcja ta zwraca wskaźnik na ostatni zarejestrowany obraz, zatem Twój kod będzie się zapewne zaczynał podobnie do tego przedstawionego poniżej:

  

CvCapture* webcam = cvCreateCameraCapture(0); if (!webcam)

To oznacza: „Nie mogłam znaleźć kamery internetowej”.

&' M67 M 6`6Q+`'& while (1) { Odczytujemy obraz z kamery.

Pętla ma być wykonywana w nieskończoność.

IplImage* image = cvQueryFrame(webcam); if (image) { Jeśli uda się odczytać obraz, to tutaj będziesz musiał go jakoś przetworzyć.

} } Jeśli uda Ci się ustalić, że na obrazie jest złodziej, to obraz możesz zapisać w pliku za pomocą poniższego wywołania: Nazwa pliku obrazka

Obraz pobrany z kamery internetowej

cvSaveImage("somefile.jpg", image, 0);

390

Jeśli nie chcesz, by zap obraz był czarno-biały isany , parametr ma mieć wa to ten rtość 0.

OpenCV Jeśli będę się ruszał baaarrrdzo pooowoooli, to mnie nie zauważą…

Wykryj włamywacza Teraz dochodzimy do naprawdę inteligentnej części kodu. W jaki sposób masz określić, czy na zdjęciu widać włamywacza? Jednym ze sposobów jest próba wykrycia ruchu na obrazie. Biblioteka OpenCV udostępnia funkcje pozwalające na tworzenie przepływu optycznego Farnebacka. Przepływ optyczny porównuje dwa obrazy i określa przesunięcia poszczególnych pikseli. Sposób wykonania tego zadania będziesz musiał opracować samemu. Najprawdopodobniej będziesz chciał skorzystać z funkcji cvCalcOpticalFlowFarneback(), by za jej pomocą porównać dwa obrazy pobrane z kamery i utworzyć przepływ optyczny. Następnie będziesz musiał napisać kod, który zmierzy wielkość ruchu pomiędzy obydwoma obrazami. Jeśli wielkość ta okaże się większa od jakiejś wartości progowej, to będziesz wiedział, że przed kamerą porusza się coś dużego.

Zadbaj o puste pole obserwacji Zapewne nie chcesz, by tuż po uruchomieniu programu kamera zarejestrowała, jak wychodzisz z domu. Dlatego warto, by program rozpoczynał analizę obrazu z pewnym opóźnieniem, które pozwoli Ci spokojnie opuścić obserwowane pomieszczenie.

Zadanie opcjonalne: prezentuj aktualny obraz z kamery Podczas testów przeprowadzanych w naszym laboratorium stwierdziliśmy, że bardzo przydatne jest sprawdzanie aktualnego obrazu rejestrowanego przez kamerę. W tym celu otworzyliśmy okno i wyświetliliśmy w nim obraz zarejestrowany przez kamerę. Korzystając z biblioteki OpenCV, w prosty sposób można utworzyć okno — wystarczy użyć poniższego wywołania: 6ƒ  *+ $A

Aby wyświetlić aktualny obrazek w oknie, użyj następującego wywołania: 6ƒV7© *+ $

391

OpenCV Końcowy produkt Będziesz wiedział, że Twój program jest gotowy, kiedy komputer będzie w stanie automatycznie robić zdjęcia osób próbujących się koło niego prześlizgnąć.

No to klapa!

Dlaczego na tym poprzestawać? Jesteśmy pewni, że będziesz miał całą masę fascynujących pomysłów na zastosowanie biblioteki OpenCV.

392

processes and system calls

Czas, abyś także Ty był jak ninja C Ostatnia część książki opisuje zaawansowane zagadnienia. Ponieważ masz zamiar zajmować się niektórymi z bardziej zaawansowanych możliwości języka C, musisz się upewnić, że na swoim komputerze będziesz miał do nich dostęp. Jeśli używasz systemu operacyjnego Linux bądź masz komputer Mac, to wszystko w porządku. Jeśli jednak używasz systemu Windows, to musisz zainstalować Cygwin. Kiedy już się z tym uporasz, odwróć stronę i przejdź przez bramę…

jesteś tutaj  393

394

Rozdział 8.

9.(     

Przekraczanie granic Dzięki, Tadku. Od kiedy nauczyłeś mnie, jak robić wywołania systemowe, nie oglądam się już za siebie. Tadku? Tadku, jesteś tam?

Czas, by zacząć myśleć kreatywnie. Dowiedziałeś się już, że można tworzyć złożone aplikacje, łącząc niewielkie programy narzędziowe wywoływane z poziomu wiersza poleceń. Ale co zrobić, gdy będziemy chcieli korzystać z innych programów we własnym kodzie? W tym rozdziale dowiesz się, jak korzystać z usług systemowych, by tworzyć i kontrolować działanie procesów. Dzięki temu Twoje programy uzyskają dostęp do poczty elektronicznej, WWW oraz wszelkich innych narzędzi zainstalowanych na komputerze. Po przeczytaniu tego rozdziału zyskasz moc pozwalającą wykraczać poza język C.

to jest nowy rozdział  395

system()

Wywołania systemowe są Twoją gorącą linią z systemem operacyjnym Programy pisane w języku C polegają na systemie operacyjnym podczas wykonywania praktycznie wszystkich operacji. Wykonują wywołania systemowe, kiedy chcą skomunikować się z komponentami sprzętowymi komputera. Wywołania systemowe to wywołania funkcji należących do jądra systemu operacyjnego. Z tych możliwości korzysta przeważająca większość kodu dostępnego w standardowej bibliotece języka C. Za każdym razem gdy wywołujesz funkcję printf(), by wyświetlić coś na ekranie, gdzieś za kulisami zostanie wykonane wywołanie systemowe nakazujące wysłanie łańcucha znaków na ekran komputera.

Oczywiście. Zaraz to wszystko zrobię.

Chcę wyświetlić to w wierszu poleceń, następnie odtworzyć ten utwór, a potem wysłać do sieci ten komunikat…

Przyjrzyjmy się przykładom wywołań systemowych. Zaczniemy od wywołania funkcji, która (całkiem adekwatnie) nosi nazwę system(). Funkcja system() wymaga przekazania jednego parametru będącego łańcuchem znaków i reprezentującego polecenie. Funkcja wykonuje to polecenie w taki sposób, jak gdyby zostało ono wpisane w wierszu poleceń: system(“dir D:”); system(“gedit”);

To polecenie wyświetli zawartość dysku D:. To polecenie, wydane w systemie Linux, spowoduje uruchomienie edytora.

system(“say ’Koniec wiersza’”);

To polecenie, wydane na komputerze Mac, spowoduje odczytanie łańcucha znaków.

Funkcja system() zapewnia prosty sposób wykonywania innych programów z poziomu naszego kodu — zwłaszcza jeśli tworzymy szybki prototyp programu i wolimy szybko wywołać program zewnętrzny, zamiast samemu mozolnie pisać niezbędny kod w C.

396

Rozdział 9.

Procesy i wywołania systemowe

Magnesiki z kodem '      -      -$        (  !               #  !             * system()        ( >   *    #     -$        !

          ( #include #include #include Ta funkcja zwraca łańcuch znaków char* now() zawierający aktualną datę i godzinę. { time_t t; time (&t); return asctime(localtime (&t)); }

&'?  M " ­  M67M67 2M"'& int main() { char comment[80]; char cmd[120]; .............. ( .............. , .............. , ..............); ........................... ........................... , ........................... , ........................... , ...................... ); system(cmd); return 0; }

sprintf

comment

"echo '%s %s' >> reports.log" fgets

comment

stdin

80

cmd

printf 120

now()

scanf

stdout

jesteś tutaj  397

Magnesiki poprzesuwane

Magnesiki z kodem. Rozwiązanie '      -      -$        (  !               #  !      

      * system()        ( .  !  #   *    #     -$        !

          ( #include #include #include char* now() { time_t t; time (&t); return asctime(localtime (&t)); } /* Master Control Program utility. ­6 676¤ "'& int main() { char comment[80]; Funkcja fgets służy do pobrania dowolnego łańcucha znaków.

char cmd[120]; fgets

Funkcja sprintf zapisze znaki w łańcuchu znaków.

(

sprintf

Znajduje się w niej miejsce na 80 znaków.

Tekst ma być zapisany w tablicy comment.

comment

80

, (

);

matowany łańcuch znaków

"echo '%s %s' >> reports.log"

,

comment

now()

,

stdin

, Sfor zostanie zapisany w tablicy cmd.

cmd

To jest wzorzec poleceń. To wywołanie powoduje wykonanie polecenia zapisanego w łańcuchu cmd.

,

Dane będą pobierane ze standardowego strumienia wejściowego, czyli z klawiatury.

Polecenie dopisze łańcuch znaków do pliku.

);

system(cmd); return 0; }

W pierwszej kolejności zostanie zapisany tekst w zmiennej comment.

Za nim zostanie zapisany znacznik czasu.

printf 120

398

Rozdział 9.

scanf

stdout

Procesy i wywołania systemowe

Jazda próbna Skompiluj program i przekonaj się, jak działa: To polecenie skompiluje program. A to polecenie uruchomi program. Wykonujemy program po raz drugi.

% &/3&%> H L& 

> gcc guard_log.c -o guard_log > ./guard_log Š  7 F¶C F!

 ! Y C: > ./guard_log ‰ `" CY  FY  C  : >

To jest komentarz.

Kolejny komentarz.

Teraz, jeśli zajrzysz do tego samego katalogu, w którym jest umieszczony program, zauważysz, że został w nim utworzony nowy plik o nazwie reports.log. H  M„M6 6 +2" To są znaczniki czasu.

To jest plik reports.log utworzony przez program.

Thu Oct 29 11:25:53 2015 ?Q  + M+  " Thu Oct 29 11:26:06 2015

Program zadziałał. Odczytuje on komentarze wpisywane w wierszu poleceń i wywołuje systemowe polecenie echo, by dodawać je na końcu pliku.

reports.log

Choć moglibyśmy napisać cały ten program w C, to jednak wywołując funkcję system(), udało się nam go uprościć i uruchomić bardzo małym nakładem pracy. Nie istnieją

głupie pytania

P: Czy funkcja system() zostaje wkompilowana w mój program?

O: Nie. Funkcja system() — podobnie jak wszystkie funkcje

systemowe — nie jest umieszczana w programie. Jej kod wchodzi w skład systemu operacyjnego.

P: A zatem, używając wywołania systemowego, wywołuję jakiś zewnętrzny fragment kodu, podobnie jak w przypadku bibliotek?

O: Coś w tym stylu. Jednak faktyczne szczegóły zależą od

używanego systemu operacyjnego. W niektórych systemach kod wywołań systemowych jest umieszczony w samym jądrze systemu, natomiast w innych — w jakiejś bibliotece dynamicznej.

jesteś tutaj  399

Zgrzyty

Wtem ktoś włamał się do systemu… Stosowanie funkcji system() ma pewną wadę. Korzystanie z niej jest szybkie i proste, lecz jednocześnie trochę niebezpieczne. Zanim dokładniej wyjaśnimy, na czym polegają problemy związane z tą funkcją, zobaczmy, co trzeba zrobić, by włamać się do programu.

UWAGA! UWAGA! Zabezpieczenia głównego systemu zostały przełamane!

Twój kod działa poprzez wygenerowanie łańcucha znaków zawierającego polecenia systemowe. Jego ogólna postać wygląda następująco: echo '

’ ‘

’ 6 6  ‘

' >> reports.log

Ale co się stanie, kiedy ktoś użyje polecenia o następującej postaci?

echo '

' && ls / && echo '

’ 6 6  ‘

' >> reports.log

Poprzez takie wstrzyknięcie kodu polecenia można sprawić, że program wykona całkowicie dowolny kod: Plik Edycja Okno Pomoc Rany

Użytkownik może użyć programu do wykonania dowolnego polecenia.

> ./guard_log ’ && ls / && echo ’ Applications Developer Library Network Space Paranoids Source >

System Users Volumes bin cores

Czy to duży problem? Jeśli użytkownik może uruchomić program guard_log, to równie łatwo może uruchomić jakiś inny program. Ale co zrobić, w przypadku gdy Twój program jest wywoływany przez serwer WWW? Albo jeśli przetwarza dane z pliku?

400

Rozdział 9.

dev etc home mach_kernel net

private sbin tmp usr var

To jest listing zawartości katalogu głównego.

Procesy i wywołania systemowe

Bezpieczeństwo nie jest jedynym problemem Poprzedni przykład wstrzykiwał kod wyświetlający zawartość katalogu głównego, jednak równie dobrze mógłby usuwać pliki lub uruchomić wirusa. Jednak Twoim zmartwieniem nie jest jedynie bezpieczeństwo.

Ì

    1   +  !   %^

To mogłoby uszkodzić polecenie.

Ì

    1  

 1  5x   %   [\   $1  ^

Ì

    1 $      1            

" 1 "^

Funkcja system() jest łatwa w użyciu, jednak w przeważającej większości przypadków będziemy potrzebowali rozwiązania o dokładnie określonej strukturze — jakiegoś sposobu wywoływania konkretnego programu z użyciem argumentów wiersza poleceń, a może nawet wymagającego ustawienia jakichś zmiennych środowiskowych.

Dla maniaków Czym jest jądro systemu? W większości systemów operacyjnych wywołania systemowe to funkcje umieszczone w jądrze systemu. Ale czym jest to jądro? Otóż nigdy nie zobaczymy go na ekranie, ale ono zawsze istnieje i zawsze kontroluje pracę komputera. To jądro jest najważniejszym z programów działających na komputerze, odpowiadającym za trzy zagadnienia:  

Żaden program nie może zostać wykonany na komputerze, jeśli jądro systemu nie wczyta go do pamięci. Jądro tworzy procesy i dba o to, by otrzymały zasoby niezbędne do działania. Jądro poszukuje także procesów, które są zbyt zachłanne lub uległy awarii.  !

Nasze komputery posiadają ograniczone zasoby pamięci, dlatego jądro systemu musi uważnie racjonować ilości pamięci przydzielane poszczególnym procesom. Jądro jest w stanie zwiększać wielkość pamięci wirtualnej poprzez niewidoczne dla użytkownika wczytywanie i zapisywanie bloków pamięci na dysku komputera. 2    

Jądro systemu korzysta ze sterowników urządzeń, by komunikować się z urządzeniami podłączonymi do komputera. Nasze programy mogą korzystać z klawiatury, ekranu oraz procesora graficznego, nie wiedząc zbyt wiele na ich temat, ponieważ jądro systemu komunikuje się z nimi w naszym imieniu. Wywołania systemowe to funkcje, z których nasze programy korzystają, by komunikować się z jądrem systemu.

jesteś tutaj  401

exec()

Funkcja exec() zapewnia większą kontrolę Kiedy wywołujesz funkcję system(), system operacyjny musi zinterpretować łańcuch polecenia i określić, które programy należy uruchomić oraz jak należy to zrobić. I właśnie na tym polega problem: system operacyjny musi zinterpretować łańcuch polecenia, a samemu mogłeś się już przekonać, jak łatwo przy tym o jakąś pomyłkę i problemy. A zatem rozwiązanie polega na usunięciu niejednoznaczności i precyzyjnym określeniu, który program chcemy uruchomić. I właśnie do tego celu służy funkcja exec().

Funkcje exec() zastępują aktualny proces Proces jest jedynie programem działającym w pamięci komputera. Jeśli wpiszesz polecenie taskmgr w systemie Windows lub ps –ef w większości innych systemów, to ujrzysz listę procesów aktualnie działających w systemie operacyjnym. System operacyjny rejestruje wszystkie te procesy, nadając każdemu z nich unikalny numer, nazywany identyfikatorem procesu (ang. process identifier, określany w skrócie jako PID). Funkcje exec() zastępują aktualny proces, uruchamiając jakiś inny program. Możesz przy tym określić, które argumenty wiersza poleceń oraz zmienne środowiskowe mają zostać użyte, a kiedy nowy program zostanie uruchomiony, będzie miał dokładnie ten sam PID co program działający wcześniej. Można by to porównać do biegu sztafetowego, w którym Twój program przekazuje swój proces innemu programowi.

W porządku, sendmail, przekazuję ci pałeczkę. To są dane, których potrzebujesz. Nie zawiedź mnie.

402

Rozdział 9.

Proces jest programem działającym w pamięci komputera.

Już się nimi zajmuję.

Procesy i wywołania systemowe

Istnieje wiele funkcji exec() Wraz z upływem czasu programiści stworzyli kilka wersji funkcji exec(). Każda z nich ma nieco inną nazwę i unikalny zestaw parametrów. Choć wersji jest wiele, to w rzeczywistości istnieją dwie grupy funkcji exec(): funkcje z listą argumentów oraz funkcje z tablicą argumentów.

Funkcje exec() są

Funkcje z listą argumentów: execl(), execlp(), execle()

nagłówkowym unistd.h.

dostępne w pliku

Funkcje należące do tej grupy pobierają argumenty wiersza poleceń w formie listy parametrów. Oto jej postać:

Ì



To może być pełna ścieżka dostępu do programu (w funkcjach execl() oraz execle()) bądź jedynie nazwa polecenia, którego należy poszukać (w przypadku funkcji execlp()). Niemniej pierwszy parametr informuje funkcję, jaki program ma uruchomić.

Ì

5     *

Musisz podać kolejno wszystkie argumenty wiersza poleceń, których chcesz użyć. Pamiętaj: zawsze pierwszym argumentem jest nazwa programu. Oznacza to, że pierwsze dwa parametry przekazywane do tej grupy funkcji exec() zawsze muszą być tym samym łańcuchem znaków.

Ì

3-//

Właśnie tak. Po ostatnim argumencie wiersza wywołania musisz umieścić NULL. W ten sposób informujesz funkcję, że nie ma już więcej argumentów.

Ì

'"  %

Odstępy na liście argumentów mogą zmylić MinGW.

Jeśli przekażesz dwa argumenty „on jest” oraz „żółwiem”, to programy uruchamiane w MinGW mogą rozpoznać trzy argumenty: „on”, „jest” oraz „żółwiem”.

& 

 1  [  \

Jeśli wywołujesz funkcję exec(), której nazwa kończy się na …e(), to możesz do niej przekazać także tablicę zmiennych środowiskowych. Jest to tablica łańcuchów znaków, takich jak: “MOC=4”, “SZYBKOSC=4”, “PORT=OPEN” itd. To są argumenty.

execL = LISTA argumentów.

execl("/home/flynn/clu", "/home/flynn/clu", "paranoids", "contract", NULL) Drugi parametr powinien być taki sam jak pierwszy.

execLP = LISTA argumentów H. oraz przeszukanie zmiennej PAT

To są argumenty.

Musisz zakończyć listę wartością NULL.

execlp("clu", "clu", "paranoids", "contract", NULL) To są argumenty.

execle("/home/flynn/clu", "/home/flynn/clu", "paranoids", "contract", NULL, env_vars) execLE = LISTA argumentów oraz zmienne ŚRODOWISKOWE.

env_vars to tablica łańcuchów znaków zawierających zmienne środowiskowe.

jesteś tutaj  403

Funkcje z tablicą argumentów

Funkcje z tablicą argumentów: execv(), execvp() oraz execve() Jeśli dysponujesz argumentami wiersza poleceń zapisanymi w tablicy, to być może uznasz, że łatwiejsze będzie skorzystanie z następujących funkcji: execV = tablica lub WEKTOR argumentów. execVP = tablica lub WEKTOR argumentów oraz przeszukanie zmiennej PATH.

execv(“/home/flynn/clu”, my_args); execvp(“clu”, my_args);

Argumenty muszą być zapisane w tablicy łańcuchów znaków — . w tym przypadku jest to my_args

Jedyną różnicą pomiędzy dwoma powyższymi funkcjami jest to, że execvp będzie poszukiwać programu w katalogach podanych w zmiennej środowiskowej PATH.

Jak zapamiętać funkcje exec() Możesz stwierdzić, której z funkcji exec() powinieneś użyć, określając jej nazwę. Do nazwy każdej z tych funkcji można dodać jedną lub dwie litery, którymi mogą być: l, v, p lub e. Litery te określają, których z możliwości funkcji chcesz używać. W ramach przykładu przeanalizujmy funkcję execle():

execle = exec + l + e = LISTA argumentów + zmienne ŚRODOWISKOWE Znak

Korzysta z Litery l oraz v zawsze są zapisywane przed literami p oraz e, a te dwie ostatnie występują opcjonalnie.

Użyj listy argumentów.

Listy argumentów

l

Tablicy (wektora) argumentów

v

  

p

     

e

Zastosuj zmienne środowiskowe.

l Wszystkie funkcje exec() zaczynają się od exec.

exec

Użyj tablicy lub wektora argumentów.

v

404

Rozdział 9.

W poszukiwaniu programu przejrzyj katalogi podane w zmiennej środowiskowej PATH.

p

Nie musisz dodawać ani litery p, ani e.

e

Procesy i wywołania systemowe

Przekazywanie zmiennych środowiskowych Każdy proces dysponuje grupą zmiennych środowiskowych. Są to te zmienne, które możesz wyświetlić, wpisując w wierszu poleceń set lub env, i które przekazują procesom użyteczne informacje, takie jak położenie katalogu domowego lub miejsce, w którym należy szukać poleceń. Programy pisane w C mogą odczytywać zmienne środowiskowe, korzystając z wywołania systemowego getenv(). Przykład zastosowania tej funkcji możesz zobaczyć w programie diner_info przedstawionym z prawej strony. Jeśli chcesz uruchomić program, korzystając przy tym z argumentów wiersza poleceń oraz ze zmiennych środowiskowych, to możesz to zrobić w następujący sposób:

Zbiór zmiennych środowiskowych możesz przygotować jako tablicę wskaźników na łańcuchy znaków.

Każda zmienna w tym zbiorze ma postać: nazwa=wartość.

#include #include int main(int argc, char *argv[]) {  ? +@ D$ƒ9A:  V@ D$getenv(“SOK”)); return 0; } Funkcja getenv() dostępna w stdlib.h pozwala odczytywać zmienne środowiskowe.

diner_info.c

Ostatnim elementem tablicy musi być NULL.

67'Mxƒ9:~›VYH~Q+¤M›ªRR€ 86 ›x››x››U›ªRRMxƒ

Funkcja execle pozwala przekazać listę argumentów oraz tablicę zmiennych środowiskowych.

Zmienna my_env zawiera zmienne środowiskowe.

Funkcja execle() określi argumenty wiersza wywołania oraz zmienne środowiskowe, a następnie zastąpi aktualny proces programem diner_info. % &/3&% ! 36

> ./my_exec_program ‰Y P K  "Y D F >

A co, jeśli pojawi się problem? Jeśli podczas wywoływania nowego programu pojawią się jakieś problemy, to będzie kontynuowane wykonywanie dotychczasowego procesu. Takie rozwiązanie jest przydatne, gdyż oznacza ono, że jeśli nie uda się nam uruchomić drugiego procesu, to będziemy mieli możliwość rozwiązania problemu i poinformowania użytkownika o tym, co poszło źle.

Jeśli przekazujesz zmienne środowiskowe w systemie Cygwin, to koniecznie przekaż także zmienną PATH.

'"  %

Cygwin wymaga, by podczas uruchamiania programu była określona wartość zmiennej środowiskowej PATH. Jeśli zatem używasz Cygwin, pamiętaj o dodaniu do zmiennych środowiskowych łańcucha takiego jak: PATH=/usr/bin.

jesteś tutaj  405

errno

Większość wywołań systemowych zawodzi w taki sam sposób Ponieważ wywołania systemowe zależą od czegoś, co znajduje się poza naszym programem, mogą zawieść w sposób, którego nie jesteśmy w stanie kontrolować. Aby rozwiązać ten problem, większość wywołań systemowych zawodzi w taki sam sposób.

Gwarantowany Standard Niepowodzenia

W ramach przykładu przyjrzyjmy się wywołaniu funkcji execle(). Bardzo łatwo możemy zauważyć, kiedy jej wywołanie się nie powiodło. Jeśli się ono uda, aktualny program przestaje działać. A zatem jeśli po wywołaniu funkcji exec() program wykona jakikolwiek kod, to będzie to oznaczało, że pojawił się jakiś problem: Jeśli wywołanie funkcji execle() się powiedzie, to ten wiersz kodu nigdy nie zostanie wykonany.

86 x$ x$ U$ªRRMxƒ  VM„ x 6‚ `$

Jednak sama informacja, czy wywołanie się powiodło, nie wystarcza. Zazwyczaj będziemy chcieli wiedzieć, dlaczego wywołanie się nie udało. Właśnie z tego powodu większość wywołań systemowych stosuje się do złotej zasady niepowodzenia. Zmienna errno jest zmienną globalną zdefiniowaną w pliku nagłówkowym errno.h wraz z grupą standardowych wartości błędów, takich jak:

Ta wartość nie jest dostępna we wszystkich systemach.

Złota zasada niepowodzenia

*

Posprzątaj po sobie najlepiej, jak potrafisz.

*

Zapisz wartość błędu w zmiennej errno.

*

Zwróć –1.

EPERM=1

Operation not permitted (niedozwolona operacja)

ENOENT=2

No such file or directory (brak takiego pliku lub katalogu)

ESRCH=3

No such process (nie ma takiego procesu)

&'*++&,-./ 0!4"5#

Można by sprawdzać zmienną errno i porównywać ją z każdą z tych wartości bądź pobrać standardowy komunikat o błędzie, używając do tego funkcji strerror() dostępnej w pliku string.h: puts(strerror(errno));

Funkcja strerror() zamienia numer błędu na komunikat tekstowy.

A zatem jeśli system nie jest w stanie odnaleźć programu, który chcemy uruchomić, i przypisze zmiennej errno wartość ENOENT, to powyższe wywołanie wyświetli następujący komunikat: No such file or directory

406

Rozdział 9.

Procesy i wywołania systemowe

Ćwiczenie

W różnych systemach używane są różne polecenia zwracające informacje o konfiguracji sieci. W systemie Linux i na komputerach Mac służy do tego program /sbin/ifconfig, natomiast w systemie Windows dostępne jest polecenie ipconfig przechowywane gdzieś na ścieżce poleceń. Poniższy program próbuje uruchomić program /sbin/ifconfig, a jeśli mu się to nie uda, próbuje wykonać polecenie ipconfig. Żadne z wykonywanych poleceń nie wymaga przekazywania jakichkolwiek argumentów. Zastanów się dobrze. Której wersji funkcji exec() użyjesz?

kowych potrzebujesz? Których plików nagłów

#include ................................... ................................... ...................................

int main() {

To wywołanie ma wykonywać polecenie /sbin/ifconfig. Co powinniśmy sprawdzać?

To wywołanie ma wykonywać polecenie ipconfig i sprawdzać, czy to się udało.

if (................................................) if (execlp(..........................................) {   676@ $""""""""""""""" return 1; } return 0;

Jak sądzisz, co należy wpisać w tym miejscu?

}

jesteś tutaj  407

Ćwiczenie rozwiązane

Rozwiązanie ćwiczenia

W różnych systemach używane są różne polecenia zwracające informacje o konfiguracji sieci. W systemie Linux i na komputerach Mac służy do tego program /sbin/ifconfig, natomiast w systemie Windows dostępne jest polecenie ipconfig przechowywane gdzieś na ścieżce poleceń. Poniższy program próbuje uruchomić program /sbin/ifconfig, a jeśli mu się to nie uda, próbuje wykonać polecenie ipconfig. Żadne z wykonywanych poleceń nie wymaga przekazywania jakichkolwiek argumentów. Zastanów się dobrze. Której wersji funkcji exec() użyjesz?

#include

ystać Potrzebujesz tego pliku, by korz (). exec z funkcji

#include

................................... #include

................................... #include ...................................

int main() {

Potrzebujesz tego pliku, by korzystać ze zmiennej errno. Ten plik pozwoli Ci wyświetlać komunikaty o błędach za pomocą funkcji strerror().

Użyj funkcji execl(), gdyż dysponujesz ścieżką do pliku programu.

Jeśli wywołanie funkcji execl() zwróci –1, oznacza to, że wystąpił błąd; wygląda zatem na to, że powinniśmy poszukać programu ipconfig.

execl("/sbin/ifconfig", "/sbin/ifconfig", NULL) == -1 if (................................................) Funkcja execlp() "ipconfig", "ipconfig", NULL) == -1 if (execlp(..........................................) { pozwoli nam poszukać strerror(errno)   676@ $""""""""""""""" programu ipconfig return 1; Sprawdzamy, czy wywołanie w katalogach Wywołanie funkcji zwróciło wartość –1, na wypadek określonych strerror() wyświetli } gdyby się ono nie powiodło. w zmiennej ewentualne błędy. środowiskowej return 0; PATH.

}

408

Rozdział 9.

Procesy i wywołania systemowe Nie istnieją

głupie pytania

P: Czy funkcja system() nie jest łatwiejsza do użycia niż P: Czy zawsze muszę sprawdzać wartość wynikową funkcje exec()?

zwracaną przez wywołania systemowe?

O: Owszem, jest. Jednak ze względu na konieczność interpretacji

O: Jeśli będziesz korzystał z wywołań systemowych i pominiesz

przekazanego łańcucha znaków korzystanie z funkcji system() jest trochę problematyczne, zwłaszcza jeśli używane w niej łańcuchy poleceń są tworzone dynamicznie.

sprawdzanie wartości wynikowych, to kod programu będzie krótszy. Jednak najprawdopodobniej jednocześnie będzie w nim więcej błędów. O błędach najlepiej jest myśleć od razu podczas pisania kodu. Dzięki temu późniejsze poszukiwanie i obsługiwanie błędów jest znacznie łatwiejsze.

P: Dlaczego istnieje tak dużo funkcji exec()? O: Wraz z upływem lat programiści chcieli uruchamiać procesy na P: Czy po wywołaniu funkcji exec() mogę wykonywać różne sposoby. Różne wersje funkcji exec() zostały stworzone, by zapewnić większą elastyczność.

w programie jakiekolwiek inne operacje?

O: Nie. Jeśli wywołanie funkcji exec() się powiedzie, to nastąpi zmiana procesu — zamiast dotychczasowego programu zostanie wykonany nowy. Oznacza to, że program wywołujący funkcję exec() przestanie działać w momencie jej wykonania.

CELNE SPOSTRZEŻENIA Q

Także wywołania systemowe exec() pozwalają wykonywać programy, a jednocześnie dają większą kontrolę nad sposobem ich wywoływania.

Wykonując wywołanie systemowe, uruchamiasz kod spoza swojego programu.

Q

Istnieje kilka różnych wersji wywołania systemowego exec().

Q

Funkcja system() to wywołanie systemowe służące do wykonania polecenia podanego w formie łańcucha znaków.

Q

Kiedy pojawia się problem, wywołania systemowe zazwyczaj, choć nie zawsze, zwracają wartość ¤A.

Q

Funkcja system() jest łatwa w użyciu, jednak może być przyczyną występowania błędów.

Dodatkowo zapisują numer błędu w zmiennej errno.

Q

Q

Wywołania systemowe to funkcje udostępniane przez system operacyjny.

Q

jesteś tutaj  409

Pomieszane komunikaty

Chłopaki z kawiarni Gwiezdny Pył napisali nowy program do generowania zamówień, który nazwali coffee: #include

(     

#include int main(int argc, char *argv[]) { char *w = getenv(“EXTRA”); if (!w) w = getenv(“FOOD”); if (!w) ~ƒ96¤A: char *c = getenv(“EXTRA”); if (!c) 6~ƒ96¤A:  @ @ D$6 return 0; }

Aby go wypróbować, napisali także program testowy. Czy potrafiłbyś dopasować fragmenty kodu do generowanych przez nie wyników?

#include #include #include

Tutaj umieść odpowiednie fragmenty kodu.

int main(int argc, char *argv[]){

 $ M /@ D$   return 1; } return 0; }

410

Rozdział 9.

Procesy i wywołania systemowe

Y    ) / [

Dopasuj fragment kodu do generowanego przez niego wyniku.

J #[

char *my_env[] = {“FOOD=kawa”, NULL}; T!:E!#:E!#! #`Œ00 F= 1'55D&'L  7#` C!+ H! Aby nawiązać połączenie z serwerem, będziesz potrzebował programu telnet. W większości systemów operacyjnych jest on instalowany domyślnie. Można to łatwo sprawdzić, wydając w wierszu poleceń komendę:

Zrób to sam!

  Jeśli nie dysponujesz tym programem, możesz go zainstalować na kilka sposobów, zależnie od używanego systemu:

466

Rozdział 11.

Cygwin: Uruchom program setup.exe środowiska i odszukaj w nim hasło telnet. Linux: Poszukaj hasła telnet w menedżerze pakietów. W wielu wersjach Linuksa menedżer pakietów nosi nazwę Synaptic. Mac: Jeśli nie dysponujesz programem telnet, to możesz go pobrać i zainstalować ze strony http://www.macports.org lub http://www.finkproject.org.

Gniazda i komunikacja sieciowa

Prezentacja serwera puk-puk Twój serwer będzie w stanie komunikować się z kilkoma klientami jednocześnie. Zarówno klienty, jak i serwer będą prowadziły konwersację o ściśle określonej strukturze nazywanej protokołem. W internecie jest stosowanych wiele różnych protokołów. Niektóre z nich są protokołami niskiego poziomu, określającymi, w jaki sposób mają być przesyłane siecią zera i jedynki. Przykładem takich protokołów jest protokół internetowy (ang. internet protocol, w skrócie IP). Istnieją także protokoły wysokiego poziomu; ich przykładem może być protokół przesyłu hipertekstu (ang. hypertext transfer protocol, w skrócie HTTP) określający, w jaki sposób przeglądarki komunikują się z serwerami WWW. Nasz serwer dowcipów będzie korzystał z protokołu wysokiego poziomu, nazywanego Internetowym Protokołem Puk-Puk (w skrócie IPPP).

Protokół jest konwersacją o określonej strukturze.

Serwer Klient oraz serwer prowadzą konwersację o ściśle określonej strukturze, nazywanej protokołem.

Klient telnet

Serwer będzie rozmawiał z kilkoma klientami jednocześnie.

Klient telnet Klient telnet

Klient oraz serwer będą wymieniały komunikaty o następującej postaci:

Serwer:

Klient:

Puk! Puk!

  &

Protokół wymaga, byś odpowiedział pytaniem: „Kto tam?”. Dlatego też natychmiast przerywam tę konwersację.

Oskar.

9 &               5(

Protokół zawsze określa zbiór reguł. Jeśli tylko zarówno klient, jak i serwer będą się do nich stosować, to wszystko będzie w porządku. Jeśli jednak któreś z nich złamie reguły, to konwersacja zapewne zakończy się bardzo szybko i gwałtownie.

jesteś tutaj  467

Bla, bla

PNAR — jak serwery komunikują się z internetem Kiedy programy pisane w języku C muszą się komunikować ze światem zewnętrznym, używają strumieni danych, by odczytywać i zapisywać bajty. Korzystałeś już ze strumieni danych podłączonych do plików oraz ze standardowych strumieni wejściowych i wyjściowych. Jeśli jednak masz zamiar napisać program komunikujący się z siecią, będziesz potrzebował nowego rodzaju strumienia nazywanego gniazdem. listener_d jest deskryptorem gniazda.

6 !79FE! :+;

Będziesz potrzebował tego pliku nagłówkowego.

...   =75! ‰³=‹`,“K…Š=K“’,‘ ”) != -1) { read_in(connect_d, buf, sizeof(buf)); Odczyt danych przesłanych przez klienta if (strncasecmp(“Kto tam?”, buf, 12)) say(connect_d, “Powinieneś zapytać: ’Kto tam?’!”); Sprawdzenie odpowiedzi else { podanej przez użytkownika. if (say(connect_d, “Oskar\r\n> ”) != –1) { read_in(connect_d, buf, sizeof(buf)); if (strncasecmp(“Jaki Oskar?”, buf, 10)) say(connect_d, “Powinieneś zapytać: ’Jaki Oskar?’!\r\n”); else say(connect_d, “Oskar, głupie pytanie, na które dostaniesz głupią odpowiedź.\r\n”); } } } Zamknięcie pomocniczego gniazdatem. close(connect_d); używanego do komunikacji z klien } return 0; }

jesteś tutaj  481

Jazda próbna

Jazda próbna Jeśli już napisałeś kod serwera, nadszedł czas, byś go skompilował i uruchomił. Plik Edycja Okno Pomoc JestemSerwerem

Konsola serwera

;C!! =1:!D =1 > ./ikkp_server   Y! 

Serwer czeka na połączenie, a zatem otwórz drugie okno konsoli i połącz się z serwerem przy użyciu programu telnet: Konsola klienta

Serwer może zrobić Ci kawał, ale co się stanie, kiedy złamiesz protokół i prześlesz do niego nieprawidłową odpowiedź? Konsola klienta

Plik Edycja Okno Pomoc JestemKlientem

; &%J:color, 2.0f); al_draw_line(-10, -20, 5, -20, a->color, 2.0f); al_draw_line(5, -20, 20, -10, a->color, 2.0f); al_draw_line(20, -10, 20, -5, a->color, 2.0f); al_draw_line(20, -5, 0, 0, a->color, 2.0f); al_draw_line(0, 0, 20, 10, a->color, 2.0f); al_draw_line(20, 10, 10, 20, a->color, 2.0f); al_draw_line(10, 20, 0, 15, a->color, 2.0f); al_draw_line(0, 15, -20, 20, a->color, 2.0f);

531

Blasteroidy Ruch asteroid Asteroidy poruszają się po ekranie wzdłuż linii prostych. Choć poruszają się po liniach prostych, to bezustannie obracają się wokół własnej osi. Jeśli asteroida wyleci poza jedną krawędź ekranu, natychmiast pojawia się z powrotem po jego drugiej stronie.

Kiedy asteroida zostanie trafiona strzałem Kiedy asteroida zostanie trafiona strzałem z działa naszego statku kosmicznego, natychmiast rozpada się na dwie części. Każda z nich będzie o połowę mniejsza od rozmiaru oryginalnej asteroidy. Kiedy asteroida zostanie trafiona i podzielona kilka razy, całkowicie znika z ekranu. Każde trafienie asteroidy powiększa wynik gracza o 100 punktów. Musisz zdecydować, w jaki sposób będziesz rejestrował dane o zbiorze wszystkich asteroid prezentowanych na ekranie. Może stworzysz jedną wielką tablicę? A może użyjesz listy połączonej?

Status gry Istnieje kilka informacji, które musisz wyświetlać na ekranie: liczba statków, jakimi gracz jeszcze dysponuje, oraz jego wynik. Kiedy wszystkie statki gracza zostaną zniszczone, wielkimi literami na samym środku ekranu masz wyświetlić napis „Gra skończona!”.

532

Blasteroidy Przekształcenia umożliwiające przesuwanie obiektów W trakcie gry będziesz musiał animować obiekty i przesuwać je po ekranie. Statek kosmiczny będzie musiał latać, a asteroidy obracać się i powoli przesuwać, a nawet zmieniać wielkość. Obroty, przesunięcia oraz skalowanie to operacje, których wykonanie wymaga przeprowadzenia całkiem dużej ilości operacji matematycznych. Na szczęście biblioteka Allegro jest wyposażona w całkiem sporą liczbę wbudowanych przekształceń. Podczas rysowania obiektu, takiego jak nasz statek kosmiczny, prawdopodobnie będziesz chciał się skoncentrować na narysowaniu go wokół środka układu współrzędnych. Środkiem układu współrzędnych jest lewy górny wierzchołek ekranu; ma on współrzędne (0, 0). Współrzędne x zmieniają się w poziomie, natomiast współrzędne y — w pionie. Korzystając z przekształceń, możesz przesunąć środek układu współrzędnych w miejsce, którego wymaga aktualnie rysowany obiekt, a następnie obrócić tak, by był skierowany w odpowiednią stronę. Kiedy już to zrobisz, wystarczy narysować obiekt wokół środka układu współrzędnych, a znajdzie się on w odpowiednim miejscu ekranu. Oto na przykład jeden ze sposobów narysowania na ekranie naszego statku kosmicznego:

void draw_ship(Spaceship* s) { ŠRR¼X­YxF­ŠVµY­¬  xMx  Z  al_rotate_transform(&transform, DEGREES(s->heading)); x x  Z  ¤‘ 8 ¤‘ M al_use_transform(&transform); al_draw_line(-8, 9, 0, -11, s->color, 3.0f); al_draw_line(0, -11, 8, 9, s->color, 3.0f); al_draw_line(-6, 4, -1, 4, s->color, 3.0f); al_draw_line(6, 4, 1, 4, s->color, 3.0f); }

533

Blasteroidy Produkt końcowy Kiedy skończysz, nadejdzie czas, byś zagrał w swoje Blasteroidy!

Swoją grę możesz usprawniać i rozbudowywać na wiele sposobów. Na przykład niby dlaczego nie mógłbyś jej zintegrować z OpenCV?

534

Wyjeżdżamy z miasta…

Doskonale bawiliśmy się w Cewicach! Przykro nam, że już wyjeżdżasz, ale nie ma nic lepszego od praktycznego wykorzystania zdobytej wiedzy i umiejętności. Wciąż jednak w tej książce czeka na Ciebie kilka dodatkowych skarbów oraz, oczywiście, indeks, który też możesz przeczytać. Jednak później nadejdzie czas, byś zebrał wszystkie nowe pomysły i zastosował je w praktyce. Bardzo byśmy chcieli dowiedzieć się, jak sobie radzisz, więc koniecznie napisz kilka słów na naszej witrynie Head First Labs — http://www.headfirstlabs.com — i daj znać, jak CI idzie pisanie w C!

jesteś tutaj  535

536

Rozdział 12.

Dodatek A(   ,

Dziesięć najważniejszych rzeczy (których nie opisaliśmy) O rany, spójrzcie tylko na tę smakowitą ucztę, którą zostawiliśmy…

Nawet po tym wszystkim, co już napisaliśmy, wciąż jeszcze pozostaje coś, czego nie wyjaśniliśmy. Jest jeszcze parę zagadnień, o których według nas powinieneś się dowiedzieć. Nie czulibyśmy się dobrze, gdybyśmy zupełnie je zignorowali. Wymagają one choć krótkiego wyjaśnienia, a my naprawdę nie chcemy oddawać Ci do rąk książki, której nie byłbyś w stanie podnieść bez solidnego treningu na lokalnej siłowni. A zatem zanim odłożysz tę książkę na półkę, przeczytaj zamieszczone tu informacje.

to jest dodatek  537

Operatory

1. Operatory W tej książce stosowaliśmy kilka operatorów, takich jak podstawowe operatory arytmetyczne — +, -, * oraz /. Jednak język C udostępnia także wiele innych operatorów, które mogą znacznie ułatwić nam życie.

Inkrementacja i dekrementacja Inkrementacja i dekrementacja to operacje powiększania i zmniejszania wartości o 1. Są to bardzo popularne operacje wykonywane w kodzie programów, zwłaszcza w przypadku gdy są w nim umieszczone pętle inkrementujące wartość pewnego licznika. Język C udostępnia cztery proste wyrażenia ułatwiające wykonywanie inkrementacji i dekrementacji: Powiększa wartość zmiennej i o a następnie zwraca nową wartość1, tej zmiennej.

++i

Powiększa wartość zm i o 1, a następnie zw iennej rac wartość tej zmiennej. a starą

i++

Zmniejsza wartość zmiennej i o 1, a następnie zwraca nową wartość tej zmiennej.

--i

iennej Zmniejsza wartość zm a rac i o 1, a następnie zw nej. starą wartość tej zmien

i--

Każde z tych wyrażeń zmienia wartość zmiennej i. Położenie operatorów ++ bądź -- określa, czy zostanie zwrócona oryginalna, czy też nowa wartość zmiennej. Oto przykład: int i = 3;

3, a i == 4. Po wykonaniu tej instrukcji j ==

int j = i++;

Operator trójargumentowy Co można zrobić, w przypadku gdy chcemy użyć jednej wartości, jeśli pewien warunek będzie spełniony, oraz drugiej — jeśli nie będzie? if (x == 1) return 2; else return 3;

Język C udostępnia operator trójargumentowy, który umożliwia skrócenie powyższego kodu do następującej postaci: return (x == 1) ? 2 : 3; W pierwszej kolejności jest zapisywany warunek.

538

Dodatek A

Następnie wartość, która zostanie użyta, jeśli warunek będzie spełniony.

I w końcu wartość, która zostanie użyta, jeśli warunek nie będzie spełniony.

Pozostałości

Zabawy bitami Język C może być używany do tworzenia programów operujących na bardzo niskim poziomie, dlatego też udostępnia kilka operatorów pozwalających wyznaczać nowe sekwencje bitów.

Operator Opis ~a

Wartości poszczególnych bitów zmiennej a zostaną zmienione na przeciwne.

a&b

Bitowa koniunkcja (operacja AND) wartości zmiennych a i b.

a | b

Bitowa alternatywa (operacja OR) wartości zmiennych a i b.

a^b

Bitowa alternatywa wykluczająca (XOR) wartości zmiennych a i b.

>

Przesunięcie bitów w prawo (zmniejszanie).

Operatora make fred !!7:!D7

zostało To polecenie kompilacji gram pro ez prz ne wygenerowa trukcji, make bez jawnych ins jak to ma zrobić.

To jest reguła niejawna.

Jest to możliwe dzięki temu, że program  jest wyposażony w zbiór wbudowanych przepisów. Więcej informacji o tym programie możesz znaleźć na stronie: http://www.gnu.org/software/make/

jesteś tutaj  547

Narzędzia programistyczne

8. Narzędzia programistyczne Jeśli programujesz w C, to najprawdopodobniej bardzo zależy Ci na wydajności działania i stabilności aplikacji. Jeśli do kompilacji kodu używasz gcc, to zapewne będziesz się także chciał przyjrzeć innym dostępnym narzędziom programistycznym GNU.

gdb GNU Project Debugger (gdb) pozwala analizować skompilowany program podczas jego działania. Jest on nieoceniony, jeśli próbujesz wykryć i poprawić jakiś nieznośny błąd. gdb można używać zarówno z poziomu wiersza poleceń, jak i w zintegrowanych środowiskach programistycznych, takich jak Xcode lub Guile. http://sourceware.org/gdb/download/onlinedocs/gdb/index.html

gprof Jeśli Twój kod nie działa tak szybko, jak planowałeś, to być może warto go poddać profilowaniu. Narzędzie GNU Profiler (gprof) jest w stanie pokazać, które fragmenty kodu są najwolniejsze, dzięki czemu będziesz mógł je odpowiednio zmodyfikować. gprof pozwala skompilować zmodyfikowaną wersję kodu, która po zakończeniu działania programu generuje raport wydajności. Po jego utworzeniu program gprof wykonywany z poziomu wiersza poleceń pozwala przeanalizować taki raport i wskazać najwolniejsze fragmenty kodu. http://sourceware.org/binutils/docs-2.22/gprof/index.html

gcov Kolejnym narzędziem profilującym jest GNU Coverage (gcov). W odróżnieniu od programu gprof, który normalnie jest używany do sprawdzania wydajności działania kodu, gcov pozwala określać, które fragmenty kodu programu zostały wykonane, a które nie. Zagadnienie to jest ważne, gdy próbujemy zautomatyzować testy pisanego programu, gdyż w takim przypadku musimy mieć pewność, że obejmują one wszystkie fragmenty kodu, który planujemy testować. http://gcc.gnu.org/onlinedocs/gcc/Gcov.html

548

Dodatek A

Pozostałości

9. Tworzenie graficznego interfejsu użytkownika W żadnym z programów pisanych w głównych rozdziałach tej książki nie stworzyłeś żadnego graficznego interfejsu użytkownika. W laboratoriach korzystałeś natomiast z bibliotek Allegro oraz OpenCV do napisania programów, które mogły wyświetlać na ekranie bardzo proste okna. Problem polega na tym, że w każdym z dostępnych systemów operacyjnych graficzny interfejs użytkownika jest tworzony całkowicie inaczej.

Linux — GTK W systemie Linux jest dostępnych wiele bibliotek służących do tworzenia graficznego interfejsu użytkownika, a jedną z najbardziej popularnych jest GIMP toolkit (GTK+). http://www.gtk.org/ Biblioteka GTK+ jest dostępna w wersjach nie tylko dla Linuksa, lecz także dla systemów Windows oraz Mac OS, choć zdecydowanie najczęściej jest używana właśnie w systemie Linux.

Windows System Windows posiada wbudowane, bardzo zaawansowane biblioteki do obsługi graficznego interfejsu użytkownika. Programowanie aplikacji o graficznym interfejsie użytkownika w tym systemie jest bardzo wyspecjalizowanym zagadnieniem, dlatego zanim będziesz w stanie łatwo tworzyć takie aplikacje, będziesz musiał poświęcić nieco czasu na poznanie szczegółów działania interfejsów programowania aplikacji (API) systemu Windows. Coraz więcej aplikacji przeznaczonych dla systemu Windows jest pisanych w językach bazujących na C, takich jak C# oraz C++. Wprowadzenie do zagadnień pisania programów dla systemu Windows można znaleźć na stronie: http://www.winprog.org/tutorial/

Mac OS — Carbon Na komputerach Macintosh jest używany graficzny interfejs użytkownika (GUI) o nazwie Aqua. Na tych komputerach programy o graficznym interfejsie użytkownika pisane w C korzystają z bibliotek o nazwie Carbon. Jednak nowszy sposób tworzenia takich programów dla komputerów Mac bazuje na wykorzystaniu bibliotek Cocoa, przeznaczonych dla kolejnego języka bazującego na C — tak zwanego Objective-C. Teraz, kiedy już dotarłeś do końca tej książki, nadarza Ci się doskonała okazja, by zacząć naukę Objective-C. My, w Head First Labs, uwielbiamy książki i kursy programowania na komputery Mac dostępne na Big Nerd Ranch: http://www.bignerdranch.com/

jesteś tutaj  549

Materiały

10. Materiały Poniżej przedstawiliśmy listę niektórych popularnych książek i witryn poświęconych programowaniu w języku C: Brian W. Kernighan, Dennis M. Ritchie, Język ANSI C. Programowanie. Wydanie II, Helion, Gliwice 2010.

Ta książka zdefiniowała oryginalny język C, jej egzemplarz ma na półce każdy programista piszący w C. Samuel P. Harbison, Guy L. Steele Jr., C: A Reference Manual, Prentice Hall, New Jersey 2002.

To doskonały podręcznik, który na pewno będziesz chciał mieć pod ręką podczas pisania programów w C. Peter van der Linden, Expert C Programming, Prentice Hall, New Jersey 1994.

Jeśli interesują Cię bardziej zaawansowane zagadnienia związane z programowaniem w C, to warto sięgnąć po tę doskonałą książkę Petera van der Lindena. Steve Oualline, Practical C Programming, O’Reilly, Sebastopol 2003.

Ta książka przedstawia praktyczne szczegóły pisania oprogramowania w języku C.

Witryny WWW Informacje dotyczące standardów można znaleźć na stronie: http://pubs.opengroup.org/onlinepubs/9699919799/ Jeśli szukasz dodatkowych poradników poświęconych programowaniu w C, zajrzyj na witrynę: http://www.cprogramming.com/ Ogólne informacje encyklopedyczne na temat programowania w C można znaleźć na witrynie: http://www.cprogrammingreference.com/ Ogólne poradniki poświęcone programowaniu w C można znaleźć na stronie: http://www.crasseux.com/books/ctutorial/

550

Dodatek A

Dodatek B Zagadnienia programowania w C

Powtórka z całego materiału

Czy kiedykolwiek pomyślałeś, że fajnie by było, gdyby te wszystkie wspaniałe fakty dotyczące C zostały zebrane w jednym miejscu? W tym dodatku zostały zebrane wszystkie zagadnienia oraz zasady związane z pisaniem w języku C, które przedstawiliśmy w całej książce. Przejrzyj je wszystkie i przekonaj się, czy je zapamiętałeś. Przy każdym fakcie został podany numer rozdziału, z którego on pochodzi, a zatem nie będziesz miał problemów z odnalezieniem dodatkowych informacji, gdybyś ich potrzebował. Możesz nawet wyciąć te strony z książki i przykleić je sobie na ścianie.

to jest dodatek  551

Podstawy

  552

Dyrektywa #include dołącza zewnętrzny kod wykonujący różne operacje, na przykład operacje wejścia-wyjścia.

Dodatek B

 

 

 

Do łączenia warunków można używać operatorów && oraz ||.

 

 

Instrukcje if wykonują kod, jeśli określony warunek jest spełniony.

 

Proste instrukcje można porównać do poleceń.

 

Podstawy Instrukcje blokowe są umieszczane pomiędzy nawiasami klamrowymi — { oraz }.

Instrukcja switch w wydajny sposób sprawdza różne wartości jednej zmiennej.

Każdy program musi zawierać funkcję main().

Pliki źródłowe powinny mieć rozszerzenie .c.

 

Pętla while wykonuje kod dopóty, dopóki jest spełniony określony warunek.

 

 

 

W wierszu poleceń możesz umieścić &&, by wykonać program wyłącznie wtedy, gdy uda się go skompilować.

count++ oznacza, że do wartości count należy dodać 1.

 

 

 

Przed uruchomieniem programu napisanego w C trzeba go skompilować.

 

 

Powtórka materiału

gcc jest najpopularniejszym kompilatorem języka C.

Parametr -o określa plik wynikowy.

count-- oznacza, że od wartości count należy odjąć 1.

Pętle do-while wykonują kod przynajmniej raz.

Pętle for są bardziej zwartym sposobem tworzenia pętli. jesteś tutaj  553

Wskaźniki

554

Zmienne lokalne są przechowywane na stosie.

Dodatek B

 1

 1

Zawartość pamięci określonej wskaźnikiem a można odczytać, używając wyrażenia *a.

&x jest nazywany wskaźnikiem na zmienną x.

 1

&x zwraca adres zmiennej x.

 1

 1

Wystarczy użyć łańcucha znaków do zainicjowania tablicy, by łańcuch został skopiowany.

Zmienna x będąca wskaźnikiem na znaki jest deklarowana jako char *x.

Zmienne tablicowe mogą być używane jak wskaźniki.

 1

Wywołanie scanf("%i", &x) pozwala użytkownikowi na bezpośrednie podanie wartości zmiennej x.

 1

 1  1

   

fgets(buf, rozmiar, stdin) jest prostszym sposobem wczytywania tekstów.

Powtórka materiału

 178

 178

Funkcja strcat() łączy ze sobą dwa łańcuchy znaków.

Funkcja strcpy() kopiuje jeden łańcuch znaków do drugiego.

Plik nagłówkowy string.h zawiera użyteczne funkcje do operacji na łańcuchach znaków.

Tablicę tablic tworzy się, używając zapisu char strings [...][...].

 178

Funkcja strstr(a, b) zwraca adres łańcucha b wewnątrz łańcucha a.

 178  178

Tablica łańcuchów znaków jest tablicą tablic.

 178  178

Literały łańcuchowe są zapisywane w pamięci tylko do odczytu.

 178

 178  1

 

Funkcja strcmp() porównuje dwa łańcuchy znaków.

Funkcja strchr() określa położenie znaku wewnątrz łańcucha.

Funkcja strlen() określa długość łańcucha znaków.

jesteś tutaj  555

Strumienie danych

556

Standardowy strumień błędów jest oddzielnym strumieniem wyjściowym przeznaczonym do wyświetlania komunikatów o błędach.

Własne strumienie danych można tworzyć, używając wywołania fopen(“plik”, tryb).

Dodatek B

Domyślnie zawartość standardowego strumienia wyjściowego jest wyświetlana na ekranie.