For a deeper exercise, I recommend compiling to machine code instead of stopping at bytecode [1]; you can learn assembly along the way, which is more satisfying than compiling to C.
For a deeper exercise, I recommend compiling to machine code instead of stopping at bytecode [1]; you can learn assembly along the way, which is more satisfying than compiling to C.
That's why I no longer object to compiler tutorials stopping at assembly or even C, as long as the phases not present in interpreter tutorials like Bob Nystrom's brilliant one are non-trivial: type checking, optimizations, lowering.
Here's Martin Jacob's code to execute arbitrary memory. If you malloc a byte array and insert opcodes directly at indexes, you can construct machine code and execute it.
https://gist.github.com/martinjacobd/3ee56f3c7b7ce621034ec3e...
Since your C program is already in memory, you have access to the C standard library and don't have to worry about linking or object formats but you'll have to worry about parameter passing and FFI.
My toy JIT compiler based on this idea is here https://github.com/samsquire/compiler but it is incomplete.