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 

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  pojawia  siê  jako

pierwsza,  a  sk³adowa  o  nazwie  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