2010-09-24 31 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)开始,然后如果它太大,最后减小大小?当然,这只是许多低效率之一。