Hacker News new | past | comments | ask | show | jobs | submit login
C implementation of Tic-Tac-Toe in a single call to printf (2020) (github.com/carlini)
202 points by ddtaylor on June 8, 2022 | hide | past | favorite | 21 comments



A few key points (also mentioned in the README): The "%n" specifier makes printf() fetch an int* argument and write the number of characters written so far to the target location. The funky stuff happens when you abuse that to overwrite the format string itself, actually making printf() Turing-complete.

This also means that if you lazily stuff a user controller string into a printf() style function (instead of doing e.g. `printf("%s", str)`), this can be used as a gadget for arbitrary memory writes (in addition to arbitrary stack reads). For this reason, there are compiler warnings if you use a format string that isn't a string literal (IIRC now on by default in GCC 11) and some C libraries like dietlibc refuse to implement %n.


%n looks like one of those mis-features that not only never should have been implemented but it is worth breaking backwards compatibility to remove.

Programmers should be able to assume that printf isn't writing to memory under ordinary operation.


That's what OpenBSD did:

> The %n conversion specifier has serious security implications, so it was changed to no longer store the number of bytes written so far into the variable indicated by the pointer argument. Instead a syslog(3) message will be generated, after which the program is aborted with SIGABRT.

https://man.openbsd.org/printf.3


N2834 ("Deprecate the %n format specifier in C2X") was discussed in WG14 (no consensus).

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2834.htm


I think it's not that serious really. Using the wrong format string already has undefined behaviour so you should always hard code it as part of the logic of your program, even if %n didn't exist.


But usually using the wrong format string would just read from undefined memory locations. While that could be bad, it’s not as obviously catastrophic as writing to arbitrary memory locations.


I had my language lawyer hat on. Technically undefined behaviour means literally any behaviour could happen, so in principle you could have a printf that wrote to random memory locations when you use a bad format string (even without %d) and it would be a conforming implementation.

But looking at it now from a more practical point of view, I guess I can't see how that would happen, even with aggressive compiler optimisations (which are the usual thing to trigger really surprising consequences of undefined behaviours).


This is a fine joke, and a it is a piece of art. I laughed and I think it is beautiful.

The authors did not only used printf() as a Turing-complete language, they also formatted their source code in a way pleasing to the eye. Cheers!

Don't forget coding is an art of fun and mystery, created to learn and play!


It's what IOCCC is about, you should checkout the other entries.

My personal favorite: https://www.ioccc.org/1998/banks.c

Hint: it's a flight sim :)



A flight sim? Isn't that obvious from the source code? ;-)

I've written a flight sim, too, but the code is not so beautiful like banks.c: https://github.com/reallew/voxelcopter


At school, out of boredom, I once coded TicTacToe in Lotus 1-2-3 (nope it was MS Multiplan for cost reasons but anyway). With "Insert coin" TUI animation and all ...


I had a good chuckle at the "Printf Oriented Programming" heading


I would rather try to understand the brainfuck version. :)


(in a loop)


I agree the "single call to printf" sounds wrong when the loop makes repeated calls to printf, but then I realize they mean there's a single place in the source code where printf is called...


More precisely, printf and scanf in a loop.


Indeed, didn't even notice the nested scanf.


Good work!


where is the scanf call? Is it obfuscated in the macros?


It's part of the `arg` macro.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: