How to Do It Right? Wide and Multi-Byte String Conversion
Posted on September 14th, 2008 at 17:17 by fr3@K

一方面是受到 jeffhung 與 augustinus 在 我家clsung 那邊留下的迴響所啟發, 令一方面也是為了讓自己更清楚了解正確 wide string and multi-byte string 間轉換的 issue. 我寫了兩個 Wide string and Multi-Bytes string conversion functions 以及方便性的 wrapper (source code hosted by Google Code).

希望能做為一個範例提供給想進一步了解這題目的朋友 (this includes me) 把玩. 也希望能請對這題目有研究的朋友來 code review 以及留下意見, 幫助讓我完善這對 conversion functions 一起造福有這需求的使用者.

Disclaimer: 小弟對實作所使用的 API (wcrtombmbrtowc) 的行為不是非常肯定, 不敢宣稱其功能上的正確. 歡迎自行取用 (MIT-licensed), 但不提供任何保證.

del.icio.us:How to Do It Right? Wide and Multi-Byte String Conversion digg:How to Do It Right? Wide and Multi-Byte String Conversion spurl:How to Do It Right? Wide and Multi-Byte String Conversion newsvine:How to Do It Right? Wide and Multi-Byte String Conversion furl:How to Do It Right? Wide and Multi-Byte String Conversion Y!:How to Do It Right? Wide and Multi-Byte String Conversion 黑米共享書籤:How to Do It Right? Wide and Multi-Byte String Conversion 推推王:How to Do It Right? Wide and Multi-Byte String Conversion
Previous Post
« re: string to/from wstring conversion «
Next Post
» Security Enhanced CRT, Safer Than Standard Library? »

7 Comments »

Comment #6172

1. 我覺得int w2mb(wstr, )有問題,因為如果wstr中如果含有BMP外的字(如Extension B或Extension C的字),++cur就有問題。
2. 雖然Unicode在處理BMP外的字會面臨與MultiByte的字類似的難題,但是他們有一個差異,Unicode的編碼不會有MultiByte中LeadByte與SecondByte的問題不分的問題。在某些特定的情況下,MultiByte字串會有反向Parse字串的處理,可是我們卻有可能永遠分不出到底目前這個Byte究竟是LeadByte還是SecondByte。舉例來說,如果一篇MultiByte文章,所有的Byte都落在LeadByte範圍內,任取一個Byte我們都要反向Parse到文章的起頭,才能確定目前這一個Byte是LeadByte還是SecondByte。更何況有些function的傳入參數根本不包含文章的起頭。

Comment by buil — May 10, 2009 @ 10:56


Comment #6237

Hi buil,

回頭看了那段 code.

針對你提的第一點, (看來你指的是 wchar_t 是 16-bits 時會遇到的狀況, 也就是一個 unicode code point 可能是由一個或兩個 utf-16 character 所組成) 原本就有考慮到. 在我看來, w2mb 在此狀況下是沒問題的. 因為 mbrtowc wcrtomb 會把這狀況紀錄在 user 的 mbstate_t object (code 中的 state 變數), 並 return 0. 而 w2mb 會在下一次呼叫 mbrtowc wcrtomb 時把第二個 utf-16 (wide) character 餵給 mbrtowc wcrtomb, 以產出超出 BMP 之外的 multi-byte character sequence.

Again, 我對 unicode 以及相關 API 實在是外行, 以上是我粗淺的了解, 不見得正確. 感謝你的意見.

關於第二點, 我了解並且同意你的看法. 餵給 mb2w 的 multi-byte char seq 必須是從頭 (leading byte) 開始的.

    ed: Correction. Changed references of mbrtowc to wcrtomb – fr3@K

Comment by fr3@K — May 12, 2009 @ 11:11


Comment #6267

1. 抱歉!我錯了!我沒有先在機器上跑一下。我還以為 int w2mb(wstr,) 可以處理BMP外的字,也就是一個 unicode code point 可能是由兩個 utf-16 所組成的狀況。後來我跑過後,才發現會傳回 -1。(我是試 U+10000 == 0xD800 0xDC00 及 U+20000 == 0xD840 0xDC00) 我想傳回 -1 也是不錯的選擇。可是就因為如此 std::string w2mb(wstr) 就麻煩了,如何回報錯誤?如果我傳進一個含有 U+20000 的字串(譬如說五個中文字的第三個字是 U+20000 ),std::string w2mb(wstr) 就…

2. 關於第二點,我想針對 2007/11/20 的 Post 文章 提出 Windows 的 wchar_t 的優點到底在那裡?或許我該說 Unicode 的優點在哪?從此不必再無止境的反向 Parse 字串。想想看如果老外用 MultiByte 的概念寫完 Word 再交給遠東國家( Japanese 或 Chinese),我們該如何修改?舉例,Mouse Click。老外看落在哪個 Byte 就把 Cursor 移過去那個 Byte 即可。中文可不許你移到一個中文字的兩個 Byte 中間,這時你就必須反向 Parse 字串看看,到底這是不是一個中文字的兩個 Byte 中間。麻煩啊!

3. 至於為什麼是 UTF-16 而非 UTF-8 或 UTF-32?UTF-16 比 UTF-32 省空間,UTF-16 比 UTF-8 效率高。想想如果 Word 用 UTF-8 處理中文字,真的是超麻煩。想想如果 font engine 用 UTF-8 作 font index 每個中文字都須要做 bit shift/bit remove 才可以在 font font 取得字型。如果用 UTF-16 作 font index 只有在 BMP 外的字才要做 bit shift/bit remove,一般中文文章中出現的機率應不到千分之一吧。

    ed: added link to the post buil referenced to – fr3@K

Comment by buil — May 13, 2009 @ 8:26


Comment #6418

Hi buil,

我沒有讀過 C 標準, 事實上我也不清楚 wcrtomb 的 defined behavior. 我曾從 libc 的 manpage 與 MS 的 msdn 上找出相關的說明出來看. 但很可惜, 對於我們在討論的問題, 兩者都不夠清楚.

首先希望你能幫忙釐清一件事, 你用來測試的 code point – U+10000, 它到底是什麼字? 是繁體中文嗎? 是 big5 支援的字嗎? 我猜不是, 因為這個 code point 的值太漂亮了, 看來只是你隨意挑選的.

如果不是, 在 locale 是 big5 時, 把它餵給 wcrtomb 會吐出 size_t(-1) 則是完全不意外的. 因為它無法以 big5 來表示, 而這也正是 wcrtomb 回傳 size_t(-1) 的意義, 與該 code point 在不在 BMP 沒有關係.

若 locale 為 big5, 要實驗這個 case 得拿一個在 BMP 之外, 且有被 big5 定義的繁體中文字來測試才對.

而為什麼我會在對 unicode API 不熟悉的狀況下對 wcrtomb 的行為做我前面說過的推測? 因為這個 API 有個, 除了如我前面所推測的用途之外讓我想不通可以拿來做什麼的 argument, a pointer to a mbstate_t object:

    size_t wcrtomb(char *s, wchar_t wc, mbstate_t *ps);
    

當然, 這只是我自認的合理推測. 也很歡迎你或是其他的朋友來告訴我: “fr3@K, 你搞錯了!”

另, 或許是我表達的不好, 我一直覺得 Unicode 很讚啊~ 但仍然感謝你再次提醒我它的優點.

最後, 關於 Unicode 的哪種 encoding 比較好. 很顯然你認為 utf-16 為最 balance 的 encoding. 這我沒什麼意見, 但我沒被你說服它是最好的 encoding. 你說:

UTF-16 比 UTF-32 省空間,UTF-16 比 UTF-8 效率高。

我也可以說:

UTF-8 比 UTF-16 省空間,UTF-32 比 UTF-16 效率高。

在你或是其他朋友成功啟發我之前, 小弟選擇維持 原本的看法:

程式好寫用 UTF-32, 若空間有限則選 UTF-8.

Comment by fr3@K — May 17, 2009 @ 21:29


Comment #6478

沒錯,U+10000是我隨意挑選的字,它是一個古代Greek的字(應該是一個拼音字母),我只是拿它來測試的。

正如我在Comment #6267中提及int w2mb(wstr,)傳回-1是不錯的選擇,當然是正確的。

只是在std::string w2mb(wstr)中,並沒有報錯的機制,如果字串中含有U+10000(比較不可能)或U+20000(機率雖較U+10000高一點,但也應該高不到哪去)甚或U+4E28(部首字,這個字出現的機率就高多了,唸ㄍㄨㄣˇ,像中就是這個部首。這個字郵局會用到,我有做過他們的字典查詢系統,支援CJK Extension A。另外又幫另一家公司做支援CJK Extension B的字典查詢系統。這個部首字就不在Big5裡,另外還有其他幾個部首字也不在Big5裡。)。如果這個字串含有這個部首字,叫用std::string w2mb(wstr)時,就會出錯了。

可行的方案
1. 加上assert
std::string w2mb(const std::wstring& wstr)
{
std::string mbstr;
assert(w2mb(wstr, mbstr) >= 0);
return mbstr;
}

2. 改變function prototype

如果從API的完備性來講,方案2似乎較好。但是如果實際應用中幾乎很少發生的話,方案1也可以接受。

不用說服哪種encoding較好,只是提供觀點作參考。參考即可。

我猜一個在 BMP 之外, 且有被 big5 定義的繁體中文字,或許在CJK Compatibility Supplement可以找得到。猜想縱歸只是猜想。

Comment by buil — May 20, 2009 @ 21:48


Comment #6514

Hi buil,

看了你的這個留言我才發現原來有些地方我們可能是在雞同鴨講 >< . 有的是我沒搞懂的, 有的是你沒弄清楚的.

你曾不只一次提到讓 int w2mb(wstr, mbstr) 回傳 -1 來表示遇到了如 U+10000 的 codepoint. 我再次回頭看了 code, 這個 function 在遇到這情況時會回傳大於 0 的值, 表示在 return value 減 1 的 offset 遇到了無法轉成 current locale 的 code point. 並以 return 0 來表示完整的 conversion.

這次我看出了其他的問題. int w2mb(wstr, mbstr) 無法偵測 codepoint 的值不是有效的 unicode 的狀況. 再次挖了挖 wcrtomb 的 manpage 與 msdn 的說明, 發現都沒有提到這一點. 暫時看來, 要解決這個問題可能需要捨 wcrtomb 而就其他的 API.

另外, 我對 string w2mb(wstr) 的定位是 convenient wrapper, 因此選擇在發生錯誤時採取容忍的策略, 並傳回轉換成功的部份. 如果你能接受這個定位的話, 應該可以理解它的 prototype 與實作是你看到的樣子的原因. 出發點不是對與錯, 而是取與捨.

真心感謝你的意見, 讓我在這溝通交流的過程想了不少事情. :)

希望經過這幾輪的交流, 我們能成功 sync 彼此的觀點. 不見得要你我同意對方的看法, 但期能夠理解不一樣的見解, 與其背後的緣由.

Cheers.

Comment by fr3@K — May 21, 2009 @ 21:16


Comment #6539

1. 仔細看了看,問題似乎是出在我的身上。抱歉了:-)
2. 建議如果有人有The Unicode 4.0 Standard這本書的人,又買了The Unicode 5.0 Standard,請不要丟了4.0,或是將4.0送人。因為4.0中有CJK Extension A及Extension B的Glyph,在5.0中這些Glyph去掉了。對中文Extension A及B的開發而言,這些資訊應該還蠻重要的。我就是將The Unicode 4.0 Standard這本書送回給開發廠商的那個笨蛋,還好我在郵局開發Extension A的階段,順便也把Extension B的資訊記錄下來,沒有造成太大的影響。後來我跟那個開發廠商講,請把4.0好好保留下來,不要丟了,這本書很重要。目前我的書架上徒留The Unicode 5.0 Standard這本書。:-(

Comment by buil — May 22, 2009 @ 12:41


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>