В первые дни C до стандартизации реализации имели множество способов обработки исключительных и полуизбыточных случаев различных действий. Некоторые из них вызовут ловушки, которые могут привести к случайному выполнению кода, если они не настроены в первую очередь. Поскольку поведение таких ловушек выходит за рамки стандарта C (и в некоторых случаях может контролироваться операционной системой вне контроля запущенной программы) и избегать того, чтобы компиляторы не допускали код, который полагался на такие ловушек, чтобы продолжать это делать, поведение действий, которые могли бы вызвать такие ловушки, оставалось полностью на усмотрение компилятора/платформы.
К концу 1990-х годов, хотя и не требовалось сделать это по стандарту C, каждый основной компилятор принимал общее поведение для многих из этих ситуаций; использование такого поведения позволило бы улучшить скорость, размер и читаемость кода.
Поскольку "очевидные" способы запроса следующих операций больше не поддерживаются, как следует их заменять таким образом, чтобы не препятствовать читаемости и не влиять на генерацию кода при использовании более старых компиляторов? Для целей описания предположим, что int
- 32-разрядный, ui
- это unsigned int, si
- подпись int, а b
- unsigned char.
-
Учитывая
ui
иb
, вычислитеui << b
для b == 0..31 или значение, которое может произвольно вести себя какui << (b & 31)
или ноль для значений 32..255. Обратите внимание: если левый операнд равен нулю всякий раз, когда правый операнд превышает 31, оба поведения будут одинаковыми. -
Для кода, который должен выполняться только на процессоре, который дает нуль при сдвиге вправо или влево на величину от 32 до 255, вычислить
ui << b
для b == 0..31 и 0 для б == 32..255. Хотя компилятор может оптимизировать условную логику, предназначенную для пропуска сдвига для значений 32..255 (поэтому код просто выполнил бы сдвиг, который даст правильное поведение), я не знаю, каким образом можно сформулировать такую условную логику что гарантирует, что компилятор не будет генерировать ненужный код для него. -
Как и для 1 и 2, но для сдвигов вправо.
-
Учитывая
si
иb
, что b0..30 иsi*(1<<b)
не будут переполняться, вычислитеsi*(1<<b)
. Обратите внимание, что использование оператора умножения сильно ухудшит производительность многих старых компиляторов, но если целью сдвига является масштабирование знакового значения, то приведение в неподписанное значение в случаях, когда операнд будет оставаться отрицательным во время сдвига, кажется неправильным. -
Учитывая различные целочисленные значения, выполняйте добавления, вычитания, умножения и сдвиги, таким образом, чтобы при отсутствии переполнения результаты были правильными, и если есть переполнения, код будет либо генерировать значения, верхние биты которых ведут себя в не-ловушке и не-UB, но в противном случае неопределенном способе или ловушке распознаваемой платформы (и на платформах, которые не поддерживают ловушки, просто даст неопределенное значение).
-
Указав указатель на выделенную область и некоторые указатели на вещи внутри нее, используйте
realloc
, чтобы изменить размер выделения и настроить вышеупомянутые указатели, чтобы они соответствовали, избегая при этом дополнительной работы в случаях, когдаrealloc
возвращает оригинальный блок. Не обязательно возможно на всех платформах, но основные платформы 1990-х годов позволят коду определять, может лиrealloc
заставить вещи двигаться, и определить, какое смещение указателя в мертвый объект было использовано, вычитая прежний базовый адрес этого объект (обратите внимание, что настройка должна быть выполнена путем вычисления смещения, связанного с каждым мертвым указателем, а затем добавления нового указателя вместо того, чтобы пытаться вычислить "разницу" между старыми и новыми указателями - то, что было бы законно fail на многих сегментированных архитектурах).
Составляют ли "гиперсовременные" компиляторы какие-либо хорошие замены для вышеуказанного, которые не ухудшат хотя бы один из размеров, скорости или удобочитаемости кода, не предлагая никаких улучшений ни в одном из других? Из того, что я могу сказать, не только 99% компиляторов на протяжении 1990-х годов делали все вышеперечисленное, но для каждого примера можно было бы написать код таким же образом почти для всех из них. Несколько компиляторов, возможно, пытались оптимизировать сдвиги влево и вправо с помощью таблицы неохраняемых прыжков, но единственный случай, когда я могу думать о том, где компилятор 1990-х годов для платформы 1990-х годов будет иметь проблемы с "очевидным" способом кодирования любой из вышеперечисленных. Если эти гиперсовременные компиляторы перестали поддерживать классические формы, что они предлагают в качестве замены?