Domanda

Ho ricevuto un commento alla mia risposta su questa discussione:

Il malloc all'interno di una chiamata di funzione sembra essere liberato al ritorno?

In breve, avevo un codice come questo:

int * somefunc (void)
{
  int * temp = (int*) malloc (sizeof (int));
  temp[0] = 0;
  return temp;
}

Ho ricevuto questo commento:

  

Posso solo dire, per favore non lanciare il   valore di ritorno di malloc? Non è   obbligatorio e può nascondere errori.

Sono d'accordo sul fatto che il cast non sia richiesto in C. È obbligatorio in C ++, quindi di solito li aggiungo nel caso in cui un giorno devo portare il codice in C ++.

Tuttavia, mi chiedo come caste come questa possano nascondere errori. Qualche idea?

Modifica:

Sembra che ci siano argomenti molto validi e validi su entrambi i lati. Grazie per la pubblicazione, gente.

È stato utile?

Soluzione

Mi sembra appropriato pubblicare una risposta, poiché ho lasciato il commento: P

Fondamentalmente, se si dimentica di includere stdlib.h il compilatore assumerà malloc restituisce un int. Senza casting, riceverai un avviso. Con il casting non lo farai.

Quindi, lanciando non ottieni nulla e corri il rischio di sopprimere gli avvisi legittimi.

Molto è scritto su questo, una rapida ricerca su Google mostrerà spiegazioni più dettagliate.

modifica

È stato sostenuto che

TYPE * p;
p = (TYPE *)malloc(n*sizeof(TYPE));

lo rende ovvio quando non si alloca accidentalmente memoria sufficiente perché, diciamo, si pensava che p fosse TYPe non TYPE, e quindi dovremmo lanciare malloc perché il vantaggio di questo metodo prevale sul minor costo di accidentale sopprimendo gli avvisi del compilatore.

Vorrei sottolineare 2 cose:

  1. dovresti scrivere p = malloc(sizeof(*p)*n); per assicurarti sempre di distribuire la giusta quantità di spazio
  2. con l'approccio sopra, è necessario apportare modifiche in 3 punti se si cambia il tipo di <=>: una volta nella dichiarazione, una volta nel <=> e una volta nel cast.

In breve, credo ancora personalmente che non sia necessario esprimere il valore di ritorno di <=> e non è certamente la migliore prassi.

Altri suggerimenti

Questa domanda è taggata sia per C che per C ++, quindi ha almeno due risposte, IMHO:

C

Ahem ... Fai quello che vuoi.

Credo che il motivo indicato sopra " Se non includi " stdlib " quindi non riceverai un avviso " non è valido perché non si dovrebbe fare affidamento su questo tipo di hack per non dimenticare di includere un'intestazione.

Il vero motivo che potrebbe farti non scrivere il cast è che il compilatore C ha già lanciato silenziosamente un void * in qualunque tipo di puntatore tu voglia, e quindi farlo da solo è eccessivo e inutile .

Se vuoi avere la sicurezza del tipo, puoi passare a C ++ o scrivere la tua funzione wrapper, come:

int * malloc_Int(size_t p_iSize) /* number of ints wanted */
{
   return malloc(sizeof(int) * p_iSize) ;
}

C ++

A volte, anche in C ++, devi trarre profitto dai programmi di utilità malloc / realloc / free. Quindi dovrai lanciare. Ma lo sapevi già. L'uso di static_cast & Lt; & Gt; () sarà migliore, come sempre, rispetto al cast in stile C.

E in C, puoi sovrascrivere malloc (e realloc, ecc.) attraverso modelli per ottenere la sicurezza del tipo:

template <typename T>
T * myMalloc(const size_t p_iSize)
{
 return static_cast<T *>(malloc(sizeof(T) * p_iSize)) ;
}

Quale sarebbe usato come:

int * p = myMalloc<int>(25) ;
free(p) ;

MyStruct * p2 = myMalloc<MyStruct>(12) ;
free(p2) ;

e il seguente codice:

// error: cannot convert ‘int*’ to ‘short int*’ in initialization
short * p = myMalloc<int>(25) ;
free(p) ;

non verrà compilato, quindi no problemo .

Tutto sommato, in puro C ++, ora non hai scuse se qualcuno trova più di un malloc C nel tuo codice ... : -)

crossover C + C ++

A volte, si desidera produrre codice che verrà compilato sia in C che in C ++ (per qualsiasi motivo ... Non è il punto del blocco C ++ extern "C" {}?). In questo caso, C ++ richiede il cast, ma C non capirà la parola chiave static_cast, quindi la soluzione è il cast in stile C (che è ancora legale in C ++ per esattamente questo tipo di motivi).

Nota che anche con la scrittura di codice C puro, compilarlo con un compilatore C ++ otterrai molti più avvisi ed errori (ad esempio, tentare di utilizzare una funzione senza dichiararla prima non verrà compilato, a differenza dell'errore sopra menzionato) .

Quindi, per essere al sicuro, scrivi il codice che verrà compilato in modo pulito in C ++, studi e correggi gli avvisi, quindi usa il compilatore C per produrre il binario finale. Questo significa, ancora una volta, scrivere il cast, in un cast in stile C.

Un possibile errore che può introdurre è se stai compilando su un sistema a 64 bit usando C (non C ++).

Fondamentalmente, se si dimentica di includere stdlib.h, verrà applicata la regola int predefinita. Pertanto, il compilatore supporrà felicemente che malloc abbia il prototipo di int malloc(); su molti sistemi a 64 bit un int è a 32 bit e un puntatore a 64 bit.

Uh oh, il valore viene troncato e ottieni solo i 32 bit inferiori del puntatore! Ora se si esegue il cast del valore restituito <=>, questo errore viene nascosto dal cast. In caso contrario, verrà visualizzato un errore (qualcosa della natura di & Quot; impossibile convertire int in T * & Quot;).

Questo non si applica ovviamente al C ++ per 2 motivi. In primo luogo, non ha una regola int predefinita, in secondo luogo richiede il cast.

Tutto sommato, tuttavia, dovresti solo essere nuovo nel codice c ++ :-P.

Beh, penso che sia esattamente l'opposto - sempre lanciarlo direttamente sul tipo necessario. Continua a leggere qui!

Il " dimenticato stdlib.h " l'argomento è un uomo di paglia. I compilatori moderni rileveranno e avviseranno del problema (gcc -Wall).

Dovresti sempre lanciare immediatamente il risultato di malloc. Non farlo dovrebbe essere considerato un errore, e non solo perché fallirà come C ++. Se stai prendendo di mira un'architettura di macchina con diversi tipi di puntatori, ad esempio, potresti finire con un bug molto complicato se non aggiungi il cast.

Modifica : il commentatore Evan Teran è corretto. Il mio errore era pensare che il compilatore non dovesse fare alcun lavoro su un puntatore vuoto in qualsiasi contesto. Mi preoccupo quando penso ai bug del puntatore FAR, quindi la mia intuizione è quella di lanciare tutto. Grazie Evan!

In realtà, l'unico modo in cui un cast potrebbe nascondere un errore è se stavi convertendo da un tipo di dati a un tipo di dati più piccolo e perdessi dati, o se stavi convertendo le pere in mele. Prendi il seguente esempio:

int int_array[10];
/* initialize array */
int *p = &(int_array[3]);
short *sp = (short *)p;
short my_val = *sp;

in questo caso la conversione in short eliminerebbe alcuni dati dall'int. E quindi questo caso:

struct {
    /* something */
} my_struct[100];

int my_int_array[100];
/* initialize array */
struct my_struct *p = &(my_int_array[99]);

in cui finiresti per indicare un tipo di dati errato o persino una memoria non valida.

Ma in generale, e se sai cosa stai facendo, va bene fare il casting. Ancor di più quando stai ricevendo memoria da malloc, che a sua volta restituisce un puntatore vuoto che non puoi usare affatto a meno che tu non lo lanci, e la maggior parte dei compilatori ti avvertirà se stai lanciando qualcosa su lvalue (il valore al lato sinistro del compito) non può accettare comunque.

#if CPLUSPLUS
#define MALLOC_CAST(T) (T)
#else
#define MALLOC_CAST(T)
#endif
...
int * p;
p = MALLOC_CAST(int *) malloc(sizeof(int) * n);

o, alternativamente

#if CPLUSPLUS
#define MYMALLOC(T, N) static_cast<T*>(malloc(sizeof(T) * N))
#else
#define MYMALLOC(T, N) malloc(sizeof(T) * N)
#endif
...
int * p;
p = MYMALLOC(int, n);

Le persone hanno già citato i motivi per cui di solito esco: il vecchio argomento (non più applicabile alla maggior parte dei compilatori) sul non includere stdlib.h e sull'uso di sizeof *p per assicurarsi che i tipi e le dimensioni corrispondano sempre indipendentemente dall'aggiornamento successivo. Voglio sottolineare un altro argomento contro il casting. È piccolo, ma penso che si applichi.

C è tipizzato in modo abbastanza debole. Le conversioni di tipo più sicure avvengono automaticamente e quelle più non sicure richiedono un cast. Prendere in considerazione:

int from_f(float f)
{
    return *(int *)&f;
}

Questo è un codice pericoloso. È un comportamento tecnicamente indefinito, anche se in pratica farà la stessa cosa su quasi tutte le piattaforme su cui lo si esegue. E il cast ti aiuta a dirti & Quot; Questo codice è un trucco terribile. & Quot;

Si consideri:

int *p = (int *)malloc(sizeof(int) * 10);

Vedo un cast e mi chiedo " Perché è necessario? Dov'è l'hack? & Quot; Solleva i peli sul mio collo che sta succedendo qualcosa di malvagio, quando in realtà il codice è completamente innocuo.

Finché stiamo usando C, i cast (in particolare i lanci dei puntatori) sono un modo per dire " C'è qualcosa di malvagio e facilmente frangibile in corso qui. " Potrebbero realizzare ciò di cui hai bisogno, ma indicano a te e ai futuri manutentori che i bambini non stanno bene.

L'uso dei cast su ogni malloc riduce il " hack " indicazione del casting del puntatore. Rende meno fastidioso vedere cose come *(int *)&f;.

Nota: C e C ++ sono lingue diverse. C è tipizzato debolmente, C ++ è tipizzato più fortemente. I cast sono necessari in C ++, anche se non indicano affatto un hack, a causa (a mio modesto parere) del sistema di tipo C ++ inutilmente forte. (Davvero, questo caso particolare è l'unico posto in cui penso che il sistema di tipo C ++ sia & Quot; troppo forte, & Quot; ma non riesco a pensare a nessun posto in cui è & Quot; troppo debole, < !> quot; che lo rende nel complesso troppo forte per i miei gusti.)

Se sei preoccupato per la compatibilità C ++, non farlo. Se stai scrivendo C, usa un compilatore C. Ce ne sono molti davvero disponibili per ogni piattaforma. Se, per qualche motivo insano, devi scrivere il codice C che si compila in modo pulito come C ++, non stai davvero scrivendo C. Se devi portare C in C ++, dovresti apportare molte modifiche per rendere il tuo codice C più idiomatico C ++.

Se non puoi fare nulla di tutto ciò, il tuo codice non sarà abbastanza, qualunque cosa tu faccia, quindi non importa come decidi di lanciare a quel punto. Mi piace l'idea di utilizzare i modelli per creare un nuovo allocatore che restituisca il tipo corretto, anche se fondamentalmente è solo reinventare la new parola chiave.

Lanciare una funzione che ritorna (void *) invece di essere (int *) è innocuo: stai lanciando un tipo di puntatore a un altro.

Il cast di una funzione che restituisce un numero intero invece di essere un puntatore è probabilmente errato. Il compilatore lo avrebbe contrassegnato se non l'avessi esplicitamente lanciato.

Un possibile errore (dipende da ciò che si desidera o meno) potrebbe essere il mallocing con una scala di dimensioni e l'assegnazione a un puntatore di tipo diverso. Per es.,

int *temp = (int *)malloc(sizeof(double));

Potrebbero esserci casi in cui vuoi farlo, ma sospetto che siano rari.

Penso che dovresti inserire il cast. Considera che ci sono tre posizioni per i tipi:

T1 *p;
p = (T2*) malloc(sizeof(T3));

Le due righe di codice potrebbero essere ampiamente separate. Pertanto è positivo che il compilatore imponi che T1 == T2. È più facile verificare visivamente che T2 == T3.

Se perdi il cast T2, allora devi sperare che T1 == T3.

D'altra parte hai l'argomento stdlib.h mancante, ma penso che sia meno probabile che sia un problema.

D'altra parte, se mai hai bisogno di portare il codice in C ++, è molto meglio usare il 'nuovo' operatore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top