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

时间:2015-05-22 03:52:32

标签: c utf-8

我的设置: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,文件输入,输出和程序代码)?

5 个答案:

答案 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 Ghoshanswer为您提供了基本问题。但是,修复代码需要更多的工作。

我使用了以下脚本(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);
}