As many people never tire of explaining, the C language is obsolete, unsafe, unwelcome in polite company and generally looked down on  by thought leaders and adepts of λ calculus alike.  Here’s a program that exhibits the lamentably low level nature of  C as it changes the call stack on x86-64 so that it calls “one” and prints out “one returns 1” and then mysteriously prints “one does not return 1”.

While it is true that this program is a good way to learn some machine architecture and also helps with understanding core operating systems code, my main excuse is that it is fun. The expressions marked in red are what make the trick work (although the color doesn’t matter -) ).

// MBD2K Irresponsible C Code (c) Victor Yodaiken 2022
#include <stdio.h>
int one(void);
int two(void);
int main() // On x86-64 executes both printfs
{ 
        if ((one()) == 1) {
            printf("one returns 1\n");
            two();
        } else printf("one does not return 1\n");
        
        return 0; 
}
void *y = 0; //holds the return address of "one"
int one(void)
{
char **x = (char **)(&x + 2); //clang&tcc 2, gcc 3
y = *x;
return 1; //always returns 1 
}
int two(void)
{
char **x = (char **)(&x + 2); //clang&tcc 2, gcc 3
*x = y;
return 2;
}

This code works on  current generations of tcc, clang, and gcc (as long as you adjust the pointer arithmetic as suggested in the comment). When I was writing this code, I figured out the pointer arithmetic and where to find the return address on the stack by experiment with code something like:

 printf("x=%p *x=%p x+1=%p *(x+1)=%p\n", x,*x,x+1,*(x+1));

extended to 2 and 3 and 4. Getting the code to work on ARM probably requires some assembler – which is fine too. Turning on the so-called “optimizer” breaks this code because current  C “optimizers” bizarrely enough can change the semantics of C code in often unpredictable ways without making any performance improvements.  This is permitted by the C Standard  for reasons that strike me as not well thought out, but let’s pass over this sad situation for the moment.

There is a famous fragment of code in early UNIX versions that uses the same stack  trick. It goes something like this:

next = find_next_process_to_run();
if(save()){
           current_process = next;
           resume();
           panic("process switch failed catastrophically\n");
           }

“Save” saves the context of the current process in a structure identified by “current_process” and returns a non-zero value while “resume” restores state from “current_process”  and then returns 0. The trick is that “save” saves the return address among other things,  so “resume”  returns to the condition test.  There is a legendary comment in the original code, “You are not expected to understand this”, but when you do, you know a lot about how process or thread switching actually works.

 

The next chapter of Unforgivable C  is on the way in about 2 weeks, I hope.

Unforgivable C programming 1
Tagged on:         

2 thoughts on “Unforgivable C programming 1

  • September 15, 2022 at 9:33 am
    Permalink

    Hi Victor,

    I’m not sure what this example demonstrates, because it’s not conforming C code. The pointer addition &x+2 is undefined behavior. Annex J.2 of the C99 standard mentions the situation: “Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object (6.5.6).”

    While the example may show the behavior of certain compilers when fed invalid code, it doesn’t “exhibit the lamentably low level nature of C.”

  • September 21, 2022 at 1:02 pm
    Permalink

    It demonstrates something fun and interesting you can do with C. As for “conforming”, I’m aware that this is undefined behavior according to the C standard even though it is obviously correct semantics for C and works in 3 widely used compilers. The concept of undefined behavior in the C standards is a serious error which has had the effect of both narrowing the effective domain of C programmers and making C semantics opaque. It’s not a useful concept.

Comments are closed.