我的设置:gcc-4.9.2,UTF-8环境。
以下C程序以ASCII格式工作,但不是UTF-8。
创建输入文件:
echo -n 'привет мир' > /tmp/вход
这是test.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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符号作为一个单元(包括argv,STDIN,STDOUT,STDERR,文件输入,输出和程序代码)?
答案 0 :(得分:10)
#define SIZE 10
缓冲区大小为10不足以存储UTF-8字符串привет мир
。尝试将其更改为更大的值。在我的系统(Ubuntu 12.04,gcc 4.8.1)上,将其更改为20,效果非常好。
UTF-8是一种多字节编码,每个字符使用1到4个字节。因此,使用40作为上面的缓冲区大小更安全。 在How many bytes does one Unicode character take?进行了大量讨论,这可能很有趣。
答案 1 :(得分:7)
Siddhartha Ghosh的answer为您提供了基本问题。但是,修复代码需要更多的工作。
我使用了以下脚本(chk-utf8-test.sh
):
echo -n 'привет мир' > вход
make utf8-test
./utf8-test
grep -q 'привет мир' выход && echo OK
我打电话给你的程序utf8-test.c
并修改了这样的来源,删除了/tmp
的引用,并且对长度更加小心:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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。
答案 2 :(得分:4)
这是其他答案的必然结果,但我会尝试从略微不同的角度解释这一点。
这是Jonathan Leffler的代码版本,有三处小改动:(1)我明确表示了UTF-8字符串中的实际单个字节;和(2)我修改了sprintf
格式化字符串宽度说明符,希望能够做你实际尝试做的事情。同时切向(3)我使用perror
在某些内容失败时获得稍微有用的错误消息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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
s - "\320\277"
。)
这显然不太理想,但如果您的代码不特别关心字形语义,则表明您可以将UTF-8视为“只是字节”。如果您这样做,最好切换到wchar_t
,例如:在这里:http://www.gnu.org/software/libc/manual/html_node/Extended-Char-Intro.html
但是,当标准期望值为UTF-8时,标准wchar_t
不太理想。参见例如GNU libunistring documentation用于较少侵入性的替代方案,以及一些背景知识。有了这个,您应该可以将char
替换为uint8_t
,将str*
个函数替换为u8_str*
替换,并且可以完成。假设一个字形等于一个字节仍然需要解决,但这在您的示例程序中变得很小。 http://ideone.com/p0VfXq提供了一个改编版(虽然遗憾的是http://ideone.com/上没有图书馆,因此无法在那里展示。)
答案 3 :(得分:0)
可能您的test.c
文件未以UTF-8格式存储,因此“приветмир”字符串为ASCII - 并且比较失败。更改源文件的文本编码,然后重试。
答案 4 :(得分:0)
以下代码按要求运行:
#include <stdio.h>
#include <locale.h>
#include <stdlib.h>
#include <wchar.h>
#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);
}