Newsgroup: it.comp.lang.c++
Titolo: ottenere un puntatore alla struct da indirizo di un elemento

Ciao,
Avviso:
Prima di porre le 6 (sei!) domande e problemi voglio avvertire che chi non
fosse interessato a comprendere perchè uno dovrebbe scrivere del codice come
quello che presento più giù ma vuole solo risolvere i problemi e dare le
risposte dal punto di vista del linguaggio C++ può eseguire una "goto
Problemi_da_risolvere_e_domande_varie;"
Premessa:
Il kernel di Linux (si è in C, anzi GNU/C, ma non è questo il punto...) deve
gestire strutture che contemporaneamente possono appartenere a molte liste
(doubly linked circular lists). Le strutture esistono di per sè e non è
possibile farne delle copie da inserire in ciascuna lista. Per risolvere
magnificamente il problema e implementare una interfaccia funzionale e
potente alla quale qualsiasi kernel hacker si deve appoggiare per gestire le
sue liste senza reinventare la ruota si è deciso che ogni struct che vuole
essere membro di un certo numero di specifiche liste deve contenere al suo
interno almeno un numero uguale alle liste stesse di "struct list_head".
Ogni "struct list_head", qualsiasi sia la struct in cui è contenuta,
contiene solo due puntatori (*prev e *next) alla precedente a alla
successiva "list_head" di due oggetti in lista. Facciamo un esempio nella
speranza di riuscire a trasmettere il senso di tutto ciò:
struct list_head {
 list_head *prev, *next;
};
struct mia {
 long ll;
 int ii;
 struct list_head a;
 struct list_head b;
 struct list_head c;
};
Io così ad esempio posso avere un totale di 10 "struct mia" istanziate di
cui 7 appartengono alla lista a, 8 alla lista b, e 6 alla lista c (le
quantità sono state scelte a caso). Da ciò si capisce che i puntatori della
"struct list_head a" riferiranno alla precedente e alla successiva "struct
list_head a" e così vale anche per le altre "struct list_head" all'interno.
La suddetta spiegazione non rende assolutamente giustizia alla soluzione
così come è stata implementata sul kernel, alla robustezza e alle
funzionalità che offre, quindi spero di evitare discussioni sulle possibili
alternative che qualcuno del NG potrebbe proporre come ipoteticamente più
adatte. Comunque chi fosse interessato può dare un'occhiata al codice:
/path_al_sorgente_del_kernel/include/linux/list.h
Il problema che si pone è come ottenere l'indirizzo di uno specifico oggetto
istanziato dalla "struct mia" avendo a disposizione solo 3 cose: un
puntatore a "struct list_head b" (per esempio, se stiamo scorrendo la lista
"b"), il tipo della struct che contiene list_head (struct mia, nel caso
esposto) e il nome "b". Si risolve con la macro "list_entry()" che a sua
volta richiama altre macro e funzioni.
Problemi_da_risolvere_e_domande_varie:
Ho il seguente codice che in parte è stato adattato espandendo e facendo un
merge di varie macro (a partire da list_entry() del Linux kernel), e poi è
stato integrato in un main() affinché possa essere compilato e mandato in
esecuzione così come si trova:
#include <iostream>
struct mlist {
    struct mlist *prev, *next;
};
 
int main()
{
    struct mine {
        long l;
        int i;
        char c;
        struct mlist m;
    } *ps;
 
    ps = new struct mine;
    ps->l = 999;
    ps->i = 1;
    ps->c = 'k';
    ps->m.prev = 0;
    ps->m.next = 0;
    struct mlist *pmlist = &ps->m;
// segue spiegazione delle prossime righe:
// voglio su *ptr l'indirizzo dell'unica istanza di "struct mine" e faccio
finta di non potere usare *ps
// ho a disposizione solo (1) il nome di una variabile => "m", (2)
l'indirizzo della stessa => "pmlist" e il tipo della struttura di cui cerco
l'indirizzo => "struct mine"
 
    struct mine *ptr = ({
        const typeof( ((struct mine *)0)->m) *__mptr = (pmlist);
        (struct mine *)( (char *)__mptr - ((size_t) &((struct mine *)0)->m) );
    });
    std::cout << ptr->l << ", " << ptr->i << ", " << ptr->c << '\n';
 
    delete ps;
    return 0;
}
Spero di non avere fatto errori di copiatura perchè l'ho scritto su un'altra
macchina non connessa a questa e qui non ho compilatore per controllare.
Ciò che voglio fare è spiegato nella "Premessa" e nei commenti interni al
codice.
1) Cosa è "typeof"? Perchè non trovo documentazione anche se compila
perfettamente in C++?
2) Quale è il "rationale" che sta dietro ad una scrittura come "((struct
mine *)0)->m"? Cioè come faccio a scrivere "0 che punta ad m" senza che ci
sia alcun errore?
3) Tutta l'assegnazione a *ptr è racchiusa dentro un paio di parentesi
tonde. Perchè? E soprattutto perchè il compilatore non compila senza
(provate per leggere i messaggi)? E le parentesi graffe? Ma che tipo di
costrutto è?
4) Questo codice è conforme allo Standard C++ oppure compila solo con
gcc/g++ sulla mia linux box? Cosa modificare per renderlo conforme allo
Standard C++?
5) Come si può costruire una funzione (non una macro, perchè la posso già
avere) che provvede a restituire *ptr prendendo come argomenti i tre
descritti nel commento all'interno del codice? Soprattutto non so come
passare "struct mine" se non come stringa o puntatore a caratteri ma non mi
piace. Ad esempio, c'è un modo in C++ che passato come quarto argomento il
puntatore al tipo che sarà restituito dalla funzione (cioè *ptr stesso prima
dell'assegnazione) si possa dedurre all'interno di questa il tipo della
struct che stiamo manipolando? Se si potesse fare non ci sarebbe più bisogno
di passare la stringa con scritto "struct mine". Sarebbe eccezionale!
6) Avendo a disposizione comunque solo i parametri già descritti, ci sono
altre idee ai fini dell'ottenimento dell'indirizzo della specifica istanza
di "struct mine"? Altri modi più adatti al C++?
Intanto un grazie a chi ha avuto la pazienza di leggere fino a qui. Aggiungo
che per me è molto importante che qualcuno di voi si impegni a rispondere
alle domande che ho posto perchè sono nel bel mezzo della produzione di una
libreria e per continuare devo ancora capire queste cose su cui ho chiesto.
Grazie ancora e saluti,
fabio de francesco
(fabiomdf)
--------------------------------
Inviato via 
http://arianna.libero.it/usenet/

 [skip]


 sia alcun errore?
Si può utilizzare come indirizzo di un oggetto, qualsiasi indirizzo "ben 
allineato", per esempio 0, oppure 64...ecc ecc.
In genere si utilizza per prendere l'offset statico di un oggetto.
In questo caso viene utilizzato, una volta per ottenere il tipo di m, e 
poi come offset statico.
Cmq il g++ credo che con zero si lamenti; per ottenere l'offset statico 
si usa 64 al posto di 0 e poi si sottrae 64.
--
Fabio.

 Fabio.
Ciao,
No, non si lamenta. Compila sia in programmi C che C++, con gcc e g++ con
flag "-Wall". Infatti si può provare a compilare l'esempio che ho scritto
senza alcun problema.
Il significato era comunque chiaro, ma ciò che chiedo è se si tratta anche
qui di una estensione del gcc oppure se è un costrutto standard(?).
Grazie,


 In genere si utilizza per prendere l'offset statico di un oggetto.
Umh... sai dirmi come si puo' scrivere tale codice in C++ standard e
leggibile? :)
Ma chissa' perche' il kernel non lo traducono in C++, beneficiano di tutti i
vantaggi della OOP, dei template, ed evitando quegli obbrobri sintattici
stile C :(
Ciao


La risposta è: scrivi una parte del kernel in C++, dimostra senza ombra di
dubbio che l'utente ne avrà un vantaggio (che ne so, magari in termini di
efficienza, vedi tu). La premessa è che questo kernel è stato costruito per
essere realmente usato e non come esercizio di stile.
Assicurati anche che si integri perfettamente a tutto l'ambiente senza
rompere nessuna funzionalità e proponi la patch su 
vger.kernel.org
. Se hai
rispettato questi propositi ti verrà accettata dai maintainers e inclusa nel
tree.
Ovviamente non devi usare nessuna funzione della libreria standard C++,
oppure devi riscriverla all'interno del kernel in quanto il kernel non può
utilizzare nessuna libreria degli utenti. Allo stesso modo sono state tutte
riscritte tutte le funzioni della libreria C usate dentro al kernel, e SOLO
quelle che servono a fini interni.
Come ultima nota, nel caso qualcuno volesse cimentarsi in una riscrittura
totale, ricordo che stiamo parlando di circa 7(sette) milioni di righe tra
codice e (pochi) commenti.
Saluti,


 
C'è tanta gente che continua ad utilizzare gli offset statici quando ci 
sono i puntatori ai dati membro.
Ma avranno i loro motivi per farlo.

 Ma avranno i loro motivi per farlo.
Ecco, allora in C++ si usano i puntatori ai dati membro. Infatti pensavo a
tale idea. Ma forse in C non e' fattibile? Cioe', avere puntatori a elementi
di una struct forse non e' proprio la stessa cosa, ma sicuramente molto piu
leggibile degli offset statici.
Ciao

 utilizzare nessuna libreria degli utenti.
Cioe' il kernel e' un sorgente unico? Non puo' essere linkato? Perche'?
Cosi' per forza che si arriva 7 milioni di righe :D
In ogni caso, anche escludendo la STL, c'e' sempre la OOP e i template (la
prima credo possa rendere il codice piu chiaro, la seconda a dire il vero
non credo sia necessaria).
Ciao

 
Un po' OT rispetto al thread:
gli offset statici non funzionano con i POD (plain old data types).
Chi mi spiega questa frase?
Perché non dovrebbero più andar bene?
E cosa è di preciso un POD, e cosa non è un POD?
--
Fabio.

 il vero non credo sia necessaria).
L'argomentazione era nata perché qualcuno voleva inserire nel tree uno
scheduler per qualcosa realizzato al MIT da qualcuno, scritto in C++. Da
lì nasse un flame tra i sostenitori del C++ e del C. Prova a cercare con
queste chiavi, magari ritrovi il thread se ti interessa.
-- 
int

Al proposito questo mi pare molto interessante:
http://netlab.ru.is/exception/LinuxCXX.shtml
Ciao,
Nicola

 Ciao
Su questo vedi sopra.
Ciao,
