книга очень ясно показывает, что у вас есть шесть регистров, в которые можно поместить шесть переменных в
Вы читаете книгу о 32-битном x86.(И книга предполагает, что EBP будет использоваться как указатель кадра, оставляя только 6 из 8 целочисленных регистров как действительно универсальное).
Вы компилируете для x86-64 с включенной оптимизацией, которая включает -fomit-frame-pointer
Таким образом, у вас есть 15 целочисленных регистров общего назначения.
Что мне нужно знать, так это то, почему программа [function] intlen()
помещает все свои значения в стек
Это , а не , вполне то, что происходит.x
остается в RDI, а не выливается в стек при входе в функцию, как если бы вы отключили оптимизацию (gcc -O0
).Компиляция без оптимизации, чтобы увидеть большую разницу.
Компилятор поддерживает как можно большее количество переменных в regs, но v
и buf
должны существовать в памяти, потому что вы передаете указателиим не встроенная функция.
Вы, кажется, как-то отключили встраивание iptoa
.Возможно, вы скомпилировали только с -O1
, потому что у вас нет __attribute__((noinline))
в вашем определении iptoa
.Если вы включили полную оптимизацию (-O3
), вы увидите, что v
оптимизируется, и вы просто получите movq %rdi, %rdx
для передачи x
в качестве 3-го аргумента sprintf
.
Передача &v
не встроенному iptoa
означает, что память для v
должна быть "синхронизирована" , потому что iptoa
разрешено читать эту память через указатель, который вы ей передали.См. Также «escape анализ» - если указатель на переменную «экранирует» функцию, компилятор не может оптимизировать ее или делать с ней слишком много странных вещей.
IDK почемувы передаете целое число по ссылке;Вы написали код, который заставляет компилятор использовать память для большинства своих переменных.(Если он не может быть встроен.)
Кстати, вы знаете, что ваша функция очень неэффективна, верно? Вам не нужно вычислять каждую десятичную цифру с помощью sprintf , просто найдите первую степень 10, которая больше числа.
int intlen_fast(long x) {
unsigned long absx = x;
unsigned len = 1; // even 0..9 takes 1 decimal digit
if (x<0) {
absx = -x; // unsigned abs correctly handles the most-negative 2's complement integer
len = 2; // the minus sign
}
// don't need to check for overflow of pow10 with 64-bit integers
// but in general we do to get the right count. (TODO)
for (unsigned long pow10 = 10; pow10 <= absx ; pow10*=10) {
len++;
}
return len;
}
Выполнение pow10 *= 10;
значительно более эффективночем x /= 10
, даже с оптимизированным делением на константу времени компиляции.
Для 64-битного unsigned long
, это имеет очень хорошее свойство, что abs(LLONG_MIN) = 9223372036854775808ULL
, и следующая наибольшая мощность 10 не делаетt переполнение unsigned long long
.(ULLONG_MAX
= 18446744073709551615ULL)
Если это не так (например, для 32-битных unsigned long
в других ABI), вам необходимо проверить, является ли специальный случай absx >= 1000000000
для правильногообрабатывать входные величины в диапазоне от 1000000000
до 2147483648
, потому что 2 ^ 32-1 = 4294967296
.(К счастью, мы не получаем бесконечный цикл, просто 2 дополнительных итерации до тех пор, пока pow10
= 0xd4a51000 не станет без знака выше величины любого 32-разрядного целого числа со знаком. Но это все еще неправильный ответ!)В общем, C ++ имеет std::numeric_limits<long>::digits10
против std::numeric_limits<unsigned long>::digits10
, что может быть полезно для определения во время компиляции, нужна ли нам дополнительная проверка.Или на самом деле нет, потому что он округляется до двоичного значения ширины в битах std::log10(2)
.
Может быть, проверка во время компиляции на основе Как округлить до ближайшей степени 10? LONG_MAX
меньше, чем ULONG_MAX
, если ваш компилятор может выполнять постоянное распространение через floor(log10(ULONG_MAX))
.
Если вы не хотите беспокоиться о деталях pow10
возможно переполнение, все равно было бы намного быстрее, чем вызывать sprintf
, чтобы просто повторить деление на 10 для подсчета цифр.
Или, может быть, сделать одно деление на 10, а затем выполнить цикл pow10
вверх.Это было бы безопасно от переполнения / обтекания и просто.(Но вы все равно должны обрабатывать отрицательный ввод специально).
Но в любом случае оптимизированная версия из gcc8.3 -O3
, конечно, сохраняет все свои переменные в регистрах ( компилятор Godboltисследователь ).-fstack-protector-strong
не влияет на эту функцию, потому что она не имеет никаких массивов.
# gcc8.3 -O3 -fverbose-asm -fstack-protector-strong
intlen_fast(long):
testq %rdi, %rdi # x
js .L14 #,
movl $1, %eax #, <retval>
movl $1, %edx #, len
.L15:
cmpq $9, %rdi #, absx
jbe .L13 #,
movl $10, %eax #, pow10
.L17:
leaq (%rax,%rax,4), %rax #, tmp95 # pow10 * 5
addl $1, %edx #, len
addq %rax, %rax # pow10 # pow10 *= 10
cmpq %rax, %rdi # pow10, absx
jnb .L17 #,
movl %edx, %eax # len, <retval>
.L13:
ret
.L14:
negq %rdi # absx
movl $2, %eax #, <retval>
movl $2, %edx #, len
jmp .L15 #
(похоже на пропущенную оптимизацию, что gcc устанавливает и EAX, и EDX. Он должен просто использовать RDX внутри цикла для pow10
и len
в EAX.)
См. Ссылку на Godbolt длянекоторые тестовые вызовы, которые показывают, что это работает для угловых случаев, таких как -9
, 99
, 100
и 101
, без ошибок, связанных с ошибкой.И для больших входов.