IOCCC 1984/decot.c - can it be compiled in the 21st century?
Asked Answered
M

4

10

This fascinating piece of code was featured in the very first (1984) edition of the International Obfuscated C Code Contest:

http://www.ioccc.org/years.html#1984 (decot)

After clearing through the debris of preprocessor abuse and unused code caused by a goto and some sneaky comments, you end up with the following surviving code (please correct me if I am wrong!):

#include <stdio.h> //used to suppress warnings
#include <math.h> //used to suppress warnings
extern int fl00r; //renamed to not clash with floor from math.h - unless it's part of the trickery???
int b, k['a'] = {
    sizeof(int(*)()),
    };
struct tag {int x0,*xO;}

*main(int i, int dup, int signal) { //int added to suppress warnings
  for(signal=0;*k *= * __FILE__ *i;) {
   printf(&*"'\",=);    /*\n\\", (*((int(*)())&fl00r))(i)); //see line 3
   if(b&&k+sin(signal)/ * ((main) (b)-> xO));
  }
}

There is a single compiler error left to conquer:

decot.c: In function 'main':
decot.c:12:28: error: too few arguments to function 'main'
   12 |    if(b&&k+sin(signal)/ * ((main) (b)-> xO));
      |                            ^
decot.c:9:2: note: declared here
    9 | *main(int i, int dup, int signal) {
      |  ^~~~

I suspect that the way compilers worked back in the day meant that you could somehow call main with just 1 argument even though it was specifically defined with 3 as in this case.

Is this accurate? Am I missing something? What are the least changes necessary to enable this code to compile nowadays?

I used GCC 9.2.0 with the suggested build command in the Makefile.

Thanks in advance and apologies if I have missed something very obvious!

Misquotation answered 30/9, 2020 at 22:0 Comment(3)
1984 was when I graduated high school and 5 years before the first C standard was published. Now we have C11 Standard - §5.1.2.2.1 Program startup(p1). See also: What should main() return in C and C++? So adjustments are needed to your code.Tilbury
You're correct that it could be called that way. There were no prototypes in those days either. The arguments could also be declared as types other than expected nowadays. You haven't missed anything obvious because it was a different time. Things were far different then so please don't feel bad about yourself for this. Actually don't for any of the IOCCC entries that you can't figure out! As for the least changes you'll probably find my answer helpful there (not my work though though I know the person who did it). See here if you haven't.Sangraal
.. though you the OP will no doubt see it for others and to reiterate for you: you did well! You almost had it. I just posted a simple fix that requires no special flags or anything. See https://mcmap.net/q/1068071/-ioccc-1984-decot-c-can-it-be-compiled-in-the-21st-century.Sangraal
R
6

tl;dr; your mistake was to give an ANSI C prototype to the main function (i.e. changing i to int i, etc), which directed the compiler to check its arguments where it was called and cause that too few arguments error.

Example:

echo 'int foo(a,b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
# K&R C function OK, no errors

echo 'int foo(int a, int b){return a+b;} int main(){return foo(3);}' |
  cc -Wall -std=c89 -xc -
...
<stdin>:1:54: error: too few arguments to function ‘foo’

That code should be preprocessed with a traditional C preprocessor, not with an "ANSI C" one. Using a standard preprocessor will result in some artifacts, like << = instead of <<=, * = instead of *=, etc.

cpp -traditional-cpp -P decot.c > decot1.c

After adding correct function declarations, and adding a cast --see the diff & result at the end of this answer-- you get something that compiles with a single warning in c89 (and a couple of them in c99), and, as described, prints some garbage to stdout:

$ cc -std=c89 decot1.c -lm -o decot1
decot1.c: In function ‘main’:
decot1.c:13:33: warning: function called through a non-compatible type
    (printf(&*"'\",x); /*\n\\", (*((int(*)())&floor))(i)));
                                ~^~~~~~~~~~~~~~~~~~~~
$ ./decot1
'",x);  /*
\

Which is exactly the same thing I get when compiling & running the original on V7 Unix, so it should be just right ;-)

decot1.c

double floor(double);
double sin(double);
int printf(const char*, ...);
int b,
k['a'] = {sizeof(
    int(*)())
,};
struct tag{int x0,*xO;}

*main(i, dup, signal) {
{
  for(signal=0;*k *= * "decot.c" *i;) do {
   (printf(&*"'\",x);   /*\n\\", (*((int(*)())&floor))(i)));
        goto _0;

_O: while (!(k['a'] <<= - dup)) {
        static struct tag u ={4};
  }
}


while(b = 3, i); {
k['a'] = b,i;
  _0:if(b&&k+
  (int)(sin(signal)             / *    ((main) (b)-> xO)));}}}

diff

$ diff -u decot1.c~ decot1.c
--- decot1.c~
+++ decot1.c
@@ -1,4 +1,6 @@
-extern int floor;
+double floor(double);
+double sin(double);
+int printf(const char*, ...);
 int b,
 k['a'] = {sizeof(
     int(*)())
@@ -20,4 +22,4 @@
 while(b = 3, i); {
 k['a'] = b,i;
   _0:if(b&&k+
-  sin(signal)          / *    ((main) (b)-> xO));}}}
+  (int)(sin(signal)            / *    ((main) (b)-> xO)));}}}
Radiograph answered 30/9, 2020 at 23:14 Comment(1)
BTW: the hint.text files will be going away in the future. In fact they're already removed from the website that's being worked on. At this point the link will fail. It'll likely end up being index.html but the hint.text file itself is going to be README.md.Sangraal
M
5

You cannot compile the original code anymore as it is. The earliest C standard GCC claims to support is C89, and the code from this contest is from before that. You did already a good job at porting it to more modern compilers. But there are more issues left than just the number of arguments of main():

  • Clang and GCC both know that sin() returns a double, even if you don't #include <math.h>, and refuse to do add a double to an int * in the subexpression k+sin(signal). TCC seems to accept it though.
  • The variable fl00r is declared but not defined, the linker will complain about an undefined reference to it.

Note that the original code can be compiled by TCC by just avoiding using x in combinations such as <<x (the preprocessor nowadays only substitutes complete tokens, and <<x counts as one token), and by calling main() with three arguments.

Your version of the code can be compiled by GCC by removing the #include statements, going back to using floor(), but forward declaring it as following:

floor();

To avoid complaints about sin(), use the -fno-builtin compiler flag. Then fix the issue with main() by calling it like (main) (b,b,b).

Marci answered 30/9, 2020 at 22:52 Comment(6)
C89 is the earliest C standard there ever was. Prior to that, you had a lot of variations on the language described by Kernighan and Ritchie, all mostly compatible with each other. I'm uncertain what the IOCCC rules were in 1984 for what compilers or target platforms submissions needed to work with, but knowing something of the ethos of that contest, I can imagine that tomfoolery around how code worked differently or not at all with different C implementations might have been part of the fun.Calumny
@JohnBollinger K&R C was considered a de-facto standard, and it predates C89. That compilers had slight variations is normal, that happens even today with newer, official standards. But yes, part of the fun was discovering what kind of tricks people could pull out of their hat. And they probably didn't expect some kinds of tricks, that were not completely in the spirit of the IOCCC, like ioccc.org/1984/mullender/mullender.c which just injects PDP-11 machine code. Related: we have codegolf.stackexchange.com where we continue to exploit our compilers :)Marci
@G.Sliepen Re And they probably didn't expect some kinds of tricks, that were not completely in the spirit of the IOCCC: it's true they don't expect certain tricks but that brilliant entry was not against the spirit at the time. Restrictions against machine dependent code were not in the rules of 1984. Though you're right on the other parts. BTW Landon is a good friend of mine if you want to know but that's in the README too.Sangraal
As for your answer it's actually much simpler than this. See my answer.Sangraal
BTW: I think it's great that you figured it out. Not that I tried much but I'm a three time winner of the IOCCC and I didn't get it to work. Of course I already knew that it was fixed so I had far less incentive in trying to figure out how to fix it for our purposes (getting the entries to work on modern systems) - and I only looked at it very briefly - but still it's a job well done.Sangraal
.. of course I just had to to have a go at it and I solved it. I'll make a new answer that is probably the cleanest one here and also simplest. Doesn't require any special flags at the compiler or anything else.Sangraal
S
4

Yes it can be compiled ... but not quite as is. You also need the C pre-processor option -traditional-cpp which clang does not support. You might need -m32 (perhaps just for 32-bit systems ... on my 64-bit systems it would not compile this way) also but I'm not sure of that.

Note that even though it might appear to be gcc in macOS gcc is actually clang so it won't compile like this under macOS unless maybe you install a real gcc compiler.

The IOCCC champion (as in the one with the most winning entries) Yusuke Endoh has written about all the entries from 1984 through 2020 (originally in Japanese) (including my entries) and has provided patches and compilation tips for those that can be compiled (not all can be).

BTW we're working on getting older entries to compile on more modern systems. (No I'm not a judge but I'm working with the judges and Landon is a good friend of mine). The website I linked to is not current however but it's the 'official' one still.

As for 1984/decot: apply the patch from here and then compile as:

gcc -traditional-cpp -o decot decot.c -lm

Write-up is at: https://mame-github-io.translate.goog/ioccc-ja-spoilers/1984/decot.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en-US&_x_tr_pto=wapp.

--

Example run:

$ patch < 1984-decot.patch 
patching file decot.c
$ $ gcc -traditional-cpp decot.c -lm
decot.c:6:12: warning: built-in function 'floor' declared as non-function [-Wbuiltin-declaration-mismatch]
    6 | extern int floor;
      |            ^~~~~
decot.c: In function 'main':
decot.c:13:2: warning: type of 'i' defaults to 'int' [-Wimplicit-int]
   13 | *main(i, dup, signal) {
      |  ^~~~
decot.c:13:2: warning: type of 'dup' defaults to 'int' [-Wimplicit-int]
decot.c:13:2: warning: type of 'signal' defaults to 'int' [-Wimplicit-int]
decot.c:16:5: warning: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
   16 |    (printf(&*"'\",x);   /*\n\\", (*((double(tag,u)(*)())&floor))(i)));
      |     ^~~~~~
decot.c:1:1: note: include '<stdio.h>' or provide a declaration of 'printf'
  +++ |+#include <stdio.h>
    1 | #define x =
decot.c:16:5: warning: incompatible implicit declaration of built-in function 'printf' [-Wbuiltin-declaration-mismatch]
   16 |    (printf(&*"'\",x);   /*\n\\", (*((double(tag,u)(*)())&floor))(i)));
      |     ^~~~~~
decot.c:16:5: note: include '<stdio.h>' or provide a declaration of 'printf'
decot.c:27:12: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
   27 |   _0:if(b&&(int)k+
      |            ^
decot.c:28:3: warning: implicit declaration of function 'sin' [-Wimplicit-function-declaration]
   28 |   sin(signal)           / *    ((main) (b)-> xO));/*}
      |   ^~~
decot.c:1:1: note: include '<math.h>' or provide a declaration of 'sin'
  +++ |+#include <math.h>
    1 | #define x =
decot.c:28:3: warning: incompatible implicit declaration of built-in function 'sin' [-Wbuiltin-declaration-mismatch]
   28 |   sin(signal)           / *    ((main) (b)-> xO));/*}
      |   ^~~
decot.c:28:3: note: include '<math.h>' or provide a declaration of 'sin'
$ ./a.out 
'",x);  /*
\$
Sangraal answered 25/2, 2023 at 13:54 Comment(3)
Nice, I didn't know about -traditional-cpp. I managed to compile it on a 64-bit system with your patch and that flag, without -m32 but by adding -fPIC to ensure it can link against the symbol floor.Marci
@G.Sliepen Please understand it's not my patch. I didn't know about that option either until Yusuke wrote about it. With -m32 it seems to not work. I'm not sure if he used a 32-bit system or not but under 64-bit it does not seem to work. As noted it doesn't work under macOS with the default compiler as it's clang not gcc even when you run it as gcc.Sangraal
GCC 12 compiling it for x86-64: godbolt.org/z/ozxvrbnxrMarci
S
1

As I am working with the judges on fixing entries etc. I have fixed a lot of entries for modern day. I just fixed this one. It's quite simple actually. The macros are problematic but you handled that well. I'll give some hints later.

The fixed version in its entirety, requiring no special compile flags (except for some systems -lm), looks like this:

int b,
k['a'] = {sizeof(
    int(*)())
,};
struct tag{int x0,*xO;};

int dup, signal;
*main(int i) {
{
  for(signal=0;*k *= * __FILE__ *i;) do {
   (printf(&*"'\",x);   /*\n\\", (*((int(*)())&floor))(i)));
        goto _0;

_O: while (!(k['a'] <<= - dup)) {       /*/*\*/
        static struct tag u = {4};
  }
}


while(b = 3, i); {
k['a'] = b,i;
  _0:if(b&&(int)k+
sin(signal)              / *    ((main) (((struct tag *)b)-> xO)));/*}
  ;      
}         
         
*/}}}   

Well you might need to add to the compiler -include stdio.h -include math.h or else add those includes in the code itself but that's it.

That's all it takes.

Compiling

We could disable more warnings as you'll get more most likely but different compilers have different warnings. These are compatible with both clang and gcc:

cc -std=gnu90 -Wall -Wextra -Wno-array-bounds -Wno-error -Wno-implicit-function-declaration -Wno-keyword-macro -Wno-main-return-type -Wno-missing-field-initializers -Wno-unused-value -Wno-unused-variable   -include stdio.h -include math.h -O3 decot.c -o decot -lm

Running it

$ ./decot 
'",x);  /*
\

Tips

  • You don't want to rename floor. Just remove that extern int floor;.
  • Change main() prototype to only take one arg - the int. This is non standard but it works with both clang and gcc.
  • You might find you need to cast b for the dereferencing in the call to main() as I did above.
  • You did well on the macro replacements.
  • About main() only having one arg. What happened to signal and dup? Well in the older days they would have defaulted to int so they're now global variables in the code. But why? Well try changing main() about and see what problem you encounter! In fact you already did. Fixing that will cause the problem that the cast I mentioned solves.

That's really all there is to it.

You did well!

Sangraal answered 4/4, 2023 at 13:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.