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.
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()
,
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:
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
}
.
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:
/*
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.
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:
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()
{
}
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);
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,
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;
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.
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.
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++;
}
}
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 */
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.
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>
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"
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
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);
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>
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.
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()
):
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
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.
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
są
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;
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);
}
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);
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>
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");
}
}
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
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);
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;
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
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>
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:
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.
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);
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);
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;
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);
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)
{
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);
}
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)
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>
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);
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
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;
}
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';
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];
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;
}
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));
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 */
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
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));
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);
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:
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,
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>
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
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
ę
bł
ę
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;
}
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 */
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));
}
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));
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:
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 */
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 */
.
.
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
};
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
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
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);
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>
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);
}
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;
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;
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,
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)
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(®s,®s,&sregs);
handle = regs.x.ax;
error = regs.x.cflag;
if (!error)
{
regs.h.ah = 0x3e;
regs.x.bx = handle;
intdos(®s,®s);
}
return error;
}
emmok()
{
/*
Sprawdza funkcjonowanie mened
ż
era pami
ę
ci robudowanej
*/
union REGS regs;
regs.h.ah = 0x40;
int86(EMM,®s,®s);
if (regs.h.ah)
return 0;
regs.h.ah = 0x41;
int86(EMM,®s,®s);
emmbase = MK_FP(regs.x.bx,0);
return 1;
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,®s,®s);
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,®s,®s);
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,®s,®s);
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;
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,®s,®s);
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);
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__
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:
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;
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;
}
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;
/*
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)
*/
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
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)
{
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ć!
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(*)());
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();
}
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);
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;
}
}
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);
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);
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)
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);
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)
{
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++;
}
}
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(;;)
{
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;
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()
{
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(" ");
}
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()
{
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)
{
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);
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)
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++;
}
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();
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))
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);
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;
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
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);
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;
}