Generischer char [] -basierter Speicher und Vermeidung von UBs, die mit striktem Aliasing zusammenhängen
Ich versuche, eine Klassenvorlage zu erstellen, die eine Reihe von Typen in ein entsprechend großes Zeichen-Array packt und den Zugriff auf die Daten als einzelne, korrekt eingegebene Verweise ermöglicht. Nach dem Standard kann dies nun zu einer Verletzung des Strict-Aliasing und damit zu undefiniertem Verhalten führen, wenn wir auf die zugreifenchar[]
Daten über ein Objekt, das nicht mit ihm kompatibel ist. Im Einzelnen heißt es in der Norm:
Wenn ein Programm versucht, über einen anderen Wert als einen der folgenden Typen auf den gespeicherten Wert eines Objekts zuzugreifen, ist das Verhalten undefiniert:
der dynamische Typ des Objekts,eine cv-qualifizierte Version des dynamischen Objekttyps,einen Typ, der dem dynamischen Typ des Objekts ähnlich ist (wie in 4.4 definiert),ein Typ, der der vorzeichenbehaftete oder vorzeichenlose Typ ist, der dem dynamischen Typ des Objekts entspricht,ein Typ, der der mit oder ohne Vorzeichen versehene Typ ist, der einer lebenslaufqualifizierten Version des dynamischen Typs des Objekts entspricht,ein Aggregat- oder Vereinigungstyp, der einen der oben genannten Typen in seine Elemente oder nicht statischen Datenelemente einschließt (einschließlich rekursiv eines Elements oder nicht statischen Datenelements eines Unteraggregats oder einer enthaltenen Vereinigung),ein Typ, der ein (möglicherweise lebenslaufqualifizierter) Basisklassentyp des dynamischen Typs des Objekts ist,achar
oderunsigned char
Art.Angesichts des Wortlauts des hervorgehobenen Aufzählungspunkts habe ich Folgendes gefundenalias_cast
Idee:
#include <iostream>
#include <type_traits>
template <typename T>
T alias_cast(void *p) {
typedef typename std::remove_reference<T>::type BaseType;
union UT {
BaseType t;
};
return reinterpret_cast<UT*>(p)->t;
}
template <typename T, typename U>
class Data {
union {
long align_;
char data_[sizeof(T) + sizeof(U)];
};
public:
Data(T t = T(), U u = U()) { first() = t; second() = u; }
T& first() { return alias_cast<T&>(data_); }
U& second() { return alias_cast<U&>(data_ + sizeof(T)); }
};
int main() {
Data<int, unsigned short> test;
test.first() = 0xdead;
test.second() = 0xbeef;
std::cout << test.first() << ", " << test.second() << "\n";
return 0;
}
(Der obige Testcode, insbesondere derData
Klasse ist nur eine niedergeschlagene Demonstration der Idee, also bitte nicht darauf hinweisen, wie ich verwenden solltestd::pair
oderstd::tuple
. Dasalias_cast
Die Vorlage sollte auch für lebenslaufqualifizierte Typen erweitert werden und kann nur dann sicher verwendet werden, wenn die Ausrichtungsanforderungen erfüllt sind. Ich hoffe jedoch, dass dieser Ausschnitt ausreicht, um die Idee zu demonstrieren.)
Dieser Trick bringt die Warnungen von g ++ zum Schweigen (wenn er mit kompiliert wurde)g++ -std=c++11 -Wall -Wextra -O2 -fstrict-aliasing -Wstrict-aliasing
), und der Code funktioniert, aber ist dies wirklich eine gültige Methode, um den Compiler anzuweisen, Optimierungen auf der Basis von Strict-Aliasing zu überspringen?
Wenn es nicht gültig ist, wie würde man dann vorgehen, um eine generische Speicherklasse auf der Basis eines Char-Arrays wie diese zu implementieren, ohne die Aliasing-Regeln zu verletzen?
Bearbeiten: Ersetzen deralias_cast
mit einem einfachenreinterpret_cast
so was:
T& first() { return reinterpret_cast<T&>(*(data_ + 0)); }
U& second() { return reinterpret_cast<U&>(*(data_ + sizeof(T))); }
erzeugt beim Kompilieren mit g ++ die folgende Warnung:
aliastest-so-1.cpp: In der Instanziierung von 'T & Data :: first () [with T = int; U = short unsigned int]: aliastest-so-1.cpp: 28: 16:
Erforderlich ab hier aliastest-so-1.cpp: 21: 58: Warnung: Die Dereferenzierung von typgesteuerten Zeigern verstößt gegen strenge Aliasing-Regeln [-Wstrict-Aliasing]