И как вы предлагаете получать колбэки/разбирать структуры в "чистых" сях?
Разумеется, можно наделать "прокладок" и их стабилизировать. Но тут уж на выбор:
* Выжимаем всё до такта — получаем вот это с регулярными камингаутами^w выходами за границы и т.п. null dereference/double free. Пример: большинство библиотек (тормознутые почему-то не популярны). Knot, unbound (как примеры наличия способов писать достаточно чистый код без массовых "памперсов", но "дорого").* Пишем фреймворк (берём чей-то) и заставляем всех в проекте им пользоваться. Поверхность атаки сокращается в разы, но всё ещё можно налажать во внутренней логике, умножаем лажу на мощь сей как продвинутого кроссассемблера. И да, разумеется результат медленнее (насколько-то). Пример: asterisk (как бы к нему не относились, там этот подход достаточно рельефно реализован). SSSD (трэш и угар, но таки тоже натянуто на фреймворк).
* Выносим всё что можно изолировать наружу и кодим на ЯП со встроенными защитами/гарантиями. Теряем в производительности/скорости разработки в зависимости от способа реализации гарантий. В прикладном софте это обычно совершенно оправдано. Проблемы начинаются при попытках потеснее работать с системными обвязками. Когда "слева" к вам приезжает не пойми что (к примеру, то ли ipv4 то ли ipv6 и там где-то в конце указатель на следующий элемент списка), а "справа" pascal|ada (к примеру, "хайповые" ЯП в той же лодке), то это таки "ой". И не разбираясь досконально в сишной "кухне" (а откуда, вы на этом изначально сэкономили, завязавшись на "прикладной" ЯП) что-то тут сделать очень непросто.