Оператор встроенной сборки не является вызовом функции .
Функция возврата в EAX предназначена для функций ; это часть соглашения о вызовах , которое позволяет компиляторам создавать код, который может взаимодействовать с другим кодом, даже если они компилируются отдельно. Соглашение о вызовах определяется как часть ABI do c.
. Помимо определения способа возврата (например, небольшие не-FP объекты в EAX, с плавающей запятой в XMM0 или ST0), они также определяют, где вызывающие абоненты ставят аргументы, и какие регистры вы можете использовать без сохранения / восстановления (call-clobbered), а какие вы можете (call-preserved). См. https://en.wikipedia.org/wiki/Calling_convention в целом и https://www.agner.org/optimize/calling_conventions.pdf для получения дополнительной информации о соглашениях о вызовах x86.
Этот негибкий жесткий набор правил не применяется к встроенный asm, потому что он не должен ; компилятор обязательно может видеть оператор asm как часть окружающего кода C. Это победило бы весь смысл inline . Вместо этого, в GNU C inline asm вы пишете операнды / ограничения, которые описывают asm для компилятора, эффективно создавая пользовательское соглашение о вызовах для каждого оператора asm. (С частями этого соглашения остается выбор компилятора для "=r"
выходов. Используйте "=a"
, если вы хотите, чтобы он выбрал AL / AX / EAX / RAX.)
Если вы хотите написать asm, который возвращается в EAX без необходимости сообщать об этом компилятору, напишите отдельную функцию. (Например, в файле .s
или в операторе asm("")
в качестве тела функции __attribute__((naked))
C. В любом случае вам нужно написать ret
самостоятельно и получить аргументы также через соглашение о вызовах.)
Падение конца функции, отличной от void
, после выполнения оператора asm
, который оставляет значение в EAX, может показаться работающим с отключенной оптимизацией, но это совершенно небезопасно и сломается, как только вы включите оптимизацию, и компилятор ее встроит.
Мой главный вопрос: как вывод «знает» читать из этого регистра% eax?
Это, вероятно, просто выбрал EAX для вывода "=r"
, когда вы компилировали с отключенной оптимизацией. EAX всегда является первым выбором G CC для оценки выражений. Посмотрите на сгенерированный компилятором вывод asm (gcc -S -fverbose-asm
), чтобы увидеть, какой asm он сгенерировал вокруг вашего asm, и какой регистр он подставил в ваш шаблон asm . У вас, вероятно, mov %eax, %eax
; mov %eax, %eax
.
Использование mov
в качестве первой или последней инструкции шаблона asm почти всегда означает, что вы делаете это неправильно и должны были использовать лучшие ограничения, чтобы сообщить компилятору, куда поместить или где найти ваши данные.
Например, asm("" : "=r"(b) : "0"(a))
заставит компилятор поместить входные данные в тот же регистр, что и ожидаемый выходной операнд. Так что это копирует значение. (И вынуждает компилятор материализовать его в регистре и забыть все, что он знает о текущем значении, обойти оптимизацию с постоянным распространением и диапазоном значений, а также помешать компилятору полностью оптимизировать временную оптимизацию.)
Почему выдача пустых команд asm меняет местами переменные? описывает, что происходит при изменении, так же, как в вашем случае с компилятором, выбирающим один и тот же регистр для входных и выходных операндов "r"
. И иллюстрирует , используя asm комментарии * внутри шаблона asm , чтобы распечатать то, что компилятор выбрал для любых операндов %0
или %1
, на которые вы иначе не ссылаетесь явно **.
См. также ошибка сегментации (сбрасывается ядро) при использовании встроенной сборки для получения дополнительных сведений об основах использования ограничений ввода и вывода.
Также связано: Что происходит с регистрами при манипулировании ими использование asm-кода на C ++? для другого примера и описание того, как компиляторы обрабатывают регистр в GNU C встроенных asm-операторах.