2010-06-05 16 views
8

Tengo un problema causado por romper la regla de alias de puntero estricta. Tengo un tipo T que viene de una plantilla y un tipo integral Int del mismo tamaño (como con sizeof). Mi código hace esencialmente las siguientes:Alias ​​de puntero estricto: ¿alguna solución para un problema específico?

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

Debido a que es un poco de T arbitrario (que no sea la restricción de tamaño) tipo que pudiesen tener un constructor, no puedo hacer una unión de T y Int. (Esto solo está permitido en C++ 0x y aún no es compatible con GCC).

¿Hay alguna manera de reescribir el pseudocódigo anterior para preservar la funcionalidad y evitar romper la regla de aliasing estricta? Tenga en cuenta que esta es una plantilla, no puedo controlar T o el valor de some_other_t; la asignación y la comparación posterior ocurren dentro del código de plantilla.

(Para el registro, el código anterior empezó a romper en GCC 4.5 si T contiene ningún campos de bits.)

+1

¿Qué estás tratando de hacer? No puedo pensar en muchas situaciones en las que ese código sea significativo. Ciertamente no está bien especificado por el estándar. Asumiendo que este truco es realmente necesario (lo cual probablemente no es), puede que tenga que usar el indicador del compilador apropiado para deshabilitar el alias estricto. – jalf

+0

@jalf: es un contenedor único. Marcaré las posiciones vacías con la integral 0. Sin embargo, dado que 'T' puede ser cualquier cosa, incluido el bit a bit 0, necesito marcar como máximo una posición como" no está vacía, aunque parezca vacía ". La comparación es la comprobación de si 'x' debe marcarse así o no. – doublep

+0

No tengo claro cómo se está resolviendo este problema. ¿Cómo puede un 'reinterpret_cast' ignorar las dos razones diferentes para almacenar' 0'? – Stephen

Respuesta

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

En mi sistema, GCC optimiza la distancia tanto is_T_0() y memcpy(), lo que resulta en tan sólo unas pocas instrucciones de montaje en myfunc().

+0

Según [la otra pregunta] (http: // stackoverflow.com/questions/2981827/strict-pointer-aliasing-is-access-through-a-volatile-pointer-reference-a-solut), este es el camino a seguir. – doublep

+0

Personalmente, haría el uso más limpio (para mí) haciendo 'is_T_0' tomar' const T & ', pero debería asegurarse de que GCC aún optimice todo en ese caso. –

1

¿Qué tal esto:

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

puede que no sea tan eficiente, pero debería deshacerse de la advertencia.


ADDENDUM # 1:

Desde T está restringida a ser del mismo tamaño que Int, te haces un valor ficticio bit a bit cero del tipo T y comparar directamente contra ella (en lugar de la fundición y la comparación de Agaist Int(0))

Si su programa es de un solo subproceso, usted podría tener algo como esto:

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 es multi-hilo, tendrá que evitar el uso de un miembro estático zero_, y que cada instancia del contenedor mantenga su propio miembro 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_; 
}; 

ADDENDUM # 2:

me deja poner por encima de la adenda en otra, manera más sencilla:

// 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

Votación máxima porque esto parece funcionar bien. Sin embargo, me gustaría una solución mejor ... – doublep

+0

@doublep: Agregué otra solución más eficiente a mi respuesta. –

+0

Desafortunadamente, esto es atroz para el rendimiento. Cuando 'T' en sí mismo es' int', estoy recibiendo 2.5 veces más ralentización en GCC 4.5-O2 (esta es una prueba compuesta de uso "casi real"). Aparentemente, GCC no puede optimizar 'memcmp()' de distancia. – doublep

1

¿Ha oído hablar de boost::optional?

Debo admitir que no tengo claro el problema real aquí ... pero boost :: optional permite almacenar por valor y aún saber si la memoria real se ha inicializado o no. También permití la construcción y la destrucción, así que podría ser una buena opción, supongo.

EDITAR:

Creo que finalmente comprendido el problema: usted quiere ser capaz de asignar una gran cantidad de objetos, en varios puntos de la memoria, y que le gustaría saber si la memoria o no en este punto realmente tiene un objeto o no.

Lamentablemente su solución tiene un gran problema: es incorrecta. Si alguna vez T se puede representar de alguna manera mediante un patrón de bits null, entonces creerá que se trata de una memoria unificada.

Tendrá que recurrir a sí mismo para agregar al menos un poco de información. En realidad no es mucho, después de todo, es solo un 3% de crecimiento (33 bits para 4 bytes).

Podría, por ejemplo, utilizar algunos mimick boost::optional pero en forma de matriz (para evitar la pérdida de relleno).

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

Entonces es tan simple como eso:

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: Por razones de simplicidad no he considerado la cuestión de la alineación. Si no sabe sobre el tema, lea sobre él antes de juguetear con la memoria :)

+0

Con la configuración actual, estoy usando 4 bytes por elemento, es decir, no más que el tamaño del elemento. Se trata de eficiencia: esto importa cuando tienes muchos miles de elementos. – doublep

+0

"Desafortunadamente, su solución tiene un gran problema: es incorrecta". ¿Puede dar más detalles sobre esto? – doublep

+0

Lea la siguiente oración: diga 'T' es un' Int' y le asigno 0, luego su prueba dirá: "unificado" mientras está inicializado (a 0). Básicamente estás usando un "valor mágico" pero sin ninguna garantía de que tu "valor mágico" esté fuera del dominio de valores significativos. Es por eso que necesita al menos un bit más para almacenar información. –

0

Utilice una computadora de 33 bits. ;-P

-2

Se siente como un truco, pero al parecer encontré una solución: usando volatile para Int de fundición. En esencia, lo que estoy haciendo ahora es:

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

El problema con el campo de bits T se ha ido. Aún así, no me siento muy feliz por esto como volatile no está bien definido en C++ que yo sepa ...

+0

Puedes estar en algo. Espero que los abogados de idiomas lo vean y comenten. –

+0

No soy un experto en estas cuestiones, pero mi comprensión de la palabra clave 'volátil' es que su único efecto es garantizar que el compilador no realice ninguna optimización que pueda generar un comportamiento incorrecto si la variable hace referencia a, p. un registro de E/S, es decir, algo que puede modificarse externamente. Lo que tienes arriba no es una de estas situaciones, por lo que probablemente no puedas confiar en 'volátil' para garantizar un comportamiento correcto. En otras palabras, no es una solución para las reglas de alias estrictos. –

+0

@Oli Charlesworth: De la especificación: [Nota: volátil es una sugerencia para la implementación para evitar la optimización agresiva que involucra el objeto porque el valor del objeto puede ser cambiado por medios indetectables por una implementación. Ver 1.9 para una semántica detallada. En general, la semántica de los volátiles está destinada a ser la misma en C++ que en C. - nota final] Diría que este caso se ajusta a la definición de "podría ser cambiado por medio de una definición de implementación indetectable", aunque es por supuesto discutible. – doublep

1

Por qué no simplemente:

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

(es posible que desee para tratar de añadir también el calificador static a zero para ver si se hace una en cuanto al rendimiento de diferencia)

Cuestiones relacionadas