Are there any other more elegant ways of setting up an IM2 isr without using pokes?
Not really except for generating some code for that address and loading that there as part of your basic loader. You have control over exact placement in memory of sections so you could create a section ORGed at 0xd000 that contains the im2 table and has your isr in it. It will be output as a separate binary by z80asm, you can create a tap file for that and load it to the right address inside your basic loader.
Every method is going to be similar to this. You need an im2 table and you need an interrupt routine at the jump destination of the table or else a jump to your isr at that address.
Some people may try to rely on specific contents of the 48k's rom as it contains a block of 0xff bytes that you can point the "I" register into so that the z80 will jump to 0xffff on interrupt. Then they poke a "jr" there so that the z80 reads jr at 0xffff and 243 from rom address 0. That's a jr -13 so you can have a jump to your interrupt routine at address 0xffff-13. That means no additional 257 byte im2 table. But I personally don't like to rely on that. You can try it if you like though. You will have to poke some stuff at the top of memory to get the jr and the jp to your isr or you could memcpy it from an array.
How do you safely allocate the space needed by the 257-byte interrupt vector table?
The memory map is completely under your control. You have to decide what goes where and you can decide what is safe.
I am assuming it's a newlib compile here:
The default zx compile is building a binary at address 32768 that grows upward. The stack is being placed just under the UDGs (65368?) and interrupts are disabled. All of this is customizable with pragmas. Line 18 of this file (
https://github.com/z88dk/z88dk/blob/mas ... ig.inc#L18 ) lists the crt defaults that you can override. The meanings of those defaults can be found here (
https://www.z88dk.org/wiki/doku.php?id= ... figuration ). So, for example, to change ORG away from 32768, change the value of CRT_ORG_CODE to something else via pragma in your .c, or pragma-define on compile line, or in a separate pragamas file that is read during compile (explained by the last link).
You can go further though by defining your own memory map to put different things in different places. I'll stop talking there, but suffice to say you can break your program into pieces that are spread into different memory banks or different regions in memory.
Long story short, you decide what the memory map is.
The only example I can find in z88dk that demonstrates setting up an IM2 isr is the SP1 demo program. This program configures the stack to grow downwards from address 0xD000 (using the REGISTER_SP pragma) and places the interrupt vector table immediately after the stack. Does SP1 require the stack to be located at address 0xD000? If you don't use the SP1 sprite package and/or have the stack at its default location at the top of memory (is that its default location?), where do you then put the interrupt vector table and have z88dk aware of its placement when linking so it is not overlapping with the code/data/bss sections of your program?
sp = 0xd000 fits well with the memory map defined by the default build of sp1. sp1 reserves some memory at the top of ram to manage sprites, hold rotation tables, etc. Its configuration is controlled by this file (
https://github.com/z88dk/z88dk/blob/mas ... fig_sp1.m4 ) which also contains a memory map at the end with suggestions for the locations of im2 table and the stack. Some discussion about changing sp1's memory map can be found here (
https://www.z88dk.org/wiki/doku.php?id= ... es:sp1_ex1 )
So the numbers for the sp1 example are not random - they are being selected to fit with the memory map defined by sp1. You can actually put things anywhere you want as long as it doesn't overlap sp1's structures or your program.
If you're not using sp1, you have more freedom where things go. The binary produced by z88dk is self-contained by default except for the stack and heap. So you just arrange to put these things like stack & im2 table out the way of the resulting binary. The heap, by default, will be initialized to fill the space between the end of the program and the bottom of the stack. If you don't want that, look into the CLIB_MALLOC_HEAP_SIZE pragma (link above).
I know this is a little bit complicated information but the gist is you decide the memory map and where things will go,