我們將代碼稍作修改,讓一些宏定義變成函數更容易理解一些:
#include "stdafx.h"
#include <stdio.h>
/* fast strcpy -- Copyright (C) 2003 Thomas M. Ogrisegg <
[email protected]> */
//#include <string.h>
//#include "dietfeatures.h"
//#include "dietstring.h"
// ----following are dietstring.h content.
//#include <endian.h>
//# define MKW(x) (x|x<<8|x<<16|x<<24)
int MKW(int x)
{
x = x | x<<8 | x << 16 | x << 24;
return x;
}
//# define STRALIGN(x) (((unsigned long)x&3)?4-((unsigned long)x&3):0)
unsigned long STRALIGN(unsigned long xPtr)
{
unsigned long xRet = (unsigned long)xPtr & 3;
if (xRet)
xRet = 4 - ((unsigned long) xPtr & 3);
else
xRet = 0;
return xRet;
}
/* GFC(x) - returns first character */
/* INCSTR(x) - moves to next character */
# define GFC(x) ((x)&0xff)
# define INCSTR(x) do { x >>= 8; } while (0)
//#define UNALIGNED(x,y) (((unsigned long)x & (sizeof (unsigned long)-1)) ^ ((unsigned long)y & (sizeof (unsigned long)-1)))
unsigned long MyUnaligned(unsigned long xPtr, unsigned long yPtr)
{
unsigned long valN1 = sizeof (unsigned long)-1;
unsigned long xVal = (unsigned long) xPtr & valN1;
unsigned long yVal = (unsigned long) yPtr & valN1;
unsigned long retVal = xVal ^ yVal;
return retVal;
}
// ----above are dietstring.h content.
char *
strcpy2 (char *s1, const char *s2)
{
char *res = s1;
int tmp;
unsigned long l;
if (MyUnaligned((unsigned long)s1, (unsigned long)s2)){
while ((*s1++ = *s2++));
return (res);
}
if ((tmp = STRALIGN((unsigned long)s1))){
while (tmp-- && (*s1++ = *s2++));
if (tmp != -1) return (res);
}
while (1) {
unsigned long key1 = MKW(0x1ul);
unsigned long key2 = MKW(0x80ul);
l = *(const unsigned long *) s2;
if (((l - key1) & ~l) & key2) {
while ((*s1++ = GFC(l))) INCSTR(l);
return (res);
}
*(unsigned long *) s1 = l;
s2 += sizeof(unsigned long);
s1 += sizeof(unsigned long);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
char* p = (char*)malloc(50* sizeof p);
char* str = "aaaabbbbbcccccc";
strcpy2(p, str);
free(p);
return 0;
}
為了不和標准庫的strcpy名字沖突,我將其改為strcpy2.
如果你把上面的程序編譯運行一下就會發現,快的原因在於strcpy2這個函數最後一部分while循環裡面的這幾行:
*(unsigned long *) s1 = l;
s2 += sizeof(unsigned long);
s1 += sizeof(unsigned long);
對C語言指針了解的朋友都知道,第一行是把l這個unsigned long類型變量值賦值給s1為地址的一個unsigned long型指針指向的內容。
在我的i386cpu PC機上,第二第三行分別是將s2以及s1指針增加了4(而不是通常函數實現裡面的++)。這也就實現了每次拷貝4個char(也就是一個unsigned long)而不是只拷貝一個char。
而strcpy2前面的函數就是確保這個拷貝可以正確執行。
我們先看MyUnaligned這個函數(在dietlibc中原為UNALIGNED宏)。
先取了一個值是sizeof(unsigned long) – 1,然後將源字符串指針以及目標字符串指針都與這個值做與操作(xPtr & valN1),最後兩個結果做一個異或xor操作(xVal ^ yVal)。
其實說白了很簡單,xPtr & valN1相當於一個取模操作,i386 cpu上valN1的值為3,也就是與的結果可能為0,1,2,3,當xPtr或者yPtr的值為4的倍數時候,與操作得到結果為0。兩個與操作結果做一下異或,只有都為0或者都為1的時候,返回為0。也就是只要有一個指針沒對齊,就老老實實的做一個個char的拷貝(*s1++ = *s2++),然後從strcpy2返回。
這個算法就是為了保證xPtr以及yPtr指針都是在內存上是對齊的(aligned),如果沒有對齊還要一次賦值4個char,那可能導致寫入內存出錯(參考這篇http://en.wikipedia.org/wiki/Data_structure_alignment)。
有的同學已經看出來了,如果源指針目標指針都沒對齊,xor結果也是零,那不就錯了麼?
OK,不還有一段代碼麼,在STRALIGN裡面,會對目標字符串指針地址取模,然後將余數返回,比如我們運行時人為地修改s1以及s2地址將其+1。debug運行如下圖,得到p以及str地址,可以看到都是對齊在unsigned long邊界上的( p & 3 一定是0)。
我們在Autos窗口裡直接修改地址,讓其加一,如下圖:
這樣兩個指針就都沒有對齊了。繼續運行: