You can use casts like
((type)(expression)) in compound expressions to limit the precision and range. With GCC, if you enable optimizations (-O1, -O2, -Os, -Og, etc.) for example for the AVR architecture, 8-bit arithmetic is used throughout; see
here for an example (godbolt.org).
It is a pity C isn't defined in terms of explicit-sized binary types (
intN_t and
uintN_t), with type promotion rules defined by the target architecture. The current C standard requires that the
int type can represent all integers between -32767 and +32767, inclusive, which makes 8-bit architecture support very difficult. For example, if you look at the assembly above, the function call ABI used places the three parameters in even registers
r24,
r22,
r20 instead of using consecutive ones, because of this "at least 16 bits" requirement being reflected in the AVR function call ABI.
Indeed, I personally could do without type promotion in C. It would complicate
stdarg processing somewhat, and break a crapton of existing C code, so it would essentially be a new programming language. And at that point, one could easily add the crucial bits missing from C as discussed many times here and elsewhere.
For some reason, I really like ATmega32U4 and AT90USB1286 AVR MCUs with native USB 2.0 (low/full speed only, max. 12 Mbit/s). When I write code for these, I accept the ABI inefficiencies, and use proper casts extensively (testing crucial expressions at godbolt.org for GCC 5.4.0 and later, or SDCC 4.0 if the target is supported). It isn't perfect, but nothing is; this suffices for me.