Ci scusiamo in anticipo per quello che potrebbe essere un primo post stupido su un terreno ben battuto. Mentre c'è molto materiale sull'argomento, molto poco è definitivo e/o comprensibile per me.C++: aliasing rigoroso contro abuso sindacale
Ho una classe modello AlignedArray
per allocare dinamicamente memoria sullo heap con allineamento arbitrario (ho bisogno dell'allineamento di 32 byte per le routine di assemblaggio AVX). Ciò richiede una brutta manipolazione del puntatore.
Agner Fog fornisce una classe di esempio in cppexamples.zip che abusa di un unione per farlo (http://www.agner.org/optimize/optimization_manuals.zip). Tuttavia, so che scrivere a un membro di un sindacato e poi leggere da un altro risulta in UB.
AFAICT è sicuro fare l'alias di qualsiasi tipo di puntatore a char *
, ma solo in una direzione. È qui che la mia comprensione diventa confusa. Ecco una versione ridotta del mio AlignedArray
classe (essenzialmente una riscrittura di Agner di, per aiutare la mia comprensione):
template <typename T, size_t alignment = 32>
class AlignedArray
{
size_t m_size;
char * m_unaligned;
T * m_aligned;
public:
AlignedArray (size_t const size)
: m_size(0)
, m_unaligned(0)
, m_aligned(0)
{
this->size(size);
}
~AlignedArray()
{
this->size(0);
}
T const & operator [] (size_t const i) const { return m_aligned[i]; }
T & operator [] (size_t const i) { return m_aligned[i]; }
size_t const size() { return m_size; }
void size (size_t const size)
{
if (size > 0)
{
if (size != m_size)
{
char * unaligned = 0;
unaligned = new char [size * sizeof(T) + alignment - 1];
if (unaligned)
{
// Agner:
/*
union {
char * c;
T * t;
size_t s;
} aligned;
aligned.c = unaligned + alignment - 1;
aligned.s &= ~(alignment - 1);
*/
// Me:
T * aligned = reinterpret_cast<T *>((reinterpret_cast<size_t>(unaligned) + alignment - 1) & ~(alignment - 1));
if (m_unaligned)
{
// Agner:
//memcpy(aligned.c, m_aligned, std::min(size, m_size));
// Me:
memcpy(aligned, m_aligned, std::min(size, m_size));
delete [] m_unaligned;
}
m_size = size;
m_unaligned = unaligned;
// Agner:
//m_aligned = aligned.t;
// Me:
m_aligned = aligned;
}
return;
}
return;
}
if (m_unaligned)
{
delete [] m_unaligned;
m_size = 0;
m_unaligned = 0;
m_aligned = 0;
}
}
};
Quindi, quale metodo è sicuro (r)?
Invece di costruire oggetti 'char' e poi colata che a T, perché non si afferra la memoria prima (da' operatore new' , o anche 'malloc'), come' void * ', e in realtà costruisci oggetti' T' in esso? Fondamentalmente: se vuoi oggetti T, costruisci oggetti T. Questo caso d'uso (allineamento allineato) ha * zero * bisogno di trucchi aliasing/sindacati/memcpy/qualunque. –
@ R.MartinhoFernandes: Tranne che la matematica non è consentita su 'void *' s. Come si ottiene un 'vuoto *' allineato? – Omnifarious
@Omnifarious Ultimo ho controllato, la matematica non è consentita su 'char *' neanche. (E anche se lo fosse, ciò non significherebbe che tu debba costruire oggetti char e non costruire oggetti T) Hai bisogno di interi per fare matematica. La soluzione portatile in C++ 11 è http://en.cppreference.com/w/cpp/memory/align. La soluzione teoricamente non-portatile è reinterpret_cast su un tipo numerico, eseguire i calcoli e reinterpretare il cast. (in pratica è abbastanza portatile perché in tutte le implementazioni so che reinterpret_cast a tipi numerici si comporta come previsto) –