2010-06-05 12 views
8

Ich habe ein Problem, das durch das Brechen der strengen Zeigeraliasingregel verursacht wird. Ich habe eine Art T, die von einer Vorlage und einigen integralen Typ Int der gleichen Größe (wie mit sizeof) kommt. Mein Code im Wesentlichen geschieht Folgendes:Strict-Pointer-Aliasing: Gibt es eine Lösung für ein bestimmtes Problem?

T x = some_other_t; 
if (*reinterpret_cast <Int*> (&x) == 0) 
    ... 

Da T einige willkürliche (anders als die Größenbeschränkung) geben, dass ein Konstruktor haben könnte, kann ich nicht eine Vereinigung von T und Int machen. (Dies ist nur in C++ 0x erlaubt und wird noch nicht einmal von GCC unterstützt).

Gibt es eine Möglichkeit, den obigen Pseudocode neu zu schreiben, um die Funktionalität zu erhalten und die strenge Aliasing-Regel zu umgehen? Beachten Sie, dass dies eine Vorlage ist, die ich nicht kontrollieren kann T oder Wert von some_other_t; Die Zuordnung und der anschließende Vergleich finden innerhalb des Vorlagencodes statt.

(Für die Aufzeichnung begann der obige Code auf GCC brechen 4.5 wenn T keine Bit-Felder enthält.)

+1

Was Sie versuchen zu tun? Ich kann mir nicht viele Situationen vorstellen, in denen dieser Code sinnvoll ist. Es ist sicherlich nicht gut durch den Standard spezifiziert. Unter der Annahme, dass dieser Hack tatsächlich notwendig ist (was wahrscheinlich nicht der Fall ist), müssen Sie möglicherweise das entsprechende Compiler-Flag verwenden, um das strikte Aliasing zu deaktivieren. – jalf

+0

@jalf: Es ist ein einzigartiger Behälter. Ich markiere leere Positionen mit ganzzahligem 0. Da aber "T" alles sein kann, einschließlich bitweisem 0, muss ich höchstens eine Position als "nicht leer, obwohl es wie leer aussieht" markieren. Der Vergleich ist die Überprüfung, ob "x" so markiert werden soll oder nicht. – doublep

+0

Ich bin nicht klar, wie Sie dieses Problem lösen - wie können Sie mit einem 'reininterpret_cast' die zwei verschiedenen Gründe für die Speicherung von' 0' ignorieren? – Stephen

Antwort

1
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)) 
     ... 

Auf meinem System optimiert GCC sowohl is_T_0() und memcpy(), was in nur wenigen Montageanleitung entfernt in myfunc().

+0

Gemäß [der anderen Frage] (http: // stackoverflow.com/questions/2981827/Strict-Pointer-Aliasing-ist-Zugriff durch eine flüchtige Zeiger-Referenz-a-Solut), das ist der Weg zu gehen. – doublep

+0

Persönlich würde ich die Verwendung (für mich) sauberer machen, indem ich 'is_T_0' nehme ein' const T & ', aber Sie sollten sicherstellen, dass GCC immer noch alles in diesem Fall optimiert. –

1

Wie wäre es damit:

Int zero = 0; 
T x = some_other_t; 
if (std::memcmp(&x, &zero, sizeof(zero)) == 0) 

Es ist nicht so effizient sein, aber Es sollte die Warnung loswerden.


NACHTRAG # 1:

Da T gezwungen ist, die gleiche Größe wie Int zu sein, machen Sie sich einen Dummy bitweise Nullwert vom Typ T und vergleichen Sie direkt gegen sie (statt Gießen und Vergleichen Agaist Int(0)).

Wenn Ihr Programm single-threaded ist, könnten Sie so etwas wie dieses:

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_; 
}; 

Wenn es multi-threaded ist, werden Sie zero_, mit einem statischen Element vermeiden wollen und jeden Container Instanz halten sie es selbst zero_ Mitglied ist:

template <typename T> 
class MTContainer 
{ 
public: 
    MTContainer() {memset(zero_, 0, sizeof(zero_));} 

    void foo(T val) 
    { 
     if (val == zero_) 
     { 
      // Do something 
     } 
    } 

private: 
    T zero_; 
}; 

NACHTRAG # 2:

Lassen Sie mich den obigen Nachtrag in einem anderen setzen, einfacheren Weg:

// 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) 
+0

Upvoted, weil dies scheint gut zu funktionieren. Allerdings würde ich mir sicher eine bessere Lösung wünschen ... – doublep

+0

@doublep: Eine weitere, effizientere Lösung zu meiner Antwort hinzugefügt. –

+0

Leider ist dies für die Leistung grauenhaft. Wenn 'T' selbst' int' ist, bekomme ich 2,5x Verlangsamung auf GCC4.5 -O2 (dies ist ein zusammengesetzter Test der "fast-realen" Verwendung). Offensichtlich kann GCC 'memcmp()' nicht optimieren. – doublep

1

Haben Sie boost::optional gehört?

Ich muss zugeben, ich bin unklar, was das eigentliche Problem hier ... aber boost :: optional erlauben, nach Wert zu speichern und doch wissen, ob der eigentliche Speicher initialisiert wurde oder nicht. Ich lasse auch Konstruktion und Zerstörung an Ort und Stelle zu, also könnte eine gute Passform sein, denke ich.

EDIT:

Ich glaube, ich ergriff schließlich das Problem: Sie wollen eine Menge von Objekten zuordnen zu können, an verschiedenen Stellen im Speicher, und Sie mögen, ob oder nicht wissen, der Speicher An diesem Punkt hält wirklich ein Objekt oder nicht.

Leider hat Ihre Lösung ein großes Problem: Es ist falsch. Wenn jemals T irgendwie durch ein null Bitmuster dargestellt werden kann, dann werden Sie denken, dass es sich um einen unitialisierten Speicher handelt.

Sie müssen sich selbst um mindestens ein Bit Informationen ergänzen. Es ist nicht wirklich viel, immerhin sind das nur 3% Wachstum (33 Bits für 4 Bytes).

Sie könnten zum Beispiel einige mimick boost::optional verwenden, aber in einer Array-Mode (um den Padding-Verlust zu vermeiden).

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 
}; 

Dann ist es so einfach wie das:

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]); 
} 

Anmerkung: Der Einfachheit halber ich nicht die Frage der Ausrichtung betrachtet haben. Wenn Sie nichts über das Thema wissen, lesen Sie darüber, bevor Sie mit dem Gedächtnis hantieren :)

+0

Mit dem aktuellen Setup verwende ich 4 Bytes pro Element - d. H. Nicht mehr als die Elementgröße. Es geht um Effizienz: Das ist wichtig, wenn Sie viele tausend Elemente haben. – doublep

+0

"Ihre Lösung hat leider ein großes Problem: Sie ist falsch" - können Sie das näher erläutern? – doublep

+0

den nächsten Satz lesen: sagen 'T' ist ein' Int' und ich zuweisen 0 bis es, dann wird Ihr Test sagen: „unitialized“, während es initialisiert (auf 0). Sie verwenden grundsätzlich einen "Magischen Wert", aber ohne die Garantie, dass Ihr "Magischer Wert" außerhalb der Domäne bedeutungsvoller Werte liegt. Aus diesem Grund benötigen Sie mindestens ein weiteres Bit, um Informationen zu speichern. –

0

Verwenden Sie einen 33-Bit-Computer. ;-P

-2

Es fühlt sich an wie ein Hack, aber anscheinend fand ich eine Lösung: mit volatile für Int Casting. Im Grunde, was ich tue jetzt:

T x = some_other_t; 
if (*reinterpret_cast <volatile Int*> (&x) == 0) 
    ... 

Das Problem mit bitfield T ist jetzt weg. Trotzdem ich nicht ganz glücklich fühlen darüber, wie volatile ist in C nicht gut definiert ++ AFAIK ...

+0

Sie könnten etwas unternehmen. Ich hoffe, die Sprachanwälte werden das sehen und kommentieren. –

+0

Ich bin kein Experte in diesen Fragen, aber mein Verständnis des 'volatile' Schlüsselwort ist, dass sein einziger Effekt ist, wird den Compiler, um sicherzustellen, keine Optimierungen machen, die zu fehlerhaftem Verhalten führen würden, wenn die Variable beispielsweise bezieht sich ein E/A-Register, d.h. etwas, das extern modifiziert werden kann. Was Sie oben haben, ist keine dieser Situationen, Sie können sich also wahrscheinlich nicht auf "flüchtig" verlassen, um korrektes Verhalten zu garantieren. Mit anderen Worten, es ist keine Problemumgehung für die strengen Aliasing-Regeln. –

+0

@Oli Charlesworth: Von der Spezifikation: [Hinweis: flüchtig ist ein Hinweis auf die Umsetzung aggressive Optimierung die das Objekt zu vermeiden, da der Wert des Objekts kann durch nicht nachweisbar durch eine Implementierung geändert werden. Eine detaillierte Semantik finden Sie in 1.9. In der Regel wird die Semantik von flüchtigen soll das gleiche in C++ sein, wie sie in C sind - Endnote] würde ich sagen, dieser Fall paßt Definition „könnte durch nicht nachweisbar durch eine Implementierung geändert werden“, obwohl es natürlich umstritten. – doublep

1

Warum nicht einfach:

const Int zero = 0; 
if (memcmp(&some_other_t, &zero, sizeof(zero)) == 0) 
    /* some_other_t is 0 */ 

(Sie können auch die static Qualifier zero hinzuzufügen, um zu versuchen, um zu sehen, ob es einen Unterschied Performance-weise macht)