Ok. Let's do a step back.
If you strace printf(), you will see that it ultimately calls write() on fd
1.
In fact, these are roughly equivalent:
printf("foobar\n");
write(1,"foobar\n",7);
(of course, normally printf does more hard work because it has to interpret
the format string, parse its arguments, etc. But eventually, it will call
write() on fd 1).
This usually results in something being written on the screen, because fd 1
is connected to the terminal device (which is basically a special device
that takes care of managing input and output from/to the user - waaaay
simplified). Normally, every program, when it's started, already has its fd
0, 1 and 2 connected to the terminal, without the need to do anything.
Now, what you do in your code is to close fd 0, 1 and 2. After that, those
descriptors are not connected to anything anymore; in fact, if you called
printf(), you would get an error EBADF (Bad file descriptor). Of course,
you won't be able to print it, but trust me (and you can check with strace
if you don't trust me).
But in your code you don't do that; instead, you reassign fd 0,1 and 2
to /dev/null, so now they exist again, are valid and are connected to
something.
Then you call printf(), which, as we know, calls write() on fd 1. But fd 1
now points to /dev/null, so whatever you print with printf(), goes
to /dev/null. That's why you don't see anything on screen.