2010-05-14 20 views
10

Hoy descubrí un comportamiento alarmante al experimentar con campos de bits. Por el bien de la discusión y la simplicidad, aquí es un programa de ejemplo:GCC, -O2 y bitfields: ¿se trata de un error o una característica?

#include <stdio.h> 

struct Node 
{ 
    int a:16 __attribute__ ((packed)); 
    int b:16 __attribute__ ((packed)); 

    unsigned int c:27 __attribute__ ((packed)); 
    unsigned int d:3 __attribute__ ((packed)); 
    unsigned int e:2 __attribute__ ((packed)); 
}; 

int main (int argc, char *argv[]) 
{ 
    Node n; 
    n.a = 12345; 
    n.b = -23456; 
    n.c = 0x7ffffff; 
    n.d = 0x7; 
    n.e = 0x3; 

    printf("3-bit field cast to int: %d\n",(int)n.d); 

    n.d++; 

    printf("3-bit field cast to int: %d\n",(int)n.d); 
} 

El programa está causando el propósito campo de bits de 3 bits se desborde. Aquí está la salida (correcto) cuando se compila utilizando "g ++ -O0":

3-bit fundido campo para INT: 7

3-bit fundido campo para INT: 0

Así es la salida cuando se compila utilizando "g ++ -O2" (y -O3):

3-bit fundido campo para iNT: 7

fundido campo de 3 bits a int: 8

Comprobación del montaje de este último ejemplo, encontré esto:

movl $7, %esi 
movl $.LC1, %edi 
xorl %eax, %eax 
call printf 
movl $8, %esi 
movl $.LC1, %edi 
xorl %eax, %eax 
call printf 
xorl %eax, %eax 
addq $8, %rsp 

Las optimizaciones han acaba de insertar "8", suponiendo 7 + 1 = 8, cuando en realidad el número desborda y es cero

Afortunadamente, el código que me importa no se desborda por lo que yo sé, pero esta situación me asusta: ¿se trata de un error conocido, una característica o es este comportamiento esperado? ¿Cuándo puedo esperar que gcc tenga razón sobre esto?

Editar (re: firmado/sin signo):

Está siendo tratado como sin firmar porque está declarado como sin signo. Declarar como int se obtiene la salida (con O0):

3-bits fundido campo para INT: -1

3-bits fundido campo para INT: 0

Un incluso lo más divertido sucede con O2 en este caso:

3-bits fundido campo a int: 7

3-bits fundido campo para iNT: 8

Admito que atributo es algo sospechoso de usar; en este caso, me preocupa una diferencia en la configuración de optimización.

+1

chedcked gcc 4.4.1 - la salida es 7/0 con/sin optimización – dimba

+0

Admito que estoy usando 4.1.2 - gracias por el aviso. – Rooke

+0

¿Qué sucede si pones '__attribute __ ((__ packed __))' después de toda la estructura? –

Respuesta

8

Si desea obtener información técnica, el minuto en que usó __attribute__ (un identificador que contiene dos guiones bajos consecutivos) su código tiene/tenía un comportamiento indefinido.

Si obtiene el mismo comportamiento con los eliminados, me parece un error del compilador. El hecho de que un campo de 3 bits se trate como 7 significa que está siendo tratado como no firmado, por lo que cuando se desborde debería funcionar como cualquier otro sin signo, y le da aritmética de módulo.

También sería legítimo que trate el campo de bits como firmado. En este caso, el primer resultado sería -1, -3 o -0 (que podría imprimirse como 0), y el segundo indefinido (ya que el desbordamiento de un entero con signo proporciona un comportamiento indefinido). En teoría, otros valores podrían ser posibles bajo C89 o el estándar actual de C++, ya que no limitan las representaciones de los enteros con signo. En C99 o C++ 0x, solo pueden ser esos tres (C99 límites firmados enteros para el complemento de uno, complemento de dos o magnitud de signo y C++ 0x se basa en C99 en lugar de C90).

Oops: No presté suficiente atención, ya que se define como unsigned, tiene que tratarse como unsigned, dejando poco margen de maniobra para salir de su error de compilación.

+0

Mejor respuesta, +1 – WhirlWind

+0

La eliminación de la __tribución __ también da un comportamiento incorrecto con -O2, pero no con O0 (según el código original). ¡Parece que necesito usar gcc 4.4! – Rooke

Cuestiones relacionadas