2012-04-04 19 views
13

私はLinuxでC言語を学んでいますが、私は少し奇妙な状況に遭遇しました。UnicodeがC言語で格納されています

私が知る限り、標準Cのcharデータ型は1バイト(8ビット)のASCIIです。それは、ASCII文字だけを保持できることを意味するはずです。この擬似コードのようなgetchar機能によって満たされている私はchar input[]を使用する私のプログラムでは、

は:

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

奇妙なことは、それがASCII文字のためだけでなく、動作することですが、任意の文字のために私は、次のような、想像します@&@{čřžŧ¶'`[łĐŧđж←^€~[←^ø{&}čžを入力します。

私の質問は - どのように可能ですか?それはCの多くの美しい例外の1つと思われますが、私は本当に説明を感謝します。それはOS、コンパイラ、隠れた言語の追加のスーパー機能の問題ですか?

ありがとうございました。

+2

これは本当に文字ではなく、getchar()で得られたバイトです。すべての文字はバイトシーケンスとしてエンコードされます。 –

+1

これらは比較的普通の文字です。あなたの想像力を広げて、中国語や日本語の文字を含むようにしてください。またはキリル文字を変更しよう:) :)あなたのためにロシア語で "こんにちは"があります: "Привет"。 – dasblinkenlight

+0

@DanielFischer私は 'getchar()'がバイトにデコードすることを理解しています。しかし、私はすでに理解していません。そのバイトが 'char'データ型で保持される方法は、一バイトでなければなりません。 –

答えて

18

ここでは魔法はありません - C言語は、生のバイトを計算機のメモリに格納するので、あなたにアクセスします。 端末がutf-8(おそらく)を使用している場合、ASCII以外の文字はメモリ内で1バイト以上使用されます。あなたが再び表示すると、これらのシーケンスを単一の表示文字に変換するターミナルコードです。

文字列のstrlenを印刷するようにコードを変更するだけで、意味を理解できます。

Cでutf-8の非ASCII文字を適切に処理するには、glib、qtなどのライブラリを使用して処理する必要があります。

+1

を入力するか、[0]だけを印刷して、最初の文字は印刷されず、おそらくは印刷不可能な文字になる最初のバイトだけが表示されるようにしてから、入力[0]と入力[1]マルチバイト文字を表示します。 – abresas

+0

さて、私はちょうどいくつかのコードの変更を試みて、それは記述されているとおりに正確に動作します。ありがとうございました。 ワイド文字の適切な処理には、ワイド文字「」だけでは不十分です。 –

3

ASCIIは8ビットではなく7ビットです。 char []にはバイトが格納されています。これは任意のエンコーディング(iso8859-1、utf-8)で使用できます。 Cは気にしない。

2

非ASCII文字のデータ型はwint_t#include <wchar.h>)です。メソッドgetwchar()を使用してそれらを読み取ることができます。

14

ASCIIは7ビットの文字セットです。通常は8ビットのcharで表されるC言語です。 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' であることが保証されていない。すなわちEBCDICを使用して、ASCIIに基づいていない

alhpa_a = 0x61; 

システム。そのようなプラットフォームのCはまだうまく動作しますが、ここでは(ほとんど)7の代わりに8ビットを使用します。すなわち、AはASCIIのままであるため、193であり、65ではありません。


ただしASCIIの場合。 128〜255の10進数を持つバイト(使用中は8ビット)は拡張され、ASCIIセットの一部ではありません。私。 ISO-8859はこの範囲を使用します。

よく行われる処理です。 1つの文字に2つ以上のバイトを結合することもできます。だから、2つのバイトを印刷して、例えば、utf80xc3 0x98 ==Øというように定義されている場合は、この文字を取得します。

これはまた、使用している環境によって異なります。多くのシステム/環境でASCII値を印刷すると、文字セットやシステムなどで同じ結果が得られますが、印刷バイト> 127または二重引用符で囲まれた文字は、

すなわち:B氏は、これは多分に特に関連性がある

Jasπß

を取得している間

A氏はを実行しているプログラムは

Jasŋ€

を取得します拡張文字の1バイト表現のISO-8859シリーズおよびWindows-1252など


  • UTF-8#Codepage_layout、UTF-8では、ASCIIを持って、あなたは不戦勝の特殊な配列を有します。
    • 各シーケンスは、すべてのビット10始まるバイトの所定数続い
    • 、(最後のASCIIバイトである)バイト> 127で始まります。
    • つまり、マルチバイトのUTF-8表現でASCIIバイトを見つけることはありません。ある

。 UTF-8の最初のバイト(ASCIIでない場合)は、この文字が持つバイト数を示します。また、最上位ビットが0であるため、ASCII文字に続くバイトがないと言うこともできます。

すなわち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 
... 

。あなたが言及した文字の1つを見ると、 UTF-8端末の場合:

$ echo -n "č" | XXD

が得られるはず:

0000000:c48d ..つまり

"C" バイト0xc4と0x8dで表されます。 xxdコマンドに-bを追加すると、バイトのバイナリ表現が得られます。次のように我々は彼らを解剖:

___ 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 = 269 = 0x10D => UncodeコードポイントU + 010D == "C"。

この数は、これと他のコード体系のロットの&#269; == č

共通としてHTMLにも使用することができる8ビットバイトがベースであることです。


頻繁に文脈に関する質問です。例として、ETSI GSM 03.38/03.40(3GPP TS 23.038,3GPP 23038)のGSM SMSを使用します。 7ビット文字テーブル、7ビットGSMデフォルトアルファベットがありますが、それらを8ビットとして保存する代わりに、7ビットとして格納されます。この方法で、指定したバイト数にさらに多くの文字をパックできます。標準的なSMS160文字は、ASCIIとして1280ビットまたは160バイト、SMSとして1120または140バイトとなる。

1例外なく、(それ以上のことです)。

e.e.e. ASCIIにC8329BFD06 SMS UDP形式のセプテット(7ビット)として保存されたバイトの簡単な例:

       _________ 
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文字になり、 "アンパック"。

+0

この記事は単純に素晴らしいです!概要と概要をありがとう。 @Mimars; –

+0

;少し長くなりましたが、:)。興味深いトピックであり、物事がどのように解決されたかを見て楽しむことができます。また、コーディング時にも同様のロジックを使用できるという点で教育的だと考えてください。 ASCIIにはかなりの美しさがあり、すべてがどのように整理されソートされているか、つまりpp3 here http://faculty.kfupm.edu.sa/ics/said/ics232Lectures/L11_LogicInstructions.docです。 - /usr/include/ctype.hなどを見るのも教育的です。 – Morpfh

1

これはUTF-8という魔法です。どのように動作するか心配する必要はありません。唯一の問題は、Cデータ型の名前がchar文字の)であり、実際にはバイトのであることです。文字とそれらをエンコードするバイトとの間に1対1の対応はありません。

あなたのコードでは、プログラムの観点から、バイトのシーケンスを入力すると、バイトがメモリに格納され、テキストを印刷すると、バイトが印刷されます。このコードは、これらのバイトがどのように文字をエンコードするか気にしません。入力時にエンコードすることを心配し、出力時に正しく解釈する必要のある端末だけです。

1

あり仕事をして、多くのライブラリはもちろんですが、すぐに任意のUTF8ユニコードをデコードするために、この小さな機能は便利です:

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<a; ++b) 
       u = (u<<6)|(s[l++]&0x3f); 
     } 
    } 
    if(i) *i += l; 
    return u; 
} 

はあなたのコードを考慮すると、文字列を反復してユニコード値を読み取ることができます:

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; 
    } 
} 
関連する問題