What does the ??!??! operator do in C?
Asked Answered
G

4

2544

I saw a line of C that looked like this:

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

It compiled correctly and seems to run ok. It seems like it's checking if an error has occurred, and if it has, it handles it. But I'm not really sure what it's actually doing or how it's doing it. It does look like the programmer is trying express their feelings about errors.

I have never seen the ??!??! before in any programming language, and I can't find documentation for it anywhere. (Google doesn't help with search terms like ??!??!). What does it do and how does the code sample work?

Gingery answered 19/10, 2011 at 16:56 Comment(3)
Sadly this gem of a program won't work in C++17 and newer.Chiapas
Trigraphs will be removed in the ISO C23 standard.Angleaangler
Is it one of Kanetkar's puzzles?Vertical
G
2035

??! is a trigraph that translates to |. So it says:

!ErrorHasOccured() || HandleError();

which, due to short circuiting, is equivalent to:

if (ErrorHasOccured())
    HandleError();

Guru of the Week (deals with C++ but relevant here), where I picked this up.

Possible origin of trigraphs or as @DwB points out in the comments it's more likely due to EBCDIC being difficult (again). This discussion on the IBM developerworks board seems to support that theory.

From ISO/IEC 9899:1999 §5.2.1.1, footnote 12 (h/t @Random832):

The trigraph sequences enable the input of characters that are not defined in the Invariant Code Set as described in ISO/IEC 646, which is a subset of the seven-bit US ASCII code set.

Gatha answered 19/10, 2011 at 16:58 Comment(9)
Trigraphs originally were needed in case you keyboard didn't have eg a '|' symbol. Here it's either the programmer deliberately being annoying or some bizarre editor 'feature'Runkel
Trigraphs were added for EBCDIC computers.Hydracid
It's not necessarily EBCDIC - the set of characters that require trigraphs almost exactly matches the set of characters that are not invariant in ISO-646 (i.e. the old 'national ascii' standards).Starr
@Random832: the standard has a footnote saying: The trigraph sequences enable the input of characters that are not defined in the Invariant Code Set as described in ISO/IEC 646, which is a subset of the seven-bit US ASCII code set.Perisarc
A perfectly readable alternative would be ErrorHasOccurred() && HandleError(); That is, if you're used to shell scripting. :)Lorenz
Just note that many coding standards specifically ban the use of Trigraphs and Digraphs, and many compilers & static analyzers will flag their use.Stelu
Not valid since C++17 :|Mallorca
web.archive.org/web/20220804081331/https://… "ASCII had no space for letters outside the basic Latin alphabet. ISO’s solution was to reserve many of the ASCII punctuation characters for national use... The authors of the C standard tried to introduce an alternate punctuation format that didn’t rely on reserved characters"Spectacular
@DwB, not only for EBCDIC - various national ASCII-based (ISO 646) character sets also lack characters needed for programming in C (e.g. Scandinavian languages which have ø where US-ASCII has |).Refill
A
593

Well, why this exists in general is probably different than why it exists in your example.

It all started half a century ago with repurposing hardcopy communication terminals as computer user interfaces. In the initial Unix and C era that was the ASR-33 Teletype.

This device was slow (10 cps) and noisy and ugly and its view of the ASCII character set ended at 0x5f, so it had (look closely at the pic) none of the keys:

{ | } ~ 

The trigraphs were defined to fix a specific problem. The idea was that C programs could use the ASCII subset found on the ASR-33 and in other environments missing the high ASCII values.

Your example is actually two of ??!, each meaning |, so the result is ||.

However, people writing C code almost by definition had modern equipment,1 so my guess is: someone showing off or amusing themself, leaving a kind of Easter egg in the code for you to find.

It sure worked, it led to a wildly popular SO question.

ASR-33 Teletype

                                            ASR-33 Teletype


1. For that matter, the trigraphs were invented by the ANSI committee, which first met after C become a runaway success, so none of the original C code or coders would have used them.
Arelus answered 19/10, 2011 at 21:9 Comment(13)
It's not the only case of missing characters, in the keyboard and the character set. The Commodore 64 is likely to be more familiar to a lot of people in their late thirties and upwards - the displayed character sets both lacked braces (and probably the bar and tilde too) - in this case because the "ASCII" wasn't ASCII. In ECMA-6 (almost always called ASCII, but not US-ASCII) there were 18 region-specific codes, but I don't know which codes they were. The one thing I can say for sure - in the British "ASCII", # was replaced with £. In other regions, maybe "ASCII" had no braces etc.Taxable
The similar ATASCII character set for Atari 8-bit computers also lacked { } as well as ~ and `.Edi
See these two Wikipedia articles. I'm just about old enough to still remember the era of 7-bit national charsets (although I'm sure they still linger on in some dark unswept corners), and the book I first learned C from found it necessary to warn about the possibility of if (x || y) { a[i] = '\0'; } looking like if (x öö y) ä aÄiÅ = 'Ö0'; å in the wrong charset.Festschrift
@Steve314: well, on C64 you could rewrite the character-set so you can use whatever encoding you like :)Flax
Another interesting historical note is that Unix (which was the big platform C rode in on) may have been the first system of any significance (and maybe the first overall) to default alphabetic values to lower case rather than upper case. Although I haven't seen with my own eyes many contemporary systems, I think this was a real sign of sophistication. Besides being really the only decent OS, Unix also converted your upper case to lower, rather than vice versa. Those guys were really cool.Arelus
Funny story I gotta tell ya... the IBM RS/6000 workstation's XL Fortran compiler was developed from the XL C compiler. In the first few releases, they accidentally left in the trigraph processing, so there were some legit Fortran character sequences (in a literal string, IIRC) that were misinterpreted as C trigraphs, leading to some interesting bugs!Centralization
@DigitalRoss: I know the Commodore came later, but whether upper- or lower-case was the default depended upon which character set was selected. Further, the relationship between screen character codes and the code used by the "getch/putch" equivalent methods in the Kernel was rather bizarre. Apple computer's putch equivalent used screen codes, which had bit 7 set for normal text. Further, until the 80-column card was released for the //e, even machines which could show lowercase could only do so in non-inverse text.Writhe
@Steve314: In case you want to refresh your memory on the exact codes, the ECMA-6 standard is available online :)Cycloid
Such a nice answer but it sure looks like the key to the right of 0 is actually a | key (with * above)...Cyclopropane
@Cyclopropane -- that's actually a : -- better pic here: pdp8.net/asr33/pics/kbd_top.shtml?largeArelus
Trigraphs are horrid. The only character which needs to be synthesizable within quoted strings is the backslash ("meta") character, and that could have been accommodated by saying that if the first line of a file contains only one character, and that character is either a backslash or is not in the C character set, the compiler should treat that character as the meta character from then on. All other characters could then be made available as "backslash" escapes. Compilers that don't know anything about this feature would simply ignore a line that contains a backslash and nothing else.Writhe
The anecdote I've heard from someone who was on the C90 committee is that by the time of ISO standardization, they still couldn't exclude trigraphs. The issue was that some keyboards for some languages (for example Nordic languages and German) were still missing various exotic symbols like |, { etc, sacrificed in favour for having keys to all letters of their alphabets.Dixson
Rather off topic, but the moment I saw the picture of the ASR-33, I had a flashback to the smell of warm oil and metal. I spent hours at one of those in high school, programming in BASIC and then Algol. 10cps was fine, given we used 300 baud modems to connect to a DEC-10. Wow, the memories. I also remember when trigraph support was added to gcc. The programmer was obviously held at gunpoint to get it to happen; his check-in comment was "you don't want to know about this brain damage."Selfeducated
A
204

As already stated ??!??! is essentially two trigraphs (??! and ??! again) mushed together that get replaced-translated to ||, i.e the logical OR, by the preprocessor.

The following table containing every trigraph should help disambiguate alternate trigraph combinations:

Trigraph   Replaces

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

Source: C: A Reference Manual 5th Edition

So a trigraph that looks like ??(??) will eventually map to [], ??(??)??(??) will get replaced by [][] and so on, you get the idea.

Since trigraphs are substituted during preprocessing you could use cpp to get a view of the output yourself, using a silly trigr.c program:

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

and processing it with:

cpp -trigraphs trigr.c 

You'll get a console output of

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

As you can notice, the option -trigraphs must be specified or else cpp will issue a warning; this indicates how trigraphs are a thing of the past and of no modern value other than confusing people who might bump into them.


As for the rationale behind the introduction of trigraphs, it is better understood when looking at the history section of ISO/IEC 646:

ISO/IEC 646 and its predecessor ASCII (ANSI X3.4) largely endorsed existing practice regarding character encodings in the telecommunications industry.

As ASCII did not provide a number of characters needed for languages other than English, a number of national variants were made that substituted some less-used characters with needed ones.

(emphasis mine)

So, in essence, some needed characters (those for which a trigraph exists) were replaced in certain national variants. This leads to the alternate representation using trigraphs comprised of characters that other variants still had around.

Arly answered 25/3, 2016 at 2:24 Comment(4)
Good explanation.... this also shows why placeholders such as char *date = "??-??-??!" may not produce what you expect (this actually produces char *date = "~~|";)Altheta
Seems like most typical C codes would be pretty hard to read if fully implemented using trigraphs: if(data??(x??)??(y??)=='??/r' ??!??! data??(x??)??(y??)==0) ??< break; ??>Kasten
@Kasten nah, you're just not hardcode enough :) just add some ?: for added readabilityJaf
@Kasten ❝As a matter of style, it may be wise to surround trigraphs with white space, so that they stand out better in program text❞ (Rationale for International Standard—Programming Languages—C, Revision 5.10, §5.2.1.1).Unmoved
S
203

It's a C trigraph. ??! is |, so ??!??! is the operator ||

Stook answered 19/10, 2011 at 16:58 Comment(3)
trigraph come from a period where some keyboard didnt have all the keys they have now. It also hels when some text editor reserved special characters for special things. It's mostly a relic of the past and a quizz enabler ;)Stook
Because some keyboards apparently don't have "|" so some people have no option but to headbutt the keyboard repeatedly until a trigraph occurs that gives them the symbols they need.Spirograph
And then there is the <iso646.h> header file.Oistrakh

© 2022 - 2024 — McMap. All rights reserved.