Tam i z powrotem cd.
Część druga - uzupełnienia:
- Adresy i wskaźniki cd.
Nie spomniałem ja wyciągnąć adres danej struktury danych, czy funkcji.
otóż w pascalu wygląda to tak:
procedure proc;
begin
writeln('whatever') {write + cr+lf}
end;
var p:pointer;
a:int {jakikolwiek typ}
begin
p:=@a; {p wskazuke teraz na a}
p:=@proc
end;
A w c++ :
void proc(){
printf("whatever\n"); // \n -> cr+lf
}
void main(){
void* p;
int a; // any type You want
p=&a;
p=proc;
}
O co chodzi?
Ano o to, że w pascalu zawsze uzywa się znaczka @, a w C++ nie zawsze uzywa się &, dlatego że literał oznczający nazwę procedury jest traktowany jako wskaźnik. Więc nawet jeśli kompilator przyjąłby zapis p=&proc, to w wyniku nie otrzymalibyśmy adresu procedury proc, tylko adres wskaźnika do niej. A zatem p=proc jest traktowane jak skopiowanie wartości proc do p.
Tworzenie wskaźników:
podstawową metodą jest użycie funkcji ptr(seg,ofs:word) (pascal) lub macra MK_FP(unsigned seg,unsigned ofs) (c++ MK_FP - make fer pointer)
funkcja ptr zwraca typ pointer, który może być przypisany do każdego typu wskaźnika (np. integer, char itepe)
MK_FP rownież zwraca wksaźnik nietypowany: void* , ale jeżeli chcemy przypisać wartośc wskaźnikowi typowanemu, to w c++ trzeba dokonać konwersji typów, np.:
int* p=(int*)MK_FP(seg,ofs);
- Konwersje typów (rzutowanie typów)
Więc pokolei... Już częściowo to było, kilkakrotnie.
Otóż istnieją dwa rodzaje konwersji: konwersja co do wartości i konwersja rozmiarowa. Konwersja co do wartości zacodzi pomiędzy typami tego samego rodzaju i w większości wypadków jest wykonywana automatycznie, tok w pascalu, jak i w C/C++:
var i:integer;
j:longint;
begin
j:=10;
i:=j
end.
void main(){
int i;
long j;
i=10;
j=i;
}
Zrozumiałe? Chyba jasne tu konwersja jest wykonywana automatycznie.
Ale tu juz nie:
var i:integer;
c:char;
begin
c:=#65 {'A'}
i:=integer(c); {i:=65}
end.
W c++ w tym przypadku konwersja zostanie wykonana automatycznie, bo i char i int są liczbami:
{
char c=65;
int i=c;
}
Ale c++ ma jeszcze jedną ciekawą cechę, o której nie wspomniałem:
void main(){
int i=10;
long j=i;
}
deklaracja może być odrazu inicjacją zmiennych.
Co to jest natomiast konwersja rozmiarowa?
To:
var i:integer;
pnil:pointer;
begin
i:=-1;
writeln(word(i)) {-> 65535}
word(i):=65534; {i:=-2}
pnil:=pointer(longint(0)); {tu najpierw konwersja wartości potem rozmiarowa}
longint(pnil):=0; {tu też, choć wartośc jest konwertowana automatycznie}
end.
albo jeszcze jaśniej :
const nil=pointer(longint(0));
dlatego, że sizeof(pointer)=sizeof(longint)=4;
albo:
var i:byte;
begin
char(i):=#65 {i:=65}
end;
int* i=(int*)MK_FP(...,...) // konwersja rozmiarowa typu void* na int*
unsigned i;
(int)i=-1 // i=65535;
albo jednoznacznie - tak jak w pascalu:
int(i)=-1;
A zatem zapis #65=char(65), a byte(#65)=65 - konwersje rozmiarowe
lub integer(#65)=65 - konwersja wartości
byte(true) =1
byte(false)= 0 - Pamiętacie, co pisałem o warunkach? I dlaczeo w c++ za boola można uzyć kazdego typu wyliczeniowego?
Oczywiście można w ten sposób robić róźne dziwne rzeczy np.:
typedef unsigned long ulong;
void far* p; // far daje 4bajtowy wskaznik niezależnie od typu pamięci- od prawej: p to wskaźnik daleki do typu void
ulong(p)=(0xa000<<16)+0x0000; // to praktycznie definicja MK_FP patrz: stdlib.h
(ulong)p=(0xa000<<16)+0x0000; // to samo, co wyżej
p=(void far*)ulong((0xa000<<16)+0x0000) // mieszamy style
albo wogóle mozna zamienic (0xa000<<16)+0 na wynik końcowy: 0xa0000000;
C/C++ jest piekne pod tym względem- jesli nie umie sie pisać, albo pisze się tak specjalnie, to mozna tak pięknie zagmatwać źródło, że Sherlock Holms rozłozy ręce.
var p:pointer;
type farptrrec=record ofs,seg:word end;
farptrrec(p).seg:=$a000;
farptrrec(p).ofs:=$0000; {albo po prostu 0}
p:=poinetr(longint($a0000 shl 16+$0000);
p:=pointer(longint($a0000000));
I tak mamy ustawiony wskaźnik do adresu $a000:$0000 (0xa0000:0x00000)
Notabente jest to adres ekrany w trybie $13 - 0x13, czyli dziesiętnie 19.
AHA znaczki << to pascalowe shl, a >> to shr- nic wielkiego.
Pamiętacie na końcu poprzedniego artykułu użyłem czegos takiego:
type bytearr=array[0..31]of byte;
bytearr((@a)^)[0]:=...
@a - to adres a;
(@a)^ to pointer do wartości a niezależnie od tego jakiego typu jest wskaźnik.
bytearr()[] to konwersja typu wskaźnika na wskaźnik do tablicy bajtowej.
mozna to zapisać troche inaczej - dodamy jedną zmienną:
type bytearr=array[0..31]of byte;
pbytearr=^bytearr;
var a:set of byte; {tak tam było}
p:pbytearr;
begin
{...}
p:=@a;
p^[0]:=...
{...}
end;
Czy juz jest to jasne?
I mimo że mozna to podciągnąć pod konwersję wskaźnika, tak na prawdę jest to jeszcze jeden typ konwersji. Konwersja samych wskazań:
type jeden=array[byte]of byte; {lub cos innego}
dwa=record a:byte;b:char end;
var
i:^integer;
c:^char;
s:^string;
b:^byte;
l:^longint;
p:pointer;
begin
longint(p^):=...
char(b^):=....
dwa(s^).a:=...
jeden(c^)[...]:=...
ponter(l^):=...
....
end.
W c/c++ nie ma czegoś takiego, trzeba się uciec do konwersji wskaźnika:
#include
<stdio.h>
main(){
typedef int* pint;
typedef struct {int a;unsigned b;} jakas;
jakas j={0,16}; // a=0,b=16
pint i=(int*)(&j);
((jakas*)(i))->a=1;
printf("%d\n",j.a); // -> 1
}
Po pierwsze
Deklarować nowe typy można również wewnątrz funkcji, ale tylko w jeden sposób: poprzez typedef. Inaczej wyskoczy błąd, że identyfikator nie znany.
Po drugie
Inicjacja struktur nie musi byc całkowita. Mozna zrobić to częściowo np.:
inna j={0};
Wtedy zainicjuje się tylko składowa a. Wyskoczy najwyżej warning, jeśli zaznaczycie, ża taki warning ma wyskakiwać.
Po trzecie
Warto zaznaczyć wszystkie warningi... Choćby po to, by zachować max kompatybilność źródła z innymi kompilatorami.
Niech kompilator sobie krzyczy. Między innymi od tego przecież jest.
Po czwarte
Nie trzeba pisac:
printf("%d\n",j.a);
mozna ...
printf("%d\n",j);
...i też wyjdzie 1. To ze względu na znaczki %d (2 pierwsze bajty). Wpiszcie np. %p - wywali wam daleki wskaźnik hex (4 pierwsze bajty) :-), gdzie a to offset, b segment. I to niezaleźnie, czy wpiszecie j.a, czy samo j. A to znowu dlatego, że &j==&(j.a) .
Po piąte
Odwołanie -> . Nie udało mi się skonstruować odwołania typu: *(wskaźnik).składowa. Jeśli ktoś wie jak to zrobić, to sam jestem ciekaw.
Po szóste
Jeśli jeszcze tego nie złapaliście w międzyczasie... W pamięci dane są trzymane od najmłodszego do najstarszego bajtu...
Pamiętacie?
type ptr=record ofs,seg:word end;
Właśnie dlatego ofs i seg nie są w takiej kolejności,
jaką na pierwszy rzut oka nakazuje logika.
- Typy proceduralne
type funtype=function(a:integer;b:char):word;
proctype=procedure(i:pointer;j:word);
var fun:funtype;
proc:proctype;
void near(proc)(void,unsigned);
unsigned far(*fun)(int,char);
W c++ widać od razu że literały fun i proc to wskaźniki.
W pascalu to też wskaźniki, choć tego nie widać na pierwszy
rzut oka. Specjalnie użyłem słowa far i near. Ale z tymi
modyfikatorami far/near dla funkcji i procedur trzeba uwazać.
UWAGA PRZEKŁAMAŁEM (poniżej strzeliłem niezłą gafę):
Typ zmiennej musi być dokładnie taki sam, jak typ wywołania
funkcji/procedury, bo inaczej program będzie źle działał-
znacie to na pewno, np.: powiesi się, zresetuje kompa,
zrobi cokolwiek innego dziwengo. To ze względu na dalekie i
bliskie wywołania i powroty z funkcji.
POWINNO BYĆ TAK (sorki za pomyłkę- walczyłem z potworami i sam stałem sie potworem- łatwo sie pomylić przy tych wskaźnikach):
Modyfikatory te NIE OZNACZAJĄ wcale typu wskaźnika, tylko sposób wywołania funkcji. To zależy od typu pamięci/lub modyfikatora. Zadeklarowany typ wywołania musi natomiast być dokładnie taki sam, jak typ wywołania funkcji/procedury, bo inaczej program będzie źle działał- znacie to na pewno, np.: powiesi się, zresetuje kompa, zrobi cokolwiek innego dziwengo. To ze względu na dalekie i bliskie wywołania i powroty z funkcji.
Natomiast typ wskaźnika reguluje się tak jak każdego innego wskaźnika:
void near(far* proc)(void*,unsigned);
unsigned far(near* fun)(int,char);
proc: daleki wskaźnik z bliskim wywołaniem
fun: bliski wskaźnik z dalekim wywołaniem.
W pascalu procedury wywoływane poprzez zmienną proceduralną muszą zawsze być opatrzone modyfikatorem far- bo wskaźnik zawsze jest daleki- 4 bajtowy. Albo trzeba ustawić w opcjach kompilatora na stałe dalekie wywołania (wywołania far), albo ustawić bądź to lokalnie bądź globalnie dyrektywę kompilatora {$F+} lub {$f+}. W c++ deklaracja parametrów wywołania w zmiennych jest oparta na tych samych zasadch, co forłardowanie, wystarczy podać tylko typy parametrów, bez ich nazw.
Przykłady:
type funtype=function(a:integer;b:char):word;
proctype=procedure(i:pointer;j:word);
var fun:funtype;
proc:proctype;
{$f+}
function jeden(c:integer;d:char):word; far;
begin
jeden:=... {cos tam- whatever}
end;
procedure dwa(k:pointer;l:word); {mamy dyrektywę $f+, więc nie trzeba pisać far}
begin
end;
{$f-}
begin
fun:=jeden;
proc:=dwa;
if(@proc<>nil)and(@FUN<>nil)then {np. tak się mozna zabezpieczyć}
proc(nil,fun(1,#2))
end.
Zauważcie, że nazwy parametrów wywołania nie muszą się
pokrywać, ważne aby pokrywały sie typy. I jeszcze jedno
jest to jedyny wskaźnik w pascalu, którego nie inicjuje
sie poprzez znaczek @: fun:=@jeden - to nie przejdzie.
Natomiast sprawdzenie adresu odbywa się na zasadzie
wyszukania adresu właśnie porzez @. Dziwny ten wskaźnik,
ale nadal jest to wskaźnik.
void near(proc)(void,unsigned); // deklaracja zmiennej proc - wywołania bliskiego;
unsigned far(*fun)(int,char); // fun - dalekie wywołanie
unsigned far jeden(int i,char c){ // far to modyfikator wywołania funkcji
return ...
}
void near dwa(void* k,unsigned l){ // a tu będzie near
}
main(){
fun=jeden;
proc=dwa;
if(fun && proc) // to samo zabezpieczenie, co wyżej
proc(0,fun(1,2)) // zamiast null mozna swobodnie pisać 0- konwersja wartości
}
- Extended syntax
Mamy funkcję w c++ , która zwraca wynik, powiedzmy dla ułatwienia int/integer.
int fun(){ // albo samo: fun(){ - int jest domyślny
return wynik
}
jeżeli podczas wywołania chcemy zignorować wynik funkcji, to jest to sprawa prosta:
zamiast powiedzmy:
{
int i=fun();
}
piszemy:
{
(void)fun(); // to jest spóścizna po czystym c- gdzie było to obowiązkowe
fun(); // to samo (void) jest tu domyślne
(void)fun(void); // czyste C - kompilator się do tego nie ma prawa przyczepić.
}
Zmieniamy funckje w procedurę...
W pascalu też mozna cos takiego robić...
W opcjach kompilatora jest checkbox - 'extended syntax', wystarczy go
zaznaczyć i będzie mozna robić to samo:
function fun:integer;
begin
fun:=wynik
end;
var i:integer;
begin
i:=fun;
fun
end.
Można to jeszcze na stałe ustawić w swoim kodzie
źródłowym -opcja kompilatora $x . Najlepiej na
samym początku pliku wpisać: {$x+} lub {$X+} -
wielkość liter w pascalu jak zwykle nie gra roli.
I będzie to samo niezależnie od tego, co jest w
opcjach kompilatora ustawione.
- referencje cd.
W poprzedniej części zająłem sie referencjami wywołań, teraz chciałem zająć sie referencjami troszkę innego typu...
var
a:pointer;
b:longint absolute a;
void* a;
long& b=a;
Istnieją takie rzeczy w obu jezykach, ale czy znając rzutowanie/konwersję naprawdę trzeba tego używać i tylko zaśmiecać sobie źródło nowymi zmiennymi? Niby w końcu to ta sama zmienna, tylko majaca dwie różne nazwy.
Tego rodzaju referencje nie muszą odnosić sie to typów tego samego rozmiaru. Mozna swobodnie pisać np.:
var
a:integer;
b:byte absolute a;
int a;
char& b=a;
Jedynie w c++ kompiltor może wyrzucić łorninga:
'temporary used to initialize ....'- Można ewentualnie to wyłaczyć, ale nie trzeba. I chyba jest to jedyny przypadek, kiedy tego rodzaju referencja jest potrzebna (moim nieskromnym zdaniem- ale ja jestem zboczony, nie lubię takich referencji, lubię się bawić konwersami).
- Absolute (pascal)
Fajowe słowo jedno zastosowanie było powyżej, teraz drugie:
type ekran=array[0..199,0..319]of byte;
var ek:ekran absolute $a000:$0000;
procedure plot(x,y:integer;col:byte);
begin
ek[y,x]:=col {lub ek[y][x]:=col}
end;
begin
asm
mov ax,0013h
int 10h
end;
...
plot(159,99,15) {kropa na środku}
...
asm
mov ax,0003h
int 10h
end
end.
asm - end to też blok, nim uzywa sie asemblera w c++ wygląda to b. podobnie asm{}. Ale tym nie będę się zajmował, bo to już inny język... Są arty traktujące o tym.
Natomiast co do absolute: tworzy statyczną zmienną pod dokładnie określonym adresem. W szczególności:
var b:byte absolute a;
było stworzeniem b pod adresem zmiennej a.
Ostatnie.
O czym jeszcze nie napisałem?
O case/switch... I o wszystkim, o czym zapomniałem- oczywiście ;). Np. przypisania wielokrotne a=b=150 tysięcy. ;)
Masz moje błogosławieństwo, najlepiej weź text z mojej stronki, jest lepszy. Oczywiście wszelkie uzupełnienia - jak najbardziej, tylko jeśli, to prosze pozostaw jakis slad po pierwotnym autorze ;). No i jakby ktoś cos chciał dodać, to adres do Kapustki znacie. Do mnie też.
wiesz lolku co to jest edycja swojego artykulu?
taka ikonka oloweczka
zrob z niej uzytek
heh - komentarz autora mówi sam za siebie...
Ej, wiecie co wróciłem z wydziału... I zaczęło mnie to wkurzać. Nie będę tutaj ciągle czegos poprawiał. Mam swoją stronę, gdzie mogę robic co chcę, więc nie będe więcej smiecił tu ciągłymi poprawkami. Tam będzie bardziej na bieżąco i zero statsów. Nie interesuje mnie to. Jak chcecie na mnie zjechać, albo chwalić to ewentualnie osobiście.
Ciągłe poprawki, sorry, ale YYYYYY!!!! Sam się dziwię, jaki czasem bywam tępy. Gdybym napisał książke, to nigdy by nie wyszła, ze względu na ciągłe poprawki. Albo wydawałbym erratę do erraty.
Zapomniałem też oczymś takim:
int i;
if(!(i=open(...))){
}
Tu sie najpierw przypisuje wynik open do i, a potem dopiero porównuje i do 0.
Dlatego, za twoją zgodą, połączę te dwa artykuły w jeden, zaopatrzę w spis treści, hiperłącza i przeformatuję - szczegóły omówimy przez gg i emaila. Ale nie zrobię tego za darmo ! Zapłatą jest to, że nabierzesz więcej dystansu do swojej pracy i lofiksa.
Ale przecież to nowy użytkownik - zaklinam na Hłaskorower - więcej wyrozumiałości.
Przeformatuję tekst, wtedy wykasujemy te dwa arty (wraz z przykrą dla wszystkich historią komentarzy) i spróbujemy jeszcze raz.
pozdrawiam i czekam na odpowiedź
kapustka3@poczta.onet.pl
gg 3499638
OK. Art spoko, ale formatowanie to jednak popraw...