g++提供了个内置的函数:__builtin_popcount,来统计整形数字的二进制表示中1的数量。我很好奇,就想看看库函数使用什么办法实现的。

一般我们自己来写这个函数,会是这样:

int popcount1(int x)
{
    int r = 0;
    while(x)
    {
        if(x&1)
            r++;
        x = x>>1;
    }
    return r;
}

或者是这样:

int popcount2(int x)
{
    int r = 0;
    while(x)
    {
        r++;
        x = x&(x-1);
    }
    return r;
}

那么g++中是怎么实现的呢?用gdb看看

测试函数如下:

#include <iostream>
using namespace std;
int main(int argc,char** argv)
{
    if(argc!=3)
        return -1;
    int c = stoi(argv[1]);
    int d = stoi(argv[2]);
    int a = __builtin_popcount(c^d);
    cout<<a<<endl;
    return 0;
}

在gdb中,运行到这个函数:

   0x5555555552de <main(int, char**)+217>:  xor    eax,DWORD PTR [rbp-0x18]
   0x5555555552e1 <main(int, char**)+220>:  mov    eax,eax
   0x5555555552e3 <main(int, char**)+222>:  mov    rdi,rax
=> 0x5555555552e6 <main(int, char**)+225>:  call   0x5555555550a0 <__popcountdi2@plt>
   0x5555555552eb <main(int, char**)+230>:  mov    DWORD PTR [rbp-0x1c],eax
   0x5555555552ee <main(int, char**)+233>:  mov    eax,DWORD PTR [rbp-0x1c]
   0x5555555552f1 <main(int, char**)+236>:  mov    esi,eax
   0x5555555552f3 <main(int, char**)+238>:  lea    rdi,[rip+0x2da6]        # 0x5555555580a0 <std::cout@@GLIBCXX_3.4>

看到这个函数了,叫__popcountdi2@plt,关于这个plt,涉及到了linux系统中dynamic shared objects的装载时重定位机制有关,这个plt(Procedure LInkage Table)涉及到延迟绑定机制的实现。具体的我后面应该会单独写一下。

下面看看popcount函数的内容:

=> 0x7ffff7ca50a0 <__popcountdi2>:      movabs rdx,0x5555555555555555
   0x7ffff7ca50aa <__popcountdi2+10>:   mov    rax,rdi
   0x7ffff7ca50ad <__popcountdi2+13>:   shr    rax,1
   0x7ffff7ca50b0 <__popcountdi2+16>:   and    rax,rdx
   0x7ffff7ca50b3 <__popcountdi2+19>:   movabs rdx,0x3333333333333333
   0x7ffff7ca50bd <__popcountdi2+29>:   sub    rdi,rax
   0x7ffff7ca50c0 <__popcountdi2+32>:   mov    rax,rdi
   0x7ffff7ca50c3 <__popcountdi2+35>:   shr    rdi,0x2
   0x7ffff7ca50c7 <__popcountdi2+39>:   and    rax,rdx
   0x7ffff7ca50ca <__popcountdi2+42>:   and    rdi,rdx
   0x7ffff7ca50cd <__popcountdi2+45>:   movabs rdx,0xf0f0f0f0f0f0f0f
   0x7ffff7ca50d7 <__popcountdi2+55>:   add    rdi,rax
   0x7ffff7ca50da <__popcountdi2+58>:   mov    rax,rdi
   0x7ffff7ca50dd <__popcountdi2+61>:   shr    rax,0x4
   0x7ffff7ca50e1 <__popcountdi2+65>:   add    rax,rdi
   0x7ffff7ca50e4 <__popcountdi2+68>:   and    rax,rdx
   0x7ffff7ca50e7 <__popcountdi2+71>:   movabs rdx,0x101010101010101
   0x7ffff7ca50f1 <__popcountdi2+81>:   imul   rax,rdx
   0x7ffff7ca50f5 <__popcountdi2+85>:   shr    rax,0x38
   0x7ffff7ca50f9 <__popcountdi2+89>:   ret

输入应该是edi,输出是rax

到这一步我就。。我太菜了,一时间看不懂。。不过应该还是很好翻译成c语言的。我在网上看到翻译过的代码,但是现在一时找不到了,听说是用了二分法?范增应该听巧妙地。整个函数都没有任何条件跳转语句,就完成了1的计数,厉害。

等我看懂了,就把这一章写完。。。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注