There are several ways of handling this problem of data access.
The first obvious (but not efficient as far as RAM use is concerned, which could be a problem on small targets) way is to put all "constants" in data memory (RAM) so that the CPU never has to access anything in, say, Flash memory during normal execution of the code.
This can be "trivially" done. A C compiler isn't required to put constants in Flash memory (or generally speaking, in some kind of read-only memory.)
All that can be done writing an appropriate linker script. Then the "startup code" would have to copy all constants in RAM, just like it does for initializing non-zero global variables, using specific instructions for doing so. Most Harvard architectures used these days are modified Harvard, and there is always some kind of bridge between the different memory areas, even when it's not fully transparent.
The second way, which is what was required on older 8-bit PICs, for instance, is that you need to use specific instructions to access Flash memory - and the compiler needs to handle two types of pointers with specific qualifiers. Not very nice, but the plus side is that you don't waste RAM as in the above solution, and you have full control over memory access. Yes, as inconvenient as it can be, I consider this also a benefit for security reasons.
And that said, using printf() on small targets when all you need are simple formats for displaying integers and maybe floating point numbers, is rarely a good idea. Writing your own conversion functions is not difficult and will be much more efficient. And, once you have written them, you can reuse them as often as you want.