In C, perché alcune persone lanciano il puntatore prima di liberarlo?


167

Sto lavorando su una vecchia base di codice e praticamente ogni invocazione di free () usa un cast sul suo argomento. Per esempio,

free((float *)velocity);
free((float *)acceleration);
free((char *)label);

dove ogni puntatore è del tipo corrispondente (e corrispondente). Non vedo assolutamente nulla nel farlo. È un codice molto vecchio, quindi mi chiedo se è una cosa di K&R. In tal caso, in realtà desidero supportare i vecchi compilatori che potrebbero averlo richiesto, quindi non desidero rimuoverli.

C'è un motivo tecnico per usare questi cast? Non vedo nemmeno un motivo pragmatico per usarli. Qual è il punto di ricordare a noi stessi il tipo di dati prima di liberarlo?

EDIT: Questa domanda è non un duplicato dell'altra domanda. L'altra domanda è un caso speciale di questa domanda, che penso sia ovvio se gli elettori vicini leggessero tutte le risposte.

Colophon: sto dando alla "const answer" il segno di spunta perché è una vera ragione autentica per cui potrebbe essere necessario farlo; tuttavia, la risposta sul fatto che è un'abitudine C pre-ANSI (almeno tra alcuni programmatori) sembra essere la ragione per cui è stata utilizzata nel mio caso. Molti punti positivi di molte persone qui. Grazie per i tuoi contributi.


13
"Qual è il punto di ricordare a noi stessi il tipo di dati prima di liberarlo?" Forse per sapere quanta memoria verrà liberata?
m0skit0,

12
@Codor Il compilatore non esegue la deallocazione, lo fa il sistema operativo.
m0skit0,

20
@ m0skit0 "Forse per sapere quanta memoria verrà liberata?" Digitare non è necessario per sapere quanto liberare. Cast per quel motivo è solo una cattiva codifica.
user694733

9
@ m0skit0 Il casting per motivi di leggibilità è sempre una codifica errata, poiché il casting modifica il modo in cui i tipi vengono interpretati e può nascondere errori gravi. Quando è necessaria la leggibilità, i commenti sono migliori.
user694733

66
Nei tempi antichi, quando i dinosauri camminavano sulla terra e scrivevano libri di programmazione, credo che non ci fosse void*nella C pre-standard, ma solo char*. Quindi, se i tuoi reperti archeologici rivelano il codice che lancia il parametro su free (), credo che debba essere di quel periodo o scritto da una creatura di quel momento. Non riesco a trovare alcuna fonte per questo, quindi mi trattengo dal rispondere.
Lundin,

Risposte:


171

Il cast può essere richiesto per risolvere gli avvisi del compilatore se i puntatori lo sono const. Ecco un esempio di codice che provoca un avviso senza trasmettere l'argomento di libero:

const float* velocity = malloc(2*sizeof(float));
free(velocity);

E il compilatore (gcc 4.8.3) dice:

main.c: In function main’:
main.c:9:5: warning: passing argument 1 of free discards const qualifier from pointer target type [enabled by default]
     free(velocity);
     ^
In file included from main.c:2:0:
/usr/include/stdlib.h:482:13: note: expected void *’ but argument is of type const float *’
 extern void free (void *__ptr) __THROW;

Se si utilizza free((float*) velocity);il compilatore si smette di lamentarsi.


2
@ m0skit0 che non spiega perché qualcuno dovrebbe lanciare float*prima di liberare. Ho provato free((void *)velocity);con gcc 4.8.3. Ovviamente non funzionerebbe con un antico compilatore
Manos Nikolaidis,

54
Ma perché dovresti assegnare in modo dinamico memoria costante? Non potresti mai usarlo!
Nils_M

33
@Nils_M è un esempio semplificato per evidenziare un punto. Quello che ho fatto nel codice effettivo in una funzione è allocare memoria non const, assegnare valori, trasmettere a un puntatore const e restituirlo. Ora, c'è un puntatore alla memoria const preassegnata che qualcuno deve liberare.
Manos Nikolaidis,

2
Esempio : “Queste subroutine restituiscono la stringa nella memoria appena malloc, indicata da * stringValueP, che alla fine devi liberare. A volte, la funzione del sistema operativo che usi per liberare memoria è dichiarata per prendere un puntatore a qualcosa di non costante come argomento, quindi perché * stringValueP è un puntatore a una const. "
Carsten S

3
Erronea, se una funzione prende const char *pcome argomento e poi lo libera, la cosa giusta da fare non è di lanciare pper char*prima di chiamare gratuitamente. Non è per dichiararlo const char *pin primo luogo, poiché modifica *p e deve essere dichiarato di conseguenza. (E se ci vuole un puntatore const anziché puntatore a const, int *const pnon è necessario eseguire il cast poiché è effettivamente legale e quindi funziona bene senza il cast.)
Ray

59

Il pre-standard C non aveva void*ma solo char*, quindi dovevi trasmettere tutti i parametri passati. Se ti imbatti nell'antico codice C, potresti quindi trovare tali cast.

Domanda simile con riferimenti .

Quando è stato rilasciato il primo standard C, i prototipi di malloc e free sono cambiati da char*quelli void*che hanno ancora oggi.

E naturalmente nello standard C, tali calchi sono superflui e danneggiano solo la leggibilità.


23
Ma perché dovresti trasmettere l'argomento allo freestesso tipo di quello che è già?
jwodder,

4
@chux Il problema con lo standard è proprio questo: non ci sono obblighi per nulla. La gente ha semplicemente indicato il libro di K&R per il canone perché era l'unica cosa che avevano. E come possiamo vedere da diversi esempi nella 2a edizione di K&R, K&R stessi sono confusi sul modo in cui i cast del parametro freefunzionano nello standard C (non è necessario eseguire il cast). Non ho letto la prima edizione, quindi non so dire se fossero confusi anche negli anni pre-standard degli anni '80.
Lundin,

7
Il C pre-standard non aveva void*, ma non aveva nemmeno prototipi di funzioni, quindi il casting dell'argomento non freeera ancora necessario anche in K&R (supponendo che tutti i tipi di puntatori di dati usassero la stessa rappresentazione).
Ian Abbott,

6
Per diversi motivi già indicati nei commenti, non credo che questa risposta abbia senso.
R .. GitHub FERMA AIUTANDO ICE

4
Non vedo come questa risposta risponderebbe davvero a qualcosa di rilevante. La domanda originale riguarda il cast di altri tipi, non solo char *. Che senso avrebbe senza i vecchi compilatori void? Cosa otterrebbero tali cast?
AnT

34

Ecco un esempio in cui il free fallirebbe senza un cast:

volatile int* p = (volatile int*)malloc(5 * sizeof(int));
free(p);        // fail: warning C4090: 'function' : different 'volatile' qualifiers
free((int*)p);  // success :)
free((void*)p); // success :)

In C puoi ricevere un avviso (ne hai preso uno in VS2012). In C ++ otterrai un errore.

Casi rari a parte, il casting gonfia semplicemente il codice ...

Modifica: ho scelto di void*non int*dimostrare l'errore. Funzionerà come int*sarà convertito in modo void*implicito. int*Codice aggiunto


Si noti che nel codice pubblicato nella domanda, i cast non sono per void *, ma per float *e char *. Quei cast non sono solo estranei, ma si sbagliano.
Andrew Henle,

1
La domanda riguarda in realtà il contrario.
m0skit0,

1
Non capisco la risposta; in che senso free(p)fallirebbe? Darebbe un errore del compilatore?
Codor

1
Questi sono buoni punti. Lo stesso vale perconst puntatori di qualificazione, ovviamente.
Lundin,

2
volatileesiste da quando C è stato standardizzato se non più a lungo. E 'stato , non aggiunta in C99.
R .. GitHub FERMA AIUTARE ICE

30

Vecchio motivo: 1. Usando free((sometype*) ptr), il codice è esplicito sul tipo che il puntatore dovrebbe essere considerato come parte della free()chiamata. Il cast esplicito è utile quando free()viene sostituito con un (fai-da-te) DIY_free().

#define free(ptr) DIY_free(ptr, sizeof (*ptr))

Un DIY_free()era (è) un modo, specialmente in modalità debug, per eseguire analisi di runtime del puntatore liberato. Questo è spesso associato a un valore DIY_malloc()aggiunto per aggiungere le credenziali, i conteggi dell'utilizzo della memoria globale, ecc. Il mio gruppo ha usato questa tecnica per anni prima che apparissero strumenti più moderni. Obbligava che l'oggetto da liberare fosse lanciato sul tipo che era originariamente assegnato.

  1. Date le molte ore trascorse a rintracciare i problemi di memoria, ecc., Piccoli trucchi come la liberazione del tipo potrebbero aiutare nella ricerca e nel restringere il debug.

Moderno: evitare conste volatileavvertimenti come affrontato da Manos Nikolaidis @ e @egur . Il pensiero Vorrei far notare gli effetti delle 3 qualificazioni : const, volatile, e restrict.

[modifica] Aggiunto char * restrict *rp2per commento @R ..

void free_test(const char *cp, volatile char *vp, char * restrict rp, 
    char * restrict *rp2) {
  free(cp);  // warning
  free(vp);  // warning
  free(rp);  // OK
  free(rp2);  // warning
}

int main(void) {
  free_test(0,0,0,0);
  return 0;
}

3
restrictè solo un problema a causa della posizione in cui si trova - influisce sull'oggetto e rpnon sul tipo puntato. Se invece lo avessi fatto char *restrict *rp, allora importerebbe.
R .. GitHub FERMA AIUTANDO ICE

16

Ecco un'altra ipotesi alternativa.

Ci viene detto che il programma è stato scritto prima del C89, il che significa che non può aggirare una sorta di mancata corrispondenza con il prototipo di free, perché non solo non esisteva nulla di simile consto void *precedente a C89, non esisteva un prototipo di funzione precedente a C89. stdlib.hstessa era un'invenzione del comitato. Se le intestazioni di sistema freesi fossero preoccupate di dichiarare , lo avrebbero fatto in questo modo:

extern free();  /* no `void` return type either! */

Ora, il punto chiave qui è che l'assenza di prototipi di funzioni significa che il compilatore non ha verificato il tipo di argomento . Ha applicato le promozioni degli argomenti predefiniti (le stesse che si applicano ancora alle chiamate di funzioni variadiche) e basta. La responsabilità di fare in modo che gli argomenti di ciascun call centre si allineino con le aspettative della persona chiamata interamente a carico del programmatore.

Tuttavia, ciò non significa ancora che fosse necessario lanciare l'argomento freesulla maggior parte dei compilatori K&R. Una funzione come

free_stuff(a, b, c)
    float *a;
    char *b;
    int *c;
{
    free(a);
    free(b);
    free(c);
}

avrebbe dovuto essere compilato correttamente. Quindi penso che quello che abbiamo qui è un programma scritto per far fronte a un compilatore buggy per un ambiente insolito: per esempio, un ambiente in cui sizeof(float *) > sizeof(int)e il compilatore non userebbe la convenzione di chiamata appropriata per i puntatori a meno che tu non li lanci nel punto della chiamata.

Non sono a conoscenza di un tale ambiente, ma ciò non significa che non ce ne fosse uno. I candidati più probabili che vengono in mente sono i compilatori "minuscoli C" ridotti per micro a 8 e 16 bit nei primi anni '80. Inoltre non sarei sorpreso di apprendere che i primi Crays avevano problemi come questo.


1
Il primo semestre sono pienamente d'accordo. E la seconda metà è una congettura intrigante e plausibile.
chux - Ripristina Monica il

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.