如何在C代码中使用UTF-8?

我的设置:gcc-4.9.2,UTF-8环境。

以下C程序以ASCII格式运行,但不以UTF-8格式运行。

创建输入文件:

echo -n 'привет мир' > /tmp/вход 

这是test.c:

 #include  #include  #include  #define SIZE 10 int main(void) { char buf[SIZE+1]; char *pat = "привет мир"; char str[SIZE+2]; FILE *f1; FILE *f2; f1 = fopen("/tmp/вход","r"); f2 = fopen("/tmp/выход","w"); if (fread(buf, 1, SIZE, f1) > 0) { buf[SIZE] = 0; if (strncmp(buf, pat, SIZE) == 0) { sprintf(str, "% 11s\n", buf); fwrite(str, 1, SIZE+2, f2); } } fclose(f1); fclose(f2); exit(0); } 

检查结果:

 ./test; grep -q ' привет мир' /tmp/выход && echo OK 

应该怎样做才能使UTF-8代码像ASCII码一样工作 – 不要打扰一个符号需要多少字节,等等。换句话说:在示例中要将任何UTF-8符号视为单个unit(包括argv,STDIN,STDOUT,STDERR,文件输入,输出和程序代码)?

 #define SIZE 10 

缓冲区大小为10不足以存储UTF-8字符串привет мир 。 尝试将其更改为更大的值。 在我的系统(Ubuntu 12.04,gcc 4.8.1)上,将其更改为20,工作得很好。

UTF-8是一种多字节编码,每个字符使用1到4个字节。 因此,使用40作为上面的缓冲区大小更安全。 有一个Unicode字符需要多少字节? 这可能很有趣。

Siddhartha Ghosh的回答为您提供了基本问题。 但是,修复代码需要更多工作。

我使用了以下脚本( chk-utf8-test.sh ):

 echo -n 'привет мир' > вход make utf8-test ./utf8-test grep -q 'привет мир' выход && echo OK 

我调用你的程序utf8-test.c并像这样修改源代码,删除对/tmp的引用,并且对长度更加小心:

 #include  #include  #include  #define SIZE 40 int main(void) { char buf[SIZE + 1]; char *pat = "привет мир"; char str[SIZE + 2]; FILE *f1 = fopen("вход", "r"); FILE *f2 = fopen("выход", "w"); if (f1 == 0 || f2 == 0) { fprintf(stderr, "Failed to open one or both files\n"); return(1); } size_t nbytes; if ((nbytes = fread(buf, 1, SIZE, f1)) > 0) { buf[nbytes] = 0; if (strncmp(buf, pat, nbytes) == 0) { sprintf(str, "%.*s\n", (int)nbytes, buf); fwrite(str, 1, nbytes, f2); } } fclose(f1); fclose(f2); return(0); } 

当我运行脚本时,我得到了:

 $ bash -x chk-utf8-test.sh + '[' -f /etc/bashrc ']' + . /etc/bashrc ++ '[' -z '' ']' ++ return + alias 'r=fc -e -' + echo -n 'привет мир' + make utf8-test gcc -O3 -g -std=c11 -Wall -Wextra -Werror utf8-test.c -o utf8-test + ./utf8-test + grep -q 'привет мир' $'в?\213?\205од' + echo OK OK $ 

为了记录,我在Mac OS X 10.10.3上使用GCC 5.1.0。

这更像是其他答案的必然结果,但我会尝试从略微不同的角度来解释这一点。

这是Jonathan Leffler的代码版本,有三处小改动: (1)我明确了UTF-8字符串中的实际单个字节; (2)我修改了sprintf格式化字符串宽度说明符,希望能够做你实际尝试做的事情。 切向上(3)我使用perror来获取稍微更有用的错误消息,当某些内容失败时。

 #include  #include  #include  #define SIZE 40 int main(void) { char buf[SIZE + 1]; char *pat = "\320\277\321\200\320\270\320\262\320\265\321\202" " \320\274\320\270\321\200"; /* "привет мир" */ char str[SIZE + 2]; FILE *f1 = fopen("\320\262\321\205\320\276\320\264", "r"); /* "вход" */ FILE *f2 = fopen("\320\262\321\213\321\205\320\276\320\264", "w"); /* "выход" */ if (f1 == 0 || f2 == 0) { perror("Failed to open one or both files"); /* use perror() */ return(1); } size_t nbytes; if ((nbytes = fread(buf, 1, SIZE, f1)) > 0) { buf[nbytes] = 0; if (strncmp(buf, pat, nbytes) == 0) { sprintf(str, "%*s\n", 1+(int)nbytes, buf); /* nbytes+1 length specifier */ fwrite(str, 1, 1+nbytes, f2); /* +1 here too */ } } fclose(f1); fclose(f2); return(0); } 

具有正数字宽度说明符的sprintf的行为是从左侧填充空格,因此您尝试使用的空间是多余的。 但是你必须确保目标字段比你正在打印的字符串宽,以便实际进行任何填充。

为了使这个答案自成一体,我将重复别人已经说过的话。 传统的char总是只有一个字节,但UTF-8中的一个字符通常不是一个字节,除非所有字符都是ASCII。 UTF-8的一个吸引人之处在于遗留的C代码不需要知道关于UTF-8的任何信息以便继续工作,但当然,假设一个字符是一个字形无法保存。 (正如您所看到的,例如,“приветмир”中的字形映射到两个字节 – 因此,两个char"\320\277" 。)

这显然不太理想,但certificate如果您的代码不特别关心字形语义,您可以将UTF-8视为“只是字节”。 如果你的话,你最好切换到wchar_t如下所示: http : //www.gnu.org/software/libc/manual/html_node/Extended-Char-Intro.html

但是,当标准期望值为UTF-8时,标准wchar_t不太理想。 请参阅例如GNU libunistring文档,了解更少侵入性的替代方案,以及一些背景知识。 有了它,您应该能够用uint8_t替换char ,并使用u8_str*替换替换各种str*函数并完成。 假设一个字形等于一个字节仍然需要解决,但这在您的示例程序中成为一个小技术问题。 可以在http://ideone.com/p0VfXq上找到改编版(虽然遗憾的是http://ideone.com/上没有该库,因此无法在那里进行演示)。

以下代码按要求运行:

 #include  #include  #include  #include  #define SIZE 10 int main(void) { setlocale(LC_ALL, ""); wchar_t buf[SIZE+1]; wchar_t *pat = L"привет мир"; wchar_t str[SIZE+2]; FILE *f1; FILE *f2; f1 = fopen("/tmp/вход","r"); f2 = fopen("/tmp/выход","w"); fgetws(buf, SIZE+1, f1); if (wcsncmp(buf, pat, SIZE) == 0) { swprintf(str, SIZE+2, L"% 11ls", buf); fputws(str, f2); } fclose(f1); fclose(f2); exit(0); } 

可能你的test.c文件没有以UTF-8格式存储,因此“приветмир”字符串是ASCII – 并且比较失败。 更改源文件的文本编码,然后重试。