Всегда ли вывод определяется регистром% eax во встроенной сборке в C? - PullRequest
0 голосов
/ 17 февраля 2020

Я читал учебники по встроенной сборке в C, и они попробовали простое присвоение переменной с

int a=10, b;
asm ("movl %1, %%eax; 
movl %%eax, %0;"
:"=r"(b)        /* output */
:"r"(a)         /* input */
:"%eax"         /* clobbered register */
);

, которое имело смысл для меня (переместите ввод в eax, затем переместите eax на вывод). Но когда я удалил строку %movl %%eax, 0 (которая должна перемещать правильное значение на выход), переменной b все еще было присвоено правильное значение из встроенной сборки.

Мой главный вопрос: как вывод «знает» читать из этого регистра% eax?

1 Ответ

2 голосов
/ 17 февраля 2020

Оператор встроенной сборки не является вызовом функции .

Функция возврата в 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-операторах.

...