2011-12-30 6 views
12

Acabo de encontrar un problema confuso al intentar compilar algún código con g ++ 4.4.3.Código de guardia después de encender la enumeración nunca se alcanza

El siguiente código compila bien, pero en lugar de golpear la aserción de esperar cuando pase un valor de enumeración 'no válido', la función simplemente devuelve 1. Lo que me parece aún más extraño es que cuando elimine el comentario de las líneas pertenecientes a la enumeración E3 valor, las cosas comienzan a funcionar como se esperaba.

El hecho de que no haya una entrada predeterminada en el bloque de interruptor es por diseño. Compilamos con la opción -Wall para obtener advertencias de valores enum no controlados.

enum MyEnum 
{ 
    E1, 
    E2, 
    //E3 
}; 

int doSomethingWithEnum(MyEnum myEnum) 
{ 
    switch (myEnum) 
    { 
     case E1: return 1; 
     case E2: return 2; 
     //case E3: return 3; 
    } 

    assert(!"Should never get here"); 
    return -1; 
} 

int main(int argc, char **argv) 
{ 
    // Should trigger assert, but actually returns 1 
    int retVal = doSomethingWithEnum(static_cast<MyEnum>(4)); 
    std::cout << "RetVal=" << retVal << std::endl; 

    return 0; 
} 
+0

Parece que un error del compilador. GCC 4.6.1 lo maneja bien. –

+0

g ++ 4.3.4 'Prog: prog.cpp: 20: int doSomethingWithEnum (MyEnum): '! "Nunca debe llegar hasta aquí"' aserción failed.' –

+0

De acuerdo con @larsmans, parece ser un error en GCC 4.4. –

Respuesta

9

Su sentencia switch se compila en este (g ++ 4.4.5):

cmpl $1, %eax 
    je  .L3 
    movl $1, %eax 
    jmp  .L4 
.L3: 
    movl $2, %eax 
.L4: 
    leave 
    ret 

Como puede verse, la aserción se optimiza por completo, y el compilador elige el que comparar E2 y volver en 1 todos los demás casos. Con tres valores enum, no puede hacer eso.

Sección 5.2.9 de la C++ 98 estándar (por vaciado estático) da la razón para permitir esto:

Valor de tipo integral o de enumeración se puede convertir explícitamente a un tipo de enumeración. El valor no se modifica si el valor original está dentro del rango de los valores de enumeración (7.2). De lo contrario, el valor de enumeración resultante es sin especificar.

En otras palabras, el compilador es libre de usar cualquier valor enum que desee, (en este caso E1) si intenta usar un valor ilegal. Esto incluye hacer lo intuitivo y usar el valor ilegal proporcionado o usar diferentes valores según las circunstancias, por lo que el comportamiento cambia según el número de valores enum.

+0

Esto tiene sentido, pero todavía me parece extraño que el comportamiento cambie (al comportamiento que esperaba) cuando agrego el valor de la enumeración E3. También traté de agregar un valor de enum E4, y luego el comportamiento es falso de nuevo. Parece un poco indefinido ... – hysj

+2

Es un comportamiento no especificado, como se dice en la cita ("comportamiento, para una construcción de programa bien formada y datos correctos, que depende de la implementación. La implementación no es necesaria para documentar qué comportamiento ocurre . ") Está totalmente a discreción del compilador hacer lo que le gusta para cada caso, incluso hacer lo que tenga sentido para usted. –

+1

IIRC (no estándar aquí) el rango de los valores enum se define para incluir todos los valores O 'junto, para permitir campos de bits enum, en cuyo caso el valor' E3 '(2) abriría el rango de valores hasta 3, que luego no se controla en la instrucción 'switch', abriendo el camino a' assert() '. –

8

No parece un error del compilador. Es más como un comportamiento indefinido. La regla dice que puede atribuir un valor indefinido a un Enum SI este valor está en el rango de la enumeración. En su caso, el compilador solo necesita un bit para representar la enumeración, por lo que probablemente esté haciendo algún tipo de optimización.

+0

Me estoy inclinando hacia un error de compilación también. Intenté imprimir sizeof (MyEnum) y dice que es de 4 bytes. – hysj

+1

No es, en realidad, indefinido. No está especificado y, por lo tanto, el compilador simplemente necesita seleccionar un comportamiento. –

7

Esto es perfectamente normal, su código se basa en un comportamiento no especificado.

En C++, se supone que una enumeración debe contener valores entre sus valores inferior y superior. Cualquier otra cosa puede ser transformada o ignorada por el compilador.

Por ejemplo, si tengo enum Foo { min = 10, max = 11 };, entonces el compilador puede representarlo con un solo bit significativo y agregar 10 cuando solicite imprimirlo o convertirlo en un entero.

En general, sin embargo, los compiladores no aprovechan esta "compactación" debido al costo de rendimiento, simplemente seleccionan un tipo subyacente que puede acomodar todos los valores y 0. La mayoría de las veces es int, a menos que int sea demasiado pequeño.

Sin embargo, no impide que asuman que los valores que este int contienen están restringidos al rango que usted definió. En su caso: [0, 2).

Como tal, la declaración switch simplemente se puede optimizar en una comparación con 0, ya que ha especificado que su enumeración sólo podía ser 0 o 1.

En la misma línea, if (foo == 3) podría considerarse una comparación tautológica (siempre falso) y optimizados de distancia.

De hecho, tal como se describe en el gcc "error" señalado por Hans Passat, esta optimización se produce normalmente en poder de 2s límites para gcc. Por ejemplo, si tenía enum { zero, one, two, three }; y le asignó 4 o superior, se produciría el mismo comportamiento.

Tenga en cuenta que el valor almacenado en realidad no se ve afectada! Esta es la razón por la cual la declaración impresa funciona como esperaba, y es una fuente de confusión.

No sé si hay una advertencia para indicar que el almacenamiento de 4 en esta enumeración dará lugar a un comportamiento no especificado. En cualquier caso, sólo funcionaría para los valores de tiempo de compilación ...


Hans Passat siempre que el gcc "bug" que está abierto a realizar un seguimiento de este "problema".

y Emil Styrke proporcionan la justificación (en este caso es la cotización actualizada para las enumeraciones de ámbito):

5.2.9/10

Un valor de tipo integral o de enumeración se puede convertir explícitamente a un tipo de enumeración. El valor no cambia si el valor original está dentro del rango de los valores de enumeración (7.2). De lo contrario, el valor resultante no está especificado (y podría no estar en ese rango). Un valor de tipo punto flotante también se puede convertir a un tipo de enumeración. El valor resultante es lo mismo que convertir el valor original en el tipo subyacente de la enumeración (4.9) y, posteriormente, en el tipo de enumeración.