Unicode存储在C char中

我现在正在Linux上学习C语言,而且我遇到了一些奇怪的情况。

据我所知,标准C的char数据类型是ASCII,1字节(8位)。 它应该意味着它只能包含ASCII字符。

在我的程序中,我使用char input[] ,它由getchar函数填充,就像这个伪代码:

 char input[20]; int z, i; for(i = 0; i < 20; i++) { z = getchar(); input[i] = z; } 

奇怪的是它不仅适用于ASCII字符,而且适用于我想象的任何字符,例如@&@{čřžŧ¶'`[łĐŧđж←^€~[←^ø{&}čž输入。

我的问题是 – 怎么可能? 它似乎是C中许多美丽的例外之一,但我真的很感激解释。 这是操作系统,编译器,隐藏语言的附加超级function吗?

谢谢。

这里没有魔法 – C语言让你可以访问原始字节,因为它们存储在comptuer内存中。 如果您的终端使用utf-8(很可能),非ASCII字符在内存中占用多个字节。 再次显示时,我们的终端代码将这些序列转换为单个显示的字符。

只需更改代码即可打印字符串的strlen ,您将看到我的意思。

要在C中正确处理utf-8非ASCII字符,你必须使用一些库来为你处理它们,比如glib,qt或许多其他字符。

ASCII是一个7位字符集。 在C中通常由8位字符表示。 如果设置了8位字节中的最高位,则它不是 ASCII字符。

另请注意,您不能保证ASCII为基础,许多人忽略其他方案。 如果你想检查“原始”字节是否是字母字符,你可以换句话说,在注意所有系统时,请说:

 is_alpha = (c > 0x40 && c < 0x5b) || (c > 0x60 && c < 0x7b); 

相反,你必须使用ctype.h并说:

 isalpha(c); 

唯一的例外是AFAIK,对于数字,至少在大多数表上,它们具有连续的值。

这样可行;

 char ninec = '9'; char eightc = '8'; int nine = ninec - '0'; int eight = eightc - '0'; printf("%d\n", nine); printf("%d\n", eight); 

但这不能保证是'a':

 alhpa_a = 0x61; 

不基于ASCII的系统,即使用EBCDIC ; 在这样的平台上的C仍然运行良好,但在这里它们(大多数)使用8位而不是7位,即A可以编码为十进制193而不是65因为它是ASCII。


但是对于ASCII; 具有十进制128 - 255(使用8位)的字节被扩展,而不是ASCII集的一部分。 即ISO-8859使用此范围。

经常做什么; 也是将两个或多个字节组合成一个字符。 因此,如果你打印两个字节后,定义为utf8 0xc3 0x98 ==Ø,那么你将得到这个字符。

这又取决于您所处的环境。在许多系统/环境中,打印ASCII值会在字符集,系统等中产生相同的结果。但打印字节> 127或双字节字符会根据本地配置给出不同的结果。

即:

A先生正在执行程序

JASN€

而B先生得到了

Jasπß

这可能与ISO-8859系列和扩展字符的单字节表示的Windows-1252特别相关。

  • ASCII_printable_characters ,注意它们是7而不是8位。
  • ISO_8859-1和ISO_8859-15 ,广泛使用的集合,以ASCII为核心。
  • Windows-1252 , Windows的遗产。

  • UTF-8#Codepage_layout ,在UTF-8中你有ASCII,那么你有特殊的byes序列。
    • 每个序列以字节> 127(最后一个ASCII字节)开头,
    • 然后是给定数量的字节,所有字节都以位10开始。
    • 换句话说,您永远不会在多字节UTF-8表示中找到ASCII字节。

那是; UTF-8中的第一个字节,如果不是ASCII,则表示该字符有多少字节。 您还可以说ASCII字符表示不再有字节 - 因为最高位为0。

即如果文件被解释为UTF-8:

 fgetc(c); if c < 128, 0x80, then ASCII if c == 194, 0xC2, then one more byte follow, interpret to symbol if c == 226, 0xE2, then two more byte follows, interpret to symbol ... 

举个例子。 如果我们看一下你提到的一个角色。 如果在UTF-8终端:

$ echo -n“č”| XXD

应该产量:

0000000:c48d ..

换句话说,“č”由两个字节0xc4和0x8d表示。 将-b添加到xxd命令,我们得到字节的二进制表示。 我们按如下方式剖析它们:

  ___ byte 1 ___ ___ byte 2 ___ | | | | 0xc4 : 1100 0100 0x8d : 1000 1101 | | | +-- all "follow" bytes starts with 10, rest: 00 1101 | + 11 -> 2 bits set = two byte symbol, the "bits set" sequence end with 0. (here 3 bits are used 110) : rest 0 0100 Rest bits combined: xxx0 0100 xx00 1101 => 00100001101 \____/ \_____/ | | | +--- From last byte +------------ From first byte 

这给我们:00100001101 2 = 269 10 = 0x10D =>解码码点U + 010D ==“č”。

这个号码也可以在HTML中用作č ==č

这个和许多其他代码系统的共同点是8位字节是基础。


通常它也是关于背景的问题。 作为示例,采用GSM SMS,具有ETSI GSM 03.38 / 03.40( 3GPP TS 23.038,3GPP 23038 )。 在那里我们还找到了一个7位字符表,7位GSM默认字母表,但不是将它们存储为8位,而是存储为7位1 。 这样,您可以将更多字符打包到给定的字节数中。 即标准SMS 160字符变为1280位或160字节作为ASCII和1120或140字节作为SMS。

1也不例外,(故事更多)。

即一个简单的字节示例,以SMS UDP格式保存为septets(7bit)C8329BFD06到ASCII:

  _________ 7 bit UDP represented | +--- Alphas has same bits as ASCII as 8 bit hex '0.......' C8329BFDBEBEE56C32 1100100 d * Prev last 6 bits + pp 1 | | | | | | | | +- 00 110010 -> 1101100 l * Prev last 7 bits | | | | | | | +--- 0 1101100 -> 1110010 r * Prev 7 + 0 bits | | | | | | +----- 1110010 1 -> 1101111 o * Last 1 + prev 6 | | | | | +------- 101111 10 -> 1010111 W * Last 2 + prev 5 | | | | +--------- 10111 110 -> 1101111 o * Last 3 + prev 4 | | | +----------- 1111 1101 -> 1101100 l * Last 4 + prev 3 | | +------------- 100 11011 -> 1101100 l * Last 5 + prev 2 | +--------------- 00 110010 -> 1100101 e * Last 6 + prev 1 +----------------- 1 1001000 -> 1001000 H * Last 7 bits '------' | +----- GSM Table as binary 

9个字节“解压缩”变为10个字符。

ASCII是7位,而不是8位。 char []包含字节,可以是任何编码 – iso8859-1,utf-8,无论你想要什么。 C不关心。

对于非ASCII字符,有一种数据类型wint_t#include )。 您可以使用方法getwchar()来读取它们。

这是UTF-8的神奇之处,你甚至不用担心它是如何工作的。 唯一的问题是C数据类型被命名为char (用于字符 ),而它实际上意味着是字节 。 字符与编码它们的字节之间没有1:1的对应关系。

在您的代码中发生的事情是,从程序的角度来看,您输入一个字节序列,它将字节存储在内存中,如果您打印文本,它会打印字节。 这段代码并不关心这些字节如何对字符进行编码,只有终端需要担心在输入上对它们进行编码并在输出上正确解释它们。

当然有很多库可以完成这项工作,但是为了快速解码任何UTF8 unicode,这个小function很方便:

 typedef unsigned char utf8_t; #define isunicode(c) (((c)&0xc0)==0xc0) int utf8_decode(const char *str,int *i) { const utf8_t *s = (const utf8_t *)str; // Use unsigned chars int u = *s,l = 1; if(isunicode(u)) { int a = (u&0x20)? ((u&0x10)? ((u&0x08)? ((u&0x04)? 6 : 5) : 4) : 3) : 2; if(a<6 || !(u&0x02)) { int b,p = 0; u = ((u<<(a+1))&0xff)>>(a+1); for(b=1; b 

考虑你的代码; 你可以迭代字符串并读取unicode值:

 int l; for(i=0; i<20 && input[i]!='\0'; ) { if(!isunicode(input[i])) i++; else { l = 0; z = utf8_decode(&input[i],&l); printf("Unicode value at %d is U+%04X and it\'s %d bytes.\n",i,z,l); i += l; } }