Lo que hace el ??!??! operador hacer en C?

1990

Vi una línea de C que se veía así:

!ErrorHasOccured() ??!??! HandleError();

Se compiló correctamente y parece funcionar bien. Parece que está comprobando si ha ocurrido un error, y si lo ha hecho, lo maneja. Pero no estoy realmente seguro de lo que realmente está haciendo o cómo lo está haciendo. Parece que el programador está tratando de expresar sus sentimientos sobre los errores.

Nunca he visto lo ??!??!anterior en ningún lenguaje de programación, y no puedo encontrar documentación para ello en ningún lado. (Google no ayuda con los términos de búsqueda como ??!??!). ¿Qué hace y cómo funciona el código de muestra?

Peter Olson
fuente
44
@PeterOlson, ¿cómo esperas !ErrorHasOccurred() ??!???! HandleError();compilar? Eso es ??! ??? !. Prueba el punto?
un CVn
31
Le sugiero que lea sobre código limpio. ErrorHasOccured () debe ser refactorizado a ErrorHasNotOccured () limpiando así el signo de exclamación ... ¿quién tiene tiempo para comprender todos estos operadores?
KadekM
17
Prefiero preferirme a ErrorHasOccured() && HandleError()mí mismo. Así también lo hace Lua.
Hugo Zink
76
@KadekM, mover la negación al nombre de la función no genera un código limpio, sino todo lo contrario.
marcelm
14
Una nota para cualquiera que terminó aquí después de una pelea a muerte con su motor de búsqueda: SymbolHound puede ayudar con las búsquedas simbólicas.
Jakob

Respuestas:

1579

??!es un trigraph que se traduce |. Entonces dice:

!ErrorHasOccured() || HandleError();

que, debido a cortocircuito, es equivalente a:

if (ErrorHasOccured())
    HandleError();

Gurú de la semana (trata sobre C ++ pero relevante aquí), donde lo recogí.

Posible origen de los trigrafos o como @DwB señala en los comentarios, es más probable debido a que EBCDIC es difícil (nuevamente). Esta discusión en la junta de desarrolladores de IBM parece apoyar esa teoría.

De ISO / IEC 9899: 1999 §5.2.1.1, nota al pie 12 (h / t @ Random832):

Las secuencias trigráficas permiten la entrada de caracteres que no están definidos en el Conjunto de códigos invariantes como se describe en ISO / IEC 646, que es un subconjunto del conjunto de códigos ASCII de EE. UU. De siete bits.

usuario786653
fuente
378
Originalmente, se necesitaban trígrafos en caso de que su teclado no tuviera, por ejemplo, un '|' símbolo. Aquí es o bien el programador deliberadamente molesto o alguna "característica" extraña del editor
Martin Beckett
36
Sí, es equivalente a if (ErrorHasOccured()) HandleError(). Afortunadamente, generalmente solo encuentras este idioma en código perl.
usuario786653
22
No es necesariamente EBCDIC: el conjunto de caracteres que requieren trigrafos coincide casi exactamente con el conjunto de caracteres que no son invariables en ISO-646 (es decir, los viejos estándares 'ascii nacionales').
Random832
52
Una alternativa perfectamente legible sería ErrorHasOccurred() && HandleError();Es decir, si está acostumbrado a ejecutar scripts. :)
Yam Marcovic
18
Léalo como "O no ErrorHasOcurred o debe HandleError", @SparkyRobinson.
Omar Antolín-Camarena
453

Bueno, por qué esto existe en general es probablemente diferente de por qué existe en su ejemplo.

Todo comenzó hace medio siglo con la reutilización de terminales de comunicación impresas como interfaces de usuario de computadora. En la era inicial de Unix y C, ese era el teletipo ASR-33.

Este dispositivo era lento (10 cps), ruidoso y feo, y su vista del conjunto de caracteres ASCII terminaba en 0x5f, por lo que no tenía (mira detenidamente la foto) ninguna de las teclas:

{ | } ~ 

Los trigrafos se definieron para solucionar un problema específico. La idea era que los programas C pudieran usar el subconjunto ASCII que se encuentra en el ASR-33 y en otros entornos sin los valores ASCII altos.

Su ejemplo es en realidad dos de ??!cada significado |, por lo que el resultado es ||.

Sin embargo, las personas que escriben el código C casi por definición tenían un equipo moderno, 1 así que supongo que: alguien presumiendo o divirtiéndose, dejando una especie de huevo de Pascua en el código para que lo encuentres.

Realmente funcionó, condujo a una pregunta SO muy popular.

Teletipo ASR-33

                                            Teletipo ASR-33


1. Para el caso, los trigrafos fueron inventados por el comité ANSI, que se reunió por primera vez después de que C se convirtiera en un éxito desbocado, por lo que ninguno de los códigos o codificadores originales de C los habría utilizado.

DigitalRoss
fuente
18
No es el único caso de caracteres faltantes, en el teclado y el conjunto de caracteres. Es probable que el Commodore 64 sea más familiar para muchas personas de treinta y tantos años en adelante: el personaje que se muestra carece de llaves (y probablemente la barra y la tilde también), en este caso porque el "ASCII" no era ASCII . En ECMA-6 (casi siempre llamado ASCII, pero no US-ASCII) había 18 códigos específicos de la región, pero no sé qué códigos eran. Lo único que puedo decir con certeza - en el "ASCII" británico, #fue reemplazado por £. En otras regiones, tal vez "ASCII" no tenía llaves, etc.
Steve314
77
El conjunto de caracteres ATASCII similar para las computadoras Atari de 8 bits también carecía de {} y ~ y `.
dan04
42
Vea estos dos artículos de Wikipedia. Soy lo suficientemente mayor como para recordar la era de los juegos nacionales de 7 bits (aunque estoy seguro de que aún persisten en algunos rincones oscuros), y el libro del que aprendí C descubrió que era necesario advertir sobre el posibilidad de if (x || y) { a[i] = '\0'; }verse if (x öö y) ä aÄiÅ = 'Ö0'; åen el juego de caracteres incorrecto.
Ilmari Karonen
99
Otra nota histórica interesante es que Unix (que era la gran plataforma en la que C montó) puede haber sido el primer sistema de importancia (y tal vez el primero en general) en predeterminar los valores alfabéticos en minúsculas en lugar de mayúsculas. Aunque no he visto con mis propios ojos muchos sistemas contemporáneos, creo que esto fue un verdadero signo de sofisticación. Además de ser realmente el único sistema operativo decente, Unix también convirtió su mayúscula a menor, en lugar de viceversa. Esos tipos fueron realmente geniales.
DigitalRoss
16
Tengo que contarte una historia divertida ... el compilador XL Fortran de la estación de trabajo IBM RS / 6000 fue desarrollado a partir del compilador XL C. En los primeros lanzamientos, se dejaron accidentalmente en el procesamiento de trigraph, por lo que hubo algunas secuencias de caracteres Fortran legítimas (en una cadena literal, IIRC) que se malinterpretaron como trigraph C, lo que provocó algunos errores interesantes.
Phil Perry
166

Es un C trigraph . ??!es |, también lo ??!??!es el operador||

Joel Falcou
fuente
55
Los trigrafos provienen de un período en el que algunos teclados no tenían todas las teclas que tienen ahora. También ayuda cuando algún editor de texto reserva caracteres especiales para cosas especiales. Es sobre todo una reliquia del pasado y un facilitador de
cuestionarios
55
Debido a que algunos teclados aparentemente no tienen "|" así que algunas personas no tienen otra opción que golpear el teclado repetidamente hasta que se produce un trigrafo que les da los símbolos que necesitan.
Búho
Y luego está el <iso646.h>archivo de encabezado.
David R Tribble
149

Como ya se dijo ??!??! trata esencialmente de dos trigrafos ( ??!y ??!nuevamente) agrupados que se reemplazan y traducen ||, es decir, el OR lógico , por el preprocesador.

La siguiente tabla que contiene cada trigraph debería ayudar a desambiguar combinaciones de trigraph alternativas:

Trigraph   Replaces

??(        [
??)        ]
??<        {
??>        }
??/        \
??'        ^
??=        #
??!        |
??-        ~

Fuente: C: A Reference Manual 5th Edition

Entonces, un trigrafo que parece ??(??)eventualmente mapeado [], ??(??)??(??)será reemplazado por [][]y así sucesivamente, se entiende la idea.

Dado que los trigrafos se sustituyen durante el preprocesamiento, puede usar cpp para obtener una vista de la salida usted mismo, usando un trigr.cprograma tonto :

void main(){ const char *s = "??!??!"; } 

y procesarlo con:

cpp -trigraphs trigr.c 

Obtendrá una salida de consola de

void main(){ const char *s = "||"; }

Como puede observar, la opción -trigraphsdebe especificarse o de lo contrariocpp emitirá una advertencia; Esto indica cómo los trigrafos son cosa del pasado y de ningún valor moderno, aparte de confundir a las personas que podrían toparse con ellos .


En cuanto a la justificación de la introducción de los trigrafos, se entiende mejor cuando se observa la sección de historia de ISO / IEC 646 :

ISO / IEC 646 y su predecesor ASCII (ANSI X3.4) respaldaron en gran medida la práctica existente con respecto a las codificaciones de caracteres en la industria de las telecomunicaciones.

Como ASCII no proporcionó una cantidad de caracteres necesarios para otros idiomas además del inglés, se crearon una serie de variantes nacionales que sustituyeron algunos caracteres menos utilizados por otros necesarios. .

(énfasis mío)

Entonces, en esencia, algunos caracteres necesarios (aquellos para los cuales existe un trigraph) fueron reemplazados en ciertas variantes nacionales. Esto lleva a la representación alternativa utilizando trigrafos compuestos por caracteres que otras variantes todavía tenían alrededor.

Dimitris Fasarakis Hilliard
fuente