Skip to content

Instantly share code, notes, and snippets.

@willard1218
Last active June 21, 2019 13:05
Show Gist options
  • Save willard1218/04cc7824143186efc5722d1ea6e71599 to your computer and use it in GitHub Desktop.
Save willard1218/04cc7824143186efc5722d1ea6e71599 to your computer and use it in GitHub Desktop.
xxxxxxxx

字串的顯示問題 (編碼問題)

問題

遇到一個字串顯示不正常的問題,語言設定中文時不會有問題,設定成日文就會。 找到最後發現程式某個地方用到反轉字串,使用了 _tcsrev 來完成。

由於專案比較舊,編碼集是用 MBCS 而不是 Unicode, 當設定成 MBCS 時,_tcsrev 被當作 _mbsrev, 如果文字內容是 "a一b" (BIG5 編碼是 0x61 0xa4 0x40 0x62), 經過 _mbsrev 轉換後,在不同的語言設定下,會有不同的結果:

// 轉換的程式
CString str = "a一b"; 
// str = "a一b" (0x61 0xa4 0x40 0x62)

LPTSTR p = str.GetBuffer(0);
_mbsrev((unsigned char *)p);
電腦語言設定中文 電腦語言設定日文
使用 _mbsrev 0x62 0xa4 0x40 0x61 0x62 0x40 0xa4 0x61

很好奇為什麼會有這樣差別,去看了一下 _mbsrev 文件, 發現文件有另外一個 function _mbsrev_l

unsigned char *_mbsrev(
   unsigned char *str
);
unsigned char *_mbsrev_l(
   unsigned char *str,
   _locale_t locale
);

看起來是因為 _mbsrev 的實作中預設會使用電腦系統的語言,而 _mbsrev_l 來讓使用者指定語言, 測試如下:

電腦語言設定中文 電腦語言設定日文
使用 _mbsrev 0x62 0xa4 0x40 0x61 0x62 0x40 0xa4 0x61
使用 _mbsrev_l ,locale 設為中文(zh-TW) 0x62 0xa4 0x40 0x61 0x62 0xa4 0x40 0x61
使用 _mbsrev_l ,locale 設為日文(ja-JP) 0x62 0x40 0xa4 0x61 0x62 0x40 0xa4 0x61

果然,如果用 _mbsrev 就直接看電腦語言,發現 0xa4 0x40 是中文字 的 Big5 編碼, 反轉的時候不會反轉到他;而 _ismbblead_l 就是讓使用者指定語言的。

所以如果有一個 Big5 編碼的文字檔要在日文電腦處理,專案又設定成 MBCS 時,要用反轉字串的功能,需要用 _mbsrev_l 來指定語言。

_mbsrev 裡面是怎麼根據 locale 的不同而知道那些字是兩個 byte,而不反轉呢?

找到 source code 發現是這樣處理的:

unsigned char * _RTLENTRY _EXPFUNC _mbsrev(unsigned char *s)
{
    unsigned char *p, *q;
    unsigned char t;

    /* first, reverse 1st and 2nd byte couple of double byte code */
    for (p = s; *p; p++)
    {
        if (_ismbblead(*p))
        {
            p++;
            if (*p == '\0')
                break;
            t = *p;
            *p = p[-1];
            p[-1] = t;
        }
    }
    /* second, reverse byte stream */
    for (q = s, p--; q < p; q++, p--)
    {
        t = *q;
        *q = *p;
        *p = t;
    }
    return s;
}

可以看到如果 _ismbblead 回傳 true 的話, 就代表接下來兩個 byte 先反轉一次,最後在反轉回來,就等於沒反轉。 那 _ismbblead 又是什麼呢?

函式定義如下:

int _ismbblead(
   unsigned int c
);
int _ismbblead_l(
   unsigned int c,
   _locale_t locale
);

文件指出 如果整數c是多位元組字元的第一個位元組,傳回非零值, 中文、日文、韓文都是屬於 DBCS (double-byte character set), 也就是說如果要檢查的這個 byte,剛好是該編碼的 Lead byte (first byte),就會回傳 true

一樣會有兩個,其中 _ismbblead 是直接看電腦設定的語言; _ismbblead_l 是可以讓使用者指定語言,測試如下:

其中 Lead byte 的定義在維基百科[4][5]可以找到,整理如下:

系統語言 locale 編碼方式 Lead byte
中文 zh-TW BIG5 0x81-0xfe 0xa1-0xf9
日文 ja-JP Shift-JIS 0x81-0x9F0xE0-0xEF0xF0-0xFC

而我用 _ismbblead_l 測試結果也如上,只要參數是給該語系的 Lead byte,就會回傳 true

結論

  1. 專案的編碼方式盡量能用 Unicode
  2. 如果被迫只能用 MBCS,又要支援多國語言的話,使用到 locale 相關的函式需要注意。
  3. 好用的 Debug 工具 - hexdump,可以直接用 十六進位表示法 來檢視文字檔內容。

資源

  1. 語系列表
  2. BIG5 內碼表
  3. Shift-JIS 內碼表
  4. Big5 介紹
  5. Shift-JIS 介紹

.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment