我在eax
中存储了一个UTF-8字符,稍后在处理中,我需要知道该字符组成了多少字节。
我已经缩小了这一点,最大限度地减少了班次和面具,并想知道我在某个地方错过了一些巧妙的伎俩?
选项1:暴力
mov r11, 4 ; Maximum bytes
bt eax, 31 ; Test 4th MSB
jc .exit
dec r11 ; Lets try 3
bt eax, 23 ; Test 3rd MSB
jc .exit
dec r11 ; Lets try 2
bt eax, 15 ; Test 2nd MSB
jc .exit
dec r11 ; It's straight up ascii (1 byte)
.exit:
注意:
eax
注册表中的累积错误,正如大家所指出的那样。答案 0 :(得分:3)
如果您可以假设correct encoding of the character,您只需检查第一个代码单元中最高零的位置(由于UTF-8的自动同步属性)。
罪魁祸首是,对于一个代码单元的代码点,最高零为第7位。对于 n 代码单元的代码点,最高位为7 - n (注意“不连续性”)。
假设第一个代码单元位于al
。
not al ;Trasform highest 0 in highest 1
bsr al, al ;Find the index (from bit0) of the first 1 from the left
xor al, 7 ;Perform 7 - index
;This gives 0 for single code unit code points
mov ah, 1
cmovz al, ah ;Change back to 1
请注意,{0}没有为输入定义bsr
,但只有无效的前导代码单元(值11111111b)才会出现{。}}。
您可以在jz <error handler>
指令后使用bsr
检测无效的0xff代码单元。
感谢@CodyGray指出原始版本的错误 感谢@PeterCorders指出要做7-AL的XOR技巧。
答案 1 :(得分:1)
如果你坚持颠倒的字节顺序(无论出于什么奇怪的原因),你仍然可以简单地扫描第一位设置为1,除以8和+1以获得字节数。
GetReversedShiftedUtf8BytesCount:
; eax = UTF8 code in reversed order, by from LSB
; 'É' (c3 89) => eax = 0x0000c389
bsr ecx,eax
cmovz ecx,eax ; needed only for eax = 0
; ^ if eax is never 0 on input, this "cmovz" can be removed
shr ecx,3
inc ecx
ret
当你将char的第一个字节放入MSB时,它将为多字节字符产生15,23或31位数,对于7b ASCII,bsr
将产生0到6之间的任何字符。 “div 8”将直接修复它们,无论哪种方式,它都不关心。
此例程实际上也适用于有效普通UTF8代码。
对于以<0>字节结尾的无效 UTF8代码,它将返回错误的字节数(没有零字节)。
当然总是也可以使用LUT解决方案:
movzx ecx,al
shr ecx,3
movzx ecx,byte [utf8lengthLUT + ecx] ; +rcx for 64b
; ecx = number of bytes or 0 for invalid leading byte value
...
utf8lengthLUT: ; 32B look-up table for upper 5b of 1st byte
db 1, 1, 1, 1, 1, 1, 1, 1 ; 00000 - 00111 ; single byte
db 1, 1, 1, 1, 1, 1, 1, 1 ; 01000 - 01111 ; single byte
db 0, 0, 0, 0, 0, 0, 0, 0 ; 10000 - 10111 ; not valid leading byte
db 2, 2, 2, 2 ; 11000 - 11011 ; two bytes code point
db 3, 3 ; 11100 - 11101 ; three bytes code point
db 4 ; 11110 ; four bytes code point
db 0 ; 11111 ; not valid leading byte
我没有调试它,只是尝试使用nasm进行语法检查。我当然也没有描述它。 :)看看那个bsr
变种的短缺,我怀疑即使在bsr
伤害的CPU上,这也会非常快。
但是这个以不同的方式处理无效的UTF8操作码,而不是检测非零MSB并返回它的数量+ 1(对前导字节内容不敏感),它将正确解码前导字节信息并在前导位为0时返回0错误。但正确的前导位(错误的第二个+字节(如c3 00
))仍将返回2
,而第一个变量在这种情况下返回1
。
(如果您不关心无效11111
前导字节信息,则可以只使用16B LUT表,并将其视为4字节代码点)
(加上考虑了多少(修复)编辑收到这两个答案...... :))
还有一个offtopic建议:如果你试图用PHP写一些东西,应该处理UTF8输入数据(不是来自可靠来源,甚至来自可靠来源),特别是如果那些输入数据来自GET / POST响应......不要靠自己。决不。获得一个框架。 :)