返回   cpper编程论坛 > Blog > tomato
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

为这篇文章评分

CRT源码解析(二)字符串函数(1)

发表于 2006-05-07 03:55 AM 作者: tomato
CRT源码解析(二)

字符串函数(1)

0、代码:
c++ 代码:
代码:
#include <string.h>

int main()
{

    int* src = new int[10];
    int* dest = new int[10];

    memset(src, 0, sizeof(int) * 10);
    *(char*)(&src[5]) = ‘a’;
    memchr(src, ‘a’, sizeof(int) * 10);
    memcpy(dest, src, sizeof(int) * 10);
    memmove(dest, src, sizeof(int) * 10);
    memcmp(src, dest, sizeof(int) * 10);

    return 0;
}
1、memset
标准中声明:void *memset(void *s, int c, size_t n);
crt\src\intel\mamset.asm中的定义:
asm 代码:
代码:
memset proc \
        dst:ptr byte, \
        value:byte, \
        count:dword
调用约定是__cdecl

判断count == 0,直接把dst放在eax中,然后结束
判断value是0,且count>=100h(256),且__sse2_available不为0,则jmp _VEC_memzero(此时有al: value,ecx: dst, edx: count)
判断count若小于4,则循环edx次mov [edi], al直到count为0,把dst放在eax中,然后结束
计算dst到下一个dword boundary的距离,放在ecx中,如果不为0,sub edx, ecx,并且先将这ecx个用mov [edi], al放好
把eax的4个byte全设成al
把edx(调整dword boundary后的count)/4放在ecx中,把edx&3放在edx中
然后rep stosd,把ecx个int的数据放好
如果edx为0,结束,不然循环edx次mov [edi], al

tips:
1) 把eax的4个byte全设成al:
asm 代码:
代码:
; set all 4 bytes of eax to [value]
mov     ecx,eax         ; ecx=0/0/0/value
shl     eax,8           ; eax=0/0/value/0
add     eax,ecx         ; eax=0/0val/val
mov     ecx,eax         ; ecx=0/0/val/val
shl     eax,10h         ; eax=val/val/0/0
add     eax,ecx         ; eax = all 4 bytes = [value]
2) _VEC_memzero
用了4个局部变量和3个参数:
形参[ebp+8],实参为dst
形参[ebp+C],实参为0,无用
形参[ebp+10],实参为count
调用约定是__cdecl
asm 代码:
代码:
MSVCR80D.dll:102335F0 sub_102335F0 proc near                  ; CODE XREF: MSVCR80D.dll:10230897j
MSVCR80D.dll:102335F0                                         ; sub_102335F0+7Dp
MSVCR80D.dll:102335F0
MSVCR80D.dll:102335F0 var_10= dword ptr -10h        ; 
MSVCR80D.dll:102335F0 var_C= dword ptr -0Ch  ; 留着放count的低7位
MSVCR80D.dll:102335F0 var_8= dword ptr -8      ; 留着放修改后的dst
MSVCR80D.dll:102335F0 var_4= dword ptr -4      ; 留着放edi
MSVCR80D.dll:102335F0 arg_0= dword ptr  8      ; dst
MSVCR80D.dll:102335F0 arg_8= dword ptr  10h  ; count
MSVCR80D.dll:102335F0
MSVCR80D.dll:102335F0 push    ebp
MSVCR80D.dll:102335F1 mov     ebp, esp
MSVCR80D.dll:102335F3 sub     esp, 10h
MSVCR80D.dll:102335F6 mov     [ebp+var_4], edi    ; var_4 <- edi,等同于push/pop的保存寄存器方法
MSVCR80D.dll:102335F9 mov     eax, [ebp+arg_0]    ; eax <- dst
MSVCR80D.dll:102335FC cdq            ; 把eax按符号扩展为edx : eax
MSVCR80D.dll:102335FD mov     edi, eax      ; edi <- dst
MSVCR80D.dll:102335FF xor     edi, edx
MSVCR80D.dll:10233601 sub     edi, edx
MSVCR80D.dll:10233603 and     edi, 0Fh
MSVCR80D.dll:10233606 xor     edi, edx
MSVCR80D.dll:10233608 sub     edi, edx      ; edi是dst的低4位保留,
                            ; edi的高28位全为0(若dst为正)或全为1(若dst为负)
MSVCR80D.dll:1023360A test    edi, edi      ; edi == 0 iff dst>0且dst整除16(xmm是16byte)
MSVCR80D.dll:1023360C jnz     short loc_1023364A
MSVCR80D.dll:1023360E mov     ecx, [ebp+arg_8]    ; ecx <- count
MSVCR80D.dll:10233611 mov     edx, ecx
MSVCR80D.dll:10233613 and     edx, 7Fh      ; edx <- count的低7位
MSVCR80D.dll:10233616 mov     [ebp+var_C], edx    ; var_C <- count的低7位
MSVCR80D.dll:10233619 cmp     ecx, edx      ; ecx - edx == 0 iff count < 128
MSVCR80D.dll:1023361B jz      short loc_1023362F
MSVCR80D.dll:1023361D sub     ecx, edx      ; ecx <- count的高25位(低7位为0)
MSVCR80D.dll:1023361F push    ecx         ; 第二个参数:count的高25位(也就是128的倍数)
MSVCR80D.dll:10233620 push    eax         ; 第一个参数:dst
MSVCR80D.dll:10233621 call    near ptr unk_10233680 ; 名字为fastzero_I,IDA竟然默认不会反汇编这一段,ft
MSVCR80D.dll:10233626 add     esp, 8
MSVCR80D.dll:10233629 mov     eax, [ebp+arg_0]    ; eax <- dst
MSVCR80D.dll:1023362C mov     edx, [ebp+var_C]    ; edx <- count的低7位
MSVCR80D.dll:1023362F
MSVCR80D.dll:1023362F loc_1023362F:                           ; CODE XREF: sub_102335F0+2Bj
MSVCR80D.dll:1023362F test    edx, edx
MSVCR80D.dll:10233631 jz      short loc_10233678
MSVCR80D.dll:10233633 add     eax, [ebp+arg_8]
MSVCR80D.dll:10233636 sub     eax, edx      ; eax <- dst + count的高25位(低7位为0)
MSVCR80D.dll:10233638 mov     [ebp+var_8], eax    ; var_8 <- 修改后的dst
MSVCR80D.dll:1023363B xor     eax, eax
MSVCR80D.dll:1023363D mov     edi, [ebp+var_8]
MSVCR80D.dll:10233640 mov     ecx, [ebp+var_C]
MSVCR80D.dll:10233643 rep stosb    ; 将剩余的清0
MSVCR80D.dll:10233645 mov     eax, [ebp+arg_0]    ; eax <- dst
MSVCR80D.dll:10233648 jmp     short loc_10233678
MSVCR80D.dll:1023364A ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

MSVCR80D.dll:1023364A
MSVCR80D.dll:1023364A loc_1023364A:                           ; CODE XREF: sub_102335F0+1Cj
                            ; edi是dst的低4位保留,
                            ; edi的高28位全为0(若dst为正)或全为1(若dst为负)
MSVCR80D.dll:1023364A neg     edi
MSVCR80D.dll:1023364C add     edi, 10h
MSVCR80D.dll:1023364F mov     [ebp+var_10], edi  ; var_10 <- dst到下一个可以整除16byte的距离
MSVCR80D.dll:10233652 xor     eax, eax
MSVCR80D.dll:10233654 mov     edi, [ebp+arg_0]
MSVCR80D.dll:10233657 mov     ecx, [ebp+var_10]
MSVCR80D.dll:1023365A rep stosb    ; 从dst开始清零var_10长度,之后edi可以整除16
MSVCR80D.dll:1023365C mov     eax, [ebp+var_10]
MSVCR80D.dll:1023365F mov     ecx, [ebp+arg_0]  
MSVCR80D.dll:10233662 mov     edx, [ebp+arg_8]  
MSVCR80D.dll:10233665 add     ecx, eax      ; ecx <- 剩余的需要清零的dst地址
MSVCR80D.dll:10233667 sub     edx, eax      ; edx <- 剩余的需要清零的长度
MSVCR80D.dll:10233669 push    edx
MSVCR80D.dll:1023366A push    0
MSVCR80D.dll:1023366C push    ecx
MSVCR80D.dll:1023366D call    sub_102335F0    ; 递归调用_VEC_memzero
MSVCR80D.dll:10233672 add     esp, 0Ch
MSVCR80D.dll:10233675 mov     eax, [ebp+arg_0]    ; eax <- dst
MSVCR80D.dll:10233678
MSVCR80D.dll:10233678 loc_10233678:                           ; CODE XREF: sub_102335F0+41j
MSVCR80D.dll:10233678                                         ; sub_102335F0+58j
MSVCR80D.dll:10233678 mov     edi, [ebp+var_4]
MSVCR80D.dll:1023367B mov     esp, ebp
MSVCR80D.dll:1023367D pop     ebp
MSVCR80D.dll:1023367E retn
MSVCR80D.dll:1023367E sub_102335F0 endp
3) fastzero_I
2个参数:
形参[dst]为[ebp+8],实参是dst
形参[len]为[ebp+C],实参是count的高25位(低7位为0)
1个局部变量:[ebp-4]
调用约定是__cdecl
asm 代码:
代码:
fastzero_I:
10233680  push        ebp
10233681  mov         ebp,esp 
10233683  sub         esp,4 
10233686  mov         dword ptr [ebp-4],edi         ; 保存edi
10233689  mov         edi,dword ptr [dst] 
1023368C  mov         ecx,dword ptr [len] 
1023368F  shr         ecx,7                 ; ecx的低7位都是0
10233692  pxor        xmm0,xmm0 
10233696  jmp         L_1 (102336A0h) 
10233698  lea         esp,[esp] 
1023369F  nop              
L_1:
102336A0  movdqa      xmmword ptr [edi],xmm0       ; mm是64位,xmm是128位合计16bytes
102336A4  movdqa      xmmword ptr [edi+10h],xmm0 
102336A9  movdqa      xmmword ptr [edi+20h],xmm0 
102336AE  movdqa      xmmword ptr [edi+30h],xmm0 
102336B3  movdqa      xmmword ptr [edi+40h],xmm0 
102336B8  movdqa      xmmword ptr [edi+50h],xmm0 
102336BD  movdqa      xmmword ptr [edi+60h],xmm0 
102336C2  movdqa      xmmword ptr [edi+70h],xmm0    ; 把128bytes清零
102336C7  lea         edi,[edi+80h]             ; 就是add edi, 80h
102336CD  dec         ecx  
102336CE  jne         L_1 (102336A0h) 
102336D0  mov         edi,dword ptr [ebp-4] 
102336D3  mov         esp,ebp 
102336D5  pop         ebp  
102336D6  ret
2、memchr
标准的声明:void *memchr(const void *s, int c, size_t n);
crt\src\intel\memchr.asm中的定义:
asm 代码:
代码:
memchr  proc \
        buf:ptr byte, \
        chr:byte, \
        cnt:dword
调用约定是__cdecl

检查cnt是否为0
判断buf是不是4的倍数(aligned on 32bits),如果不是,先找前面几个byte直到4的倍数,
判断剩余需要比较的bytes是否大于4,如果小于4,判断最后的几个bytes
如果大于等于4,类似于memset的方法把ebx用chr填满,跳至main_loop_entry
主循环:
asm 代码:
代码:
main_loop:
        sub     eax,4
        jb      short return_from_main
main_loop_entry:
        mov     ecx,dword ptr [edx]     ; read 4 bytes

        xor     ecx,ebx         ; ebx is byte\byte\byte\byte
        mov     edi,7efefeffh      ; 0111 1110  1111 1110  1111 1110  1111 1111

        add     edi,ecx
        xor     ecx,-1

        xor     ecx,edi
        add     edx,4

        and     ecx,81010100h      ; 1000 0001  0000 0001  0000 0001  0000 0000
        je      short main_loop  ; 这里为什么要用7efefeffh和81010100h?

; found zero byte in the loop?
char_is_found:
        mov     ecx,[edx - 4]
        xor     cl,bl           ; is it byte 0
        je      short byte_0
        xor     ch,bl           ; is it byte 1
        je      short byte_1
        shr     ecx,10h         ; is it byte 2
        xor     cl,bl
        je      short byte_2
        xor     ch,bl           ; is it byte 3
        je      short byte_3
        jmp     short main_loop ; taken if bits 24-30 are clear and bit
                                ; 31 is set
byte_0~byte_3分别是找到后退出



3、memcpy & memmove
标准中声明:
void *memcpy(void * restrict s1,
const void * restrict s2,
size_t n);
void *memmove(void *s1, const void *s2, size_t n);
crt\src\intel\mamcpy.asm中的定义:
asm 代码:
代码:
ifdef MEM_MOVE
        _MEM_     equ <memmove>
else  ; MEM_MOVE
        _MEM_     equ <memcpy>
endif  ; MEM_MOVE

_MEM_   proc \
        dst:ptr byte, \
        src:ptr byte, \
        count:IWORD
调用约定是__cdecl

若dst <= src或dst >= src + count,则CopyUp,否则,CopyDown
1)CopyUp
先判断若count >= 256且__sse2_available != 0且dst - src整除16,则调用_VEC_memcpy
否则做Dword_align:
判断edi是否align到dword了
a. 如果已经完成
ecx记录count / 4,edx记录count % 4
判断若ecx < 8,手写从esi到edi的复制过程(至于吗,难道rep movsd的效率不如手写?或是为了便于cpu乱序执行?)
否则rep movsd,然后调用TrailUpVec复制剩余的bytes
b.如果没有完成
若count小于4直接调用TrailUpVec复制剩余的bytes
否则调用LeadUpVec

UnwindUpVec长度为8,其实就是手写index个dword的复制,然后把剩下的调用TrailUpVec
LeadUpVec长度为3(注意没有LeadUp0),作用是先将dst的align所差的几个bytes(就是4 - dst % 4)复制过去,然后再rep movsd,最后将count中不足4的剩余bytes调用TrailUpVec复制剩余的bytes
TrailUpVec长度为4,作用是拷贝最后的index个字符,然后退出

2) CopyDown,情况完全一样,也是LeadDownVec有3个元素、UnwindDownVec有8个元素、TrailDownVec有4个元素,但注意在rep movsd前后分别加上std/cld修改方向

tips:这个函数是跳转表优化的绝佳例子



3、memcmp
标准中声明:
int memcmp(const void *s1, const void *s2, size_t n);
3个参数:
形参const void *s1为[ebp+8]
形参const void *s2为[ebp+C]
形参size_t n为[ebp+10]
调用约定是__cdecl

好长的程序,从MSVCR80D.dll:10232280~MSVCR80D.dll:10232CEC
实在没法看完……

入口程序
asm 代码:
代码:
MSVCR80D.dll:10232280 ; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?

MSVCR80D.dll:10232280
MSVCR80D.dll:10232280 ; Attributes: bp-based frame
MSVCR80D.dll:10232280
MSVCR80D.dll:10232280 MSVCR80D_memcmp proc near
MSVCR80D.dll:10232280
MSVCR80D.dll:10232280 var_24= dword ptr -24h
MSVCR80D.dll:10232280 var_20= dword ptr -20h
MSVCR80D.dll:10232280 var_1C= dword ptr -1Ch
MSVCR80D.dll:10232280 var_18= dword ptr -18h
MSVCR80D.dll:10232280 var_14= dword ptr -14h
MSVCR80D.dll:10232280 var_10= dword ptr -10h            ; 存放size_t n
MSVCR80D.dll:10232280 var_C= dword ptr -0Ch 
MSVCR80D.dll:10232280 var_8= dword ptr -8         ; 存放const void *s2
MSVCR80D.dll:10232280 var_4= dword ptr -4         ; 存放const void *s1
MSVCR80D.dll:10232280 arg_0= dword ptr  8         ; const void *s1
MSVCR80D.dll:10232280 arg_4= dword ptr  0Ch   ; const void *s2
MSVCR80D.dll:10232280 arg_8= dword ptr  10h   ; size_t n
MSVCR80D.dll:10232280
MSVCR80D.dll:10232280 push    ebp
MSVCR80D.dll:10232281 mov     ebp, esp
MSVCR80D.dll:10232283 sub     esp, 28h
MSVCR80D.dll:10232286 mov     eax, [ebp+arg_4]
MSVCR80D.dll:10232289 mov     [ebp+var_8], eax
MSVCR80D.dll:1023228C mov     ecx, [ebp+arg_0]
MSVCR80D.dll:1023228F mov     [ebp+var_4], ecx
MSVCR80D.dll:10232292 mov     edx, [ebp+arg_8]
MSVCR80D.dll:10232295 mov     [ebp+var_10], edx
MSVCR80D.dll:10232298 cmp     [ebp+var_10], 4
MSVCR80D.dll:1023229C ja      loc_10232409      ; 当n > 4时调用另外一个函数,在10232490
MSVCR80D.dll:102322A2 mov     eax, [ebp+var_10]
MSVCR80D.dll:102322A5 jmp     ds:dword_10232424[eax*4]    ; 一个vector,当0 <= n <= 4时走这里

MSVCR80D.dll:10232421 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

MSVCR80D.dll:10232424 dword_10232424 dd 10232405h             ; DATA XREF: MSVCR80D_memcmp+25r
MSVCR80D.dll:10232428 dd 102323F3h        ; vector的5个值在这里
MSVCR80D.dll:1023242C dd 102323B4h
MSVCR80D.dll:10232430 dd 10232348h
MSVCR80D.dll:10232434 dd 102322ACh
简单评价一下
memset中规中矩,对sse2做了优化,但竟然没有对mmx做优化(sigh,我用的电脑还不支持sse2),memchr里面的一个算法很诡异,memcpy写得很漂亮,memcmp就压根看不懂了……
评论 2 Email文章
评论总数 2

评论

旧
你好!

我阅读了你的c++的一些分析笔记,感觉c++的根是汇编,是这样么?学习c++是不是一定要翻阅汇编这座大山?

另外图书市场的变化很快,我觉得看图书积累不是学习的办法,太被动了,深入学习c++/编程有什么好建议?

谢谢!
发表于 2008-01-22 07:48 PM 作者: 我来了(sxfcity@gmail.com)
旧
不是这样子的,crt与c++没有太多关系,c++也和汇编没有太多关系。对于c++的书来说,我比较推崇Bjarne的c++设计与演化那本书。
发表于 2008-01-22 07:48 PM 作者: tomato tomato 当前离线
发表评论 发表评论
作者为 tomato 的最新文章

所有时间均为格林尼治时间 +9。现在的时间是 12:49 PM


Powered by vBulletin® 版本 3.7.0
版权所有 ©2000 - 2008,Jelsoft Enterprises Ltd.
(C) Copy Right All Right Reserved 2001 - 2007

Search Engine Friendly URLs by vBSEO 3.1.0