$ o) C/ m8 z; ]% {<TR> , f. q: K' s% ~% E. A# _, p P9 |9 h<TD align=middle width="25%">42 00 </TD>+ j: j( D8 {4 m/ h n% Q: \
<TD align=middle width="25%"><FONT color=#990000>6F</FONT> 00</TD> * P5 d1 L+ {! {1 U! m<TD align=middle width="25%">62 00</TD> 0 z4 {9 |$ Q, t/ p+ C/ D7 _<TD align=middle width="25%">00 00</TD></TR> ' _- ?4 v7 Q, x7 q( [: }8 H ^<TR> 9 s# }( ?( \$ k4 ~<TD align=middle width="25%"><FONT color=#990000>B</FONT></TD> " _: O1 n1 c3 E& P" s<TD align=middle width="25%"><FONT color=#990000>o</FONT></TD> - s9 G# T! B) |2 r) z0 i T: v<TD align=middle width="25%"><FONT color=#990000>b</FONT></TD> $ R& z9 |6 _ l<TD align=middle width="25%"><FONT color=#990000>BOS</FONT></TD></TR></TABLE> |* x f4 H u; M" Y Q<> 因为x86CPU是little-endian,值0x0042在内存中的存储形式是42 00。你能看出如果这个字符串被传给strlen()函数会出现什么问题吗?它将先看到第一个字节42,然后是00,而00是字符串结束的标志,于是strlen()将会返回1。如果把"Bob"传给wcslen(),将会得出更坏的结果。wcslen()将会先看到0x6f42,然后是0x0062,然后一直读到你的缓冲区的末尾,直到发现00 00结束标志或者引起了GPF。 , T0 c) w" W7 U/ F$ B7 R: q8 m 到目前为止,我们已经讨论了str***()和wcs***()的用法及它们之间的区别。Str***()和_mbs**()之间的有区别区别呢?明白他们之间的区别,对于采用正确的方法来遍历DBCS字符串是很重要的。下面,我们将先介绍字符串的遍历,然后回到str***()与_mbs***()之间的区别这个问题上来。 & b# V$ T( r" X0 Q) k( z) h8 w; {; c; ^6 e, V
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"> <B>正确的遍历和索引字符串</B>& w- e6 q: g- ]4 C8 P
* r- F; E9 g% X4 s: n6 Y
因为我们中大多数人都是用着SBCS字符串成长的,所以我们在遍历字符串时,常常使用指针的++-和-操作。我们也使用数组下标的表示形式来操作字符串中的字符。这两种方式是用于SBCS和Unicode字符串,因为它们中的字符有着相同的宽度,编译器能正确的返回我们需要的字符。 ' ]) g; V: B9 U8 C4 V 然而,当碰到DBCS字符串时,我们必须抛弃这些习惯。这里有使用指针遍历DBCS字符串时的两条规则。违背了这两条规则,你的程序就会存在DBCS有关的bugs。</P>. n6 x6 R5 t& j: f/ F4 Y$ j6 D/ E
<DIR>8 ]& l3 ?- ]& F& a
<LI>1.在前向遍历时,不要使用++操作,除非你每次都检查lead byte; . A1 g8 x! x& [/ P* b6 I4 z" K1 \6 m
<LI>2.永远不要使用-操作进行后向遍历。 </LI></DIR> @3 s' Q' p' ~3 {& h<> 我们先来阐述规则2,因为找到一个违背它的真实的实例代码是很容易的。假设你有一个程序在你自己的目录里保存了一个设置文件,你把安装目录保存在注册表中。在运行时,你从注册表中读取安装目录,然后合成配置文件名,接着读取该文件。假设,你的安装目录是C:\Program Files\MyCoolApp,那么你合成的文件名应该是C:\Program Files\MyCoolApp\config.bin。当你进行测试时,你发现程序运行正常。 * Q0 t' A6 L9 I: g! b 现在,想象你合成文件名的代码可能是这样的:</P><RE>bool GetConfigFileName ( char* pszName, size_t nBuffSize )1 `6 x5 E$ R) _4 Y" L
{* e% o: o8 [9 ~' M/ y
char szConfigFilename[MAX_PATH]; $ x. B5 i5 U2 _, L 8 C1 a" E i; @' k // Read install dir from registry... we''ll assume it succeeds. # z2 j$ Q0 m' L 4 L3 a6 f! w: G // Add on a backslash if it wasn''t present in the registry value.2 I J4 e$ e6 V% W: i
// First, get a pointer to the terminating zero." z- f+ C% [7 G7 ~; e
char* pLastChar = strchr ( szConfigFilename, ''\0'' );' L* \4 I+ L* `; Y
( i b# i5 s# b! ^
// Now move it back one character.( Z! K, L7 u" h: r# J3 u
pLastChar--; 9 ^0 _) u* R2 ~: S1 z
( |; k' E, D4 ]# V if ( *pLastChar != ''\\'' ) % a0 f% a. C# B5 F strcat ( szConfigFilename, "\\" );3 f0 x$ d/ @6 V( u! m
3 }1 B3 j0 q! S& L; h // Add on the name of the config file. 4 @- a8 R8 L4 v/ U6 T strcat ( szConfigFilename, "config.bin" ); 1 X* q" `+ L' y 0 g/ j9 r# a& t // If the caller''s buffer is big enough, return the filename., ]" B8 _. M2 w2 L' R* c9 D
if ( strlen ( szConfigFilename ) >= nBuffSize )1 }) e9 R$ A( b" l4 B
return false;4 P1 B8 a2 h, _0 p; i' u# n: U; G
else % j& u( X4 G- L8 E/ H+ y4 U1 ? { ; E: ^2 z/ {. J+ U/ V, N strcpy ( pszName, szConfigFilename ); * d- c1 E# K: i e/ p return true;- ]" P' s, T4 a. s7 G3 a
}. x8 l) {+ z4 @" A3 V0 n
} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式:* n4 P' ~+ ?4 ]
# S8 y V& l1 {& S) N1 a k: u5 a; Z<TABLE> # C% |" {% ^3 {. T. T* A# D" |; x0 i9 @9 ]: a3 R
<TR>/ S& S) k* g; y3 z/ E) R
<TD align=middle width="12%">43</TD>' @) x% Z. b \' X3 z/ J" v
<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD>& Z, z+ v" S2 J0 [) R. ]2 Z7 I
<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD> 1 Z. T' p+ N* x: u<TD align=middle width="12%">83 88</TD>, p) c4 l7 [/ S- _+ O+ U7 s; t
<TD align=middle width="13%">83 45</TD> ; M0 Q! J6 l. s3 N$ P( f<TD align=middle width="13%">83 52</TD>' |% }& M) [& r( X* l6 P u
<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD>: Z0 b- Y# C5 o' H3 C2 n6 j
<TD align=middle width="13%">00</TD></TR> 9 m* G4 a* k# d- i; b<TR>0 C; k6 q# D4 c& G+ a
<TD align=middle width="12%"> </TD> ; p( O" @. K2 j' W% [0 N* H0 k/ b2 M<TD align=middle width="12%"> </TD> 3 N9 N, s( K- D* y9 u<TD align=middle width="12%"> </TD>4 X) X8 F5 h( B6 v# u
<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD> / f( w& J; P" R<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>5 s# ^# \7 F( s* {+ ^! a, R( ~$ ^5 j
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>3 M( V4 r O$ @+ U/ O- z" h
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> , c L& Y! O! J7 c" u7 Y$ J/ f<TD align=middle width="13%"> </TD></TR> 6 Y" A9 R1 l* @. L2 ]1 m" }<TR> D, r/ m& A: @
<TD align=middle width="12%">C</TD> # u" E0 @# l" ^2 }, q. _<TD align=middle width="12%">:</TD> 1 ]' G7 A5 l1 w, ^, m. c. s<TD align=middle width="12%">\</TD> 9 S o4 J. v' D% F% J0 @2 ?1 E4 S<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD>6 k/ Z* W! H. Y9 w6 S
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD>& O! S6 S6 v& N* [4 l, C
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD>5 y- a7 y7 P0 s
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD>% M# O4 Q% P/ p3 q" I' f2 g: v
<TD align=middle width="13%">EOS</TD></TR></TABLE> " ^7 ]' G& y, H3 `! P" m<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。 & v5 z w: T' i+ h. s; Y4 @8 l$ U 哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。 . Q0 Q- b5 r2 u: e1 j 正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize ) 0 p/ _' W/ C8 ?, \8 j! B{ ( m; V# n# ]9 t9 t7 c& L2 o1 u7 c8 I char szConfigFilename[MAX_PATH]; 4 z6 w% ^5 T3 V: }7 W1 L# r ' c9 U; P' U: X/ h( S: ?1 u7 u
// Read install dir from registry... we''ll assume it succeeds., G* |; r# _ H# v7 w
( X0 {/ A% W% q5 M; o5 X // Add on a backslash if it wasn''t present in the registry value. - a* s, g/ |3 e" S( ]9 v9 j // First, get a pointer to the terminating zero. 9 o( T+ f) K+ s char* pLastChar = _mbschr ( szConfigFilename, ''\0'' ); - j7 g8 K3 ]+ B % S% k; B" } g1 M/ r. { // Now move it back one double-byte character.& `9 D5 y9 X! h5 q- x
<FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT>5 @0 [1 t$ ^4 J- G$ L( s
; k; d& t1 V: w8 K1 M. n0 H if ( *pLastChar != ''\\'' )( W/ e8 B! X1 G3 N; j+ n
_mbscat ( szConfigFilename, "\\" ); : \. l8 l2 A. n! K8 U% @ # {' g! o9 i" P$ }2 b' A // Add on the name of the config file. 4 }! E$ i8 l9 m& N+ Y3 h: G _mbscat ( szConfigFilename, "config.bin" ); / U) P6 L9 ]4 [3 `* \" ^2 }: D 8 U E4 u0 |" w8 k! Y9 ?# z% \ // If the caller''s buffer is big enough, return the filename. 9 ^0 e+ k/ P5 l+ W W* ?7 k if ( _mbslen ( szInstallDir ) >= nBuffSize ) ; C" B( |( j$ o: W9 B, p return false; : x% r6 @6 @* S3 i& ^& X2 x1 k else + y1 z: o0 m! K' h& Y3 R, `+ ~ {) ]. H9 u% i) b. w9 N: ?' R
_mbscpy ( pszName, szConfigFilename );0 `+ x- d/ F. a: \4 `9 ~
return true; / U8 z. C# s0 o; X1 Y } & u; j0 R! i" F6 i}, K8 c' C3 B# [& P
</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。; l$ ] a( k' m# G A
让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。 + {8 O& x; T( B" r( M/ S* S与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE>- n# v. c% p6 q& [$ j
<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE>* H2 X/ \. s. Y' p, W
<>这和向后移动一个指针是同样的效果。 . F; d3 c% y4 |. y2 D8 | % C5 K) c/ U7 Y4 d) {<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B>! G; X- z- c7 v# @& y" x! P: P$ `
% s# k! f" Z* A
现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。 " f# J+ c1 t9 b) l: K$ S: _ 关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。6 G# ?% ?. E. n
! f. ~) m# ^/ k4 ]) z- x* m
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B> G9 P$ X1 [6 G