Strumenti Utente

Strumenti Sito


programmazione:cpp:buone_pratiche

Buone pratiche di programmazione in C/C++

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
programmazione/cpp/buone_pratiche.txt · Ultima modifica: 07/09/2026 11:17 da Fabio Di Matteo