libreria - memoria c




Do il risultato di malloc? (18)

No, non si esegue il cast di risultati malloc().

In generale, nonvoid * si esegue il cast da o verso .

Un tipico motivo addotto per non farlo è che il fallimento #include <stdlib.h>potrebbe passare inosservato. Questo non è più un problema da molto tempo poiché C99 ha reso dichiarazioni di funzioni implicite illegali, quindi se il compilatore è conforme almeno al C99, riceverai un messaggio di diagnostica.

Ma c'è una ragione molto più forte per non introdurre cast di puntatori inutili:

In C, un cast puntatore è quasi sempre un errore . Ciò è dovuto alla seguente regola ( §6.5 p7 in N1570, l'ultima bozza per C11):

Un oggetto deve avere il suo valore memorizzato accessibile solo da un'espressione lvalue che ha uno dei seguenti tipi:
- un tipo compatibile con il tipo effettivo dell'oggetto,
- una versione qualificata di un tipo compatibile con il tipo effettivo dell'oggetto,
- un tipo che è il tipo firmato o senza segno corrispondente al tipo effettivo dell'oggetto,
- un tipo che è il tipo firmato o senza segno corrispondente a una versione qualificata del tipo effettivo dell'oggetto,
- un tipo di aggregato o unione che include uno dei tipi sopra menzionati tra i suoi membri (incluso, in modo ricorsivo, un membro di un'unione subaggregata o contenuta), o
- un tipo di carattere.

Questa è anche nota come regola di aliasing rigorosa . Quindi il seguente codice è un comportamento non definito :

long x = 5;
double *p = (double *)&x;
double y = *p;

E, a volte sorprendentemente, è anche il seguente:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

A volte, si fa necessario lanciare puntatori, ma data la rigida regola di aliasing , bisogna stare molto attenti con esso. Quindi, qualsiasi occorrenza di un puntatore lanciato nel tuo codice è un luogo che devi ricontrollare per la sua validità . Pertanto, non si scrive mai un cast puntatore non necessario.

tl; dr

In poche parole: poiché in C, qualsiasi occorrenza di un cast di puntatori dovrebbe sollevare una bandiera rossa per il codice che richiede un'attenzione speciale, non si dovrebbero mai scrivere cast di puntatori inutili .

Note a margine:

  • Ci sono casi in cui hai effettivamente bisogno di un cast void *, ad esempio se vuoi stampare un puntatore:

    int x = 5;
    printf("%p\n", (void *)&x);
    

    Il cast è necessario qui, perché printf()è una funzione variadica, quindi le conversioni implicite non funzionano.

  • In C ++, la situazione è diversa. I tipi di puntatori del cast sono in qualche modo comuni (e corretti) quando si gestiscono oggetti di classi derivate. Pertanto, ha senso che in C ++, la conversione da e verso nonvoid * è implicita. Il C ++ ha un intero set di differenti gusti di casting.

In questa domanda , qualcuno ha suggerito in un comment che non avrei dovuto esprimere il risultato di malloc , ad es

int *sieve = malloc(sizeof(int) * length);

piuttosto che:

int *sieve = (int *) malloc(sizeof(int) * length);

Perché dovrebbe essere così?


  1. Come altri affermato, non è necessario per C, ma per C ++.

  2. Includere il cast potrebbe consentire a un programma o una funzione C di compilare come C ++.

  3. In C non è necessario, poiché void * viene automaticamente e tranquillamente promosso a qualsiasi altro tipo di puntatore.

  4. Ma se lanci allora, può nascondere un errore se hai dimenticato di includere stdlib.h . Ciò può causare arresti anomali (o, peggio, non causare un arresto anomalo fino a un momento successivo in una parte completamente diversa del codice).

    Perché stdlib.h contiene il prototipo per malloc trovato. In assenza di un prototipo per malloc, lo standard richiede che il compilatore C presuma che malloc restituisca un int. Se non c'è cast, viene emesso un avviso quando questo intero è assegnato al puntatore; tuttavia, con il cast, questo avviso non viene prodotto, nascondendo un bug.


Come altri affermato, non è necessario per C, ma per C ++. Se pensi di compilare il tuo codice C con un compilatore C ++, per quale motivo, puoi usare una macro invece, come:

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

In questo modo puoi ancora scriverlo in un modo molto compatto:

int *sieve = NEW(int, 1);

e verrà compilato per C e C ++.


Da Wikipedia

Vantaggi del casting

  • Includere il cast potrebbe consentire a un programma o una funzione C di compilare come C ++.

  • Il cast consente versioni di malloc precedenti al 1989 che originariamente restituivano un char *.

  • Il cast può aiutare lo sviluppatore a identificare incongruenze nel dimensionamento del tipo in caso di cambiamento del tipo di puntatore di destinazione, in particolare se il puntatore è dichiarato lontano dalla chiamata malloc (), anche se i moderni compilatori e analizzatori statici possono avvisare su tale comportamento senza richiedere il cast.

Svantaggi del casting

  • Sotto lo standard ANSI C, il cast è ridondante.

  • L'aggiunta del cast può mascherare l'errore di includere l'intestazione stdlib.h , in cui viene trovato il prototipo per malloc. In assenza di un prototipo per malloc, lo standard richiede che il compilatore C presuma che malloc restituisca un int. Se non c'è cast, viene emesso un avviso quando questo intero è assegnato al puntatore; tuttavia, con il cast, questo avviso non viene prodotto, nascondendo un bug. Su alcune architetture e modelli di dati (come LP64 su sistemi a 64 bit, dove lunghi e puntatori sono 64-bit e int è a 32-bit), questo errore può effettivamente comportare un comportamento indefinito, poiché il malloc implicitamente dichiarato restituisce un 32- valore di bit mentre la funzione effettivamente definita restituisce un valore a 64 bit. A seconda delle convenzioni di chiamata e del layout di memoria, ciò potrebbe causare problemi di stacking. È meno probabile che questo problema passi inosservato nei moderni compilatori, poiché generano in modo uniforme avvertimenti che è stata utilizzata una funzione non dichiarata, pertanto verrà visualizzato un avviso. Ad esempio, il comportamento predefinito di GCC è di mostrare un avviso che legge "dichiarazione implicita incompatibile della funzione incorporata" indipendentemente dal fatto che il cast sia presente o meno.

  • Se il tipo del puntatore viene modificato nella sua dichiarazione, è anche possibile che sia necessario modificare tutte le righe in cui viene chiamato e lanciato cast di malloc.

Sebbene malloc senza casting sia il metodo preferito e la maggior parte dei programmatori esperti lo scelga , dovresti usare quello che preferisci avere a conoscenza dei problemi.

es .: Se hai bisogno di compilare il programma C come C ++ (anche se sono lingue separate) dovresti usare malloc con il cast.


Il tipo restituito è void *, che può essere convertito nel tipo desiderato di puntatore dati per poter essere dereferenziabile.


In C è possibile convertire implicitamente un puntatore void in qualsiasi altro tipo di puntatore, quindi un cast non è necessario. Usarne uno può suggerire all'osservatore casuale che ci sono alcune ragioni per cui è necessario, il che può essere fuorviante.


In C, non è necessario eseguire il cast del valore di ritorno di malloc . Il puntatore a void restituito da malloc viene convertito automagicamente nel tipo corretto. Tuttavia, se vuoi che il tuo codice venga compilato con un compilatore C ++, è necessario un cast. Un'alternativa preferita tra le comunità è di utilizzare quanto segue:

int *sieve = malloc(sizeof *sieve * length);

che ti libera inoltre dal doverti preoccupare di cambiare il lato destro dell'espressione se cambi mai il tipo di sieve .

I cast sono cattivi, come hanno sottolineato le persone. Puntatori appositamente puntati.


Inserisco il cast semplicemente per mostrare la disapprovazione del brutto buco nel sistema di tipi, che consente al codice come il seguente frammento di compilare senza diagnostica, anche se non vengono utilizzati cast per provocare la conversione errata:

double d;
void *p = &d;
int *q = p;

Vorrei che non esistesse (e non in C ++) e così ho lanciato. Rappresenta il mio gusto e la mia politica di programmazione. Non sto solo lanciando un puntatore, ma effettivamente, lanciando una scheda e scacciando demoni di stupidità . Se non riesco a scacciare la stupidità , almeno lasciami esprimere il desiderio di farlo con un gesto di protesta.

In effetti, una buona pratica è quella di avvolgere malloc (e amici) con funzioni che restituiscono unsigned char * , e in pratica non utilizzare mai void * nel codice. Se hai bisogno di un puntatore generico a qualsiasi oggetto, usa un char * o un char * unsigned char * e hai cast in entrambe le direzioni. L'unico rilassamento che può essere indulgere, forse, sta usando funzioni come memset e memcpy senza cast.

Per quanto riguarda il cast e la compatibilità con C ++, se scrivi il tuo codice in modo che compili sia come C che come C ++ (nel qual caso devi trasmettere il valore restituito di malloc quando lo assegni a qualcosa di diverso da void * ), puoi farlo a very helpful thing for yourself: you can use macros for casting which translate to C++ style casts when compiling as C++, but reduce to a C cast when compiling as C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

Se aderisci a queste macro, una semplice grepricerca della tua base di codice per questi identificatori ti mostrerà dove sono tutti i tuoi cast, quindi puoi verificare se alcuni di essi sono errati.

Quindi, andando avanti, se si compila regolarmente il codice con C ++, si imporrà l'uso di un cast appropriato. Ad esempio, se si utilizza strip_qualsolo per rimuovere un consto volatile, ma il programma cambia in modo tale da coinvolgere una conversione di tipo, si otterrà una diagnostica e sarà necessario utilizzare una combinazione di cast per ottenere la conversione desiderata.

Per aiutarvi ad aderire a queste macro, il compilatore GNU C ++ (non C!) Ha una bella funzione: una diagnostica opzionale che viene prodotta per tutte le occorrenze dei cast di stile C.

     -Wold-style-cast (C++ and Objective-C++ only)
         Warn if an old-style (C-style) cast to a non-void type is used
         within a C++ program.  The new-style casts (dynamic_cast,
         static_cast, reinterpret_cast, and const_cast) are less vulnerable
         to unintended effects and much easier to search for.

Se il tuo codice C viene compilato come C ++, puoi usare questa -Wold-style-castopzione per scoprire tutte le occorrenze della (type)sintassi di casting che possono insinuarsi nel codice e seguire questi test diagnostici sostituendolo con un'opportuna scelta tra le macro sopra (o una combinazione, se necessario).

Questo trattamento delle conversioni è la sola giustificazione tecnica autonoma per lavorare in una "C pulita": il dialetto combinato C e C ++, che a sua volta giustifica tecnicamente la trasmissione del valore di ritorno di malloc.


Le persone abituate a GCC e Clang sono viziate. Non è poi così bello là fuori.

Sono stato piuttosto orripilato nel corso degli anni dai compilatori invecchiati in modo sconcertante che mi è stato richiesto di usare. Spesso aziende e manager adottano un approccio ultra-conservatore per cambiare i compilatori e non testeranno nemmeno se un nuovo compilatore (con una migliore conformità agli standard e l'ottimizzazione del codice) funzionerà nel loro sistema. La realtà pratica per gli sviluppatori che lavorano è che quando si sta codificando è necessario coprire le basi e, sfortunatamente, lanciare mallocs è una buona abitudine se non si riesce a controllare quale compilatore può essere applicato al codice.

Suggerirei inoltre che molte organizzazioni applichino uno standard di codifica proprio e che quello dovrebbe essere il metodo seguito dalle persone se viene definito. In assenza di una guida esplicita, io tendo ad andare per la maggior parte delle probabilità di compilare ovunque, piuttosto che seguire pedissequamente uno standard.

L'argomento secondo cui non è necessario secondo gli standard attuali è abbastanza valido. Ma questa argomentazione omette gli aspetti pratici del mondo reale. Non codifichiamo in un mondo governato esclusivamente dallo standard del giorno, ma dagli aspetti pratici di ciò che mi piace definire "il campo della realtà della gestione locale". E questo è piegato e distorto più di quanto lo spazio non sia mai stato. :-)

YMMV.

Tendo a pensare di lanciare malloc come operazione difensiva. Non bello, non perfetto, ma generalmente sicuro. (Onestamente, se non hai incluso stdlib.h, hai molti più problemi che lanciare malloc!).


Nel linguaggio C, un puntatore vuoto può essere assegnato a qualsiasi puntatore, motivo per cui non si dovrebbe usare un cast di tipo. Se si desidera allocare "sicuro", posso raccomandare le seguenti macro funzioni, che utilizzo sempre nei miei progetti C:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

Con questi in posizione puoi semplicemente dire

NEW_ARRAY(sieve, length);

Per gli array non dinamici, la terza funzione macro deve avere

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

che rende i loop array più sicuri e più convenienti:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

Non lanci il risultato di malloc, perché così facendo aggiungi clutter inutili al tuo codice.

Il motivo più comune per cui la gente lancia il risultato di malloc è perché non sono sicuri di come funziona il linguaggio C. Questo è un segnale di avvertimento: se non sai come funziona un particolare meccanismo linguistico, non indovinare. Cercalo o chiedi su .

Alcuni commenti:

  • Un puntatore vuoto può essere convertito in / da qualsiasi altro tipo di puntatore senza un cast esplicito (C11 6.3.2.3 e 6.5.16.1).

  • Tuttavia, C ++ non consentirà un cast implicito tra void* e un altro tipo di puntatore. Quindi in C ++, il cast sarebbe stato corretto. Ma se programmi in C ++, dovresti usare new e non malloc (). E non dovresti mai compilare il codice C usando un compilatore C ++.

    Se è necessario supportare sia C che C ++ con lo stesso codice sorgente, utilizzare le opzioni del compilatore per contrassegnare le differenze. Non tentare di saziare entrambi gli standard di lingua con lo stesso codice, perché non sono compatibili.

  • Se un compilatore C non riesce a trovare una funzione perché hai dimenticato di includere l'intestazione, otterrai un errore del compilatore / linker. Quindi se hai dimenticato di includere <stdlib.h> non è un problema, non sarai in grado di creare il tuo programma.

  • Su antichi compilatori che seguono una versione dello standard che ha più di 25 anni, dimenticando di includere <stdlib.h> otterrebbe un comportamento pericoloso. Perché in quello standard antico, le funzioni senza un prototipo visibile convertivano implicitamente il tipo restituito in int . Lanciare esplicitamente il risultato di malloc nasconderebbe questo bug.

    Ma questo è davvero un non-problema. Non stai usando un computer di 25 anni, quindi perché dovresti usare un compilatore di 25 anni?


Questo è ciò che dice il manuale di riferimento della libreria GNU C :

È possibile memorizzare il risultato di malloc in qualsiasi variabile puntatore senza cast, poiché ISO C converte automaticamente il tipo void * in un altro tipo di puntatore quando necessario. Ma il cast è necessario in contesti diversi dagli operatori di assegnamento o se desideri che il tuo codice venga eseguito nella tradizionale C.

E infatti lo standard ISO C11 (p347) lo dice:

Il puntatore restituito se l'allocazione ha esito positivo è opportunamente allineato in modo che possa essere assegnato a un puntatore a qualsiasi tipo di oggetto con un requisito di allineamento fondamentale e quindi utilizzato per accedere a tale oggetto o una matrice di tali oggetti nello spazio allocato (fino al lo spazio è esplicitamente deallocato)


Fai il cast perché:

  • Rende il tuo codice più portabile tra C e C ++, e come mostra l'esperienza SO, molti programmatori sostengono di scrivere in C quando stanno davvero scrivendo in C ++ (o C oltre alle estensioni del compilatore locale).
  • Non riuscendo a farlo può nascondere un errore : nota tutti gli esempi SO di confusione quando scrivere type * contro type ** .
  • L'idea che ti impedisce di notare che hai fallito #include un file di intestazione appropriato, manca la foresta per gli alberi . È come dire "non ti preoccupare del fatto che non hai chiesto al compilatore di lamentarsi del fatto di non vedere i prototipi - che fastidioso stdlib.h è la vera cosa importante da ricordare!"
  • Costringe un ulteriore controllo incrociato cognitivo . Mette il (presunto) tipo desiderato proprio accanto all'aritmetica che stai facendo per la dimensione grezza di quella variabile. Scommetto che potresti fare uno studio SO che mostra che i bug di malloc() vengono catturati molto più velocemente quando c'è un cast. Come per le asserzioni, le annotazioni che rivelano l'intenzione diminuiscono i bug.
  • Ripetersi in un modo che la macchina può controllare è spesso una grande idea. In realtà, questo è ciò che è un'affermazione, e questo uso del cast è un'asserzione. Le asserzioni sono ancora la tecnica più generale che abbiamo per ottenere il codice corretto, dal momento che Turing ha avuto l'idea di tanti anni fa.

La fusione di malloc non è necessaria in C ma è obbligatoria in C ++.

La trasmissione non è necessaria in C a causa di:

  • void * viene automaticamente e tranquillamente promosso a qualsiasi altro tipo di puntatore nel caso di C.
  • Può nascondere un errore se hai dimenticato di includere <stdlib.h>. Questo può causare arresti anomali.
  • Se i puntatori e gli interi sono di dimensioni diverse, stai nascondendo un avvertimento mediante il cast e potresti perdere dei bit dell'indirizzo restituito.
  • Se il tipo del puntatore viene modificato nella sua dichiarazione, potrebbe anche essere necessario modificare tutte le linee in cui mallocè chiamato e lanciato.

D'altra parte, il casting può aumentare la portabilità del tuo programma. cioè, consente a un programma o una funzione C di compilare come C ++.


Il cast è solo per C ++ non C. Nel caso in cui si usi un compilatore C ++ è meglio cambiarlo in C compilatore.


La cosa migliore da fare quando si programma in C ogni volta che è possibile:

  1. Crea il tuo programma compilato tramite un compilatore C con tutti gli avvisi attivati -Walle correggi tutti gli errori e gli avvertimenti
  2. Assicurarsi che non ci siano variabili dichiarate come auto
  3. Quindi compilarlo usando un compilatore C ++ con -Walle -std=c++11. Correggi tutti gli errori e gli avvertimenti.
  4. Ora compila di nuovo usando il compilatore C. Il tuo programma dovrebbe ora compilare senza alcun avviso e contenere meno bug.

Questa procedura consente di trarre vantaggio dal controllo rigoroso del tipo C ++, riducendo così il numero di errori. In particolare, questa procedura ti obbliga a includere stdlib.ho otterrai

malloc non è stato dichiarato all'interno di questo ambito

e ti costringe anche a lanciare il risultato malloco otterrai

conversione non valida da void*aT*

o qualunque sia il tuo tipo di bersaglio.

Gli unici benefici derivanti dalla scrittura in C anziché in C ++ che posso trovare sono

  1. C ha un ABI ben specificato
  2. C ++ può generare più codice [eccezioni, RTTI, modelli, polimorfismo di runtime ]

Si noti che i secondi svantaggi dovrebbero nel caso ideale scomparire quando si utilizza il sottoinsieme comune a C insieme con la caratteristica polimorfica statica .

Per coloro che trovano le regole rigide del C ++ inopportune, possiamo usare la funzione C ++ 11 con il tipo dedotto

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...

Il concetto dietro il puntatore void è che può essere castato a qualsiasi tipo di dati, ecco perché malloc restituisce void. Inoltre, devi essere consapevole del typecasting automatico. Quindi non è obbligatorio lanciare il puntatore anche se devi farlo. Aiuta a mantenere il codice pulito e aiuta a fare il debug


Preferisco fare il cast, ma non manualmente. Il mio preferito è l'utilizzo g_newe le g_new0macro di glib. Se non si usa glib, aggiungerei macro simili. Queste macro riducono la duplicazione del codice senza compromettere la sicurezza del tipo. Se si ottiene il tipo sbagliato, si otterrebbe un cast implicito tra i puntatori non void, che causerebbe un avviso (errore in C ++). Se dimentichi di includere l'intestazione che definisce g_newe g_new0otterrai un errore. g_newed g_new0entrambi prendono gli stessi argomenti, diversamente da mallocquello che richiede meno argomenti rispetto a calloc. Basta aggiungere 0per ottenere la memoria inizializzata a zero. Il codice può essere compilato con un compilatore C ++ senza modifiche.







casting