web-dev-qa-db-ja.com

C文字で保存されたUnicode

私は現在LinuxでC言語を学んでいて、少し奇妙な状況に遭遇しました。

私の知る限り、標準Cのcharデータ型はASCII、1バイト(8ビット)です。つまり、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、コンパイラ、隠された言語の追加のスーパー機能の問題ですか?

ありがとう。

22
Miroslav Mares

ここには魔法はありません。C言語では生のバイトがコンピューターのメモリーに格納されるため、それらにアクセスできます。端末がutf-8を使用している場合(これは可能性があります)、非ASCII文字はメモリ内で1バイト以上かかります。次に再び表示すると、これらのシーケンスを単一の表示文字に変換する端末コードです。

文字列のstrlenを出力するようにコードを変更するだけで、意味がわかります。

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

25
jsbueno

ASCIIは7ビットの文字セットです。 Cでは通常、8ビット文字で表されます。 8ビットバイトの最上位ビットが設定されている場合、notan 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は引き続き正常に動作しますが、ここでは(ほとんど)7ではなく8ビットを使用します。つまり、Aは、ASCIIの場合のように_193_ではなく、10進_65_としてコーディングできます。


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

よく行われること2バイト以上を1つの文字に結合することもできます。したがって、たとえば tf8 _0xc3 0x98_ ==Øのように定義されている2バイトを連続して出力すると、この文字が得られます。

これもまた、現在の環境に依存します。多くのシステム/環境では、ASCIIの値は、文字セットやシステムなどで同じ結果になります。ただし、127バイトを超える文字や二重の文字を出力すると、異なる結果になりますローカル構成によって異なります。

つまり:

実行中のtheプログラムは

Jasŋ€

B氏が

Jasπß

これはおそらく、拡張文字のシングルバイト表現のISO-8859シリーズやWindows-1252などに特に関係があります。


  • TF-8#Codepage_layout 、UTF-8ではASCIIがあり、次に特殊なシーケンスのバイがあります。
    • 各シーケンスは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
...
_

例として。あなたが言及するキャラクターの1つを見れば。 UTF-8端末の場合:

$ echo -n "č" | xxd

譲歩する必要があります:

0000000:c48d ..

つまり、「č」はtwoバイト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 = 26910 = 0x10D =>コードポイントU + 010Dのコードを解除== "č"。

この数値は、_&#269;_ == asとしてHTMLでも使用できます。

これと他の多くのコードシステムに共通するのは、8ビットバイトがベースであることです。


多くの場合、それはコンテキストに関する問題でもあります。例として、ETSI GSM 03.38/03.40( GPP TS 23.038GPP 23038 )を使用したGSM SMSを取り上げます。そこでは、7ビットの文字テーブル、7ビットのGSMデフォルトアルファベットもありますが、8ビットとして保存する代わりに、7ビットとして保存されます。1。このようにして、指定したバイト数により多くの文字をパックできます。つまり、標準SMS 160文字はASCIIとして1280ビットまたは160バイトになり、SMSとして1120または140バイトになります。

1 例外なく、(それは物語にもっとあります)。

つまりSMS UDP形式でASCIIにセプテット(7ビット)C8329BFD06として保存された単純なバイトの例:

_                                _________
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バイト「unpacked」は10文字になります。

17
Morpfh

ASCIIは8ビットではなく7ビットです。 char []はバイトを保持します。これは、任意のエンコーディング(iso8859-1、utf-8など)にすることができます。 Cは気にしません。

4
evil otto

これが TF-8 の魔法であり、その動作を心配する必要さえありませんでした。唯一の問題は、Cデータ型の名前がchar(forcharacter))であり、実際にはbyte。文字とそれらをエンコードするバイトの間に1:1の対応はありません。

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

3
ybungalobill

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

2
greg

もちろん、多くのライブラリが機能しますが、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;
   }
}
1
Per Löwgren