06 Podstawy programowania dla hake (2)

background image

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

201

Rozdział 6.

Podstawy programowania dla
hakerów

Język C

Dla każdego hakera, młodego czy starego, mniej lub bardziej doświadczonego,
znajomość języka C jest jednym z fundamentów wiedzy. Niemal wszystkie narzędzia
i programy, stosowane w trakcie analiz sieci i włamań, powstają właśnie w tym
języku. Również w niniejszej książce większość przedstawianego kodu to właśnie
kod źródłowy w języku C. Programy te można modyfikować, dostosowywać do
własnych potrzeb i odpowiednio kompilować.

W pracy nad niniejszym rozdziałem wykorzystano obszerne fragmenty pracy guru
programowania Matthew Proberta. Mają one pełnić funkcję wprowadzenia do
programowania w języku C i umożliwić stosowanie przedstawianych w książce (i
załączonych na CD-ROM-ie) listingów programów. Pełny kurs języka znajdziesz w
niejednej książce wydawnictwa Helion.

Język C wyróżniają następujące cechy, które omawiamy niżej.









Blokowe konstrukcje sterowania wykonywaniem programu (typowe dla
większości języków wysokiego poziomu).









Swobodne operowanie podstawowymi obiektami „maszynowymi” (takimi jak
bajty) i możliwość odwoływania się do nich przy użyciu dowolnej, wymaganej
w danej sytuacji, perspektywy obiektowej (typowe dla języków asemblerowych).









Możliwość wykonywania operacji zarówno wysokiego poziomu (na przykład
arytmetyka zmiennoprzecinkowa), jak i niskiego poziomu (zbliżonych
do instrukcji języka maszynowego), co umożliwia tworzenie kodu wysoce
zoptymalizowanego bez utraty jego przenośności.

background image


202

Hack Wars. Tom 1. Na tropie hakerów

202

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Przedstawiony w niniejszym rozdziale opis języka C bazować będzie na funkcjach
oferowanych przez większość kompilatorów dla komputerów PC. Powinien dzięki
temu

umożliwić

rozpoczęcie

tworzenia

prostych

programów

osobom

nieposiadającym szerokiej wiedzy o języku (uwzględnimy między innymi funkcje
zapisane w pamięci ROM i funkcje DOS-u).

Przyjmujemy założenie, że masz, drogi Czytelniku, dostęp do kompilatora C i
odpowiedniej dokumentacji funkcji bibliotecznych. Programy przykładowe powstały
w Turbo C firmy Borland; większość elementów niestandardowych tego narzędzia
uwzględniono również w późniejszych edycjach Microsoft C.

Wersje języka C

W pierwotnej edycji języka C (jeszcze przed publikacją Kernighana i Ritchie’ego,
The C Programming Language, Prentice-Hall 1988 (polskie wydanie: Język ANSI C,
Wydawnictwa Naukowo-Techniczne 1994
)) zintegrowane operatory przypisania (+=,
*= itd.) definiowane były odwrotnie (tj. =+, =* itd.). Znakomicie utrudniało to
interpretację wyrażeń takich jak:

x=-y

co mogłoby znaczyć

x = x - y

lub

x = (-y)

Ritchie szybko zauważył dwuznaczność takiego zapisu i zmodyfikował go do postaci
znanej dzisiaj (+=, *= itd.). Mimo to wciąż stosowanych jest wiele odmian będących
rodzajem wypośrodkowania między pierwotną wersją języka C Kernighana i Ritchie’ego
a językiem ANSI C. Różnice między nimi dotyczą przede wszystkim:









wprowadzenia prototypów funkcji i zmiany preambuły definicji funkcji,
aby dostosować ją do stylu prototypów,









wprowadzenia znaku wielokropka (...) do oznaczenia list argumentów o zmiennej
długości,









wprowadzenia słowa kluczowego

void

(dla funkcji, które nie zwracają

wartości) i typu

void *

dla ogólnych zmiennych wskaźnikowych,









wprowadzenie w preprocesorze mechanizmów scalania ciągów, wklejania
elementu (token-pasting) i zamiany na ciąg (string-izing),









dodanie w preprocesorze translacji „trygrafów” (trigraph) — trójznakowych
sekwencji reprezentujących znaki specjalne,









dodanie w preprocesorze dyrektywy

#pragma

i formalizacja pseudofunkcji

declared()

,

background image


Rozdział 6.

Podstawy programowania dla hakerów

203

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

203









wprowadzenie ciągów i znaków wielobajtowych, zapewniających obsługę
języków narodowych,









wprowadzenie słowa kluczowego

signed

(jako uzupełnienie słowa

unsigned

, stosowane w deklaracjach liczb całkowitych) i

jednoargumentowego operatora plus (

+

).

Klasyfikowanie języka C

Szerokie możliwości języka C, dopuszczenie bezpośredniego operowania na adresach
i danych w pamięci oraz strukturalne podejście do programowania sprawiają, że język
ten klasyfikuje się jako „język programowania średniego poziomu”. Znajduje to wyraz
w mniejszej liczbie gotowych rozwiązań niż w językach wysokiego poziomu, takich
jak BASIC, ale wyższym poziomie strukturalnym niż niskopoziomowy Assembler.

Słowa kluczowe

Pierwotna edycja języka C definiuje 27 słów kluczowych. Komitet ANSI dodał do
nich 5 nowych. Wynikiem są dwa standardy języka, choć norma ANSI przejęła
większość elementów od Kerninghana i Ritchie’ego. Oto lista:

auto

double

int

struct

break

else

long

switch

case

enum

register

typedef

char

extern

return

union

const

float

short

unsigned

continue

for

signed

void

default

goto

sizeof

volatile

do

if

static

while

Warto zwrócić uwagę, że niektóre kompilatory C wprowadzają dodatkowe słowa
kluczowe, specyficzne dla środowiska sprzętowego. Warto zapoznać się z nimi.

Struktura języka C

Język C wymaga programowania strukturalnego. Oznacza to, że na program składa
się pewna grupa nawzajem wywołujących się bloków kodu. Dostępne są różnorodne
polecenia służące do konstruowania pętli i sprawdzania warunków:

do-while, for, while, if, case

Blok programu w języku C ujmowany jest w nawiasy klamrowe (

{}

). Może on być

kompletną procedurą, nazywaną funkcją lub częścią kodu funkcji. Przyjrzyjmy się
przykładowi:

background image


204

Hack Wars. Tom 1. Na tropie hakerów

204

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

if (x < 10)
{
a = 1;
b = 0;
}

Instrukcje wewnątrz nawiasów klamrowych wykonane zostaną tylko wtedy, gdy
spełniony zostanie warunek

x < 10

.

Jako kolejny przykład przedstawimy pełny blok kodu funkcji, zawierający wewnątrz
blok pętli:

int GET_X()
{
int x;

do
{
printf ("\nWprowadz liczbe z zakresu od 0 do 10 ");
scanf("%d",&x);
}
while(x < 0 || x > 10);
return(x);
}

Zwróćmy uwagę, że każdy wiersz instrukcji zakończony jest średnikiem, o ile nie jest
sygnałem początku bloku kodu (w takim przypadku kolejnym znakiem jest nawias
klamrowy). Język C rozpoznaje wielkość liter, ale nie bierze pod uwagę białych
znaków. Odstępy między poleceniami są pomijane, stąd konieczność użycia średnika,
aby oznaczyć koniec wiersza. Tego rodzaju podejście powoduje, że następujące
polecenia interpretowane są jako identyczne:

x = 0;
x =0;
x=0;

Ogólna postać programu w języku C jest następująca:









instrukcje preprocesora kompilacji,









globalne deklaracje danych.









deklaracje i definicje funkcji (włączając w to zawartość programu):

typ-zwracany main (lista parametrów)
{
instrukcje
}
typ-zwracany f1 (lista parametrów)
{
instrukcje
}
typ-zwracany f2 (lista parametrów)
{
instrukcje
}
.

background image


Rozdział 6.

Podstawy programowania dla hakerów

205

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

205

typ-zwracany fn (lista parametrów)
{
instrukcje
}

Komentarze

Podobnie jak większość języków, C pozwala umieszczać w kodzie programu
komentarze. Ich ogranicznikami są symbole

/*

i

*/

:

/* To jest wiersz komentarza w j

ę

zyku C */

(Równie często korzysta się z komentarzy jednoliniowych, otrzymywanych poprzez
sekwencję //, np.:

//To te

ż

jest wiersz komentarza P.B.)

Biblioteki

Programy w języku C kompiluje się i łączy z funkcjami bibliotecznymi,
dostarczanymi wraz z kompilatorem. Na biblioteki składają się funkcje standardowe,
których działanie zdefiniowane zostało w normie ANSI. Ich powiązanie z
konkretnym kompilatorem zapewnia dostosowanie do platformy sprzętowej. Wynika
stąd, że standardowa funkcja biblioteczna

printf()

działa tak samo w systemach

DEC VAX i IBM PC, choć różni się jej, zapisany w bibliotece, kod maszynowy.
Programista C nie musi zagłębiać się w zawartość bibliotek, wymagana jest jedynie
umiejętność ich stosowania i znajomość działania funkcji, które pozostają niezmienne
na każdym komputerze.

Tworzenie programów

Kompilacja

Zanim zajmiemy się funkcjami, poleceniami, sekwencjami i innymi zaawansowanymi
zagadnieniami, przyjrzyjmy się praktycznemu przykładowi, w którym doprowadzimy
do skompilowania kodu. Kompilowanie programów C jest stosunkowo prostą
czynnością, jednak różni się zależnie od stosowanego kompilatora. Kompilatory
wyposażone w menu umożliwią skompilowanie, skonsolidowanie i uruchomienie
programu jednym wciśnięciem klawisza. Podchodząc jednak do zagadnienia
możliwie uniwersalnie i tradycyjnie, przeprowadzimy poniżej całą procedurę w
oparciu o wiersz poleceń.

W dowolnym edytorze wprowadzamy poniższy fragment kodu i zapisujemy plik jako
przyklad.c:

/*

background image


206

Hack Wars. Tom 1. Na tropie hakerów

206

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

przykładowy komunikat tekstowy
*/
#include<stdio.h>
void main()
{
printf( "Hello!\n" );
}

Kolejnym krokiem jest skompilowanie kodu do postaci pliku programu — dopiero
wtedy można będzie go uruchomić (czy też wykonać). W wierszu poleceń w tym
samym katalogu, w którym zapisaliśmy plik przyklad.c, wprowadzamy następujące
polecenie kompilacji:

cc przyklad.c

Nie wolno zapominać, że składnia polecenia kompilacji zależy od kompilatora. Nasz
przykład opiera się na standardzie języka C. Współcześnie jednak popularne jest
stosowanie składni wywodzącej się z kompilatora GNU C:

gcc przyklad.c

Po wykonaniu takiego polecenia nasz kod jest już skompilowany i ma postać pliku
programu, który możemy uruchomić. Wynik jego działania łatwo wydedukować
z prostego kodu:

Hello!
Press any key to continue

To wszystko! Kompilowanie małych programów w C nie jest trudne, należy jedynie
mieć świadomość szkodliwych niekiedy efektów ich działania. Programy
przedstawiane na stronach tej książki i załączone na CD-ROM-ie są oczywiście
znacznie bardziej skomplikowane, jednak zasady pozostają te same.

Typy danych

W języku C wyróżnia się cztery podstawowe typy danych: znakowy, całkowity,
zmiennoprzecinkowy i nieokreślony. Odpowiadają im słowa kluczowe:

char

,

int

,

float

i

void

. Dalsze typy danych tworzy się na tej podstawie, dodając

modyfikatory:

signed

(ze znakiem),

unsigned

(bez znaku),

long

(długa) i

short

(krótka). Modyfikator

signed

jest elementem domyślnym, co sprawia, że jego użycie

może się okazać konieczne jedynie w wypadku gdy zastosowano przełącznik
kompilacji nakazujący domyślne korzystanie ze zmiennych bez znaku. Rozmiar
każdego typu danych zależy od platformy sprzętowej, jednak norma ANSI wyznacza
pewne zakresy minimalne, zestawione w tabeli 6.1.

W praktyce tak określone konwencje oznaczają, że typ danych

char

nadaje się

najlepiej do przechowywania zmiennych typu znacznikowego, takich jako kody
stanu, o ograniczonym zakresie wartości. Można również korzystać z typu

int

. Gdy

jednak zakres wartości nie przekracza 127 (lub 255 dla

unsigned char

), każda

deklarowana w ten sposób zmienna przyczynia się do niepotrzebnego obciążania
pamięci.

background image


Rozdział 6.

Podstawy programowania dla hakerów

207

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

207

Natomiast trudniejsze jest pytanie o to, z którego typu liczb rzeczywistych korzystać

float

,

double

czy

long double

. Gdy wymagana jest dokładność, na

przykład w aplikacji stosowanej w księgowości, instynktownie powinniśmy użyć typu

long

double

,

Tabela 6.1. Rozmiary i zakresy typów danych języka C

Typ

Rozmiar

Zakres

char

8

–128 do 127

unsigned char

8

0 do 255

int

16

–32 768 do 32 767

unsigned int

16

0 do 65 535

long int

32

–2 147 483 648 do 2 147 483 647

unsigned long int

32

0 do 4 294 967 295

float

32

precyzja 6-cyfrowa

double

64

precyzja 10-cyfrowa

long double

80

precyzja 10-cyfrowa

wiąże się to jednak z wykorzystaniem przez każdą zmienną 10 bajtów. Obliczenia na
liczbach rzeczywistych nie są tak dokładne jak na liczbach całkowitych, warto więc
zawsze rozważyć użycie typu

int

i „obejście” problemu. Typ danych

float

nie jest

zbyt dobry, gdyż jego 6-cyfrowa precyzja nie zapewnia dokładności, na której zawsze
będziemy mogli polegać. Ogólną zasadą jest korzystanie z typów całkowitych tak
szeroko, jak tylko jest to możliwe, a gdy pojawia się konieczność użycia liczb
rzeczywistych, wprowadzenie typu

double

.

Deklarowanie zmiennej

Każda zmienna musi zostać zadeklarowana przed użyciem. Ogólną postacią
deklaracji zmiennej jest:

typ nazwa;

Aby więc przykładowo zadeklarować zmienną

x

typu

int

, przeznaczoną do

przechowywania wartości z zakresu od –32 768 do 32 767, użyjemy instrukcji:

int x;

Ciągi znakowe deklarować można jako tabele znaków:

char nazwa[liczba_elementów];

Deklaracja ciągu o nazwie

nazwisko

i długości 30 znaków, wyglądać będzie

następująco:

char nazwisko[30];

Tablice danych innych typów mogą mieć więcej niż jeden wymiar. Oto deklaracja
dwuwymiarowej tablicy liczb całkowitych:

background image


208

Hack Wars. Tom 1. Na tropie hakerów

208

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

int x[10][10];

Elementy tablicy wywołujemy jako:

x[0][0]
x[0][1]
x[n][n]

Wyróżnia się trzy poziomy dostępu do zmiennych: lokalny, na poziomie modułu
i globalny. Zmienna deklarowana wewnątrz bloku kodu będzie dostępna wyłącznie
dla instrukcji wewnątrz tego bloku. Zmienna deklarowana poza blokami kodu funkcji,
ale poprzedzona modyfikatorem

static

, będzie dostępna wyłącznie instrukcjom

wewnątrz modułu kodu źródłowego. Zmienna deklarowana poza blokami kodu
funkcji i niepoprzedzona modyfikatorem będzie dostępna dla dowolnych instrukcji w
dowolnym module programu. Na przykład:

int blad;
static int a;

void main() (Co prawda funkcja main działa i bez deklaracji warto

ś

ci

zwracanej, jednak w takim przypadku wy

ś

wietla si

ę

ostrze

ż

enie (bo

kompilator domy

ś

lnie przyjmuje j

ą

jako int i szuka funkcji return.

Aby tego unikn

ąć

, w ka

ż

dym nast

ę

pnym przykładzie dopisuj

ę

void P.B.)

{
int x;
int y;
}

funkcjaa()
{
/* Sprawdzenie czy zmienna a jest równa 0 */
if (a == 0)
{
int b;
for(b = 0; b < 20; b++)
printf ("\nHello World");
}

}

W powyższym przykładzie zmienna

blad

jest dostępna dla wszystkich,

kompilowanych jako jeden program, modułów kodu źródłowego. Zmienna

a

jest

osiągalna dla wszystkich instrukcji w funkcjach

main()

i

funkcjaa()

, ale pozostaje

niewidoczna z poziomu innych modułów. Zmienne

x

i

y

są dostępne wyłącznie

instrukcjom wewnątrz funkcji

main()

. Z kolei zmienna

b

może być użyta wyłącznie

przez instrukcje wewnątrz bloku kodu po instrukcji

if

.

Jeżeli drugi blok kodu faktycznie ma skorzystać ze zmiennej

blad

, wymagane będzie

umieszczenie w nim deklaracji zmiennej globalnej

extern

:

extern int blad;

funkcjab()
{
}

background image


Rozdział 6.

Podstawy programowania dla hakerów

209

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

209

Język C nie stawia szczególnych przeszkód w przypisywaniu do siebie różnych typów
danych. Przykładowo możemy zadeklarować zmienną typu

char

, co spowoduje

przypisanie do przechowywania jej wartości jednego bajtu danych. Można podjąć
próbę przypisania do niej wartości spoza tego zakresu:

void main()
{

x = 5000;

}

Zmienna

x

może przechowywać wartości z zakresu od –127 do 128, a więc wartość

5000 nie zostanie przypisana.

x

przyjmie jednak wartość 136.

Potrzeba przypisania różnych typów danych nie jest niczym oryginalnym. Aby
powstrzymać kompilator od generowania ostrzeżeń o takich operacjach, można
skorzystać z instrukcji konwersji (cast statement), informując kompilator o tym, że
operacja wykonywana jest świadomie. Instrukcję taką budujemy, umieszczając przed
zmienną lub wyrażeniem nazwę typu danych ujętą w nawiasy:

void main()
{
float x;
int y;

x = 100 / 25;

y = (int)x;
}

Operacja rzutowania

(int)

informuje kompilator o konieczności konwersji wartości

zmiennej zmiennoprzecinkowej

x

do liczby całkowitej, zanim ta zostanie przypisana

do zmiennej

y

.

Parametry formalne

Funkcja w języku C może przyjmować parametry przekazywane przez funkcję
wywołującą. Parametry te deklaruje się podobnie jak zmienne, podając ich nazwy
wewnątrz towarzyszących nazwie funkcji nawiasów:

int MNOZ(int x, int y)
{
/* Zwró

ć

parametr x pomno

ż

ony przez parametr y */

return(x * y);
}

void main()
{
int a;
int b;
int c;

a = 5;
b = 7;
c = MNOZ(a,b);

background image


210

Hack Wars. Tom 1. Na tropie hakerów

210

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc


printf("%d razy %d równa si

ę

%d\n",a,b,c);

}

Modyfikatory dostępu

Stosuje się dwa modyfikatory dostępu:

const

i

volatile

. Wartość zmiennej

zadeklarowanej jako

const

nie może zostać zmieniona przez program, wartość

zmiennej zadeklarowanej jako

volatile

może zostać zmieniona przez program.

Dodatkowo, zadeklarowanie zmiennej jako

volatile

uniemożliwia kompilatorowi

zaalokowanie jej do rejestru i ogranicza przeprowadzaną na niej optymalizację.

Typy klas przechowywania zmiennych

Język C przewiduje cztery rodzaje przechowywania zmiennych:

extern

,

static

,

auto

i

register

. Typ

extern

umożliwia modułowi kodu źródłowego dostęp do

zmiennej zadeklarowanej w innym module. Zmienne

static

dostępne są wyłącznie

z poziomu bloku kodu, w którym zostały zadeklarowane. Dodatkowo, jeżeli zmienna
ma zasięg lokalny, zachowuje swoją wartość między kolejnymi wywołaniami bloku
kodu.

Zmienne rejestrowe (

register

) są, gdy tylko jest to możliwe, przechowywane w

rejestrach procesora. Zapewnia to najszybszy dostęp do ich wartości. Typ

auto

stosuje się wyłącznie w odniesieniu do zmiennych lokalnych. Nakazuje on
zachowywanie wartości zmiennej lokalnej. Ponieważ jest to modyfikator domyślny,
rzadko można spotkać go w programach.

Operatory

Operatory to elementy kodu, które nakazują wykonanie obliczeń na zmiennych. W
języku C dostępne są następujące:

&

adres,

*

pośredniość,

+

plus jednoargumentowy,

-

minus jednoargumentowy,

~

dopełnienie bitowe,

!

negacja logiczna,

++

jako prefiks — preinkrementacja, jako sufiks — postinkrementacja,

––

jako prefiks — predekrementacja, jako sufiks — postdekrementacja,

+

dodawanie,

-

odejmowanie,

*

mnożenie,

/

dzielenie,

background image


Rozdział 6.

Podstawy programowania dla hakerów

211

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

211

%

reszta z dzielenia (modulo),

<<

przesunięcie w lewo,

>>

przesunięcie w prawo,

&

bitowa operacja AND,

|

bitowa operacja OR,

^

bitowa operacja XOR,

&&

logiczna operacja AND,

||

logiczna operacja OR,

=

przypisanie,

*=

przypisanie iloczynu,

/=

przypisanie ilorazu,

%=

przypisanie reszty (modułu),

+=

przypisanie sumy,

-=

przypisanie różnicy,

<<=

przypisanie przesunięcia w lewo,

>>=

przypisanie przesunięcia w prawo,

&=

przypisanie wyniku bitowej operacji AND,

|=

przypisanie wyniku bitowej operacji OR,

^=

przypisanie wyniku bitowej operacji XOR,

<

mniejsze niż,

>

większe niż,

<=

mniejsze lub równe,

>=

większe lub równe,

==

równe,

!=

różne od,

.

bezpośredni selektor składnika,

->

pośredni selektor składnika,

a ? x :
y

jeżeli

a

to prawda, to

x

, w przeciwnym razie

y,

[ ]

definiowanie tablic,

()

nawiasy oddzielają warunki i wyrażenia,

...

wielokropek wykorzystuje się w listach parametrów formalnych prototypów
funkcji do deklarowania zmiennej liczby parametrów lub parametrów
zmiennych typów.

Aby zilustrować sposób korzystania z podstawowych operatorów, przyjrzyjmy się
krótkiemu programowi:

void main()
{
int a;
int b;
int c;

background image


212

Hack Wars. Tom 1. Na tropie hakerów

212

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

a = 5; /*Przypisanie zmiennej a warto

ś

ci 5*/

b = a/2; /*Przypisanie zmiennej b warto

ś

ci a podzielonej przez 2*/

c = b*2; /*Przypisanie zmiennej c warto

ś

ci b pomno

ż

onej przez 2*/


if (a == c) /*Sprawdzenie czy a ma tak

ą

sam

ą

warto

ść

jak c*/


puts("Zmienna a jest parzysta");
else
puts("Zmienna a jest nieparzysta");
}

Typowym sposobem zwiększenia wartości zmiennej o 1 jest wiersz:

x = x + 1

Język C dostarcza operatora inkrementacji, wystarczy więc napisać:

x++

W podobny sposób korzystamy z operatora dekrementacji, czyli zmniejszania
wartości o 1:

x--

Pozostałe operatory matematyczne wykorzystujemy podobnie. Warto jednak
pamiętać o wprowadzanych przez język C możliwościach zapisu skróconego:

Zapis typowy

Zapis w języku C

x = x + 1

x++

x = x - 1

x--

x = x * 2

x *= 2

x = x / y

x /= y

x = x % 5

x %= 5

Funkcje

Funkcje to procedury kodu źródłowego tworzące program w języku C. Ich ogólną
postacią jest:

zwracany_typ nazwa_funkcji(lista_parametrów)
{
instrukcje
}

Zwracany_typ

to typ zwracanej przez funkcję wartości:

char

,

int

,

double

,

void

itp. Kod wewnątrz funkcji C pozostaje niewidoczny dla innych funkcji C. Nie można
wykonywać skoków z jednej funkcji do wnętrza innej. Funkcje mogą jedynie
wywoływać inne funkcje. Nie wolno również definiować funkcji wewnątrz innych
funkcji. Definicja musi zostać umieszczona bezpośrednio na poziomie modułu kodu.

Parametry przekazywane są do funkcji jako wartości lub jako odwołania (wskaźniki).
Gdy parametr jest przekazywany jako wartość, funkcja otrzymuje kopię tej wartości.

background image


Rozdział 6.

Podstawy programowania dla hakerów

213

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

213

Parametr przekazywany jako odwołanie jest jedynie wskaźnikiem do właściwego
parametru. Pozwala to na zmianę jego wartości z poziomu wywołanej funkcji. W
poniższym przykładzie przekazujemy dwa parametry jako wartość do funkcji

funkcjaa()

, która następnie podejmuje próbę zmiany wartości przekazanych

zmiennych. Drugim krokiem jest przekazanie tych samych parametrów do funkcji

funkcjab()

, która również podejmuje próbę zmiany wartości zmiennych:

#include <stdio.h>

int funkcjaa(int x, int y)
{
/* Funkcja przyjmuje dwa parametry jako warto

ś

ci, x i y */


x = x * 2;
y = y * 2;

printf ("\nWarto

ść

x w funkcjaa() %d. Warto

ść

y w funkcjaa()

%d",x,y);

return(x);
}


int funkcjab(int *x, int *y)
{
/* Funkcja przyjmuje dwa parametry jako odwołania, x i y */

*x = *x * 2;
*y = *y * 2;

printf ("\nWarto

ść

x w funkcjab() %d. Warto

ść

y w funkcjab()

%d",*x,*y);

return(*x);
}

void main()
{
int x;
int y;
int z;

x = 5;
y = 7;

z = funkcjaa(x,y);
z = funkcjab(&x,&y);

printf ("\nWarto

ść

x %d, warto

ść

y %d, warto

ść

z %d",x,y,z);

}

funkcjab()

nie zmienia wartości otrzymanych parametrów. Modyfikowana jest

zawartość wskazywanych parametrami adresów pamięci. O ile

funkcjaa()

otrzymuje z funkcji

main()

wartości zmiennych

x

i

y

,

funkcjab()

otrzymuje z

funkcji

main()

ich adresy w pamięci.

background image


214

Hack Wars. Tom 1. Na tropie hakerów

214

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Przekazywanie tablicy do funkcji

Następujący program przekazuje do funkcji tablicę, a funkcja nadaje wartości
elementom tablicy:

#include <stdio.h>

void funkcjaa(int x[])
{
int n;

for(n = 0; n < 100; n++)
x[n] = n;
}

void main()
{
int tablica[100];
int licznik;

funkcjaa(tablica);

for(licznik = 0; licznik < 100; licznik++)
printf ("\nWarto

ść

elementu %d wynosi %d",licznik,

tablica[licznik]);
}

Parametr funkcji,

int x[]

, jest tablicą dowolnej długości. Deklaracja taka jest

możliwa, ponieważ kompilator przekazuje jedynie adres początkowy tablicy, a nie
wartości poszczególnych jej elementów. Konsekwencją tego jest fakt, że funkcja
może zmieniać wartości elementów tablicy. Aby uniemożliwić funkcji wprowadzanie
modyfikacji, konieczne jest użycie typu

const

:

funkcjaa(const int x[])
{
}

Przy takiej deklaracji wiersz zmieniający zawartość tablicy wywołałby błąd
kompilacji. Określenie parametru jako wartości stałej nie likwiduje jednak
pośredniości jego przekazania. Ilustruje to poniższy program:

#include <stdio.h>

void funkcjaa(const int x[])
{
int *ptr;
int n;

/*Ten wiersz generuje ostrze

ż

enie 'suspicious pointer conversion'*/

/*(niebezpieczna konwersja wska

ź

nika)*/

/*x jest wska

ź

nikiem const, a ptr - nie*/

ptr = x;

for(n = 0; n < 100; n++)
{
*ptr = n;
ptr++;
}
}

background image


Rozdział 6.

Podstawy programowania dla hakerów

215

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

215


void main()
{
int tablica[100];
int licznik;

funkcjaa(tablica);

for(licznik = 0; licznik < 100; licznik++)
printf("\nWarto

ść

elementu %d wynosi

%d",licznik,tablica[licznik]);
}

Przekazywanie parametrów funkcji main()

Język C umożliwia przekazanie parametrów do uruchamianego programu z poziomu
systemu operacyjnego. Do ich odczytania wykorzystuje się zmienne

argc

i

argv[]

:

#include <stdio.h>

void main(int argc, char *argv[])
{
int n;

for(n = 0; n < argc; n++)
printf ("\nWartosc parametru %d to %s",n,argv[n]);
}

Parametr

argc

przechowuje liczbę przekazanych programowi parametrów. W tablicy

argv[]

zapisane są ich adresy;

argv[0]

jest zawsze nazwą uruchamianego

programu. Mechanizm ten ma szczególne znaczenie dla aplikacji wymagających
dostępu do plików systemowych i danych. Rozważmy następującą sytuację: mała
aplikacja obsługi baz danych przechowuje swoje dane w pojedynczym pliku dane.dat;
aplikacja ta musi zostać tak zaprojektowana, aby można było uruchomić ją z
dowolnego katalogu, czy to na dysku twardym, czy dyskietce; musi również zapewnić
uruchamianie za pośrednictwem ścieżki wyszukiwania DOS-u (

path

). Do poprawnej

pracy aplikacji jest więc wymagane, aby zawsze mogła odnaleźć plik dane.dat.
Rozwiązanie takie zapewni przyjęcie założenia, że plik danych jest zawsze w
identycznym katalogu co sam program. Poniższy fragment ilustruje wykorzystanie
parametrów

argc

i

argv

w celu utworzenia ścieżki do pliku danych aplikacji:

#include <string.h>

char nazwa_pliku[160];

void main(int argc, char *argv[])
{
char *plik_danych = "DATA.DAT";
char *p;

strcpy(nazwa_pliku,argv[0]);

p = strstr(nazwa_pliku, ".exe"); (kompilator tworzy plik z

rozszerzeniem o małych literach P.B.)
if (p == NULL)
{
/* Plik uruchomieniowy jest plikiem .COM */

background image


216

Hack Wars. Tom 1. Na tropie hakerów

216

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc


p = strstr(nazwa_pliku, ".com");
}

/* Wyszukujemy ostatni uko

ś

nik */

while(*(p-1) != '\\')
p--;

strcpy(p,plik_danych);
}

Przedstawiony program tworzy i zapisuje w zmiennej

nazwa_pliku

ciąg postaci

ś

cieżka\dane.dat. Jeżeli więc przykładową nazwą pliku uruchomieniowego będzie

test.exe i zostanie on umieszczony w katalogu \borlandc, zmiennej

nazwa_pliku

przypisany zostanie ciąg

\borlandc\dane.dat

.

Wyjście z funkcji

Polecenie

return

powoduje natychmiastowe wyjście z funkcji. Jeżeli w deklaracji

funkcji podano typ zwracanej wartości, w poleceniu

return

należy użyć parametru

tego samego typu.

Prototypy funkcji

Prototypy funkcji umożliwiają kompilatorowi C sprawdzanie poprawności
przekazywanych, do i z funkcji, danych. Ma to istotne znaczenie jako zabezpieczenie
przed przekroczeniem zakresu zaalokowanego dla zmiennej obszaru pamięci. Prototyp
funkcji umieszcza się na początku programu po poleceniach preprocesora (takich jak

#include

) i przed deklaracjami funkcji.

Polecenia preprocesora C

W języku C w treści kodu źródłowego można umieszczać polecenia dla kompilatora.
Określa się je terminem polecenia preprocesora. Norma ANSI definiuje następujące:

#if
#ifdef
#ifndef
#else
#elif
#endif
#include
#define
#undef
#line
#error
#pragma

Wszystkie polecenia preprocesora rozpoczyna znak krzyżyka (hash), czyli #. Każde
wymaga osobnego wiersza kodu (uzupełnionego ewentualnie komentarzem). Poniżej
przedstawiamy krótkie omówienie.

background image


Rozdział 6.

Podstawy programowania dla hakerów

217

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

217

#define

Polecenie

#define

tworzy identyfikator, który kompilator zastąpi podanym ciągiem

w danym module kodu źródłowego. Na przykład:

#define FALSE 0
#define TRUE !FALSE

Kompilator zastąpi wszystkie dalsze wystąpienia ciągu

FALSE

znakiem

0

, a wszystkie

dalsze wystąpienia ciągu

TRUE

— ciągiem

!0

. Zastępowaniu nie podlegają

identyfikatory wewnątrz znaków cudzysłowu, a więc wiersz:

printf ("TRUE");

nie zostanie zmieniony, ale

printf ("%d",FALSE);

podlega modyfikacji.

Polecenie

#define

może również zostać użyte do definiowania makr, także makr z

parametrami. Do zapewnienia poprawności zastąpień zaleca się ujmowanie
parametrów w nawiasy. W poniższym przykładzie deklarujemy makro o nazwie

larger()

, przyjmujące dwa parametry i zwracające ten z nich, którego wartość jest

większa.

#include <stdio.h>

#define larger(a,b) (a > b) ? (a) : (b)

int main()
{
printf("\n%d jest wi

ę

ksze",larger(5,7));

}

#error

Polecenie

#error

powoduje przerwanie procesu kompilacji i wyświetlenie podanego

tekstu, na przykład:

#error SKOMPILOWANE DO MODULU B

powoduje zatrzymanie kompilacji i wyświetlenie:

SKOMPILOWANE DO MODULU B

#include

Polecenie

#include

nakazuje kompilatorowi odczytanie i przetworzenie zawartości

dodatkowego pliku źródłowego. Nazwa pliku musi zostać ujęta w cudzysłów lub
wstawiona między znaki

<>

, na przykład:

#include "module2.c"
#include <stdio.h>

background image


218

Hack Wars. Tom 1. Na tropie hakerów

218

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Jeżeli nazwa pliku została wpisana między znaki

<>

, kompilator wyszukuje go w

katalogu określonym w konfiguracji. Jest to zasada ogólna.

#if, #else, #elif, #endif

Grupa poleceń

#if

dostarcza mechanizmu kompilacji warunkowej. Stosowana jest

dość typowa składnia:

#if wyra

ż

enie_stale

instrukcje
#else
instrukcje
#endif

Polecenie

#elif

to skrócona postać

#else if

:

#if wyra

ż

enie

instrukcje
#elif wyra

ż

enie

instrukcje
#endif

#ifdef, #ifndef

Rozwinięciem tych poleceń jest

#if defined

(jeżeli zdefiniowano) i

#if not

defined

(jeżeli nie zdefiniowano). Konstrukcje składniowe są następujące:

#ifdef nazwa_makra
instrukcje
#else
instrukcje
#endif

#ifndef nazwa_makra
instrukcje
#else
instrukcje
#endif

nazwa_makra

to identyfikator utworzony za pomocą deklaracji

#define

.

#undef

Polecenie

#undef

usuwa definicję makra utworzonego przy użyciu wcześniejszej

instrukcji

#define

.

#line

Polecenie

#line

modyfikuje zmienne globalne kompilatora

__LINE__

i

__FILE__

.

Ogólną postacią instrukcji jest:

#line numer "nazwa_pliku"

background image


Rozdział 6.

Podstawy programowania dla hakerów

219

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

219

Wartość

numer

zostaje umieszczona w zmiennej

__LINE__

, a

"nazwa_pliku"

— w

zmiennej

__FILE__

.

#pragma

Umożliwia korzystanie z poleceń specyficznych dla kompilatora.

Instrukcje sterujące

Jak w każdym języku programowania, również w C, znajdziemy instrukcje
sprawdzające wartość wyrażenia. Wynikiem takiego sprawdzenia jest wartość

TRUE

lub

FALSE

. Wartości

FALSE

odpowiada liczba

0

, a

TRUE

— liczba różna od zera.

Instrukcje wykonania warunkowego

Podstawową instrukcją wykonania warunkowego jest

if

o następującej składni:

if (wyra

ż

enie)

instrukcje
else
instrukcje

gdzie

instrukcje

może być instrukcją pojedynczą lub ujętym w nawiasy klamrowe

blokiem kodu. Element

else

jest opcjonalny. Jeżeli wartością

wyra

ż

enie

jest

TRUE

,

wykonywana jest instrukcja podana bezpośrednio po nim. W pozostałych przypadkach
wykonywana jest instrukcja podana po słowie

else

(o ile ta część składni została

użyta).

Alternatywą dla konstrukcji

if...else

jest polecenie

?:

w postaci:

wyra

ż

enie ? instrukcja_prawda : instrukcja_fałsz

Jeżeli wartością wyrażenia jest

TRUE

, wykonywana jest pierwsza instrukcja. W

pozostałych przypadkach wykonywana jest instrukcja druga. Ilustruje to przykład:

#include <stdio.h>

void main()
{
int x;
x = 6;

printf("\nx to liczba %s", x % 2 == 0 ? "parzysta" :
"nieparzysta");
}

Język C oferuje również instrukcję

switch

, ułatwiającą porównywanie wyrażenia

z pewną listą wartości. Wykonywane są instrukcje powiązane z pierwszą dopasowaną
wartością listy. Składnia polecenia

switch

jest następująca:

switch (wyra

ż

enie)

{
case warto

ść

1 : instrukcje

background image


220

Hack Wars. Tom 1. Na tropie hakerów

220

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

break;
case warto

ść

2 : instrukcje

break;
.
.

case warto

ść

n : instrukcje

break;
default : instrukcje
}

Użycie instrukcji

break

nie jest wymagane, ale jej pominięcie powoduje dalsze

porównywanie wyrażenia z kolejnymi elementami listy wartości.

#include <stdio.h>

void main()
{
int x;

x = 6;

switch (x)
{
case 0 : printf ("\nx równa si

ę

zero");

break;
case 1 : printf ("\nx równa si

ę

jeden");

break;
case 2 : printf ("\nx równa si

ę

dwa");

break;
case 3 : printf ("\nx równa si

ę

trzy");

break;
default : printf ("\nx jest wi

ę

ksze od trzech");

}
}

Instrukcje

switch

można zagnieżdżać.

Instrukcje iteracji

W języku C stosuje się trzy instrukcje pętli (iteracji):

for

,

while

i

do-while

.

Składnia pętli

for

jest następująca:

for(inicjalizacja;warunek;inkrement)
instrukcje

Jest ona szczególnie przydatna, gdy korzystamy z licznika, jak w poniższym
przykładzie wyświetlającym zestaw znaków ASCII:

#include <stdio.h>

void main()
{
int x;

for(x = 32; x < 128; x++)
printf ("%d\t%c\t",x,x);

background image


Rozdział 6.

Podstawy programowania dla hakerów

221

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

221

}

Dopuszczalna jest również nieskończona pętla

for

:

for(;;)
{
instrukcje
}

Język C pozwala używać też pustych instrukcji. Poniższa pętla usuwa z ciągu
początkowe znaki odstępu:

for(; *str == ' '; str++)
;

Warto zwrócić uwagę na średniki odpowiadające inicjalizacji pętli i pustej instrukcji.

Pętla

while

ma konstrukcję nieco prostszą:

while(warunek)
instrukcje

Instrukcja lub blok instrukcji (ujęty w nawiasy klamrowe) będą powtarzane do czasu,
gdy wyrażenie warunku przyjmie wartość

FALSE

. Jeżeli wyrażenie nie jest

prawdziwe jeszcze przed wejściem do pętli, instrukcje nie będą wykonywane w
ogóle. Jest to istotna różnica w stosunku do pętli

do-while

, która zawsze zostaje

wykonana co najmniej raz. Jej składnia to:

do
{
instrukcje
}
while(warunek);

Instrukcje skoku

Instrukcja

return

pozwala powrócić z funkcji wykonywanej do funkcji, z której ta

została wywołana. W zależności od zadeklarowanego typu wartości zwracanej przez
funkcję instrukcja

return

może wymagać odpowiedniego parametru:

int MULT(int x, int y)
{
return(x * y);
}

lub

void funkcjaa()
{
printf ("\nHello World");
return; (w tym wypadku return nie jest konieczny P.B.)
}

Instrukcja

break

służy do wychodzenia z pętli lub instrukcji

switch

. W przypadku

pętli powoduje to jej przedwczesne zakończenie, jak w poniższym przykładzie:

#include <stdio.h>

background image


222

Hack Wars. Tom 1. Na tropie hakerów

222

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

void main()
{
int x;

for(x = 0; x < 256; x++)
{
if (x == 100)
break;

printf ("%d\t",x);
}
}

Uzupełnieniem

break

jest polecenie

continue

, wymuszające przeprowadzenie

następnej iteracji pętli. Kolejną wykonywaną instrukcją jest w tym przypadku
instrukcja pętli (dalsze instrukcje w iterowanym bloku są pomijane). Dostępna jest
również funkcja przedwczesnego zakończenia wykonywania programu —

exit()

.

Można za jej pomocą przekazać wartość zwracaną do programu wywołującego:

exit(warto

ść

_zwracana);

Continue

Słowo kluczowe

continue

nakazuje skok do instrukcji kontrolnej pętli. W

przypadku pętli zagnieżdżonych jest to instrukcja pętli wewnętrznej (

while

,

do...while()

). To sposób na łagodne zakończenie pętli jak w poniższym

przykładzie, gdzie odczytujemy zapisane w pliku ciągi:

#include <stdio.h>

void main()
{
FILE *fp;
char *p;
char buff[100];

fp = fopen("dane.txt","r");
if (fp == NULL)
{
fprintf(stderr,"Nie mo

ż

na otworzy

ć

pliku data.txt");

exit(0);
}

do
{
p = fgets(buff,100,fp);
if (p == NULL)
/* Wymuszenie wyj

ś

cia */

continue;
puts(p);
}
while(p);
}

W przypadku pętli

for

instrukcja

continue

powoduje najpierw wykonanie

wyrażenia inkrementacji, a dopiero po nim następuje sprawdzenie warunku
zakończenia.

background image


Rozdział 6.

Podstawy programowania dla hakerów

223

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

223

Wejście-wyjście

Pobieranie danych

Program w języku C może pobierać dane z konsoli (która jest standardowym
urządzeniem wejściowym), pliku lub portu. Ogólnym poleceniem odczytu danych ze
standardowego strumienia wejściowego

stdin

jest

scanf()

. Skanuje ono po jednym

znaku kolejne pola wejściowe. Podlegają one formatowaniu zgodnie z pierwszym z
przekazanych funkcji

scanf()

parametrów. Następnie pole zostaje zapisane pod

adresem przekazanym jako kolejny parametr wywołania funkcji. Przykładowy
program odczytuje pojedynczą liczbę całkowitą ze strumienia

stdin

:

void main()
{
int x;

scanf("%d", &x);
}

Warto zwrócić uwagę na operator użyty jako prefiks zmiennej

x

na liście parametrów

wywołania funkcji

scanf()

. Funkcja ta zapisuje bowiem wartość pod określonym

adresem, nie posługując się mechanizmem przypisywania wartości zmiennej.
Ciągiem formatującym jest ciąg znakowy, który może zawierać trzy typy danych: znaki
odstępu
(spacja, tabulator, przejście do nowego wiersza), znaki właściwe (wszystkie
znaki ASCII z wyjątkiem znaku %) i specyfikatory formatowania. Specyfikatory te
mają następującą składnię:

%[*][szeroko

ść

][h|l|L]typ

Oto przykład:

#include <stdio.h>

void main()
{
char nazwisko[30];
int wiek;

printf ("Podaj nazwisko i wiek ");
scanf("%30s%d",nazwisko,&wiek);
printf ("\n%s %d",nazwisko,wiek);
}

Zwróćmy uwagę na wiersz

#include <stdio.h>

— nakazuje on kompilatorowi

przetwarzanie pliku nagłówkowego stdio.h, w którym zawarte są prototypy funkcji

scanf()

i

printf()

. Po uruchomieniu tego prostego programu łatwo przekonamy

się, że użycie znaku odstępu przerwie wprowadzanie pierwszego pola danych.

Alternatywną funkcją pobierania danych jest

gets()

, odczytująca ciąg znaków ze

strumienia

stdin

do momentu napotkania znaku nowego wiersza. W ciągu docelowym

znak nowego wiersza zastąpiony zostaje znakiem

NULL (0)

. Charakterystyczna dla tej

funkcji jest możliwość odczytywania znaków odstępu. Oto nowa wersja powyższego
programu (korzystająca z

gets()

w miejsce

scanf()

):

background image


224

Hack Wars. Tom 1. Na tropie hakerów

224

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main()
{
char dane[80];
char *p;
char nazwisko[30];
int wiek;

printf ("\nPodaj nazwisko i wiek ");
/* Odczyt ci

ą

gu danych */

gets(dane);

/* p jest wska

ź

nikiem do ostatniego znaku pobieranego ci

ą

gu */

p = &dane[strlen(dane) - 1];

/* Usuwamy spacje ko

ń

cowe, zast

ę

puj

ą

c je znakami NULL */

while(*p == ' '){
*p = 0;
p--;
}

/* Lokalizujemy ostatni

ą

spacj

ę

w ci

ą

gu */

p = strrchr(dane,' ');

/* Odczytujemy wiek i zamieniamy na liczb

ę

*/

wiek = atoi(p);

/* Wstawiamy znak ko

ń

ca ci

ą

gu przed polem wieku */

*p = 0;

/* Kopiujemy ci

ą

g danych do zmiennej */

strcpy(nazwisko, dane);

/* Wy

ś

wietlamy wyniki operacji */

printf ("\nNazwisko: %s, wiek: %d", nazwisko, wiek);
}

Wyprowadzanie danych

Podstawową funkcją wyprowadzania danych jest

printf()

. Jest ona podobna do

scanf()

z tą różnicą, że zapisuje dane do standardowego strumienia wyjściowego

stdout

. Funkcja pobiera listę pól danych wyjściowych, odpowiednio stosuje

specyfikatory formatowania i wyprowadza wynik. Można stosować takie same
przekształcenia formatujące jak w przypadku funkcji

scanf()

, jak również

dodatkowe znaczniki:

-

wyrównuje dane wyjściowe do lewej, uzupełniając je z prawej strony
znakami odstępu międzywyrazowego (spacji),

+

wymusza poprzedzanie liczb znakiem.

Nieco odmienna jest także postać specyfikatora szerokości. Jest on rozbudowany o
element określający precyzję:

szeroko

ść

.precyzja

background image


Rozdział 6.

Podstawy programowania dla hakerów

225

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

225

Aby więc wyświetlić liczbę zmiennoprzecinkową z dokładnością do trzech miejsc
dziesiętnych, piszemy:

printf ("%.3f",x);

Poniżej przedstawiamy listę specjalnych stałych znakowych, które mogą pojawić się
na liście parametrów funkcji

printf()

:

\n

nowy wiersz (NL),

\r

powrót karetki (CR),

\t

tabulator,

\b

znak cofania (backspace),

\f

znak nowej strony,

\v

tabulator pionowy,

\\

ukośnik odwrotny (backslash),

\'

apostrof,

\"

cudzysłów,

\?

znak zapytania,

\o

ciąg w notacji ósemkowej,

\x

ciąg w notacji szesnastkowej.

Kolejny program ilustruje, w jaki sposób wyświetlić liczbę całkowitą w postaci
dziesiętnej, szesnastkowej i ósemkowej. Liczba

04

po znaku procentów (

%

) w

instrukcji

printf()

nakazuje kompilatorowi dopełnienie wyświetlanej liczby do

szerokości co najmniej czterech cyfr:

/* Prosty program konwersji liczb dziesi

ę

tnych */

/* do postaci szesnastkowej i ósemkowej */

#include <stdio.h>

void main()
{
int x;

do
{
printf ("\nPodaj liczb

ę

(lub 0, aby zako

ń

czy

ć

) ");

scanf("%d",&x);
printf ("%04d %04X %04o",x,x,x);
}
while (x != 0);

}

Do funkcji pokrewnych

printf()

należy

fprintf()

, której prototyp ma postać:

fprintf(FILE *fp, char *format[,argument,...]);

Jej zadaniem jest przesyłanie sformatowanych danych wyjściowych do określonego
strumienia plikowego.

background image


226

Hack Wars. Tom 1. Na tropie hakerów

226

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Kolejną tego rodzaju funkcją jest

sprintf()

o prototypie:

sprintf(char *s, char *format[,argument,...]);

Alternatywą dla

printf()

jest

puts()

, funkcja przesyłająca prosty ciąg do

strumienia

stdout

. Przesyłany ciąg zostaje automatycznie uzupełniony znakiem

nowego wiersza. Jest to rozwiązanie szybsze od

printf()

, jednak jego możliwości

są ograniczone.

Bezpośrednia wymiana danych z konsolą

Do przesyłania i odczytu danych z konsoli (klawiatury i ekranu) można
wykorzystywać również bezpośrednie funkcje we-wy. Wyróżnia je litera „c” na
początku — odpowiednikiem

printf()

jest więc

cprintf()

, a odpowiednikiem

puts()

— funkcja

cputs()

. Różnice między funkcjami bezpośredniej wymiany

danych a funkcjami standardowymi są następujące.









Nie są wykorzystywane strumienie predefiniowane, nie można więc przekierować
danych przesyłanych funkcjami komunikacji bezpośredniej.









Funkcji bezpośrednich nie można przenosić między różnymi systemami
operacyjnymi (m.in. nie można z nich korzystać w programach dla Windows).









Funkcje bezpośrednie są szybsze niż standardowe.









Nie zapewniają współpracy ze wszystkimi trybami wyświetlania (zwłaszcza
trybami graficznymi VESA).

Wskaźniki

Wskaźnik to zmienna, która przechowuje adres elementu danych w pamięci.
Deklaracja wskaźnika jest podobna do deklaracji zwykłej zmiennej, ale nazwa
poprzedzana jest znakiem gwiazdki (

*

), na przykład:

char *p;

Powyższy wiersz deklaruje zmienną

p

jako wskaźnik do zmiennej typu

char

.

Wykorzystanie wskaźników dostarcza szerokich możliwości, wymaga jednak
szczególnej

uwagi.

Skutki

przypisania

błędnego

adresu

najczęściej

nieprzewidywalne. Oto przykład prostego programu, w którym wykorzystywany jest
wskaźnik:

#include <stdio.h>

void main()
{
int a;
int *x;

/* x jest wska

ź

nikiem do danych typu int */

a = 100;
x = &a;

background image


Rozdział 6.

Podstawy programowania dla hakerów

227

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

227

printf("\nZmienna a przechowuje warto

ść

%d pod adresem %p.",a,x);

}

Wartości wskaźników można zwiększać i zmniejszać, dopuszczalne są również inne
operacje matematyczne. Typowym zastosowaniem wskaźników jest zapewnienie
dynamicznego przydziału pamięci. W trakcie pracy programu często pojawia się
potrzeba przejściowego (tymczasowego) zaalokowania bloku pamięci. Korzystamy
wówczas z funkcji

malloc()

:

wska

ź

nik_dowolnego_typu = malloc(liczba_bajtów);

Funkcja

malloc()

zwraca wskaźnik typu

void

, co oznacza, że może on wskazywać

dane dowolnego typu —

int

,

char

,

float

itd. W poniższym przykładzie alokujemy

pamięć dla tabeli 1000 liczb całkowitych.

#include <stdio.h>
#include <stdlib.h>

void main()
{
int *x;
int n;

/* x jest wska

ź

nikiem do danych typu int */


/* Tworzymy tablic

ę

1000-elementow

ą

*/

/* sizeof() dostarcza kompilatorowi informacji o liczbie */
/* bajtów wymaganej do przechowywania zmiennej typu int */

x = malloc(1000 * sizeof(int));

/* Sprawdzamy czy alokacja została wykonana */
if (x == NULL)
{
printf("\nNie mo

ż

na zaalokowa

ć

pami

ę

ci dla 1000-elementowej

tablicy warto

ś

ci

int");
exit(0);
}

/* Przypisujemy warto

ś

ci poszczególnym elementom */

for(n = 0; n < 1000; n++)
{
*x = n;
x++;
}

/* Przywracamy x warto

ść

adresu pocz

ą

tkowego tabeli */

x -= 1000;

/* Wy

ś

wietlamy warto

ś

ci tabeli */

for(n = 0; n < 1000; n++){
printf("\nElement %d przechowuje warto

ść

%d",n,*x);

x++;
}
/* Po u

ż

yciu, dealokujemy blok pami

ę

ci */

free(x);
}

background image


228

Hack Wars. Tom 1. Na tropie hakerów

228

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Wskaźniki wykorzystuje się również w odniesieniu do tablic znaków, czyli ciągów
(strings). Ponieważ wszystkie ciągi w programach C kończy bajt o wartości 0,
korzystając ze wskaźnika, możemy policzyć znaki w ciągu:

#include <stdio.h>
#include <string.h>

void main()
{
char *p;
char tekst[100];
int dlugosc;

/* Inicjujemy zmienn

ą

'tekst' */

strcpy(tekst,"To jest ciag znakowy");

/* Ustawiamy warto

ść

zmiennej p na pocz

ą

tek tekstu */

p = tekst;

/* Inicjujemy zmienn

ą

długo

ść

*/

dlugosc = 0;

/* Zliczamy znaki w zmiennej tekst */
while(*p)
{
dlugosc++;
p++;
}

/* Wy

ś

wietlamy wynik */

printf("\nDlugosc ciagu znakowego to: %d",dlugosc);
}

Wymaganą do zaadresowania 1 MB pamięci 20-bitową liczbę dzieli się na dwie
wartości: przesunięcie (offset) i segment (każdy segment to 64 kB). Do przechowywania
numerów segmentów pamięci komputer IBM PC wykorzystuje tzw. rejestry
segmentowe. Konsekwencją takiego rozwiązania są w języku C trzy dodatkowe słowa
kluczowe:









near

— wskaźniki „bliskie” mają rozmiar 16 bitów i umożliwiają dostęp

do danych bieżącego segmentu,









far

— wskaźniki „dalekie” obejmują wartości określające przesunięcie

i segment, umożliwiając dostęp do dowolnego adresu w pamięci,









huge

— wskaźniki „ogromne” to odmiana wskaźników dalekich, zapewniająca

możliwość zwiększania i zmniejszania wartości w całym zakresie 1 MB
(kompilator generuje odpowiedni kod modyfikujący wartość przesunięcia).

Nie będzie zapewne zaskakujące stwierdzenie, że przetwarzanie programu
korzystającego ze wskaźników typu

near

będzie szybsze niż w przypadku programu, w

którym zastosowano wskaźniki

far

. Wskaźniki

huge

są oczywiście największym

obciążeniem. Kompilatory C wyposażone są w makro zwracające adres
odpowiadający podanym wartościom numeru segmentu i przesunięcia:

void far *MK_FP(unsigned segment, unsigned offset);

background image


Rozdział 6.

Podstawy programowania dla hakerów

229

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

229

Struktury

Język C oferuje technikę grupowania zmiennych pod jedną nazwą, dostarczając w ten
sposób wygodnego sposobu przechowywania powiązanych ze sobą informacji i
strukturalizowania ich. Składnia definicji struktury jest następująca:

typedef struct
{
typ_zmiennej nazwa_zmiennej;
typ_zmiennej nazwa_zmiennej;
.
.
.
}
nazwa_struktury;

Używanie zmiennych strukturalnych jest niezbędne przy korzystaniu z plików, w
których występuje uporządkowanie oparte na rekordach danych. W poniższym
przykładzie operować będziemy na prostym pliku z listą adresów. Rozpoczniemy od
deklaracji struktury

dane

, złożonej z sześciu pól:

nazwisko

,

adres

,

miasto

,

wojewodztwo

,

poczta

i

nrtelefonu

:

typedef struct
{
char nazwisko[30];
char adres[30];
char miasto[30];
char wojewodztwo[30];
char kod[6];
char nrtelefonu[15];
}
dane;

Odwołania do pól zmiennej strukturalnej mają postać:

zmienna_strukt.nazwa_pola;

Nie ma ograniczenia liczby pól struktury, nie jest również wymagane, aby typy pól
były takie same lub podobne, na przyklad:

typedef struct
{
char nazwisko[30];
int wiek;
char *notatki;
}
dp;

Jest to poprawna deklaracja struktury obejmująca: pole tablicy znakowej, pole liczby
całkowitej i pole wskaźnika do zmiennej znakowej. Aby przekazać zmienną
strukturalną jako parametr, korzystamy z jej adresu — poprzedzamy nazwę zmiennej
operatorem

&

. Oto przykładowy program wykorzystujący struktury w celu wykonania

prostych operacji na pliku listy adresów:

#include <stdio.h>
#include <stdlib.h>
#include <io.h>

background image


230

Hack Wars. Tom 1. Na tropie hakerów

230

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

#include <string.h>
#include <fcntl.h>
#include <sys\stat.h>

/* liczba_wierszy to liczba wierszy ekranu */
#define liczba_wierszy 25

typedef struct
{
char nazwisko[30];
char adres[30];
char miasto[30];
char wojewodztwo[30];
char kod[6];
char nrtelefonu[15];
}
dane;

dane rekord;
int handle;


/* Prototypy funkcji */

void ADD_REC(void);
void CLS(void);
void DISPDATA(void);
void FATAL(char *);
void GETDATA(void);
void MENU(void);
void OPENDATA(void);
int SEARCH(void);

void CLS()
{
int n;

for(n = 0; n < liczba_wierszy; n++)
puts("");
}

void FATAL(char *blad)
{
printf(" \nBlad krytyczny: %s",blad);
exit(0);
}

void OPENDATA()
{
/* Sprawd

ź

czy istnieje plik danych. Je

ż

eli nie, utwórz. */

/* Je

ż

eli tak, otwórz do odczytu-zapisu na ko

ń

cu pliku. */

handle = open("address.dat",O_RDWR|O_APPEND,S_IWRITE);

if (handle == -1)
{
handle = open("address.dat",O_RDWR|O_CREAT,S_IWRITE);
if (handle == -1)
FATAL("Nie mo

ż

na utworzy

ć

pliku danych");

}
}

background image


Rozdział 6.

Podstawy programowania dla hakerów

231

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

231

void GETDATA()
{
/* Pobierz dane adresowe */

CLS();

printf("Nazwisko ");
gets(rekord.nazwisko);
printf("\nAdres ");
gets(rekord.adres);
printf("\nMiasto ");
gets(rekord.miasto);
printf("\nWojewództwo ");
gets(rekord.wojewodztwo);
printf("\nKod pocztowy ");
gets(rekord.kod);
printf("\nNumer telefonu ");
gets(rekord.nrtelefonu);
}

void DISPDATA()
{
/* Wy

ś

wietl dane adresowe */

char tekst[5];

CLS();

printf("Nazwisko %s",rekord.nazwisko);
printf("\nAdres %s",rekord.adres);
printf("\nMiasto %s",rekord.miasto);
printf("\nWojewództwo %s",rekord.wojewodztwo);
printf("\nKod pocztowy %s",rekord.kod);
printf("\nNumer telefonu %s\n\n",rekord.nrtelefonu);

puts(" Wci

ś

nij ENTER");

gets(tekst);
}

void ADD_REC()
{
/* Doł

ą

cz do pliku danych nowy rekord */

int wynik;

wynik = write(handle,&rekord,sizeof(dane));

if (wynik == -1)
FATAL("Zapis do pliku danych niemo

ż

liwy");

}

int SEARCH()
{
char tekst[100];
int wynik;

printf("Wprowad

ź

wzór wyszukiwania ");

gets(tekst);
if (*tekst == 0)
return(-1);

/* Zlokalizuj pocz

ą

tek pliku */

lseek(handle,0,SEEK_SET);

do

background image


232

Hack Wars. Tom 1. Na tropie hakerów

232

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

{
/* Załaduj rekord do pami

ę

ci */

wynik = read(handle,&rekord,sizeof(dane));
if (wynik > 0)
{
/* Przeszukaj rekord */
if (strstr(rekord.nazwisko,tekst) != NULL)
return(1);
if (strstr(rekord.adres,tekst) != NULL)
return(1);
if (strstr(rekord.miasto,tekst) != NULL)
return(1);
if (strstr(rekord.wojewodztwo,tekst) != NULL)
return(1);
if (strstr(rekord.kod,tekst) != NULL)
return(1);
if (strstr(rekord.nrtelefonu,tekst) != NULL)
return(1);
}
}
while(wynik > 0);
return(0);
}

void MENU()
{
int opcja;
char tekst[10];

do
{
CLS();
puts("\n\t\t\tWybierz opcj

ę

");

puts("\n\n\t\t\t1 Dodaj nowy rekord");
puts("\n\n\t\t\t2 Przeszukiwanie danych");
puts("\n\n\t\t\t3 Wyj

ś

cie");

puts("\n\n\n\n\n");
gets(tekst);
opcja = atoi(tekst);

switch(opcja)
{
case 1 : GETDATA();
/* Przed doł

ą

czaniem rekordu przejd

ź

do ko

ń

ca pliku */

lseek(handle,0,SEEK_END);
ADD_REC();
break;

case 2 : if (SEARCH())
DISPDATA();
else
{
puts("NIEZNALEZIONE!");
puts("Wci

ś

nij ENTER");

gets(tekst);
}
break;

case 3 : break;
}
}
while(opcja != 3);

background image


Rozdział 6.

Podstawy programowania dla hakerów

233

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

233

}

void main()
{
CLS();
OPENDATA();
MENU();
}

Pola bitowe

Język C przewiduje możliwość korzystania w strukturach ze zmiennych o rozmiarze
mniejszym niż 8 bitów. Określa się je mianem pól bitowych, a ich rozmiar może być
dowolny, od 1 bitu wzwyż. Deklaracja pola bitowego wygląda następująco:

typ nazwa : liczba_bitów;

Przykładem może być deklaracja kilku jednobitowych znaczników stanu:

typedef struct
{
unsigned przeniesienie : 1;
unsigned zero : 1;
unsigned przepelnienie : 1;
unsigned parzystosc : 1;
}
df;

df znaczniki;

Zmienna

znaczniki

będzie zajmować w pamięci tylko 4 bity, mimo że składa się z 4

pól, z których każde dostępne jest jako osobne pole struktury.

Union

Kolejnym ułatwieniem języka C, pozwalającym zapewnić optymalne wykorzystanie
dostępnej pamięci, jest struktura

union

, czyli zbiór zmiennych, współużytkujących

jeden adres pamięci. Oznacza to, oczywiście, że w danym momencie dostępna jest
tylko jedna ze zmiennych składowych. Deklaracja

union

ma następującą postać:

union nazwa
{
typ nazwa_zmiennej;
typ nazwa_zmiennej;
.
.
.
typ nazwa_zmiennej;
};

Wyliczenia

Wyliczenie (enumeracja) to przypisanie liście symboli rosnących wartości
całkowitych. Wyliczenie deklarujemy:

enum nazwa { lista } lista_zmiennych;

background image


234

Hack Wars. Tom 1. Na tropie hakerów

234

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Przykładem może być definicja listy kolorów:

enum KOLORY
{
CZARNY,
NIEBIESKI,
ZIELONY,
CZERWONY,
BRAZOWY,
JASNOSZARY,
CIEMNOSZARY,
JASNONIEBIESKI,
JASNOZIELONY,
JASNOCZERWONY,
ZOLTY,
BIALY
};

Operacje na plikach

W operacjach dostępu do plików język C posługuje się buforowanymi strumieniami
plikowymi. Niektóre z platform języka, jak UNIX i DOS, oferują również
niebuforowane uchwyty plików.

Strumienie buforowane

Dostęp do strumieni buforowanych realizowany jest za pośrednictwem wskaźnika do
zmiennej typu

FILE

. Ten szczególny typ danych zdefiniowany został w nagłówku

stdio.h. Aby więc zadeklarować wskaźnik do pliku, wprowadzamy:

#include <stdio.h>

FILE *ptr;

Aby otworzyć strumień, używamy funkcji

fopen()

. Pobiera ona dwa parametry:

nazwę otwieranego pliku oraz tryb dostępu. Oto lista trybów dostępu.

Tryb

Opis

r

otwórz tylko do odczytu (plik musi istnieć),

w

utwórz do zapisu; zastąp, jeżeli plik o podanej nazwie istnieje,

a

otwórz do dołączania danych (dopisywania na końcu pliku); utwórz nowy plik,
jeżeli plik o podanej nazwie nie istnieje,

r+

otwórz istniejący plik do odczytu i zapisu (plik musi istnieć),

w+

utwórz do odczytu i zapisu; zastąp, jeżeli istnieje,

a+

otwórz do czytania i dołączania danych; utwórz nowy plik, jeżeli nie istnieje.

Aby określić tryb tekstowy lub binarny, do opisu trybu można dołączyć

t

lub

b

. W

przypadku pominięcia tego znacznika strumień zostanie otwarty w trybie określanym
zmienną globalną

_fmode

. Odczyt i zapis danych do strumieni plikowych w trybie

tekstowym wiąże się z konwersją — podczas zapisu znaki CR i LF zamieniane są na

background image


Rozdział 6.

Podstawy programowania dla hakerów

235

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

235

pary CR LF, a przy odczycie pary CR LF ulegają zamianie na pojedynczy znak LF.
Tego rodzaju operacje nie są wykonywane w trybie binarnym.

Jeżeli funkcja

fopen()

nie będzie mogła otworzyć pliku, zwróci w miejsce

wskaźnika wartość

NULL

(zdefiniowaną w stdio.h). Poniższy program utworzy nowy

plik dane.txt i udostępni go do odczytu i zapisu:

#include <stdio.h>

void main()
{
FILE *fp;

fp = fopen("dane.txt","w+");
}

Aby zamknąć strumień, używamy funkcji

fclose()

, wymagającej podania

wskaźnika do pliku.

fclose(fp);

Jeżeli podczas zamykania strumienia wystąpi błąd, funkcja

fclose()

zwróci wartość

niezerową (znacznik EOF — End Of File). Do przesyłania i odbierania danych ze
strumieni służą cztery podstawowe funkcje:

fgetc()

,

fputc()

,

fgets()

i

fputs()

. Funkcja

fgetc()

odczytuje pojedynczy znak z określonego strumienia

wejściowego (przekształcany do liczby całkowitej):

int fgetc(FILE *fp);

Jej odwrotnością jest

fputc()

, zapisująca pojedynczy znak do określonego

strumienia wyjściowego:

int fputc( int c, FILE *fp);

Funkcja

fgets()

odczytuje ze strumienia wejściowego ciąg:

char *fgets(char *s, int liczba_bajtów, FILE *fp);

Odczyt zostaje przerwany po pobraniu

liczba_bajtów-1

znaków lub znaku

nowego wiersza (również wstawianego do tablicy). Do odczytanego ciągu

s

dołączany

jest kończący znak

NULL (znany tak

ż

e pod postaci

ą

’\0’)

. W przypadku

wystąpienia błędów funkcja zwraca

NULL

.

Funkcja

fputs()

zapisuje do strumienia ciąg zakończony znakiem

NULL

(inaczej

’\0’

):

int fputs(const char *s, FILE *fp);

Wszystkie opisywane funkcje zwracają w przypadku błędów wartość

EOF

(zdefiniowaną w stdio.h) z wyjątkiem funkcji

fgets()

, która w przypadku

wystąpienia błędu zwraca

NULL

. Poniższy program tworzy kopię pliku dane.dat, o

nazwie dane.old, ilustrując zarazem użycie wszystkich czterech funkcji:

#include <stdio.h>

background image


236

Hack Wars. Tom 1. Na tropie hakerów

236

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

int main()
{
FILE *in;
FILE *out;

in = fopen("data.dat","r");

if (in == NULL)
{
puts("\nNie mo

ż

na otworzy

ć

do odczytu pliku dane.dat");

return(0);
}

out = fopen("dane.old","w+");

if (out == NULL)
{
puts("\nNie mo

ż

na utworzy

ć

pliku dane.old");

return(0);
}

/* Powtarzaj odczytywanie i zapisywanie pojedynczych bajtów */
/* a

ż

do natrafienia na EOF */

while(!feof(in))
fputc(fgetc(in),out);

/* Zamknij strumienie plikowe */
fclose(in);
fclose(out);

return(0);
}

W kolejnym przykładowym programie używamy funkcji

fputs

do kopiowania tekstu

ze strumienia

stdin

(zazwyczaj oznacza to znaki wprowadzane z klawiatury) do

nowego pliku dane.txt:

#include <stdio.h>

int main()
{
FILE *fp;
char tekst[100];

fp = fopen("dane.txt","w+");

do
{
gets(tekst);
fputs(tekst,fp);
}
while(*tekst);

fclose(fp);
}

Swobodny dostęp do danych strumieni

Dostęp swobodny do danych dostarczanych za pośrednictwem strumieni zapewnia
funkcja

fseek()

o prototypie:

background image


Rozdział 6.

Podstawy programowania dla hakerów

237

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

237

int fseek(FILE *fp, long liczba_bajtów, int zacznij_od);

Funkcja zmienia pozycję wskaźnika pliku skojarzonego ze strumieniem otwartym
wcześniej przez

fopen()

. Wskaźnik ustawiany jest na

liczba_bajtów

za (lub

przed w przypadku wartości ujemnej) pozycją

zacznij_od

. Tą ostatnią może być

początek pliku, bieżące położenie wskaźnika lub koniec pliku. Pozycje te symbolizują
stałe

SEEK_SET

,

SEEK_CUR

i

SEEK_END

. Udaną operację

fseek()

sygnalizuje

zwrócenie wartości

0

. Uzupełnieniem

fseek()

jest funkcja

ftell()

, zwracająca

wartość bieżącej pozycji wskaźnika pliku:

long int ftell(FILE *fp);

Funkcja zwraca pozycję wskaźnika pliku, określoną jako ilość bajtów od początku
pliku, lub

-1

w przypadku błędu.

Uchwyty

Uchwyty plików (handles) otwiera funkcja

open()

o prototypie:

int open(char *nazwa_pliku, int dost

ę

p[, unsigned tryb]);

Udaną operację sygnalizuje zwrócenie numeru uchwytu. W pozostałych przypadkach
zwracane jest

–1

. Na wartość

dost

ę

p

składają się połączone bitową operacją OR

stałe symboliczne, odpowiadające deklaracjom w pliku fcntl.h. Różnią się one w
zalezności od kompilatora. Do typowych należą:

O_APPEND

przed każdym zapisem wskaźnik pliku będzie ustawiany na końcu pliku,

O_CREAT

jeżeli plik nie istnieje, zostanie utworzony,

O_TRUNC

obcina istniejący plik do długości 0 bajtów,

O_EXCL

używane w połączeniu z

O_CREAT

,

O_BINARY

otwiera plik w trybie binarnym,

O_TEXT

otwiera plik w trybie tekstowym.

Po przypisaniu uchwytu pliku za pomocą polecenia

open()

można korzystać z

funkcji

read()

i

write()

. Prototyp

read()

jest następujący:

int read(int handle, void *buf, unsigned liczba_bajtów);

Funkcja podejmuje próbę odczytu podanej liczby bajtów i zwraca liczbę bajtów
faktycznie pobranych przez uchwyt pliku. Odczytane dane umieszczane są w bloku
pamięci określonym parametrem

buf

. Funkcja

write()

działa podobnie, nie różni

się również jej prototyp i sposób generowania wartości zwracanej. Zapisuje ona
podaną ilość bajtów z określonego wskaźnikiem bloku pamięci. Pliki otwierane
funkcją

open()

zamykamy funkcją

close()

:

int close(int handle);

Funkcja

close()

zwraca

0

w przypadku operacji udanej, a

–1

w przypadku

wystąpienia błędów.

background image


238

Hack Wars. Tom 1. Na tropie hakerów

238

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Dostęp swobodny zapewnia funkcja

lseek()

, bardzo podobna do

fseek()

, ale

pobierająca jako parametr numer uchwytu, a nie wskaźnik strumienia

FILE

. W

poniższym przykładzie wykorzystujemy uchwyt pliku do zapisu danych z

stdin

(czyli klawiatury) do nowego pliku o nazwie dane.txt:

#include <io.h>
#include <fcntl.h>
#include <sys\stat.h>

int main()
{
int handle;
char tekst[100];

handle = open("dane.txt", O_RDWR|O_CREAT|O_TRUNC,S_IWRITE);

do
{
gets(tekst);
write(handle, &tekst, strlen(tekst));
}
while(*tekst);

close(handle);
}

Przegląd funkcji plikowych

Norma ANSI definiuje związane z plikami operacje we-wy przy użyciu strumieni,
opisując różnorodne funkcje. Prototyp funkcji

fopen()

ma postać:

FILE *fopen(const char *nazwa, const char *tryb);

Funkcja podejmuje próbę otwarcia strumienia łączącego z plikiem o podanej nazwie
w określonym trybie. Udana operacja kończy się zwróceniem wskaźnika typu

FILE

.

W przypadku niepowodzenia funkcji zwraca

NULL

. Na wcześniejszych stronach

przedstawiony został opis parametru

tryb

.

Funkcja

fclose()

służy do zamykania strumienia otwartego wcześniejszym

wywołaniem

fopen()

:

int fclose(FILE *fp);

Udana operacja

fclose()

kończy się opróżnieniem wszystkich buforów pliku i

zwróceniem wartości

0

. W przypadku błędów zwracana jest wartość

EOF

.

Wiele komputerów korzysta z buforowanego dostępu do plików. Oznacza to, że dane,
zapisywane do strumienia, wstępnie umieszczane są w pamięci, a faktyczny zapis
następuje dopiero po przekroczeniu pewnej granicznej ilości bajtów. Jeżeli w czasie,
gdy dane nie zostały jeszcze faktycznie zapisane do strumienia, nastąpi awaria
zasilania, dane zostaną utracone. Zabezpiecza przed tym funkcja

fflush()

,

wymuszająca zapisanie wszystkich danych oczekujących:

int fflush(FILE *fp);

background image


Rozdział 6.

Podstawy programowania dla hakerów

239

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

239

Jeżeli wywołanie

fflush()

jest udane, związane ze strumieniem bufory zostają

opróżnione i zwracana jest wartość

0

. W przypadku błędów funkcja zwraca wartość

EOF

.

Kolejną funkcją jest

ftell()

zwracająca lokalizację wskaźnika pliku:

long int ftell(FILE *fp);

Funkcja zwraca przesunięcie wskaźnika pliku w stosunku do początku pliku lub

–1L

w przypadku błędów. Przesunięcie wskaźnika pliku do nowej pozycji umożliwia

fseek()

:

int fseek(FILE *fp, long offset, int zacznij_od);

Funkcja podejmuje próbę przesunięcia wskaźnika pliku o

offset

bajtów od pozycji

zacznij_od

, określonej jedną ze stałych:

SEEK_SET

początek pliku,

SEEK_CUR

bieżąca pozycja wskaźnika pliku,

SEEK_END

koniec pliku.

Przesunięcie (

offset

) może być wartością dodatnią (przesuwanie wskaźnika w

stronę końca pliku) lub ujemną (przesuwanie wskaźnika w stronę początku pliku).
Aby szybko przenieść wskaźnik do początku pliku i usunąć wcześniejsze odwołania
do błędów, C dostarcza funkcji

rewind()

:

void rewind(FILE *fp);

Funkcja ta działa podobnie jak

fseek(fp,0L,SEEK_SET)

. Jednak

fseek()

usuwa

znacznik

EOF

, a

rewind() dodatkowo

wszystkie sygnały błędów. Informacje o

błędach funkcji plikowych można pobrać przy użyciu funkcji

ferror()

:

int ferror (FILE *fp);

Funkcja zwraca wartość niezerową, jeżeli w określonym strumieniu wystąpił błąd. Po
sprawdzeniu wartości

ferror()

należy zadbać o usunięcie sygnałów błędów za

pomocą funkcji

clearerr()

:

void clearerr(FILE *fp);

Sprawdzenie, czy spełniony jest warunek osiągnięcia końca pliku, realizuje
predefiniowane makro

feof()

:

int feof(FILE *fp);

Makro zwraca wartość niezerową, gdy dla danego strumienia stwierdzono osiągnięcie
końca pliku. W pozostałych przypadkach zwracaną wartością jest

0

.

Dostępnych jest kilka funkcji realizujących odczyt danych ze strumienia plikowego.
Pojedyncze znaki można odczytywać funkcją

fgetc()

:

int fgetc(FILE *fp);

background image


240

Hack Wars. Tom 1. Na tropie hakerów

240

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

fgetc()

zwraca wartość ASCII pobranego znaku lub znak

EOF

w przypadku

wystąpienia błędu. Odczyt ciągu danych umożliwia funkcja

fgets()

, odczytująca

ciąg zakończony znakiem nowego wiersza:

char *fgets(char *s, int n, FILE *fp);

W wyniku udanego wywołania funkcji w zmiennej

s

umieszczany jest ciąg

zakończony znakiem nowego wiersza lub zawierający

n-1

znaków. Funkcja

zachowuje kończący ciąg znak nowego wiersza, dołączając do ciągu

s

bajt

NULL

. W

przypadku nieudanego wywołania zwracany jest wskaźnik pusty. Ciągi zapisujemy
do strumienia funkcją

fputs()

:

int fputs(const char *s, FILE *fp);

Funkcja

fputs()

zapisuje wszystkie znaki ciągu

s

, z wyjątkiem końcowego bajtu

NULL

, do strumienia

fp

. Standardowo funkcja zwraca ostatni zapisany znak, a w

przypadku wystąpienia błędów —

EOF

. Dostępna jest również funkcja zapisująca do

strumienia pojedynczy znak

fputc()

:

int fputc(int c, FILE *fp);

Funkcja zwraca zapisany znak lub, w przypadku wystąpienia błędów, znak

EOF

.

Aby odczytać ze strumienia duży blok danych lub rekord, można posłużyć się funkcją

fread()

:

size_t fread(void *ptr, size_t rozmiar, size_t n, FILE *fp);

Funkcja podejmuje próbę odczytu

n

elementów, z których każdy ma długość

rozmiar

, ze strumienia plikowego

fp

do bloku pamięci określonego wskaźnikiem

ptr

. Aby ustalić, czy operacja przebiegła bez zakłóceń, korzystamy z funkcji

ferror()

.

Siostrzaną funkcją

fread()

jest

fwrite()

:

size_t fwrite(const void *ptr, size_t rozmiar, size_t n, FILE *fp);

Funkcja zapisuje

n

elementów o długości

rozmiar

z obszaru pamięci określonego

wskaźnikiem

ptr

do strumienia

fp

.

Funkcja

fscanf()

umożliwia odczyt danych formatowanych:

int fscanf(FILE *fp, const char *format[,adres ...]);

Funkcja zwraca liczbę faktycznie odczytanych pól, a

EOF

w przypadku końca pliku.

Poniższy przykład ilustruje użyteczność funkcji

fscanf()

podczas odczytywania ze

strumienia liczb:

#include <stdio.h>

void main()
{
FILE *fp;
int a;
int b;

background image


Rozdział 6.

Podstawy programowania dla hakerów

241

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

241

int c;
int d;
int e;
char tekst[100];

fp = fopen("dane.txt","w+");

if(!fp)
{
perror("Nie mo

ż

na utworzy

ć

pliku");

exit(0);
}
fprintf(fp,"1 2 3 4 5 \"Wiersz liczb\"");

fflush(fp);

if (ferror(fp))
{
fputs("Bł

ą

d przy zapisie strumienia", stderr);

exit(1);
}

rewind(fp);
if (ferror(fp))
{
fputs("Bł

ą

d przy przewijaniu strumienia", stderr);

exit(1);
}

fscanf(fp,"%d %d %d %d %d %s", &a, &b, &c, &d, &e, tekst);
if (ferror(fp))
{
fputs("Bł

ą

d odczytu ze strumienia", stderr);

exit(1);
}

printf("\nFunkcja fscanf() zwróciła %d %d %d %d %d
%s",a,b,c,d,e,tekst);
}

Jak łatwo zauważyć, zapis formatowanych danych realizuje funkcja

fprintf()

. Gdy

pojawia się potrzeba zapisania położenia wskaźnika pliku i późniejszego jego
przywrócenia, można skorzystać z funkcji

fgetpos()

i

fsetpos()

. Pierwsza z nich

odczytuje bieżącą pozycję wskaźnika pliku:

int fgetpos(FILE *fp, fpos_t *pozycja);

Funkcja

fsetpos()

ustawia wskaźniki pliku na określonej pozycji:

int fsetpos(FILE *fp, const fpos_t *pozycja);

Typ

fpos_t

zdefiniowany został w nagłówku stdio.h. Funkcje te są wygodniejsze

w użyciu niż

ftell()

i

fseek()

.

Z otwartym już strumieniem można skojarzyć nowy plik. Umożliwia to funkcja

freopen()

:

FILE *freopen(const char *nazwa, const char *tryb, FILE *fp);

background image


242

Hack Wars. Tom 1. Na tropie hakerów

242

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Funkcja zamyka strumień istniejący i podejmuje próbę jego ponownego otwarcia przy
użyciu podanej nazwy pliku. Znajduje to zastosowanie przy przekierowywaniu
strumieni predefiniowanych

stdin

,

stdout

i

stderr

do pliku lub urządzenia.

Przykładowo, gdy pojawia się potrzeba przekierowania wszystkich danych
wyjściowych kierowanych do

stdout

na drukarkę, można użyć polecenia:

freopen("LPT1","w",stdout);

Predefiniowane strumienie we-wy

Wstępnie zdefiniowane zostały trzy strumienie we-wy:

stdin

,

stdout

i

stderr

.

Domyślnie

stdin

i

stdout

odpowiadają klawiaturze i monitorowi. Na wielu

platformach, w tym systemów DOS i UNIX, dostępna jest możliwość ich
przekierowania. Strumień

stderr

domyślnie powiązany jest z monitorem

(wyświetlaczem). Praktyka jego przekierowywania nie jest raczej stosowana. Jego
podstawowym zadaniem jest zapewnienie możliwości wyświetlania komunikatów
błędów, nawet w sytuacji gdy powiązanie standardowego wyjścia (

stdout

) zostało

zmienione:

fputs("Komunikat o bł

ę

dzie", stderr);

Funkcje

printf()

i

puts()

przekazują dane do strumienia

stdout

. Funkcje

scanf()

i

gets()

pobierają dane ze strumienia

stdin

. Przekierowanie tych

strumieni zmienia sposób działania funkcji.

Jako przykład plikowych operacji we-wy na platformie PC, korzystających z
możliwości przekierowania strumieni, przedstawimy prosty program przesyłający do
strumienia

stdout

zawartość określonego pliku, przedstawioną jako wartości

szesnastkowe. Polecenie w postaci:

dump nazwa_pliku.xxx > dane_wyj

ś

ciowe.xxx

pozwoli zmienić domyślne powiązanie strumienia

stdout

z monitorem.

#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <string.h>

main(int argc, char *argv[])
{
unsigned licznik;
unsigned char v1[20];
int f1;
int x;
int n;

if (argc != 2)
{
fputs("\nBŁ

Ą

D. Poprawna składnia wywołania: dump f1\n",stderr);

return(1);
}

f1 = open(argv[1],O_RDONLY);

if (f1 == -1)
{

background image


Rozdział 6.

Podstawy programowania dla hakerów

243

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

243

fprintf(stderr, "\nBŁ

Ą

D. Nie mo

ż

na otworzy

ć

%s\n",argv[1]);

return(1);
}

fprintf(stdout,"\nZAWARTO

ŚĆ

PLIKU %s\n\n",strupr(argv[1]));


licznik = 0;

while(1)
{
/* Wypełnienie bufora zerami */
memset(v1,0,20);

/* Pobranie do bufora danych z pliku */
x = _read(f1,&v1,16);

/* x = 0 to EOF, x = -1 oznacza bł

ą

d */

if (x < 1)
break;

/* Wyprowad

ź

offset w pliku */

fprintf(stdout,"%06d(%05x) ",licznik,licznik);

licznik +=16;

/* Wyprowad

ź

szesnastkowe warto

ś

ci bajtów z bufora */

for(n = 0; n < 16; n++)
fprintf(stdout,"%02x ",v1[n]);

/* Wyprowad

ź

warto

ś

ci ASCII bajtów z bufora */

for(n = 0; n < 16; n++)
{
if ((v1[n] > 31) && (v1[n] < 128))
fprintf(stdout,"%c",v1[n]);
else
fputs(".",stdout);
}

/* Zako

ń

cz znakiem nowego wiersza */

fputs("\n",stdout);
}

/* zako

ń

czenie normalne */

return(0);
}

Ciągi

Język C należy do najlepiej wyposażonych w funkcje obsługi ciągów pośród
uniwersalnych języków programowania. Ciąg to jednowymiarowa tablica znaków
zakończona bajtem zerowym. Ciągi można inicjować dwoma sposobami. Pierwszym
jest nadanie im stałej wartości w kodzie programu:

int main()
{
char *p = "System 5";
char nazwa[] = "Program testowy";
return(0);
}

background image


244

Hack Wars. Tom 1. Na tropie hakerów

244

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Drugi to utworzenie ciągu w czasie wykonywania programu za pomocą funkcji

strcpy()

:

char *strcpy(char *cel,const char *

ź

ródło);

Funkcja

strcpy()

kopiuje ciąg źródłowy do lokalizacji docelowej, na przykład:

#include <stdio.h>

int main()
{
char nazwa[50];

strcpy(nazwa,"Servile Software");

printf("\nWarto

ść

ci

ą

gu 'nazwa' to %s",nazwa);

return 0;
}

Język C umożliwia bezpośredni dostęp do każdego bajtu ciągu:

#include <stdio.h>

int main()
{
char nazwa[50];

strcpy(nazwa,"Servile Software");

printf("\nWarto

ść

ci

ą

gu 'nazwa' to %s",nazwa);


/* Zast

ą

pienie pierwszego bajtu liter

ą

's' */

nazwa[0] = 's';

printf("\nWarto

ść

ci

ą

gu 'nazwa' to %s",nazwa);

return 0;
}

Niektóre kompilatory C wyposażone zostały w funkcje konwersji ciągów do wielkich
i małych liter, nie obejmuje ich jednak norma ANSI. W specyfikacji pojawiają się za
to funkcje

toupper()

i

tolower()

, zwracające pojedynczy znak ( w postaci wartości

int

) zamieniony na literę wielką lub małą. Łatwo na tej podstawie utworzyć własne

funkcje konwersji ciągów:

#include <stdio.h>

void strupr(char *zrodlo)
{
char *p;

p = zrodlo;
while(*p)
{
if((*p)>=97 && (*p)<=122)
*p = toupper(*p);
p++;
}
}

void strlwr(char *zrodlo)

background image


Rozdział 6.

Podstawy programowania dla hakerów

245

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

245

{
char *p;

p = zrodlo;
while(*p)
{
if((*p)>=65 && (*p)<=90)
*p = tolower(*p);
p++;
}
}

int main()
{
char nazwa[50];

strcpy(nazwa,"Servile Software");

printf("\nWarto

ść

ci

ą

gu 'nazwa' to %s",nazwa);


strupr(nazwa);

printf("\nWarto

ść

ci

ą

gu 'nazwa' to %s",nazwa);


strlwr(nazwa);

printf("\nWarto

ść

ci

ą

gu 'nazwa' to %s",nazwa);

return 0;
}
(To niezupełnie tak. Funkcje toupper i tolower tworz

ą

litery wielkie

i małe nie sprawdzaj

ą

c, jaka jest posta

ć

ź

ródłowa. Konwersja odbywa

si

ę

odpowiednio poprzez odj

ę

cie lub dodanie do warto

ś

ci znaku 32 (bo

taka jest ró

ż

nica pomi

ę

dzy odpowiadaj

ą

cymi sobie literami wielkimi i

małymi). Je

ś

li argument funkcji toupper b

ę

dzie ju

ż

liter

ą

wielk

ą

,

wynik konwersji oka

ż

e si

ę

bezsensowny. W tym przypadku ‘S’ zostanie

zamienione na ‘3’). W funkcjach strlwr i strupr potrzebne byłoby wi

ę

c

sprawdzanie, czy znak spełnia kryteria, np.:
while (*p)
{
if((*p)>=97 && (*p)<=122)

*p = toupper(*p);

p++;
}
oraz
while (*p)
{
if((*p)>=65 && (*p)<=90)

*p = tolower(*p);

p++;
}

Dodatkowe linie programu wstawiłem w kod P.B.).

W przeciwieństwie do innych języków programowania C nie narzuca ograniczenia
długości ciągu. Jednak w przypadku niektórych procesorów (CPU) pojawia się
ograniczenie wielkości bloku pamięci. Oto prosty program odwracający kolejność
znaków w ciągu:

#include <stdio.h>
#include <string.h>

background image


246

Hack Wars. Tom 1. Na tropie hakerów

246

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc


char *strrev(char *s)
{
/* Odwraca kolejno

ść

znaków w ci

ą

gu, pozostawiaj

ą

c jedynie */

/* ko

ń

cowy znak NULL */


char *pocz;
char *koniec;
char tmp;

/* Ustaw wska

ź

nik 'koniec' na ostatni znak ci

ą

gu */

koniec = s + strlen(s) - 1;

/* Zabezpiecz wska

ź

nik do pocz

ą

tku ci

ą

gu */

pocz = s;

/* Zamiana */
while(koniec >= s)
{
tmp = *koniec;
*koniec = *s;
*s = tmp;
koniec--;
s++;
}
return(pocz);
}

void main()
{
char tekst[100];
char *p;

strcpy(tekst,"To jest ci

ą

g");


p = strrev(tekst);

printf("\n%s",p);
}

strtok()

Funkcja strtok() jest istotną funkcją języka C, służącą do wyłączania fragmentów
ciągu. Stosuje się ją, gdy poszczególne podciągi rozdzielone są znanymi
ogranicznikami, na przykład przecinkami:

#include <stdio.h>
#include <string.h>

void main()
{
char dane[50];
char *p;

strcpy(dane,"CZERWONY,POMARA

Ń

CZOWY,

ś

ÓŁTY,ZIELONY,NIEBIESKI");


p = strtok(dane,",");
while(p)
{
puts(p);

background image


Rozdział 6.

Podstawy programowania dla hakerów

247

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

247

p = strtok(NULL,",");
};
}

Program można oprzeć też na pętli

for()

:

#include <stdio.h>
#include <string.h>

void main()
{
char dane[50];
char *p;

strcpy(dane,"CZERWONY,POMARA

Ń

CZOWY,

ś

ÓŁTY,ZIELONY,NIEBIESKI");


for( p = strtok(dane,","); p; p = strtok(NULL,","))
{
puts(p);
};
}

W pierwszym wywołaniu funkcji

strtok()

podajemy nazwę zmiennej ciągu oraz

ogranicznik. Funkcja zwraca wówczas wskaźnik do początku pierwszego podciągu
i zastępuje pierwszy ogranicznik zerem. Kolejne wywołania

strtok()

wykonywane są

w pętli. Pierwszym parametrem jest wówczas

NULL

, a funkcja zwraca kolejne

podciągi. Ponieważ dopuszczalne jest podanie listy ograniczników, funkcja

strtok()

może posłużyć do utworzenia prostego programu zliczającego słowa:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(int argc, char *argv[])
{
FILE *fp;
char bufor[256];
char *p;
long licznik;

if (argc != 2)
{
fputs("\nBŁ

Ą

D. Poprawna składnia wywołania: wordcnt

f1\n",stderr);
exit(0);
}

/* Otwórz plik do odczytu */
fp = fopen(argv[1],"r");

/* Sprawd

ź

czy plik został otwarty */

if (!fp)
{
fputs("\nBŁ

Ą

D. Nie mo

ż

na otworzy

ć

pliku

ź

ródłowego\n",stderr);

exit(0);
}

/* Inicjuj licznik */
licznik = 0;

do

background image


248

Hack Wars. Tom 1. Na tropie hakerów

248

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

{
/* Odczytaj z pliku wiersz danych */
fgets(bufor, 255, fp);

/* Sprawd

ź

czy nie wyst

ą

pił bł

ą

d lub znak EOF */

if (ferror(fp) || feof(fp))
continue;

/* Zlicz słowa w pobranym wierszu */
/* Słowa wyró

ż

nia si

ę

jako elementy rozdzielone znakami */

/* \t (tab) \n (nowy wiersz) , ; : . ! ? ( ) - spacja */
p = strtok(bufor, "\t\n,;:.!?()- ");
while(p)
{
licznik++;
p = strtok(NULL,"\t\n,;:.!?()- ");
}
}
while(!ferror(fp) && !feof(fp));

/* Odczyt zako

ń

czony. Bł

ą

d? */

if (ferror(fp))
{
fputs("\nBł

ą

d przy odczycie pliku

ź

ródłowego\n",stderr);

fclose(fp);
exit(0);
}

/* Odczyt zako

ń

czony poprawnie, znakiem EOF */

/* Wyprowadzamy liczb

ę

słów */

printf("\nPlik %s zawiera %ld słów(słowa)\n",argv[1],licznik);
fclose(fp);
}

Zamiana liczb na ciągi i ciągów na liczby

Wszystkie kompilatory C zapewniają możliwość konwertowania liczb na ciągi przy
użyciu takich funkcji jak

sprintf()

. Funkcja ta ma jednak wiele zastosowań, co

powoduje, że jest rozbudowana i mało wydajna. Może ją zastępować funkcja

ITOS()

, korzystająca z dwóch parametrów: liczby całkowitej ze znakiem i wskaźnika

do ciągu znakowego. Funkcja kopiuje liczbę do określonego wskaźnikiem miejsca w
pamięci. Podobnie jak

sprintf()

, funkcja

ITOS()

nie sprawdza, czy ciąg docelowy

ma wystarczającą do przechowania wyniku konwersji długość. Oto przykładowa
funkcja, która kopiuje liczbę

signed int

do ciągu znakowego.

void ITOS(long x, char *ptr)
{
/*Zamie

ń

dziesi

ę

tn

ą

liczb

ę

całkowit

ą

ze znakiem na ci

ą

g znaków */

long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000,
100, 10, 1 };
int n;

/* Sprawd

ź

znak */

if (x < 0)
{
*ptr++ = '-';
/* Zamie

ń

x na warto

ść

bezwzgl

ę

dn

ą

*/

x = 0 - x;
}

background image


Rozdział 6.

Podstawy programowania dla hakerów

249

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

249


for(n = 0; n < 9; n++)
{
if (x > pt[n])
{
*ptr++ = '0' + x / pt[n];
x %= pt[n];
}
}
*ptr='\0';
(zapewnia zako

ń

czenie ła

ń

cucha znakowego i zapobiega wypisywaniu

głupot, gdy

zmienna tablicowa ma wi

ę

kszy wymiar, ni

ż

liczba tego potrzebuje

P.B.)
return;
}

(Powy

ż

szy program działa nieprawidłowo, gdy w zamienianej liczbie

znajduj

ą

si

ę

zera. Poni

ż

ej przedstawiam proponowan

ą

przeze mnie poprawn

ą

wersj

ę

P.B.):

void ITOS(long x, char *ptr)
{
/*Zamie

ń

dziesi

ę

tn

ą

liczb

ę

całkowit

ą

ze znakiem na ci

ą

g znaków */

long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000,
100, 10, 1 };
int n;
int licznik=0; //licznik potrzebny do zliczania zer na pocz

ą

tku

ci

ą

gu


/* Sprawd

ź

znak */

if (x < 0)
{
*ptr++ = '-';
licznik++;
/* Zamie

ń

x na warto

ść

bezwzgl

ę

dn

ą

*/

x = 0 - x;
}

for(n = 0; n < 9; n++)
{
licznik++;
*ptr++ = '0' + x / pt[n];
x %= pt[n];
}
*ptr='\0';
ptr=ptr-licznik; //powrót wska

ź

nika na pocz

ą

tek ci

ą

gu

licznik=0;
if(*ptr=='-') //omini

ę

cie minusa na pocz

ą

tku (je

ś

li jest)

ptr++;

while(*ptr=='0') { //pomijanie pocz

ą

tkowych zer

licznik++;

ptr++;

}
while(*ptr!='\0') {

*(ptr-licznik)=*ptr; //przepisywanie ci

ą

gu ju

ż

bez zer na

pocz

ą

tku

ptr++;

}
*(ptr-licznik)='\0';

background image


250

Hack Wars. Tom 1. Na tropie hakerów

250

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

return;
}

Język C oferuje dwie funkcje do zamiany ciągów znakowych na liczby
zmiennoprzecinkowe:

atof()

i

strtod()

. Prototyp funkcji

atof()

ma postać:

double atof(const char *s);

a prototyp funkcji

strtod()

:

double strtod(const char *s, char **endptr);

Obie funkcje przeglądają ciąg i przeprowadzają konwersję aż do momentu natrafienia
na niezrozumiały znak. Różnica między nimi polega na tym, że

strtod()

pobiera

dodatkowy parametr, wskaźnik

char

ustawiany na pierwszy znak ciągu, który nie

został objęty konwersją. Znacznie zwiększa to wygodę sprawdzania poprawności
wykonania operacji.

Aby zamienić ciąg na wartość całkowitą, można użyć funkcji

atoi()

:

int atoi(const char *s);

Należy pamiętać, że funkcja

atoi()

nie zapewnia żadnej kontroli przepełnienia

zmiennej. Nie jest zdefiniowana wartość zwracana w takiej sytuacji. W podobny
sposób działa funkcja

atol()

, zwracająca wartość

long

. Odpowiedniki z

dodatkowym parametrem noszą nazwy

strol()

i

stroul()

.

Obsługa tekstu

Człowiek zapisuje informacje jako pewien „tekst”, złożony ze słów, liczb i znaków
przestankowych. Słowa złożone są z liter wielkich i małych, odpowiednio do
wymagań gramatyki. Wszystko to sprawia, że komputerowe przetwarzanie tekstu nie
jest zadaniem prostym. Norma ANSI definiuje wiele funkcji przetwarzania ciągów
znakowych, które z natury rozpoznają wielkość liter. Oznacza to, że litera „A”
rozpoznawana jest jako różna od „a”. Jest to pierwsze zagadnienie, którego
rozwiązanie musi znaleźć programista pracujący nad programem przetwarzającym tekst.
Na szczęście, zarówno kompilatory Borlanda, jak i Microsoftu wyposażone zostały w
funkcje obsługi ciągów, które nie rozpoznają wielkości liter.

Taką odmianą funkcji

strcmp()

jest

stricmp()

, a

strncmp()

strnicmp()

.

Gdy jednak pojawia się kwestia przenośności kodu, niezbędna jest zgodność z ANSI
C, co pociąga za sobą napisanie własnych funkcji.

Poniżej przedstawiamy prostą implementację nierozróżniającej wielkości liter
odmiany funkcji

strstr()

. Tworzy ona kopie ciągów, zamienia je na wielkie litery i

wykonuje standardową operację

strstr()

. Pozwala to określić poszukiwaną

wartość przesunięcia i utworzyć wskaźnik do ciągu źródłowego.

char *stristr(char *s1, char *s2)
{
char c1[1000];

background image


Rozdział 6.

Podstawy programowania dla hakerów

251

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

251

char c2[1000];
char *p;

strcpy(c1,s1);
strcpy(c2,s2);

strupr(c1);
strupr(c2);

p = strstr(c1,c2);
if (p)
return s1 + (p - c1);
return NULL;
}

Kolejna funkcja przegląda ciąg

s1

, wyszukując słowo podane jako

s2

. Aby funkcja

zwróciła wartość

TRUE

, znalezione musi zostać odrębne słowo, a nie jedynie

sekwencja znaków. Wykorzystujemy przygotowaną wcześniej funkcję

stristr()

.

int word_in(char *s1, char *s2)
{
/*zwraca warto

ść

niezerow

ą

, je

ż

eli s2 jest słowem zawartym w s1*/

char *p;
char *q;
int ok;

ok = 0;
q = s1;

do
{
/* Lokalizuj wyst

ą

pienie sekwencji znaków s2 w s1 */

p = stristr(q,s2);
if (p)
{
/* Znaleziony */
ok = 1;

if (p > s1)
{
/* Sprawd

ź

znak przed znalezionym ci

ą

giem*/

if (*(p-1) >= 'A' && *(p-1) <= 'z')
ok = 0;
}

/* Niech p wskazuje koniec ci

ą

gu */

p += strlen(s2);

if (*p)
{
/* Sprawd

ź

znak za znalezionym ci

ą

giem */

if (*p >= 'A' && *p <= 'z')
ok = 0;
}
}
q = p;
}
while(p && !ok);
return ok;
}

background image


252

Hack Wars. Tom 1. Na tropie hakerów

252

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Szerokie zastosowanie znajdzie kilka dalszych prostych funkcji znakowych.

truncstr()

obcina ciąg znakowy:

void truncstr(char *p, int liczba)
{
/* Obcina 'liczba' znaków z ci

ą

gu 'p' */

if (liczba < strlen(p))
p[strlen(p) - liczba] = 0;
}

trim()

usuwa końcowe znaki spacji (odstępu międzywyrazowego) w ciągu:

void trim(char *tekst)
{
/* usuwa spacje ko

ń

cowe */

char *p;

p = &tekst[strlen(tekst) - 1];
while(*p == 32 && p >= tekst)
*p-- = 0;
}

strlench()

zmienia długość ciągu:

void strlench(char *p,int num)
{
/* Zmienia długo

ść

ci

ą

gu, doł

ą

czaj

ą

c lub usuwaj

ą

c znaki */


if (num > 0)
memmove(p + num,p,strlen(p) + 1);
else
{
num = 0 - num;
memmove(p,p + num,strlen(p) + 1);
}
}

strins()

umieszcza jeden ciąg w innym:

void strins(char *p, char *q)
{
/* Wstaw ci

ą

g q do ci

ą

gu p */

strlench(p,strlen(q));
strncpy(p,q,strlen(q));
}

strchg()

zastępuje wszystkie wystąpienia pewnego podciągu innym podciągiem:

void strchg(char *dane, char *s1, char *s2)
{
/* Zast

ę

puje wszystkie wyst

ą

pienia s1 ci

ą

giem s2 */

char *p;
char zmienione;

do
{
zmienione = 0;
p = strstr(dane, s1);
if (p)
{
/* Usu

ń

ci

ą

g znaleziony */

strlench(p, 0 - strlen(s1));

background image


Rozdział 6.

Podstawy programowania dla hakerów

253

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

253


/* Wstaw ci

ą

g */

strins(p,s2);
zmienione = 1;
}
}
while(zmienione);
}

Data i godzina

Język C wyposażony jest w funkcję

time()

, która odczytuje zegar systemowy

komputera i podaje informację o dacie i godzinie w postaci liczby sekund, która
upłynęła od północy 1 stycznia 1970 roku. Wartość ta może zostać zamieniona na
czytelny dla człowieka ciąg znaków za pomocą funkcji

ctime()

:

#include <stdio.h>
#include <time.h>

int main()
{
/* Struktura do przechowywania daty i godziny, z time.h */
time_t t;
/* Pobierz dat

ę

i godzin

ę

systemu */

t = time(NULL);
printf("Bie

żą

ca data i godzina: %s\n",ctime(&t));

}

Na ciąg zwracany przez

ctime()

składa się siedem pól:









dzień tygodnia,









miesiąc roku,









dzień miesiąca,









godzina,









minuty,









sekundy,









rok.

Uzupełnieniem jest znak nowego wiersza i końcowe 0. Ponieważ pola mają stałą
szerokość, ciąg zwracany przez

ctime()

idealnie nadaje się do operacji

wymagających wyodrębnienia elementów daty lub godziny. W poniższym programie
definiujemy strukturę

godzina

oraz funkcję

pobierz_godzine()

, której zadaniem

jest wypełnienie struktury treścią pól ciągu

ctime()

:

#include <stdio.h>
#include <time.h>
#include <string.h>

struct godzina
{
int g_min; /* Minuty */
int g_godz; /* Godziny */

background image


254

Hack Wars. Tom 1. Na tropie hakerów

254

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

int g_sek; /* Sekundy */
};

void pobierz_godzine(struct godzina *teraz)
{
time_t t;
char temp[26];
char *ts;

/* Pobierz dat

ę

i godzin

ę

systemu */

t = time(NULL);

/* Przedstaw dat

ę

i godzin

ę

w postaci ci

ą

gu */

strcpy(temp,ctime(&t));

/* Obetnij ostatnie pole */
temp[19] = 0;

ts = &temp[11];

/* Przeszukaj ci

ą

g i skopiuj elementy do struktury */

sscanf(ts, "%2d:%2d:%2d",&teraz->g_godz,&teraz->g_min,&teraz-
>g_sek);
}

int main()
{
struct godzina teraz;

pobierz_godzine(&teraz);

printf("\nJest godzina
%02d:%02d:%02d",&teraz.g_godz,&teraz.g_min,&teraz.g_sek);
}

Norma ANSI przewidziała również funkcję konwertującą wartość zwracaną przez
funkcję

time()

do postaci struktury. Przedstawiony poniżej przykład zawiera

deklarację struktury

tm

z nagłówka

time.h

:

#include <stdio.h>
#include <time.h>

int main()
{
time_t t;
struct tm *tb;

/* Pobierz czas do t */
t = time(NULL);

/* Zamie

ń

warto

ść

t na struktur

ę

tb */

tb = localtime(&t);

printf("\nJest godzina %02d:%02d:%02d",tb->tm_hour,tb->tm_min,tb-
>tm_sec);
return (0);
}

Struktura

tm

(zawarta w pliku

time.h

) ma następującą postać:

struct tm

background image


Rozdział 6.

Podstawy programowania dla hakerów

255

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

255

{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
(Ta struktura nie mo

ż

e by

ć

cz

ęś

ci

ą

programu, jak to zasugerowano, bo

jest ju

ż

zdefiniowana w pliku nagłówkowym. W takiej sytuacji kompilator

wy

ś

wietla bł

ą

d. Mo

ż

na j

ą

zostawi

ć

w tym miejscu z komentarzem,

który podałem na górze, wzgl

ę

dnie przenie

ść

na stron

ę

51 P.B.)

Liczniki czasu

Programy często korzystają z możliwości pobrania daty i czasu z nieulotnej pamięci
RAM komputera. Norma ANSI przewiduje kilka różnych funkcji, które mogą zostać
do tego celu wykorzystane. Pierwszą jest funkcja

time()

, zwracająca liczbę sekund

od 1 stycznia 1970 roku:

time_t time(time_t *timer);

Funkcja wypełnia przekazaną jej jako parametr zmienną typu

time_t

(

jeśli nie jest

to

NULL)

, zwracając tę samą wartość również jako wartość wyjściową. Można więc

wywoływać funkcję

time()

z parametrem

NULL

i korzystać z wartości zwracanej:

#include <time.h>

void main()
{
time_t teraz;

teraz = time(NULL);
}

Funkcja

asctime()

zamienia strukturę

tm

na 26-znakowy ciąg (przedstawiony przy

opisie funkcji

ctime()

):

char *asctime(const struct tm *struktura);

Funkcja

ctime()

zamienia wartość czasu (zwracaną przez

time()

) na 26-znakowy

ciąg:

#include <stdio.h>
#include <time.h>
#include <string.h>


void main()
{
time_t teraz;
char data[30];

teraz = time(NULL);
strcpy(data,ctime(&teraz));

background image


256

Hack Wars. Tom 1. Na tropie hakerów

256

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

}

Kolejna funkcja,

difftime()

, zwraca, liczoną w sekundach, różnicę między dwoma

wartościami typu

time_t

. Służy więc do wyznaczania ilości czasu, jaki upłynął

między dwoma zdarzeniami, czasu wykonywania funkcji lub generowania przerw w
pracy programu, na przykład:

#include <stdio.h>
#include <time.h>

void DELAY(int okres)
{
time_t pocz;

pocz = time(NULL);
while(time(NULL) < pocz + okres)
;
}

void main()
{
printf("\nRozpoczynam oczekiwanie... (5 sekund)");

DELAY(5);

puts("\nOczekiwanie zako

ń

czone.");

}

Funkcja

gmtime()

zamienia lokalną wartość czasu

time_t

na wartość GMT o

postaci struktury

tm

. Działanie tej funkcji zależy od ustawienia globalnej zmiennej

strefy czasowej. Struktura

tm

została wstępnie zdefiniowana w nagłówku

time.h

.

Przedstawiliśmy ją kilka stron wcześniej.

struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};

Element struktury

tm_mday

przechowuje dzień miesiąca (od 1 do 31), a

tm_wday

dzień tygodnia (gdzie niedzieli odpowiada 0). Czas jest mierzony od 1900 roku.
Wartość

tm_isdst

to znacznik, który informuje o tym, czy stosowany jest czas letni.

Stosowane nazwy struktury i jej elementów mogą różnić się w zależności od
kompilatora, jednak sama struktura zasadniczo pozostaje niezmieniona.

Funkcja

mktime()

zamienia strukturę

tm

na wartość

time_t

, uzupełniając wartości

pól

tm_wday

i

tm_yday

:

time_t mktime(struct tm *t);

background image


Rozdział 6.

Podstawy programowania dla hakerów

257

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

257

W kolejnym przykładzie umożliwiamy wprowadzanie daty i używamy funkcji

mktime()

do ustalenia dnia tygodnia. Należy pamiętać, że funkcje związane z

czasem rozpoznają wyłącznie daty późniejsze niż 1 stycznia 1970:

#include <stdio.h>
#include <time.h>
#include <string.h>

void main()
{
struct tm tstruct;
int okay;
char data[100];
char *p;
char *wday[] =
{"niedziela","poniedziałek","wtorek","

ś

roda","czwartek","pi

ą

tek","sob

ota", "przed rokiem 1970 - nieznany"};
do
{
okay = 0;
printf("\nWprowad

ź

dat

ę

w formacie dd/mm/rr ");

p = fgets(data, 9,stdin);
p = strtok(data,"/");

if (p!= NULL)
tstruct.tm_mday = atoi(p);
else
continue;

p = strtok(NULL,"/");
if (p != NULL)
tstruct.tm_mon = atoi(p);
else
continue;

p = strtok(NULL, "/");

if (p != NULL)
tstruct.tm_year = atoi(p);
else
continue;
okay = 1;
}
while(!okay);

tstruct.tm_hour = 0;
tstruct.tm_min = 0;
tstruct.tm_sec = 1;
tstruct.tm_isdst = -1;

/* Teraz ustalimy dzie

ń

tygodnia */

if (mktime(&tstruct) == -1)
tstruct.tm_wday = 7;

printf ("Ten dzie

ń

to %s\n", wday[tstruct.tm_wday]);

}

Funkcja

mktime()

zapewnia również wprowadzenie odpowiednich poprawek dla

wartości przekraczających swój dopuszczalny zakres. Można to wykorzystać do
ustalenia dokładnej daty, odległej o

n

dni:

background image


258

Hack Wars. Tom 1. Na tropie hakerów

258

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

#include <stdio.h>
#include <time.h>
#include <string.h>

void main()
{
struct tm *tstruct;
time_t dzisiaj;

dzisiaj = time(NULL);
tstruct = localtime(&dzisiaj);

tstruct->tm_mday += 10;
mktime(tstruct);
if(tstruct->tm_year>99)

tstruct->tm_year%=100;


(te dwie linie zapobiegaj

ą

wy

ś

wietlaniu bł

ę

dnego roku w przypadku,

gdy czas, jaki upłyn

ą

ł od roku 1900 jest dłu

ż

szy od 99 lat P.B.)


printf("Za dziesi

ęć

dni b

ę

dzie %02d/%02d/%02d\n",tstruct-

>tm_mday,tstruct->tm_mon +

1,tstruct->tm_year);
}

Pliki nagłówkowe

W plikach nagłówkowych umieszczone są prototypy funkcji bibliotecznych
kompilatora oraz standardowe makra. Norma ANSI wymienia następujące pliki tej
grupy.

Plik nagłówka

Opis

assert.h

definicja makra wspomagającego analizę programu,

assert

,

ctype.h

makra do klasyfikowania i konwersji znaków,

errno.h

stałe kodów błędów,

float.h

specyficzne dla implementacji makra dla operacji
zmiennoprzecinkowych,

limits.h

opisuje specyficzne dla implementacji ograniczenia dla wartości różnych
typów,

locale.h

parametry dla ustawień narodowych,

math.h

prototypy funkcji matematycznych,

setjmp.h

definicja

typedef

oraz funkcji dla

setjmp

i

longjmp

,

signal.h

zawiera stałe i deklaracje wykorzystywane przez funkcje

signal()

i

raise()

,

stdarg.h

zawiera makra do obsługi list argumentów,

stddef.h

definicje podstawowych typów danych i makr,

stdio.h

typy i makra wymagane do obsługi standardowych funkcji we-wy,

stdlib.h

zbiór różnorodnych, często wykorzystywanych deklaracji i prototypów
funkcji,

background image


Rozdział 6.

Podstawy programowania dla hakerów

259

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

259

string.h

prototypy funkcji operujących na łańcuchach znakowych,

time.h

struktury dla procedur konwersji wartości daty i godziny.

Debugowanie programu

Norma ANSI definiuje wspomagające debugowanie makro

assert()

. Działa ono jak

instrukcja

if()

. Spełnienie podanego warunku powoduje jednak przerwanie

wykonywania programu i wyprowadzenie na standardowy strumień informacji o błędach
komunikatu:

Assertion failed: <test>, file < nazwa_pliku>, line <numer_wiersza>
Abnormal program termination

Rozważmy przykład, w którym program omyłkowo przypisuje wskaźnikowi wartość 0:

#include <stdio.h>
#include <assert.h>

void main()
{
/* Przykład zastosowania makra assert */

int *ptr;
int x;

x = 0;

/* W tym wierszu jest bł

ą

d! */

ptr=x;

assert(ptr != NULL);
}

Program taki po uruchomieniu zakończy szybko pracę wyświetleniem komunikatu:

Assertion failed: ptr != 0, file TEST.C, line 16
Abnormal program termination

Gdy program pracuje już poprawnie, funkcja

assert()

może zostać z niego usunięta

przez proste dopisanie przed wierszem

#include <assert.h>

:

#define NDEBUG

Dyrektywa taka zapewni oznaczenie, w przygotowywanym do kompilacji kodzie,
wszystkich funkcji

assert

jako komentarzy.

Błędy wartości zmiennoprzecinkowych

Liczby zmiennoprzecinkowe to ułamki dziesiętne, które nie odpowiadają dokładnie
ułamkom zwykłym (nie każda liczba może zostać równo podzielona przez 10).
Konsekwencją tego jest niebezpieczeństwo błędów zaokrągleń w obliczeniach.
Poniższy program przedstawia przykładowy problem:

#include <stdio.h>

background image


260

Hack Wars. Tom 1. Na tropie hakerów

260

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc


void main()
{
float liczba;

for(liczba = 1; liczba > 0.4; liczba -= 0.01)
printf ("\n%f",liczba);
}

Poczynając od wartości ok. 0,47 (zależnie od komputera i kompilatora), program
zacznie generować niedokładne wartości.

Zjawisko to można ograniczyć, stosując dłuższe typy zmiennoprzecinkowe,

double

lub

long double

, korzystające z większej liczby bitów. Gdy wymagana jest wysoka

dokładność,

należy

używać

wartości

całkowitych,

zamienianych

na

zmiennoprzecinkowe tylko przed wyświetleniem na ekranie. Nie wolno również
zapominać, że większość kompilatorów C jako domyślny typ zmiennoprzecinkowy
przyjmuje

double

i użycie innych typów może wymagać konwersji.

Obsługa błędów

Gdy w trakcie wykonywania programu wystąpi błąd systemowy — jak na przykład
przy nieudanej próbie otwarcia pliku — powinien zostać wyświetlony komunikat
dostarczający informacji o zdarzeniu. Takie przygotowanie pomaga w dużej mierze
programiście, dostarczając mu, jeżeli nie informacji o przyczynie błędu, to
przynamniej pewnych wskazówek. Norma C opisuje funkcję obsługującą wymianę
tego rodzaju informacji,

perror()

:

void perror(const char *s);

Jako parametr podajemy ciąg, który funkcja wyświetli jako pierwszy. Drugim
ciągiem, wyświetlanym po dwukropku, jest treść systemowego komunikatu błędu.
Poniżej przedstawiamy prosty przykład użycia funkcji

perror()

:

#include <stdio.h>

void main()
{
FILE *fp;
char nazwa[] = "nic.xyz";

fp = fopen(nazwa,"r");

if(!fp)
perror(nazwa);
return;
}

Jeżeli operacja

fopen()

nie zostanie wykonana, wyświetlany jest komunikat w

rodzaju:

nic.xyz: No such file or directory

background image


Rozdział 6.

Podstawy programowania dla hakerów

261

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

261

Funkcja

perror()

przesyła dane do strumienia predefiniowanego

stderr

,

zazwyczaj skojarzonego z kartą graficzną i monitorem komputera. Komunikat
systemowy odnajdywany jest za pośrednictwem zmiennej globalnej

errno

, której

wartość ustawia większość funkcji systemowych (choć nie wszystkie).

Jedynie w wyjątkowych przypadkach skorzystać można z funkcji

abort()

, która,

przerywając wykonywanie programu, wyświetla komunikat w rodzaju „Niewłaściwe
zakończenie programu” i zwraca procesowi nadrzędnemu lub systemowi
operacyjnemu kod 3.

Obsługa błędów krytycznych na platformie IBM PC i w systemie DOS

System PC DOS przewiduje możliwość wpływu użytkownika na działania
podejmowane przez funkcje obsługi błędów krytycznych. Łatwo się o tym przekonać,
próbując dokonać zapisu w pustej stacji dyskietek. Pojawia się wówczas znajome:

Not ready; error writing drive A
Abort Retry Ignore?

Poniższy program przedstawia, w jaki sposób można przekierować obsługę tego
rodzaju zdarzeń do własnych funkcji:

#include <stdio.h>
#include <dos.h>

void interrupt nowe_int();
void interrupt (*stare_int)();

char stan;

void main()
{
FILE *fp;

stare_int = getvect(0x24);

/* Skieruj obsług

ę

ę

dów krytycznych do mojej funkcji */

setvect(0x24, nowe_int);

/* Generuj bł

ą

d braku dysku w stacji A */

fp = fopen("a:\\dane.txt","w+");

/* Wy

ś

wietl kod stanu */

printf("\nKod stanu == &d",stan);
}

void interrupt nowe_int()
{
/* zapisz warto

ść

globalnego kodu bł

ę

du */

stan = _DI;

/* ignoruj bł

ą

d i zako

ń

cz */

_AL = 0;
}

background image


262

Hack Wars. Tom 1. Na tropie hakerów

262

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Gdy wywoływane jest przerwanie błędu krytycznego systemu DOS, komunikat stanu
umieszczany jest w młodszym bajcie rejestru DI. Lista kodów obejmuje wymienione
poniżej.

Kod stanu operacji

Znaczenie

00

zabezpieczenie przed zapisem,

01

jednostka nieznana,

02

dysk nie jest gotowy,

03

nieznane polecenie,

04

błąd danych, niezgodna wartość CRC,

05

niewłaściwa długość struktury żądania,

06

błąd wyszukiwania,

07

nieznany nośnik danych,

08

nie znaleziono sektora,

09

brak papieru w drukarce,

0A

błąd zapisu,

0B

błąd odczytu,

0C

błąd ogólny.

Własna funkcja obsługi błędów krytycznych może przekazać komunikat stanu do
zmiennej globalnej i ustawić w rejestrze AL kod dalszych czynności.

Kod

Operacja

00

ignoruj błąd,

01

powtórz,

02

zakończ program,

03

zrezygnuj (dostępny od DOS 3.3).

Jeżeli decydujemy się na ustawienie wartości rejestru AL na 02, musimy koniecznie
zadbać o zamknięcie wszystkich plików — DOS kończy pracę programu natychmiast,
pozostawiając otwarte pliki i poprzydzielane bloki pamięci.

Poniżej przedstawiamy całkiem praktyczną funkcję służącą do sprawdzania, czy można
uzyskać dostęp do określonego dysku. Ma ona współpracować z przedstawionym
wcześniej programem obsługi błędów i zmienną globalną

stan

:

int DISKOK(int dysk)
{
/* Sprawdza czy mo

ż

na odczytywa

ć

dane z okre

ś

lonego dysku */

/* Zwraca false (0) w przypadku bł

ę

du */

/* St

ą

d if(!DISKOK(dysk)) */

/* error(); */

unsigned char bufor[25];

/* Zakładamy,

ż

e wszystko jest OK */

background image


Rozdział 6.

Podstawy programowania dla hakerów

263

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

263

stan = 0;

/* Je

ż

eli znamy ju

ż

dysk, zwracamy OK */

if ('A' + dysk == diry[0])
return(1);

/* Próbuj odczytu z dysku */
memset(bufor,0,20);
sprintf(bufor,"%c:$$$.$$$",'A'+dysk);
_open(bufor,O_RDONLY);

/* Sprawd

ź

stan */

if (stan == 0)
return(1);

/* Odczyt nie jest mo

ż

liwy */

return(0);
}

Konwersja typów zmiennych

Konwersja typów zmiennych pozwala poinformować kompilator, jakiego typu są
wykorzystywane dane oraz zmieniać ich typ. Rozważmy następujący przykład:

#include <stdio.h>

void main()
{
int x;
int y;

x = 10;
y = 3;

printf("\n%1f", x / y);
}

Poinformowaliśmy funkcję

printf()

, że powinna oczekiwać wartości

double

.

Kompilator rozpoznaje jednak liczby całkowite

x

i

y

— generowany jest błąd (tylko

w niektórych środowiskach programistycznych — np. Microsoft Visual C++ bez
problemu skompiluje taki program, tyle, że wyświetlona wartość będzie równa 0 P.B.).
Aby program mógł działać, należy poinformować kompilator, że wartość wyrażenia

x/y

będzie miała typ

double

:

#include <stdio.h>

void main()
{
int x;
int y;

x = 10;
y = 3;

printf("\n%1f", (double)(x / y));
}

background image


264

Hack Wars. Tom 1. Na tropie hakerów

264

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Zwróćmy uwagę na fakt, że w nawiasy ujęta jest zarówno nazwa typu, jak i samo
konwertowane wyrażenie. Kompilator traktuje teraz liczby

x

i

y

jako całkowite, a

wynik operacji dzielenia jako wartość

double

— oznacza to, że przeprowadzone

zostanie dzielenie całkowite (dające w tym wypadku wynik równy 3). Rozwiązaniem
jest zatem konwersja obu zmiennych:

#include <stdio.h>

void main()
{
int x;
int y;

x = 10;
y = 3;

printf("\n%1f", (double)(x) / (double)(y));
}

Teraz, gdy obie liczby mają typ

double

, oczywiste jest, że i wynik ich dzielenia

będzie wartością

double

.

Prototypy

Zadaniem prototypów funkcji jest odpowiednio wczesne dostarczenie kompilatorowi
informacji o tym, jakiego typu wartości funkcja pobiera i zwraca. Przyjrzyjmy się na
przykład funkcji

strtok()

o prototypie:

char *strtok(char *s1, const char *s2);

Jest to informacja dla kompilatora, że funkcja

strtok

zwraca wskaźnik do danych

znakowych. Pierwszym parametrem będzie wskaźnik do ciągu znakowego i parametr
ten jest modyfikowalny wewnątrz funkcji. Drugi parametr to wskaźnik do ciągu
znakowego, którego zawartość nie może być przez funkcję zmieniana. Kompilator
dysponuje dzięki temu informacją o ilości pamięci wymaganej przez wartość funkcji

sizeof (char *)

. Bez dostępu do prototypu funkcji kompilator przyjąłby

założenie, że funkcja zwraca wartość

int

i zaalokował pamięć wielkości

(sizeof(int))

. Jeżeli wartość

int

i wskaźnik korzystają na danym komputerze z

takiej samej liczby bajtów, nic złego się nie dzieje. Jednak jeżeli rozmiar wskaźnika
jest większy, kompilator nie będzie dysponował odpowiednią ilością miejsca dla
wartości zwróconej przez funkcję

strtok()

. Wykorzystywany jest wówczas

nieoczekiwanie kolejny adres pamięci.

Szczęśliwie jednak większość kompilatorów C ostrzega o niepoprzedzonych
prototypem wywołaniach funkcji. Oto prosty przykład programu, na którego
kompilację nie pozwoli większość nowoczesnych kompilatorów C.

#include <stdio.h>

int funkcjaa(int x, int y)
{
return(MNOZ(x,y));

background image


Rozdział 6.

Podstawy programowania dla hakerów

265

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

265


double MNOZ(double x, double y)
{
return(x * y);
}

void main()
{
printf("\n%d",funkcjaa(5,5));
}

Gdy kompilator po raz pierwszy napotyka funkcję

MNOZ()

, jest to wywołanie z

wnętrza bloku

funkcjaa()

. W przypadku braku prototypu

MNOZ()

przyjmowane

jest założenie, że funkcja zwraca wartość

int

. Kiedy kompilator napotyka definicję

funkcji

MNOZ()

, stwierdza, że deklarowany jest typ

double

. Generowany jest

wówczas błąd kompilacji:

"Type mismatch in redeclaration of function 'MNOZ'"

Jest to wyraźny nakaz wprowadzenia prototypu funkcji! Gdyby udało się taki
program skompilować i uruchomić, najbardziej prawdopodobnym scenariuszem
byłaby awaria stosu.

Wskaźniki do funkcji

Język C przewiduje, że wskaźnik może odwoływać się do adresu funkcji. Co więcej,
wskaźnik taki może być stosowany w miejsce jawnego wywołania funkcji.
Wykorzystują to funkcje modyfikujące przerwania. Mechanizm ten można również
zastosować do indeksowania funkcji, na przykład:

#include <stdio.h>
#include <math.h>

double (*fp[7])(double x);

void main()
{
double x;
int p;

fp[0] = sin;
fp[1] = cos;
fp[2] = acos;
fp[3] = asin;
fp[4] = tan;
fp[5] = atan;
fp[6] = ceil;

p = 4;

x = fp[p](1.5);
printf("\nWynik: %1f",x);
}

Definiujemy tu tablicę wskaźników do funkcji

(*fp[])()

, które wywoływane są

odpowiednio do wartości zmiennej

p

. Identycznie działa program:

background image


266

Hack Wars. Tom 1. Na tropie hakerów

266

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

#include <stdio.h>
#include <math.h>

void main()
{
double x;
int p;

p = 4;

switch (p)
{
case 0 : x = sin(1.5);
break;
case 1 : x = cos(1.5);
break;
case 2 : x = acos(1.5);
break;
case 3 : x = asin(1.5);
break;
case 4 : x = tan(1.5);
break;
case 5 : x = atan(1.5);
break;
case 6 : x = ceil(1.5);
break;
}
printf("\nWynik: %1f",x);
}

Dzięki zastosowaniu wskaźników do funkcji kod w pierwszym przykładzie jest
zwięźlejszy i szybciej wykonywany. Tablica wskaźników do funkcji może być
przydatnym narzędziem podczas pisania interpretera języka. Program porównuje
wówczas wprowadzoną instrukcję z tablicą słów kluczowych, wyszukując
odpowiednią wartość indeksującą. Następnie wywoływany jest odpowiedni wskaźnik
do funkcji. Rozwiązanie takie znakomicie zastępuje rozbudowaną instrukcję

switch()

.

Sizeof

Instrukcja preprocesora

sizeof

zwraca rozmiar elementu danych, którym może być

struktura, wskaźnik, ciąg lub dowolny inny obiekt. Jednak i w tym przypadku
wymagana jest odrobina uwagi. Przyjrzyjmy się programowi:

#include <stdio.h>
#include <mem.h>

char ciag1[80]; char *tekst = "To jest ciag znakowy";

void main()
{
/* Inicjujemy ciag1 */
memset(ciag1, 0, sizeof(ciag1));

/* Kopiujemy do ciagu pewien tekst ? */
memcpy(ciag1, tekst, sizeof(tekst));

/* Wy

ś

wietlamy ciag1 */

background image


Rozdział 6.

Podstawy programowania dla hakerów

267

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

267

printf("\nCi

ą

g 1 = %s\n",ciag1);

}

Rozpoczynamy od zainicjowania ciągu

ciag1

80 zerami, po czym kopiujemy ciąg

tekst

do zmiennej

ciag1

. Jednak zmienna

tekst

jest wskaźnikiem, a więc

sizeof(tekst)

zwraca rozmiar wskaźnika (najczęściej 2 bajty), a nie długość ciągu,

który wskazuje. Błąd taki może pozostać niezauważony, gdy długość ciągu jest zgodna
z rozmiarem wskaźnika.

Przerwania

BIOS komputera PC i system DOS wyposażone są w funkcje, które mogą być
wywoływane przez program za pośrednictwem skojarzonego z nimi numeru
przerwania.

Adresy

przypisanych

do

poszczególnych

przerwań

funkcji

przechowywane są w tabeli w pamięci RAM, określanej jako tablica wektorów
przerwań
. Zmiana adresu wektora przerwania umożliwia programowi zastąpienie
standardowej funkcji obsługi przerwania funkcją własną.

Borland Turbo C oferuje dwie funkcje biblioteczne służące do odczytywania i
modyfikowania wartości wektorów przerwań:

setvect()

i

getvect()

. Ich

odpowiedniki w bibliotekach Microsoftu noszą nazwy:

_dos_setvect()

i

_dos_getvect()

.

Prototyp funkcji

getvect()

ma postać:

void interrupt(*getvect(int nr_przerwania))();

a prototyp funkcji

setvect()

:

void setvect(int nr_przerwania, void interrupt(*funkcja)());

Przy odczytywaniu i zapisywaniu adresów użycie funkcji

getvect()

wygląda

następująco:

void interrupt(*stary)(void);

void main()
{
/* pobierz stary wektor przerwania */
stary = getvect(0x1C);
.
.
.
}

W tym przypadku pobieranym wektorem jest 0x1C. Aby ustawić wektor na adres
własnej funkcji, używamy

setvect()

:

void interrupt nowa(void)
{
.
.
/* Nowa funkcja obsługi przerwania */
.
.

background image


268

Hack Wars. Tom 1. Na tropie hakerów

268

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

.
}

void main()
{
.
.
.
setvect(0x1C, nowa);
.
.
.
.
}

Gdy zajmujemy się przerwaniami, nie możemy zapominać o dwóch rzeczach.
Pierwszą jest to, że przed zmianą wektora przerwania wywoływanego zdarzeniami
zewnętrznymi należy wstrzymać możliwość takich wywołań poleceniem

disable()

.

Po wprowadzeniu modyfikacji przywracamy funkcjonowanie przerwania poleceniem

enable()

. Efekt wywołania przerwania w trakcie zmiany wektora (gdy nie użyjemy

funkcji

disable()

) jest nieprzewidywalny.

Drugą istotną rzeczą jest zadbanie o przywrócenie stanu wektorów przerwań przed
zakończeniem pracy programu i przywróceniem kontroli systemowi operacyjnemu.
Wyjątkiem jest jedynie wektor przerwania obsługi błędów krytycznych, przywracany
przez system DOS automatycznie.

Poniższy przykładowy program przejmuje przerwanie zegara systemowego (hooks an
interrupt
):

#include <stdio.h>
#include <dos.h>
#include <time.h>
#include <conio.h>
#include <stdlib.h>

enum {FALSE, TRUE};

#define KOLOR (NIEBIESKI << 4 ) | ZOLTY

#define BIOS_TIMER 0x1C

static unsigned zainstalowany = FALSE;
static void interrupt (*stary_tick) (void);

static void interrupt tick (void)
{
int i;
struct tm *teraz;
time_t godzina;
char bufor[9];
static time_t godzina_pop = 0L;
static char bufor_wysw[20] =
{
' ', KOLOR, '0', KOLOR, '0', KOLOR, ':', KOLOR, '0', KOLOR,
'0', KOLOR, ':', KOLOR, '0', KOLOR, '0', KOLOR, ' ', KOLOR
};

background image


Rozdział 6.

Podstawy programowania dla hakerów

269

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

269

enable ();

if (time (&godzina) != godzina_pop)
{
godzina_pop = godzina;

teraz = localtime(&godzina);

sprintf(bufor,"02d:%02d.%02d",teraz->tm_hour,teraz->tm_min,teraz-
>tm_sec);

for (i = 0; i < 8; i++)
{
bufor_wysw[(i + 1) << 1] = bufor[i];
}

puttext (71, 1, 80, 1, bufor_wysw);
}

stary_tick();
}

void zatrzymaj (void)
{
if (zainstalowany)
{
setvect(BIOS_TIMER, stary_tick);
zainstalowany = FALSE;
}
}

void zegar_start (void)
{
static unsigned pierwszy_raz = TRUE;

if (!zainstalowany)
{
if (pierwszy_raz);
{
atexit (zatrzymaj);
pierwszy_raz = FALSE;
}

stary_tick = getvect (BIOS_TIMER);
setvect (BIOS_TIMER, tick);
zainstalowany = TRUE;
}
}

Funkcja signal()

Przerwania można przechwytywać i wykorzystywać na różne sposoby. Jednym z
prostszych jest użycie funkcji

signal()

. Korzysta ona z dwóch parametrów:

void (*signal (int sygnał, void(*funkcja) (int))) (int);

Pierwszy parametr określa przechwytywany sygnał, drugi — wywoływaną w
momencie uaktywnienia sygnalizatora funkcję. Większość sygnałów zdefiniowanych

background image


270

Hack Wars. Tom 1. Na tropie hakerów

270

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

zostało w nagłówku signal.h. W pliku tym umieszczonych zostało również kilka makr
— wykonujących podstawowe operacje, takie jak ignorowanie sygnału — mogą one
zostać użyte jako parametr

sygnał

.

Na platformie PC często pojawia się potrzeba uniemożliwienia korzystania z
przerywającej pracę programu kombinacji klawiszy CTRL+BREAK. Poniższe
wywołanie funkcji

signal()

zastępuje standardową obsługę predefiniowanego

sygnału

SIGINT

(obsługującego żądanie CTRL+BREAK) również predefiniowanym

makrem

SIG-IGN

, co prowadzi do ignorowania wciskanych przez użytkownika

klawiszy.

signal(SIGINT,SIG_IGN);

W poniższym przykładzie instalujemy przechwytywanie błędów operacji
zmiennoprzecinkowych i dzielenia przez 0 (na komputerze klasy PC):

#include <stdio.h>
#include <signal.h>

void (*stary_sygnal)(int);

void przechwyc(int sygnal)
{
printf("Funkcja przechwyc wywołana przez: %d\n",sygnal);
}

void main()
{
int a;
int b;

stary_sygnal = signal(SIGFPE,przechwyc);

a = 0;
b = 10 / a;

/* Przed wy

ś

ciem przywracamy obsług

ę

standardow

ą

! */

signal(SIGFPE, stary_sygnal);
}

Dynamiczne alokowanie pamięci

Jeżeli program wymaga pewnej tabeli, której rozmiar ulega zmianom (na przykład do
przechowywania listy plików katalogu bieżącego), deklarowanie jej rozmiaru jako
największego

dopuszczalnego

czy

wymaganego,

nie

jest

ekonomicznym

rozwiązaniem. Istnieje bowiem możliwość dynamicznego alokowania pamięci w
zależności od pojawiających się wymagań.

Pamięć dostępną do alokacji dynamicznej Turbo C przyznaje w obszarze nazywanym
stertą (heap). Rozmiar sterty zależy od przyjętego modelu pamięci. Model tiny
przewiduje, że zajętych zostanie ogółem nie więcej niż 64 kB. Model small przyznaje
64 kB dla kodu programu i sterty, umożliwiając korzystanie ze sterty odległej, która
może zajmować pozostałą pamięć konwencjonalną. Inne modele pamięci
udostępniają stercie całą pamięć konwencjonalną. Przy korzystaniu z pierwszego

background image


Rozdział 6.

Podstawy programowania dla hakerów

271

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

271

wymienionych modeli, tj. tiny, istotne jest ograniczenie zużycia pamięci. Często
redukuje się wówczas rozmiar sterty do minimum (czyli jednego bajtu).

Język C oferuje funkcję

malloc()

, pozwalającą zaalokować wolny blok pamięci o

określonym rozmiarze. Funkcja zwraca wskaźnik do początku bloku. Dostępna jest
również funkcja

free()

, dealokująca blok przyznany wcześniej poleceniem

malloc()

. Podczas korzystania z nich nie wolno zapominać, że komputery PC nie

zwalniają poprawnie bloków pamięci i wielokrotne użycie obu funkcji prowadzi do
fragmentacji dostępnych zasobów, a w konsekwencji — niekiedy — braku pamięci.

Poniższy program wyszukuje we wskazanym pliku określony ciąg znaków
(rozróżniając wielkość liter). Funkcja

malloc()

wykorzystywana jest do zaalokowania

dokładnie takiej ilości pamięci, jaka jest wymagana, aby załadować do niej zawartość
pliku:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *bufor;

void main(int argc, char *argv[])
{
FILE *fp;
long flen;

/* Sprawd

ź

liczb

ę

parametrów */

if (argc != 3)
{
fputs("Prawidłowa składnia wywołania: sgrep <tekst>
<plik>",stderr);
exit(0);
}

/* Otwórz powi

ą

zany z plikiem strumie

ń

fp */

fp = fopen(argv[2],"r");
if (!fp)
{
perror("Nie mo

ż

na otworzy

ć

pliku");

exit(0);
}

/* Znajd

ź

koniec pliku */

if (fseek(fp,0L,SEEK_END))
{
fputs("Nie mo

ż

na okre

ś

li

ć

długo

ś

ci pliku", stderr);

fclose(fp);
exit(0);
}

/* Okre

ś

l długo

ść

pliku */

flen = ftell(fp);

/* Sprawd

ź

czy OK. */

if (flen == -1L)
{
fputs("Nie mo

ż

na okre

ś

li

ć

długo

ś

ci pliku", stderr);

background image


272

Hack Wars. Tom 1. Na tropie hakerów

272

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

fclose(fp);
exit(0);
}

/* Ustaw wska

ź

nik na pocz

ą

tek pliku */

rewind(fp);

/* Zaalokuj bufor pami

ę

ci */

bufor = malloc(flen);

if (!bufor)
{
fputs("Nieudana alokacja pami

ę

ci", stderr);

fclose(fp);
exit(0);
}

/* Załaduj plik do bufora */
fread(bufor,flen,1,fp);

/* Sprawd

ź

czy odczyt udany */

if(ferror(fp))
{
fputs("Bł

ą

d odczytu pliku",stderr);


/* Dealokacja bloku pami

ę

ci */

free(bufor);

fclose(fp);
exit(0);
}

printf("%s %s w pliku %s",argv[1],(strstr(bufor,argv[1])) ?
"znalezione" :

"nieznalezione",argv[2]);

/* Przed wyj

ś

ciem dealokujemy blok pami

ę

ci */

free(bufor);
fclose(fp);
}

Funkcja atexit()

Gdy program kończy pracę, powinien zamknąć wszystkie otwarte pliki (wyręcza w tym
programistę kod rozpoczęcia i zakończenia programu, dołączany przez kompilator C)
oraz przywrócić komputer do stanu pewnego uporządkowania. W przypadku dużego
programu, z którego wyjście może nastąpić w wielu różnych punktach, wielokrotne
wypisywanie wywołań do procedury oczyszczającej wymaga pewnego wysiłku. Nie
jest on, na szczęście, konieczny.

Norma ANSI opisuje funkcję

atexit()

, która rejestruje określoną funkcję,

przekazaną poprzez parametr, jako funkcję, która zostanie wywołana przy kończeniu
programu. Zapewniamy w ten sposób jej automatyczne wywołanie. Poniższy
program, bez względu na to, czy wystąpił w nim błąd, czy nie, wywołuje funkcję

zakoncz()

.

#include <stdio.h>

background image


Rozdział 6.

Podstawy programowania dla hakerów

273

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

273

#include <stdlib.h>

void zakoncz()
{
puts("\nPrzetwarzanie zako

ń

czone.");

}

void main()
{
FILE *fp;
int a;
int b;
int c;
int d;
int e;
char tekst[100];

atexit(zakoncz);

fp = fopen("dane.txt","w");

if (!fp)
{
perror("Nie mo

ż

na utworzy

ć

pliku");

exit(0);
}

fprintf(fp,"1 2 3 4 5 \"Wiersz liczb\"");

fflush(fp);

if (ferror(fp))
{
fputs("Bł

ą

d przy zapisywaniu danych strumienia",stderr);

exit(1);
}

rewind(fp);
if (ferror(fp))
{
fputs("Bł

ą

d zerowania wska

ź

nika strumienia",stderr);

exit(1);
}

fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,tekst);
if (ferror(fp))
{
/* Je

ż

eli nie zauwa

ż

yłe

ś

wcze

ś

niejszego bł

ę

du */

/* program ko

ń

czy prac

ę

w tym miejscu */

fputs("Bł

ą

d przy odczycie strumienia",stderr);

exit(1);
}

printf("\nFunkcja fscanf() zwróciła %d %d %d %d %d
%s",a,b,c,d,e,tekst);
}

background image


274

Hack Wars. Tom 1. Na tropie hakerów

274

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

Wydajność

Gdy pojawia się kwestia skrócenia czasu wykonywania programu, istotna jest
znajomość komputera, na którym ten będzie uruchamiany. Większość systemów
stosunkowo wolno wyświetla informacje na ekranie. Język C oferuje różnorodne
funkcje, służące do tego celu. Najbardziej typową, ale zarazem najwolniejszą, jest

printf()

. Gdy tylko jest to możliwe, warto korzystać z konstrukcji

puts(nazwa_zmiennej)

w miejsce

printf("%s\n",nazwa_zmiennej)

(

puts()

dołącza do przesyłanego na ekran ciągu znak nowego wiersza automatycznie).

Podczas mnożenia zmiennej przez stałą o wartości 2 wiele kompilatorów C rozpoznaje,
ż

e w kodzie asemblera jedyną wymaganą operacją jest przesunięcie bitowe w lewo.

Przy mnożeniu przez inne wartości często szybszą pracę zapewni zastosowanie
dodawania, a więc zamiast:

x * 3

piszemy:

x + x + x

Jest to korzystne, gdy mnożnikiem jest stała. Nie należy jednak stosować takiej
konstrukcji z różnymi wartościami mnożnika w pętli.

Kolejną metodą przyspieszenia operacji mnożenia i dzielenia jest odwołanie się do
funkcji przesunięcia bitowego,

<<

i

>>

. Instrukcję

x/=2

można zapisać jako

x>>=1

(przesunięcie bitowe o jedną pozycję w prawo). Wiele kompilatorów samodzielnie
zamienia operację dzielenia przez dwa na operację przesunięcia w prawo.
Przesunięcia można jednak stosować również przy mnożeniu i dzieleniu przez 2, 4, 8,
16, 32, 64, 128, 256, 512, 1024 itd. Jeżeli polecenia przesunięcia są Ci obce,
wystarczy rozważenie dwójkowej postaci liczby:

01001101

czyli, dziesiętnie, 77. Przesunięcie w prawo prowadzi do wartości:

00100110

czyli, dziesiętnie, 38.

Warto stosować w miejsce liczb zmiennoprzecinkowych wartości całkowite. Jest to
możliwe w większej ilości sytuacji, niż mogłoby się początkowo wydawać.
Przykładowo do przekształcenia ułamka na liczbę dziesiętną normalnie używamy
polecenia:

procent = x / y * 100;

Wymaga to zastosowania zmiennych zmiennoprzecinkowych. Można jednak
posłuzyć się konstrukcją:

z = x * 100;
procent = z / y;

background image


Rozdział 6.

Podstawy programowania dla hakerów

275

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

275

Przeszukiwanie katalogów

Funkcje „znajdź pierwszy” i „znajdź następny” stosuje się do wyszukiwania w
katalogu systemu DOS określonej nazwy lub nazw plików. Pierwsza z nich, „znajdź
pierwszy”, realizowana jest za pośrednictwem przerwania systemu DOS nr 21,
funkcji 4E. Pobiera ona specyfikację nazwy pliku w postaci ciągu ASCII, który może
zawierać symbole wieloznaczne, oraz wymagane atrybuty pliku. Funkcja umieszcza
informacje o pliku w obszarze przesyłania danych dysku (DTA, disk transfer area)
i zwraca wyłączony znacznik przeniesienia (carry flag). W przypadku wystąpienia
błędu, którego przyczyną może być brak plików odpowiadających podanemu
wzorcowi, funkcja zwraca ustawiony znacznik przeniesienia.

Po udanym wywołaniu funkcji „znajdź pierwszy” program może wywołać funkcję
„znajdź następny” (przerwanie systemu DOS nr 21, funkcja 4F), która wyszuka kolejny
plik odpowiadający specyfikacji podanej w wywołaniu „znajdź pierwszy”. Udane
wykonanie kończy umieszczenie informacji o pliku w obszarze DTA i zwrócenie
wyłączonego znacznika przeniesienia. W pozostałych przypadkach znacznik jest
ustawiany.

Większość kompilatorów C dla komputerów PC oferuje niestandardowe funkcje
biblioteczne, umożliwiające korzystanie z tych dwóch funkcji systemu DOS. W
Turbo C noszą one nazwy

findfirst()

i

findnext()

(wykorzystanie funkcji

bibliotecznych uwalnia programistę od niezbyt wygodnego, bezpośredniego
korzystania z DTA). Jeżeli korzystamy z Microsoft C, zamiast

findfirst()

i

findnext()

używamy

_dos_ findfirst()

i

_dos_findnext()

.

Poniższy przykład jest próbą najprostszej implementacji polecenia systemu DOS,

dir

:

#include <stdio.h>
#include <dir.h>
#include <dos.h>

void main(void)
{
/* Wy

ś

wietlanie zawarto

ś

ci katalogu bie

żą

cego */

int skonczone;
int dz;
int mies;
int rok;
int godz;
int min;
char amflag;
struct ffblk ffblk;
struct fcb fcb;

/* Rozpocznij od wy

ś

wietlenia podkatalogów */

skonczone = findfirst("*.",&ffblk,16);

while (!skonczone)
{
rok = (ffblk.ff_fdate >> 9) + 80;
mies = (ffblk.ff_fdate >> 5) & 0x0f;
dz = ffblk.ff_fdate & 0x1f;

background image


276

Hack Wars. Tom 1. Na tropie hakerów

276

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

godz = (ffblk.ff_ftime >> 11);
min = (ffblk.ff_ftime >> 5) & 63;

amflag = 'a';

if (godz > 12)
{
godz -= 12;
amflag = 'p';
}

printf("&-11.11s <DIR> %02d-%02d-%02d %2d:%02d%c\n",
ffblk.ff_name,dz,mies,rok,godz,min,amflag);
skonczone = findnext(&ffblk);
}

/* Teraz wszystkie pliki, z wyj

ą

tkiem katalogów */

skonczone = findfirst("*.*",&ffblk,231);

while (!skonczone)
{
rok = (ffblk.ff_fdate >> 9) + 80;
mies = (ffblk.ff_fdate >> 5) & 0x0f;
dz = ffblk.ff_fdate & 0x1f;
godz = (ffblk.ff_ftime >> 11);
min = (ffblk.ff_ftime >> 5) & 63;

amflag = 'a';

if (godz > 12)
{
godz -= 12;
amflag = 'p';
}

parsfnm(ffblk.ff_name,&fcb,1);

printf("%-8.8s %-3.3s %8ld %02d-%02d-%02d %2d:%02d%c\n",
fcb.fcb_name,fcb.fcb_ext,ffblk.ff_fsize,
dz,mies,rok,godz,min,amflag);
skonczone = findnext(&ffblk);
}
}

Funkcja

parsfnm()

to polecenie biblioteki Turbo C, wykorzystujące funkcję

systemu DOS do rozdzielenia zawierającego nazwę pliku ciągu ASCII na części
składowe. Są one umieszczane w tzw. bloku kontrolnym pliku (FCB, file control
block
), skąd mogą zostać łatwo pobrane i wyświetlone przez

printf()

. Obszar DTA

systemu DOS opiera się na następującej strukturze.

Przesunięcie

Długość

Zawartość

00

15

zarezerwowane,

15

Bajt

atrybuty pliku,

16

Słowo

godzina,

18

Słowo

data,

1A

04

rozmiar,

background image


Rozdział 6.

Podstawy programowania dla hakerów

277

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

277

1E

0D

nazwa i rozszerzenie pliku jako ciąg ASCII.

Pole godziny odnosi się do ostatniej modyfikacji pliku i zawiera następujące elementy.

Bity

Zawartość

0 – 4

liczba sekund podzielona przez 2,

5 – 10

minuty,

11 – 15

godziny.

Pole daty również odnosi się do ostatniej modyfikacji pliku.

Bity

Zawartość

0 – 4

dzień,

5 – 8

miesiąc,

9 – 15

liczba lat od roku 1980.

Pobranie tych danych z obszaru DTA wymaga nieco wysiłku, co widać na
wcześniejszym przykładzie. Szerszego komentarza wymaga jeszcze bajt atrybutów.

Bity

Zawartość

0

tylko do odczytu,

1

ukryty,

2

systemowy,

3

etykieta woluminu,

4

katalog,

5

archiwum

Dostęp do pamięci rozbudowanej

Pamięć RAM komputerów PC dostępna jest w trzech odmianach: jako pamięć
konwencjonalna (conventional), rozbudowana (expanded) i rozszerzona (extended).
Pamięć konwencjonalna to początkowe 640 kB, dostępne dla systemu operacyjnego
DOS. Jest to pamięć wykorzystywana standardowo, choć często niewystarczająca.
Adresy pamięci rozbudowanej pozostają poza zakresem pamięci konwencjonalnej.
Dostępu do niej nie umożliwia sam system DOS, ale dodatkowy program, tzw.
program obsługi pamięci LIM EMS. Do jego wywoływania służy przerwanie 67h.

Głównym problemem w dostępie do pamięci rozbudowanej jest to, że bez względu na
ilość tej pamięci w komputerze, jej adresowanie odbywa się za pośrednictwem 16-ki-
lobajtowych bloków, tzw. stron. Jeżeli więc programowi przyznane zostaną 2 MB
pamięci rozbudowanej, będzie to 128 stron (128

16 kB = 2 MB). Dostępność

sterownika LIM EMS można ustalić, otwierając plik (urządzenie IOCTL)

background image


278

Hack Wars. Tom 1. Na tropie hakerów

278

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

EMMXXXX0. Norma LIM wymaga bowiem, aby plik taki istniał, gdy sterownik jest
aktywny.

Poniższy przykład ilustruje sposób korzystania z podstawowych funkcji kontroli i
dostępu do pamięci rozbudowanej.

#include <dos.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define EMM 0x67

char far *emmbase;

emmtest()
{
/*
Sprawdza czy istnieje pami

ęć

rozbudowana,

próbuj

ą

c otworzy

ć

plik EMMXXXX0.

*/

union REGS regs;
struct SREGS sregs;
int error;
long handle;

/* Próba otwarcia urz

ą

dzenia plikowego EMMXXXX0 */

regs.x.ax = 0x3d00;
regs.x.dx = (int)"EMMXXXX0";
sregs.ds = _DS;
intdosx(&regs,&regs,&sregs);
handle = regs.x.ax;
error = regs.x.cflag;

if (!error)
{
regs.h.ah = 0x3e;
regs.x.bx = handle;
intdos(&regs,&regs);
}
return error;
}

emmok()
{
/*
Sprawdza funkcjonowanie mened

ż

era pami

ę

ci robudowanej

*/

union REGS regs;

regs.h.ah = 0x40;
int86(EMM,&regs,&regs);

if (regs.h.ah)
return 0;

regs.h.ah = 0x41;
int86(EMM,&regs,&regs);

emmbase = MK_FP(regs.x.bx,0);
return 1;

background image


Rozdział 6.

Podstawy programowania dla hakerów

279

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

279

}

long emmavail()
{
/*
Zwraca liczb

ę

dost

ę

pnych (wolnych) 16-kilobajtowych stron

pami

ę

ci rozbudowanej lub -1 w przypadku bł

ę

du.

*/

union REGS regs;

regs.h.ah = 0x42;
int86(EMM,&regs,&regs);
if (!regs.h.ah)
return regs.x.bx;
return -1;
}

long emmalloc(int n)
{
/*

śą

da przyznania n stron pami

ę

ci rozbudowanej i zwraca

przypisany im uchwyt plikowy lub -1 w przypadku bł

ę

du

*/

union REGS regs;

regs.h.ah = 0x43;
regs.x.bx = n;
int86(EMM,&regs,&regs);
if (regs.h.ah)
return -1;
return regs.x.dx;
}

emmmap(long handle, int phys, int page)
{
/*
Mapuje fizyczn

ą

stron

ę

pami

ę

ci rozbudowanej do ramki strony

w 16-kilobajtowym oknie pami

ę

ci konwencjonalnej, aby

umo

ż

liwi

ć

wymian

ę

danych pomi

ę

dzy pami

ę

ci

ą

EMS a

konwencjonaln

ą

.

*/

union REGS regs;

regs.h.ah = 0x44;
regs.h.al = page;
regs.x.bx = phys;
regs.x.dx = handle;
int86(EMM,&regs,&regs);
return (regs.h.ah == 0);
}

void emmmove(int page, char *str, int n)
{
/*
Przenosi n bajtów z pami

ę

ci konwencjonalnej do okre

ś

lonej

strony pami

ę

ci rozbudowanej.

*/

char far *ptr;

background image


280

Hack Wars. Tom 1. Na tropie hakerów

280

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc


ptr = emmbase + page * 16384;
while(n-- > 0)
*ptr++ = *str++;
}

void emmget(int page, char *str, int n)
{
/*
Przenosi n bajtów z okre

ś

lonej strony pami

ę

ci rozbudowanej

do pami

ę

ci konwencjonalnej

*/

char far *ptr;

ptr = emmbase + page * 16384;
while(n-- > 0)
*str++ = *ptr++;
}

emmclose(long handle)
{
/*
Zwalnia strony pami

ę

ci EMS alokowane do uchwytu 'handle'

*/

union REGS regs;

regs.h.ah = 0x45;
regs.x.dx = handle;
int86(EMM,&regs,&regs);
return (regs.h.ah == 0);
}

/* Funkcja testuj

ą

ca zdefiniowane wy

ż

ej. */


void main()
{
long emmhandle;
long avail;
char teststr[80];
int i;

if(!emmtest())
{
printf("Pami

ęć

EMS nie jest dost

ę

pna\n");

exit(0);
}

if(!emmok())
{
printf("Nie mo

ż

na odnale

źć

mened

ż

era pami

ę

ci EMS\n");

exit(0);
}

avail = emmavail();
if (avail == -1)
{
printf("Bł

ą

d mened

ż

era pami

ę

ci EMS\n");

exit(0);
}
printf("Liczba dost

ę

pnych stron: %ld\n", avail);

background image


Rozdział 6.

Podstawy programowania dla hakerów

281

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

281

/*

śą

danie 10 stron pami

ę

ci EMS */

if((emmhandle = emmalloc(10)) < 0)
{
printf("Brak pami

ę

ci\n");

exit(0);
}

for (i = 0; i < 10; i++)
{
sprintf(teststr,"%02d Dane testowe\n",i);
emmmap(emmhandle,i,0);
emmmove(0,teststr,strlen(teststr) + 1);
}

for (i = 0; i < 10; i++)
{
emmmap(emmhandle,i,0);
emmget(0,teststr,strlen(teststr) + 1);
printf("ODCZYT BLOKU %d: %s\n",i,teststr);
}

emmclose(emmhandle);
}

Dostęp do pamięci rozszerzonej

Pamięć rozszerzona wyparła niemal całkowicie pamięć rozbudowaną, bo jest szybsza
i prostsza w użyciu. Podobnie jednak jak w przypadku pamięci rozbudowanej, nie jest
możliwy dostęp do pamięci rozszerzonej w trybie standardowym systemu DOS —
przesyłanie danych realizowane jest za pośrednictwem bufora w pamięci
konwencjonalnej (pamięci „trybu rzeczywistego”). Proces kopiowania danych do
pamięci rozszerzonej jest więc dwuetapowy: skopiowanie danych do bufora i dopiero
z niego — do pamięci rozszerzonej.

Przed rozpoczęciem korzystania z pamięci rozszerzonej program powinien sprawdzić,
czy jest ona dostępna. Przedstawiona funkcja

XMS_init()

przeprowadza taki test, po

czym wywołuje kolejną funkcję,

GetXMSEntry()

, która inicjuje korzystanie z

pamięci rozszerzonej przez program. Dodatkowo alokowany jest bufor przesyłania
danych w pamięci konwencjonalnej.

/*
BLOCKSIZE b

ę

dzie rozmiarem bufora w pami

ę

ci konwencjonalnej,

przeznaczonego do wymiany danych z pami

ę

ci

ą

XMS

(wielokrotno

ść

1-kilobajtowej jednostki alokacji XMS)

*/

#ifdef __SMALL__
#define BLOCKSIZE (16L * 1024L)
#endif


#ifdef __MEDIUM__
#define BLOCKSIZE (16L * 1024L)
#endif


#ifdef __COMPACT__

background image


282

Hack Wars. Tom 1. Na tropie hakerów

282

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

#define BLOCKSIZE (64L * 1024L)
#endif

#ifdef __LARGE__
#define BLOCKSIZE (64L * 1024L)
#endif


char XMS_init()
{
/*
zwraca 0, gdy istnieje pami

ęć

XMS,

1 - gdy nie istnieje
2 - je

ż

eli nie mo

ż

na zaalokowa

ć

bufora wymiany danych w

pami

ę

ci konwencjonalnej

*/
unsigned char status;
_AX=0x4300;
geninterrupt(0x2F);
status = _AL;
if(status==0x80)
{
GetXMSEntry();
XMSBuf = (char far *) farmalloc(BLOCKSIZE);
if (XMSBuf == NULL)
return 2;
return 0;
}
return 1;
}

void GetXMSEntry(void)
{
/*
Ustawia XMSFunc na punkt wej

ś

cia mened

ż

era XMS

co umo

ż

liwi pó

ź

niejsze wywołania

*/

_AX=0x4310;
geninterrupt(0x2F);
XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);
}

Po potwierdzeniu obecności pamięci rozszerzonej w systemie kolejna funkcja może
ustalić, jaka jej ilość jest dostępna:

void XMSSize(int *kbAvail, int *largestAvail)
{
/*
Zwraca wielko

ść

dost

ę

pnej pami

ę

ci oraz najwi

ę

kszego

dost

ę

pnego bloku (w kilobajtach)

*/

_AH=8;
(*XMSFunc)();
*largestAvail=_DX;
*kbAvail=_AX;
}

Kolejna funkcja alokuje blok pamięci rozszerzonej, w podobny sposób jak przy
alokacji pamięci konwencjonalnej:

background image


Rozdział 6.

Podstawy programowania dla hakerów

283

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

283

char AllocXMS(unsigned long numberBytes)
{
/*
Alokacja bloku pami

ę

ci XMS wielko

ś

ci 'numberBytes'

zwraca 1, gdy zako

ń

czona poprawnie

0 w przypadku bł

ę

du

*/

_DX = (int)(numberBytes / 1024);
_AH = 9;
(*XMSFunc)();
if (_AX==0)
{
return 0;
}
XMSHandle=_DX;
return 1;
}

System DOS nie zwalnia zaalokowanej pamięci rozszerzonej automatycznie. Przed
zakończeniem program musi więc zadbać o „uwolnienie” przyznanych mu wcześniej
bloków. Wykona to kolejna funkcja, zwalniająca blok pamięci zaalokowany
wcześniej przez funkcję

AllocXMS()

:

void XMS_free(void)
{
/*
Zwolnij pami

ęć

XMS

*/
_DX=XMSHandle;
_AH=0x0A;
(*XMSFunc)();
}

Kolejne dwie dłuższe funkcje służą do wymiany danych: jedna zapisuje dane do
pamięci rozszerzonej, druga kopiuje dane z pamięci rozszerzonej do pamięci
konwencjonalnej.

/*
Struktura XMSParms słu

ż

y do wymiany danych pomi

ę

dzy pami

ę

ci

ą

trybu rzeczywistego, a XMS
*/

struct parmstruct
{
/*
rozmiar kopiowanego bloku w bajtach
*/
unsigned long blockLength;

/*
sourceHandle to uchwyt XMS obszaru

ź

ródłowego; 0 oznacza,

ż

e sourcePtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego

(16:16); w pozostałych przypadkach sourcePtr jest
32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS

okre

ś

lonego przez sourceHandle

*/

unsigned int sourceHandle;
far void *sourcePtr;

background image


284

Hack Wars. Tom 1. Na tropie hakerów

284

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

/*
destHandle to uchwyt XMS obszaru docelowego; 0 oznacza,

ż

e destPtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego

(16:16); w pozostałych przypadkach destPtr jest
32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS

okre

ś

lonego przez destHandle

*/

unsigned int destHandle;
far void *destPtr;
}
XMSParms;


char XMS_write(unsigned long loc, char far *val, unsigned length)
{
/*
Zaokr

ą

glij długo

ść

do kolejnej warto

ś

ci parzystej

*/
length += length % 2;

XMSParms.sourceHandle=0;
XMSParms.sourcePtr=val;
XMSParms.destHandle=XMSHandle;
XMSParms.destPtr=(void far *) (loc);
XMSParms.blockLength=length; /* Musi by

ć

liczb

ą

parzyst

ą

! */

_SI = FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return 0;
}
return 1;
}


void *XMS_read(unsigned long loc,unsigned length)
{
/*
Zwraca wska

ź

nik do danych

lub NULL w przypadku bł

ę

du

*/

/*
Zaokr

ą

glij długo

ść

do kolejnej warto

ś

ci parzystej

*/
length += length % 2;

XMSParms.sourceHandle=XMSHandle;
XMSParms.sourcePtr=(void far *) (loc);
XMSParms.destHandle=0;
XMSParms.destPtr=XMSBuf;
XMSParms.blockLength=length; /* Musi by

ć

liczb

ą

parzyst

ą

*/

_SI=FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return NULL;
}
return XMSBuf;
}

background image


Rozdział 6.

Podstawy programowania dla hakerów

285

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

285

Czas teraz połączyć przedstawione dotąd funkcje w jednym programie:

/* Sekwencyjna tabela rekordów zmiennej długo

ś

ci

w pami

ę

ci XMS */


#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <string.h>

#define TRUE 1
#define FALSE 0

/*
BLOCKSIZE b

ę

dzie rozmiarem bufora w pami

ę

ci konwencjonalnej,

przeznaczonego do wymiany danych z pami

ę

ci

ą

XMS

(wielokrotno

ść

1-kilobajtowej jednostki alokacji XMS)

*/

#ifdef __SMALL__
#define BLOCKSIZE (16L * 1024L)
#endif


#ifdef __MEDIUM__
#define BLOCKSIZE (16L * 1024L)
#endif


#ifdef __COMPACT__
#define BLOCKSIZE (64L * 1024L)
#endif


#ifdef __LARGE__
#define BLOCKSIZE (64L * 1024L)
#endif


/*
Struktura XMSParms słu

ż

y do wymiany danych pomi

ę

dzy pami

ę

ci

ą

trybu rzeczywistego, a XMS
*/

struct parmstruct
{
/*
rozmiar kopiowanego bloku w bajtach
*/
unsigned long blockLength;

/*
sourceHandle to uchwyt XMS obszaru

ź

ródłowego; 0 oznacza,

ż

e sourcePtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego

(16:16); w pozostałych przypadkach sourcePtr jest
32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS

okre

ś

lonego przez sourceHandle

*/

unsigned int sourceHandle;
far void *sourcePtr;

/*

background image


286

Hack Wars. Tom 1. Na tropie hakerów

286

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

destHandle to uchwyt XMS obszaru docelowego; 0 oznacza,

ż

e destPtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego

(16:16); w pozostałych przypadkach destPtr jest
32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS

okre

ś

lonego przez destHandle

*/

unsigned int destHandle;
far void *destPtr;
}
XMSParms;

void far (*XMSFunc) (void); /* Posłu

ż

y do wywołania mened

ż

era XMS

(himem.sys) */
char GetBuf(void);
void GetXMSEntry(void);

char *XMSBuf; /* Bufor wymiany danych, w pami

ę

ci konwencjonalnej */

unsigned int XMSHandle; /* uchwyt do zaalokowanego bloku XMS */


char XMS_init()
{
/*
zwraca 0, gdy istnieje pami

ęć

XMS,

1 - gdy nie istnieje
2 - je

ż

eli nie mo

ż

na zaalokowa

ć

bufora wymiany danych w

pami

ę

ci konwencjonalnej

*/
unsigned char status;
_AX=0x4300;
geninterrupt(0x2F);
status = _AL;
if(status==0x80)
{
GetXMSEntry();
XMSBuf = (char far *) farmalloc(BLOCKSIZE);
if (XMSBuf == NULL)
return 2;
return 0;
}
return 1;
}

void GetXMSEntry(void)
{
/*
Ustawia XMSFunc na punkt wej

ś

cia mened

ż

era XMS

co umo

ż

liwi pó

ź

niejsze wywołania

*/

_AX=0x4310;
geninterrupt(0x2F);
XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);
}

void XMSSize(int *kbAvail, int *largestAvail)
{
/*
Zwraca wielko

ść

dost

ę

pnej pami

ę

ci oraz najwi

ę

kszego

dost

ę

pnego bloku (w kilobajtach)

*/

background image


Rozdział 6.

Podstawy programowania dla hakerów

287

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

287


_AH=8;
(*XMSFunc)();
*largestAvail=_DX;
*kbAvail=_AX;
}

char AllocXMS(unsigned long numberBytes)
{
/*
Alokacja bloku pami

ę

ci XMS wielko

ś

ci 'numberBytes'

Zwraca 1, gdy zako

ń

czona poprawnie

0 w przypadku bł

ę

du

*/

_DX = (int)(numberBytes / 1024);
_AH = 9;
(*XMSFunc)();
if (_AX==0)
{
return FALSE;
}
XMSHandle=_DX;
return TRUE;
}

void XMS_free(void)
{
/*
Zwolnij pami

ęć

XMS

*/
_DX=XMSHandle;
_AH=0x0A;
(*XMSFunc)();
}

char XMS_write(unsigned long loc, char far *val, unsigned length)
{
/*
Zaokr

ą

glij długo

ść

do kolejnej warto

ś

ci parzystej

*/
length += length % 2;

XMSParms.sourceHandle=0;
XMSParms.sourcePtr=val;
XMSParms.destHandle=XMSHandle;
XMSParms.destPtr=(void far *) (loc);
XMSParms.blockLength=length; /* Musi by

ć

liczb

ą

parzyst

ą

! */

_SI = FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return FALSE;
}
return TRUE;
}


void *XMS_read(unsigned long loc,unsigned length)
{
/*
Zwraca wska

ź

nik do danych

background image


288

Hack Wars. Tom 1. Na tropie hakerów

288

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

lub NULL w przypadku bł

ę

du

*/

/*
Zaokr

ą

glij długo

ść

do kolejnej warto

ś

ci parzystej

*/
length += length % 2;

XMSParms.sourceHandle=XMSHandle;
XMSParms.sourcePtr=(void far *) (loc);
XMSParms.destHandle=0;
XMSParms.destPtr=XMSBuf;
XMSParms.blockLength=length; /* Musi by

ć

liczb

ą

parzyst

ą

*/

_SI=FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return NULL;
}
return XMSBuf;
}


/*
Przykładowy program
Zapisuje ci

ą

gi ró

ż

nej długo

ś

ci

do pojedycznego bloku XMS(EMB)
i odczytuje je
*/

int main()
{
int kbAvail,largestAvail;
char buffer[80];
char *p;
long pos;
long end;

if (XMS_init() == 0)
printf("Pami

ęć

XMS dost

ę

pna ...\n");

else
{
printf("Pami

ęć

XMS nie jest dost

ę

pna\n");

return(1);
}

XMSSize(&kbAvail,&largestAvail);
printf("Wielko

ść

dost

ę

pnej pami

ę

ci XMS: %d; Largest block:

%dK\n",kbAvail,largestAvail);

if (!AllocXMS(2000 * 1024L))
return(1);


pos = 0;

do
{
p = fgets(buffer,1000,stdin);
if (p != NULL)
{

background image


Rozdział 6.

Podstawy programowania dla hakerów

289

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

289

XMS_write(pos,buffer,strlen(buffer) + 1);
pos += strlen(buffer) + 1;
}
}
while(p != NULL);

end = pos;

pos = 0;

do
{
memcpy(buffer,XMS_read(pos,100),70);
printf("%s",buffer);
pos += strlen(buffer) + 1;
}
while(pos < end);

/*
Zwolnienie pami

ę

ci XMS jest BARDZO wa

ż

ne!

*/
XMS_free();
return 0;
}

Tworzenie programów TSR

Ostatnim tematem w naszym omówieniu podstaw języka C, mającym zarazem
szczególne znaczenie dla zagadnień zabezpieczeń, są wszechstronne programy
rezydentne
, określane również skrótem TSR (terminate and stay resident, zakończ i
pozostań w pamięci). Programy takie pozostają aktywne w pamięci, w trakcie gdy „na
pierwszym planie” pracują inne programy. Tworzenie programów rezydentnych to
ulubione zajęcie wielu programistów i hakerów.

Trudność w tworzeniu programów TSR wynika z ograniczeń DOS-u, który nie jest
wielozadaniowym systemem operacyjnym i nie współpracuje dobrze z kodem
wielowątkowym, przede wszystkim gdy pojawiają się wzajemne wywołania funkcji
systemowych (przerwań). Teoria programów rezydentnych jest dość prosta. Program
taki kończy pracę funkcją DOS-u keep (przerwanie 27h), a nie przy użyciu
standardowej funkcji terminate. Zapewnia to zarezerwowanie w pamięci obszaru
wykorzystywanego przez program. Nie jest to skomplikowane, koniecznie trzeba
jednak podać, jaka ilość pamięci ma zostać zarezerwowana.

Głównym źródłem problemów jest brak możliwości korzystania z wywołań funkcji
DOS-u od momentu przyjęcia przez programu statusu programu rezydentnego.
Poniżej przedstawiamy kilka podstawowych zasad, których przestrzeganie pomoże
uniknąć problemów z programami TSR.

1.

Unikanie wywołań funkcji DOS-u.

2.

Monitorowanie znacznika zajętości DOS-u (busy flag). Gdy ma on wartość
niezerową, oznacza to, że DOS wykonuje właśnie funkcję 21h i nie można
mu przeszkadzać!

background image


290

Hack Wars. Tom 1. Na tropie hakerów

290

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

3.

Monitorowanie przerwania 28h. Sygnalizuje ono, że DOS jest zajęty
oczekiwaniem na dane wejściowe z konsoli. W tym czasie można wykonywać
różne operacje bez względu na stan znacznika zajętości.

4.

Sprawdzanie, czy program jest już uruchomiony, aby uniknąć jego wielokrotnego
ładowania do pamięci.

5.

Nie wolno zapominać, że również inne programy TSR mogą przechwytywać
przerwania. Konieczne jest więc tworzenie „łańcucha” — po wywołaniu własnej
funkcji wywołujemy funkcję określoną wektorem zastanym przy uruchamianiu
programu.

6.

Program TSR musi korzystać z własnego stosu, a nie stosu uruchomionego
procesu.

7.

Programy TSR należy kompilować, wybierając model pamięci small i wyłączając
sprawdzanie stosu.

8.

Po przejęciu kontroli program TSR musi poinformować DOS o zmianie procesu
aktywnego.

Przedstawione poniżej trzy moduły kodu stanowią kompletny program rezydentny.
Jest to prosta baza danych typu „książka adresowa”, aktywowana w trakcie pracy
innych programów wciśnięciem kombinacji klawiszy ALT i kropki (.). Jeżeli praca
systemu operacyjnego nie może zostać przerwana, program nie reaguje na
kombinację klawiszy. Wówczas należy spróbować ponownie.

/*
Przykładowy program TSR (wyskakuj

ą

ca ksi

ąż

ka adresowa)

Kompilowa

ć

z modelem pami

ę

ci 'small' i

wył

ą

czonym sprawdzaniem stosu (stack checking)

*/

#include <dos.h>
#include <stdio.h>
#include <string.h>
#include <dir.h>

static union REGS rg;

/*
Rozmiar programu pozostaj

ą

cego jako rezydentny

Aby okre

ś

li

ć

wielko

ść

minimaln

ą

, konieczne s

ą

eksperymenty

*/
unsigned sizeprogram = 28000/16;

/* Aktywacja kombinacj

ą

'Alt+.' */

unsigned scancode = 52; /* . */
unsigned keymask = 8; /* ALT */

char signature[]= "POPADDR";
char fpath[40];

/*
Prototypy funkcji
*/

void curr_cursor(int *x, int *y);
int resident(char *, void interrupt(*)());

background image


Rozdział 6.

Podstawy programowania dla hakerów

291

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

291

void resinit(void);
void terminate(void);
void restart(void);
void wait(void);
void resident_psp(void);
void exec(void);

/*
Punkt wej

ś

cia z DOS-u

*/

void main(int argc, char *argv[])
{
void interrupt ifunc();
int ivec;

/*
Dla uproszczenia zakładamy,

ż

e plik danych jest w

katalogu głównym dysku C:
*/
strcpy(fpath,"C:\\ADDRESS.DAT");

if ((ivec = resident(signature,ifunc)) != 0)
{
/* Ju

ż

jest */

if (argc > 1)
{
rg.x.ax = 0;
if (strcmp(argv[1],"quit") == 0)
rg.x.ax = 1;
else if (strcmp(argv[1],"restart") == 0)
rg.x.ax = 2;
else if (strcmp(argv[1],"wait") == 0)
rg.x.ax = 3;
if (rg.x.ax)
{
int86(ivec,&rg,&rg);
return;
}
}
printf("\nKsi

ąż

ka adresowa jest ju

ż

uruchomiona");

}
else
{
/* Załadowanie programu TSR */
printf("Ksi

ąż

ka adresowa została uruchomiona.\nWci

ś

nij 'Alt+.',

aby

wywoła

ć

...\n");

resinit();
}
}

void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax)
{
if(ax == 1)
terminate();
else if(ax == 2)
restart();
else if(ax == 3)
wait();
}

background image


292

Hack Wars. Tom 1. Na tropie hakerów

292

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

popup()
{
int x,y;

curr_cursor(&x,&y);

/* Wywołujemy program rezydentny C */
exec();
cursor(x,y);
}

/*
Drugi moduł
*/

#include <dos.h>
#include <stdio.h>

static union REGS rg;
static struct SREGS seg;
static unsigned mcbseg;
static unsigned dosseg;
static unsigned dosbusy;
static unsigned enddos;
char far *intdta;
static unsigned intsp;
static unsigned intss;
static char far *mydta;
static unsigned myss;
static unsigned stack;
static unsigned ctrl_break;
static unsigned mypsp;
static unsigned intpsp;
static unsigned pids[2];
static int pidctr = 0;
static int pp;
static void interrupt (*oldtimer)();
static void interrupt (*old28)();
static void interrupt (*oldkb)();
static void interrupt (*olddisk)();
static void interrupt (*oldcrit)();

void interrupt newtimer();
void interrupt new28();
void interrupt newkb();
void interrupt newdisk();
void interrupt newcrit();

extern unsigned sizeprogram;
extern unsigned scancode;
extern unsigned keymask;

static int resoff = 0;
static int running = 0;
static int popflg = 0;
static int diskflag = 0;
static int kbval;
static int cflag;

void dores(void);
void pidaddr(void);

background image


Rozdział 6.

Podstawy programowania dla hakerów

293

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

293

void resinit()
{
segread(&seg);
myss = seg.ss;

rg.h.ah = 0x34;
intdos(&rg,&rg);
dosseg = _ES;
dosbusy = rg.x.bx;

mydta = getdta();
pidaddr();
oldtimer = getvect(0x1c);
old28 = getvect(0x28);
oldkb = getvect(9);
olddisk = getvect(0x13);

setvect(0x1c,newtimer);
setvect(9,newkb);
setvect(0x28,new28);
setvect(0x13,newdisk);

stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300;
rg.x.ax = 0x3100;
rg.x.dx = sizeprogram;
intdos(&rg,&rg);
}

void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
diskflag++;
(*olddisk)();
ax = _AX;
newcrit();
flgs = cflag;
--diskflag;
}

void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
ax = 0;
cflag = flgs;
}

void interrupt newkb()
{
if (inportb(0x60) == scancode)
{
kbval = peekb(0,0x417);
if (!resoff && ((kbval & keymask) ^ keymask) == 0)
{
kbval = inportb(0x61);
outportb(0x61,kbval | 0x80);
outportb(0x61,kbval);
disable();
outportb(0x20,0x20);
enable();
if (!running)
popflg = 1;
return;
}
}

background image


294

Hack Wars. Tom 1. Na tropie hakerów

294

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

(*oldkb)();
}

void interrupt newtimer()
{
(*oldtimer)();
if (popflg && peekb(dosseg,dosbusy) == 0)
if(diskflag == 0)
{
outportb(0x20,0x20);
popflg = 0;
dores();
}
}

void interrupt new28()
{
(*old28)();
if (popflg && peekb(dosseg,dosbusy) != 0)
{
popflg = 0;
dores();
}
}

resident_psp()
{
intpsp = peek(dosseg,*pids);
for(pp = 0; pp < pidctr; pp++)
poke(dosseg,pids[pp],mypsp);
}

interrupted_psp()
{
for(pp = 0; pp < pidctr; pp++)
poke(dosseg,pids[pp],intpsp);
}

void dores()
{
running = 1;
disable();
intsp = _SP;
intss = _SS;
_SP = stack;
_SS = myss;
enable();
oldcrit = getvect(0x24);
setvect(0x24,newcrit);
rg.x.ax = 0x3300;
intdos(&rg,&rg);
ctrl_break = rg.h.dl;
rg.x.ax = 0x3301;
rg.h.dl = 0;
intdos(&rg,&rg);
intdta = getdta();
setdta(mydta);
resident_psp();
popup();
interrupted_psp();
setdta(intdta);
setvect(0x24,oldcrit);

background image


Rozdział 6.

Podstawy programowania dla hakerów

295

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

295

rg.x.ax = 0x3301;
rg.h.dl = ctrl_break;
intdos(&rg,&rg);
disable();
_SP = intsp;
_SS = intss;
enable();
running = 0;
}

static int avec = 0;

unsigned resident(char *signature,void interrupt(*ifunc)())
{
char *sg;
unsigned df;
int vec;

segread(&seg);
df = seg.ds-seg.cs;
for(vec = 0x60; vec < 0x68; vec++)
{
if (getvect(vec) == NULL)
{
if (!avec)
avec = vec;
continue;
}
for(sg = signature; *sg; sg++)
if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg))
break;
if (!*sg)
return vec;
}
if (avec)
setvect(avec,ifunc);
return 0;
}

static void pidaddr()
{
unsigned adr = 0;

rg.h.ah = 0x51;
intdos(&rg,&rg);
mypsp = rg.x.bx;
rg.h.ah = 0x52;
intdos(&rg,&rg);
enddos = _ES;
enddos = peek(enddos,rg.x.bx-2);
while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos <<4))
{
if (peek(dosseg,adr) == mypsp)
{
rg.h.ah = 0x50;
rg.x.bx = mypsp + 1;
intdos(&rg,&rg);
if (peek(dosseg,adr) == mypsp + 1)
pids[pidctr++] = adr;
rg.h.ah = 0x50;
rg.x.bx = mypsp;
intdos(&rg,&rg);

background image


296

Hack Wars. Tom 1. Na tropie hakerów

296

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

}
adr++;
}
}

static resterm()
{
setvect(0x1c,oldtimer);
setvect(9,oldkb);
setvect(0x28,old28);
setvect(0x13,olddisk);
setvect(avec,(void interrupt (*)()) 0);
rg.h.ah = 0x52;
intdos(&rg,&rg);
mcbseg = _ES;
mcbseg = peek(mcbseg,rg.x.bx-2);
segread(&seg);
while(peekb(mcbseg,0) == 0x4d)
{
if(peek(mcbseg,1) == mypsp)
{
rg.h.ah = 0x49;
seg.es = mcbseg+1;
intdosx(&rg,&rg,&seg);
}
mcbseg += peek(mcbseg,3) + 1;
}
}

terminate()
{
if (getvect(0x13) == (void interrupt (*)()) newdisk)
if (getvect(9) == newkb)
if(getvect(0x28) == new28)
if(getvect(0x1c) == newtimer)
{
resterm();
return;
}
resoff = 1;
}

restart()
{
resoff = 0;
}

wait()
{
resoff = 1;
}

void cursor(int y, int x)
{
rg.x.ax = 0x0200;
rg.x.bx = 0;
rg.x.dx = ((y << 8) & 0xff00) + x;
int86(16,&rg,&rg);
}

void curr_cursor(int *y, int *x)

background image


Rozdział 6.

Podstawy programowania dla hakerów

297

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

297

{
rg.x.ax = 0x0300;
rg.x.bx = 0;
int86(16,&rg,&rg);
*x = rg.h.dl;
*y = rg.h.dh;
}

/*
Trzeci moduł, przykładowa ksi

ąż

ka adresowa

z obsług

ą

myszy

*/

#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <fcntl.h>
#include <sys\stat.h>
#include <dos.h>
#include <conio.h>
#include <graphics.h>
#include <bios.h>

/* left nie mo

ż

e mie

ć

warto

ś

ci mniejszej od 3 */

#define left 4

/* Struktura rekordu */
typedef struct
{
char name[31];
char company[31];
char address[31];
char area[31];
char town[31];
char county[31];
char post[13];
char telephone[16];
char fax[16];
}
data;

extern char fpath[];

static char scr[4000];

static char sbuff[2000];
char stext[30];
data rec;
int handle;
int recsize;
union REGS inreg,outreg;

/*
Prototypy funkcji
*/
void FATAL(char *);
void OPENDATA(void);
void CONTINUE(void);
void EXPORT_MULTI(void);
void GETDATA(int);
int GETOPT(void);

background image


298

Hack Wars. Tom 1. Na tropie hakerów

298

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

void DISPDATA(void);
void ADD_REC(void);
void PRINT_MULTI(void);
void SEARCH(void);
void MENU(void);

int GET_MOUSE(int *buttons)
{
inreg.x.ax = 0;
int86(0x33,&inreg,&outreg);
*buttons = outreg.x.bx;
return outreg.x.ax;
}

void MOUSE_CURSOR(int status)
{
/* Status = 0 kursor wył

ą

czony */

/* 1 kursor wł

ą

czony */


inreg.x.ax = 2 - status;
int86(0x33,&inreg,&outreg);
}

int MOUSE_LOCATION(int *x, int *y)
{
inreg.x.ax = 3;
int86(0x33,&inreg,&outreg);

*x = outreg.x.cx / 8;
*y = outreg.x.dx / 8;

return outreg.x.bx;
}

int GETOPT()
{
int result;
int x;
int y;

do
{
do
{
result = MOUSE_LOCATION(&x,&y);
if (result & 1)
{
if (x >= 52 && x <= 53 && y >= 7 && y <= 15)
return y - 7;
if (x >= 4 && x <= 40 && y >= 7 && y <= 14)
return y + 10;

if (x >= 4 && x <= 40 && y == 15)
return y + 10;
}
}
while(!bioskey(1));

result = bioskey(0);
x = result & 0xff;
if (x == 0)
{

background image


Rozdział 6.

Podstawy programowania dla hakerów

299

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

299

result = result >> 8;
result -= 60;
}
}
while(result < 0 || result > 8);
return result;
}

void setvideo(unsigned char mode)
{
/* Okre

ś

la tryb wy

ś

wietlania i czy

ś

ci ekran */


inreg.h.al = mode;
inreg.h.ah = 0x00;
int86(0x10, &inreg, &outreg);
}


int activepage(void)
{
/* Zwraca wybran

ą

stron

ę

ekranu */


union REGS inreg,outreg;

inreg.h.ah = 0x0F;
int86(0x10, &inreg, &outreg);
return(outreg.h.bh);
}

void print(char *str)
{
/*
Wyprowadza znaki bezpo

ś

ednio na bie

żą

c

ą

stron

ę

ekranu,

poczynaj

ą

c od bie

żą

cej pozycji kursora. Kursor nie jest

przenoszony.
Funkcja zakłada,

ż

e stosowana jest karta graficzna COLOR.

Aby korzysta

ć

z monochromatycznej karty graficznej,

nale

ż

y zmieni

ć

0xB800 na 0xB000.

*/

int page;
int offset;
unsigned row;
unsigned col;
char far *ptr;

page = activepage();
curr_cursor(&row,&col);

offset = page * 4000 + row * 160 + col * 2;

ptr = MK_FP(0xB800,offset);

while(*str)
{
*ptr++= *str++;
ptr++;
}
}

background image


300

Hack Wars. Tom 1. Na tropie hakerów

300

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

void TRUESHADE(int lef, int top, int right, int bottom)
{
int n;

/* Cieniowanie */

gettext(lef,top,right,bottom,sbuff);
for(n = 1; n < 2000; n+= 2)
sbuff[n] = 7;
puttext(lef,top,right,bottom,sbuff);
}

void DBOX(int l, int t, int r, int b)
{
/* Rysowanie prostok

ą

ta podwójn

ą

lini

ą

*/

int n;

cursor(t,l);
print("É");
for(n = 1; n < r - l; n++)
{
cursor(t,l + n);
print("Í");
}
cursor(t,r);
print("»");

for (n = t + 1; n < b; n++)
{
cursor(n,l);
print("ž");
cursor(n,r);
print("ž");
}
cursor(b,l);
print("E");
for(n = 1; n < r - l; n++)
{
cursor(b,l+n);
print("Í");
}
cursor(b,r);
print("È");
}

int INPUT(char *text,unsigned length)
{
/* Pobranie ci

ą

gu wprowadzonego przez u

ż

ytkownika */

unsigned key_pos;
int key;
unsigned start_row;
unsigned start_col;
unsigned end;
char temp[80];
char *p;

curr_cursor(&start_row,&start_col);

key_pos = 0;
end = strlen(text);
for(;;)
{

background image


Rozdział 6.

Podstawy programowania dla hakerów

301

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

301

key = bioskey(0);
if ((key & 0xFF) == 0)
{
key = key >> 8;
if (key == 79)
{
while(key_pos < end)
key_pos++;
cursor(start_row,start_col + key_pos);
}
else
if (key == 71)
{
key_pos = 0;
cursor(start_row,start_col);
}
else
if ((key == 75) && (key_pos > 0))
{
key_pos--;
cursor(start_row,start_col + key_pos);
}
else
if ((key == 77) && (key_pos < end))
{
key_pos++;
cursor(start_row,start_col + key_pos);
}
else
if (key == 83)
{
p = text + key_pos;
while(*(p+1))
{
*p = *(p+1);
p++;
}
*p = 32;
if (end > 0)
end--;
cursor(start_row,start_col);
cprintf(text);
cprintf(" ");
if ((key_pos > 0) && (key_pos == end))
key_pos--;
cursor(start_row,start_col + key_pos);
}
}
else
{
key = key & 0xFF;
if (key == 13 || key == 27)
break;
else
if ((key == 8) && (key_pos > 0))
{
end--;
key_pos--;
text[key_pos--] = '\0';
strcpy(temp,text);
p = text + key_pos + 2;

background image


302

Hack Wars. Tom 1. Na tropie hakerów

302

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

strcat(temp,p);
strcpy(text,temp);
cursor(start_row,start_col);
cprintf("%-*.*s",length,length,text);
key_pos++;
cursor(start_row,start_col + key_pos);
}
else
if ((key > 31) && (key_pos < length) &&
(start_col + key_pos < 80))
{
if (key_pos <= end)
{
p = text + key_pos;
memmove(p+1,p,end - key_pos);
if (end < length)
end++;
text[end] = '\0';
}
text[key_pos++] = (char)key;
if (key_pos > end)
{
end++;
text[end] = '\0';
}
cursor(start_row,start_col);
cprintf("%-*.*s",length,length,text);
cursor(start_row,start_col + key_pos);
}
}
}
text[end] = '\0';
return key;
}

void FATAL(char *error)
{
/* Wyst

ą

pił bł

ą

d krytyczny */


printf("\nBŁ

Ą

D KRTYTYCZNY: %s",error);

exit(0);
}

void OPENDATA()
{
/* Sprawd

ź

, czy istnieje plik danych; je

ż

eli nie, utwórz*/

/* je

ż

eli tak, otwórz do odczytu-zapisu na ko

ń

cu pliku */


handle = open(fpath,O_RDWR,S_IWRITE);

if (handle == -1)
{
handle = open(fpath,O_RDWR|O_CREAT,S_IWRITE);
if (handle == -1)
FATAL("Nie mo

ż

na utworzy

ć

pliku danych");

}
/* Odczytaj pierwszy rekord */
read(handle,&rec,recsize);
}

void CLOSEDATA()
{

background image


Rozdział 6.

Podstawy programowania dla hakerów

303

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

303

close(handle);
}

void GETDATA(int start)
{
/* Pobierz adres wprowadzany przez u

ż

ytkownika */


textcolor(BLACK);
textbackground(GREEN);
gotoxy(left,8);
print("Imi

ę

i nazwisko ");

gotoxy(left,9);
print("Firma ");
gotoxy(left,10);
print("Adres ");
gotoxy(left,11);
print("Wojewodztwo ");
gotoxy(left,12);
print("Miasto ");
gotoxy(left,13);
print("Powiat ");
gotoxy(left,14);
print("Kod pocztowy ");
gotoxy(left,15);
print("Telefon ");
gotoxy(left,16);
print("Faks ");

switch(start)
{
case 0: gotoxy(left + 10,8);
if(INPUT(rec.name,30) == 27)
break;
case 1: gotoxy(left + 10,9);
if(INPUT(rec.company,30) == 27)
break;
case 2: gotoxy(left + 10,10);
if(INPUT(rec.address,30) == 27)
break;
case 3: gotoxy(left + 10,11);
if(INPUT(rec.area,30) == 27)
break;
case 4: gotoxy(left + 10,12);
if(INPUT(rec.town,30) == 27)
break;
case 5: gotoxy(left + 10,13);
if(INPUT(rec.county,30) == 27)
break;
case 6: gotoxy(left + 10,14);
if(INPUT(rec.post,12) == 27)
break;
case 7: gotoxy(left + 10,15);
if(INPUT(rec.telephone,15) == 27)
break;
case 8: gotoxy(left + 10,16);
INPUT(rec.fax,15);
break;
}
textcolor(WHITE);
textbackground(RED);
gotoxy(left + 23,21);
print(" ");
}

background image


304

Hack Wars. Tom 1. Na tropie hakerów

304

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc


void DISPDATA()
{
/* Wy

ś

wietl dane adresowe */

textcolor(BLACK);
textbackground(GREEN);
cursor(7,3);
cprintf("Imi

ę

i nazwisko %-30.30s",rec.name);

cursor(8,3);
cprintf("Firma %-30.30s",rec.company);
cursor(9,3);
cprintf("Adres %-30.30s",rec.address);
cursor(10,3);
cprintf("Województwo %-30.30s",rec.area);
cursor(11,3);
cprintf("Miasto %-30.30s",rec.town);
cursor(12,3);
cprintf("Powiat %-30.30s",rec.county);
cursor(13,3);
cprintf("Kod pocztowy %-30.30s",rec.post);
cursor(14,3);
cprintf("Telefon %-30.30s",rec.telephone);
cursor(15,3);
cprintf("Faks %-30.30s",rec.fax);
}

int LOCATE(char *text)
{
int result;

do
{
/* Załaduj rekord do pami

ę

ci */

result = read(handle,&rec,recsize);
if (result > 0)
{
/* Szukanie dopasowania w danym rekordzie */
if (strstr(strupr(rec.name),text) != NULL)
return(1);
if (strstr(strupr(rec.company),text) != NULL)
return(1);
if (strstr(strupr(rec.address),text) != NULL)
return(1);
if (strstr(strupr(rec.area),text) != NULL)
return(1);
if (strstr(strupr(rec.town),text) != NULL)
return(1);
if (strstr(strupr(rec.county),text) != NULL)
return(1);
if (strstr(strupr(rec.post),text) != NULL)
return(1);
if (strstr(strupr(rec.telephone),text) != NULL)
return(1);
if (strstr(strupr(rec.fax),text) != NULL)
return(1);
}
}
while(result > 0);
return(0);
}

void SEARCH()
{

background image


Rozdział 6.

Podstawy programowania dla hakerów

305

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

305

int result;

gotoxy(left,21);
textcolor(WHITE);
textbackground(RED);
cprintf("Wprowad

ź

wzorzec wyszukiwania ");

strcpy(stext,"");
INPUT(stext,30);
if (*stext == 0)
{
gotoxy(left,21);
cprintf("%70c",32);
return;
}
gotoxy(left,21);
textcolor(WHITE);
textbackground(RED);
cprintf("Wyszukiwanie %s Prosz

ę

czeka

ć

....",stext);

strupr(stext);
/* Zlokalizuj pocz

ą

tek pliku */

lseek(handle,0,SEEK_SET);
result = LOCATE(stext);
if (result == 0)
{
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left + 27,21);
cprintf("Brak pasuj

ą

cych rekordów");

gotoxy(left + 24,22);
cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

");

bioskey(0);
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left,22);
cprintf("%70c",32);
}
else
{
lseek(handle,0 - recsize,SEEK_CUR);
read(handle,&rec,recsize);
DISPDATA();
}
textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("%70c",32);
textcolor(BLACK);
textbackground(GREEN);
}

void CONTINUE()
{
int result;
long curpos;

curpos = tell(handle) - recsize;

result = LOCATE(stext);
textcolor(WHITE);
textbackground(RED);
if (result == 0)
{

background image


306

Hack Wars. Tom 1. Na tropie hakerów

306

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

gotoxy(left + 24,21);
cprintf("Brak dalszych rekordów");
gotoxy(left + 24,22);
cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

");

bioskey(0);
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left,22);
cprintf("%70c",32);
lseek(handle,curpos,SEEK_SET);
read(handle,&rec,recsize);
DISPDATA();
}
else
{
lseek(handle,0 - recsize,SEEK_CUR);
read(handle,&rec,recsize);
DISPDATA();
}
textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left,22);
cprintf(" ");
textcolor(BLACK);
textbackground(GREEN);
}

void PRINT_MULTI()
{
data buffer;
char destination[60];
char text[5];
int result;
int ok;
int ok2;
int blanks;
int total_lines;
char *p;
FILE *fp;

textcolor(WHITE);
textbackground(RED);
gotoxy(left + 23,21);
cprintf("Podaj kryterium wyboru");

/* Usu

ń

wcze

ś

niejsze dane rekordu */

memset(&rec,0,recsize);

DISPDATA();
GETDATA(0);

textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("Podaj, gdzie przesła

ć

PRN");

strcpy(destination,"PRN");
gotoxy(left,22);
cprintf("Podaj długo

ść

adresu w wierszach 18");

strcpy(text,"18");
gotoxy(left + 25,21);

background image


Rozdział 6.

Podstawy programowania dla hakerów

307

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

307

INPUT(destination,40);
gotoxy(left +30,22);
INPUT(text,2);
gotoxy(left,21);
cprintf("%72c",32);
gotoxy(left,22);
cprintf("%72c",32);

total_lines = atoi(text) - 6;
if (total_lines < 0)
total_lines = 0;

fp = fopen(destination,"w+");
if (fp == NULL)
{
gotoxy(left,21);
cprintf("Nie mo

ż

na drukowa

ć

do %s",destination);

gotoxy(left,22);
cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

");

bioskey(0);
gotoxy(left,21);
cprintf("%78c",32);
gotoxy(left,22);
cprintf(" ");
}

/* Zlokalizuj pocz

ą

tek pliku */

lseek(handle,0,SEEK_SET);

do
{
/* Załaduj rekord do pami

ę

ci */

result = read(handle,&buffer,recsize);
if (result > 0)
{
ok = 1;
/* Szukaj danych w rekordzie */
if (*rec.name)
if (stricmp(buffer.name,rec.name))
ok = 0;
if (*rec.company)
if (stricmp(buffer.company,rec.company))
ok = 0;
if (*rec.address)
if (stricmp(buffer.address,rec.address))
ok = 0;
if (*rec.area)
if (stricmp(buffer.area,rec.area))
ok = 0;
if (*rec.town)
if (stricmp(buffer.town,rec.town))
ok = 0;
if (*rec.county)
if (stricmp(buffer.county,rec.county))
ok = 0;
if (*rec.post)
if (stricmp(buffer.post,rec.post))
ok = 0;
if (*rec.telephone)
if (stricmp(buffer.telephone,rec.telephone))
ok = 0;
if (*rec.fax)

background image


308

Hack Wars. Tom 1. Na tropie hakerów

308

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

if (stricmp(buffer.fax,rec.fax))
ok = 0;
if (ok)
{
blanks = total_lines;
p = buffer.name;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.name);
p = buffer.company;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.company);
p = buffer.address;
ok2 = 0;

while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.address);
p = buffer.area;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}

background image


Rozdział 6.

Podstawy programowania dla hakerów

309

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

309

if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.area);
p = buffer.town;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.town);
p = buffer.county;
ok2 = 0;

while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.county);
p = buffer.post;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.post);
while(blanks)
{
fprintf(fp,"\n");
blanks--;
}
}
}
}
while(result > 0);
fclose(fp);
lseek(handle,0,SEEK_SET);
read(handle,&rec,recsize);
DISPDATA();

background image


310

Hack Wars. Tom 1. Na tropie hakerów

310

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

}

void EXPORT_MULTI()
{
data buffer;
char destination[60];
int result;
int ok;
FILE *fp;

textcolor(WHITE);
textbackground(RED);
gotoxy(left + 23,21);
cprintf("Wprowad

ź

kryterium wyboru");

/* Usu

ń

wcze

ś

niejsze dane rekordu */

memset(&rec,0,recsize);

DISPDATA();
GETDATA(0);

textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("Wprowad

ź

nazw

ę

pliku address.txt");

strcpy(destination,"address.txt");
gotoxy(left + 18,21);
INPUT(destination,59);
gotoxy(left,21);
cprintf("%70c",32);

fp = fopen(destination,"w+");
if (fp == NULL)
{
gotoxy(left,21);
cprintf("Nie mo

ż

na drukowa

ć

do %s",destination);

gotoxy(left,22);
cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

");

bioskey(0);
gotoxy(left,21);
cprintf("%78c",32);
gotoxy(left,22);
cprintf(" ");
}
/* Zlokalizuj pocz

ą

tek pliku */

lseek(handle,0,SEEK_SET);

do
{
/* Załaduj rekord do pami

ę

ci */

result = read(handle,&buffer,recsize);
if (result > 0)
{
ok = 1;
/* Przeszukaj rekord */
if (*rec.name)
if (stricmp(buffer.name,rec.name))
ok = 0;
if (*rec.company)
if (stricmp(buffer.company,rec.company))
ok = 0;
if (*rec.address)
if (stricmp(buffer.address,rec.address))

background image


Rozdział 6.

Podstawy programowania dla hakerów

311

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

311

ok = 0;
if (*rec.area)
if (stricmp(buffer.area,rec.area))
ok = 0;
if (*rec.town)
if (stricmp(buffer.town,rec.town))
ok = 0;
if (*rec.county)
if (stricmp(buffer.county,rec.county))
ok = 0;
if (*rec.post)
if (stricmp(buffer.post,rec.post))
ok = 0;
if (*rec.telephone)
if (stricmp(buffer.telephone,rec.telephone))
ok = 0;
if (*rec.fax)
if (stricmp(buffer.fax,rec.fax))
ok = 0;
if (ok)
{
fprintf(fp,"\"%s\",",buffer.name);
fprintf(fp,"\"%s\",",buffer.company);
fprintf(fp,"\"%s\",",buffer.address);
fprintf(fp,"\"%s\",",buffer.area);
fprintf(fp,"\"%s\",",buffer.town);
fprintf(fp,"\"%s\",",buffer.county);
fprintf(fp,"\"%s\",",buffer.post);
fprintf(fp,"\"%s\",",buffer.telephone);
fprintf(fp,"\"%s\"\n",buffer.fax);

}
}
}

while(result > 0);
fclose(fp);
lseek(handle,0,SEEK_SET);
read(handle,&rec,recsize);
DISPDATA();
}

void MENU()
{
int option;
long result;
long end;
int new;

do
{
cursor(21,26);
print("Wybierz polecenie (F2 - F10)");
cursor(7,52);
print("F2 Nast

ę

pny rekord");

cursor(8,52);
print("F3 Poprzedni rekord");
cursor(9,52);
print("F4 Popraw rekord");
cursor(10,52);
print("F5 Dodaj nowy rekord");
cursor(11,52);

background image


312

Hack Wars. Tom 1. Na tropie hakerów

312

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

print("F6 Szukaj");
cursor(12,52);
print("F7 Szukaj dalej");
cursor(13,52);
print("F8 Drukuj etykiety adresowe");
cursor(14,52);
print("F9 Eksportuj rekordy");
cursor(15,52);
print("F10 Wyj

ś

cie");

MOUSE_CURSOR(1);
option = GETOPT();
MOUSE_CURSOR(0);

switch(option)
{
case 0 : /* Nast

ę

pny rekord */

result = read(handle,&rec,recsize);
if (!result)
{
lseek(handle,0,SEEK_SET);
result = read(handle,&rec,recsize);
}
DISPDATA();
break;

case 1 : /* Poprzedni rekord */
result = lseek(handle,0 - recsize * 2,SEEK_CUR);
if (result <= -1)
lseek(handle,0 - recsize,SEEK_END);
result = read(handle,&rec,recsize);
DISPDATA();
break;

case 2 : /* Popraw bie

żą

cy rekord */

new = 1;
if (*rec.name)
new = 0;
else
if (*rec.company)
new = 0;
else
if (*rec.address)
new = 0;
else
if (*rec.area)
new = 0;
else
if (*rec.town)
new = 0;
else
if (*rec.county)
new = 0;
else
if (*rec.post)
new = 0;
else
if (*rec.telephone)
new = 0;
else
if (*rec.fax)
new = 0;

background image


Rozdział 6.

Podstawy programowania dla hakerów

313

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

313

result = tell(handle);
lseek(handle,0,SEEK_END);
end = tell(handle);

/* Powrót do pozycji pocz

ą

tkowej */

lseek(handle,result,SEEK_SET);

/* Je

ż

eli nie EOF && !new przesu

ń

o jeden rekord */

if (result != end || ! new)
result = lseek(handle,0 - recsize,SEEK_CUR);
result = tell(handle);
gotoxy(left + 22,21);
print(" Wprowad

ź

dane adresu ");

GETDATA(0);
if (*rec.name || *rec.company)
result = write(handle,&rec,recsize);
break;

case 3 : /* Dodaj rekord */
lseek(handle,0,SEEK_END);
memset(&rec,0,recsize);
DISPDATA();

case 4 : /* Szukaj */
gotoxy(left + 22,21);
print(" ");
SEARCH();
break;

case 5 : /* Szukaj dalej */
gotoxy(left + 22,21);
print(" ");
CONTINUE();
break;

case 6 : /* Drukuj */
gotoxy(left + 22,21);
print(" ");
PRINT_MULTI();
break;

case 7 : /* Eksportuj */
gotoxy(left + 22,21);
print(" ");
EXPORT_MULTI();
break;

case 8 : /* Wyj

ś

cie */

break;

default: /* Popraw rekord bie

żą

cy */

new = 1;
if (*rec.name)
new = 0;
else
if (*rec.company)
new = 0;
else
if (*rec.address)
new = 0;
else

background image


314

Hack Wars. Tom 1. Na tropie hakerów

314

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

if (*rec.area)
new = 0;
else
if (*rec.town)
new = 0;
else
if (*rec.county)
new = 0;
else
if (*rec.post)
new = 0;
else
if (*rec.telephone)
new = 0;
else
if (*rec.fax)
new = 0;
result = tell(handle);
lseek(handle,0,SEEK_END);
end = tell(handle);

/* Powrót do pozycji pocz

ą

tkowej */

lseek(handle,result,SEEK_SET);

/* Je

ż

eli nie EOF && !new przesu

ń

o jeden rekord */

if (result != end || ! new)
result = lseek(handle,0 - recsize,SEEK_CUR);
result = tell(handle);
gotoxy(left + 22,21);
print(" Wprowad

ź

dane adresowe ");

GETDATA(option - 17);
if (*rec.name || *rec.company)
result = write(handle,&rec,recsize);
option = -1;
break;

}
}

while(option != 8);
}

void exec()
{
gettext(1,1,80,25,scr);
setvideo(3);
textbackground(WHITE);
textcolor(BLACK);
clrscr();
recsize = sizeof(data);

OPENDATA();

TRUESHADE(left,3,79,5);
window(left - 2,2 ,78, 4);
textcolor(YELLOW);
textbackground(MAGENTA);
clrscr();
DBOX(left - 3, 1, 77, 3);
gotoxy(3,2);

background image


Rozdział 6.

Podstawy programowania dla hakerów

315

D:\KISIU\PDFy\Chudy\Książki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

315

print("Servile Software PC ADDRESS BOOK 5.2 (c)
1994");

TRUESHADE(left,8,left + 43,18);
window(left - 2,7 , left + 42, 17);
textcolor(BLACK);
textbackground(GREEN);
clrscr();
DBOX(left - 3, 6, left + 41, 16);

TRUESHADE(left + 48,8,79,18);
window(left + 46, 7 , 78, 17);
textbackground(BLUE);
textcolor(YELLOW);
clrscr();
DBOX(left + 45,6,77,16);

TRUESHADE(left ,21,79,24);
window(left - 2, 20 , 78, 23);
textbackground(RED);
textcolor(WHITE);
clrscr();
DBOX(left - 3,19,77,22);

window(1,1,80,25);
textcolor(BLACK);
textbackground(GREEN);
DISPDATA();

MENU();

CLOSEDATA();
puttext(1,1,80,25,scr);
return;
}


Wyszukiwarka

Podobne podstrony:
Podstawa programowa dla gimnazjum
Charakterystyka nowej podstawy programowej dla przedszkoli i klas początkowych, Współczesne koncepcj
nowa podstawa programowa dla umierkowanych, nacznych i ze sprzężeniami rozporzadzenie 20081223 zal 7
Podstawy programowania dla Hackerow
etyka- podstawa programowa dla gimnazjum, Filozofia
podstawy programowe dla poszczególnych klas z zakresu PCziI, informacja nukowa i bibliotekoznawstwo
Podstawa programowa dla przedszkoli
Podstawa programowa dla szkoły podstawowej
Podstawa programowa dla gimnazjum
Charakterystyka nowej podstawy programowej dla przedszkoli i klas początkowych, Współczesne koncepcj
Nowa podstawa programowa dla szkoły podstawowej i gimnazjum, Zając
PODSTAWA PROGRAMOWA WYCHOWANIA PRZEDSZKOLNEGO DLA PRZEDSZKOLI
Program dla elektrotechniki I-stopień- K Ciesielska 06-10-2013, Politechnika Poznańska, 2 rok, III,
02-06 NA JESIENNE, EDUKACJA, Plany pracy - wg. nowej podstawy programowej
podstawa programowa ksztalcenia ogolnego dla szkol podstawowych i gimnazjow, teologia
program z Podstaw przedsiębiorczości dla APS październik 2013, Studia - Pedagogika Specjalna, Notatk

więcej podobnych podstron