Settings

Theme

C implementation of Tic-Tac-Toe in a single call to printf

github.com

377 points by Megabeets 6 years ago · 84 comments

Reader

simias 6 years ago

Oddly enough I find that the #define macro soup detracts from the performance here. I was rather unimpressed at first because obfuscating C code by just #define'ing a bunch of code is a trivial and rather uninteresting way to write unreadable code. Of course you can make any arbitrary code look like a printf call with enough macros!

But it's actually a lot more clever than that.

I feel like this would be a lot more impressive if it was written in simple and clear C since then you'd see that there really isn't any tic-tac-toe logic being explicitly implemented the way you'd expect.

  • hugomg 6 years ago

    If you are curious, this is what the resulting format string looks like after expanding all the macros:

    https://gist.github.com/hugomg/73e91ebca7ced3c5c6f955e9e830b...

  • coliveira 6 years ago

    The reason he had to use macros is that the actual formatting string is huge and difficult to understand. In this case, macros really serve a useful purpose, instead of just obfuscating code.

    • ufo 6 years ago

      Another reason is that this was intended as an IOCCC submission and IOCCC has a file size limit.

  • russfink 6 years ago

    One time, I had to maintain C code That somebody had #define'd to look like FORTRAN. It was impressive.

    • gamegoblin 6 years ago

      Much of the original Bourne shell was written in C that had been macro'd to look like Algol [1].

      E.g.

          /usr/src/cmd/sh/mac.h:
          #define IF      if(
          #define THEN    ){
          #define ELSE    } else {
          #define ELIF    } else if (
          #define FI      ;}
          
          #define BEGIN   {
          #define END     }
          #define SWITCH  switch(
          #define IN      ){
          #define ENDSW   }
          #define FOR     for(
          #define WHILE   while(
      
      [1] https://research.swtch.com/shmacro
    • simias 6 years ago

      I've had to deal with code like that, only poorly mimicking Lisp instead: `#define unless(_x) if (!(_x))` etc...

      One of my favourite accidental features in Rust is that macros are so annoying and cryptic to write that people think twice before abusing them.

      • TeMPOraL 6 years ago

        I understand the temptation. I keep catching myself on writing `unless( ... )` in C++ every couple days, and I've considered adding a macro for it, but ultimately decided against it, as the rest of the team would not understand why I need it.

      • Gene_Parmesan 6 years ago

        Wouldn't be surprised if that was intentional, actually. The idea of making syntax a little unwieldy for things that you should really think twice before using is not a new one.

    • keitmo 6 years ago

      "The determined Real Programmer™ can write FORTRAN in any language."

    • codezero 6 years ago

      One time, I had to maintain FORTRAN code. Much of it was still in FORTRAN 66.

  • voldacar 6 years ago

    The macros are a form of data compression. Otherwise the string would be absurdly huge

mci 6 years ago

I am afraid that the author has disqualified himself by publishing the source code before the judging of IOCCC 2020 is over. See catch 22 in the rules: "The judges STRONGLY prefer to not know who is submitting entries to the IOCCC."[1] Even the winners are asked not to publish the code until it is available on the IOCCC web site.[2]

[1] https://ioccc.org/2020/rules.txt [2] Personal communication

mjburgess 6 years ago

Relevant talk on the motivation for printf-programming:

https://www.usenix.org/conference/usenixsecurity15/technical....

Very interesting. Roughly, that the existence of Turing-complete functions within programs creates a fundamental vulnerability that even rigid control over the control flow of a program cannot avoid.

yokohummer7 6 years ago

> %n takes a pointer and writes (!!) the number of bytes printed so far.

> Okay, everyone probably knows this. Let's get a bit more advanced.

Ok, but I didn't know about that. What's the use?

  • Someone 6 years ago

    One use is aligning outputs:

      char *prefix = "example";
      char *line1 = "line 1";
      char *line2 = "line 2";
      printf("%s: %n%s\n", prefix, &n, line1);
      printf("%*s%s\n", n, "", line2);
    
    will output

      example: line 1
               line 2
    
    That’s a bit more robust than using strlen(s)+2, where you have to keep that magic constant 2 in sync with ": ". Moving ": " to a variable and using strlen(s)+strlen(separator) would fix that, though (at the price of speed, unless you’ve a compiler that optimizes that away)
    • quietbritishjim 6 years ago

      > That’s a bit more robust than using strlen(s)+2

      strlen wouldn't even be an option if you were formatting something that's not a string e.g.

          int n;
          int prefix_num = 23;
          char *line1 = "line 1";
          char *line2 = "line 2";
          printf("example %d: %n%s\n", prefix_num, &n, line1);
          printf("%*s%s\n", n, "", line2);
    • JoshTriplett 6 years ago

      This only works if you're not dealing with Unicode, where the number of bytes, the number of characters, and the width of those characters can all vary.

    • tgv 6 years ago

      I'd never heard of %n, but I use printf's return value (the number of bytes written) for this kind of purpose, so

        n = printf("%s: ", prefix);
        printf("%s\n", line1);
        printf("%*s%s\n", n, " ", line2);
      • MertsA 6 years ago

        Yeah but the difference is that you can use %n wherever you need it in the format string. Depending on what arguments come after it figuring out the length of the printed arguments might not be trivial whereas with printf, it already has to keep a count of it during execution in order to return it at the end so it's easy to add support for it.

  • zlynx 6 years ago

    I seem to recall using it to auto-adjust column widths.

    And we did something with it involving string translations. Since we didn't control the translated format string we couldn't just count the characters in the source code.

    But it's been a really long time and I don't recall the details.

  • MertsA 6 years ago

    Well the fun use is using it to exploit printf format string vulnerabilities. Microcorruption has a fun level involving that IIRC.

  • CamperBob2 6 years ago

    Normally you'd use %n with input functions like scanf(), not printf().

  • bawolff 6 years ago

    To support format string vulnerabilities! /s

lukaszdk 6 years ago

This is just crazy and I love it <3

From a description on the author's website[1] of a Doom clone written in 13k of JavaScript.

> Until recently all content on this website was research, and while writing papers can be fun, sometimes you just need to blow off a little steam.

I think more companies should allow for their employees to have some plain old fun with no strings attached on a regular basis.

[1] https://nicholas.carlini.com/

  • learnstats2 6 years ago

    >I think more companies should allow for their employees to have some plain old fun with no strings attached on a regular basis.

    Maybe we could call this additional 'holiday pay' or a longer 'weekend' and mandate it through law so that everyone benefits.

    • commandlinefan 6 years ago

      Ha, if I had a longer weekend my wife would demand I use it taking her out.

    • LoSboccacc 6 years ago

      a law would be government overreach, but the idea is solid and the results tangibles; should be within the realm of a union regulations, if we ever manage to get one going.

  • DaiPlusPlus 6 years ago

    > I think more companies should allow for their employees to have some plain old fun with no strings attached on a regular basis.

    Nicholas Carlini's resume is crazy-insane and he's highly desirable, so of course Google Brain is going to lend him far more flexibility than Google Analytics would have with a recent college-hire, or at the other end of the supply/demand distribution: the working flexibility Amazon would afford a warehouse employee (if they aren't a contractor...).

  • joaizn 6 years ago

    >with no strings attached

    What about char arrays?

codegladiator 6 years ago

> in a single call to printf

> while(*d) printf(fmt, arg)

How's that a single call ?

  • zeroimpl 6 years ago

    It also turns out “arg” is a macro which calls scanf. Would be better if they described it more realistically.

  • usr1106 6 years ago

    > How's that a single call

    syntactically it is. But it's not a very clear description. So how would you describe that single call location getting executed in a loop so everybody understands what is really meant? A single line of code is worse.

haberman 6 years ago

In case you're wondering (like me) how you'd get input from printf():

> We ab^H^Huse [the Turing-completeness of printf()] to implement a the logic of tic-tac-toe entirely within this one printf call (and a call to scanf() to read user input).

So it should be "one printf() and one scanf()."

  • sfoley 6 years ago

    Should be "an infinite number of printf()s and scanf()s".

    • identity0 6 years ago

      Would you say that an infinite loop has infinite lines of code?

      • yjftsjthsd-h 6 years ago

        It has infinite instructions, yes. (Well, "unbounded" or "endless", if we're being picky.)

        • identity0 6 years ago

          No it doesn’t. It has finite instructions for the loop body, and then one jump instruction for the loop.

blunte 6 years ago

Title correction: Tic Tac Toe written almost entirely in #defines.

eternauta3k 6 years ago

I was expecting the tic-tac-toe to be written in machine code in an array, and printf would smash the stack to jump to it.

f2f 6 years ago

thanks. now i have nightmares.

yenwodyah 6 years ago

It isn't really a "single call" to printf if it's in a while loop, is it?

  • phire 6 years ago

    That's what you are getting hung up over?

    There is a call to scanf in the middle of the arg #define

rosstex 6 years ago

If you're reading these Nicholas, thanks for your super help in CS 161 Spring 2016!

peter_d_sherman 6 years ago

Holy Crap Batman!

Never in a million years would I have thought that printf() was Turing-Complete -- and yet, here's the proof that it is...

And then there's this related paper:

https://www.usenix.org/system/files/conference/usenixsecurit...

Page 175: "Printf is Turing-complete"

phkahler 6 years ago

I thought the program was going to consist of only the call to printf(). But alas, that is inside a while loop.

staycoolboy 6 years ago

Excellent obfuscation. It ticks all the boxes:

- esoteric modes of operation of a common function

- truly novel use of macros

- visibly beautiful

avidphantasm 6 years ago

I am jealous of people who have time to do things like this.

  • thr0w__4w4y 6 years ago

    I hear you on that. But for me, the jealousy dissipates quickly once I realize that even if I had /lots/ of time, I don't think I would come up with this.

yitchelle 6 years ago

Love the attempt the blindside the reader with funky formatting showing "%N".

  • TapamN 6 years ago

    That's not too unusual for IOCCC entries. Of the top of my head, there's a flight simulator shaped like an airplane, and an addition routine shaped like a full-adder.

    A more subtle touch is that the #defines spell out NOUGHTs AnD CRoSSES.

techbio 6 years ago

I’m impressed with the aesthetics, and humor: formatted ‘fmt’, “printf oriented programming”...

Suggests in my mind, somehow, thought I’d share: “CTRL-F oriented programming” :)

anta40 6 years ago

ttt.c(25): fatal error C1091: compiler limit: string exceeds 65535 bytes in length

Aaargghhh MSVC 2019 doesn't like this :(

hebetude 6 years ago

fake news, single call to while

tikej 6 years ago

Looks a bit like scientific codes from 3 decades ago :)

mariomatos 6 years ago

Some advanced clickbait we have here.

  • encom 6 years ago

    Nah.

    Dennis Ritchie HATES him. Program like a pro with this one weird trick! You won't believe what happens!

    That's proper bait.

    • qllp100 6 years ago

      Hence advanced bait. printf() is called in a while loop, so it is hardly a single call.

      • taspeotis 6 years ago

        I don’t want to jump on the title hate bandwagon - what the author has done is definitely creative and clever and this discussion detracts from it - but it also uses scanf.

        • quietbritishjim 6 years ago

          Where?

              int main() {
                  while(*d) printf(fmt, arg);
              }
          • shawnz 6 years ago
            • quietbritishjim 6 years ago

              Ah, well spotted, thanks. I didn't think it was fair to call the title click bait just because it used a while loop to drive the calls to printf, but also including a scanf call is definitely not in the same spirit.

              • hibbelig 6 years ago

                Alas, it's technically difficult to get input from the user using printf.

                • Someone 6 years ago

                  If you’re willing to lose almost all portability, I think you could read a value from an I/O port, pass that to printf, use that as the field width for a string to print, count the character length of the resulting string using %n to move it into a variable, and then backspace over that to prevent the output from dirtying your output. You could only use the results in a subsequent call to printf, though.

                  Of course, just hiding an assignment in a printf argument would be much easier, but wouldn’t be fun.

                  Of course, that would lose lots and lots of portability, and you could, likely, only get it to work on systems that don’t do memory protection between processes, and then, not all of them.

                  I guess it would not be very hard to use this trick to have printf read joysticks on many micro’s from the 1980’s.

                • quietbritishjim 6 years ago

                  Hmm that is a good point :-)

  • mariomatos 6 years ago

    Well, I got downvoted. I should have mentioned that I meant this in a good way. My account is new and I have since read the rules in the welcome page. But think about it, a whole game implemented only in a print statement? Wow, that can’t be real. And it is at the very 1st place of the top posts. I clicked on it expecting a cool article explaining something interesting, but no. It is the implementation. So, well done.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection