Now the Z80 is (like its predecessor, the 8080) quite able to perform everything needed. Due to it'sits (inherited) structure of a single memory pointer it does, however, need to replace a single (PDP-11 based) C-operation with several machine instructions. So far not a real issue. Except, when an assembly programmer looks at the result, he immediately sees Z80 specific ways to improve the result. Like - like holding two pointers and exchanging HL/DE when needed. That's hard to 'understand' for a C compiler, as it is based on semantics - the knowledge 'why' something is done - not just being told 'how' it's done.
##It is not strictly a strict C problem,
but an issue with all high-level languages. They compile best to a simple symmetric CPU model with a set of equal resources, offering exactly the operation asoperations the abstraction layer needs. The higher the language's abstraction is, the better the underlying 'CPU' level can perform. That's why the UCSD P-Code System did perform so well across many platforms. The offeringsoffering of its virtual CPU was exactly what a compiler wants. Despite being an interpreter at the core, performance was, on many machines, comparable to native code generated from the same language source. The reason for this platform optimization lies within the interpreter. Here, each rather abstract function gets performed by optimized routines. A string move might have the same invocation (due to the P-Code) across all platforms, but its implementation is CPU specific, using all advantages the specific CPU offers - like the mentioned working of 8-bit register pointers and only increasing memory base pointers every 256th cycle on a 6502. Operating on a greater abstraction in a language allows the compiler and/or runtime do employ greater optimization than fixing low level-detail within the source code.
Looking back (*3), the last 30 years do show two developments to bridge the problems of less than 'simple' CPUs and too simple languages. The 8086 family is not only an important, but eventually the best, example for changes in CPUs, as it is a nonot a simple CPU at first. Sure, compared to the Z80, it is much more powerful and symmetric, - still, not as simple as C assumes it to be.
Over time, the x86 got not only instruction set additions likesuch as scaling factors to move array indexing calculations into microcode, but the whole CPU got redesigned in a way that instruction sequences are analyzed, reordered and reformed to make C like-like operations performingperform better. Bottom Line, the 8086 became more PDP-11ish. One way to close the gap.
At the same time, the C Standard development worked hard to define a common set of data types and functions thereon that now can be used by the compiler to get a glimpse of the whywhy instead of the howhow. These source statements (may) no longer be directly translated into function calls, but be used by the compiler to generate different, more specialized, target optimized code. In the end, a way to make C a bit more high level than originally intended.
###Whats###What's the Lesson for Z80 Users?
Another, more practical, way is to go the same path that standard C is doing: Useuse more task-specific high-level functions and optimize them (in assembly) for the Z80.
BTW: The 6502s6502's short call stack is often cited here, but there is no relation to C. C doesn't require the usage of the return stack for parameters. It can as well be a separate parameter stack. In fact, strictly speaking, C doesn't require a stack at all.
C does require a way of bookkeeping for nested calls, some way of parameter passing (with undefined length) and a way to handle local variables. How this is done is up to the compiler (or it'sits creator). Using some hardware stack is one (simple) way, but not necessarily the best with a given CPU.
*2 - No, the C-LIB isn't a runtime as part of the language,: it is a collection of standard functions, itself (almost) completecompletely written in C, and compiled/linked at compile time.
*3 - Looking back is rather rare in IT, but we are Retrocomputing, - we not only play nostalgia but also try to learn from history, don't we?
*4 - A serious choice could be Ada. Due its declarative nature, code generation can be way better optimized for individual CPUs. After all, it was one of the main goals of Ada's development to be able to produce good code no only for mainframes but also for little bastards like an 8048. There have been several special Z80 compilers during the 1980s,1980s; most prominent maybemay be RR Software's Janus/Ada 83. While no longer mentioned, there was also a Z80 version.