GNU libc6 tsearch és tfind
void *tsearch(__const void *__key,void **__rootp,__compar_fn_t __compar);
void *tfind(__const void *__key,void *__const *__rootp,__compar_fn_t __compar);
Restellem, de nem igazán értem mi a különbség a
void **__rootp
és a
void *__const *__rootp
pointerek között.
UI: Vannak sejtéseim, de mielőtt a magam "butaságait" eröltetném, szeretném mások definícióját hallani.
- 3258 megtekintés
Hozzászólások
Jó. Azt már tudom én hol rontottam el (a pointer értelmezésnbak ehhez semmi köze) Sajna csak ékes angolsággal leírt szöveg van arról milyen egy tétel amit ezek a rutinok kezelnek :)
* Én egy indián vagyok. Minden indián hazudik.
- A hozzászóláshoz be kell jelentkezni
Kezdjük az elején.
void *x;
Ekkor x egy olyan változó lesz, ami egy címet tartalmaz, és a cím egy olyan memóriaterületre mutat, ahol egy "void" típusú érték található vagy egy tömb első eleme, amely tömbnek az elemei "void" típusúak.
Így
v = *x;
értékadás az jelenti, hogy behívjuk azt a "void" típusú változót, ami x-ben lévő címen van, és v-be másoljuk.
void **x;
Ekkor az x-ben lévő cím nem közvetlenül "void" típusú elemre (vagy tömbre) mutat, hanem olyan mutatóra (vagy mutató tömbre), ami (vagy aminek az elemei) már tényleg "void" típusú elemre (vagy tömbre) mutat (vagy mutatnak). Tehát a lényeg, hogy x egy másik mutatóra fog mutatni nem pedig "void" típusú memória területre.
Így
pv = *x;
értékadás azt jelenti, hogy egy olyan változónak adjuk át az x által mutatott memóriaterület tartalmát, amit void *pv; módon deklaráltunk. Azaz a pv-ben lévő cím fog majd egy "void" típusú változóra mutatni.
void *nem_void_tipus *x;
Egy ilyen deklarációt az én fordítóm (g++) nem enged meg.
Sőt ezt sem:
void *void *x;
Ezt a szintaktikát én abszolút nem értem. Nem is tudom jelenlegi fogalmaimmal elképzelni, hogy mi lehet.
Lehet, hogy C-ben megengedett, C++-ban pedig nem?
Ha valaki tudja a választ ezekre a kérdésekre, ne hallgassa el! Én is szívesen okulnék belőle.
Egyébként számomra az is érdekes, hogyan lehet egy változó "void" típusú. Mit takar ez?
- A hozzászóláshoz be kell jelentkezni
Változó!
Mármint nem úgy... :D
Nem void típusú változóról van szó, hanem void típusú mutatóról!
A void típusú mutató az általános mutatótípus, amely infóvesztés nélkül átalakítható bármilyen más típusú mutatóvá, és vissza.
Mivel mindegyik egy memóriacímet tartalmaz (ugyanolyan memóriacímet)...
- A hozzászóláshoz be kell jelentkezni
OK. Így már értem. Kösz a magyarázatot!
- A hozzászóláshoz be kell jelentkezni
Elírás lehet a második. Honnan másoltad?
void *tsearch(const void *key, void **rootp, int(*compar)(const void *, const void *));
void *tfind(const void *key, const void **rootp, int(*compar)(const void *, const void *));
- A hozzászóláshoz be kell jelentkezni
Bocs!
Még egy kérdés. És az alábbi deklaráció mit jelent?
int(*compar)(const void *, const void *)
- A hozzászóláshoz be kell jelentkezni
int(*compar)(const void *, const void *)
Függvényre mutató mutató!
A compar egy olyan függvényt címző mutató, amelynek két const void * argumentuma van, és a visszatérési értéke int típusú.
Azaz például variálhatod, hogy az fsearch függvény milyen függvényt használjon két eleme összehasonlítására.
c-ben lehet a függvényekre mutatni..., (szerepelhetnek tömbben, értékadásban, lehet függvény visszatérési értéke, és argumentuma.
(életemben nem programoztam ilyet)
Azaz a *compar a függvény. Viszont asszem elhagyhatod az argumentumot, mikor használod a tsearch-et (akkor az alapértelmezett compare függvényt használja)
(*compar)(ez, az);
módon kell használni, meghívni majdan a függvényben, aminek átadtad.
- A hozzászóláshoz be kell jelentkezni
Ha tehát van egy olyan függvényem, aminek a deklarációja így néz ki:
int hasonlit(const int* i1, const int* i2);
akkor ezt beletehetem a tfind függvénybe mint argumentum? Kb. így:
int* mutat = tfind(a_key, a_rootp, &hasonlit(int1, int2));
Így most helyes vagy sem?
- A hozzászóláshoz be kell jelentkezni
Nem, ez így nem jó.
Először is a compar deklarációja ez:
int (*compar)(const void *, const void *);
Tehát void-nak kell lennie a típusnak, hogy a gcc hiba nélkül lefordítsa. Az intre való konverziót a hasonlít függvényben kell elvégezni.
Továbbá:
int* mutat = tfind(a_key, a_rootp, &hasonlit(int1, int2));
A hasonlít(int1, int2) az egy függvényhívás c-ben, neked nem ezt kell csinálni, hanem a függvény memóriacímét kell átadnod, tehát:
int* mutat = tfind(a_key, a_rootp, hasonlit);
(Nem kell & a hasonlit elé)
- A hozzászóláshoz be kell jelentkezni
Nekem valamiért az rémlik, hogy futás közben már nem változhat. Azaz már fordításkor végleges lesz, hogy adott tsearch hívásakor melyik függvényt használja majd a változók összehasonlítására.
Így van?
- A hozzászóláshoz be kell jelentkezni
Nem; pont azért lehet paraméterként megadni a függvényre mutató mutatót, hogy futás közben is bármikor megváltoztasd. Az, hogy általában egy hívásnál mindig ugyanazt az összehasonlító függvényt adjuk be neki, az nem változtat azon, hogy ugyanannak a tsearch függvénynek egy másik hívása mást használ, ezért paraméterezhető kell legyen.
Ha fordításkor eldőlő dolgok érdekelnek, akkor a C++-t és a sablonokat nézd meg.
- A hozzászóláshoz be kell jelentkezni
Sima c...
Úgy értettem hogy a forrásban egy helyen meghívva a tsearch, mindig ugyanazt a compare függvényt kell hogy használja.
Azaz ott nem lehet a tsearch függvény paramétere például egy tömbben tárolt függvénymutató, ha a tömb indexe futás közben változik.
Kétségtelenül ettől sokkal kevésbe lenne rugalmas a használhatósága, de azért olyat még lehetne csinálni, hogy két helyen hivod meg a tsearch-et, és mindkét helyen más compare függvényt használ.
Szóval hogy az általad írt "általában" helyett mindig lenne.
Érthető hogyan gondoltam, meg mi a különbség a kettő között? Nem tudom eldönteni...
Viszont biztos igazad van, és nem úgy van ahogy nekem rémlett valamiért..., nem is tudom hogy miért. Sohasem használtam c-ben függvénymutatót, csak valamiért az elméletre így emlékeztem c kapcsán.
- A hozzászóláshoz be kell jelentkezni
Nem; bármikor használhat más mutatót. Igen, pl. tömbelem is lehet a mutató. Próbáld ki.
Onnan is meg lehet közelíteni, hogy ahány helyen hívod, de legalábbis ahány mutatóval hívod, annyi példányban kellene le fordítani a tsearch-ot, a különböző callback-ek miatt. Ezt azért nem teszi meg a fordító. Bár maga a visszahívás gyorsabb lenne egy kicsivel, ez tény, de elhanyagolható.
- A hozzászóláshoz be kell jelentkezni
(Nem kell & a hasonlit elé)
Nem kell, de lehet. Ugyanis fuggvenyeknel es fuggvenymutatoknal az * (indirekcio) es a & (cime) operatorok elhagyhatoak.
- A hozzászóláshoz be kell jelentkezni
Nem elírás, te gyakorlatilag ugyanazt írtad.
- A hozzászóláshoz be kell jelentkezni
Az "__const" ugyanazt jelenti, mint a "const" kulcsszó?
- A hozzászóláshoz be kell jelentkezni
__const vagy makro, vagy compiler extenzio, ami valoszinuleg vagy const-kent helyettesitodik be, vagy teljesen eltunik.
- A hozzászóláshoz be kell jelentkezni
Aha, értem. Köszi!
- A hozzászóláshoz be kell jelentkezni
Azaz válaszolva a kérdésedre, a különbség egy plussz const, és hogy az egyik el van írva.
- A hozzászóláshoz be kell jelentkezni
Ha el van írva, és úgy helyes, ahogy te írod, akkor teljesen világos a helyzet. Biztos csodálkoztam volna azon a válaszon, ami a fenti példát meg tudja magyarázni.
Kösz!
- A hozzászóláshoz be kell jelentkezni
Amúgy:
man tsearch
man tfind
-ból másoltam a deklarációkat. Nem tévedhet nagyot. Szerintem első körben ezt használjátok.
- A hozzászóláshoz be kell jelentkezni
A fo kerdesre eddig adott valaszok nem felelnek meg a valosagnak.
void **x;
Itt x egy olyan mutato, amely egy tetszoleges tipusu mutatora mutat.
void *const *x;
Itt x egy olyan mutato, amely egy tetszoleges tipusu konstans mutatora mutat.
Tehat hol kulonbozik a hasznalat:
void **x;
void *const *y;
x = &ptr; // helyes: x nem konstans
y = &ptr; // helyes: y nem konstans
*x = &a; // helyes: x nem konstans mutatora mutat
*y = &a; // HIBA: y konstans mutatora mutat, nem lehet ertekadas
**x = 3; // HIBA: **x void tipusu, nem lehet neki erteket adni
**y = 3; // HIBA: **y void tipusu, nem lehet neki erteket adni
*(int *)*x = 3; // explicit tipuskonverzio, rendben
*(int *)*y = 3; // explicit tipuskonverzio, rendben
- A hozzászóláshoz be kell jelentkezni
"void *const *x;
Itt x egy olyan mutato, amely egy tetszoleges tipusu konstans mutatora mutat."
--
Sztem te a
const void **x;
-re gondolsz. :)
De próbáld ki:
int main() {
void *const *y;
const void *ptr;
// test.cpp:x: error: invalid conversion from ‘const void**’ to ‘void* const*’
y = &ptr; // pedig ez const mutató... :)
void *aaa;
void *const *yy = &aaa; // lefordul szépen. pedig az "aaa" nem const.
}
A saját definíciómat - legalábbis C++ esetén - lsd. lentebb.
kl223
- A hozzászóláshoz be kell jelentkezni
quote:
*x = &a; // helyes: x nem konstans mutatora mutat
*y = &a; // HIBA: y konstans mutatora mutat, nem lehet ertekadas
**x = 3; // HIBA: **x void tipusu, nem lehet neki erteket adni
**y = 3; // HIBA: **y void tipusu, nem lehet neki erteket adni
szoval szerintem erti.
Amugy erdemes azt tudni, h a const balra kot. Kiveve a bal szelen :). Tehat erdemes megszokni a kovetkezo jeloleseket:
// sima valtozok
const int ci;// felrevezeto
int const ic;// konzekvens
// mutatok
// konstanra mutato nem konstans
const int * cip;// felrevezeto
int const * icp;// konzekvens
//nem konstansra mutato konstans
// nincs felrevezeto
int * const ipc;// konzekvens
//konstansra mutato konstans
const int * const cipc;// felrevezeto
int const * const icpc;// konzekvens
- A hozzászóláshoz be kell jelentkezni
"const balra kot. Kiveve a bal szelen"
Ebbe inkább bele sem gondolok. A lényeg, hogy a * melyik oldalán van.
KisKresz
- A hozzászóláshoz be kell jelentkezni
"szoval szerintem erti."
Igen, azt néztem, hogy a kód ott végülis jó. Csak a definíció nem fedte a valóságot abban a formában. (A kódba nem is kötöttem bele.)
kl223
- A hozzászóláshoz be kell jelentkezni
Gondold at megegyszer! Ebben a peldaban ket csillag van, nem egy.
Ne keverd a "void *const *"-ot a "void **const"-tal!
- A hozzászóláshoz be kell jelentkezni
"void *const *rootp"
"void **const rootp"
"const void **rootp"
Ez három dolog, vagy kettő..., az egyik kétféle képpen leírva? :)
Nem bogarásztam végig a példákat, nekem ezek most nem aktuálisak. c sem, de azért érdekelne egy rövid és tömör válasz! :)
- A hozzászóláshoz be kell jelentkezni
Ez harom dolog.
- A hozzászóláshoz be kell jelentkezni
Semmi, benéztem (;
- A hozzászóláshoz be kell jelentkezni
Ez három dolog.
void *const *const rootp = ...;
És még ilyen is van. :)
Meg persze a const előtaggal is lehet kombinálni az első kettődet és ezt is.
- A hozzászóláshoz be kell jelentkezni
Ha három dolog, akkor viszont vagy a témaindító írásába bemásolt, vagy az általam korábban bemásolt tfind deklaráció hibás nem?
Az általam bemásolt nem hibás, ill. én mindenhol így találtam meg.
Lehet persze hogy különböző c-kben, vagy c++-ban más-más..., de nem tartom valószínűnek. Vagy mégis?
- A hozzászóláshoz be kell jelentkezni
Én is így látom.
Nem használtam még ezeket a függvényeket, de szerintem a témaindítóban lévő változat lesz a hibás. Ugye az adatra mutat egy mutató, ennek a mutatónak a címét adjuk át (másszóval a mutatót adjuk át cím szerint).
const void * * rootp
void const * * rootp
a függvény megígéri, hogy nem nyúl az adathoz, bár a mutatót odébb tolhatja rajta.
void * const * rootp
megígéri, hogy nem nyúl a mutatóhoz (amely az adatra mutat). Az adatot azonban felülcsaphatja. Ennek én se nem sok értelmét látnám.
- A hozzászóláshoz be kell jelentkezni
"de szerintem a témaindítóban lévő változat lesz a hibás."
$ grep -A1 tfind /usr/include/search.h
extern void *tfind (__const void *__key, void *__const *__rootp,
__compar_fn_t __compar);
- A hozzászóláshoz be kell jelentkezni
Nem bírtam tovább, megnéztem a tfind forrását.
Van egy mutató (rootp), ami egy konstans mutatóra mutat (pl. root), ami egy struct node_t-re mutat.
Valóban jó, ha levédjük a root mutatót felülírás ellen. A man ezek szerint rosszul mondja.
Jó lenne az adatot is levédeni (a node_t területet), azaz
const void *const *rootp
lehetne a paraméter (lefordul így).
Tehát mindkettő ,,rossz'', de mindkettő működik. Adalék: még a harmadik (általam javasolt) megoldás sem lenne elég jó, mivel mindegyik csak a gyökérpontot védi meg, a fa többi pontjával úgysem tudnak mit csinálni...
- A hozzászóláshoz be kell jelentkezni
Hát sztem egyik variációra sem igaz, hogy "tetszőleges típusú konstans mutatóra mutat".
De megkérlek, hogy javíts ki, ha tévednék.
- A hozzászóláshoz be kell jelentkezni
Lehet, hogy szavakkal nem ez a legerthetobb megfogalmazasa.
En ugy gondoltam, hogy tetszoleges tipusu [void] konstans mutatora [*const] mutat [*]. Szoval ez egy olyan pointer, ami nem konstans, de konstans altalanos pointerre [void *const] mutat.
- A hozzászóláshoz be kell jelentkezni
Nem láttam egyértelmű választ erre eddig a szálban, szóval (kis fejtágítás ^^):
egy mutató kétféleképpen lehet const:
ad1) úgy, hogy az általa megcímzett memóriát rajta keresztül nem lehet módosítani. Ebben az esetben az, hogy a mutató hova mutat, szabadon módosítható a program futása során. Pl:
int i, j;
const int *ip = &i; // helyes
ip = &j; // helyes
*ip = 1; // fordítási hiba
ad2) úgy, hogy a mutató által megcímzett memóriaterület ugyan módosítható rajta keresztül, de az nem, hogy a mutató hova mutasson. Ekkor értelemszerűen inicializáláskor értéket kell adni a mutatónak. Ennek a szintaxisa az az ominózus *const. Pl:
int i, j;
int *const ip = &i; // helyes
*ip = 1; // helyes
ip = &j; // hibás
A kettőt lehet kombinálni is. Ekkor egy "nagyon const" mutatót kapunk valamire, amin semmit nem lehet módosítani. ;)
C-ben a const-ot simán lehet konvertálni nem const-tá, ott nincs sok értelme.
C++ban viszont van, ott komolyan veszik ezt a kulcsszót.
Egyébként C++ban ezért van értelme egy ilyen definíciójú metódusnak:
class A {
public:
void aaa();
void vmi() const;
};
A fenti deklaráció azt jelenti, hogy a vmi() metódushívás nem fogja megváltoztatni az objektumát, ezért akkor is meghívható, ha az objektumhoz csak const referencián/mutatón keresztül férünk hozzá. Pl:
const A *a = ....; // vhogy értéket kap
a->vmi(); // helyes
a->aaa(); // fordítási hiba
Úgy is mondhatnánk, hogy az aaa() metódusban a "this" mutató vhogy így lehet deklarálva:
A *const this = ...;
...míg a vmi() metódusban vhogy így:
const A *const this = ...;
Üdv,
kl223
- A hozzászóláshoz be kell jelentkezni
...és akkor most jó a témát indító írásban a tfind deklarációja, vagy nem? Mert szerintem az volt a fő kérdés?! :)
- A hozzászóláshoz be kell jelentkezni
C/C++ban alapértelmezés szerint érték szerinti paraméterátadás van.
Magyarul ha átadsz egy függvénynek egy mutatót, akkor a függvény ugyan módosíthatja azt, hogy a mutató hova mutasson, de ez a módosítás nem fog látszódni a függvényen kívül.
Miért? Mert a függvénybeli mutató csak egy lokális másolata az eredetinek. (Természetesen azt a memóriacímet, ahova a pointer mutat, ettől függetlenül lehet valóban módosítani a függvényen belülről, és épp ez a lényeg....)
Ugyanis... mi is a helyzet a "tfind" deklarációban? Ott "pointerre mutató pointerünk" van. Tehát a függvény a sima
void **ptr
paraméternél képes lenne valóban módosítani a második pointer értékét.
Az a "tfind" deklaráció mindössze annyit köt ki, hogy a függvény _azt_ változatlanul hagyja, hogy az átadott pointerek hova mutatnak. (lényegében ha látod azt a *const-ot egy függvénydeklarációban, az inkább megnyugvásra adhat okot, mint aggódásra. ^.-)
Mutatok példát!
#include <iostream>
#include <cstdio>
using namespace std;
void test1( int **p ) {
p = 0;
}
void test2( int **p ) {
*p = 0;
}
int main() {
int i = 1;
int *a = &i;
int **y = &a;
cout << "orig : " << y << " " << *y << endl;
test1( y );
cout << "test1: " << y << " " << *y << endl;
test2( y );
cout << "test2: " << y << " " << *y << endl;
return 0;
}
A program kimenete pedig:
kl223@freelancer /mnt/prog/fun $ ./test
orig : 0xbfed7654 0xbfed7658
test1: 0xbfed7654 0xbfed7658
test2: 0xbfed7654 0
kl223@freelancer /mnt/prog/fun $
Mit kell itt látni? Azt, hogy a "test1" nem módosította a neki átadott paramétert.
A "test2" viszont igen, és ez nem feltétlenül jó.
Ha viszont egy kicsit módosítunk a függvények deklarációján, valahogy így:
void test1( int *const *p ) {
p = 0;
}
void test2( int *const *p ) {
// test.cpp:11: error: assignment of read-only location ‘* p’
*p = 0;
}
Akkor teljesen biztos, hogy a mutatóink nem "sérülnek", mert fordítási hibát fog dobni az a kód, ami megpróbálja módosítani a mutatókban tárolt memóriacímet. (és itt most nem a memóriacímen tárolt adatokról beszélek, hanem lényegében a mutató jobbértékéről - asszem így hívják)
Természetesen a fenti, módosított test{1,2} metódusok a mutatók által megcímzett memóriacímen lévő adatokat ugyanúgy módosíthatják, mint az eredeti példakódban lévők.
Summa summarum: _igen_, jó az a deklaráció. Ne aggódj miatta. :P
Üdv,
kl223
[szerk: legalábbis szintaktikailag jó... hogy a glibc-ben így van-e benne azt nemtom. :)]
- A hozzászóláshoz be kell jelentkezni
Át fogom nézni az összes hozzászólást, és kipróbálok néhány variációt. Valószínűleg az sokat fog segíteni a megértésben.
- A hozzászóláshoz be kell jelentkezni