在项目开发过程中,对字符串的处理是经常遇到的问题,稍有不慎,就会留下一些不易察觉的瑕疵;熟练使用字符串函数,理解其实现过程,对编程会大有裨益;

在开始之前,请思考一下,是否真的明白了这样几个函数的用法。
1. sizeof,这不是函数,而是一个运算符,在编译时就已经计算好了参数所占用的内存空间,并分配;
2. strlen,函数,运行时计算参数中的元素个数,不包括'\0'结束符
对应的版本有两个:strlenA窄字符版本,strlenW宽字符版本
3. strcpynA, strcpyA的区别,strcpynA中的 unsigned long len长度表示的是内存长度还是元素个数,从什么地方开始计算
4. 快速求出一个字符串的最后元素,可以用前面方法的组合,也可以自己实现一个函数strendA
5.在前面几个函数的基础上,就可以自己实现strcatA等一些扩展函数

6.如果上面的都十分清楚,那可以分析下内部实现,增强功力;汇编、数据结构、这些都是作为一个程序员所应具备的功底。

一点说明:WINDOWS 提供的大多库函数都是fastcall调用约定,顾名思义,就是为了提高传输参数的效率而使用的一个调用约定;我们知道,传递参数,要么用堆栈,要么用寄存器,而fastcall调用约定就是二者的结合;前两个参数用ecx,edx分别传递,之后的参数用堆栈从右向左依次传递。

现在,感兴趣的可以看下面的代码。代码是我经过严格测试的,注释也是为了方便大家阅读加上去的。

代码:
//
//  ANSI字符串求元素个数
//

__declspec(naked) unsigned long __fastcall strlenA(const char *s)
{
  __asm
  {
    mov edx, edi    //保存edi的值,到后面用 mov edi,edx恢复
    mov edi, ecx    //将参数ecx传递给edi,也就是说edi指向的内存空间存储字符串s
    or ecx, -1      //初始化ecx 为 0FFFFFFFFh
    xor eax, eax    //初始化 eax =0

    repne scasb      //scan byte,字符串搜索,在edi指向的内存空间中搜索与al相等的值,也就是结束符0      
    
    dec eax        // eax-1,这里,eax = -1,和repne scasb执行之前的ecx的值一样
    dec eax        // eax-1,相当于减去了'\0'的长度

    sub eax, ecx    //执行repne scasb的过程中,每循环一次,ecx--,那么sub eax,ecx 就是元素的个数了
              //这里比较巧妙,运算都是在负数范围内,元素的个数也就是eax与ecx相对与0的距离之差
    mov edi, edx    
    retn        //返回值在eax默认返回
  }
}

//
//  WIDE CHAR 字符串求元素个数
//
__declspec(naked) unsigned long __fastcall strlenW(const wchar_t *s)
{
  __asm
  {
    mov edx, edi
    mov edi, ecx
    or ecx, -1
    xor eax, eax
    repne scasw    //scan word,结构和思想与strlenA函数一样,只是这里,我们是每次比较word长度的单位(2字节)
          //每循环一次,ecx--,edi +=2
    dec eax
    dec eax
    sub eax, ecx
    mov edi, edx
    retn
  }
}
代码:
//
//  ANSI字符串的copy函数
//  参数三:unsigned long len:表示要copy的长度,即元素个数  
//

__declspec(naked) char* __fastcall strcpynA(char *dest, const char *src, unsigned long len)
{
  __asm
  {
    push edi
    push esi
    mov edi, ecx      //fast call调用约定,第一个参数用ecx传递,第二个参数用edx传递
    mov eax, ecx      //现在,edi 保存 dest 指向的内存地址

    mov esi, edx      //esi 保存 src 指向的内存地址

    mov edx, [esp + 12]                   //传递将要copy的长度len,第三个参数用堆栈传递,[esp+8] 就是返回地址    
    mov ecx, edx      //字符串的长度转存到ecx中

    shr ecx, 2        //长度缩小4倍,其实就是循环次数缩小4倍,每次传递4个字节

    repnz movsd      //mov dword

    mov ecx, edx      //余数部分,每次只传递1个字节
    and ecx, 3      //取余数部分,mod 4 的余数

    repnz movsb
    
    mov byte ptr [edi], 0                  //字符串结尾处,填充0
    
    pop esi        //按照进栈的顺序,弹栈,使堆栈平衡
    pop edi
    
    retn 4
  }
}

//
//  WIDE CHAR字符串的copy函数
//  参数三:unsigned long len:表示要copy的长度,即元素个数
//

__declspec(naked) wchar_t* __fastcall strcpynW(wchar_t *dest, const wchar_t *src, unsigned long len)
{
  __asm
  {
    push edi
    push esi
    mov edi, ecx
    mov eax, ecx      //让eax指向目的字符串的地址[ecx]
    mov esi, edx
    mov edx, [esp + 12]                   //保存要传递的元素个数

    mov ecx, edx
    shr ecx, 1        //循环次数缩小2倍,每次传输两个word长度,也就是4字节

    repnz movsd        //每次传递4个字节
    mov ecx, edx

    and ecx, 1        //取余数部分,mod 2的余数

    repnz movsw        //每次循环传递一个word长度,也是最小单元传输长度                
    
    mov word ptr [edi], 0  //字符串结尾,填充0
    
    pop esi
    pop edi
    
    retn 4          //返回的字符串保存于eax
  }
}
代码:
//
// 求ANSI字符串的最后一个元素
//

__declspec(naked) char* __fastcall strendA(const char *s)
{
  __asm
  {
    mov edx, edi
    mov edi, ecx
    or ecx, -1
    xor eax, eax
    repnz scasb
    lea eax, [edi - 1]  //此时,edi已经指向字符串的\0处,[edi-1]指向字符串的最后一个元素
    mov edi, edx
    retn
  }
}

//
//  求WIDE CHAR 字符串的最后一个元素
//
__declspec(naked) wchar_t* __fastcall strendW(const wchar_t *s)
{
  __asm
  {
    mov edx, edi
    mov edi, ecx
    or ecx, -1
    xor eax, eax
    repnz scasw
    lea eax, [edi - 2]
    mov edi, edx
    retn
  }
}

char* __fastcall strcatA(char *dest, const char *src)
{
  strcpyA(strendA(dest), src);
  return dest;
}

wchar_t* __fastcall strcatW(wchar_t *dest, const wchar_t *src)
{
  strcpyW(strendW(dest), src);
  return dest;
}

希望大家感兴趣。
别说我古董,我大学还没毕业,不过也快了。但是自己深知道,编程这东西,理解的越透彻,越深入,才会游刃有余;
现在大家知道,假如求一个字符串的长度,时间复杂度是多少,不用说,都是O(N)啊,不要因为我们平时见不到这些内部实现,就认为CPU会做的多智能...
顺祝大家春节快乐!