Net-Base Rivista

23.08.2026

Caccia alle perdite di memoria: utilizzare in modo mirato FastMM FullDebugMode e leggere correttamente gli stack trace

FastMM FullDebugMode è, nei progetti Delphi, uno degli strumenti più efficaci contro le perdite di memoria — ma solo se viene attivato in modo mirato, se i report vengono interpretati correttamente e se si evitano le assunzioni errate più comuni. Questo contributo pratico mostra un flusso operativo chiaro dal...

23.08.2026

Dal tema della rivista alla pratica di progetto

Pagine di servizi e tecniche correlate all'articolo

Se un’applicazione Delphi in esercizio si gonfia lentamente, si arresta sporadicamente con Access Violations o diventa improvvisamente instabile dopo giorni di esecuzione, spesso non si tratta di un singolo bug, ma di uno schema: memoria allocata ma non liberata correttamente – oppure liberata troppo presto e poi ancora utilizzata. Proprio qui FastMM FullDebugMode è prezioso. Non come stato permanente, ma come strumento diagnostico mirato, che trasforma un generico «qualcosa nel heap è corrotto» in una causa verificabile.

Lo svantaggio: FullDebugMode produce molto output, incide sulle prestazioni e porta facilmente a interpretazioni errate. Un leak report non indica automaticamente il punto in cui si trova «l’errore». E uno stacktrace vale tanto quanto la risoluzione dei simboli (file MAP, informazioni di debug, inlining). In questo articolo esamino i casi limite tipici, spiego l’approccio corretto e le insidie — in modo che alla fine non solo individui i leak, ma li elimini in modo duraturo.

Quando FastMM FullDebugMode è davvero utile

FastMM è nelle versioni moderne di Delphi spesso già il gestore di memoria predefinito o viene comunque incluso in molti progetti. Il FullDebugMode è però una configurazione particolare: aggiunge pattern di controllo ai blocchi di memoria, raccoglie stacktrace delle allocazioni e verifica in modo più aggressivo la corruzione dell’heap (cioè dati di gestione danneggiati nell’heap, p.es. a causa di buffer overrun).

Uso FullDebugMode in modo mirato quando si presenta una di queste situazioni:

  • Leak riproducibile: il consumo di memoria aumenta durante un test per ogni operazione (es. per richiesta, per import, per azione UI).
  • AV sporadiche: particolarmente quelle che avvengono «a volte qui, a volte là» nello stesso ambito (classico: use-after-free).
  • Corruzione dell’heap: messaggi come «Invalid pointer operation», «Access violation in ntdll» o crash alla chiusura/finalizzazione.
  • Ricerca di regressioni: instabilità nuova improvvisa dopo refactoring, aggiornamento di libreria o cambio di compilatore.

Non ha senso usare FullDebugMode come «lo attiviamo in tutte le build». L’overhead è alto, il timing cambia, e proprio le race condition possono scomparire o spostarsi. Per l’esercizio continuativo conviene piuttosto un monitoraggio snello (es. working set del processo, Private Bytes, contatori per operazione) — FullDebugMode è il bisturi, non il rilevatore del polso.

Principio di base: il leak report è un sintomo, lo stacktrace è la traccia

Un leak report ti mostra prima di tutto: Questi blocchi sono ancora allocati alla terminazione del programma. Questo è problema solo se quei blocchi avrebbero dovuto essere liberati. Esistono «leak» legittimi: singleton globali, cache, handle di OS con durata di processo o librerie di terze parti che intenzionalmente non finalizzano. Questi casi devi conoscerli, ma non correggerli ciecamente.

Lo stacktrace nel report mostra il punto in cui è stato richiesto il blocco. Spesso non è il luogo in cui hai «dimenticato di fare Free». Realtà comune nei sistemi cresciuti nel tempo:

  • Allocazione nel livello UI o livello di servizio, la liberazione dovrebbe avvenire in uno strato più profondo (ownership non chiara).
  • Allocazione in una factory, l’ownership viene passata al chiamante — ma il chiamante pensa che sia «owned».
  • Gli oggetti sono mantenuti in collection (liste, dizionari), ma il modello di ownership non è consistente.
  • Un percorso di eccezione salta la pulizia perché manca un try/finally o inizia troppo tardi.

La procedura accurata è quindi: riprodurreisolaredecodificare lo Stacktraceindividuare l’errore di ownershipcorrezione con test di regressione. FastMM ti fornisce le tracce, ma devi tradurle nell’architettura e nei cicli di vita.

Attivare correttamente FastMM FullDebugMode (senza trascurare effetti collaterali)

Grafica schematica con blocchi di heap e margini di controllo che vengono convertiti in un report di leak
Astratto: FullDebugMode lavora con margini di controllo aggiuntivi e output di report.

Il FullDebugMode viene nella pratica attivato tramite le opzioni di FastMM e una configurazione FastMM adeguata. Più importante non è «come si chiama esattamente il file di include», ma cosa la configurazione determina e in quali condizioni di build la usi.

Condizioni consigliate per il build di debug

  • Debug DCUs e informazioni di debug: gli Stacktraces sono utili solo se possono essere risolti su unità/linea/indirizzo reali. Assicurati che vengano generate informazioni di debug e che sia disponibile un file MAP.
  • Scegliere l’ottimizzazione consapevolmente: per la leggibilità dello Stacktrace generalmente è preferibile un build non ottimizzato. L’inlining e ottimizzazioni aggressive possono «offuscare» i frame dello stack.
  • Stesse condizioni di esecuzione: usa per quanto possibile gli stessi dati, la stessa configurazione, gli stessi privilegi. Molte fughe di memoria dipendono dai dati (es. formati rari, percorsi speciali).
  • Separare 64-bit e 32-bit: il comportamento della memoria, l’allineamento e le librerie di terze parti differiscono. Esegui il debug sulla piattaforma target dove si manifesta il problema.

Un aspetto che amministratori e responsabili tecnici spesso sottovalutano: FullDebugMode può anche alterare il timing. Se è presente threading, le race condition possono manifestarsi in modo diverso. Per questo è sensato avere in parallelo anche una esecuzione senza FullDebugMode che confermi solo la riproduzione. FullDebugMode è quindi il passo per la diagnosi.

Attenzione a „ReportMemoryLeaksOnShutdown“

Delphi può segnalare leak alla chiusura del programma tramite ReportMemoryLeaksOnShutdown. È pratico, ma in applicazioni complesse (servizi, host di plug-in, run-time prolungato) può ingannare: durante lo shutdown entrano in gioco sezioni di finalizzazione, i thread si fermano, le cache vengono smaltite. Un leak che è critico a metà esecuzione può scomparire fino alla fine — o al contrario: un apparente leak si manifesta solo in fase di shutdown perché è ancora in corso lavoro in background.

Per un leak-hunting pratico è quindi più importante: misurare il leak per operazione (es. dopo 100 richieste), non solo alla terminazione. FastMM può aiutare, ma l’assetto del test deve riprodurre questo scenario.

Il caso limite tipico: il report dei leak mostra „un oggetto qualsiasi“, ma la causa è l’ownership

Un classico delle applicazioni aziendali: un processo di importazione crea per ogni record oggetti di supporto (p.es. StringLists, JSON-Parser, liste temporanee). Nel percorso principale vengono rilasciati correttamente. In rari casi (skip dovuto a validazione, eccezione, uscita anticipata) un oggetto rimane in memoria. Dopo 10.000 record diventa visibile.

FastMM FullDebugMode aiuta qui, perché mostra il punto di allocazione. Ma la „correzione“ non è „free nel punto di allocazione“. La correzione è un pattern di ownership robusto:

  • Chi crea un oggetto non è automaticamente l’Owner.
  • L’Ownership deve essere chiara nel contratto API (parametri/return, documentazione, convenzioni di nomenclatura).
  • Le Collections devono essere inequivocabili: owning vs. non-owning. Le forme miste si rivelano problematiche.
  • I percorsi di eccezione richiedono blocchi try/finally precoci.

Se dal stacktrace vedi solo „TStringList.Create“, l’informazione non è inutile – ma ti dice solo: qui viene allocato qualcosa. La domanda è: dove dovrebbe terminare? E su questo il pensiero architetturale aiuta più dell’acrobazia del debugger.

Leggere correttamente gli Stacktraces: cosa puoi davvero dedurne

Dettaglio di un'analisi di debugging con debugger sfocato e catena di chiamate annotata a mano
In uno stacktrace conta la catena di chiamate – non la singola riga.

Uno stacktrace generato da FastMM è di norma una lista di indirizzi di ritorno che – con i simboli di debug – vengono mappati su unit, procedure e idealmente numeri di riga. Quando lo leggi, tre aspetti sono decisivi:

  • La cima dello stack non è sempre l’errore: i frame più alti sono spesso il memory manager/RTL. Diventa interessante dove inizia il tuo codice.
  • Catena di chiamate invece di una singola riga: la riga è solo un punto. La catena mostra quale percorso ha portato all’allocazione.
  • Più blocchi identici: se FastMM segnala più leak della stessa dimensione, spesso è un percorso ricorrente. Questo è utile: hai riproducibilità.

Quando mancano i numeri di riga: MAP-Datei, Packages, Release-DCUs

Molti team inciampano qui: FullDebugMode è attivo, arriva il report dei leak, ma invece di unit/riga ci sono solo indirizzi o simboli criptici. Cause tipiche:

  • Nessun file MAP o non sono state generate informazioni di debug.
  • Stai eseguendo contro Release-DCUs o DLL di terze parti senza simboli.
  • L’applicazione usa Runtime Packages: allora parti del codice sono in BPLs, e la risoluzione dei simboli deve essere coerente.
  • L’ottimizzazione/il inlining ha reso lo stacktrace meno leggibile.

In pratica questo significa: per la caccia ai leak ti serve un build consapevolmente „diagnosticabile“. È un obiettivo diverso da „più veloce possibile“. I responsabili tecnici dovrebbero gestirlo come un profilo di build separato, così che non ogni membro del team modifichi le opzioni del progetto ad hoc.

Valutare i frame: „Interessant“ ist oft eine Zeile weiter oben

Un esempio reale (senza codice cliente concreto): lo stacktrace ti mostra come primo frame nel tuo codice una routine “LoadConfig”. Vedi lì una creazione di oggetto. Aggiungi una chiamata Free, la perdita scompare – e all’improvviso si pianta altrove con un Double Free. Perché? Perché “LoadConfig” mette l’oggetto in una cache, e un altro percorso di codice è già Owner e provvede alla pulizia più tardi.

La lettura corretta sarebbe stata: lo stacktrace ti mostra dove nasce il blocco. La correzione si trova spesso nella definizione: chi possiede l’oggetto dopo il return? Se non rispondi a questa domanda in modo netto, cambi solo il quadro degli errori (Leak → AV).

Corruzione dell’heap vs. Leak: Perché FullDebugMode spesso individua il vero colpevole

Grafica che mostra un buffer-overrun che deborda in un'area di memoria adiacente
La corruzione dell’heap si manifesta spesso in modo ritardato – FullDebugMode la rende visibile prima.

Molti “Leak” sono in realtà problemi secondari: un buffer-overrun sovrascrive i metadati dell’heap, il memory manager non riesce poi a liberare correttamente, e alla fine vedi perdite apparentemente casuali o operazioni su puntatori invalidi. FullDebugMode è efficace perché usa pattern di controllo e nelle operazioni di Free/Reuse esegue validazioni aggiuntive.

Importante è la distinzione:

  • Leak: un blocco è stato allocato e non è mai stato liberato. La stabilità peggiora nel tempo, il crash non è necessariamente immediato.
  • Use-after-free: un blocco viene liberato ma viene ancora utilizzato in seguito. Porta ad AV sporadici, difficili da riprodurre.
  • Double Free: un blocco viene liberato due volte. Può causare un crash immediato o solo più tardi (quando il blocco è stato riutilizzato).
  • Corruzione dell’heap: qualcuno scrive oltre i confini di un blocco. I sintomi sono spesso ritardati.

FullDebugMode è particolarmente prezioso quando i sintomi compaiono con ritardo temporale. La validazione aggiuntiva rende gli errori visibili prima – spesso esattamente nel punto in cui avviene l’accesso scorretto, non solo minuti dopo in un qualsiasi Free.

Approccio nei progetti: caccia ai leak riproducibile invece di “debugging nella nebbia”

Se vuoi cacciare le perdite di memoria, ti serve una procedura ripetibile che possa essere condivisa nel team. Lavoro volentieri con un frame diagnostico ben definito:

1) Riproduzione in uno scenario deterministico

Definisci una sequenza di test che mostri il leak in modo affidabile: “Avvia il servizio, elabora 500 messaggi, arresta il servizio” oppure “Apri la maschera X, esegui l’azione Y 200 volte”. È importante documentare la sequenza con i parametri (dataset, tenant, feature flag), così altri possono ripeterla.

2) Minimizzare: rendere visibile il leak passo dopo passo

Se la sequenza dura 20 minuti, dividila. L’obiettivo è: vuoi poter confrontare “prima” e “dopo” il più rapidamente possibile. Nelle applicazioni grandi questo è spesso il vero consumatore di tempo, non il fix in sé.

3) Abilitare FullDebugMode e interpretare il report

Solo ora entra in gioco FastMM FullDebugMode. Raccogli i report, raggruppali per dimensione del blocco/stack di chiamate e verifica le ripetizioni. Un singolo blocco residuo può essere una cache legittima. 10.000 blocchi identici sono quasi sempre una perdita reale.

4) Chiarezza sull’ownership e correzione nel livello appropriato

Correggi i leak là dove viene definita l’ownership: Factory, contratto API, Collection-Wrapper. „Inserire velocemente un Free“ direttamente accanto a Create è spesso il posto sbagliato se l’oggetto viene passato oltre.

5) Regressione: stessa Sequenz, stesso Build, stesso Report

La correzione è valida solo quando la sequenza viene rieseguita e non compaiono né leak né nuovi errori di memoria. Specialmente con Use-after-free, un „leak sparito“ non è una prova, ma solo un nuovo sintomo.

Tipici trabocchetti nel codice Delphi, che FastMM rende visibili

Collezioni e ownership (Liste, Dictionaries, Interfaces)

Molti leak non derivano da algoritmi complessi, ma da strutture dati di uso quotidiano. Due scenari d’errore classici:

  • Una lista contiene oggetti, ma nessuno sa chi li libera. Soluzione: usare una lista owning o svuotarla coerentemente nel finally.
  • Un dizionario mantiene oggetti come valori; al Remove il valore non viene rilasciato o viene dimenticato durante il Clear.

Particolarmente insidiose sono le Interfaces: il conteggio dei riferimenti (simile ad ARC) è comodo, ma il funzionamento misto con object-ownership può generare leak in presenza di riferimenti ciclici o eventi. FullDebugMode spesso mostra il percorso di allocazione, ma la causa è un ciclo di riferimenti (A tiene B tramite un Interface, B tiene A tramite un callback).

Exceptions e uscite anticipate

Nei sistemi software aziendali maturi le eccezioni sono spesso parte del controllo normale (ad es. validazione, abort, retry). Il problema raramente è l’eccezione stessa, ma il percorso circostante: un oggetto viene creato prima del try/finally, poi si verifica un’eccezione e il cleanup viene saltato. FullDebugMode fornisce lo stacktrace dell’allocazione — e devi verificare se esiste un percorso di rilascio garantito.

Thread e lifetime: „rilasciare nel thread sbagliato“

Con VCL/FMX e servizi con worker-threads emerge un altro caso limite: un oggetto viene creato in un thread, ma rilasciato nel UI-Thread (o viceversa), perché si passa „solo velocemente“ qualcosa tramite Queue/Synchronize. Questo può funzionare, ma può anche portare a Use-after-free se il Producer continua a lavorare mentre il Consumer ha già rilasciato.

FastMM FullDebugMode può aiutare qui perché rileva precocemente errori ritardati. La correzione reale però è un modello di lifetime pulito: proprietà chiare, passaggio solo tramite dati immutabili o punti di trasferimento di ownership ben definiti.

Come rendere i report utilizzabili: filtrare, confrontare, documentare

Nei team conviene non limitarsi a „guardare“ i leak-Report, ma trattarli come un artefatto. Tre misure pragmatiche che si sono dimostrate efficaci:

  • Baseline-Report: uno „stato noto“ (ad es. versione prodotto corrente) viene eseguito una volta con FullDebugMode e archiviato come riferimento. Così riconosci subito nuovi leak.
  • Confronto per caso d’uso: per workflow critici (Import, Export, API-Request, operazione massiva UI) definisci per ciascuno una breve sequenza ripetibile regolarmente.
  • Leak „legittimi“ documentati: se una cache non viene intenzionalmente finalizzata, documentalo. Altrimenti fra sei mesi qualcuno ricomincerà a inseguire le stesse voci.

Questo non è burocrazia, ma un risparmio di tempo: la caccia alle perdite di memoria altrimenti diventa rapidamente un ciclo senza fine, perché gli stessi schemi si ripresentano in ogni sprint.

Quando conviene lo sforzo – e quando dovresti procedere diversamente

FastMM FullDebugMode è uno strumento di diagnostica che comporta costi. Lo sforzo vale la pena soprattutto se:

  • L’applicazione funziona a lungo (Service, Terminalserver-Client, sistema a turni, processi 24/7).
  • Elabori flussi di dati reali dei clienti e non copri tutti i percorsi nei test.
  • La stabilità è più importante della velocà di rilascio delle feature a breve termine (tipico nelle soluzioni software vicine ai processi).

Se invece hai solo un piccolo desktop-helper che termina dopo 30 secondi, la caccia alle perdite è spesso secondaria. Allo stesso modo: se hai un problema di spike di memoria occasionale (p.es. un grande export), spesso non si tratta di una perdita, ma di una questione di strategia di streaming e del carico di picco nell’heap.

Conclusione pratica: FullDebugMode non è un interruttore, ma un processo

FastMM FullDebugMode introduce struttura nella ricerca degli errori di memoria: rende visibili le allocazioni, scopre prima la corruzione dell’heap e fornisce stack trace con cui puoi risolvere la causa anziché il sintomo. La leva decisiva però non è lo strumento, ma il processo: scenari riproducibili, build diagnostici, contratti di ownership chiari e regressione rispetto a una baseline.

Se sei alle prese con una perdita ostinata o errori sporadici dell’heap e vuoi stabilizzare la questione in modo sostenibile in un sistema Delphi più ampio, vale la pena un breve setup diagnostico pulito con sequenza chiara e report analizzabili. Se hai bisogno di supporto per analisi, profili di build o refactoring architetturale: contatta Net-Base Software GmbH.

Per questo argomento sono anche importanti Delphi Trovare perdite di memoria e Lettura del Fastmm Leak Report. L’articolo inserisce questi aspetti in modo chiaro e mostra cosa conta nella pratica quotidiana.

Discutere un progetto o un intervento di modernizzazione con Net-Base.

Passo successivo

Quando un tema diventa un progetto reale, architettura, sistemi esistenti e gestione operativa dovrebbero essere considerati insieme fin dalle fasi iniziali.

Non forniamo solo supporto per questioni isolate, ma anche quando da frammenti di codice sorgente, tematiche legacy o idee di portale deve nascere un progetto aziendale solido.

  • Stato attuale, stato obiettivo e rischi tecnici vengono valutati insieme.
  • REST, l'accesso ai dati, i portali e il rollout non vengono rinviati a fasi successive.
  • Vede in anticipo quale percorso è economicamente e operativamente sostenibile.

Condividi il post

Condividi direttamente questo articolo

LinkedIn, X, XING, Facebook, WhatsApp e e-mail sono immediatamente disponibili. Per Instagram prepariamo direttamente il link e un breve testo.

E-mail

Instagram si apre in una nuova scheda. Il link e il breve testo vengono copiati prima negli appunti.