gcc8.3 -O3 выдает именно то, что задано в вопросе для этого способа записи проверки диапазона с использованием трюка без знака сравнения.
int is_ascii_lowercase_v2(int y){
unsigned char x = y-'a';
return x <= (unsigned)('z'-'a');
}
Сужение до 8 бит после int
вычитание соответствует asm более точно, но это не обязательно для корректности или даже для убеждения компиляторов использовать 32-битный sub
.Для unsigned char y
старшие байты RDI могут содержать произвольный мусор (соглашение о вызовах System V x86-64), но перенос только распространяется от низкого к высокому с помощью sub и add.
Младшие 8 битоврезультат (который читает cmp
) будет таким же, как у sub $'a', %dil
или sub $'a', %edi
.
Запись в качестве обычной проверки диапазона также позволяет gcc выдавать идентичный код, потому что компиляторы знают, как оптимизировать проверку диапазона .(И gcc выбирает 32-битный размер операнда для sub
, в отличие от clang, который использует 8-битный.)
int is_ascii_lowercase_v3(char y){
return (y>='a' && y<='z');
}
В проводнике компилятора Godbolt , this и _v2
компилируются следующим образом:
## gcc8.3 -O3
is_ascii_lowercase_v3: # and _v2 is identical
subl $97, %edi
xorl %eax, %eax
cmpb $25, %dil
setbe %al
ret
Возвращая результат сравнения в виде целого числа, вместо использования if
, гораздо более естественно соответствует asm.
Но даже запись этого «без ответвления» в C не будет соответствовать asm, если вы не включите оптимизацию.Код по умолчанию для gcc / clang: -O0
: анти-оптимизация для согласованной отладки, хранения / перезагрузки всего в памяти между операторами.(И аргументы функции при входе в функцию.) Вам нужна оптимизация, потому что -O0 code-gen (намеренно) в основном мозговой мертвец , и выглядит неприятно.См. Как удалить «шум» из выходных данных сборки GCC / clang?
## gcc8.3 -O0
is_ascii_lowercase_v2:
pushq %rbp
movq %rsp, %rbp
movl %edi, -20(%rbp)
movl -20(%rbp), %eax
subl $97, %eax
movb %al, -1(%rbp)
cmpb $25, -1(%rbp)
setbe %al
movzbl %al, %eax
popq %rbp
ret
gcc и clang с включенной оптимизацией будут выполнять if-преобразование в код без ответвлений, когда это эффективно.например,
int is_ascii_lowercase_branchy(char y){
unsigned char x = y-'a';
if (x < 25U) {
return 1;
}
return 0;
}
по-прежнему компилируется с тем же ассемблером с GCC8.3 -O3
is_ascii_lowercase_branchy:
subl $97, %edi
xorl %eax, %eax
cmpb $25, %dil
setbe %al
ret
Мы можем сказать, что уровень оптимизации был по крайней мере gcc -O2
.В -O1
, gcc использует менее эффективный setbe / movzx вместо обнуления EAX перед setbe
is_ascii_lowercase_v2:
subl $97, %edi
cmpb $25, %dil
setbe %al
movzbl %al, %eax
ret
Я никогда не смогу заставить clang воспроизвести точно такую же последовательность инструкций.Ему нравится использовать add $-97, %edi
и cmp с $26
/ setb
.
Или он будет делать действительно интересные (но неоптимальные) вещи вроде этого:
# clang7.0 -O3
is_ascii_lowercase_v2:
addl $159, %edi # 256-97 = 8-bit version of -97
andl $254, %edi # 0xFE; I haven't figured out why it's clearing the low bit as well as the high bits
xorl %eax, %eax
cmpl $26, %edi
setb %al
retq
Так что это что-то, связанное с -(x-97)
, возможно, где-то там используется идентификатор дополнения 2 (-x = ~x + 1
).