2010-10-10 3 views
15

У меня возникли трудности с пониманием роли ограничений в встроенной сборке GCC (x86). У меня read the manual, что объясняет, что именно делает каждое ограничение. Проблема в том, что, хотя я понимаю, что делает каждое ограничение, я мало понимаю, почему вы должны использовать одно ограничение над другим или какие последствия могут быть.Встроенная сборка GCC: ограничения

Я понимаю, что это очень широкая тема, поэтому небольшой пример должен помочь сузить фокус. Ниже приведена простая процедура asm, которая просто добавляет два числа. Если происходит переполнение целых чисел, оно записывает значение 1 в переменную выходного С.

int32_t a = 10, b = 5; 
int32_t c = 0; // overflow flag 

__asm__ 
(
    "addl %2,%3;"  // Do a + b (the result goes into b) 
    "jno 0f;"   // Jump ahead if an overflow occurred 
    "movl $1, %1;"  // Copy 1 into c 
    "0:"     // We're done. 

    :"=r"(b), "=m"(c) // Output list 
    :"r"(a), "0"(b)  // Input list 
); 

Теперь это работает отлично, за исключением того, я должен был произвольно возиться с ограничениями, пока я не получил его, чтобы работать правильно. Первоначально я использовал следующие ограничения:

:"=r"(b), "=m"(c) // Output list 
    :"r"(a), "m"(b)  // Input list 

Обратите внимание, что вместо «0», я использую «M» ограничение для b. У этого был странный побочный эффект: если я скомпилировал флаги оптимизации и дважды вызвал функцию, по какой-то причине результат операции добавления также будет сохранен в c. В конце концов я прочитал о «matching constraints», который позволяет указать, что переменная должна использоваться как как входной, так и выходной операнд. Когда я сменил "m"(b) на "0"(b), он сработал.

Но я действительно не понимаю, почему вы использовали бы одно ограничение над другим. Я имею в виду, да, я понимаю, что «r» означает, что переменная должна быть в регистре, а «m» означает, что она должна быть в памяти, - но я не знаю действительно понять, какие последствия выбора одного над другим, или почему операция добавления не работает правильно, если я выберу определенную комбинацию ограничений.

Вопросы: 1) В приведенном выше примере код, почему ограничение «m» на b вызывать c, чтобы получить письменность? 2) Есть ли какой-либо учебник или онлайн-ресурс, который более подробно описывает ограничения?

ответ

13

Вот пример, чтобы лучше проиллюстрировать, почему вы должны тщательно выбирать ограничения (те же функции, как у вас, но, возможно, написал немного более лаконично):

bool add_and_check_overflow(int32_t& a, int32_t b) 
{ 
    bool result; 
    __asm__("addl %2, %1; seto %b0" 
      : "=q" (result), "+g" (a) 
      : "r" (b)); 
    return result; 
} 

Таким образом, ограничения были использованы: q, r, и g.

  • q означает только eax, ecx, edx или ebx может быть выбран. Это связано с тем, что инструкции set* должны записываться в 8-битный адресный регистр (al, ah, ...). Использование b в значении %b0 использует самую низкую 8-разрядную часть (al, cl, ...).
  • Для большинства инструкций с двумя операндами по крайней мере один из операндов должен быть регистром. Поэтому не используйте m или g для обоих; используйте r для хотя бы одного из операндов.
  • Для окончательного операнда не имеет значения, является ли это регистром или памятью, поэтому используйте g (общий).

В приведенном выше примере, я решил использовать g (а не r) для a поскольку ссылки обычно реализуются как указатели памяти, таким образом, используя r ограничение потребовало бы копирования референта в регистр, а затем копирование назад. Используя g, референт может быть обновлен напрямую.


Как почему ваша оригинальная версия затирает ваши c со значением добавлением в это потому, что вы указали =m в выходной слот, а не (скажем) +m; это означает, что компилятору разрешено повторно использовать одну и ту же ячейку памяти для ввода и вывода.

В вашем случае, это означает, что два результата (так же область памяти используется для b и c):

  • добавлением не Переполнение: то, c получил перезаписаны со значением b (результат добавления).
  • дополнение переполнение: тогда c стал 1 (и b может стать 1 также, в зависимости от того, как был сгенерирован код).
+0

Спасибо, это отличный ответ. Просто одно уточнение: почему модификатор ограничения '=' (только для записи) дает компилятору право повторного использования одного и того же места в памяти, даже если 'b' и' c' - разные переменные с разными местоположениями в памяти? – Channel72

+0

@ Channel72: «хотя' b' и 'c' - разные переменные с разными местами в памяти» --- это на самом деле основное предположение, которое часто не применяется. Если 'b' и' c' являются локальными переменными, есть вероятность, что оба они фактически поддерживаются регистрами, а не местом памяти. В этом случае место памяти является просто временным местом хранения, которое настроено исключительно для размещения вашего ограничения 'm' --- в этом случае' b' и 'c' могут очень хорошо использовать одно и то же временное местоположение. –

+0

Теперь, если 'b' и' c' на самом деле были действительно поддержаны ячейками памяти, тогда вы были бы правы в том, что обычно они не должны перекрываться вообще. И, если один поддерживается памятью, а другой поддерживается регистром ... тогда возможен любой из этих сценариев. –

Смежные вопросы