Rigorosa puntatore aliasing: qualsiasi soluzione per un problema specifico?
-
24-10-2019 - |
Domanda
Ho un problema causato dalla rottura regola rigorosa puntatore aliasing. Ho un tipo T
che viene da un modello e un certo tipo integrale Int
della stessa dimensione (come con sizeof
). Il mio codice fa essenzialmente la seguente:
T x = some_other_t;
if (*reinterpret_cast <Int*> (&x) == 0)
...
A causa T
è un po 'arbitrario (diversa dalla restrizione di dimensione) tipo che potrebbe avere un costruttore, non posso fare un'unione di T
e Int
. (Questo è consentito solo in solo C ++ 0x e non è nemmeno supportato da GCC ancora).
C'è un modo ho potuto riscrivere la pseudocodice sopra per preservare la funzionalità ed evitare la rottura regola rigorosa aliasing? Si noti che questo è un modello, non posso controllare T
o il valore di some_other_t
; l'assegnazione e il successivo confronto accadono all'interno del codice basato su modelli.
(Per la cronaca, il codice di cui sopra ha iniziato rottura su GCC 4.5 se T
contiene tutti i campi di bit.)
Soluzione
static inline int is_T_0(const T *ob)
{
int p;
memcpy(&p, ob, sizeof(int));
return p == 0;
}
void myfunc(void)
{
T x = some_other_t;
if (is_T_0(&x))
...
Sul mio sistema, GCC ottimizza via sia is_T_0()
e memcpy()
, con conseguente poche istruzioni di montaggio in myfunc()
.
Altri suggerimenti
Avete sentito parlare boost::optional
?
Devo ammettere che sono poco chiare per quanto riguarda il vero problema qui ... ma boost :: opzionali consentono di memorizzare in base al valore e tuttavia sapere se la memoria effettiva è stata inizializzata. Mi permette anche in luogo di costruzione e distruzione, quindi potrebbe essere una buona misura immagino.
Modifica :
Credo di aver finalmente afferrato il problema: si vuole essere in grado di allocare un sacco di oggetti, in vari punti in memoria, e vuoi sapere se la memoria a questo punto detiene veramente un oggetto o no .
Purtroppo la soluzione ha un problema enorme: non è corretto. Se T
mai possa in qualche modo essere rappresentato da uno schema di bit null
Allora ti pensi che sia la memoria non inizializzate.
Si dovrà ricorrere a voi stessi di aggiungere almeno un bit di informazione. Non è molto in realtà, dopo tutto questo è solo il 3% della crescita (33 bit per 4 byte).
Si potrebbe ad esempio utilizzare un po 'di boost::optional
Mimick ma in modo array (per evitare la perdita padding).
template <class T, size_t N>
class OptionalArray
{
public:
private:
typedef unsigned char byte;
byte mIndex[N/8+1];
byte mData[sizeof(T)*N]; // note: alignment not considered
};
Allora è così semplice come sembra:
template <class T, size_t N>
bool OptionalArray<T,N>::null(size_t const i) const
{
return mIndex[i/8] & (1 << (i%8));
}
template <class T, size_t N>
T& OptionalArray<T,N>::operator[](size_t const i)
{
assert(!this->null(i));
return *reinterpret_cast<T*>(mData[sizeof(T)*i]);
}
Nota ??strong>: Per semplicità non ho considerato la questione dell'allineamento. Se non si conosce sull'argomento, leggere su di esso prima di giocherellare con la memoria:)
Che ne dite di questo:
Int zero = 0;
T x = some_other_t;
if (std::memcmp(&x, &zero, sizeof(zero)) == 0)
Si potrebbe non essere il più efficiente, ma dovrebbe sbarazzarsi di avvertimento.
APPENDICE # 1:
Poiché T
è vincolata ad essere le stesse dimensioni Int
, farsi un manichino bit valore zero di tipo T
e confrontarlo direttamente contro di essa (invece di colata e confrontando agaist Int(0)
).
Se il programma è single-threaded, si potrebbe avere qualcosa di simile:
template <typename T>
class Container
{
public:
void foo(T val)
{
if (zero_ == val)
{
// Do something
}
}
private:
struct Zero
{
Zero() {memset(&val, 0, sizeof(val));}
bool operator==(const T& rhs) const {return val == rhs;}
T val;
};
static Zero zero_;
};
Se è multi-threaded, ti consigliamo di evitare di utilizzare un zero_
membro statico, e avere ogni attesa esempio contenitore di un proprio membro zero_
:
template <typename T>
class MTContainer
{
public:
MTContainer() {memset(zero_, 0, sizeof(zero_));}
void foo(T val)
{
if (val == zero_)
{
// Do something
}
}
private:
T zero_;
};
Addendum # 2:
Mettiamola l'addendum di cui sopra in un altro modo più semplice:
// zero is a member variable and is inialized in the container's constructor
T zero;
std::memset(&zero, 0, sizeof(zero));
T x = some_other_t;
if (x == zero)
Perché non semplicemente:
const Int zero = 0;
if (memcmp(&some_other_t, &zero, sizeof(zero)) == 0)
/* some_other_t is 0 */
(si consiglia di provare ad aggiungere anche la qualificazione per static
zero
per vedere se fa la differenza di prestazioni-saggio)
Usa un computer 33-bit. ;-P
Ci si sente come un hack, ma a quanto pare ho trovato una soluzione: utilizzando volatile
per Int
casting. In sostanza, quello che sto facendo ora è:
T x = some_other_t;
if (*reinterpret_cast <volatile Int*> (&x) == 0)
...
Il problema con T
campo di bit è ormai andato. Eppure, non mi sento molto felice di questo come volatile
non è ben definito in C ++ per quanto ne so ...