2526

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?

Peter Olson
  • 139,199
  • 49
  • 202
  • 242

4 Answers4

2019

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

Lucas
  • 523
  • 2
  • 10
  • 20
user786653
  • 29,780
  • 4
  • 43
  • 53
  • 543
    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' – Martin Beckett Oct 19 '11 at 17:02
  • Trigraphs were added for EBCDIC computers. – DwB Oct 19 '11 at 17:23
  • 36
    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). – Random832 Oct 19 '11 at 18:01
  • 4
    @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._ – ninjalj Oct 19 '11 at 18:10
  • 94
    A perfectly readable alternative would be `ErrorHasOccurred() && HandleError();` That is, if you're used to shell scripting. :) – Yam Marcovic Oct 24 '11 at 15:01
  • 13
    Just note that many coding standards specifically ban the use of Trigraphs and Digraphs, and many compilers & static analyzers will flag their use. – Luciano Apr 15 '15 at 15:35
  • 13
    Not valid since C++17 :| – val - disappointed in SE Dec 15 '18 at 17:12
  • 3
    https://web.archive.org/web/20220804081331/https://cs61.seas.harvard.edu/site/2021/Unicode/ "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" – MaudPieTheRocktorate Sep 22 '22 at 01:41
  • @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 `|`). – Toby Speight May 01 '23 at 13:02
587

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.
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • 35
    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. –  Oct 20 '11 at 04:06
  • 9
    The similar ATASCII character set for Atari 8-bit computers also lacked { } as well as ~ and `. – dan04 Oct 20 '11 at 06:16
  • 55
    See [these](http://en.wikipedia.org/wiki/ASCII#Incompatibility_vs_interoperability) [two](http://en.wikipedia.org/wiki/ISO/IEC_646) 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. – Ilmari Karonen Oct 20 '11 at 13:36
  • @Steve314: well, on C64 you could rewrite the character-set so you can use whatever encoding you like :) – Karoly Horvath Oct 21 '11 at 22:26
  • 17
    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. – DigitalRoss Oct 26 '11 at 02:45
  • 29
    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! – Phil Perry Apr 11 '14 at 18:25
  • 1
    @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. – supercat Jun 09 '14 at 16:57
  • 1
    @Steve314: In case you want to refresh your memory on the exact codes, the ECMA-6 standard is [available online](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-006.pdf) :) – psmears Oct 26 '15 at 10:23
  • Such a nice answer but it sure looks like the key to the right of 0 is actually a `|` key (with `*` above)... – Gus Jul 28 '16 at 20:00
  • 1
    @Gus -- that's actually a `:` -- better pic here: http://www.pdp8.net/asr33/pics/kbd_top.shtml?large – DigitalRoss Jul 28 '16 at 20:29
  • 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. – supercat Oct 25 '17 at 21:22
  • 6
    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. – Lundin Nov 21 '18 at 15:15
  • 2
    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." – Scott M Jul 28 '22 at 01:01
203

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

MD XF
  • 7,860
  • 7
  • 40
  • 71
Joel Falcou
  • 6,247
  • 1
  • 17
  • 34
  • 14
    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 ;) – Joel Falcou Mar 23 '17 at 19:45
  • 8
    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. – Owl Jan 11 '19 at 18:06
  • 4
    And then there is the `` header file. – David R Tribble Oct 25 '19 at 21:08
199

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.

Community
  • 1
  • 1
Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • 4
    Good explanation.... this also shows why placeholders such as `char *date = "??-??-??!"` may not produce what you expect (this actually produces `char *date = "~~|";`) – Andrew Sep 23 '21 at 09:38
  • 1
    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; ??>` – wojtow Mar 07 '22 at 19:00
  • 2
    @wojtow nah, you're just not hardcode enough :) just add some `?:` for added readability – quetzalcoatl Apr 08 '22 at 11:33