Я должен реализовать «полосовой» фильтр. Пусть a
и b
обозначают два целых числа, которые вызывают полуоткрытый интервал [a, b)
. Если какой-то аргумент x
находится в этом интервале (т. Е. a <= x < b
), я возвращаю указатель на C строку const char* high
, в противном случае я возвращаю указатель const char* low
. Ванильная реализация этой функции выглядит как
const char* vanilla_bandpass(int a, int b, int x, const char* low,
const char* high)
{
const bool withinInterval { (a <= x) && (x < b) };
return (withinInterval ? high : low);
}
, которая при компиляции с -O3 -march=znver2
на Godbolt дает следующий код сборки
vanilla_bandpass(int, int, int, char const*, char const*):
mov rax, r8
cmp edi, edx
jg .L4
cmp edx, esi
jge .L4
ret
.L4:
mov rax, rcx
ret
Теперь я посмотрел на создание версии без перехода / перехода, который выглядит следующим образом
#include <cstdint>
const char* funky_bandpass(int a, int b, int x, const char* low,
const char* high)
{
const bool withinInterval { (a <= x) && (x < b) };
const auto low_ptr = reinterpret_cast<uintptr_t>(low) * (!withinInterval);
const auto high_ptr = reinterpret_cast<uintptr_t>(high) * withinInterval;
const auto ptr_sum = low_ptr + high_ptr;
const auto* result = reinterpret_cast<const char*>(ptr_sum);
return result;
}
, который в конечном итоге является просто «аккордом» между двумя указателями. Используя те же опции, что и раньше, этот код компилируется в
funky_bandpass(int, int, int, char const*, char const*):
mov r9d, esi
cmp edi, edx
mov esi, edx
setle dl
cmp esi, r9d
setl al
and edx, eax
mov eax, edx
and edx, 1
xor eax, 1
imul rdx, r8
movzx eax, al
imul rcx, rax
lea rax, [rcx+rdx]
ret
. Хотя на первый взгляд эта функция имеет больше инструкций, тщательный сравнительный анализ показывает, что она в 1,8x - 1,9 раза быстрее, чем реализация vanilla_bandpass
.
Является ли это использование uintptr_t
допустимым и свободным от неопределенного поведения? Я хорошо знаю, что язык вокруг uintptr_t
является, по меньшей мере, неопределенным и неоднозначным, и что все, что явно не указано в стандарте (например, arithmeti c на uintptr_t
), обычно считается неопределенным поведением. С другой стороны, во многих случаях стандарт явно вызывает, когда что-то имеет неопределенное поведение, чего в этом случае он также не делает. Мне известно, что «смешивание», которое происходит при объединении low_ptr
и high_ptr
, затрагивает темы так же, как происхождение указателя, что само по себе является мутным топи c.