What does the ??!??! operator do in C?

Asked 2023-09-20 20:28:50 View 380,905

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?

Answers

??! 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.

Answered   2023-09-20 20:28:50

  • 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' - anyone
  • 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). - anyone
  • A perfectly readable alternative would be ErrorHasOccurred() && HandleError(); That is, if you're used to shell scripting. :) - anyone
  • Just note that many coding standards specifically ban the use of Trigraphs and Digraphs, and many compilers & static analyzers will flag their use. - anyone
  • Not valid since C++17 :| - anyone

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.

Answered   2023-09-20 20:28:50

  • 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. - anyone
  • The similar ATASCII character set for Atari 8-bit computers also lacked { } as well as ~ and `. - anyone
  • 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. - anyone
  • 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. - anyone
  • 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! - anyone

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

Answered   2023-09-20 20:28:50

  • 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 ;) - anyone
  • 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. - anyone
  • And then there is the <iso646.h> header file. - anyone

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.

Answered   2023-09-20 20:28:50

  • Good explanation.... this also shows why placeholders such as char *date = "??-??-??!" may not produce what you expect (this actually produces char *date = "~~|";) - anyone
  • 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; ??> - anyone
  • @wojtow nah, you're just not hardcode enough :) just add some ?: for added readability - anyone