Nello specifico, cosa c'è di pericoloso nel lanciare il risultato di malloc?


86

Ora, prima che le persone inizino a contrassegnarlo come un duplice, ho letto tutto quanto segue, nessuno dei quali fornisce la risposta che sto cercando:

  1. C FAQ: Cosa c'è di sbagliato nel lanciare il valore di ritorno di malloc?
  2. COSÌ: Devo lanciare esplicitamente il valore di ritorno di malloc ()?
  3. COSÌ: inutili lanci di puntatori in C
  4. SO: lancio il risultato di malloc?

Sia la C FAQ che molte risposte alle domande precedenti citano un misterioso errore che mallocil valore di ritorno del casting può nascondere; tuttavia, nessuno di essi fornisce un esempio specifico di tale errore nella pratica. Ora fai attenzione che ho detto errore , non avviso .

Ora dato il seguente codice:

#include <string.h>
#include <stdio.h>
// #include <stdlib.h>

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

La compilazione del codice precedente con gcc 4.2, con e senza il cast, fornisce gli stessi avvertimenti e il programma viene eseguito correttamente e fornisce gli stessi risultati in entrambi i casi.

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

Quindi chiunque può fornire un esempio di codice specifico di un errore di compilazione o di runtime che potrebbe verificarsi a causa del mallocvalore di ritorno del casting , o è solo una leggenda metropolitana?

Modifica Mi sono imbattuto in due argomenti ben scritti riguardo a questo problema:

  1. A favore del cast: Avviso CERT: cast immediatamente il risultato di una chiamata alla funzione di allocazione della memoria in un puntatore al tipo allocato
  2. Contro la trasmissione (errore 404 a partire dal 14/02/2012: utilizza la copia Internet Archive Wayback Machine del 27/01/2010. {18/03/2016: "Impossibile eseguire la scansione o la visualizzazione della pagina a causa di robots.txt."})

6
casting voidpointers permette di compilare il codice come C ++; alcune persone dicono che è una caratteristica, direi che è un bug;)
Christoph

1
inoltre, leggi i commenti al primo dei tuoi link in quanto descrive cosa dovresti fare invece di trasmettere
Christoph

3
Seguirò i consigli del CERT per includere il cast. Inoltre, non dimenticherò mai di includere stdlib.h. :)
Abhinav

1
Di seguito è riportato un esempio SO di un errore di runtime di compilazione a causa del mallocvalore restituito del casting: casting su int*su un'arch a 64 bit.
John_West

1
questa domanda è contrassegnata come Cno C++(sono due lingue diverse) Quindi qualsiasi discussione (come in alcune risposte) non è pertinente a questa domanda.
user3629249

Risposte:


66

Non riceverai un errore del compilatore , ma un avviso del compilatore . Come dicono le fonti che citi (specialmente la prima ), puoi ottenere un errore di runtime imprevedibile quando usi il cast senza includerestdlib.h .

Quindi l'errore dalla tua parte non è il cast, ma dimenticare di includere stdlib.h. I compilatori possono presumere che mallocsia una funzione che restituisce int, quindi converte il void*puntatore effettivamente restituito da malloca inte quindi al tipo di puntatore a causa del cast esplicito. Su alcune piattaforme, i intpuntatori possono richiedere un numero diverso di byte, quindi le conversioni di tipo possono causare il danneggiamento dei dati.

Fortunatamente, i compilatori moderni forniscono avvisi che indicano il tuo errore effettivo. Guarda l' gccoutput che hai fornito: Ti avverte che la dichiarazione implicita ( int malloc(int)) è incompatibile con il built-in malloc. Quindi gccsembra sapere mallocanche senza stdlib.h.

Tralasciare il cast per evitare questo errore è per lo più lo stesso ragionamento della scrittura

if (0 == my_var)

invece di

if (my_var == 0)

poiché il secondo potrebbe portare a un grave bug se si confondesse =e ==, mentre il primo porterebbe ad un errore di compilazione. Personalmente preferisco quest'ultimo stile poiché riflette meglio le mie intenzioni e non tendo a fare questo errore.

Lo stesso vale per il casting del valore restituito da malloc: preferisco essere esplicito nella programmazione e generalmente controllo due volte per includere i file di intestazione per tutte le funzioni che uso.


2
Sembrerebbe che poiché il compilatore avverte della dichiarazione implicita incompatibile, allora questo non è un problema fintanto che presti attenzione agli avvisi del compilatore.
Robert S. Barnes,

4
@ Robert: sì, date alcune ipotesi sul compilatore. Quando le persone danno consigli su come scrivere al meglio C in generale , non possono presumere che la persona che riceve il consiglio stia usando una versione recente di gcc.
Steve Jessop,

4
Oh, e la risposta alla seconda domanda è che il chiamante contiene il codice per raccogliere il valore di ritorno (che pensa sia un int) e convertirlo in T *. Il chiamato scrive semplicemente il valore restituito (come void *) e restituisce. Quindi, a seconda della convenzione di chiamata: int ritorni e void * ritorni possono o meno essere nello "stesso posto" (registro o stack slot); int e void * possono o non possono avere la stessa dimensione; la conversione tra i due può o non può essere un no-op. Quindi potrebbe "funzionare", o il valore potrebbe essere danneggiato (alcuni bit persi, forse), o il chiamante potrebbe prendere completamente il valore sbagliato.
Steve Jessop,

1
@ RobertS.Barnes in ritardo alla festa, ma: il valore restituito generalmente non fa parte della firma della funzione, nemmeno in C ++. Il linker genera solo un salto a un simbolo, tutto qui.
Peter - Ripristina Monica

3
È possibile ottenere un errore di runtime imprevedibile quando si utilizza il cast senza includere stdlib.h . È vero, ma non includere stdlib.hè già di per sé un errore, anche se si ottengono solo avvisi di "dichiarazione implicita".
Jabberwocky

45

Uno dei buoni argomenti di livello superiore contro il casting del risultato di mallocviene spesso lasciato in sospeso, anche se, a mio parere, è più importante dei ben noti problemi di livello inferiore (come troncare il puntatore quando manca la dichiarazione).

Una buona pratica di programmazione consiste nello scrivere codice, che sia il più indipendente possibile dal tipo. Ciò significa, in particolare, che i nomi dei tipi dovrebbero essere menzionati nel codice il meno possibile o meglio non menzionati affatto. Questo vale per i cast (evitare cast non necessari), i tipi come argomenti di sizeof(evitare di usare i nomi dei tipi in sizeof) e, generalmente, tutti gli altri riferimenti ai nomi dei tipi.

I nomi dei tipi appartengono alle dichiarazioni. Per quanto possibile, i nomi dei tipi dovrebbero essere limitati alle dichiarazioni e solo alle dichiarazioni.

Da questo punto di vista, questo bit di codice è cattivo

int *p;
...
p = (int*) malloc(n * sizeof(int));

e questo è molto meglio

int *p;
...
p = malloc(n * sizeof *p);

non semplicemente perché "non esegue il cast del risultato di malloc", ma piuttosto perché è indipendente dal tipo (o indipendente dal tipo, se preferisci), perché si adatta automaticamente a qualunque tipo psia dichiarato con, senza richiedere alcun intervento da l'utente.


Fwiw, penso che questo sia più o meno lo stesso motivo di questo: stackoverflow.com/questions/953112/… ma focalizzato sull'indipendenza dal tipo piuttosto che sul fai-da-te. Ovviamente il primo segue dal secondo (o viceversa), quindi almeno a volte viene menzionato . :)
rilassati il

5
@unwind molto probabilmente intendi SECCO piuttosto che fai
kratenko

18

Si presume che le funzioni non prototipate restituiscano int.

Quindi stai lanciando un inta un puntatore. Se i puntatori sono più larghi delle ints sulla tua piattaforma, questo è un comportamento altamente rischioso.

Inoltre, ovviamente, alcune persone considerano gli avvisi come errori, cioè il codice dovrebbe essere compilato senza di essi.

Personalmente, penso che il fatto che non sia necessario eseguire il cast void *su un altro tipo di puntatore è una funzionalità di C e considera il codice che non funziona.


14
Ho questa convinzione che il compilatore sappia di più sulla lingua di me, quindi se mi avverte di qualcosa, faccio attenzione.
György Andrasek

3
In molti progetti, il codice C viene compilato come C ++ dove è necessario eseguire il cast del file void*.
laalto

nit: " per impostazione predefinita , si presume che le funzioni non prototipate restituiscano int." - Vuoi dire che è possibile cambiare il tipo di ritorno di funzioni non prototipate?
pmg

1
@laalto - Lo è, ma non dovrebbe essere. C è C, non C ++ e dovrebbe essere compilato con un compilatore C, non un compilatore C ++. Non ci sono scuse: GCC (uno dei migliori compilatori C in circolazione) funziona su quasi tutte le piattaforme immaginabili (e genera anche codice altamente ottimizzato). Quali ragioni potresti avere per compilare C con un compilatore C ++, oltre alla pigrizia e agli standard sciolti?
Chris Lutz

3
Esempio di codice che si potrebbe desiderare di compilazione sia come C e C ++: #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. Ora, perché dovresti chiamare malloc in una funzione inline statica non lo so davvero, ma le intestazioni che funzionano in entrambi sono difficilmente inaudite.
Steve Jessop

11

Se si esegue questa operazione durante la compilazione in modalità a 64 bit, il puntatore restituito verrà troncato a 32 bit.

EDIT: Ci scusiamo per essere troppo breve. Ecco un esempio di frammento di codice a scopo di discussione.

principale()
{
   char * c = (char *) malloc (2);
   printf ("% p", c);
}

Supponiamo che il puntatore dell'heap restituito sia qualcosa di più grande di ciò che è rappresentabile in un int, ad esempio 0xAB00000000.

Se malloc non è prototipato per restituire un puntatore, il valore int restituito sarà inizialmente in un registro con tutti i bit significativi impostati. Ora il compilatore dice "ok, come faccio a convertire e int in un puntatore". Sarà un'estensione del segno o un'estensione zero dei 32 bit di ordine basso a cui è stato detto che malloc "ritorna" omettendo il prototipo. Poiché int è firmato, penso che la conversione sarà l'estensione del segno, che in questo caso convertirà il valore a zero. Con un valore di ritorno di 0xABF0000000 otterrai un puntatore diverso da zero che causerà anche un po 'di divertimento quando proverai a dereferenziarlo.


1
Potresti spiegare in dettaglio come sarebbe accaduto?
Robert S. Barnes,

5
Penso che Peeter Joot avesse capito che "Per impostazione predefinita, si presume che le funzioni non prototipate restituiscano int" senza includere stdlib.he sizeof (int) è 32 bit mentre sizeof (ptr) è 64.
Test

4

Una regola del software riutilizzabile:

Nel caso di scrittura di una funzione inline in cui si usa malloc (), per renderla riutilizzabile anche per codice C ++, si prega di fare un cast di tipo esplicito (eg (char *)); altrimenti il ​​compilatore si lamenterà.


si spera, con la (recente) inclusione di ottimizzazioni del tempo di collegamento in gcc (vedere gcc.gnu.org/ml/gcc/2009-10/msg00060.html ), la dichiarazione di funzioni inline nei file di intestazione non sarà più necessaria
Christoph

hai cattive idee. lo sai cos'è portabile e multipiattaforma tra diversi compilatori / versioni / architetture? ok, potresti non farlo. allora cosa significa riutilizzabile?
Test

2
quando si scrive C ++, malloc / free NON è la metodologia giusta. Piuttosto usa nuovo / cancella. IE non dovrebbero esserci chiamate / nada / zero a malloc / free nel codice C ++
user3629249

3
@ user3629249: Quando crei una funzione che deve essere utilizzabile dall'interno sia codice C o codice C ++, usando malloc/ freeper entrambi è atto ad essere meglio cercare di utilizzare mallocin C e newin C ++, specialmente se le strutture di dati sono condivisi tra C e C ++ codice e c'è la possibilità che un oggetto possa essere creato in codice C e rilasciato in codice C ++ o viceversa.
supercat

3

Un puntatore void in C può essere assegnato a qualsiasi puntatore senza un cast esplicito. Il compilatore fornirà un avviso ma può essere riutilizzato in C ++ mediante il casting del tipo malloc()al tipo corrispondente. Senza il casting del tipo può essere utilizzato anche in C , perché C non è un controllo del tipo rigoroso . Ma C ++ è rigorosamente il controllo del tipo, quindi è necessario digitare cast malloc()in C ++.


Se usi malloc in C ++ è meglio che tu abbia una dannata buona ragione! ; p
antred
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.