2009-10-20 8 views
18

De acuerdo con el estándar C/C++ (see this link), el operador >> en C y C++ no es necesariamente un desplazamiento aritmético para números con signo. Depende de la implementación del compilador si los 0 (lógico) o el bit de signo (aritmética) se desplazan a medida que los bits se desplazan hacia la derecha.Verificando que el desplazamiento a la derecha con signo C/C++ es aritmético para un compilador en particular?

¿Este código funcionará para ASSERT (error) en tiempo de compilación para compiladores que implementan un desplazamiento lógico a la derecha para enteros con signo?

#define COMPILE_TIME_ASSERT(EXP) \ 
    typedef int CompileTimeAssertType##__LINE__[(EXP) ? 1 : -1] 

#define RIGHT_SHIFT_IS_ARITHMETIC \ 
    ((((signed int)-1)>>1) == ((signed int)-1)) 

// SHR must be arithmetic to use this code 
COMPILE_TIME_ASSERT(RIGHT_SHIFT_IS_ARITHMETIC); 
+3

¿Cuál es su compilación fallida para aquellos que tienen una máquina que usa un cambio lógico? ¿Por qué su software no va a ser utilizable en una máquina/compilador de este tipo? ¿No sería mejor escribir el código para que funcione independientemente de si el desplazamiento correcto de un número firmado es aritmético o lógico? –

+6

Estoy usando la selección sin ramificación (BFS) a través del intercambio de bits. Requiere un cambio aritmético para funcionar. Estoy colocando el COMPILE_TIME_ASSERT (RIGHT_SHIFT_IS_ARITHMETIC); en el encabezado BFS. El código debe usar la definición RIGHT_SHIFT_IS_ARITHMETIC para seleccionar las rutas tradicionales o sin ramificaciones. Puede ser una aceleración masiva en la CPU de PS3/XBOX360 el uso de código sin ramificación debido a penalizaciones por errores de trazado de la bifurcación. – Adisak

+1

BTW, la compilación fallida en un momento de compilación afirman que la razón se menciona explícitamente es mejor que simplemente tener el código misteriosamente fallado ... básicamente va a decir que estas rutinas no son compatibles con este compilador (o CPU). – Adisak

Respuesta

6

Se ve bien para mí! También puede configurar el compilador para que emita un archivo de ensamblaje (o cargue el programa compilado en el depurador) y observe qué código de operación emite para signed int i; i >> 1;, pero eso no es automático como su solución.

Si alguna vez encuentra un compilador que no implementa el desplazamiento aritmético a la derecha de un número firmado, me gustaría saberlo.

+0

Sí, yo básicamente quiero que sea automático ... mirar los códigos de operación no es una expectativa razonable para este requerimiento porque lo estoy usando en una biblioteca que otros equipos pueden usar en varias plataformas. – Adisak

+0

El compilador del [sistema UNISYS 2200] (http://stackoverflow.com/a/12277974/995714) utiliza el desplazamiento lógico a la derecha para los tipos firmados. "El resultado de la expresión E1 >> E2 es que E1 (interpretado como un patrón de bits) se desplaza a las posiciones de bit E2 correctas. El desplazamiento a la derecha es * lógico (es decir, lleno de cero en la izquierda) incluso si E1 expresión es un tipo entero con signo *. " http://public.support.unisys.com/2200/docs/cp14.0/pdf/78310422-011.pdf –

1

¿Por qué afirmar? Si el operador de desplazamiento de su compilador no se ajusta a sus necesidades, puede corregir la situación con agrado extendiendo el resultado. Además, a veces el tiempo de ejecución es lo suficientemente bueno. Después de todo, el optimizador del compilador puede hacer que el tiempo de compilación de tiempo de ejecución:

template <typename Number> 
inline Number shift_logical_right(Number value, size_t bits) 
{ 
    static const bool shift_is_arithmetic = (Number(-1) >> 1) == Number(-1); 
    const bool negative = value < 0; 
    value >>= bits; 
    if (!shift_is_arithmetic && negative) // sign extend 
     value |= -(Number(1) << (sizeof(Number) * 8 - bits)); 
} 

El static const bool se puede evaluar en tiempo de compilación, por lo que si shift_is_arithmetic se garantiza que sea true, cada compilador que se precie va a eliminar la entero if cláusula y la construcción de const bool negative como código muerto.

Nota: el código está adaptado de la función encode_sleb128 de Mono: here.

actualización

Si realmente desea abortar la compilación de las máquinas sin desplazamiento aritmético, todavía estás mejor no confiar en el preprocesador. Puede utilizar static_assert (o BOOST_STATIC_ASSERT):

static_assert((Number(-1) >> 1) == Number(-1), "Arithmetic shift unsupported."); 
+0

Hay un montón de código (por ejemplo, Branch-Free-Selection que usa máscaras) que solo tiene sentido para usar en compiladores con desplazamiento aritmético firmado. Emular con gracia un cambio aritmético en ellos probablemente sería significativamente más lento que el código original, eliminando así la "optimización". – Adisak

+0

Bastante justo. Sin embargo, mi segundo punto sigue siendo válido: no confíe en el preprocesador. Ver actualización – marton78

+1

static_assert() es C++ 11x y no podemos usar boost. Pero el control que estaba haciendo no depende del preprocesador, podría hacer lo siguiente, pero es mucho más difícil de leer y entender que el ejemplo que escribí: 'typedef int CompileTimeAssertArithmeticShift [((((signed) -1) >> 1) == ((firmado int) -1))? 1: -1]; ' – Adisak

0

Desde sus diversos comentarios, se habla de utilizar este multiplataforma. Asegúrese de que sus compiladores le garanticen que cuando compilan para una plataforma, sus operadores en tiempo de compilación se comportarán igual que los de tiempo de ejecución.

Se puede encontrar un ejemplo de comportamiento diferente con números de coma flotante. ¿Está haciendo su compilador su matemática de expresiones constantes en precisión simple, doble o extendida si está volviendo a int? Tal como

constexpr int a = 41; constexpr int b = (a/7.5);

Lo que estoy diciendo es que debes asegurarte de que tus compiladores garantizan el mismo comportamiento durante el tiempo de ejecución que el tiempo de compilación cuando trabajas en tantas arquitecturas diferentes.

Es muy posible que un compilador pueda extenderse internamente pero no generar los códigos de operación previstos en el destino. La única forma de estar seguro es probar en tiempo de ejecución o mirar la salida del conjunto.

No es el fin del mundo para ver la salida de montaje ... ¿Cuántas plataformas diferentes hay? Como esto es tan crítico para el rendimiento, simplemente haga el "trabajo" de mirar 1-3 líneas de salida de ensamblador para 5 arquitecturas diferentes.No es como si tuviera que bucear a través de una salida de conjunto (¡normalmente!) Para encontrar su línea. Es muy, muy fácil de hacer.

Cuestiones relacionadas