C ABI: изменяет ли void функцию, чтобы вернуть int критическое изменение?

Есть ли какая-либо неэкзотическая архитектура/ОС/компилятор, где изменение:

void func(void *, int, int)

в

int func(void *, int, int)

нарушит ABI? (то есть программа, скомпилированная для "void" версии разделяемой библиотеки, прерывается при запуске с версией "int" ).

Ответ 1

Несколько комментариев сказали нет, и мы можем укрепить это с небольшими рассуждениями. В наиболее знакомых ABI функции с объявлением void func(void *, int, int) разрешено использовать регистр, в котором результат int будет возвращен как регистр нуля; его не нужно сохранять и восстанавливать. Функция с объявлением int func(void *, int, int) требуется , чтобы использовать регистр, в который будет возвращен результат int. В остальном эти декларации идентичны. Поэтому машинный код любой реализации int func(void *, int, int) также является машинным кодом, который удовлетворяет void func(void *, int, int).

Другими словами, вызывающий не имеет возможности отличать машинный код, который сознательно возвращает результат в регистре возврата из кода, который, как случается, оставляет в этом регистре некоторый расчет нуля.

Обратите внимание, что это рассуждение требует, чтобы вызываемая функция скрывалась за ABI; он полагается на двоичное поведение, указанное ABI. Если источник вызываемой функции отображается, когда вызываемая функция скомпилирована (или другие части реализации, которые могут повлиять на реализацию вызова), тогда оптимизация может привести к поведению, которое обходит ABI (например, соблюдая, что вызванная процедура не использует регистр возврата и поэтому использует его в вызывающем абоненте, чтобы сохранить некоторое значение, которое, как ожидается, останется неизменным по вызову).

Поскольку вы говорите, что это для общей библиотеки, вы, вероятно, безопасны: общая библиотека скомпилирована отдельно, а ее источник недоступен для ее вызывающих. Однако вы должны рассмотреть боковые каналы. Например, возможно, разделяемая библиотека является частью набора библиотек, которые обмениваются источниками и содержат межбиблиотечные вызовы. В этом случае у кого-то может быть старая версия общей библиотеки, которая была скомпилирована с представлением источника void и новой версией разделяемой библиотеки, содержащей источник int. (И даже для этого требуются необычные компоновки исходного кода или модные компиляторы, которые интегрируют информацию по нескольким единицам компиляции.)