PDA

View Full Version : [C] È più efficace passare ritornare una struct o sfruttare le variabili d'ambiente ?


Y3PP4
12-08-2009, 14:47
Come da titolo,

ho bisogno di far ritornare diversi parametri da una funzione (che li definisce), affinchè possano essere eventalmente passati alle altre funzioni che richiedano uno o più di questi parametri. Per utilizzare una soluzione elegante ( e facilmente mantenibile ) voglio evitare di definire numerose variabili globali sparse negli header files, ma pensavo alla definizione di una struct che li contenga tutti.

Non posso che chiedermi, se invece convenga (in rapporto prestazioni/comodità) accedere alla lettura/scrittura di variabili d'ambiente.
Probabilmente dovrei valutare quante volte mi servono questi parametri nel programma e decidere di conseguenza...

Poichè molte funzioni avranno bisogno (ad ogni chiamata) di ricevere i corretti parametri, questo implicherebbe dover sempre passare i valori della struct (o tramite puntatore, o tutta la struct), oppure accedere ogni volta in lettura alla variabile d'ambiente.

Secondo voi cosa converebbe usare (in una soluzione generica, ovvio che poi nel caso specifico si può dover sacrificare qualcosa) ?
Se possibile (proprio per la genericità del caso) vorrei che mi spiegaste anche i motivi di una scelta o dell'altra.

Un esempio in peudocodice (molto grezzo e banale) che ipotizza la struct:
funzione main:
struct = funzione_che_ritorna_i_valori;
chiamata_a_funzione#1(struct);
chiamata_a_funzione#2(struct);
// .... etc etc

Mille grazie,

buona giornata.
Ps. perdonate il titolo inesatto, ho dimenticato di togliere la parola "passare".

WarDuck
13-08-2009, 18:49
IMHO basta far ritornare semplicemente un puntatore ad una struct precedentemente allocata nell'heap.

Quindi ti mantieni una struct nell'heap (visto che a quanto ho capito questa struct dev'essere globale) e passi i puntatori all'occorrenza.


typedef struct pippo {
// definisci la tua struttura
} Pippo;

main() {

Pippo* prova = calloc(1, sizeof(Pippo)); // calloc azzera anche la struttura

funzione1(prova);
funzione2(prova);
}

Y3PP4
13-08-2009, 20:26
IMHO basta far ritornare semplicemente un puntatore ad una struct precedentemente allocata nell'heap.

Quindi ti mantieni una struct nell'heap (visto che a quanto ho capito questa struct dev'essere globale) e passi i puntatori all'occorrenza.


typedef struct pippo {
// definisci la tua struttura
} Pippo;

main() {

Pippo* prova = calloc(1, sizeof(Pippo)); // calloc azzera anche la struttura

funzione1(prova);
funzione2(prova);
}


Grazie mille per la risposta.
In effetti, cercando sul web un po' a fondo ho trovato questa soluzione, ma non così chiara (leggevo giusto: passare un puntatore alla struct) e mi è sembrata da subito adatta come soluzione al mio quesito. Ma ti ringrazio per la esposizione e lo snippet che illustra il suo utilizzo (e soprattuto per il fatto che mi hai indicato di usare la calloc, che altrimenti, probabilmente avrebbe richiesto un ulteriore topic in seguito :p).

Per ora mi affiderò a questa soluzione,

grazie e buona serata! :)

WarDuck
13-08-2009, 20:37
Prego, di nulla... :sofico:

mr_hyde
14-08-2009, 20:06
Io personalmente preferisco evitare il piu' possibile le variabili globali.
Preferirei una cosa del tipo

typedef struct myStruct
{
unsigned short bIsInitialized;
int theInt;
float theFloat;
} myStructType;

/* esempio di funzione che usa la struttra SOLO in input */
int myFunction(const myStructType* pStruct)
{
/* controllo puntatore (MAI pensare che un puntatore e' sicuramente valido) */
if (!pStruct) return -1;

/* controllo su campo correttezza struttura */
if (!pStruct->bIsInitialized) return -2;

/* fai qualcosa con pStruct */
printf("int = %d float = %f\n", pStruct->theInt, pStruct->theFloat);

return 0;
}

/* esempio di funzione che usa la struttra in input/output */
void myFunctionBis(myStructType* pStruct)

/* altro esempio scemo */
int initMyStruct(myStuctType* pStruct)
{
/* controllo puntatore (MAI pensare che un puntatore e' sicuramente valido) */
if (!pStruct) return -1;

if (qualche_altra_condizione_di_errore) return -10;

/* inizializzo tutto a 0 */
memset(pStruct, 0x0, sizeof(myStructType));

/* popolo quello che mi interessa */
pStruct->theInt = 12;
pStruct->theFloat = 123.41f;

/* dico che il contenuto della struttura e' inizializzato / valido */
pStruct->bIsInitialized = 1;

return 0;
}

int main(int argc, char** argv)
{
/* instanzio la mia struttura vuota */
myStructType theStruct;

/* dove memorizzare risultato operazioni */
short int result = 0;

/* chiamo la sua funzione di inizializzazione */
result = initMyStruct(&theStruct);

/* a seconda del risultato faccio diverse operazioni */
if (result != 0)
{
/* gestisco l'errore in qualche modo */
...

return 1;
}

/* uso la struttura */
result = myFunction(&theStruct);

...

return 0;
}



In questo modo:
1) ho evitato var globali che sono il male assoluto per i programmatori (dopo qualche sessione di debug su progetti grossi con variabili globali, alla ricerca della funzione che le ha impostate in modo errato, capirai il perche')
2) ho evitato allocazioni (perche' complicarsi la vita se non serve? Altra cosa: la calloc in genere e' usata per gli array, per allocare un'unica struttura in genere si usa la malloc)
3) nota poi l'uso di una variabile di "check" (bIsInitialized) per vedere se la struttura ha un contenuto valido oppure no (questo e' un esempio scemo, tuttavia in cose "grosse" puo' far comodo una variabile di check)
4) (e non mi stanchero' mai di ripeterlo) controllare SEMPRE i parametri in input, specialmente i puntatori.

Ciao,
Mr Hyde

Y3PP4
14-08-2009, 22:07
Wow...:eek:

....

son rimasto senza parole O.O".
Più che una risposta sembra proprio un tutorial, mi hai dato in una sola risposta una caterva di preziosissime informazioni (molte delle quali a me sconosciute).
Ti ringrazio mille per la risposta, veramente completa. Inoltre (senza farmi solo sorprendere dall'esposizione) la soluzione proprosta mi piace molto, sia perchè si avvicina di gran lunga al tipo di programmazione a me congeniale, sia per la chiara esposizione dei punti di forza di questa soluzione.
Ti ringrazio anche per non esserti limitato alla sola esposizione della soluzione, ma anche di aver confutato l'utilizzo delle variabili globali motivatamente.
:D

Che dire,
proverò subito il sistema da te spiegato.

Mille grazie ancora,
e buona serata.

Ps: rispondi spesso ai miei quesiti :D risposte come questa mi sono preziosissime (come del resto qualunque risposta - attualmente -).

Ciao!

Krywen
31-08-2009, 14:19
Aggiungo una cosa in cui mi sono scontrato in uno dei miei primi progetti:
nel caso in cui i campi della struttura siano tanti.
I casi sono due: continuare a passare l'indirizzo della struttura stessa a tutte le funzioni che usino informazioni contenute in essa, oppure.. passare ad ogni funzione solo i campi di tale struttura che le servono.
Pro e contro?

Se passi l'indirizzo della struttura:
- Esponi a libero accesso a tutti i campi della struttura a tutte le funzioni, quindi esse potrebbero accedere a parametri non di loro competenza. In progetti di piccole dimensioni non si nota molto il problema, ma se i campi sono tanti, si possono creare problemi di "competenze".

- è immediatamente più facile scrivere la funzione,(usa i campi che vuoi), ma non è chiaro come agisce la funzione: cioè idealmente i parametri di ingresso di una funzione sono i dati che riceve in input una funzione per perseguire il suo scopo. Così facendo rompi l'astrazione, gli passi una struttura che contiene tutto, e per capire cosa usa la funzione devi andar in giro per le righe a vedere a quali campi accede. Dunque può essere più complicato capire velocemente come lavora la funzione.

- può esserci un effetto indesiderato: una funzione che usa solo un campo A, può richiamare una funzione che usa un campo B delal struttura. In questo caso si può erroneamente pensare che la priam funzione usi solo il campo A, invece deve avere accesso anche al campo B per potre invocare la seconda funzione (è un altro esempio di come fraintendere l'input delle funzioni)

- il caso precedente può anche scaturire in una seria di chiamate a funzione non logiche: ad esempio una funzione che deve inizializzare i campi A e B, potrebbe chiamarne una altra per determinare il una media algoritmica su altre cose (il problema cioè è che ha poco senso vedere una funzione inizializza(struct *a) che richiama una funzione media(struct *a) solo perchè può farlo avendo visibilità a tutta la struttura).

- è difficile capire cosa fanno le funzioni se tutte hanno lo stesso parametro di ingresso (se ben documentato, con nomi esplicativi, può essere anche semplice, dipende come lavori).

Se passi alle funzioni i puntatori ai singoli campi della struttura:
- è un pò meno efficiente, ma puoi usarlo comunque se non è questo il tuo collo di bottiglia
- ogni funzione è astratta bene, cioè prende in ingresso solo i dati che le servono. Questo conferisce a tutto il programma una buona e dettagliata interfaccia tra le funzioni, nonchè un astrazione consistente.
- per quello detto sopra è più facile revisionare il programma e farlo capire agli altri esseri umani.

Appena mi verranno in mente altre motivazioni le scriverò.

Scegliere l'uno o l'altro metodo è una decisione da ponderare nel caso specifico, io non sò darti una risposta definitiva, ma usare un approccio misto mi sembra la soluzione peggiore.

P.S. Se ho sbagliato qlc di enorme o del codice è perché vengo dal C++ e non dal C.

banryu79
31-08-2009, 14:49
Non ricordo molto del C, ma potrebbe essere il caso di spezzare la struttura in sotto-strutture in modo che essa memorizzi i putatori alle sottostrutture? Si avrebbe una nelle parti da passare alle diverse funzioni che operano sulla struttura o una sua sottoparte.
Alla fine sembra una questione di design della struttura dati (e relative funzioni che ci operano sopra).

Krywen
31-08-2009, 16:20
Si giusto, si possono scomporre i campi in 'sottosezioni', cioè in struct separate, e poi passare quelle. Non è una brutta idea. Bisogna prestare attenzione poi alle tante strutture diverse che si creano.

A lezione all'università mi sembra che varie parti del kernel sono sviluppate in questo modo.

Cmq anche questa è un opzione da valutare nel contesto specifico, ma grazie per avermela ricordata; devo tenermela in mente.

banryu79
31-08-2009, 16:34
Eh-eh, divide et impera!
Salvo poi non dover fare la stessa fine dell'impero romano...

Y3PP4
01-09-2009, 19:31
Aggiungo una cosa in cui mi sono scontrato in uno dei miei primi progetti:
nel caso in cui i campi della struttura siano tanti.
I casi sono due: continuare a passare l'indirizzo della struttura stessa a tutte le funzioni che usino informazioni contenute in essa, oppure.. passare ad ogni funzione solo i campi di tale struttura che le servono.
Pro e contro?

Se passi l'indirizzo della struttura:
- Esponi a libero accesso a tutti i campi della struttura a tutte le funzioni, quindi esse potrebbero accedere a parametri non di loro competenza. In progetti di piccole dimensioni non si nota molto il problema, ma se i campi sono tanti, si possono creare problemi di "competenze".

- è immediatamente più facile scrivere la funzione,(usa i campi che vuoi), ma non è chiaro come agisce la funzione: cioè idealmente i parametri di ingresso di una funzione sono i dati che riceve in input una funzione per perseguire il suo scopo. Così facendo rompi l'astrazione, gli passi una struttura che contiene tutto, e per capire cosa usa la funzione devi andar in giro per le righe a vedere a quali campi accede. Dunque può essere più complicato capire velocemente come lavora la funzione.

- può esserci un effetto indesiderato: una funzione che usa solo un campo A, può richiamare una funzione che usa un campo B delal struttura. In questo caso si può erroneamente pensare che la priam funzione usi solo il campo A, invece deve avere accesso anche al campo B per potre invocare la seconda funzione (è un altro esempio di come fraintendere l'input delle funzioni)

- il caso precedente può anche scaturire in una seria di chiamate a funzione non logiche: ad esempio una funzione che deve inizializzare i campi A e B, potrebbe chiamarne una altra per determinare il una media algoritmica su altre cose (il problema cioè è che ha poco senso vedere una funzione inizializza(struct *a) che richiama una funzione media(struct *a) solo perchè può farlo avendo visibilità a tutta la struttura).

- è difficile capire cosa fanno le funzioni se tutte hanno lo stesso parametro di ingresso (se ben documentato, con nomi esplicativi, può essere anche semplice, dipende come lavori).

Se passi alle funzioni i puntatori ai singoli campi della struttura:
- è un pò meno efficiente, ma puoi usarlo comunque se non è questo il tuo collo di bottiglia
- ogni funzione è astratta bene, cioè prende in ingresso solo i dati che le servono. Questo conferisce a tutto il programma una buona e dettagliata interfaccia tra le funzioni, nonchè un astrazione consistente.
- per quello detto sopra è più facile revisionare il programma e farlo capire agli altri esseri umani.

Appena mi verranno in mente altre motivazioni le scriverò.

Scegliere l'uno o l'altro metodo è una decisione da ponderare nel caso specifico, io non sò darti una risposta definitiva, ma usare un approccio misto mi sembra la soluzione peggiore.

P.S. Se ho sbagliato qlc di enorme o del codice è perché vengo dal C++ e non dal C.

Grazie mille per la chiara esposizione dei pro/contro di ogni soluzione.
In effetti, ammetto di non aver pensato a questa soluzione. Potrebbe essere utile, ma ogni passaggio di un campo della struct comporta, per l'appunto, la copia del valore, mentre passare il puntatore della intera struct evito questa situazione (almeno se non mi sfugge niente, l'unico modo di fare quello è di dereferenziare il puntatore alla struct, ottenendo e quindi passando il valore con il proprio tipo).
Sicuramente in molti casi potrà non importare ma dato che mi venne in mente questa domanda quando riflettevo a come realizzare un modo per contenere i dati ricavati dalla configurazione all'avvio dell'applicazione e renderli, in seguito, disponibili alle varie funzioni in tutto il programma, in un progetto grande potrebbe essere pesantuccio. Ma come già detto, bisogna studiare il caso. Il fatto di vedere, comunque, la cosa come una sorta di "database" a cui ogni funzione possa accedere agli specifici campi (dando l'effetto di definire dei privilegi) è una cosa che mi stuzzica molto :p
Non ricordo molto del C, ma potrebbe essere il caso di spezzare la struttura in sotto-strutture in modo che essa memorizzi i putatori alle sottostrutture? Si avrebbe una nelle parti da passare alle diverse funzioni che operano sulla struttura o una sua sottoparte.
Alla fine sembra una questione di design della struttura dati (e relative funzioni che ci operano sopra)
Uhm. Questa sembra la soluzione idealmente posta tra le due (passaggio di puntatori e accesso ai soli valori necessari). Mi piace, devo tenerla in considerazione per una futura applicazione.


Grazie ad entrambi per la risposta :) entrambe molto utili (e apprezzate).
PS. Grazie Krywen per aver riscovato questo topic :p