Analisi di reinterpret_cast e Undefined Behaviour

di il
0 risposte

Analisi di reinterpret_cast e Undefined Behaviour

Buon giorno a tutti. Faccio una doverosa premessa al post, che sarà piuttosto lungo. Il titolo potrebbe non essere chiarissimo, ma non mi veniva in mente di meglio. Il problema di cui sto per parlare richiede una profonda conoscenza e comprensione di una parte dello standard ISO C++, per lo meno fino alla versione 17, che pare abbia cambiato qualcosa riguardo il memory object model. 

Chi non ritiene di essere in questa posizione, è pregato di non dare risposte "autorevoli" al post, in quanto potrebbe aumentare solo la confusione(tanta) riguardo l'argomento. Sono ben accette osservazioni o ulteriori domande che accrescano i dubbi. In rete ci sono tanti sedicenti esperti, per cui si è in balia di fiumi informazioni che spesso si contraddicono e in cui ci si perde quando "non si ha la bussola".

Di tutta la bibliografia che mi è passata tra le mani, non ho trovato nessuno che si immergesse ne meandri dello standard, che lo sviscerasse e ne chiarisse tutti i punti deboli, oscuri o ambigui insieme alle parti subdole/problematiche del linguaggio come quella di cui sto per parlare. 

Nell'illustarre la domanda protrei commettere degli errori, il che è indice della mia poca chiarezza e comprensione dell'argomento, del resto se fossi io l'esperto, non starei certamente ponendo la domanda. 

Il cuore del problema riguarda una necessita che si incontra spesso in programmazione con linguaggi come il C e il C++ che offrono feature di basso livello. La questione è sul cosiddetto "type punning" ovvero trattare i bit di un ogetto di un determinato tipo come se fosse un tipo differente. Ciò si puo fare in C++ grazie al reinterpret_cast o come si farebbe in C con un cast esplicito con puntatori (A quanto pare nella versione del c11 è sicuron farlo usando le union, non nel C++). Per esempio se si ha un tipo di dato a virgola mobile come il float e lo si volesse interpretare come un intero, senza alterarne la rappresentazione (posto che i due tipi abbiano pari dimensione e allineamento). Ciò tecnicamente rappresenta per lo standard un cosiddetto "Undefined Behaviour", ovvero in tal caso il comportamento del programma non è più definito, in quanto si sta facendo conversione tra puntatori di tipi incompatibili (è chiaro che la conversione in se è legittima, il problema sorge quando si deve leggere il dato "reinterpretatpo").

E' presente una mole di codice in giro che tecnicamente contiene undefined behaviour, ma che per una serie di motivi ha sempre funzionato senza innescare comportamenti strani. Tuttavia tale codice non è portabile. Il problema sta dunque nel come risolvere il problema, ovvero fare type-punning in maniera type-safe. A quanto pare esistono vari modi di fare questo, quindi non è questo il punto del problema, ma capire invece nel codice già presente, quale si valido per lo standard e quale no. Stando a ciò che dice lo standard, è ammesso manipolare un oggetto attraverso uno dei seguenti tipi: unsigned char*, char*, std::byte*. Questo è lagato alla strict-aliasing rule, per cui solo quei tipi possono fungere da alias per tutti gli altri tipi(parlando di puntatori). Poco tempo fa mi sono imbattuto in questo frammento di codice: 

template<typename T>
struct Simple_alloc 
{ // use new[] and delete[] to allocate and deallocate bytes

	using value_type = T;

	Simple_alloc() {}

	T* allocate(size_t n)
	
	{ return reinterpret_cast<T*>(new char[n*sizeof(T)]); }

	void deallocate(T* p, size_t n)

	{ delete[] reinterpret_cast<char*>(p); }

	// ...

};

In questo frammento di codice ho il forte dubbio ci sia un caso di undefined_behaviour riguardo l'uso di reminterpret_cast per avere un puntatore di un tipo generico ad un buffer grezzo di byte. Se lo avessi riperito da un pinco pallo qualsiasi non mi sarei mai posto il dubbio, ma il problema è proviene da un testo "Bibbia", ovvero il The C++ Programming Language di Stroustrup. E' possibile che sia presente un errore del genereo che mi stia sfuggendo qualcosa. I mie dubbi riguardano in ciclo di vita degli oggetti che non esistono all'atto di creazione del buffer, il loro allineamento non specificato che potrebbe causare una lettura dei dati non allineata e il dereferencing del puntatore per accedere ai dati che dovrebbe innescare un undefined behaviour.

Con le dovute premesse fatte, ringrazio chi sappia mitigare definitivamente i miei dubbi e metterci una "pietra sopra".

Devi accedere o registrarti per scrivere nel forum
0 risposte