pointeur stricte aliasing: toute solution à un problème spécifique?
-
24-10-2019 - |
Question
J'ai un problème causé par la rupture stricte règle de l'aliasing pointeur. J'ai un type T
qui vient d'un modèle et une Int
de type intégral de la même taille (comme sizeof
). Mon code fait essentiellement ce qui suit:
T x = some_other_t;
if (*reinterpret_cast <Int*> (&x) == 0)
...
Parce que T
est un peu arbitraire (autre que la restriction de taille) type qui pourrait avoir un constructeur, je ne peux pas faire une union de T
et Int
. (Ceci est uniquement autorisé en C ++ 0x uniquement et même pas encore pris en charge par GCC).
Est-il possible que je pourrais réécrire le pseudocode ci-dessus pour préserver la fonctionnalité et éviter de casser la règle stricte aliasing? Notez que ceci est un modèle, je ne peux pas contrôler T
ou de la valeur de some_other_t
; l'affectation et la comparaison ultérieure ne se produisent à l'intérieur du code templated.
(Pour mémoire, le code ci-dessus a commencé à briser le GCC 4.5 si T
contient des champs de bits.)
La solution
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))
...
Sur mon système, GCC optimise détruiront is_T_0()
et memcpy()
, ce qui en quelques instructions de montage dans myfunc()
.
Autres conseils
Avez-vous entendu parler boost::optional
?
Je dois admettre que je suis pas clair quant au vrai problème ici ... mais boost :: en option permettent de stocker en valeur et savoir encore a été initialisé ou non la mémoire réelle. Je permet également en place la construction et la destruction, pourrait donc être un bon ajustement je suppose.
EDIT :
Je pense que j'ai finalement saisi le problème: vous voulez être en mesure d'allouer beaucoup d'objets, à divers points en mémoire, et que vous souhaitez savoir si la mémoire ou non à ce point tient vraiment un objet ou non .
Malheureusement, votre solution a un énorme problème: il est incorrect. Si jamais T
peut en quelque sorte être représenté par un motif de bits de null
, alors vous pensez que c'est la mémoire non initialisée.
Vous devrez vous recourir à ajouter au moins un bit d'information. Il n'y a pas beaucoup vraiment, après tout ce qui est seulement 3% de la croissance (33 bits pour 4 octets).
Vous pouvez par exemple utiliser certaines Mimick boost::optional
mais d'une manière de tableau (pour éviter la perte de remplissage).
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
};
Ensuite, il est aussi simple que cela:
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]);
}
Remarque : Par souci de simplicité, je n'ai pas examiné la question de l'alignement. Si vous ne savez pas sur le sujet, lire à ce sujet avant de jongler avec la mémoire:)
Que diriez-vous ceci:
Int zero = 0;
T x = some_other_t;
if (std::memcmp(&x, &zero, sizeof(zero)) == 0)
Il pourrait ne pas être aussi efficace, mais il devrait se débarrasser de l'avertissement.
ADDENDA # 1:
Depuis T
est contrainte d'être la même taille que Int
, vous faire un mannequin zéro valeur de type binaire T
et comparer directement contre elle (au lieu de la coulée et la comparaison agaist Int(0)
).
Si votre programme est mono-thread, vous pourriez avoir quelque chose comme ceci:
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_;
};
Si elle est multi-thread, vous voulez éviter d'utiliser un membre statique zero_
, et ont chaque instance de conteneur tenir son propre membre de 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_;
};
ADDENDA # 2:
Permettez-moi de mettre l'additif ci-dessus d'une autre manière plus simple:
// 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)
Pourquoi ne pas simplement:
const Int zero = 0;
if (memcmp(&some_other_t, &zero, sizeof(zero)) == 0)
/* some_other_t is 0 */
(vous pouvez essayer d'ajouter aussi le qualificatif de static
à zero
pour voir si cela fait une différence en terme de performance)
Utilisation d'un ordinateur 33 bits. ;-P
Il se sent comme un hack, mais apparemment je l'ai trouvé une solution: en utilisant volatile
pour la coulée de Int
. Essentiellement, ce que je fais est maintenant:
T x = some_other_t;
if (*reinterpret_cast <volatile Int*> (&x) == 0)
...
Le problème avec T
bitfield est maintenant disparu. Pourtant, je ne me sens pas tout à fait heureux de ce que volatile
est pas bien définie en C ++ ... AFAIK