24

A function to sleep a 1000 years: explained

 4 years ago
source link: https://bowero.nl/blog/2019/12/14/a-function-to-sleep-a-1000-years-explained/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

I encountered a code challenge that asked for a sleep function that sleeps a 1000 years. The current functions use an integer to declare the amount of seconds, but 2 32 is only around a 100 years long.

A smart guy posted this interesting line of C code:

i;f(){--i&&f(sleep(7));}

This works fairly correctly, surprisingly. Let’s look further into it!

First, we’ll dissect the code into more readable code:

i;
f() {
	--i && f(sleep(7));
}

The first line creates a signed integer, called i . According to the C99 standard , section 6.7.8.10, it will be initialized as 0 :

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static storage duration is not initialized explicitly, then:
— if it has pointer type, it is initialized to a null pointer; — if it has arithmetic type, it is initialized to (positive or unsigned) zero; — if it is an aggregate, every member is initialized (recursively) according to these rules; — if it is a union, the first named member is initialized (recursively) according to these rules.

Section 6.7.1.2 tells us that we have to define a declaration specifier. Therefore, i would have to become int i . However, in C89 , this was not yet a rule. This is what the rationale of C99 tells us about that:

A new feature of C99: In C89, all type specifiers could be omitted from the declaration specifiers in a declaration. In such a case int was implied. The Committee decided that the inherent danger of this feature outweighed its convenience, and so it was removed. The effect is to guarantee the production of a diagnostic that will catch an additional category of programming errors. After issuing the diagnostic, an implementation may choose to assume an implicit int and continue to translate the program in order to support existing source code that exploits this feature.

Now, we know that i would have been an int , if we use C89 rules. gcc will not give us an error if we try to do this, but just give us a warning:

warning: type defaults to ‘int’ in declaration of ‘i’ [-Wimplicit-int]

According to section 6.2.5.12, integers are arithmetic types. This, in combination with the second rule, makes that i will now be 0 .

Then, the function f() is declared. This function takes no parameters and gets the default return type. This is also an int , because of the exact same rule as quoted above.

So, now we have an int called i and a function with return type int called f() . Let’s look at the contents of f() !

--i && f(sleep(7));

You have to understand that --i means that 1 will be substracted from i , before it is being evaluated. This means that we start with -1 in i .

The second part is actually more interesting. f() is called recursively, but with a parameter! Let’s look at another example that I wrote:

#include <stdio.h>

f() {
	printf("Bye world!\n");
}

int main(int argc, char const *argv[])
{
	f(printf("Hello world!\n"));
	return 0;
}

This actually outputs the following:

Hello world!
Bye world!

Apparently, we can still pass things to our function. They will be executed. If you want to avoid this, you will have to give void as the parameter type. So the following would not have worked:

f(void) {
	printf("Bye world!\n");
}

Now, we know why the recursive function works. So, when does it stop? According to the author, it stops at approximately a thousand years. He tells us it will run for around 952.69 years.

Let’s look at when the function technically stops. To start with this, you have to know that every number but 0 , is true . Just looking at the function does not help a lot. There is not even a return value! For this, you have to be familiar with short-circuit operators . The following means that f(sleep(7)) will only be executed, if --i is true. This is always the case, except for i == 0 . This means that f(sleep(7)) will be run until i is 0 :

--i && f(sleep(7));

So, when will i be 0 ? Well, i is a signed integer, which has a size of 32 bits. That means that i has a minimum value of -(2^31) , which is -2147483648 , and a maximum value of 2^31 , thus 2147483648 . We will go from -1 to -2147483648 and then we will flip to 2147483648 . We will see 2^32 = 4294967296 values for i . -1 though, because 0 is not an option. 0 would mean that f(sleep(7)) would not be run, and that’s the end of our f() .

So, sleep(7) will be executed 4294967295 times. Let’s calculate the amount of years that this will run.

The amount of seconds: 4294967295 * 7 = 30064771065
The amount of hours: 30064771065 / 3600 = 8351325.3
The amount of days: 8351325.3 / 24 = 347971.9
The amount of years: 347971.9 / 365.25 = 952.7

So this code will indeed run for a thousand years, with a margin of ten percent.

With this, we have proven that this beautiful line of code is correct!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK