Este código fuente está activando una cadena en C. ¿Cómo lo hace?

106

Estoy leyendo un código de emulador y he respondido a algo realmente extraño:

switch (reg){
    case 'eax':
    /* and so on*/
}

¿Cómo es esto posible? Pensé que solo podrías switchen tipos integrales. ¿Hay algún truco macro?

Ian Colton
fuente
29
no es la cadena 'eax'y enumera el valor entero constante
P__J__
12
Comillas simples, no dobles. Se promueve una constante de carácter int, por lo que es legal. Sin embargo, el valor de una constante de varios caracteres está definido por la implementación, por lo que es posible que el código no funcione como se esperaba en otro compilador. Por ejemplo, eaxpodría ser 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, o alguna otra cosa.
Davislor
2
@Davislor: dado el nombre de la variable "reg", y el hecho de que eax es un registro x86, supongo que el comportamiento definido por la implementación estaba destinado a ser correcto, porque es el mismo en todos los lugares del código. Siempre y cuando 'eax' != 'ebx', por supuesto, solo falle uno o dos de sus ejemplos. Aunque puede haber algún código en algún lugar que de hecho asume *(int*)("eax") == 'eax'y, por lo tanto, falla en la mayoría de sus ejemplos.
Steve Jessop
2
@SteveJessop No estoy en desacuerdo con lo que dice, pero existe el peligro real de que alguien intente compilar el código en un compilador diferente, incluso para la misma arquitectura, y obtenga un comportamiento diferente. Por ejemplo, 'eax'podría comparar igual ao 'ebx'a 'ax', y la instrucción de cambio no funcionaría como se esperaba.
Davislor
1
Todo ese misterio se habría disipado rápidamente si hubiera mirado hacia arriba / nos hubiera mostrado el tipo de datos de reg.
THS

Respuestas:

146

(Solo usted puede responder a la parte del "truco de macros", a menos que pegue más código. Pero aquí no hay mucho en lo que puedan trabajar las macros, formalmente no se le permite redefinir palabras clave ; el comportamiento al hacerlo no está definido).

Para lograr la legibilidad del programa, el desarrollador ingenioso está explotando el comportamiento definido por la implementación . no'eax' es una cadena, sino una constante de varios caracteres . Tenga en cuenta con mucho cuidado los caracteres de comillas simples alrededor . Lo más probable es que en tu caso te esté dando una que sea exclusiva de esa combinación de caracteres. (Muy a menudo, cada carácter ocupa 8 bits en 32 bits ). ¡Y todo el mundo sabe que puedes hacerlo !eaxintintswitchint

Finalmente, una referencia estándar:

El estándar C99 dice:

6.4.4.4p10: "El valor de una constante de carácter entero que contiene más de un carácter (por ejemplo, 'ab'), o que contiene un carácter o secuencia de escape que no se corresponde con un carácter de ejecución de un solo byte, está definido por la implementación. "

Betsabé
fuente
55
En caso de que alguien vea eso y entre en pánico, se requiere que la "implementación definida" funcione y su compilador lo documente de alguna manera apropiada (el estándar no requiere que el comportamiento sea intuitivo o que la documentación sea buena, pero ...). Esto es "seguro" de usar para un codificador que entiende completamente lo que está escribiendo, en lugar de "indefinido".
Leushenko
7
@Justin Si bien podría, sería bastante perverso. Si no hace lo que la respuesta sugiere que es más probable, la siguiente posibilidad es probablemente que solo use el primer carácter e ignore el resto.
Barmar
5
@ZanLynx No estoy seguro, pero creo que la función es anterior a Unicode y otros estándares MBCS. Los "números mágicos" que parecen texto en volcados de memoria y los ID de fragmentos de formato de archivo al estilo RIFF fueron las primeras aplicaciones que conozco.
Russell Borogove
16
@ jpmc26 Este no es un comportamiento indefinido, está definido por la implementación. Entonces, a menos que la documentación del compilador mencione demonios, su nariz está a salvo.
Barmar
7
@ZanLynx: Me temo que la intención original es anterior a Unicode, UTF-8 y cualquier codificación de caracteres multibyte por casi 20 años. La constante de varios caracteres era solo una forma práctica de expresar números enteros que representan grupos de 2, 3 o 4 bytes (dependiendo de los tamaños de bytes e int). Las inconsistencias entre las implementaciones y arquitecturas llevaron al comité a declarar esto como una implementación definida , lo que significa que no hay una forma portátil de calcular el valor de 'ab'from 'a'y 'b'.
chqrlie
45

De acuerdo con el estándar C (6.8.4.2 La declaración de cambio)

3 La expresión de cada etiqueta de caso será una expresión constante entera ...

y (6.6 Expresiones constantes)

6 Una expresión de constante entera tendrá tipo entero y solo tendrá operandos que sean constantes enteras, constantes de enumeración, constantes de caracteres , tamaño de expresiones cuyos resultados sean constantes enteras y constantes flotantes que sean los operandos inmediatos de conversiones. Los operadores de conversión en una expresión constante entera solo convertirán tipos aritméticos en tipos enteros, excepto como parte de un operando al operador sizeof.

Ahora, ¿qué es 'eax'?

El estándar C (6.4.4.4 Constantes de caracteres)

2 Una constante de carácter entero es una secuencia de uno o más caracteres multibyte entre comillas simples , como en 'x' ...

Entonces 'eax'es una constante de carácter entero de acuerdo con el párrafo 10 de la misma sección

  1. ... El valor de una constante de carácter entero que contiene más de un carácter (por ejemplo, 'ab'), o que contiene un carácter o secuencia de escape que no se asigna a un carácter de ejecución de un solo byte, está definido por la implementación.

Entonces, de acuerdo con la primera cita mencionada, puede ser un operando de una expresión constante entera que se puede usar como etiqueta de caso.

Preste atención a que una constante de carácter (entre comillas simples) tiene tipo inty no es lo mismo que un literal de cadena (una secuencia de caracteres entre comillas dobles) que tiene un tipo de matriz de caracteres.

Vlad de Moscú
fuente
12

Como han dicho otros, esta es una intconstante y su valor real está definido por la implementación.

Supongo que el resto del código se parece a

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

Puede estar seguro de que 'eax' en la primera parte tiene el mismo valor que 'eax' en la segunda parte, así que todo funciona, ¿verdad? ... incorrecto.

En un comentario, @Davislor enumera algunos valores posibles para 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, o alguna otra cosa

¿Observa el primer valor potencial? Es decir 'e', ignorando a los otros dos personajes. El problema es, probablemente, el programa utiliza 'eax', 'ebx'y así sucesivamente. Si todas estas constantes tienen el mismo valor que el 'e'que terminas con

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

Esto no se ve muy bien, ¿verdad?

Lo bueno de "definido por implementación" es que el programador puede verificar la documentación de su compilador y ver si hace algo sensato con estas constantes. Si es así, casa gratis.

La parte mala es que algún otro pobre puede tomar el código e intentar compilarlo usando algún otro compilador. Error de compilación instantánea. El programa no es portátil.

Como @zwol señaló en los comentarios, la situación no es tan mala como pensaba, en el mal caso, el código no se compila. Esto le dará al menos un nombre de archivo exacto y un número de línea para el problema. Aún así, no tendrá un programa que funcione.

Stig Hemmer
fuente
1
Aparte de alguna forma de, assert('eax' != 'ebx'); //if this fails you can't compile the code because...¿hay algo que el autor original pudiera hacer para evitar otras fallas del compilador sin reemplazar la construcción por completo?>
Dan Is Fiddling By Firelight
6
Dos etiquetas de caso con el mismo valor son una violación de la restricción (6.8.4.2p3: "... dos de las expresiones de la constante de caso en la misma instrucción de cambio no tendrán el mismo valor después de la conversión"), así que, siempre que todo el código trata los valores de estas constantes como opacos, se garantiza que funcionará o no se compilará.
zwol
La peor parte es que el pobre compañero que compila en otro compilador probablemente no verá ningún error en tiempo de compilación (encender ints está bien); en cambio, aparecerán errores en tiempo de ejecución ...
tucuxi
1

El fragmento de código utiliza una rareza histórica llamada constante de caracteres de varios caracteres , también conocida como varios caracteres .

'eax' es una constante entera cuyo valor está definido por la implementación.

Aquí hay una página interesante sobre varios caracteres y cómo se pueden usar, pero no deben:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


Mirando hacia atrás en el espejo retrovisor, así es como el manual C original de Dennis Ritchie de los buenos viejos tiempos ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) especificaba las constantes de caracteres .

2.3.2 Constantes de caracteres

Una constante de carácter tiene 1 o 2 caracteres entre comillas simples '' '''. Dentro de una constante de carácter, una comilla simple debe ir precedida de una barra invertida '' \''. Ciertos caracteres no gráficos, y '' \'' en sí mismo, se pueden escapar de acuerdo con la siguiente tabla:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

El escape '' \ddd'' consiste en la barra invertida seguida de 1, 2 o 3 dígitos octales que se toman para especificar el valor del carácter deseado. Un caso especial de esta construcción es '' \0'' (no seguido de un dígito) que indica un carácter nulo.

Las constantes de carácter se comportan exactamente como enteros (no, en particular, como objetos de tipo carácter). De acuerdo con la estructura de direccionamiento del PDP-11, una constante de carácter de longitud 1 tiene el código para el carácter dado en el byte de orden inferior y 0 en el byte de orden superior; una constante de carácter de longitud 2 tiene el código para el primer carácter en el byte bajo y el del segundo carácter en el byte de orden superior. Las constantes de caracteres con más de un carácter son inherentemente dependientes de la máquina y deben evitarse.

La última frase es todo lo que necesita recordar acerca de esta curiosa construcción: las constantes de carácter con más de un carácter son inherentemente dependientes de la máquina y deben evitarse.

chqrlie
fuente