Почему инструкции x86 INC
(приращение) и DEC
(уменьшение) не влияют на CF
(флаг переноса) в FLAGSREGISTER?
Почему инструкции INC и DEC *не* влияют на флаг переноса (CF)?
Ответы (4)
Чтобы понять, почему вам, вероятно, нужно вспомнить, что нынешние процессоры «x86» с 32- и 64-битными значениями начали свою жизнь как гораздо более ограниченные 8-битные машины, восходящие к Intel 8008. (Я кодировал в этом мире еще в 1973 году, я до сих пор помни (тьфу) это!).
В том мире регистры были драгоценными и маленькими. Вам нужны INC
/DEC
для различных целей, наиболее распространенной из которых является управление контуром. Многие циклы включали выполнение «арифметики с множественной точностью» (например, 16 бит или больше!). Установив INC
/DEC
флаг нуля (Z
), вы могли бы довольно хорошо использовать их для управления циклами; настаивая на том, чтобы инструкции управления циклом не изменяли флаг переноса (CF
), перенос сохраняется между итерациями цикла, и вы можете реализовывать операции с множественной точностью без написания тонны кода для запоминания состояния переноса.
Это работало очень хорошо, как только вы привыкли к уродливому набору инструкций.
На более современных машинах с большими размерами слов вам это не нужно, поэтому INC
и DEC
могут быть семантически эквивалентны ADD
...,1 и т. д. На самом деле это то, что я использую, когда мне нужен набор переносов: - }
В основном я сейчас держусь подальше от INC
и DEC
, потому что они выполняют частичное обновление кода условия, и это может вызвать забавные задержки в конвейере, а ADD
/SUB
этого не происходит. Так что там, где это не имеет значения (в большинстве мест), я использую ADD
/SUB
, чтобы избежать киосков. Я использую INC
/DEC
только в тех случаях, когда код не имеет большого значения, например, вписывается в строку кэша, где размер одной или двух инструкций имеет достаточное значение, чтобы иметь значение. Это, вероятно, бессмысленная нано[буквально!]-оптимизация, но я довольно старомоден в своих привычках кодирования.
Мое объяснение говорит нам, почему INC
/DEC
установили нулевой флаг (Z
). У меня нет особенно убедительного объяснения, почему INC
/DEC
устанавливают знак (и флаг четности).
РЕДАКТИРОВАТЬ Апрель 2016: кажется, что проблема зависания лучше решается на современных x86s. См. инструкцию INC против ADD 1: имеет ли это значение?
dec / jge
выполнять цикл от n до 0, а не от n до 1. (т. е. провал, когда dec
производит -1
вместо 0
).
- person Peter Cordes; 25.04.2018
cmova
и cmovbe
все еще 2 моп из-за 4 общих входных данных, в отличие от других инструкций cmov, которым нужно только 3. jcc и setcc всегда 1 моп, даже если им нужно 2 отдельных входа флага). См. ответ @Bee на Что такое частичная остановка флага?
- person Peter Cordes; 04.11.2020
Вопрос о том, зачем подписывать, когда у вас есть флаг ноль, установленный inc/dec, лучше всего решать с помощью вопроса: вы бы предпочли обойтись без опции a сильный> ?
a) for (n=7;n>=0;n--) // translates to `dec + jns`
b) for (n=8;n>0;n--) // translates to `dec + jnz`
Как уже пояснил Ира Бакстер, флаг переноса используется во многих алгоритмах -- не только для арифметики с высокой точностью, но и для обработка растровых изображений в эпоху монохромного/cga/EGA: это сдвигает строку шириной 80 пикселей на один пиксель вправо...
mov cx, 10
begin: lodsb
rcr al,1 // this is rotate though carry:
stosb // for the algorithm to work, carry must not be destroyed
LOOP begin //
Но тогда: почему паритет?
Я считаю, что ответ: почему бы и нет. Этот набор инструкций относится к концу 70-х, когда транзисторов было мало. Отказ от вычисления флага четности для какой-то конкретной инструкции не имел бы никакого смысла, а только усложнил бы работу процессора.
Инструкции inc и dec обычно используются для поддержания количества итераций или циклов. При использовании 32 бит количество итераций может достигать 4 294 967 295. Этого числа достаточно для большинства приложений. Что делать, если нам нужно количество больше, чем это? Должны ли мы использовать add вместо inc? Это приводит ко второй и основной причине.
Состояние, обнаруженное флагом переноса, также может быть обнаружено флагом нуля. Почему? Потому что inc и dec изменяют число только на 1. Например, предположим, что регистр ECX достиг своего максимального значения 4 294 967 295 (FFFFFFFFH). Если мы затем выполним
inc ECX
обычно мы ожидаем, что флаг переноса будет установлен в 1. Однако мы можем обнаружить это условие, заметив, что ECX = 0, что устанавливает нулевой флаг. Таким образом, установка флага переноса действительно избыточна для этих инструкций.
inc
, но не для dec
. Для dec
ZF==0 определяет итерацию до переноса (переход от 0 к 2^32-1). Я сделал тот же комментарий, этот на другой ответ, который также приводил этот аргумент.
- person Peter Cordes; 09.07.2018
Потому что не нужно воздействовать. Достаточно проверить флаг Zero. Таким образом, после инструкций inc и dec флаг переноса остается прежним, и в некоторых случаях это полезно.
inc
: перенос идет от 0xFFFF... до 0, так что ZF и CF равны. Но dec
переходит от 0
к 0xFFFF...
, поэтому ZF устанавливается на декремент перед переносом. Настоящая причина, я думаю, заключается в том, что sub reg,1
доступен, если вам нужен CF, и обычно вам не нужен результат CF. Таким образом, сохранение CF позволяет использовать его в сочетании с ADC, сдвигами, RCL и другими вещами, которые сохраняют что-то в CF. (Конечно, это конструктивное решение было принято для 8086, задолго до того, как проблемы с частичным регистром стали рассматриваться как будущая проблема. В тот момент loop
был эффективен, поэтому зацикливание без флажков было легко.)
- person Peter Cordes; 17.04.2018