在项目开发过程中,对字符串的处理是经常遇到的问题,稍有不慎,就会留下一些不易察觉的瑕疵;熟练使用字符串函数,理解其实现过程,对编程会大有裨益;
在开始之前,请思考一下,是否真的明白了这样几个函数的用法。
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会做的多智能...
顺祝大家春节快乐!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课