Так или иначе, но знать она БУДЕТ. Либо непосредственно (в
switch-case). Либо в скрипте линкера (OVERLAY). По-другому в
принципе не сделаешь. Если у тебя коллбек устанавливается ровно
один раз на старте -- можешь имя функции коллбэка передавать при
компиляции макросом, например. Ну или как "зарезервированное имя
внешней функции". Но суть одна -- компилятор должен непосредственно
знать, что будет вызываться. Скомпилировать такой код, когда
компилятор вызывает неизвестную функцию -- на PIC18 вообще толком нельзя, на x51 можно с какими-то костылями, через reentrant, но вряд ли оно того стоит (ещё где-нибудь огребёшь, если функция станет чуть сложней). Особенности архитектуры как раз и мешают: отсутствие нормального стека у процессора. У PIC18 вообще нет..., у x51 он 256 байт, что выглядит издевательски, потому толком и не используется. Нужно понимать, что на мелких МК язык C -- это скорей недо-C с урезанными возможностями. Впрочем не только там, в webassembly тоже выкрутасы вокруг указателей на функции.