2012-04-04 36 views
13

Tôi đang học ngôn ngữ C trên Linux ngay bây giờ và tôi đã gặp một tình huống hơi lạ.Unicode được lưu trữ trong C char

Theo như tôi biết, loại dữ liệu chuẩn char của C là ASCII, 1 byte (8 bit). Nó có nghĩa là, nó có thể chỉ giữ ký tự ASCII.

Trong chương trình của tôi, tôi sử dụng char input[], được lấp đầy bởi getchar chức năng như giả này:

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

Điều kỳ lạ là nó hoạt động không chỉ đối với các ký tự ASCII, nhưng đối với bất kỳ ký tự tôi tưởng tượng, chẳng hạn như @&@{čřžŧ¶'`[łĐŧđж←^€~[←^ø{&}čž trên đầu vào.

Câu hỏi của tôi là - làm cách nào có thể? Nó có vẻ là một trong nhiều ngoại lệ đẹp trong C, nhưng tôi thực sự sẽ đánh giá cao sự giải thích. Đó có phải là vấn đề về hệ điều hành, trình biên dịch, tính năng siêu bổ sung của ngôn ngữ ẩn không?

Cảm ơn.

+2

Nó không thực sự là ký tự, đó là byte được nhận với 'getchar()'. Mỗi ký tự được mã hóa dưới dạng chuỗi byte. –

+1

Đây là những ký tự tương đối bình thường. Hãy thử mở rộng trí tưởng tượng của bạn để bao gồm, nói rằng, một số chữ cái Trung Quốc hoặc Nhật Bản. Hoặc thử Cyrillic để thay đổi :) Đây là "Xin chào" bằng tiếng Nga cho bạn: "Привет". – dasblinkenlight

+0

@DanielFischer Tôi hiểu rằng 'getchar()' giải mã nó thành byte (s). Nhưng tôi đã không hiểu, làm thế nào mà byte có thể được tổ chức trong 'char' kiểu dữ liệu, mà nên được _one_ byte. –

Trả lời

18

Không có phép thuật ở đây - Ngôn ngữ C cho phép bạn truy cập vào các byte thô, vì chúng được lưu trữ trong bộ nhớ comptuer. Nếu thiết bị đầu cuối của bạn đang sử dụng utf-8 (có khả năng), các ký tự không phải ASCII mất nhiều hơn một byte trong bộ nhớ. Khi bạn hiển thị sau đó một lần nữa, là mã đầu cuối của chúng tôi chuyển đổi các chuỗi này thành một ký tự được hiển thị duy nhất.

Chỉ cần thay đổi mã của bạn để in strlen của các chuỗi và bạn sẽ thấy ý tôi là gì.

Để xử lý đúng các ký tự không phải ASCII utf-8 trong C, bạn phải sử dụng một số thư viện để xử lý chúng cho bạn, như glib, qt hoặc nhiều thứ khác.

+1

hoặc cố gắng in chỉ cần nhập [0] để thấy rằng nó sẽ không in ký tự đầu tiên, nhưng chỉ byte đầu tiên có thể là ký tự không in được, rồi thử in đầu vào [0] và nhập [1] lại với nhau để xem ký tự nhiều byte. – abresas

+0

Ok, tôi vừa thử một số sửa đổi mã và nó hoạt động chính xác như mô tả. Cảm ơn bạn. Chỉ ghi chú về các ký tự rộng - '' không đủ để xử lý đúng các ký tự rộng? –

3

ASCII là 7 bit, không phải 8 bit. a char [] giữ byte, có thể ở bất kỳ mã hóa nào - iso8859-1, utf-8, bất kể bạn muốn gì. C không quan tâm.

2

Có một kiểu dữ liệu wint_t (#include <wchar.h>) cho các ký tự không phải ASCII. Bạn có thể sử dụng phương thức getwchar() để đọc chúng.

14

ASCII là bộ ký tự 7 bit. Trong C thường được đại diện bởi một char 8 bit. Nếu bit cao nhất trong một byte 8 bit được đặt, thì đó là không ký tự ASCII.

Cũng lưu ý rằng bạn đang không được bảo đảm ASCII làm cơ sở, nhiều người bỏ qua các tình huống khác. Nếu bạn muốn kiểm tra xem một "nguyên thủy" byte là một nhân vật alpha bạn có thể nói cách khác không, khi Phải cẩn thận với tất cả các hệ thống, nói:

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

Thay vào đó bạn sẽ phải sử dụng ctype.h và nói :

isalpha(c); 

Chỉ ngoại lệ, AFAIK, là số, trên hầu hết các bảng ít nhất, chúng có giá trị liền nhau.

Do đó, tính năng này hoạt động;

char ninec = '9'; 
char eightc = '8'; 

int nine = ninec - '0'; 
int eight = eightc - '0'; 

printf("%d\n", nine); 
printf("%d\n", eight); 

Nhưng điều này không đảm bảo được 'a':

alhpa_a = 0x61; 

Hệ thống không dựa trên ASCII, ví dụ: sử dụng EBCDIC; C trên nền tảng như vậy vẫn chạy tốt nhưng ở đây họ (chủ yếu) sử dụng 8 bit thay vì 7 và tức là A có thể được mã hóa dưới dạng số thập phân 193 và không phải là 65 vì nó nằm trong ASCII.


Tuy nhiên, đối với ASCII; các byte có số thập phân 128 - 255, (8 bit được sử dụng), được mở rộng và không phải là một phần của tập hợp ASCII. I E. ISO-8859 sử dụng phạm vi này.

Điều gì thường được thực hiện; cũng là để kết hợp hai hoặc nhiều byte vào một ký tự. Vì vậy, nếu bạn in hai byte sau mỗi byte khác được định nghĩa là nói, utf80xc3 0x98 == Ø, thì bạn sẽ nhận được ký tự này.

Điều này một lần nữa phụ thuộc vào môi trường của bạn. Trên nhiều hệ thống/môi trường in các giá trị ASCII cho kết quả tương tự trên các bộ ký tự, hệ thống, v.v.

Ie:

ông A chạy chương trình được

Jasŋ €

Trong khi ông B bị

Jasπß

Đây có lẽ là đặc biệt liên quan đến Dòng ISO-8859 và Windows-1252 biểu diễn byte đơn của các ký tự mở rộng, v.v.


  • UTF-8#Codepage_layout, Trong UTF-8 bạn có ASCII, sau đó bạn có trình tự đặc biệt của tạm biệt.
    • Mỗi chuỗi bắt đầu với một byte> 127 (đó là ASCII cuối byte),
    • theo sau là một số lượng nhất định của byte đó tất cả bắt đầu với các bit 10.
    • Nói cách khác, bạn sẽ không bao giờ tìm thấy một byte ASCII trong một biểu diễn UTF-8 đa byte.

Tức là; byte đầu tiên trong UTF-8, nếu không phải ASCII, cho biết số byte mà ký tự này có. Bạn cũng có thể nói các ký tự ASCII nói không còn byte nào nữa - vì bit cao nhất là 0.

tức là nếu tập tin hiểu là 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 
... 

Như một ví dụ. Nếu chúng ta nhìn vào một trong các nhân vật bạn đề cập đến. Nếu trong thiết bị đầu cuối UTF-8:

$ echo -n "č" | xxd

nên nhường:

0000000: c48d ..

Nói cách khác "C" được đại diện bởi các hai byte 0xc4 và 0x8d. Thêm -b vào lệnh xxd và chúng ta nhận được biểu diễn nhị phân của các byte. Chúng tôi phân tích như sau:

___ 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 

này cung cấp cho chúng tôi: 00100001101 = 269 = 0x10D => Uncode điểm mã U + 010D == "C".

Con số này cũng có thể được sử dụng trong HTML như &#269; == č

chung cho điều này và rất nhiều hệ thống mã khác là một 8-bit byte là cơ sở.


Thường thì đó cũng là câu hỏi về ngữ cảnh. Ví dụ lấy GSM SMS, với ETSI GSM 03.38/03.40 (3GPP TS 23.038, 3GPP 23038). Ở đó chúng tôi cũng tìm thấy một bảng ký tự 7bit, bảng chữ cái mặc định GSM 7 bit, nhưng thay vì lưu trữ chúng dưới dạng 8 bit, chúng được lưu trữ dưới dạng 7 bit . Bằng cách này bạn có thể đóng gói nhiều ký tự hơn vào một số byte nhất định. Tức là SMS tiêu chuẩn 160 ký tự trở thành 1280 bit hoặc 160 byte dưới dạng ASCII và 1120 hoặc 140 byte dưới dạng SMS.

1 Không ngoại lệ, (đó là câu chuyện nhiều hơn).

I.e. một ví dụ đơn giản của byte lưu lại dưới dạng septets (7bit) C8329BFD06 ở định dạng SMS UDP để 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 

Và 9 byte "giải nén" trở thành 10 ký tự.

+0

Bài viết này chỉ đơn giản là tuyệt vời! Cảm ơn bạn đã tóm tắt và tổng quan. –

+0

@Mimars; Đã trở thành một chút dài, nhưng, :). Đây là một chủ đề thú vị và tìm thấy nó thú vị để xem cách mọi thứ đã được giải quyết. Cũng nghĩ rằng đó là giáo dục trong đó người ta có thể sử dụng logic tương tự khi mã hóa - cũng hoàn toàn khác nhau. Ngoài ra còn có khá nhiều người đẹp với ASCII và cách mọi thứ được sắp xếp và sắp xếp - tức là: pp3 đây http://faculty.kfupm.edu.sa/ics/said/ics232Lectures/L11_LogicInstructions.doc. - Nó cũng mang tính giáo dục để xem tại /usr/include/ctype.h, v.v. – Morpfh

1

Đây là sự kỳ diệu của UTF-8, rằng bạn thậm chí không phải lo lắng về cách hoạt động của nó. Vấn đề duy nhất là kiểu dữ liệu C có tên là char (đối với ký tự), trong khi ý nghĩa thực sự là byte. không có sự tương ứng 1: 1 giữa các ký tự và các byte mã hóa chúng.

Điều gì xảy ra trong mã của bạn, từ quan điểm của chương trình, bạn nhập một chuỗi byte, nó lưu trữ byte trong bộ nhớ và nếu bạn in văn bản, nó sẽ in byte.Mã này không quan tâm làm thế nào các byte mã hóa các ký tự, nó chỉ là thiết bị đầu cuối mà cần phải lo lắng về mã hóa chúng trên đầu vào và giải thích chính xác chúng trên đầu ra.

1

Tất nhiên có nhiều thư viện mà không được công việc, nhưng để nhanh chóng giải mã bất kỳ unicode UTF8, chức năng này ít là tiện dụng:

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; 
} 

Xét mã của bạn; bạn có thể lặp lại chuỗi và đọc các giá trị 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; 
    } 
} 
Các vấn đề liên quan