Как посчитать количество бит в одном байте на ассемблере?

Рейтинг: 0Ответов: 2Опубликовано: 04.11.2014

Здравствуйте.

Никак не могу додумать, как посчитать количество бит в одном байте, используя битовую маску и логические операции AND, OR, XOR, NOT.
Пишу на эмуляторе 8086.

Что такое битовая маска, я знаю, когда мы можем определенные биты числа сделать нулями или единицами, при этом другие биты оставить нетронутыми. Но как, используя маску, можно посчитать количество единиц в числе, состоящем из 8 бит (к примеру, 00011001)?

Ответы

▲ 1Принят

@walik, наверное, очевидней всего будет такой алгоритм:

Установим какой-нибудь регистр в 0xff (т.е. его младший байт будет содержать только единицы) и будем сдвигать его в цикле вправо, пока он не станет нулем. Подсчитаем число итераций и добавим к ним 1.

Теперь по поводу ассемблера. На мой взгляд, поучиться у оптимизирующего компилятора не самая плохая идея, особенно если Вы хоть немного знаете C.

Напишем простую функцию, реализующую наш алгоритм. Для того, чтобы компилятор не вычислил ответ (да, если информации достаточно, то он сделает это и лишит нас ассемблерного кода), передадим ему заполненный регистр через аргумент функции.

Вот сишный код нашей функции:

int nbits (int m) {
  int n = 0;

  while (m >>= 1)
    n++;

  return n + 1;
}

Вызовем компилятор для получения ее оптимизированного ассемблерного кода: gcc -S -O3 nbits.c
и получим в файле nbits.s вот такой текст:

        .file   "nbits.c"
        .text
        .p2align 4,,15
        .globl  nbits
        .type   nbits, @function
nbits:
.LFB0:
        .cfi_startproc
        sarl    %edi
        je      .L5
        xorl    %eax, %eax
        .p2align 4,,10
        .p2align 3
.L4:
        addl    $1, %eax
        sarl    %edi
        jne     .L4
        addl    $1, %eax
        ret
.L5:
        movl    $1, %eax
        ret
        .cfi_endproc
.LFE0:
        .size   nbits, .-nbits
        .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
        .section        .note.GNU-stack,"",@progbits

Вызывающая программа передает в nbits() один аргумент в регистре edi. Интересующий нас результат возвращается в регистре eax.

Поскольку Вы изучаете ассемблер, то, думаю, сами выудите оттуда и приведет к нужному виду относящийся к делу цикл.

Вызывающий main очевиден:

// file c.c: prints byte size using external function 
int 
main (int ac, char *av[])
{
  return printf ("byte size: %d\n", nbits(0xff)) == EOF;
}

Теперь соберем все вместе и посмотрим на результат:

avp@avp-ubu1:hashcode$ gcc c.c nbits.s -O3
avp@avp-ubu1:hashcode$ ./a.out 
byte size: 8
avp@avp-ubu1:hashcode$ uname -a
Linux avp-ubu1 3.13.0-39-generic #66-Ubuntu SMP Tue Oct 28 13:30:27 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
avp@avp-ubu1:hashcode$
▲ 1

Если надо посчитать биты в байте, проще всего использовать lookup-таблицу:

mov ebx, table
xlat

; ...
table db 0    ; 0000 0000
      db 1    ; 0000 0001
      db 1    ; 0000 0010
      db 2    ; 0000 0011
; и т. д.