The avr-gcc stdio.h gives specific advice.
printf() needs a valid stdout descriptor,
which needs a generic putc() function.
// ---- place globally, above setup() ---
FILE serialStdout = {0} ;
static int uart_write(char c, FILE *stream)
{
loop_until_bit_is_set(UCSR0A, UDRE0) ;
UDR0 = c;
return 0;
}//end of global stuff
// ---- place after Serial.begin() invocation ----
/* fill in the FILE structure */
fdev_setup_stream( &serialStdout, uart_write,
NULL,_FDEV_SETUP_WRITE);
/* reassign stdout */
stdout = &serialStdout ;
/* ... and test the result */
printf("stdout's address is %p\n", stdout) ;
bon appetit.