内核-strcpy
看libc里面实现的strcpy
说明一下,这个libc代码的来源貌似比较复杂,有来自BSD的,有来自GNU,还有一些其他的
#define UNALIGNED(x,y) (((unsigned long)x & (sizeof (unsigned long)-1)) ^ ((unsigned long)y & (sizeof (unsigned long)-1)))
#define STRALIGN(x) (((unsigned long)x&3)?4-((unsigned long)x&3):0)
# define MKW(x) (x|x<<8|x<<16|x<<24)
# define GFC(x) ((x)&0xff)
# define INCSTR(x) do { x >>= 8; } while (0);
char *
strcpy (char *s1, const char *s2)
{
char *res = s1;
#ifdef WANT_SMALL_STRING_ROUTINES
while ((*s1++ = *s2++));
return (res);
#else
int tmp;
unsigned long l;
if (UNALIGNED(s1, s2)) {
while ((*s1++ = *s2++));
return (res);
}
if ((tmp = STRALIGN(s1))) {
while (tmp-- && (*s1++ = *s2++));
if (tmp != -1) return (res);
}
while (1) {
l = *(const unsigned long *) s2;
if (((l - MKW(0x1ul)) & ~l) & MKW(0x80ul)) {
while ((*s1++ = GFC(l))) INCSTR(l);
return (res);
}
*(unsigned long *) s1 = l;
s2 += sizeof(unsigned long);
s1 += sizeof(unsigned long);
}
#endif
}
那些需要的宏我也给拎出来了,好看一些.
先来看这个简单的实现:WANT_SMALL_STRING_ROUTINES
while ((*s1++ = *s2++));
return (res);
就两行代码,也确实够简单的…这个不说.
再看后面的实现,前面两个宏的判断我也不想看,貌似是什么是否是四字节对齐的判断,猜的…
真正有意思的在这里while(1)这里,这里才是问题的关键.
DEBUG几次大约就能知道if语句里面判断字符串是否包含’\0’.
如果包含’\0’,那么就用while把剩余的字符串拷贝过去;
如果不包含’\0’,那么就用long拷贝,因为一次可以拷贝四个字节.
if语句外面的,基本上大家都知道意思了,问题就在里面的,里面猜是能猜到拷贝剩余的(不足四个字节的)字符串.
先来看:
while ((*s1++ = GFC(l))) INCSTR(l);
return (res);
这两句代码吧.
while( *si++ = GetFirstChar(l) ) //获取l的第一个字符
INC_STR(l); //往后偏移一个字节
那么他是怎么判断这个long里面是否有’\0’的呢?问题的关键就在
((l - MKW(0x1ul)) & ~l) & MKW(0x80ul)
这句代码上!
这句代码看这个很费解,我看了很长时间才看懂了.OK,提个问题,你怎么判断一个字节是否是0呢?
你也许会用==0,可是这样的代码只能对一个字节有效,这个libc用了一种比较复杂的办法:
byte i;
((i - 0x1) & ~i) & 0x80
他是这么判断的.
那句代码可以这么写(i-1) & (0xFF-i) & 0x80,我们画一个表就明白是怎么回事了.
i 0 1 2 … 127 128 129 … 253 254 255
i-1 255 0 1 … 126 127 128 … 252 253 254
255-i 255 254 253 … 128 127 126 … 2 1 0
& 255 <=0 <=1 … <=126 <=127 <=126 … <=2 <=1 <=0
有一个命题,N >= M,那么,(N & M) <= M 必然成立.(谁帮忙证明一下_)
那么(i-1) & (255-i)里面最大的数,也就指望中间这就几个数了,很可惜
127 & 127 = 127 < 128 = 0x80
所以他就用这个算法,去衡量一个byte是否是’\0’,也就是0.
是0的话,会返回255;否则返回0.
一个long里面有四个byte,只要有一个byte出现0,也就是出现字符串的终结符,都会是那个表达式变成一个非0的数字,从而他的目的达到了.
OK,再来回顾一下他是怎么做的呢?
- 读取四个字节,构成一个long
- 判断这个long里面有没有包含C String的终结符’\0’
- 包含的话,按byte拷贝
- 不包含的话,按long拷贝
- 回到1