La vera risposta è che l'unico modo per creare un meccanismo di raccolta dei rifiuti sicuro ed efficiente è disporre di un supporto a livello di lingua per riferimenti opachi. (O, al contrario, una mancanza di supporto a livello di lingua per la manipolazione diretta della memoria.)
Java e C # possono farlo perché hanno tipi di riferimento speciali che non possono essere manipolati. Questo dà al runtime la libertà di fare cose come spostare oggetti allocati in memoria , che è cruciale per un'implementazione GC ad alte prestazioni.
Per la cronaca, nessuna moderna implementazione di GC utilizza il conteggio dei riferimenti , quindi è completamente un'aringa rossa. I GC moderni utilizzano la raccolta generazionale, in cui le nuove allocazioni vengono trattate essenzialmente nello stesso modo in cui le allocazioni dello stack sono in un linguaggio come C ++, e quindi periodicamente tutti gli oggetti allocati di recente che sono ancora vivi vengono spostati in uno spazio "sopravvissuto" separato e un'intera generazione di oggetti viene deallocato in una sola volta.
Questo approccio presenta vantaggi e svantaggi: il lato positivo è che le allocazioni di heap in una lingua che supporta GC sono veloci quanto le allocazioni di stack in una lingua che non supporta GC, e il lato negativo è che gli oggetti che devono eseguire la pulizia prima di essere distrutti richiedono un meccanismo separato (ad esempio la using
parola chiave di C # ) oppure il loro codice di pulizia viene eseguito in modo non deterministico.
Si noti che una chiave per un GC ad alte prestazioni è che ci deve essere supporto linguistico per una classe speciale di riferimenti. C non ha questo supporto linguistico e non lo farà mai; poiché il C ++ ha un sovraccarico da parte dell'operatore, potrebbe emulare un tipo di puntatore GC'd, sebbene debba essere eseguito con attenzione. Infatti, quando Microsoft ha inventato il suo dialetto di C ++ che sarebbe stato eseguito sotto il CLR (il runtime .NET), hanno dovuto inventare una nuova sintassi per "riferimenti in stile C #" (ad esempio Foo^
) per distinguerli da "riferimenti in stile C ++" (ad es Foo&
.).
Ciò che ha C ++ e ciò che viene regolarmente utilizzato dai programmatori C ++ sono i puntatori intelligenti , che sono in realtà solo un meccanismo di conteggio dei riferimenti. Non considererei il conteggio dei riferimenti come GC "vero", ma offre molti degli stessi vantaggi, a costo di prestazioni più lente rispetto alla gestione manuale della memoria o al vero GC, ma con il vantaggio della distruzione deterministica.
Alla fine della giornata, la risposta si riduce davvero a una funzionalità di progettazione del linguaggio. C ha fatto una scelta, C ++ ha fatto una scelta che gli ha permesso di essere retrocompatibile con C pur fornendo alternative che sono abbastanza buone per la maggior parte degli scopi, e Java e C # hanno fatto una scelta diversa che è incompatibile con C ma è anche abbastanza buona per la maggior parte degli scopi. Sfortunatamente, non esiste un proiettile d'argento, ma avere familiarità con le diverse scelte là fuori ti aiuterà a scegliere quella corretta per qualsiasi programma che stai attualmente cercando di costruire.