Вы не можете знать, какое значение сгенерированный компилятором код будет храниться в любом регистре при выполнении встроенного оператора asm
, поэтому значение обычно не имеет смысла, и вам будет гораздо лучше использовать отладчик для просмотра регистрировать значения при остановке в точке останова.
Как говорится, если вы собираетесь выполнить это странное задание, вы также можете сделать это эффективно.
В некоторых целях (например, x86) вы можете использовать выходные ограничения конкретного регистра, чтобы сообщить компилятору , в каком регистре будет находиться выход. Используйте выходное ограничение конкретного регистра с пустым asm template (нулевые инструкции), чтобы сообщить компилятору, что ваш оператор asm не заботится об этом значении регистра на входе, но после этого данная переменная C будет в этом регистре.
#include <stdint.h>
int foo() {
uint64_t rax_value; // type width determines register size
asm("" : "=a"(rax_value)); // =letter determines which register (or partial reg)
uint32_t ebx_value;
asm("" : "=b"(ebx_value));
uint16_t si_value;
asm("" : "=S"(si_value) );
uint8_t sil_value; // x86-64 required to use the low 8 of a reg other than a-d
// With -m32: error: unsupported size for integer register
asm("# Hi mom, my output constraint picked %0" : "=S"(sil_value) );
return sil_value + ebx_value;
}
Скомпилировано с clang5.0 на Godbolt для x86-64 . Обратите внимание, что 2 неиспользуемых выходных значения оптимизированы, а не #APP
/ #NO_APP
сгенерированных компилятором пар asm-комментариев (которые переводят ассемблер / в режим быстрого анализа или, по крайней мере, используются, если это больше не вещь ). Это потому, что я не использовал asm volatile
, и у них есть выходной операнд, поэтому они не являются неявно volatile
.
foo(): # @foo()
# BB#0:
push rbx
#APP
#NO_APP
#DEBUG_VALUE: foo:ebx_value <- %EBX
#APP
# Hi mom, my output constraint picked %sil
#NO_APP
#DEBUG_VALUE: foo:sil_value <- %SIL
movzx eax, sil
add eax, ebx
pop rbx
ret
# -- End function
# DW_AT_GNU_pubnames
# DW_AT_external
Обратите внимание на сгенерированный компилятором код для добавления двух выходных данных напрямую из указанных регистров. Также обратите внимание на push / pop в RBX, потому что RBX - это сохраняемый вызовом регистр в соглашении о вызовах System V. (И в основном все 32 и 64-битные соглашения о вызовах x86). Но мы сказали компилятору, что наш оператор asm записывает туда значение. (Использование пустого оператора asm - это своего рода хак; нет синтаксиса для прямого указания компилятору, что мы просто хотим прочитать регистр, потому что, как я сказал, вы не знаете, что компилятор делал с регистрами, когда ваш оператор asm вставлено.) * * одна тысяча двадцать два
Компилятор будет обрабатывать ваш оператор asm так, как если бы он на самом деле записал этот регистр, поэтому, если ему понадобится значение на более позднее время, он скопирует его в другой регистр (или выгрузит в память), когда ваш asm оператор "бежит".
Другие x86 регистровые ограничения : b
(bl / bx / ebx / rbx), c
(... / rcx), d
(... / rdx), S
(sil / si / esi / rsi), D
(... / rdi) . Для bpl / bp / ebp / rbp нет особых ограничений, даже если они не являются специальными в функциях без указателя кадра. (Возможно, потому что его использование сделает ваш код не компилятором с -fno-omit-frame-pointer
.)
Вы можете использовать register uint64_t rbp_var asm ("rbp")
, в этом случае asm("" : "=r" (rbp_var));
гарантирует, что ограничение "=r"
выберет rbp
. Аналогично для r8-r15, которые также не имеют явных ограничений. На некоторых архитектурах, таких как ARM, переменные asm-register являются единственным способом указать, какой регистр требуется для ограничений ввода / вывода asm. (И обратите внимание, что ограничения asm - это only поддерживаемое использование register asm
переменных ; нет никакой гарантии, что значение переменной будет в этом регистре в любое другое время .
Ничто не мешает компилятору размещать эти операторы asm в любом месте функции (или родительских функций после встраивания) . Таким образом, вы не можете контролировать , где вы выбираете значение из регистра. asm volatile
может избежать переупорядочения, но может быть только в отношении других volatile
обращений. Вы можете проверить сгенерированный компилятором asm, чтобы увидеть, получили ли вы то, что хотели, но помните, что это могло произойти случайно и могло сломаться позже.
Вы можете поместить оператор asm в цепочку зависимостей для чего-то еще, чтобы контролировать, где компилятор помещает его . Используйте ограничение "+rm"
, чтобы сообщить компилятору, что он изменяет некоторую другую переменную, которая фактически используется для чего-то, что не оптимизируется.
uint32_t ebx_value;
asm("" : "=b"(ebx_value), "+rm"(some_used_variable) );
, где some_used_variable
может быть возвращаемым значением из одной функции и (после некоторой обработки) передаваться как аргумент другой функции. Или вычисляется в цикле и будет возвращено как возвращаемое значение функции. В этом случае оператор asm гарантированно должен появиться в некоторый момент после окончания цикла и перед любым кодом, который зависит от более позднего значения этой переменной.
Это победит оптимизацию, такую как постоянное распространение для этой переменной. https://gcc.gnu.org/wiki/DontUseInlineAsm. Компилятор не может принять что-либо о выходном значении; он не проверяет, что оператор asm
содержит нулевые инструкции.
Это не работает для некоторых регистров, которые gcc не позволит вам использовать в качестве выходных операндов или сгустков, например. указатель стека.
Однако чтение значения в переменную C может иметь смысл для указателя стека, если ваша программа делает что-то особенное со стеками.
В качестве альтернативы inline-asm есть __builtin_frame_address(0)
для получения адреса стека. (Но, согласно IIRC, эта функция создает кадр полного стека, даже когда -fomit-frame-pointer
включен, как по умолчанию в x86.)
Тем не менее, во многих функциях это почти бесплатно (а создание стекового кадра может быть полезно для размера кода из-за меньших режимов адресации для относительного RBP, чем относительного RSP-доступа к локальным переменным).
Использование инструкции mov
в операторе asm
также, конечно, сработает.