LEKCJA 37
KAŹDY DYSK JEST ZA MAŁY, A KAŹDY PROCESOR ZBYT WOLNY...
W trakcie tej lekcji dowiesz się, jak komputer dysponuje swoimi zasobami w środowisku tekstowym (DOS).
Truizmy użyte w tytule mają znaczyć, że "zasoby najlepszego nawet komputera są ograniczone" i zwykle okazują się wystarczające tylko do pewnego momentu. Najbardziej newralgiczne
zasoby to:
* czas mikroprocesora i
* miejsce w pamięci operacyjnej.
Tworzone przez nas programy powinny wystrzegać się zatem najcięższych grzechów:
* nie pozwalać mikroprocesorowi na słodkie nieróbstwo;
Rzadko uzmysławiamy sobie, że oczekiwanie na naciśnięcie klawisza przez użytkownika (czasem po przeczytaniu napisu na ekranie) trwa sekundy (1, 2, .... czasem 20), a każda sekunda lenistwa PC to stracone miliony cykli mikroprocesora.
* oszczędnie korzystać z pamięci dyskowej, a szczególnie oszczędnie z pamięci operacyjnej RAM.
MODELE PAMIĘCI IBM PC.
Jak zapewne wiesz, Twój PC może mieć:
* pamięć ROM (tylko do odczytu),
* konwencjonalną pamięć RAM (640 KB),
* pamięć rozszerzoną EMS i XMS,
* pamięć karty sterownika graficznego ekranu (np. SVGA-RAM),
* pamięć Cache dla buforowania operacji dyskowych.
Najczęściej stosowane modele pamięci to:
* Small - mały,
* Medium - średni,
* Compact - niewielki (tu mam wątpliwość, może "taki sobie" ?),
* Large - duży,
* Huge - jeszcze większy, odległy.
Dodatkowo może wystąpić
* Tiny - najmniejszy.
Taki podział został spowodowany segmentacją pamięci komputera przez procesory Intel 8086 i podziałem pamięci na bloki o wielkości 64 KB. Model Small (Tiny, jeśli jest) jest najszybszy, ale najmniej pojemny. Model Huge - odwrotnie - najpojemniejszy, za to najwolniejszy. Model Tiny powoduje ustawienia wszystkich rejestrów segmentowych mikroprocesora na tę samą wartość (początek tej samej stronicy pamięci) i umieszczenie wszystkich zasobów programu wewnątrz wspólnego obszaru pamięci o wielkości nieprzekraczającej 64 KB. Wszystkie skoki są wtedy "krótkie", a wszystkie pointery (adresy) 16-bitowe. Kompilacja z zastosowaniem modelu Tiny pozwala uzyskać program wykonywalny w wersji *.COM (a nie *.EXE). Ale niestety nie wszystkie programy mieszczą się w 64 KB. W modelu Small segment kodu jest jeden
(kod max. 64 K) i segment danych też tylko jeden (dane max. 64 K), ale są to już dwa różne segmenty. Zestawienia najważniejszych parametrów poszczególnych modeli pamięci przedstawia tabelka poniżej:
Modele pamięci komputera IBM PC.
MODEL |
SEGMENT KODU |
SEGMENT DANYCH |
*dp |
*cp |
Tiny |
1 |
1 (CS = DS.) |
16 bit |
16 bit |
Small |
1 |
1 |
16 bit |
16 bit |
Medium |
Wiele |
1 |
16 bit |
32 bit |
Compact |
1 |
Wiele |
32 bit |
16 bit |
Large |
Wiele |
Wiele |
32 bit |
32 bit |
Huge |
Wiele |
Wiele |
32 bit |
32 bit |
*dp - data pointer - wskaźnik do danych (near/far)
*cp - code pointer - wskaźnik do kodu.
Large - kod + dane = max. 1 MB.
Huge - kod = max. 1 MB, wiele segmentów danych po 64 K każdy.
Wynikające z takich modeli pamięci kwalifikatory near, far, huge dotyczące pointerów w C++ nie są akceptowane przez standard ANSI C (ponieważ odnoszą się tylko do IBM PC i nie mają charakteru uniwersalnego). Trzeba tu zaznaczyć, że typ wskaźnika jest przez kompilator przyjmowany domyślnie (automatycznie) zgodnie z wybranym do kompilacji modelem pamięci. Jeśli poruszamy się wewnątrz niewielkiego obszaru pamięci, możesz "forsować" bliższy typ pointera, przyspieszając tym samym działanie programów:
huge *p;
...
near *ptr; //Bliski pointer
...
near int Funkcja(...) //Bliska funkcja
{
...
}
#define ILE (1024*640)
near unsigned int Funkcja(void)
{
huge char *ptr; // tu długi pointer jest niezbędny
long suma = 0;
for (p = 0; p < ILE; p++) suma += *p;
return (suma);
}
Zarówno zadeklarowanie funkcji jako bliskiej (near), jak i jako statycznej (static) powoduje wygenerowanie uproszczonej sekwencji wywołania funkcji przez kompilator. Daje to w efekcie mniejszy i szybszy kod wynikowy.
IDENTYFIKACJA KLAWISZY.
Znane Ci z pliku <stdio.h> i <conio.h> "klasyczne" funkcje obsługi konsoli mają pewne zalety. Korzystanie z klasycznych, nieobiektowych mechanizmów powoduje z reguły wygenerowanie znacznie krótszego kodu wynikowego. Funkcje scanf() i gets() wymagają wciśnięcia klawisza [Enter]. Dla szybkiego dialogu z komputerem znacznie bardziej nadają się szybsze getch() i kbhit(). Ponieważ klawiatura zawiera także klawisze specjalne (F1 ... F10, [Shift], [Del], itp.), pełną informację o stanie klawiatury można uzyskać za pośrednictwem funkcji bioskey(), korzystającej z przerywania BIOS Nr 16. Oto krótki przykład zastosowania funkcji bioskey():
#include "bios.h"
#include "ctype.h"
#include "stdio.h"
#include "conio.h"
# define CTRL 0x04
# define ALT 0x08
# define RIGHT 0x01
# define LEFT 0x02
int klawisz, modyfikatory;
void main()
{
clrscr();
printf("Funkcja zwraca : %d", bioskey(1));
printf("\n Nacisnij klawisz ! \n");
while (!bioskey(1));
printf("Funkcja zwrocila: %c", bioskey(1));
printf("\nKod: %d", (char)bioskey(1));
...
A to jeszcze inny sposób korzystania z tej bardzo przydatnej funkcji, tym razem z innymi parametrami:
/* Funkcja z parametrem (0) zwraca kod klawisza: ------ */
klawisz = bioskey(0);
/* Funkcja sprawdza stan klawiszy specjalnych --------- */
modyfikatory = bioskey(2);
if (modyfikatory)
{
printf("\n");
if (modyfikatory & RIGHT) printf("RIGHT");
if (modyfikatory & LEFT) printf("LEFT");
if (modyfikatory & CTRL) printf("CTRL");
if (modyfikatory & ALT) printf("ALT");
printf("\n");
}
/* drukujemy pobrany klawisz */
if (isalnum(klawisz & 0xFF))
printf("'%c'\n", klawisz);
else
printf("%#02x\n", klawisz);
}
Należy tu zwrócić uwagę, że funkcje kbhit() i bioskey() nie dokonują czyszczenia bufora klawiatury. Identyfikują znak (znaki) w buforze, ale pozostawiają bufor w stanie niezmienionym do dalszej obróbki. Zwróć uwagę, że funkcja getch() może oczekiwać na klawisz w nieskończoność. Sprawdzić szybciej, czy użytkownik nacisnął już cokolwiek możesz np. tak:
if (kbhit()) ...; if (!kbhit()) ...;
while (!bioskey(1)) ... if (bioskey(1)) ...;
Inną wielce przydatną "szybką" funkcją jest getch(). Oto praktyczny przykład pobierania i testowania naciśniętych klawiszy klawiatury.
[P131.CPP]
# include "stdio.h"
# include "conio.h"
char z1, z2;
void Odczyt(void)
{
z2 = '\0';
z1 = getch();
if (z1 == '\0') z2 = getch();
}
main()
{
clrscr();
printf("\nKropka [.] = Quit");
printf("\nRozpoznaje klawisze [F1] ... [F3] \n\n");
for (;;)
{
while(!kbhit());
Odczyt();
if (z1 == '.') break;
if (z1 != '\0') printf("\nZnak: %c", z1);
else
switch (z2)
{
case ';' : printf("\n F1"); break;
case '<' : printf("\n F2"); break;
case '=' : printf("\n F3"); break;
default : printf("\n Inny klawisz specjalny!");
}
}
return 0;
}
Klawisze specjalne powodują wygenerowanie dwubajtowego kodu (widzianego w powyższym przykładowym programie jako dwa jednobajtowe znaki z1 i z2). Funkcja getch() pobiera te bajty z bufora klawiatury kolejno jednocześnie czyszcząc bufor. W przypadku klawiszy specjalnych pierwszy bajt jest zerowy (NULL, '\0', 00h), co jest sprawdzane w programie. A oto tabela kodów poszczególnych klawiszy:
Kody klawiszy klawiatury IBM PC.
KODY KLAWISZY KLAWIATURY IBM PC |
||
KLAWISZE |
KODY |
ASCII (dec) |
Home |
G |
71 (00:47h) `\0','G' |
End |
O |
79 (00:47h) `0','O' |
PgUp |
I |
73 |
PgDn |
Q |
81 |
Ins |
R |
82 |
Del |
S |
83 |
F1 |
; |
59 |
F2 … F10 |
<, … D |
60, … 68 |
Shift + F1 |
T |
84 |
. . . |
|
|
Shift + F10 |
} |
93 |
Ctrl + F1 |
^ |
94 |
… |
|
|
Ctrl + F10 |
f |
103 |
Alt + F1 … F10 |
h,...,g |
104,...113 |
Alt + 1...9 |
x, ...Ą(?) |
120,...128 |
Alt+0 |
Ć(?) |
129 |
STRZAŁKI KURSORA |
||
LeftArrow |
K |
75 |
RightArrow |
M |
77 |
UpArrow |
H |
72 |
DownArrow |
P |
80 |
|
|
|
Ctrl + PgDn |
v |
118 |
Ctrl + PgUp |
Ń(?) |
132 |
Ctrl + Home |
w |
119 |
Ctrl + End |
u |
117 |
Wyprowadzanie znaków na ekran można przeprowadzić szybciej posługując się przerywaniem DOS INT 29H. Drukowanie na ekranie w trybietekstowym przebiega wtedy szybciej niż robią to standardowe funkcje <stdio.h>, <conio.h>, czy <iostream.h>. Poniżej prosty przykład praktyczny wykorzystania przerywania 29H:
[P132.CPP]
# include <stdlib.h>
# include <conio.h>
# pragma inline
void SpeedBox(int, int, int, int, char);
main()
{
clrscr();
for (; !kbhit(); )
{
int x = rand() % 40;
int y = rand() % 12;
SpeedBox(x, y, (80 - x), (24 - y), ('€' + x % 50));
}
return 0;
}
void SpeedBox(int x1, int y1, int x2, int y2, char znak)
{
int k;
for (; y1 < y2; y1++) { gotoxy(x1, y1);
for (k = x1; k < x2; k++)
{
asm MOV AL, znak
asm INT 29H
}
}
}
[Z]
1. Opracuj program pozwalający porównać szybkość wyprowadzania danych na ekran monitora różnymi technikami (cout, puts(), printf(), asm).
2. Porównaj wielkość plików wynikowych .EXE powstających w różnych wariantach z poprzedniego zadania.
5