2010-09-24 32 views
1

注意:如果你已經關注了我最近的問題,你會發現它們都是關於我在C語言中的Unicode庫練習 - 作爲我的第一批幾個嚴肅的項目之一在C中,我遇到了很多問題,所以如果我對一件事提出太多問題,我很抱歉。UTF-8解碼器在非ASCII字符上失敗

我的部分庫將UTF-8編碼的char指針解碼爲原始unsigned代碼點。但是,某些飛機不能正確解碼。讓我們來看看(相關)代碼:

typedef struct string { 
unsigned long length; 
unsigned *data; 
} string; 

// really simple stuff 

string *upush(string *s, unsigned c) { 
if (!s->length) s->data = (unsigned *) malloc((s->length = 1) * sizeof(unsigned)); 
else s->data = (unsigned *) realloc(s->data, ++s->length * sizeof(unsigned)); 
s->data[s->length - 1] = c; 
return s; 
} 

// UTF-8 conversions 

string ctou(char *old) { 
unsigned long i, byte = 0, cur = 0; 
string new; 
new.length = 0; 
for (i = 0; old[i]; i++) 
    if (old[i] < 0x80) upush(&new, old[i]); 
    else if (old[i] < 0xc0) 
    if (!byte) { 
    byte = cur = 0; 
    continue; 
    } else { 
    cur |= (unsigned)(old[i] & 0x3f) << (6 * (--byte)); 
    if (!byte) upush(&new, cur), cur = 0; 
    } 
    else if (old[i] < 0xc2) continue; 
    else if (old[i] < 0xe0) { 
    cur = (unsigned)(old[i] & 0x1f) << 6; 
    byte = 1; 
    } 
    else if (old[i] < 0xf0) { 
    cur = (unsigned)(old[i] & 0xf) << 12; 
    byte = 2; 
    } 
    else if (old[i] < 0xf5) { 
    cur = (unsigned)(old[i] & 0x7) << 18; 
    byte = 3; 
    } 
    else continue; 
return new; 
} 

所有upush呢,對了,是推動一個代碼點到string末,需要重新分配內存。 ctou進行解碼工作,並將byte中仍然需要的字節數存儲在一個序列中,以及cur中的進行中的代碼點。

該代碼似乎對我來說都是正確的。我們嘗試使用UTF-8解碼U+10ffff,即f4 8f bf bd。這樣做:

long i; 
string b = ctou("\xf4\x8f\xbf\xbd"); 
for (i = 0; i < b.length; i++) 
printf("%z ", b.data[i]); 

應該打印出來:

10ffff 

而是它打印出:

fffffff4 ffffff8f ffffffbf ffffffbd 

這基本上是四個字節UTF-8的,與ffffff前上漲了它。

有關我的代碼中出現什麼問題的任何指導?

+0

順便說一下,您的問題主題是誤導。這個問題與高平面(非BMP)字符無關;它發生在** any **非ascii字符。它也與UTF-8無關,而與基本的C算法無關。你的UTF-8解碼器也有一些缺陷,最糟糕的是你將解碼無效的超長序列。 – 2010-09-24 14:21:47

+0

我已編輯標題以改善相關性。如果你能讓我知道你發現的其他一些錯誤,我會非常感激。 – 2010-09-24 14:24:11

+2

您正在阻止兩個字節的溢出,但不會再延長,例如。 0xE0,0x80,0xBC。您還允許超過0x10FFFF的代碼點,代理代碼單元(不應以UTF-8出現)以及大於等於0xC0字節的序列,然後是低位字節,然後是0x80-0xBF字節。具有不同代碼的「while」循環/檢查每個長度的情況可能更容易。但是真的,我會使用一些現有的庫代碼來解碼UTF-8,而不是自己進行滾動(因爲錯誤解碼/無效序列最終會導致過濾器逃避並帶來安全後果)。此外'upush'實現在病理上是低效的。 – bobince 2010-09-24 14:34:42

回答

4

char類型允許簽署,並轉換成int,然後簽名(也就是當你直接轉換爲unsigned什麼隱含發生)顯示了錯誤:

#include <stdio.h> 

int main() { 
    char c = '\xF4'; 
    int i = c; 
    unsigned n = i; 
    printf("%X\n", n); 
    n = c; 
    printf("%X\n", n); 
    return 0; 
} 

打印:

FFFFFFF4
FFFFFFF4

改爲使用無符號字符。

+0

非常感謝!改變'ctou'的原型爲固定它:'string ctou(unsigned char * old);' – 2010-09-24 14:18:21

2

您可能已經忽略了這個事實,即char是您平臺上的簽名類型。始終使用:

  • unsigned char如果你如果你使用的字節的小符號整數
  • char抽象的字符串,你不關心的值來讀取字節
  • signed char的實際值除了可能爲0.

順便說一句,您的代碼是非常低效的。不要每個字符反覆調用realloc,爲什麼不分配sizeof(unsigned)*(strlen(old)+1)開始,然後如果它太大,最後減小大小?當然,這只是許多低效率之一。