Autore: Fabio Di Matteo
Ultima revisione: 07/09/2026 10:59
Buone pratiche di programmazione in C/C++ e cose da evitare.
| Costrutto/funzione | Linguaggio | Perché è pericoloso | Alternativa più sicura |
|---|---|---|---|
gets() | C | Non limita la quantità di dati letti e può causare buffer overflow. | fgets() con controllo della dimensione |
strcpy() | C/C++ | Copia senza verificare la dimensione del buffer di destinazione. | snprintf() , strncpy() con attenzione oppure std::string |
strcat() | C/C++ | Concatena stringhe senza controllare lo spazio disponibile. | strncat() con attenzione oppure std::string |
sprintf() | C/C++ | Può scrivere oltre i limiti del buffer. | snprintf() |
vsprintf() | C/C++ | Variante senza controllo di dimensione di sprintf() . | vsnprintf() |
scanf("%s", ...)
| C/C++ | Legge stringhe senza limite e può provocare buffer overflow. | Specificare una larghezza, per esempio %19s , o usare fgets() |
scanf() | C/C++ | Può lasciare input non consumato e causare errori di conversione o loop imprevisti. | Controllare sempre il valore restituito; in C++ preferire std::getline() |
printf(user_input) | C/C++ | Crea una format-string vulnerability se l'input controlla il formato. | printf("%s", user_input)
|
fprintf(file, user_input) | C/C++ | Presenta lo stesso rischio delle format string controllate dall'utente. | Usare una stringa di formato fissa |
memcpy() | C/C++ | Una dimensione errata può causare letture o scritture fuori dai limiti. | Controllare rigorosamente dimensioni e sovrapposizione |
memmove() | C/C++ | Evita alcuni problemi di sovrapposizione, ma non impedisce buffer overflow. | Verificare sempre la dimensione dei buffer |
memset() | C/C++ | Una lunghezza errata può sovrascrivere memoria; non è adatta a inizializzare ogni tipo C++. | Usare dimensioni corrette e costruttori C++ |
free() | C/C++ | Può causare double free, use-after-free o heap corruption. | Gestione RAII e smart pointer |
delete | C++ | Può causare double delete, use-after-free o comportamento indefinito. | std::unique_ptr e std::shared_ptr |
delete[] | C++ | Usarlo con memoria allocata da new invece di new[] causa comportamento indefinito. | Evitare array dinamici manuali; usare std::vector |
new / new[] | C++ | Richiede una gestione manuale della memoria e può causare memory leak. | std::make_unique() , std::make_shared() , std::vector |
malloc() / calloc() / realloc() | C/C++ | Errori di dimensione, overflow, memory leak e mancato controllo del valore restituito. | In C++ usare contenitori STL e RAII |
Conversione implicita di malloc() | C++ | Convertire direttamente il risultato di malloc() può nascondere errori e non invoca costruttori. | Usare new gestito da smart pointer o contenitori STL |
| Puntatori non inizializzati | C/C++ | Dereferenziarli può causare crash o corruzione della memoria. | Inizializzare a nullptr o usare oggetti automatici |
| Puntatori dangling | C/C++ | Puntano a memoria già rilasciata o a oggetti usciti dallo scope. | Usare RAII, riferimenti validi e smart pointer |
Dereferenziazione di nullptr | C++ | Provoca normalmente un crash e comporta comportamento indefinito. | Controllare il puntatore o evitare puntatori non necessari |
| Accesso oltre i limiti di un array | C/C++ | Può corrompere memoria, causare crash o vulnerabilità. | std::array::at() , std::vector::at() o range-based for |
| Aritmetica dei puntatori | C/C++ | Un calcolo errato può produrre puntatori fuori dall'oggetto valido. | Usare iteratori e contenitori della libreria standard |
reinterpret_cast | C++ | Può violare l'allineamento, l'aliasing e le regole sui tipi. | Preferire conversioni tipate o interfacce sicure |
Cast in stile C, per esempio (Tipo*)ptr | C/C++ | Nasconde il tipo di conversione e può rimuovere controlli importanti. | static_cast , dynamic_cast o const_cast solo quando appropriato |
const_cast | C++ | Modificare un oggetto realmente dichiarato const causa comportamento indefinito. | Evitare di rimuovere const |
static_cast verso un tipo incompatibile | C++ | Una conversione errata può produrre dati corrotti o comportamento indefinito. | Verificare i tipi e usare conversioni esplicite controllate |
union usata per reinterpretare tipi | C/C++ | La lettura di un membro diverso da quello scritto può violare le regole del linguaggio. | Usare std::variant o conversioni definite |
| Variabili locali restituite per indirizzo | C/C++ | L'indirizzo diventa invalido quando la funzione termina. | Restituire un valore, un oggetto o usare memoria gestita |
| Riferimenti a temporanei | C++ | Il riferimento può sopravvivere all'oggetto temporaneo e diventare dangling. | Restituire per valore o prolungare correttamente la durata |
exit() in codice complesso | C/C++ | Salta il normale flusso di distruzione degli oggetti e può lasciare risorse aperte. | Restituire errori e lasciare che RAII gestisca le risorse |
abort() | C/C++ | Termina bruscamente il programma senza una normale gestione delle risorse. | Gestire l'errore o usare eccezioni dove appropriato |
system() | C/C++ | Se l'input contiene dati controllabili dall'utente può permettere command injection. | Evitare la shell; usare API dedicate e argomenti separati |
popen() | C/C++ | Esegue comandi tramite shell e può esporre a command injection. | Usare API di processo sicure senza interpretazione della shell |
execl() , execv() e simili | C/C++ | Argomenti o percorsi non controllati possono eseguire programmi indesiderati. | Convalidare percorsi e argomenti; evitare input non fidato |
tmpnam() | C/C++ | Il nome temporaneo può essere prevedibile o soggetto a race condition. | Usare API sicure per file temporanei |
mktemp() | C/C++ | Genera nomi temporanei non sicuri e può permettere attacchi di tipo symlink/race condition. | Usare mkstemp() o API moderne del sistema |
fopen() con percorso controllato dall'utente | C/C++ | Può permettere path traversal o apertura di file arbitrari. | Normalizzare, validare e limitare il percorso |
atoi() | C/C++ | Non segnala correttamente gli errori e può produrre risultati ambigui. | strtol() oppure std::from_chars() |
atol() / atoll() | C/C++ | Come atoi() , non gestiscono bene errori e overflow. | strtol() , strtoll() o std::from_chars() |
| Conversioni da interi signed a unsigned | C/C++ | Possono trasformare valori negativi in numeri molto grandi. | Controllare i limiti prima della conversione |
| Overflow di interi signed | C/C++ | È comportamento indefinito e può invalidare i controlli di sicurezza. | Verificare i limiti o usare controlli aritmetici sicuri |
| Overflow di interi unsigned | C/C++ | Il valore ricomincia da zero e può causare allocazioni o dimensioni errate. | Controllare l'overflow prima dei calcoli |
| Shift con quantità negativa o troppo grande | C/C++ | Può causare comportamento indefinito o risultati non portabili. | Validare la quantità dello shift |
| Divisione intera per zero | C/C++ | Causa comportamento indefinito e generalmente termina il programma. | Controllare il divisore prima della divisione |
| Macro con argomenti valutati più volte | C/C++ | Possono produrre effetti collaterali inattesi, per esempio incrementi duplicati. | Preferire funzioni, template o lambda |
volatile usato per sincronizzazione | C/C++ | Non garantisce atomicità né sincronizzazione tra thread. | Usare std::atomic , mutex o variabili condition |
| Variabili condivise senza mutex o atomiche | C++ | Possono causare data race e comportamento indefinito. | std::mutex , std::lock_guard o std::atomic |
std::thread::detach() | C++ | Il thread può usare oggetti già distrutti e gli errori diventano difficili da gestire. | Preferire join() o std::jthread |
| Ordine errato di acquisizione dei mutex | C++ | Può causare deadlock quando più thread attendono risorse circolarmente. | Usare un ordine fisso o std::scoped_lock |
| Distruzione manuale di oggetti | C++ | Può causare doppia distruzione o uso di oggetti non più validi. | Lasciare gestire la durata a RAII |
| Eccezioni lanciate dai distruttori | C++ | Possono causare std::terminate() durante lo svolgimento di un'altra eccezione. | Rendere i distruttori noexcept |
assert() per controlli di sicurezza | C/C++ | Le asserzioni possono essere disabilitate in compilazione. | Usare controlli normali per input e autorizzazioni |
| Disabilitare avvisi e protezioni del compilatore | C/C++ | Può nascondere errori e rimuovere difese utili. | Mantenere warning elevati e protezioni abilitate |
| Uso di dati non inizializzati | C/C++ | Può causare risultati imprevedibili, divulgazione di dati o crash. | Inizializzare sempre variabili e memoria |
| Serializzazione tramite copia diretta di struct | C/C++ | Padding, endianness e puntatori rendono il formato non portabile e insicuro. | Definire un formato esplicito e validare le lunghezze |
| Deserializzazione senza validazione | C/C++ | Dati manipolati possono causare overflow, accessi fuori limite o esecuzione non prevista. | Validare tipo, lunghezza, limiti e versione dei dati |
| Regex o parser applicati a input senza limiti | C/C++ | Input molto grandi possono causare consumo eccessivo di CPU o memoria. | Imporre limiti di dimensione e timeout |
new / delete manuali | C++ | Possono causare memory leak, double delete e use-after-free. | Oggetti automatici, RAII e std::unique_ptr |
std::shared_ptr usato indiscriminatamente | C++ | Può nascondere la proprietà delle risorse, creare cicli e aggiungere costi inutili. | Preferire std::unique_ptr e usare std::shared_ptr solo per proprietà realmente condivise |
| Array C | C/C++ | Non contengono informazioni sulla dimensione e facilitano gli accessi oltre i limiti. | std::array o std::vector |
| Stringhe C gestite manualmente | C/C++ | Possono causare buffer overflow, terminazione errata e use-after-free. | std::string |
| Cast C-style | C/C++ | Nasconde il tipo di conversione e può consentire conversioni pericolose. | static_cast , dynamic_cast o altra conversione esplicita appropriata |
reinterpret_cast | C++ | Può violare allineamento, aliasing e regole sui tipi, causando comportamento indefinito. | Conversioni tipizzate o std::bit_cast quando applicabile |
operator[] senza controllo | C++ | Non verifica che l’indice sia nei limiti del contenitore. | .at() quando è necessario un controllo esplicito |
| Puntatore e dimensione separati | C/C++ | Possono diventare incoerenti e causare accessi oltre i limiti. | std::span |
std::string_view verso oggetti temporanei | C++ | La vista può riferirsi a memoria già distrutta. | std::string oppure garantire la durata della stringa originale |
| Iteratori usati dopo modifiche al contenitore | C++ | Inserimenti o riallocazioni possono invalidare gli iteratori. | Rispettare le regole di invalidazione degli iteratori |
| Conversioni implicite signed/unsigned | C/C++ | Possono trasformare valori negativi in numeri molto grandi e causare errori nei controlli. | Usare tipi coerenti e conversioni esplicite verificate |
| Operazioni aritmetiche non controllate | C/C++ | Overflow e underflow possono produrre risultati errati o comportamento indefinito. | Controllare i valori e usare tipi numerici adeguati |
| Variabili globali modificabili | C/C++ | Rendono difficile controllare lo stato e aumentano il rischio di dipendenze nascoste e race condition. | Stato locale, incapsulamento e dati const |
| Dati condivisi senza sincronizzazione | C/C++ | Possono causare data race e comportamento indefinito nei programmi concorrenti. | Mutex, atomici, immutabilità o comunicazione tramite messaggi |
| Warning del compilatore ignorati | C/C++ | Errori e conversioni sospette possono rimanere inosservati. | Compilare con warning severi come -Wall -Wextra -Wpedantic |
| Codice non verificato con sanitizzatori | C/C++ | Errori di memoria, comportamento indefinito e data race possono non essere rilevati. | Usare AddressSanitizer, UndefinedBehaviorSanitizer e ThreadSanitizer |
| Input esterno non validato | C/C++ | Dati inattesi possono causare crash, overflow o consumo eccessivo di risorse. | Validazione, limiti di dimensione e gestione esplicita degli errori |
| Risorse gestite manualmente | C++ | File, mutex e altre risorse possono non essere rilasciati in caso di errore o eccezione. | RAII e classi come std::lock_guard |