Linux下FASM西里尔字母转大写函数处理俄语字母失效问题
解决UTF-8俄语字母转大写的FASM通用方案
你的代码失效的核心问题是直接对UTF-8编码的字节值做比较和加减——俄语字母的UTF-8大小写编码并非统一差0x20(比如ё的大小写编码差就不是0x20),而且原有的范围判断逻辑完全错误,因为UTF-8编码的大小写值并不落在你设定的区间里。
正确的思路是绕开直接操作UTF-8字节,先把双字节UTF-8解码成Unicode码点,完成大写转换后再重新编码回UTF-8,这样就能通用处理所有俄语字母。
原代码的具体问题
- 范围判断逻辑错误:你拿я的UTF-8编码(
0xD18F)和Я的UTF-8编码(0xD0AF)做区间判断,但这两个值本身就不是连续区间的边界,导致大部分俄语小写字母都被错误跳过。 - 直接对UTF-8字节减0x20:只有部分俄语字母的UTF-8大小写编码差是0x20,像ё这种特殊字母完全不适用,无法覆盖所有情况。
通用解决步骤
- 解码UTF-8到Unicode码点:双字节UTF-8的格式是
110xxxxx 10yyyyyy,解码成Unicode码点的公式是:码点 = ((第一个字节 & 0x1F) << 6) | (第二个字节 & 0x3F) - Unicode码点转大写:
- 俄语小写字母的码点范围是
U+0430到U+044F,对应大写码点是U+0410到U+042F,直接减0x20即可。 - 特殊字母ё的码点是
U+0451,对应大写Ё的码点是U+0401,单独处理。
- 俄语小写字母的码点范围是
- 编码回UTF-8双字节:如果码点在
U+0080到U+07FF之间(所有俄语字母都在此区间),编码公式是:第一个字节 = 0xC0 | (码点 >> 6) 第二个字节 = 0x80 | (码点 & 0x3F)
修改后的FASM代码
; 前提:AX中存储双字节UTF-8字符(ah=第一个字节,al=第二个字节) ; 第一步:解码UTF-8到Unicode码点,存入BX mov bx, ah ; 取UTF-8第一个字节 and bx, 0x1F ; 保留低5位(对应格式中的xxxxx) shl bx, 6 ; 左移6位,为第二个字节的低6位腾位置 mov cx, al ; 取UTF-8第二个字节 and cx, 0x3F ; 保留低6位(对应格式中的yyyyyy) or bx, cx ; 合并得到完整的Unicode码点 ; 第二步:判断是否需要转大写 cmp bx, 0x0430 ; 俄语小写字母的起始码点(а) jb skip_convert ; 小于则不是俄语小写,跳过转换 cmp bx, 0x044F ; 俄语小写字母的结束码点(я) ja check_yo ; 大于则检查是否是ё ; 普通俄语小写字母,直接转大写 sub bx, 0x20 jmp encode_back check_yo: cmp bx, 0x0451 ; 检查是否是ё的码点 jne skip_convert mov bx, 0x0401 ; 转成Ё的码点 jmp encode_back skip_convert: ; 不需要转换,直接用原AX的UTF-8值 jmp write_output encode_back: ; 第三步:将Unicode码点重新编码为双字节UTF-8 mov ax, bx shr ax, 6 ; 取码点的高5位 or ah, 0xC0 ; 设置UTF-8第一个字节的前缀110 mov al, bx and al, 0x3F ; 取码点的低6位 or al, 0x80 ; 设置UTF-8第二个字节的前缀10 ; 现在AX中就是转换后的UTF-8双字节(ah=第一个字节,al=第二个字节) write_output: ; 写入结果到RDI指向的内存(x86是小端序,必须按UTF-8顺序存ah和al) mov byte [rdi], ah mov byte [rdi+1], al add rdi, 2 jmp loop_string
关键说明
- 你之前
mov word [rdi], ax失效的原因:x86是小端字节序,这条指令会先把al存入[rdi],再把ah存入[rdi+1],但UTF-8需要按原字节顺序(第一个字节在前)存储,所以必须分开写入ah和al。 - 这个方案通过Unicode码点处理,能覆盖所有俄语字母的大小写转换,不用再逐个字符写判断逻辑。
内容的提问来源于stack exchange,提问作者Alexandr
相关产品推荐
相关产品推荐

