kursC czesc008

background image

37

Programowanie

Elektronika dla Wszystkich

W poprzedniej czêœci zapoznaliœmy siê

z funkcj¹ printf. Pokaza³em ideê dzia³añ na

strumieniach i korzystaliœmy ze strumieni

domyœlnych. Dziœ kontynuujemy poznawanie

mo¿liwoœci biblioteki stdio.

Niestandardowe

wejœcie / wyjœcie

Do tej pory korzystaliœmy ze standardowego

wejœcia–wyjœcia. W przypadku

WinAVR wykorzystanie strumie-

ni innych ni¿ standardowe nie jest

du¿o trudniejsze, a mo¿e ju¿ dziœ

daæ nam spore korzyœci.

Zajrzyj do ramki ABC... C

Powi¹zanie strumienia z „pli-

kiem”. Zawarte tam informacje

oraz zdobyta do tej pory wiedza

powinny byæ wystarczaj¹ce do

zrozumienia niewielkich zmian

wprowadzonych do programów

z zesz³ego miesi¹ca. Oba progra-

my przerobione zosta³y zgodnie

z listingami 59 oraz 60. Jeœli

porównasz je z programami, które

uzyskaliœmy w zesz³ym miesi¹cu, stwierdzisz

zapewne, ¿e ró¿nica jest niewielka i ³atwa do

zrozumienia. Tym sposobem zaczêliœmy

pos³ugiwaæ siê strumieniami innymi ni¿

domyœlne.

G³êbiej w stdio

Obieca³em Ci, ¿e zaoszczêdzimy na pamiêci.

Jeœli skompilowa³eœ nowe wersje programów,

przyjrzyj siê wynikowi kompilacji. U mnie

pierwszy zaj¹³ co prawda kilka bajtów mniej,

drugi z kolei zaj¹³ kilka wiêcej. Stanowczo

nie to mia³em na myœli, pisz¹c o

oszczêdzaniu pamiêci. Gdzie tkwi

przyczyna tego, ¿e pozornie pros-

ty program z listingu 59 wci¹¿

zajmuje ponad 1kB pamiêci?

Sposobem, aby siê o tym przeko-

naæ jest kolejne usuwanie wywo³ywanych

funkcji. Umieszczenie znaku komentarza

przed funkcj¹ fputs powoduje zaoszczêdzenie

jedynie trochê ponad 100B. Dopiero zazna-

czenie komentarzem funkcji fdevopen zdra-

dza nam sekret. Wywo³anie tylko funkcji

pisz¹cej na wyjœcie spowodowa³o spadek

zajêcia pamiêci do 340B! Okazuje siê wiêc,

¿e du¿y udzia³ w zajêtoœci pamiêci programu

ma pozornie prosta funkcja tworz¹ca powi¹-

zanie. Problem przybli¿y trochê znajduj¹ca

siê na tej stronie ramka Jak to robi GCC.

Uwaga – #include i nazwy plików

Podczas rozmowy jeden z twórców biblioteki

z której dziœ korzystamy, zaleci³ mi stosowanie

znaków slash (/) zamiast backslash (\) w nazwach

plików. Podobno zapis taki jest kompatybilny

z wiêksz¹ iloœci¹ systemów. Nasz kompilator

„prze³knie” obie konwencje. Od dzisiejszego od-

cinka bêdê konsekwentnie stosowa³ nowy zapis.

PP

PP

rr

rr

oo

oo

gg

gg

rr

rr

aa

aa

m

m

m

m

oo

oo

w

w

w

w

aa

aa

nn

nn

ii

ii

ee

ee

pp

pp

rr

rr

oo

oo

cc

cc

ee

ee

ss

ss

oo

oo

rr

rr

óó

óó

w

w

w

w

w

w

w

w

jj

jj

êê

êê

zz

zz

yy

yy

kk

kk

uu

uu

CC

CC

czêœæ 8

Listing 59 - wykorzystanie funkcji fputs

#include <avr/io.h>

#include <stdio.h>

#include <avr/pgmspace.h>

#include „rs.h“

#include „harddef.h“

iinntt

main((

vvooiidd

))

{{

FILE** fRS;;

////////////////////////////////////////////

// Inicjacja portu szeregowego

RS_SET_BAUD((DEF_BAUD));;

UCSR0C ==

1

<<<<URSEL0 ||

1

<<<<UCSZ01 ||

1

<<<<UCSZ00;;

UCSR0B ==

1

<<<<RXEN0 ||

1

<<<<TXEN0;;

UCSR0A ==

0

;;

// Koniec inicjacji

////////////////////////////////////////////

// Inicjacja we/wy

fRS == fdevopen((rs_put,, rs_get,,

0

));;

fputs_P((PSTR((

„Hello world!\r\n“

)),, fRS));;

}}

ABC... C

Powi¹zanie strumienia z „plikiem”

Zmienna FILE

W pierwotnej wersji operacje z biblioteki stdio mia³y

na celu dzia³anie na plikach. Urz¹dzenia wejœcia–wyj-

œcia by³y traktowane, od strony programowej, jako

plik o pewnych specyficznych w³aœciwoœciach. St¹d

te¿ wziê³a siê nazwa prawdopodobnie najwa¿niejszej

dla naszej struktury biblioteki. Struktura FILE prze-

chowuje wszystkie informacje na temat strumienia,

umo¿liwiaj¹ce dzia³anie funkcji do niego pisz¹cych

lub z niego czytaj¹cych. Ze struktur¹ t¹ zwi¹zana jest

pewna „nieprzyjemnoœæ”. Standard w ogóle nie defi-

niuje jej zawartoœci. Jest to zmienna wewnêtrzna bib-

lioteki stdio i s³u¿y do przekazywania informacji tylko

miêdzy jej funkcjami. Co wa¿niejsze, twórcy bibliote-

ki do³¹czonej do WinAVR informuj¹, ¿e jej zawartoœæ

mo¿e zmieniaæ siê w przysz³ych wersjach. Na razie

nie wnikaj¹c w sprawê g³êboko, mo¿emy za³o¿yæ, ¿e

znajd¹ siê tam przede wszystkim wskaŸniki na nasze

funkcje put oraz get, które inicjowane s¹ w chwili

wywo³ania fdevopen. Funkcja fdevopen nie jest stan-

dardow¹ funkcj¹ ANSI C. Jest prób¹ umieszczenia

biblioteki stdio tam, gdzie nie wystêpuj¹ rzeczywiste

pliki. Wspomniana funkcja wykonuje dla nas dwa

dzia³ania. Po pierwsze, na podstawie podanych

danych tworzy strukturê FILE, oraz zwraca

wskaŸnik do niej – powiemy, ¿e dokonuje

powi¹zania miêdzy strumieniem a plikiem.

Po drugie, jeœli odpowiedni strumieñ

domyœlny (stdin lub/i stdout – podajemy

get lub/i put) nie jest jeszcze zainicjowany,

wpisuje do niego wskaŸnik na aktualnie

utworzon¹ strukturê.

Gdy powi¹¿emy ju¿ urz¹dzenie z pli-

kiem, mamy mo¿liwoœæ operowania po-

œrednio na zwi¹zanym z nim strumieniu.

Do tej pory zajmowaliœmy siê tylko funkc-

jami operuj¹cymi na strumieniach domyœl-

nych. Niewielka zmiana w programie

umo¿liwia nam dzia³anie na podanym stru-

mieniu. Wystarczy zapamiêtaæ wskaŸnik

zwrócony przez fdevopen. Praktycznie

wszystkie funkcje stdio posiadaj¹ swoje

odpowiedniki z przedrostkiem f. Funkcje zaczynaj¹ce

siê od tej litery przyjmuj¹ jako jeden z parametrów

zapamiêtany przez nas wskaŸnik, a swoje dzia³anie

kieruj¹ do strumienia po³¹czonego z podan¹ struktur¹

FILE

.

ABC... C

Zamieszanie z puts i gets

W praktyce wiêkszoœæ funkcji posiada swoje wersje

z przedrostkiem f, mog¹ce dzia³aæ na wybranym

strumieniu. Trochê zamieszania wprowadzaj¹ w tym

przypadku jedynie funkcje fputs oraz fgets. Otó¿

funkcja puts po wys³aniu napisu do³¹cza na jego

koniec znak nowej linii (przypominam: ‘\n’). Funk-

cja fputs tego nie robi. Podobnie, funkcja gets wczy-

tuje napis do znaku koñca linii, ale nie wpisuje go do

podanego bufora. Funkcja fgets wpisuje wystêpuj¹cy

znak nowej linii.

Nie jest to b³¹d wprowadzony w WinAVR, dzia-

³anie takie okreœla standard ANSI-C. Nie pytajcie

mnie dlaczego.

background image

Znamy ju¿ przyczynê problemu. Jak go

rozwi¹zaæ? W posiadanej przez nas wersji

biblioteki stdio nie ma innej ni¿ fdevopen

funkcji umo¿liwiaj¹cej powi¹zanie strumienia

ze zmienn¹ FILE. Rozwi¹zaniem by³oby

samodzielne utworzenie zmiennej typu FILE

w normalny, poznany przez nas do tej pory

sposób – bez dynamicznego zajmowania

pamiêci. Nastêpnie musielibyœmy samodziel-

nie wpisaæ do niej odpowiednie wartoœci. Nie

jest to eleganckie rozwi¹zanie... jest ponadto

(pozornie) niemo¿liwe.

Zmienna typu FILE jest definiowana w ra-

mach omawianej biblioteki jako struktura.

Zapoznaj siê z ramk¹ opisuj¹c¹ korzystanie

z tych u¿ytecznych tworów. Trochê przydat-

nych informacji o tym, jak u¿ywaæ struktury

jako nowego typu danych, znajdziesz w ram-

ce ABC... C typedef – nazywanie typu. Zaj-

rzyj do niej. Nied³ugo bêdziemy korzystaæ

z opisanej tam mo¿liwoœci.

Pocz¹tek naszej idei pozbycia siê proble-

mu pokazuje listing 61, bêd¹cy przeróbk¹

programu z listingu 59. Chwilowo nie wpisu-

jemy nic do struktury (jeszcze nie wiemy,

jakie posiada sk³adowe). Wa¿ne jest jedynie

sprawdzenie, jak zareaguje kompilator. Oka¿e

siê, ¿e ju¿ na tym poziomie pojawi¹ siê prob-

lemy. Spróbuj skompilowaæ taki program.

Jeœli wszystko zrobi³eœ prawid³owo, to kom-

pilacja pójdzie... Ÿle. Otrzymasz komunikat

o b³êdzie polegaj¹cym na tym, ¿e nie jest

znany rozmiar struktury g_fRS. Có¿ to za

dziwna struktura, której rozmiar jest nieokreœ-

38

Programowanie

Elektronika dla Wszystkich

Listing 60 - wykorzystanie funkcji fprintf

iinntt

main((

vvooiidd

))

{{

FILE** fRS;;

iinntt

a ==

1234

;;

iinntt

b ==

0xff

;;

//////////////////////////////

// Inicjacja portu szeregowego

((......))

// Koniec inicjacji

//////////////////////////////

// Inicjacja we/wy

fRS == fdevopen((rs_put,, rs_get,,

0

));;

fprintf_P((fRS,, PSTR((

„a(%%d)=%d\r\n“

„a(%%x)=%x\r\n“

„a(%%X)=%X\r\n“

„b(%%#x)=%#x\r\n“

„b(%%o)=%o\r\n“

)),,

a,, a,, a,, b,, b));;

}}

ABC... C

struktura

Tworzenie, przeznaczenie

Wspomnia³em o tym, ¿e zmienna typu FILE to struk-

tura. Struktura to bardzo wygodny obiekt, w którym

mo¿emy pogrupowaæ wszystkie potrzebne nam

zmienne jakiegoœ typu. WyobraŸmy sobie, ¿e chcemy

utworzyæ strukturê zawieraj¹c¹ informacjê o punkcie:

ssttrruucctt

[[punkt]]

{

iinntt

x;;

iinntt

y;;

iinntt

z;;

} [[punktA,, punktB,, x]];;

Pola umieszczone w nawiasach kwadratowych s¹

opcjonalne. Zaraz za s³owem kluczowym struct

wystêpuje etykieta. Jeœli umieœcimy j¹ tutaj, bêdziemy

mogli wykorzystaæ j¹ zamiast wszystkiego, co zawar-

liœmy w nawiasach klamrowych. Przyk³adowo, chce-

my utworzyæ kolejny punkt:

ssttrruucctt

punktKolejny;;

Z kolei wszystko, co zostanie umieszczone za

nawiasem klamrowym, powoduje utworzenie odpo-

wiednich zmiennych. W praktyce sk³adnia:

ssttrruucctt

{......} zmienna;;

Oznacza to samo co:

iinntt

zmienna;;

Z tym ¿e w pierwszym przypadku tworzymy

obiekt typu podanej struktury, w drugim tworzymy

obiekt typu int.

W nawiasach klamrowych umieszczamy infor-

macje o sk³adowych struktury. Po prostu wymieniamy

kolejno wystêpuj¹ce w niej zmienne oraz nadajemy im

nazwy. Mo¿emy korzystaæ tutaj z wszelkich dostêp-

nych typów zmiennych. Wczeœniej zdeklarowane

struktury tak¿e mog¹ byæ wykorzystane.

Nale¿y pilnowaæ jedynie, aby nazwy sk³adowych

nie pokrywa³y siê miêdzy sob¹. Jednak nazwa struktu-

ry mo¿e przyj¹æ postaæ identyczn¹ jak na przyk³ad

etykieta czy nazwa zmiennej globalnej – nie ma

obawy o b³êdy w programie.

Dostêp do odpowiedniej zmiennej w strukturze

jest rozró¿niany poprzez kontekst.

Dostêp do sk³adowych,

wykorzystanie w programie

Do sk³adowych struktury mo¿emy siê dostaæ na dwa

sposoby. Pierwszy z nich dotyczy przypadku, gdy dys-

ponujemy odpowiedni¹ zmienn¹. W naszym przypad-

ku niech bêdzie to punktA. Do poszczególnych pól

struktury dostaniemy siê za pomoc¹ operatora kropki:

punktA..x ==

0

;;

punktA..y ==

1

;;

punktA..z ==

2

;;

Myœlê, ¿e nie budzi to w¹tpliwoœci. Jeœli teraz

wydaje siê nienaturalne, stanie siê prostsze podczas

pisania przyk³adów.

Drugi sposób dotyczy momentu, gdy mamy dostêp

do wskaŸnika na strukturê. Mo¿na wyobraziæ sobie

nastêpuj¹cy przyk³ad:

vvooiidd

Zeruj((

ssttrruucctt

punkt **pPt))

{

pPt-->>x ==

0

;;

pPt-->>y ==

0

;;

pPt-->>z ==

0

;;

}

Sytuacja bardzo zbli¿ona do poprzed-

niej, korzystamy jedynie z innego operatora.

Co prawda mo¿liwe jest dzia³anie bardziej „zakrêco-

ne”:

(*pPt)..x ==

0

;;

Przypomnê, ¿e za pomoc¹ operatora gwiazdki

dostajemy siê do obiektu wskazywanego przez wskaŸ-

nik. Mo¿emy korzystaæ z tego jak powy¿ej, ale jest to

mniej eleganckie i mniej czytelne ni¿ podany wczeœ-

niej sposób.

Przedstawion¹ funkcjê wywo³amy jak ni¿ej:

Zeruj((&&punktA))

Przyk³ad mo¿e nie jest zaawansowany, ale poka-

zuje ju¿ si³ê, jaka drzemie w strukturach. Zauwa¿,

¿e do funkcji Zeruj przekazujemy tak naprawdê tylko

jeden argument – 16-bitowy adres – a w jej wnêtrzu

mamy dostêp do wszystkich pól naszej struktury.

Nadanie wartoœci pocz¹tkowych

Nadanie wartoœci pocz¹tkowych strukturze odbywa

siê podobnie jak mia³o to miejsce w przypadku tablic.

W nawiasach klamrowych musimy podaæ wartoœci

ka¿dej sk³adowej. Pamiêtaj, ¿e takie dzia³anie dozwo-

lone jest jedynie w chwili tworzenia zmiennej:

ssttrruucctt

punktZ == {

0

,,

1

,,

2

};;

Operowanie ca³ymi strukturami

Jeœli stworzysz w³asn¹ strukturê, mo¿esz przekonaæ

siê, ¿e nie dzia³aj¹ na nich standardowe operatory.

Zapis jak poni¿ej jest nieprawid³owy:

puntA = punktB;

Strukturê nale¿y traktowaæ jako pewien ci¹g

danych w pamiêci (obrazek w ramce). Sprawa jest

zbli¿ona do dzia³ania tablic. Mamy dwie mo¿liwoœci

zast¹pienia powy¿szego przepisania. Jedna polega na

przepisywaniu wszystkich pól, dla których powy¿sze

dzia³anie jest dozwolone:

punktA..x == punktB..x;;

punktA..y == punktB..y;;

punktA..z == punktB..z;;

Pola x, y, z s¹ typu int. U¿ycie operatora przypisa-

nia na zmiennych typu int jest mo¿liwe.

Druga, wygodniejsza i czêsto bardziej optymalna

metoda polega na przekopiowaniu bloku pamiêci:

Funkcja memcpy skopiuje dane z obszaru wskazy-

wanego przez drugi argument do obszaru wskazywa-

nego przez pierwszy argument. Trzeci argument poda-

je w bajtach iloϾ danych do skopiowania. Operator

sizeof zostanie w czasie kompilacji zamieniony na

odpowiedni¹ wartoœæ.

Jak to robi GCC

Dlaczego fdevopen jest tak

pamiêcioch³onna?

Dokumentacja funkcji fdevopen informuje nas,

¿e funkcja ta tworzy now¹ strukturê FILE, aloku-

j¹c w tym celu dynamicznie pamiêæ. Daje nam to

doœæ ciekaw¹ mo¿liwoœæ: gdy zaczynamy u¿ywaæ

na przyk³ad interfejsu RS232, dokonuj¹c powi¹za-

nia poprzez funkcjê fdevopen, zajmujemy pamiêæ.

Gdy teraz zamkniemy po³¹czenie za pomoc¹

funkcji fclose – pamiêæ zostanie zwolniona. Jest

jedno ale...

Dynamiczna alokacja pamiêci danych wymaga

umieszczenia doϾ skomplikowanych procedur

w pamiêci programu. Czy to siê op³aca? Zauwa¿y-

³eœ mo¿e, ¿e do tej pory nie zapozna³em Ciê

z funkcj¹ fclose? Prawda jest taka, ¿e w mikro-

kontrolerze bardzo rzadko mamy do czynienia z

sytuacj¹, gdy chcemy zamkn¹æ otwarty strumieñ.

W ogromnej wiêkszoœci przypadków to po prostu

nie ma sensu. Zwykle dokonamy powi¹zania

wszystkich strumieni w chwili startu programu i

nie bêdziemy ich nigdy zwalniaæ. Wykorzystanie

dynamicznego przydzielania pamiêci jest wiêc dla

nas przerostem formy nad treœci¹.

memcpy((&&punktA,, &&punktB,,

ssiizzeeooff

((punktA))));;

background image

lony? Aby siê tego dowiedzieæ, poszukujemy

miejsca, gdzie zosta³a ona zdeklarowana.

Znajdziesz j¹ w pliku C:\WinAVR\avr\inclu-

de\stdio.h. Interesuj¹cy nas fragment przed-

stawi³em na listingu 62. Zauwa¿, ¿e FILE jest

zdefiniowane tutaj jako sta³a symboliczna,

a nie nowy typ. Informacja o takiej mo¿liwoœ-

ci znajduje siê w ramce ABC... C typedef –

nazywanie typu. Ciekawa rzecz dzieje siê

wy¿ej. W pierwszej z przedstawionych linii.

Aby zrozumieæ, z czym mamy do czynienia,

zajrzyj do ramki ABC... C deklaracja nie-

kompletna.

Okazuje siê, ¿e z naszego punktu widzenia

zmienna typu FILE zosta³a utworzona w taki

sposób, aby by³a w³aœnie typu niekompletne-

go. Od twórców WinAVR dostaliœmy do rêki

jedynie narzêdzie umo¿liwiaj¹ce nam pos³u-

giwanie siê uchwytami do podanej struktury.

Zabezpieczyli oni siê w ten sposób przed pró-

bami jej rêcznego modyfikowania (co w C

formalnie jest zabronione). Wydawa³oby siê,

¿e tajemnica FILE zosta³a przed nami dok³ad-

nie schowana. Jednak¿e... mo¿e nie?

Myœlê, ¿e przynajmniej czêœæ Czytelników

w tej chwili zaprotestuje. Jeœli nie umknê³a Ci

sprawa, o której zaraz napiszê – bardzo siê

cieszê.

Nie przestrasz siê tego, jak g³êboko w na-

sze narzêdzia chcê Ciê teraz zaprowadziæ.

Przekonasz siê zaraz, ¿e poszukiwanie potrze-

bnych nam informacji w kodzie wcale nie jest

takie trudne jakby siê wydawa³o. Poza tym,

w tej chwili, niejako „przeprowadzê Ciê za rê-

kê”, pokazuj¹c krok po kroku sposób odnale-

zienia oraz wykorzystania potrzebnych infor-

macji.

Korzystamy z mo¿liwoœci,

jakie daje Open Source:

Jeœli masz teraz tak¹ mo¿liwoœæ, wejdŸ na

stronê projektu: winavr.sourcefoge.net. Spójrz

na rysunek 43. Nie œpiesz siê z przechodze-

niem przez proponowane strony. Gdyby

wa¿na by³a jedynie strona koñcowa, móg³byœ

przecie¿ wpisaæ w okno przegl¹darki bezpo-

œrednio: www.nongnu.org/avr-libc. Na pierw-

szej stronie znajdziesz pasek z ze-

spo³em linków. W tej chwili intere-

suje nas link [Package]. Znajdziesz

pod nim informacje o elementach

wchodz¹cych w sk³ad WinAVR.

Nie jest to jednolity pakiet tworzo-

ny tylko przez jedn¹ grupê ludzi.

Najbardziej chyba interesuj¹ce s¹

trzy pierwsze pozycje. Na pozycji

drugiej znajduje siê kompilator

GCC, generuje on kod, który mo¿e

byæ przekszta³cony na kod maszy-

nowy za pomoc¹ narzêdzi widocz-

nych na pozycji pierwszej – znajdu-

je siê tam, przede wszystkim, linker oraz

kompilator asemblera.

Wystêpuj¹ce w ANSI C biblioteki standar-

dowe, stworzone specjalnie z myœl¹ o proce-

sorach AVR, znajduj¹ siê pod nazw¹ avr-libc.

Po tej nazwie mo¿esz znaleŸæ sporo informac-

ji za pomoc¹ wyszukiwarki internetowej.

WinAVR jest programem na licencji Open Sour-

ce. Niczego nie da siê ukryæ w takim przypadku.

Jest to potê¿na przewaga w stosunku do programów

komercyjnych.

39

Programowanie

Elektronika dla Wszystkich

ABC... C

typedef - nazywanie typu

C udostêpnia mechanizm umo¿liwiaj¹cy nadawanie

nowych nazw typom danych. S³u¿y do tego s³owo klu-

czowe typedef. Mechanizmu tego u¿ywaliœmy, nie-

œwiadomie ju¿ w drugiej czêœci kursu, korzystaj¹c z

pliku <inttypes.h>. W ten sposób do³¹czaliœmy do

naszego programu deklaracje jak ni¿ej:

ttyyppeeddeeff ssiiggnneedd cchhaarr

int8_t;;

ttyyppeeddeeff uunnssiiggnneedd cchhaarr

uint8_t;;

ttyyppeeddeeff lloonngg

int16_t;;

Jest to przyk³ad prostego nadania nazwy typom.

Przyporz¹dkowujemy nowe nazwy typom podstawo-

wym. Zapis taki jak wy¿ej jest praktycznie równowa¿-

ny z:

#define int8_t signed char

......

Jednak we wprowadzonym do C nazywaniu typów

tkwi znacznie wiêksza si³a. Mo¿emy nazwaæ tak¿e

bardziej rozbudowany typ. Zamiast tworzyæ strukturê

opisuj¹c¹ punkt, za ka¿dym razem korzystaj¹c ze

s³owa kluczowego struct, utwórzmy nowy typ:

ttyyppeeddeeff ssttrruucctt

[[punkt]]

{

iinntt

x;;

iinntt

y;;

iinntt

z;;

} PUNKT;;

W ten sposób tworzymy nowy typ zmiennej o na-

zwie PUNKT. Opcjonalne pole [punkt] ma dok³adnie

takie samo znaczenie jak mia³o w przypadku struktu-

ry. Zgodnie z moimi próbami umieszczona w tym

miejscu etykieta mo¿e byæ identyczna z nazw¹ nowe-

go typu, a kompilator bêdzie wiedzia³ zawsze, co

mamy na myœli poprzez kontekst. Nie uda³o mi siê

jednak znaleŸæ oficjalnego potwierdzenia tego faktu.

Uwaga. Samo umieszczenie s³owa typedef przed

s³owem struct ogromnie zmienia znaczenie zapisu. W

poprzedniej ramce, gdy tylko po zamkniêciu klamry

umieœciliœmy jak¹œ nazwê,

tworzona by³a nowa zmienna,

w pamiêci rezerwowane by³o na ni¹ miejsce. W dru-

gim przypadku tworzony jest jedynie nowy typ danej.

Nie bêdziemy mieli z tego ¿adnej korzyœci, dopóki

z niego nie skorzystamy.

Teraz ca³¹ strukturê typu PUNKT mo¿emy utwo-

rzyæ w bardzo intuicyjny sposób:

PUNKT punktC == {

1

,,

2

,,

3

};;

Mo¿na pokazaæ przyk³ad, gdzie wykorzystanie

s³owa #define do nadania nazwy typu by³oby znacznie

utrudnione. Pamiêtasz nasz¹ rozmowê o wskaŸnikach

na funkcjê? Mo¿esz zajrzeæ w tym celu do czêœci 4

kursu. Mamy mo¿liwoœæ nadania nazwy typowi bêd¹-

cemu wskaŸnikiem na funkcjê (specjalnie oznaczy³em

nazwê nowego typu na czerwono):

ttyyppeeddeeff vvooiidd

((**

MyFuncPtr

))((uint16_t));;

Nastêpnie utworzenia zmiennej bêd¹cej wskaŸni-

kiem na funkcjê:

MyFuncPtr

moja_f;;

Albo wykorzystania nowej nazwy typu do wygod-

nego zapisania jednego z parametrów:

vvooiidd

ObslozPrzycisk((uint8_t maska,,

MyFuncPtr

proc))

......

Listing 62

deklaracja zmiennej typu FILE w pliku <stdio.h>

ssttrruucctt

__file;;

((......))

#define FILE struct __file

Listing 61 - próba statycznego utworzenia struktury FILE

FILE g_fRS;

iinntt

main((

vvooiidd

))

{{

//////////////////////////////////////////

// Inicjacja portu szeregowego

((......))

// Koniec inicjacji

//////////////////////////////////////////

fputs_P((PSTR((

„Hello world!\r\n“

)),, &&g_fRS));;

}}

ABC... C

deklaracja niekompletna

Gdy spotkamy siê z zapisem jak poni¿ej:

ssttrruucctt

Etykieta

Przyk³adowo:

ssttrruucctt

Nazwa** pS1;;

Funkcja((

ssttrruucctt

Nazwa** pS2))

I jeœli etykieta Nazwa nie by³a poprzednio

zdeklarowana, mamy do czynienia z typem nie-

kompletnym. W takim przypadku kompilator wie

jedynie, ¿e pS1 jest wskaŸnikiem na jak¹œ strukturê.

Nie wie o niej nic ponadto. Nie zna jej sk³adowych

czy rozmiaru. Wa¿ne jest to, ¿e z typem niekomplet-

nym mo¿emy bardzo niewiele zrobiæ. W praktyce

mo¿emy korzystaæ jedynie ze wskaŸnika do niego.

Jest to mo¿liwe, poniewa¿ wskaŸnik na ka¿d¹ struk-

turê bêdzie wygl¹da³ tak samo. W naszym przypad-

ku bêdzie to zawsze 16-bitowy adres. Uwaga: oczy-

wiœcie, na takim wskaŸniku operator „->” jest

zabroniony. Przy powy¿szych za³o¿eniach, zapis jak

ni¿ej:

ssttrruucctt

Nazwa S1;;

jest nieprawid³owy – dostaniemy informacjê o b³ê-

dzie, poniewa¿ kompilator nie wie, ile miejsca w pa-

miêci zaj¹æ na podan¹ strukturê.

Specjalne znaczenie ma w tym przypadku zapis:

ssttrruucctt

Nazwa;;

Zauwa¿ œrednik! Taka deklaracja sprawia, ¿e ety-

kieta Nazwa staje siê etykiet¹ nowej struktury, jed-

nak bêdzie to struktura typu niekompletnego. Przypi-

sanie takie odbêdzie siê tak¿e, jeœli podana etykieta

by³a wczeœniej powi¹zana z jak¹œ inn¹ struktur¹.

Ta regu³a umo¿liwia nam zdeklarowanie nie-

kompletnej struktury, nawet jeœli gdzieœ indziej (na

przyk³ad w innym pliku) u¿yliœmy ju¿ podanego

identyfikatora. Dokonana zmiana bêdzie mia³a dzia-

³anie jedynie lokalne (dla pliku kodu Ÿród³owego, we

wnêtrzu funkcji, we wnêtrzu instrukcji z³o¿onej...

zale¿nie od miejsca, gdzie powy¿sza deklaracja siê

pojawi).

background image

Wybieraj¹c odnoœnik avr-libc, zostaniemy

przeniesieni do strony zawieraj¹cej podsumo-

wanie informacji na temat projektu. Mo¿li-

woϾ pobrania nowych wersji bibliotek, doku-

mentacji oraz kodów Ÿród³owych uzyskamy

po przejœciu na stronê domow¹ (odnoœnik

Homepage w ramce Public Areas). Mniej -

wiêcej w œrodku strony domowej znajdziemy

linki do pobrania kodów Ÿród³owych oraz

dokumentacji. Po jego wybraniu bêdziesz

mia³ mo¿liwoœæ dostêpu do ka¿dej wersji,

jaka zosta³a utworzona.

Pobierane kody Ÿród³owe s¹ spakowane

w pewien szczególny sposób. S¹ to archiwa

TAR, z którymi radzi sobie dobrze na przy-

k³ad popularny WinRAR. Po otwarciu archi-

wum oka¿e siê jednak, ¿e wewn¹trz znajduje

siê plik o nazwie avr-libc-1.2.3.tar[1]. Plik

ten nale¿y wypakowaæ na przyk³ad na pulpit,

a nastêpnie usun¹æ z jego rozszerzenia cyfrê 1

razem z nawiasami kwadratowymi. Gdy roz-

pakujemy i to archiwum, uzyskamy dostêp do

wszystkich Ÿróde³ biblioteki standardowej.

Znajdziesz tutaj siedem folderów. Aktual-

nie znaczenie dla nas maj¹ dwa z nich: inclu-

de oraz libc. Pierwszy zawiera pliki nag³ów-

kowe, które mo¿emy znaleŸæ w pakiecie

WinAVR w folderze C:\WinAVR\avr\inclu-

de\. Pliki te ju¿ znamy. W folderze libc

umieszczono dalsze podfoldery grupuj¹ce

funkcje ró¿nych bibliotek. Znajdziemy tutaj

„upragniony” folder stdio.

Po otwarciu folderu libc\stdio\ odkryjesz

du¿¹ liczbê plików. Okazuje siê, ¿e praktycz-

nie ka¿da funkcja zosta³a napisana w taki spo-

sób, ¿e znajduje siê w oddzielnym pliku. Dla

wygody nazwy plików s¹ zgodne ze znajduj¹-

cymi siê w nich funkcjami. Wyjaœnienie, sk¹d

wzi¹³ siê taki podzia³ oraz czemu on s³u¿y,

znajdziesz w ramce Jak to robi GCC linker

– ³¹czenie z bibliotekami.

Teraz, gdy ju¿ wiesz, gdzie znaleŸæ kody

Ÿród³owe bibliotek, po przeczytaniu tego aka-

pitu zajrzyj do ramki „Nieczyste zagranie –

zastêpowanie fdevopen”. Przedstawiam tam

opis, jak korzystaj¹c z udostêpnionych kodów

Ÿród³owych, mo¿emy obejœæ wprowadzone

ograniczenia. Staram siê opisaæ w tym miejs-

cu sposób, w jaki dochodzi³em do pewnych

wniosków. Jednak si³¹ rzeczy opis jest skróto-

wy. Tak naprawdê utworzenie ostatecznej

wersji „nak³adki” na stdio by³o poprzedzone

kilkoma godzinami spêdzonymi na forach

(niestety, g³ównie angielskojêzycznych) oraz

studiowaniu podstawowej dokumentacji z

katalogu C:\WinAVR\doc\avr-libc. Pamiêtaj o

tym, jeœli opisany tok myœlenia bêdzie czasa-

mi zaskakiwa³. Opisane tutaj dzia³anie jest

wysoce ryzykowne. Ryzykujemy, ¿e utworzo-

ny kod bêdzie niekompatybilny z przysz³¹

wersj¹ bibliotek avr-libc. Jednak jak siê za

chwilê przekonamy, korzyœci s¹ na tyle du¿e,

¿e czêsto warto podj¹æ to ryzyko. Jest to

szczególnie odczuwalne, jeœli zaczynamy

mieæ problemy ze zmieszczeniem siê z pro-

gramem w pamiêci FLASH procesora.

Ramka opisuj¹ca pomys³ pozbycia siê

pamiêcioch³onnego wywo³ania fdevopen

mo¿e w jakimœ miejscu wydawaæ siê niejasna.

Wiem, ¿e mo¿e byæ to trudne dla osób przy-

zwyczajonych do BASCOM-a, gdzie takie

dzia³ania absolutnie nie by³y konieczne (nie

by³y te¿ mo¿liwe). Nie przejmuj siê tym w tej

chwili. Do wspomnianej ramki mo¿esz

zawsze powróciæ zaraz po przyk³adach.

Dalej bêdziemy prowadziæ eksperyment

z programem z listingu 59. Dodamy do niego

nowy plik nag³ówkowy o nazwie

fcheat_stdio.h. Pamiêtaj, ¿e dodanie nowego

pliku nag³ówka nie wymaga jakichkolwiek

zmian w pliku makefile. Dla w³asnej wygody

mo¿emy dodaæ go do projektu Programmers

Notepada, co sprawi, ¿e bêdzie on dostêpny

z poziomu panelu Projects.

Do nowo utworzonego pliku wprowadŸ

kod widoczny na listingu 65. Plik main.c

naszego programu zmieniamy zgodnie

z listingiem 67. Skompiluj now¹ wersjê

programu. Podczas kompilacji pojawi siê

jedno ostrze¿enie. Jest to zwi¹zane z rzutowa-

Uwa¿aj. W WinAVR 20050214 zaimplementowa-

no biblioteki avr-libc 1.2.3. Nie ma sensu pobiera-

nie w tej chwili nowszych Ÿróde³ – wprowadzi to za

du¿o zamieszania.

40

Programowanie

Elektronika dla Wszystkich

Jak to robi GCC

linker – ³¹czenie z bibliotekami

Pamiêtasz moment, gdy zaczynaliœmy tworzyæ pro-

gramy z wiêkszej liczby plików? Zrobiliœmy to

w czêœci 6 kursu. T³umaczy³em wtedy, czym jest lin-

ker. Jeœli masz tak¹ mo¿liwoœæ, zerknij na rysunek

27 znajduj¹cy siê we wspomnianej czêœci. Jeœli

w programie u¿yjemy teraz funkcji z jakiejœ bibliote-

ki, jej kod zostanie dodany w procesie linkowania

programu. Istniej¹ linkery bêd¹ce w stanie zrobiæ to

w bardzo inteligentny sposób. Analizuj¹ one mo¿li-

we, wewnêtrzne odwo³ania i usuwaj¹ wszelki kod,

który nie jest u¿ywany. AVR-GCC nie posiada, nie-

stety, takiej mo¿liwoœci. Jeœli wykryte zostanie odwo-

³anie do jakiejkolwiek funkcji znajduj¹cej siê

w danym pliku relokowalnym (przypominam: pliki

typu *.o), ca³y plik zostanie dodany do kodu wyjœcio-

wego. Nawet jeœli spoœród 20 znajduj¹cych siê w takim

pliku funkcji, wykorzystamy tylko jedn¹, w pamiêci

mikrokontrolera znajd¹ siê wszystkie. Nie jest to wiel-

kim problemem, jeœli chodzi o pliki programu – tutaj

bardzo rzadko piszemy funkcje, z których nie korzysta-

my. Bardziej uci¹¿liwy fakt ten staje siê w chwili two-

rzenia oraz korzystania z uniwersalnych bibliotek.

Aby zminimalizowaæ ten efekt, biblioteki zaleca

siê dzieliæ na ma³e kawa³ki. W ten sposób w³aœnie

stworzona zosta³a biblioteka avr-libc. Praktycznie

ka¿da funkcja umieszczona jest w oddzielnym pliku.

To, ¿e fakt ten u³atwia nam znalezienie tego, czego

szukamy, jest jedynie mi³ym skutkiem ubocznym

osi¹gniêcia znacznie wa¿niejszego celu.

Listing 67 - nowa wersja programu przywitania

#include <avr/io.h>

#include <stdio.h>

#include „rs.h“

#include „harddef.h“

#include „stdio_fcheat.h“

fcheat_file fileRS ==

FCHEAT_STATIC_FDEVOPENWR((rs_put,, rs_get));;

FILE** fRS == ((FILE**))&&fileRS;;

iinntt

main((

vvooiidd

))

{

/////////////////////////////////////////

// Inicjacja portu szeregowego

((......))

// Koniec inicjacji

/////////////////////////////////////////

fputs((

„Hello world!\r\n“

,, fRS));;

}

Rys. 43 Poszukiwanie Ÿróde³ bibliotek

standardowych.

background image

niem naszego wskaŸnika na wskaŸnik na

strukturê typu niekompletnego. Poniewa¿

kompilator nie wie, co siê pod ni¹ kryje, infor-

muje nas, ¿e dzia³anie takie mo¿e byæ niepra-

wid³owe. W tym przypadku ostrze¿enie to

mo¿emy zignorowaæ. Mo¿emy tak¿e pozbyæ

siê jego wypisywania poprzez operacjê jak na

listingu 68. Wyjaœniê znaczenie tego dzia³a-

nia, gdy tylko powiemy sobie na czym polega

rzutowanie. Tak czy inaczej, program bêdzie

dzia³a³ prawid³owo i zajmie nieca³e 360B

pamiêci programu.

Nieczyste zagranie

Nieczyste zagranie

Zastêpowanie fdevopen

Umieszczone w ramce kody s¹ fragmentami kodów

Ÿród³owych biblioteki avr-lic-1.2.3. Pochodz¹ one ze

strony www.nongnu.org/avr-libc. Autor pokazanych

fragmentów zgodzi³ siê na ich publikacjê.

Poszukiwania prawdziwej deklaracji struktury

FILE zaprowadz¹ nas do pliku \libc\stdio\stdio_priva-

te.h. Interesuj¹cy nas fragment pokaza³em na listingu

63. Wiemy ju¿, jakie pola zawiera tajemnicza struktu-

ra FILE. Jednak teraz mo¿emy jedynie domyœlaæ, siê

co powinniœmy wpisaæ w ka¿de z nich, aby funkcje

biblioteki stdio dzia³a³y prawid³owo. Zajrzyjmy wiêc,

co robi funkcja fdevopen. Jak ³atwo siê domyœliæ, jej

kod znajdziemy w pliku \libc\stdio\fdevopen.c. Przed-

stawiam go tak¿e na listingu 64, gdzie zaznaczy³em

¿ó³tym t³em miejsce, gdzie rezerwowana jest dyna-

micznie pamiêæ. Warto wiedzieæ, ¿e zgodnie ze stan-

dardem ANSI C funkcja calloc wype³nia zarezerwo-

wan¹ pamiêæ zerami.

Przyjrzyjmy siê, co robi kod, do którego uda³o

nam siê dostaæ. Wbrew pozorom nie dzieje siê tutaj

wiele. Rezerwowana jest pamiêæ na strukturê FILE.

Domyœlnie wszystkie pola nowej struktury s¹ ustawia-

ne na zera. Podczas tworzenia powi¹zania ze strumie-

niem ustawiane s¹ wskaŸniki na nasze funkcje zapisy-

wania i odczytu znaku oraz w polu flags ustawiane s¹

bity wskazuj¹ce, ¿e strumieñ nadaje siê do odczytu

i/lub zapisu.

Rozwi¹zanie: fcheat_stdio.h

Poniewa¿ w pisanym przez nas programie nie mo¿emy

utworzyæ zmiennej typu FILE, stworzymy nowy typ

zmiennej, bêd¹cy struktur¹, która bêdzie identyczna

z orygina³em. Dziêki temu uzyskamy mo¿liwoœæ

odpowiedniego wype³nienia obszaru pamiêci (tak

mo¿na potraktowaæ strukturê, porównaj z odpowied-

ni¹ ramk¹ ABC... C). Jeœli teraz podstawimy pod

wskaŸnik do zmiennej FILE wskaŸnik na

nasz¹ strukturê – wszystko bêdzie dzia³a³o

zgodnie z oczekiwaniami.

Nowej zmiennej postanowi³em nadaæ

nazwê fcheat_stdio (cheat – oszust). Propo-

zycjê zawartoœci pliku fcheat_stdio.h przed-

stawiam na listingu 65. Utworzon¹ struktu-

rê moglibyœmy inicjowaæ, wype³niaj¹c

odpowiednie pola po jej utworzeniu. Jednak

najwygodniej w naszym przypadku bêdzie

wykorzystaæ mo¿liwoœæ inicjacji nowo two-

rzonej zmiennej podan¹ wartoœci¹. Widocz-

ne na listingu 65 makra u³atwiaj¹ intuicyjne

stworzenie potrzebnych danych. Dziêki

temu utworzenie nowej struktury, zawiera-

j¹cej odpowiednio ustawione wszystkie

pola mo¿e przebiegaæ jak na listingu 66.

Dlaczego nie mo¿emy

u¿yæ strumieni domyœlnych

Wydawa³oby siê, ¿e wskaŸniki do utworzo-

nych struktur mo¿emy przypisaæ tak¿e stru-

mieniom domyœlnym (zobacz jak jest to

robione na listingu 64 – zielone oznacze-

nia). Nie jest to do koñca prawda. Jeœli

przyjrzysz siê plikowi zawieraj¹cemu kod

fdevopen, odkryjesz, ¿e zmienna zawieraj¹-

ca dane strumieni domyœlnych, bêd¹ca tak

na prawdê tablic¹ wskaŸników na strukturê

__file, zosta³a zdeklarowana w³aœnie tutaj.

Zgodnie wiêc z ramk¹ o dzia³aniu linkera,

jakiekolwiek odwo³anie do strumieni

domyœlnych spowoduje dodanie tak¿e kodu

fdevopen do pliku kodu wyjœciowego. Tego

przecie¿ ca³y czas staramy siê unikn¹æ.

fclose - uwaga

Stosuj¹c opisan¹ sztuczkê, utworzymy

nowe powi¹zanie, którego nie wolno nam

zamykaæ! Funkcja fclose uznaje, ¿e struktura FILE

by³a utworzona dynamicznie i zwalnia pamiêæ. Próba

zwolnienia pamiêci zajmowanej przez zmienn¹, która

nie zosta³a stworzona w odpowiedni sposób, mo¿e

spowodowaæ b³¹d programu.

Od technicznej strony dynamicznie zajêta pamiêæ

znajduje siê w odpowiednim obszarze RAM-u pro-

cesora. Funkcje dostaj¹ siê do niej tak samo jak do

ka¿dej innej zmiennej, tak wiêc mo¿esz mi wierzyæ,

¿e funkcjom, które chcemy „nabraæ”, nie zrobi ró¿-

nicy czy zmienna FILE bêdzie utworzona dynamicz-

nie, czy bêdzie to zmienna lokalna lub globalna.

45

Programowanie

Elektronika dla Wszystkich

Listing 63 - tajemnica struktury FILE, fragment pliku stdio_private.h

ssttrruucctt

__file

{

cchhaarr

**

buf;;

/* buffer pointer */

uunnssiiggnneedd cchhaarr

unget;; /* ungetc() buffer */

uint8_t flags;;

/* flags, see below */

#define __SRD 0x0001

/* OK to read */

#define __SWR 0x0002

/* OK to write */

#define __SSTR 0x0004

/* this is an sprintf/snprintf string */

#define __SPGM 0x0008

/* fmt string is in progmem */

#define __SERR 0x0010

/* found error */

#define __SEOF 0x0020

/* found EOF */

#define __SUNGET 0x040

/* ungetc() happened */

#if 0

/* possible future extensions, will require uint16_t flags */

#define __SRW 0x0080

/* open for reading & writing */

#define __SLBF 0x0100

/* line buffered */

#define __SNBF 0x0200

/* unbuffered */

#define __SMBF 0x0400

/* buf is from malloc */

#endif

iinntt

size;;

/* size of buffer */

iinntt

len;;

/* characters read or written so far */

iinntt

((**put))((

cchhaarr

));;

/* function to write one char to device */

iinntt

((**get))((

vvooiidd

));;

/* function to read one char from device */

};;

Listing 64 - Kod funkcji fdevopen

FILE *

fdevopen((

iinntt

((**put))((

cchhaarr

)),,

iinntt

((**get))((

vvooiidd

)),,

iinntt

opts __attribute__((((unused))))))

{

FILE **s;;

iiff

((put ====

0

&&&& get ====

0

))

rreettuurrnn

0

;;

iiff

((((s == calloc((

1

,,

ssiizzeeooff

((FILE)))))) ====

0

))

rreettuurrnn

0

;;

iiff

((get !!==

0

)) {

s-->>get == get;;

s-->>flags == __SRD;;

iiff

((stdin ====

0

))

stdin == s;;

}

iiff

((put !!==

0

)) {

s-->>put == put;;

s-->>flags ||== __SWR;;

iiff

((stdout ====

0

)) {

stdout == s;;

iiff

((stderr ====

0

))

stderr == s;;

}

}

rreettuurrnn

s;;

}

Listing 65 - rozwi¹zanie: plik fcheat_stdio.h

#ifndef FCHEAT_STDIO_INCLUDED

#define FCHEAT_STDIO_INCLUDED

// Struktura odpowiadaj¹ca FILE

ttyyppeeddeeff ssttrruucctt

fcheat_file

{

cchhaarr

**buf;;

uunnssiiggnneedd cchhaarr

unget;;

uint8_t flags;;

#define FCHEAT_SRD 0x0001

#define FCHEAT_SWR 0x0002

iinntt

size;;

iinntt

len;;

iinntt

((**put))((

cchhaarr

));;

iinntt

((**get))((

vvooiidd

));;

}fcheat_file;;

#define FCHEAT_STATIC_FDEVOPENR(get) \

{0,0,FCHEAT_SRD,0,0,0,get}

#define FCHEAT_STATIC_FDEVOPENW(put) \

{0,0,FCHEAT_SWR,0,0,put,0}

#define FCHEAT_STATIC_FDEVOPENWR(put, get) \

{0,0,FCHEAT_SWR|FCHEAT_SRD,0,0,put,get}

#endif

//FCHEAT_STDIO _INCLUDED

Listing 66 - tworzenie oraz inicjacja fcheat_stdio

fcheat_file fileRS ==

FCHEAT_STATIC_FDEVOPENWR((rs_put,, rs_get));;

background image

Podsumowanie rozwi¹zania

Mo¿esz uznaæ teraz, ¿e C jest straszne. Jeœli

taka myœl pojawia siê w Twojej g³owie, spró-

buj zerkn¹æ na dzisiejsz¹ sztuczkê z innej per-

spektywy. Du¿a liczba stron nie zosta³a

poœwiêcona dzisiaj jedynie opisowi tego, jak

obejœæ ograniczenia WinAVR. Poœwiêci³em

dziœ wiele stron, pokazuj¹c zasadê dzia³ania

biblioteki stdio od œrodka. Znakomicie by³o-

by, gdybyœ sam pobra³ na swój komputer jej

kod oraz przejrza³ go zgodnie z przedstawio-

nym opisem. Jednoczeœnie przedstawi³em

pojêcie struktury, delaracji niepe³nej, dzia³a-

nie s³owa typedef i wreszcie sam¹ zmienn¹

FILE.

Jednak to, ¿e w WinAVR mamy mo¿li-

woœæ podejrzenia kodów Ÿród³owych ka¿dego

dostêpnego modu³u, nie zmusza nas wcale do

robienia tego. Jest to dodatkowa mo¿liwoœæ.

Napisany dzisiaj kod mo¿esz traktowaæ nawet

jak czarn¹ skrzynkê. Napisany plik

fcheat_stdio.h po prostu dzia³a –

pamiêtaj jednak, ¿e dotyczy to jedy-

nie aktualnej wersji WinAVR. Jeœli w

bibliotece stdio dokonana zostanie

zmiana struktury FILE, konieczne bêdzie

wprowadzenie identycznej modyfikacji w jej

46

Programowanie

Elektronika dla Wszystkich

ABC... C

Rzutowanie

– konwersja zakresu wartoœci zmiennej

Podczas wykonywania obliczeñ pewne przekszta³ce-

nia odbywaj¹ siê automatycznie. Zgodnie ze standar-

dem ANSI C nie wykonuje siê dzia³añ na liczbach

o rozmiarze mniejszym ni¿ int. Oznacza to w prakty-

ce, ¿e jeœli dodajemy do siebie dwie liczby typu

uint8_t, przed wykonaniem dodawania zostan¹ one

zmienione na unsigned int (u nas: 16 bitów, bez

znaku). Nie protestuj, sprawdzaj¹c wygenerowany

kod. Prawdopodobnie znajdziesz tam w³aœnie proste

dodawanie oœmiobitowe. Jest to wynik odpowiedniej

optymalizacji, a to, o czym w³aœnie napisa³em, ma

wa¿ne konsekwencje, dotycz¹ce samych wyników

obliczeñ, o czym przekonamy siê za moment.

Jeœli wykonamy dzia³anie arytmetyczne na dwóch

argumentach ró¿nego typu, ten, którego typ posiada

mniejszy zakres wartoœci, zostanie zamieniony na typ

drugi o wiêkszym zakresie. To proste. Wykonujemy

dodawanie dwóch zmiennych. Jedna jest zmienn¹

32-bitow¹, druga 16-bitow¹. Niezale¿nie od tego ilu

bitowa jest zmienna, do której wpisujemy wynik,

zmienna 16-bitowa zostanie potraktowana jako posia-

daj¹ca 32 bity. Wydaje siê to oczywiste i m¹dre – w

pamiêci nie przechowujemy procedur na ka¿d¹ ewen-

tualnoœæ, jak mno¿enie 8x8, 16x8, 16x16, 32x8,

32x16... Znajd¹ siê tam tylko dzia³ania 16x16 oraz

32x32 (w WinAVR mo¿e pojawiæ siê tak¿e 64x64, ale

specyfikacja ANSI C tego nie normuje).

Na pocz¹tku nie musisz znaæ wszystkich regu³.

Zapamiêtaj dwie proste zasady:

1. Zmienna o mniejszym zakresie wartoœci zmie-

niana jest na typ o zakresie wiêkszym.

2. Nie wykonujemy dzia³añ na typie mniejszym

ni¿ int.

Typ wartoœci wykonywanego dzia³ania jest taki

sam jak argumenty (po przekszta³ceniu). Oznacza to,

¿e dzia³anie 16x16 da wynik 16-bitowy. Tymczasem,

aby pomieœciæ wszystkie mo¿liwe wyniki, powinien

on mieæ raczej 32 bity!

Tutaj z pomoc¹ przychodzi nam mo¿liwoœæ rzuto-

wania. W nawiasie okr¹g³ym przed argumentem, któ-

rego zakres chcemy zmieniæ, umieszczamy nazwê

typu. Kompilator zinterpretuje to tak, ¿e znajduj¹c¹ siê

z nim wartoœæ ma traktowaæ, jakby by³a typu podane-

go w nawiasie. Dokona wiêc albo zwiêkszenia liczby

bitów, albo jego obciêcia. Zwiêkszanie liczby bitów

odbywa siê zawsze w taki sposób, ¿e argument nie

zmienia swojej wartoœci (jeœli tylko to mo¿liwe; wynik

przekszta³cenia liczby ujemnej na typ bez znaku nie

jest okreœlony przez standard).

Najlepiej wyjaœni zasadê obiecany przyk³ad.

Spójrz na listing 69. To kompletny kod, jaki nale¿y

wpisaæ do pliku main.c ostatniej wersji naszego pro-

gramu. Rysunek 44 pokazuje dane odebrane przez

komputer. Na czerwono oznaczy³em nieprawid³owe

wyniki obliczeñ. Przyjrzyjmy siê, jak zostan¹ wyko-

nane kolejne dzia³ania:

Œ Mno¿one s¹ dwie zmienne oœmiobitowe. Wynik

mno¿enia zosta³ dobrany tak, ¿e wykracza poza

dostêpny zakres (prawid³owy wynik to 20 000). Jed-

nak zgodnie z zasad¹ 2 obliczenia s¹ wykonywane na

typach int, tak wiêc 16-bitowych. Dopiero po podzie-

leniu przez sta³¹ liczbê (100) wynik jest zamieniany na

postaæ oœmiobitow¹.

 Mno¿ymy dwie zmienne 16-bitowe. Prawid³owy

wynik mno¿enia to 120 000, co nie daje siê zapisaæ na 16

bitach. W tym momencie powstaje b³¹d. Nic nie pomaga

ju¿ podzielenie wyniku przez 10, co sprowadza go do

wartoœci 12 000, która mieœci siê na szesnastu bitach.

Ž Sytuacja praktycznie identyczna jak w . Rzu-

towanie umieszczone w z³ym miejscu. Najpierw

wykonane zostanie dzia³anie typu 16x16 z wynikiem

16-bitowym, dopiero uzyskany wynik zostanie rozsze-

rzony do 32 bitów. Wynik obliczenia bêdzie niepra-

wid³owy.

 Prawid³owe podejœcie do problemu. Ka¿emy

kompilatorowi wykonaæ mno¿enie typu 32=32x32.

32-bitowy wynik dzielimy przez 10. Dopiero teraz

wynik jest przycinany do 16 bitów. Wynik prawid³o-

wy. Dobrym zwyczajem jest tak¿e ujêcie ca³ego obli-

czenia w nawiasy oraz kolejne rzutowanie na typ

odpowiadaj¹cy zmiennej, do której zapisujemy wynik.

 Mniej elegancki sposób uzyskania prawid³owe-

go wyniku. Dziêki zasadzie 1 z tej ramki dzia³anie

32x16 zostanie potraktowane jako 32x32.

‘ Mo¿na powiedzieæ, ¿e to typowy przyk³ad, na

którym niejeden siê ju¿ wy³o¿y³. Z naszego punktu

widzenia mo¿e wszystko wygl¹da prawid³owo, ale

mimo tego, ¿e wynik mno¿enia 16x16 chcemy zapisaæ

do zmiennej 32-bitowej, rozszerzenie wyniku jest

wykonywane dopiero po skoñczeniu mno¿enia. Efekt?

Nieprawid³owy wynik.

’ Zrzutowanie jednej ze zmiennych na typ 32

bitowy. Ca³e dzia³anie zostanie wykonane jako

32x32=32. Wynik jest zgodny z oczekiwanym.

Specjalnie wybra³em przyk³ady bardzo charakte-

rystyczne. Nie wyczerpuj¹ one oczywiœcie wszystkich

mo¿liwoœci. Masz teraz przed sob¹ program, który

pozwoli Ci eksperymentowaæ. Spróbuj zmieniæ zapi-

sane w nim dzia³ania tak, aby przetestowaæ tak¿e jak

zachowa sie przy przepe³nieniu dodawanie. SprawdŸ,

jak WinAVR radzi sobie z rzutowaniem liczby ujem-

nej na typ bez znaku.

Aby prawid³owo operowaæ rzutowaniem w pro-

gramie, potrzeba praktyki. Konieczna jest zdolnoϾ

przewidywania, jakie wartoœci mog¹ przyj¹æ argumen-

ty w konkretnej aplikacji.

Rys. 44 Wynik dzia³ania programu

z listingu 69.

Listing 68 - sposób na pozbycie siê zbêdnego ostrze¿enia
fcheat_file fileRS ==

FCHEAT_STATIC_FDEVOPENWR((rs_put,, rs_get));;

FILE** fRS == ((

vvooiidd

**))&&fileRS;;

ABC... C

Kilka dodatkowych s³ów o printf

Jak pisaæ/czytaæ ³añcuch formatowania

W poprzedniej czêœci pokaza³em Ci, z jakich elemen-

tów sk³ada siê ³añcuch formatowania. Obrazek w tej

ramce pokazuje, jak nale¿y rozumieæ teksty widoczne

na listingu 69. Specjalnie wybra³em bardziej rozbu-

dowan¹ formê, gdzie korzystamy ze zmiennej long

int zamiast zwyklej int.

Kolejne zastosowanie rzutowania

– odpowiedni typ argumentów

Zauwa¿ ponadto, ¿e w przyk³adzie widocznym na lis-

tingu 69 rzutowanie wykorzystane zosta³o nie tylko

do obliczeñ. Wykonujemy je tak¿e podczas podawa-

nia argumentów dla funkcji fprintf. O ile w przypad-

ku zwyk³ej funkcji nie ma to znaczenia – zmienna

zostanie automatycznie przekszta³cona do takiej,

jakiej wymaga funkcja, o tyle w przypadku funkcji

z otwart¹ list¹ argumentów kompilator nie wie, jakie-

go typu argumentów funkcja siê spodziewa. Gdybyœ-

my wprowadzili w tym miejscu zmienn¹ oœmiobito-

w¹, zostanie ona jako taka umieszczona na stosie.

Umieszczenie przed ka¿d¹ ze zmiennych operatora

rzutowania zapewnia nas, ¿e nawet jeœli typ zmiennej

jest nieprawid³owy, zostanie on odpowiednio prze-

kszta³cony przed wys³aniem na stos. Dobrym zwycza-

jem jest stosowanie takiego rzutowania, nawet jeœli

jako argument podajemy zmienn¹ typu uint16_t albo

int16_t. My wiemy, ¿e jest on równowa¿ny z int. Jed-

nak jeœli przesi¹dziemy siê na kompilator dla innego

mikrokontrolera, ta zasada nie musi byæ spe³niona.

Pamiêtaj, ¿e zgodnie z ANSI C, typ int mo¿e mieæ 16

albo 32 bity – jest to zale¿ne od docelowej maszyny.

background image

kopii fcheat_file. Jest to dzia³anie bardzo

proste. Mam jednoczeœnie znakomit¹ infor-

macjê dla wszystkich chc¹cych unikn¹æ

koniecznoœci stosowania takich sztuczek

w przysz³oœci.

Mimo wszystko opisane dzia³anie by³o

znakomitym pretekstem do pokazania kodów

Ÿród³owych naszego œrodowiska oraz g³êb-

szego zapoznania siê z wewnêtrzn¹ zasad¹

dzia³ania strumieni.

Rzutowanie

Poruszyliœmy dziœ trudny temat. Na zakoñ-

czenie zajmiemy siê czymœ, co przy na-

szych aktualnych mo¿liwoœciach po-

winno byæ proste i przyjemne. Znaj¹c ju¿

funkcjê printf (oraz jej odpowiednik:

fprintf), uzyskaliœmy narzêdzie umo¿li-

wiaj¹ce nam wykonanie eksperymentów

dotycz¹cych rzutowania. Mamy teraz ca-

³¹ komputerow¹ konsolê do wyœwietlania

wyników ;).

R a m k i :

ABC... C Rzu-

towanie – kon-

wersja zakresu wartoœci

zmiennej oraz ABC... C

Rzutowanie wskaŸnika,

przedstawiaj¹ obiecane

od pewnego ju¿ czasu

informacjê o tym, jak do-

k³adniej dzia³a rzutowa-

nie i do czego s³u¿y.

Poeksperymentuj z ko-

dami z listingów 69

oraz 70 obrazuj¹cych

wykorzystanie obu ty-

pów rzutowania.

Jeœli wci¹¿ masz

w¹tpliwoœci, jak dzia³a

³añcuch formatowania

w funkcji printf, ram-

ka Kilka dodatko-

wych s³ów o printf byæ mo¿e rozwieje Two-

je w¹tpliwoœci. Spróbuj wróciæ do poprzed-

niej czêœci, gdzie pojawi³ siê dok³adny opis

tworzenia odpowiedniego opisu do³¹czanych

do printfa argumentów.

Na koniec, w ostatniej ju¿ ramce, przedsta-

wiam dodatkowe informacje o wskaŸniku na

typ pusty. Nazwa brzmi byæ mo¿e powa¿niej,

ni¿ sprawa naprawdê wygl¹da. Ramka wy-

jaœnia miêdzy innymi sens kodu z listingu 68.

Problem, o którym pisaliœmy, zauwa¿y³o wiêcej

u¿ytkowników WinAVR. Nieca³y tydzieñ przed

oddaniem tego tekstu do sk³adu nowa wersja jest ju¿

dostêpna i problem, z którym dziœ walczyliœmy,

zosta³ tam rozwi¹zany. Rozwi¹zanie to jest doœæ

podobne do zaproponowanego tutaj, tak wiêc zdo-

byta wiedza u³atwi zrozumienie wprowadzonych

zmian.

47

Programowanie

Elektronika dla Wszystkich

Listing 69 Wp³yw ograniczonego zakresu zmiennych
na przebieg obliczeñ oraz rozwi¹zanie za pomoc¹ rzutowania.

#include <avr/io.h>

#include <stdio.h>

#include <avr/pgmspace.h>

#include „rs.h”

#include „harddef.h”

#include „fcheat_stdio.h”

fcheat_file fileRS == FCHEAT_STATIC_FDEVOPENWR((rs_put,, rs_get));;

FILE** fRS == ((

vvooiidd

**))&&fileRS;;

iinntt

main((

vvooiidd

))

{

uint8_t u8a,, u8b;;

uint16_t u16a,, u16b,, u16c;;

uint32_t u32a;;

///////////////////////////////////////////////

// Inicjacja portu szeregowego

((......))

// Koniec inicjacji

///////////////////////////////////////////////

fputs_P((PSTR((

„START\r\n“

)),, fRS));;

u8a ==

100

;;

u8b ==

200

;;

Œ u8a == ((u8b ** u8a)) //

100

;;

fprintf_P((fRS,, PSTR((

„8x8 Wynik: %u\r\n”

)),,

((

uunnssiiggnneedd iinntt

))u8a));;

u16a ==

200

;;

u16b ==

600

;;

 u16c == ((u16a ** u16b)) //

10

;;

fprintf_P((fRS,, PSTR((

„16x16 Wynik: %u\r\n”

)),,

((

uunnssiiggnneedd iinntt

))u16c));;

Ž

u16c == ((uint32_t))((u16a ** u16b)) //

10

;;

fprintf_P((fRS,, PSTR((

„(32)(16x16) Wynik: %u\r\n”

)),,

((

uunnssiiggnneedd iinntt

))u16c));;

 u16c == ((((uint32_t))u16a ** ((uint32_t))u16b)) //

10

;;

fprintf_P((fRS,, PSTR((

„(32)16x(32)16 Wynik: %u\r\n”

)),,

((

uunnssiiggnneedd iinntt

))u16c));;

 u16c == ((((uint32_t))u16a ** u16b)) //

10

;;

fprintf_P((fRS,, PSTR((

„(32)16x16 Wynik: %u\r\n”

)),,

((

uunnssiiggnneedd iinntt

))u16c));;

‘ u32a == u16a ** u16b;;

fprintf_P((fRS,, PSTR((

„32=16x16 Wynik: %lu\r\n”

)),,

((

uunnssiiggnneedd lloonngg iinntt

))u32a));;

’ u32a == ((uint32_t))u16a ** u16b;;

fprintf_P((fRS,, PSTR((

„32=(32)16x16 Wynik: %lu\r\n”

)),,

((

uunnssiiggnneedd lloonngg iinntt

))u32a));;

fputs_P((PSTR((

„KONIEC\r\n“

)),, fRS));;

}

ABC... C

Rzutowanie wskaŸnika

Rzutowanie wskaŸnika powinno byæ prostsze do zro-

zumienia ni¿ przedstawione poprzednio rzutowanie

zakresu zmiennej. Pamiêtaæ musimy, ¿e dzia³anie

takie ma sens jedynie miêdzy wskaŸnikami na obiek-

ty które maj¹ miêdzy sob¹ coœ wspólnego. Spójrz na

listing 70. Jest to kolejna modyfikacja ostatniego pro-

gramu, która umo¿liwia wypróbowanie, jak dzia³a

rzutowanie wskaŸników. WskaŸnik traktowany jest

jako adres do pewnego obszaru pamiêci. Zauwa¿, ¿e

w nowo tworzonej strukturze, któr¹ nazwa³em nor-

malna, sk³adowe umieszczone s¹ w pamiêci w taki

sposób, ¿e sk³adowa o nazwie a pojawia siê jako

pierwsza, a sk³adowa o nazwie d jako ostatnia.

Struktura odwrotna odwraca tê kolejnoœæ oraz posia-

da o jedno pole mniej. Spójrz na rysunek 45 – wynik

dzia³ania naszego programu. Zauwa¿, jaki efekt daje

dostêp za pomoc¹ wskaŸnika typu odwrotna do

obszaru pamiêci zajmowanego przez strukturê n1

typu normalna. £atwo zauwa¿yæ, ¿e do sk³adowej d

zawartej w n1 nie jesteœmy w stanie w ogóle dostaæ

sie poprzez wskaŸnik po1. Sk³adowe struktury

umieszczane bêd¹ w pamiêci w kolejnoœci, na jakie

wskazuje kolejnoϾ ich deklarowania. Nie ma to nic

wspólnego z ich nazw¹.

Uwaga. Nawet gdyby obie struktury z listingu

70 by³y identyczne, dla kompilatora i tak s¹ to dwa

ró¿ne typy danych. Oznacza to, ¿e przy kopiowa-

niu ich wskaŸników niezbêdne jest rzutowanie albo

na typ docelowy (jak na listingu 67), albo poprzez

wskaŸnik typu void (jak na listingu 68).

Listing 70 Rzutowanie wskaŸników.

ttyyppeeddeeff ssttrruucctt

normalna

{

int8_t a,, b,, c,, d;;

} normalna;;

ttyyppeeddeeff ssttrruucctt

odwrotna

{

int8_t c,, b,, a;;

} odwrotna;;

iinntt

main((

vvooiidd

))

{

normalna n1;;

odwrotna** po1 == ((odwrotna**))&&n1;;

///////////////////////////////

// Inicjacja portu szeregowego

((......))

// Koniec inicjacji

///////////////////////////////

fputs_P((PSTR((

”START\r\n”

)),, fRS));;

n1..a ==

1

;;

n1..b ==

2

;;

n1..c ==

3

;;

n1..d ==

4

;;

fprintf_P((fRS,,

PSTR((

”odwrotna:\r\na=%d\r\nb=%d\r\nc=%d\r\n”

)),,

((

iinntt

))po1-->>a,, ((

iinntt

))po1-->>b,, ((

iinntt

))po1-->>c));;

fputs_P((PSTR((

”KONIEC\r\n”

)),, fRS));;

}

Rys. 45 Wynik
dzia³ania progra-
mu z listingu 70.

background image

Podsumowanie

Nie zniechêcaj siê, jeœli czegoœ nie zrozumia³eœ.

Wszystkie kody prezentowanych programów

bêd¹ jak zwykle dostêpne na stronach Elporta-

lu. Siêgaj do nich œmia³o, zmieniaj je, ekspery-

mentuj. To chyba najlepszy sposób na zdobycie

nowej wiedzy. Jeœli jeszcze tego nie zrobi³eœ –

postaraj siê zdobyæ kody Ÿród³owe biblioteki

stdio, zgodnie z opisem w tym odcinku.

W nadchodz¹cym odcinku zobaczymy

zmiany, jakie nast¹pi³y w nowym WinAVR.

Nowe œrodowisko jest ju¿ dostêpne, jednak nie

instaluj go jeszcze teraz, jeœli nie czujesz siê w C

pewnie. Niektóre z wprowadzonych zmian spo-

woduj¹ niestety, ¿e czêœæ z napisanych przez nas

programów nie bêdzie dzia³aæ prawid³owo.

Czêœæ 9 bêdzie trochê spokojniejsza.

Poznaliœmy du¿o nowych elementów C i teraz

bêdziemy zajmowaæ siê przyk³adami ich

zastosowania.

Rados³aw Koppel

radoslaw.koppel@elportal.pl

"&

Programowanie

Elektronika dla Wszystkich

ABC... C

WskaŸnik na typ pusty

Pozna³eœ ju¿ typ „zmiennej” o nazwie void. Powie-

dzia³em Ci wtedy, ¿e jest to specyficzny typ, który nie

istnieje. Nie ma sensu tworzenie zmiennej typu void

i taka próba zostanie zg³oszona przez kompilator jako

b³¹d. Jedynym przeznaczeniem tego dziwnego tworu

jest oznaczenie w pewnych miejscach, ¿e ¿adnych

zmiennych nie ma. Ze s³owa kluczowego void korzy-

staliœmy na razie przy tworzeniu funkcji. Funkcja

mo¿e pobieraæ albo zwracaæ wartoœæ typu pustego, co

oznacza, ¿e... nic nie pobiera albo nic nie

zwraca.

Zupe³nie inne znaczenie ma jednak wskaŸnik na

obiekt typu pustego. Utworzenie takiego wskaŸnika

jest jak najbardziej prawid³owym dzia³aniem:

vvooiidd

** pv;;

WskaŸnik taki ma pewne szczególne w³asnoœci.

Przede wszystkim nie wolno na nim wykonywaæ

jakichkolwiek dzia³añ, o jakich mowa by³a w przy-

padku wskaŸników. Nie mo¿na go inkrementowaæ czy

dekrementowaæ, dodawaæ do niego wartoœci, nie

mo¿na odj¹æ od siebie dwóch wskaŸników tego typu...

Teraz trochê o tym, co nam wolno. WskaŸnik typu

void mo¿e byæ bez problemu zrzutowany na dowolny

inny wskaŸnik. Wiêcej nawet. O ile normalnie nie

wolno, bez rzutowania, kopiowaæ do wskaŸnika dane-

go typu zawartoœci wskaŸnika innego typu (patrz lis-

ting 70 – w drugiej linii funkcji g³ównej odbywa siê

takie rzutowanie), o tyle wskaŸnik typu void jest jedy-

nym elementem, dla którego jest to mo¿liwe. Do

wskaŸnika typu void mo¿emy wpisaæ zawartoœæ inne-

go typu wskaŸnika bez koniecznoœci rzutowania...

oraz w drug¹ stronê – mo¿emy zapisaæ go do dowol-

nego wskaŸnika bez koniecznoœci rzutowania.

W³aœciwoœæ ta u³atwia czasami zapis tam, gdzie

typ wskazywanego obiektu nie ma znaczenia. Zobacz-

my na przyk³ad deklaracjê funkcji memcpy, któr¹

wstêpnie poznaliœmy w ramce omawiaj¹cej struktury:

Gdyby nie specyficzne w³asnoœci wskaŸnika do

typu pustego, jej wywo³anie musia³oby przebiegaæ

nastêpuj¹co:

memcpy((((

vvooiidd

**))&&s1,, ((

vvooiidd

**))&&s2,,

ssiizzeeooff

((s1))));;

Zamiast tego jednak, piszemy znacznie krócej:

memcpy((&&s1,, &&s2,,

ssiizzeeooff

((s1))));;

Ponadto, jak pokaza³ nam listing 68, wskaŸnik

typu void pozwala nam czasami pozbyæ siê niektó-

rych wyrzucanych przez kompilator ostrze¿eñ. Ele-

ment ten jest po prostu mniej pilnowany i kompilator

musi za³o¿yæ, ¿e stosuj¹c go, sami wiemy, co robimy.

vvooiidd

**memcpy((

vvooiidd

**,,

ccoonnsstt vvooiidd

**,, size_t));;

R

E

K

L

A

M

A

R E K L A M A


Wyszukiwarka

Podobne podstrony:
kursC czesc007
kursC czesc006
kursC czesc001
kursC czesc003
kursC czesc006a przeprowadzka
kursC czesc000 programowanie
kursC czesc018
kursC czesc002
kursC czesc013
kursC czesc011
kursC czesc010
kursC czesc007
kursC czesc018

więcej podobnych podstron