c++ - libro - ¿Es esto un error de optimización del compilador o un comportamiento indefinido?
manual completo de c++ pdf (2)
Este es un error de optimizador de código. Incluye tanto getData () como SetBit (). La combinación parece ser fatal, pierde la pista del valor de 1 << ((pos) & 7) y siempre produce cero.
Este error no ocurre en VS2012. Una solución alternativa es forzar una de las funciones para que no se inline. Dado el código, es probable que desee hacer eso para getData ():
__declspec(noinline)
size_t getData(size_t p_value)
{
// etc..
}
Tenemos un error molesto que no puedo explicar en torno a este fragmento de código:
unsigned char bitmap[K_BITMAP_SIZE] = {0} ;
SetBit(bitmap, K_18); // Sets the bit #18 to 1
for(size_t i = 0; i < K_END; ++i)
{
if(TestBit(bitmap, i)) // true for 18
{
size_t i2 = getData(i); // for 18, will return 15
SetBit(bitmap, i2); // BUG: IS SUPPOSED TO set the bit #15 to 1
}
}
- Sucede en Visual C ++ 2010
- Sucede tanto en compilaciones de 32 bits como de 64 bits
- Sucede solo en versiones de lanzamiento (con el conjunto "Maximizar velocidad (/ O2)"
- No ocurre solo en las compilaciones de Release con el conjunto "Minimize Size (/ O1)"
- Sucede en Visual C ++ 2008 solo si
__forceinline
la función getData (de forma predeterminada, VC ++ 2008 no incorpora esa función, mientras que VC ++ 2010 sí) - Sucede en la pieza de código que se da a continuación, probablemente porque la incorporación masiva dentro del ciclo
- No sucede si eliminamos el bucle y establecemos directamente el valor interesante (18)
Información de bonificación:
1- BenJ comentó que el problema no aparece en Visual C ++ 2012, lo que significa que podría ser un error en el compilador
2- Si agregamos un molde a unsigned char
en las funciones de Prueba / Ajuste / Reinicio, el error desaparece, también
size_t TestBit(const unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) & (1 << (unsigned char)((pos) & 7))) ; }
size_t SetBit(unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) |= (1 << (unsigned char)((pos) & 7))) ; }
size_t ResetBit(unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) &= ~(1 << (unsigned char)((pos) & 7))) ; }
La pregunta es:
¿Este error ocurre porque nuestro código se basa en un comportamiento indefinido o hay algún error en el compilador de VC ++ 2010?
La siguiente fuente es autosuficiente y se puede compilar como tal en su compilador favorito:
#include <iostream>
const size_t K_UNKNOWN = (-1) ;
const size_t K_START = (0) ;
const size_t K_12 = (K_START + 12) ;
const size_t K_13 = (K_START + 13) ;
const size_t K_15 = (K_START + 15) ;
const size_t K_18 = (K_START + 18) ;
const size_t K_26 = (K_START + 26) ;
const size_t K_27 = (K_START + 27) ;
const size_t K_107 = (K_START + 107) ;
const size_t K_128 = (K_START + 128) ;
const size_t K_END = (K_START + 208) ;
const size_t K_BITMAP_SIZE = ((K_END/8) + 1) ;
size_t TestBit(const unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) & (1 << ((pos) & 7))) ; }
size_t SetBit(unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) |= (1 << ((pos) & 7))) ; }
size_t ResetBit(unsigned char * bits, size_t pos) { return (((bits)[(pos) >> 3]) &= ~(1 << ((pos) & 7))) ; }
size_t getData(size_t p_value)
{
size_t value = K_UNKNOWN;
switch(p_value)
{
case K_13: value = K_12; break;
case K_18: value = K_15; break;
case K_107: value = K_15; break;
case K_27: value = K_26; break;
case K_128: value = K_12; break;
default: value = p_value; break;
}
return value;
}
void testBug(const unsigned char * p_bitmap)
{
const size_t byte = p_bitmap[1] ;
const size_t bit = 1 << 7 ;
const size_t value = byte & bit ;
if(value == 0)
{
std::cout << "ERROR : The bit 15 should NOT be 0" << std::endl ;
}
else
{
std::cout << "Ok : The bit 15 is 1" << std::endl ;
}
}
int main(int argc, char * argv[])
{
unsigned char bitmap[K_BITMAP_SIZE] = {0} ;
SetBit(bitmap, K_18);
for(size_t i = 0; i < K_END; ++i)
{
if(TestBit(bitmap, i))
{
size_t i2 = getData(i);
SetBit(bitmap, i2);
}
}
testBug(bitmap) ;
return 0;
}
Alguna información de fondo: Inicialmente:
- las funciones Test / Set / ResetBit fueron macros.
- las constantes fueron definidas
- los índices eran
long
oint
(en Windows 32 bits, tienen el mismo tamaño)
Si es necesario, agregaré algunos datos más (por ejemplo, el ensamblador generado para ambas configuraciones, la actualización sobre cómo manejar el problema), tan pronto como sea posible.
Addendum 2 La parte más pequeña posible del código OP se da a continuación. Este fragmento conduce a dicho error del optimizador en VS2010, que depende del contenido de GetData()
expandido en línea. Incluso si uno combina los dos retornos en GetData()
en uno, el error "se fue". Además, no produce un error si combina bits solo en el primer byte (como el char bitmap[1];
necesita dos bytes).
El problema no ocurre en VS2012. Esto se siente horrible porque MS lo solucionó, obviamente, en 2012, pero no en 2010. ¿WTF?
Por cierto:
- g ++ 4.6.2 x64 (-O3) - ok
- icpc 12.1.0 x64 (-O3) - ok
Verificación de errores del optimizador VS2010:
#include <iostream>
const size_t B_5=5, B_9=9;
size_t GetBit(unsigned char * b, size_t p) { return b[p>>3] & (1 << (p & 7)); }
void SetBit(unsigned char * b, size_t p) { b[p>>3] |= (1 << (p & 7)); }
size_t GetData(size_t p) {
if (p == B_5) return B_9;
return 0;
}
/* SetBit-invocation will fail (write 0)
if inline-expanded in the vicinity of the GetData function, VS2010 */
int main(int argc, char * argv[])
{
unsigned char bitmap[2] = { 0, 0 };
SetBit(bitmap, B_5);
for(size_t i=0; i<2*8; ++i) {
if( GetBit(bitmap, i) ) // no difference if temporary variable used,
SetBit(bitmap, GetData(i)); // the optimizer will drop it anyway
}
const size_t byte=bitmap[1], bit=1<<1, value=byte & bit;
std::cout << (value == 0 ? "ERROR: The bit 9 should NOT be 0"
: "Ok: The bit 9 is 1") << std::endl;
return 0;
}
Después de una inspección, uno puede ver que la parte de inicialización / puesta a cero no es parte de este problema específico.
Miró nuevamente después de la comida. Parece ser un error de propagación char / int. Se puede curar cambiando las funciones de máscara (como ya se ha descubierto por el OP) a:
size_t TestBit (const unsigned char * bits, size_t pos) {
return (bits)[pos >> 3] & (1 << ( char(pos) & 7) ) ;
}
size_t SetBit (unsigned char * bits, size_t pos) {
return (bits)[pos >> 3] |= (1 << ( char(pos) & 7) ) ;
}
size_t ResetBit (unsigned char * bits, size_t pos) {
return (bits)[pos >> 3] &= ~(1 << ( char(pos) & 7) ) ;
}
al convertir la posición de posición int en tamaño char. Esto llevará al optimizador en VS2010 a hacer lo correcto. Quizás alguien pueda comentar.