Laboratorium 3: Preprocesor i funkcje ze zmienną liczbą argumentów. mgr inż. Arkadiusz Chrobot

Podobne dokumenty
znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Wstęp do programowania. Wykład 1

Katedra Elektrotechniki Teoretycznej i Informatyki. wykład 12 - sem.iii. M. Czyżak

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

Co nie powinno być umieszczane w plikach nagłówkowych:

Funkcje. Spotkanie 5. Tworzenie i używanie funkcji. Przekazywanie argumentów do funkcji. Domyślne wartości argumentów

Programowanie w języku Python. Grażyna Koba

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

Laboratorium 3: Tablice, tablice znaków i funkcje operujące na ciągach znaków. dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

1 Podstawy c++ w pigułce.

zastępować zarezerwowane słowa lub symbole innymi,

Wstęp do Programowania, laboratorium 02

Język C, tablice i funkcje (laboratorium)

1 Podstawy c++ w pigułce.

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 2. Karol Tarnowski A-1 p.

Język C, tablice i funkcje (laboratorium, EE1-DI)

Podstawy Programowania C++

1. Które składowe klasa posiada zawsze, niezależnie od tego czy je zdefiniujemy, czy nie?

Wstęp do informatyki- wykład 9 Funkcje

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

Podstawy programowania komputerów

Programowanie Proceduralne

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 3. Karol Tarnowski A-1 p.

Laboratorium Informatyka (I) AiR Ćwiczenia z debugowania

Wstęp do informatyki- wykład 11 Funkcje

Podstawy programowania skrót z wykładów:

Wstęp do programowania

Fragment wykładu z języka C ( )

Wykład 8: klasy cz. 4

Wykład VII. Programowanie. dr inż. Janusz Słupik. Gliwice, Wydział Matematyki Stosowanej Politechniki Śląskiej. c Copyright 2014 Janusz Słupik

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

Funkcja (podprogram) void

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Programowanie w języku C++ Grażyna Koba

Podstawy Informatyki. Algorytmy i ich poprawność

Podstawy programowania. Wykład 9 Preprocesor i modularna struktura programów. Krzysztof Banaś Podstawy programowania 1

Sposoby wykrywania i usuwania błędów. Tomasz Borzyszkowski

Podstawy programowania - 1

Tablice, funkcje - wprowadzenie

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Język C - podstawowe informacje

Wykład 5: Klasy cz. 3

Zaawansowane programowanie w języku C++ Funkcje uogólnione - wzorce

Laboratorium 10: Biblioteki. dr inż. Arkadiusz Chrobot

Podział programu na moduły

Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Ćwiczenie 1. Podstawy. Wprowadzenie do programowania w języku C. Katedra Metrologii AGH

3. Instrukcje warunkowe

7. Pętle for. Przykłady

Programowanie strukturalne i obiektowe. Funkcje

Skrypty i funkcje Zapisywane są w m-plikach Wywoływane są przez nazwę m-pliku, w którym są zapisane (bez rozszerzenia) M-pliki mogą zawierać

Jeśli chcesz łatwo i szybko opanować podstawy C++, sięgnij po tę książkę.

Zmienne, stałe i operatory

2 Przygotował: mgr inż. Maciej Lasota

Języki programowania zasady ich tworzenia

Delphi Laboratorium 3

Szablony funkcji i klas (templates)

Etapy kompilacji. Wykład 7. Przetwarzanie wstępne, str. 1. #define ILE for(i=0; i<ile; i++)...

Pytania sprawdzające wiedzę z programowania C++

Przedmiot: Informatyka w inżynierii produkcji Forma: Laboratorium Temat: Zadanie 4. Instrukcja warunkowa.

Program szkolenia VBA (VISUAL BASIC FOR APPLICATIONS) W EXCELU PODSTAWOWY.

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

#include <iostream> using namespace std; void ela(int); int main( ); { Funkcja 3. return 0; }

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Instrukcja laboratoryjna cz.0

Programowanie strukturalne i obiektowe

Programowanie hybrydowe C (C++) - assembler. MS Visual Studio Inline Assembler

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

Być może jesteś doświadczonym programistą, biegle programujesz w Javie,

Functionalization. Jeszcze o funkcjach i strukturze projektu. Marcin Makowski. 3 grudnia Zak lad Chemii Teoretycznej UJ

Zad. 5: Układ równań liniowych liczb zespolonych

Lab 9 Podstawy Programowania

Języki i techniki programowania Ćwiczenia 2

Wyrażenie include(sciezka_do_pliku) pozwala na załadowanie (wnętrza) pliku do skryptu php. Plik ten może zawierać wszystko, co może się znaleźć w

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

Programowanie I. O czym będziemy mówili. Plan wykładu nieco dokładniej. Plan wykładu z lotu ptaka. Podstawy programowania w językach. Uwaga!

Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Opracował Jan T. Biernat

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 5

Programowanie w C++ Wykład 8. Katarzyna Grzelak. 15 kwietnia K.Grzelak (Wykład 8) Programowanie w C++ 1 / 33

Laboratorium nr 5: Mnożenie wektorów i macierzy

8. Wektory. Przykłady Napisz program, który pobierze od użytkownika 10 liczb, a następnie wypisze je w kolejności odwrotnej niż podana.

Podstawy programowania C. dr. Krystyna Łapin

Podstawy programowania Laboratorium. Ćwiczenie 2 Programowanie strukturalne podstawowe rodzaje instrukcji

Wstęp do programowania INP003203L rok akademicki 2016/17 semestr zimowy. Laboratorium 1. Karol Tarnowski A-1 p.

Poprzedni wykład [ ] :

Programowanie obiektowe

Instrukcja do ćwiczenia P4 Analiza semantyczna i generowanie kodu Język: Ada

Języki programowania C i C++ Wykład: Typy zmiennych c.d. Operatory Funkcje. dr Artur Bartoszewski - Języki C i C++, sem.

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Laboratorium 1 Temat: Przygotowanie środowiska programistycznego. Poznanie edytora. Kompilacja i uruchomienie prostych programów przykładowych.

Programowanie w C++ Wykład 9. Katarzyna Grzelak. 14 maja K.Grzelak (Wykład 9) Programowanie w C++ 1 / 30

4. Funkcje. Przykłady

Mikrokontroler ATmega32. Język symboliczny

Język C++ zajęcia nr 2

Podstawy programowania (1)

Język C część 1. Sformułuj problem Zanalizuj go znajdź metodę rozwiązania (pomocny może byd algorytm) Napisz program Uruchom i przetestuj czy działa

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Elementy języka C. ACprogramislikeafastdanceonanewlywaxeddancefloorbypeople carrying razors.

Transkrypt:

Laboratorium 3: Preprocesor i funkcje ze zmienną liczbą argumentów mgr inż. Arkadiusz Chrobot 10 listopada 2010

1 Preprocesor Preprocesor jest programem uruchamianym przed właściwym procesem kompilacji programów napisanych w języku C. Jego działanie przypomina funkcjonowanie kompilatora, ale jest znacznie prostsze. Preprocesor analizuje kod źródłowy programu w poszukiwaniu przeznaczonych dla niego poleceń. Te polecenia mają postać dyrektyw lub makr. Celem wykonania preprocesora jest przekształcenie kodu źródłowego programu zgodnie z treścią tych poleceń. To przekształcenie może mieć na celu: włączenie do programu treści pliku nagłówkowego, wprowadzenie do kodu źródłowego wartości stałych, umieszczenie fragmentów kodu, które mogą być użyte np. do debugowania, dostosowanie kodu źródłowego programu do architektury na której będzie wykonywany kod wynikowy (np. użycie odpowiedniego dla danej platformy typu danych), wykonanie czynności, które powinny być zrobione przed rozpoczęciem kompilacji programu, ale z pewnych przyczyn nie mogą być wykonane ręcznie. Preprocesor w przeciwieństwie do kompilatora nie dokonuje sprawdzenia typów danych ani innych czynności mających na celu kontrolę poprawności programu. Powinniśmy więc pamiętać, aby do stosowania preprocesora podchodzić z umiarem. Jest on niewątpliwie bardzo pomocnym narzędziem, ale należy umieć korzystać z niego właściwie, aby uniknąć trudnych do znalezienia błędów. Dzięki preprocesorowi możliwa jest tzw. kompilacja warunkowa, która pozwala na otrzymanie różnych kodów wynikowych z tego samego kodu źródłowego. Kompilator gcc automatycznie wywołuje preprocesor przed rozpoczęciem właściwego procesu kompilacji, ale możemy też uruchomić preprocesor samodzielnie, z wiersza poleceń, za pomocą polecenia cpp podając jako jego argument wywołania nazwę pliku z kodem źródłowym. 2 Dyrektywy preprocesora Dyrektywy są to krótkie polecenia dla preprocesora. W praktyce już się z takimi poleceniami spotkaliśmy. Dyrektywa #include nakazuje włączenie do treści programu zawartości pliku nagłówkowego. Dyrektywa #define definiuje stałe. Preprocesor po napotkaniu takiej definicji zapamiętuje identyfikator i wartość za nią umieszczoną, a następnie analizuje resztę kodu i wszędzie tam, gdzie napotka ten identyfikator zastępuje go związaną z nim wartością. Wartownicy plików nagłówkowych są niczym innym jak instrukcją warunkową dla preprocesora. Taka instrukcja może się zaczynać od dyrektywy #ifndef (jeśli nie zdefiniowano) po które występuje nazwa identyfikatora, który powinien być zdefiniowany. Jeśli preprocesor dotychczas nie napotkał w kodzie źródłowym takiego znacznika, to powinien wykonać resztę instrukcji które występują w kolejnych wierszach po #ifndef. W przypadku wartowników plików nagłówkowych jest to definicja takiego samego znacznika jak występował w #ifndef, oraz szereg innych zwykłych instrukcji języka C lub preprocesora, które zależne są od przeznaczenia pliku nagłówkowego. Całość jest zakończona dyrektywą #endif. Zauważmy, że identyfikator definiowany w pliku nagłówkowym nie ma przypisanej żadnej wartości. Służy on jedynie za znacznik. Jeśli 1

preprocesor napotkał już wcześniej taki znacznik to znaczy, że plik nagłówkowy został już włączony do kodu źródłowego i nie należy włączać go ponownie. Jeśli nie to plik nagłówkowy jest włączany. Instrukcja warunkowa preprocesora ma jeszcze inną postać, która zaczyna się od dyrektywy #ifdef (jeśli zdefiniowano). Może również zawierać dyrektywę #else, która określa co preprocesor powinien zrobić jeśli warunek nie jest spełniony. Instrukcje warunkowe preprocesora można zagnieżdżać. Poniżej zamieszczony jest przykład programu w którym użyto instrukcji preprocesora. sample.c #i f d e f TEST p r i n t f ( Znacznik TEST zdefiniowany. \ n ) ; #e l s e p r i n t f ( Znacznik TEST n i e zdefinownay. \ n ) ; #e n d i f Jeśli skompilujemy program tak jak dotychczas to robiliśmy to po jego uruchomieniu otrzymamy komunikat, że znacznik TEST nie jest zdefiniowany. Jeśli jednak wywołamy kompilator tak: gcc -Wall -o sample sample.c -DTEST, to po uruchomieniu programu otrzymamy komunikat o tym, że znacznik został zdefiniowany. Podobny efekt można uzyskać umieszczając w kodzie źródłowym programu przez istniejącymi instrukcjami preprocesora wiersz #define TEST. Spora część programistów piszących w języku C uważa, że instrukcje preprocesora umieszczane wewnątrz funkcji utrudniają czytanie ich kodu. Można zapobiec takiemu utrudnieniu definiując osobne funkcje dla każdego przypadku instrukcji warunkowej, tak jak to pokazano na następnym przykładzie. sample2.c #i f d e f TEST p r i n t f ( Znacznik TEST zdefiniowany. \ n ) ; #e l s e p r i n t f ( Znacznik TEST n i e zdefinownay. \ n ) ; #e n d i f 2

3 Makra preprocesora O makrach preprocesora możemy myśleć jako o pewnego rodzaju podprogramach. Od zwykłych funkcji języka C różni je to, że nie są wywoływane w trakcie wykonywania programu, ale są rozwijane w miejscu wywołania przez preprocesor przed rozpoczęciem kompilacji. Przypominają pod tym względem funkcje poprzedzone słowem kluczowym inline. Mogą przyjmować dowolną liczbę parametrów, jednakże zgodność typów tych parametrów nie jest sprawdzana. Jest to główna wada makr preprocesora w porównaniu do funkcji inline. Struktura makr jest stosunkowo prosta. Ich definicję rozpoczyna dyrektywa #define po której występuje nazwa makra. Po nazwie makra można umieścić listę argumentów. Argumenty makr nie posiadają typów, a jedynie identyfikatory (nazwy). Po liście argumentów umieszcza się instrukcję w języku C, która ma być wykonana w ramach makra. Jeśli makro ma zawierać kilka instrukcji to umieszczamy te instrukcje w nawiasach klamrowych, przy czym każdy kolejny wiersz makra, łącznie z nagłówkiem, ale oprócz ostatniego, powinien być zakończony znakiem \ (backslash). Poniżej zamierszczony jest krótki program ilustrujący użycie makra preprocesora. sample3.c #d e f i n e PRINT( format, value ) \ p r i n t f ( ( format ), ( value ) ) ; \ p r i n t f ( \n ) ; \ i n t i = 2 4 ; double j = 5. 5 ; PRINT( %x, i ) ; PRINT( %f, j ) ; Umieszczone w kodzie makro ma za zadanie wypisać podaną wraz z odpowiednim formatowaniem wartość i przenieść kursor do następnego wiersza na ekranie. Efekt jego użycia możemy obejrzeć wywołując preprocesor w następujący sposób: cpp sample3.c. Makra nazywane są wymiennie makrodefinicjami. Definiując makro możemy jego treści nie zamykać w nawiasach klamrowych. Czasem takie postępowanie jest konieczne, ale często powoduje trudne do wykrycia błędy. Zaleca się aby, tak jak w przykładzie, umieszczać wewnątrz makr odwołania do argumentów w nawisach okrągłych. 4 Asercje Ciekawym i pożytecznym zastosowaniem makr są asercje nazywane również niezmiennikami. Zostały one wprowadzone do programowania przez R. Floyda i pierwotnym ich przeznaczeniem było formalne dowodzenie poprawności programów. Asercja jest po prostu warunkiem, który musi być spełniony przed i po wykonaniu 3

określonego fragmentu programu (np. pętli), aby uznać że ten fragment działa poprawnie. Szybko zauważono, że niezmienniki można wykorzystać w programowaniu nie tylko do dowodzenia poprawności programu przy użyciu aparatu matematycznego, ale również do testowania jego zachowania podczas wykonania. W języku C dostępne jest makro o nazwie assert(), które jako parametr przyjmuje warunek, który ma być prawdziwy podczas wykonania programu. Jeśli tak nie będzie to makro wypisuje na ekran krótki komunikat i przerywa działanie programu. Makra assert() można używać podczas testowania programu. Po opracowaniu jego wersji finalnej można je wyłączyć 1 definiując znacznik NDEBUG. Aby móc w ogóle skorzystać z makra assert() należy w kodzie źródłowym programu włączyć nagłówek assert.h #include <a s s e r t. h> sample4.c #d e f i n e SIZE 10 unsigned char t a b l i c a [ SIZE ] ; i n t i ; f o r ( i =0; i <200 SIZE ; i++) a s s e r t ( i <SIZE ) ; t a b l i c a [ i ]= i ; Przedstawiony przykładowy program w rażący sposób narusza ograniczenie rozmiaru zdefiniowanej tablicy. Po jego skompilowaniu możemy się przekonać, że makro assert() przerywając wykonanie programu nie dopuści aby zmienna indeksująca miała wartość większą lub równą rozmiarowi tablicy. Jeśli wyłączymy to marko za pomocą znacznika NDEBUG to program najprawdopodobniej zostanie zakończony w trybie krytycznym z komunikatem, że została naruszona ochrona pamięci. 5 Makra w debugowaniu Nie tylko makrodefinicja assert() może być użyta podczas debugowania programu. Możemy tworzyć również własne makra, których będzie można użyć podczas testowania programu, a następnie wyłączyć za pomocą znacznika NDEBUG lub innego, który sami zdefiniujemy. Niżej umieszczony został przykład takiego makra. Jego działanie można wyłączyć definiując znacznik NOTEST. Proszę zwrócić uwagę, na fakt, że po dyrektywie #else zostało zdefiniowane makro o takiej samej nazwie, ale puste. To oznacza, że jeśli znacznik NOTEST będzie zdefiniowany, to w miejsce makrodefinicji w kodzie źródłowym preprocesor nic nie wstawi. Na tym 1 Niektórzy informatycy, jak np. C.A.R Hoare uważają, że asercje powinno się zostawiać na wszelki wypadek również w wersjach finalnych programów. Takie działanie wydaje się być jak najbardziej uzasadnione. 4

polega właśnie wyłączenie działania makrodefinicji. sample5.c #i f n d e f NOTEST #d e f i n e PRINT( format, value ) \ p r i n t f ( ( format ), ( value ) ) ; \ p r i n t f ( \n ) ; \ #e l s e #d e f i n e PRINT( format, value ) #e n d i f i n t i ; f o r ( i =0; i <20; i ++) PRINT( %x, i ) ; 6 Funkcje o zmiennej liczbie argumentów Język C pozwala definiować funkcje o zmiennej liczbie argumentów (ang. variadic functions). Oznacza to, że liczba argumentów, które możemy przekazać do takiej funkcji ograniczona jest jedynie rozmiarem stosu. Na liście argumentów formalnych funkcji o zmiennej liczbie argumentów powinien znaleźć się choć jeden zwykły argument, który nie jest zmienną rejestrową ani wskaźnikiem. Jeśli takich argumentów będzie na liście więcej, to ta uwaga dotyczy ostatniego z nich. Po zwykłych argumentach w liście argumentów funkcji o zmiennej liczbie argumentów występuje przecinek i trzy kropki (...), które oznaczają, że funkcja oprócz wymienionych na liście argumentów może przyjąć dowolną liczbę innych argumentów. Aby obsłużyć dodatkowe argumenty wywołania przekazane w ten sposób do funkcji potrzebny jest nam typ va list i odpowiednie makra zdefiniowane w pliku nagłówkowym stdarg.h. Makro va start przyjmuje dwa argumenty, pierwszym jest zmienna typu va list, którą to makro inicjalizuje a drugim nazwa ostatniego argumentu spośród zwykłych argumentów umieszczonych na liście argumentów funkcji. To makro musi zostać wywołane przed wszystkimi innymi, które zostaną opisane. Zmienna typu va list jest listą wszystkich argumentów dodatkowych, przekazanych do funkcji. Makro va arg umożliwia poruszanie się po tej liście. Przyjmuje ono dwa parametry. Pierwszym jest lista parametrów dodatkowych, drugim typ wartości, która zostanie zwrócona. Wywoływanie makra va arg umieszczone jest najczęściej w pętli. Przy każdej iteracji pętli makro to zwraca wartość bieżącego argumentu na liście, przyjmując typ wartości tego argumentu taki, jaki został określony drugim argumentem wywołania marka oraz modyfikuje tak listę, aby po jego ponownym wywołaniu zwrócić następny element z tej listy. Po przejrzeniu całej listy należy wywołać makro va end, które 5

jako argument wywołania przyjmuje listę argumentów dodatkowych. Jeśli ponownie chcielibyśmy przejrzeć tę listę, to musimy jeszcze raz użyć wyżej wymienionych makr. Ostatnim makrem związanym z obsługą listy argumentów dodatkowych jest va copy. Służy ono, zgodnie ze swoją nazwą do tworzenia kopii listy argumentów. Nie należy do tego celu używać instrukcji przypisania! W przykładzie znajdującymi się poniżej przedstawiono program z funkcją, która liczy średnią wartości jej argumentów dodatkowych. Przyjęto w niej, że pierwszy argument przechowuje liczbę argumentów dodatkowych. W wywołaniu takiej funkcji można przekazać jej argumenty, które są literałami (wartościami), wyrażeniami, stałymi i zmiennymi. #include <stdarg. h> sample6.c f l o a t average ( i n t counter,... ) v a l i s t ap ; i n t next =0, sum=0, i=counter ; i f ( counter==0) r eturn 0. 0 ; v a s t a r t ( ap, counter ) ; while ( i ) next = va arg ( ap, i n t ) ; sum += next ; va end ( ap ) ; r eturn ( f l o a t )sum/ counter ; f l o a t r e s u l t = average ( 4, 1, 2, 3, 4 ) ; p r i n t f ( %f \n, r e s u l t ) ; Warto pamiętać, że w języku C prototyp funkcji zapisany np. tak: int funkcja() również oznacza funkcję, która przyjmuje zmienną liczbę argumentów. Jeśli chcemy stworzyć funkcję, która nie przyjmuje w ogóle argumentów, to powinniśmy jej prototyp zapisać następująco int funkcja(void). 6