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

Igo*_*nko 16 c utf-8

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

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

创建输入文件:

echo -n '?????? ???' > /tmp/????
Run Code Online (Sandbox Code Playgroud)

这是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);
}
Run Code Online (Sandbox Code Playgroud)

检查结果:

./test; grep -q ' ?????? ???' /tmp/????? && echo OK
Run Code Online (Sandbox Code Playgroud)

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

Sid*_*osh 10

#define SIZE 10
Run Code Online (Sandbox Code Playgroud)

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

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

  • 除了将SIZE更改为20之外,您还需要做更多工作,但这是此过程中的关键步骤. (2认同)

Jon*_*ler 7

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

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

echo -n '?????? ???' > ????
make utf8-test
./utf8-test
grep -q '?????? ???' ????? && echo OK
Run Code Online (Sandbox Code Playgroud)

我打电话给你的程序,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);
}
Run Code Online (Sandbox Code Playgroud)

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

$ 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
$
Run Code Online (Sandbox Code Playgroud)

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

  • 哦,我忘了提及我的编译器反对该空间。(它对你有什么作用——消息中提到了“gnu_printf”?空格标志与数字转换相关,但与字符串转换无关)。如果我想在开头有一个空格,那么它就在“%”之前。我没有忘记11;我将“11”更改为“.*”,并将正确的字节数作为“int”参数传递给“printf()”。您没有使用宽字符;你使用的是字节字符串,UTF-8字符的宽度是可变的,尽管除了空格之外,你的UTF-8字符都是2个字节长。你必须使用字节。 (2认同)

tri*_*eee 7

这更像是其他答案的推论,但我会尝试从稍微不同的角度解释这一点。

这是 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);
}
Run Code Online (Sandbox Code Playgroud)

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

为了使这个答案自成一体,我将重复其他人已经说过的内容。传统char的总是一个字节,但 UTF-8 中的一个字符通常不完全是一个字节,除非您的所有字符实际上都是 ASCII。UTF-8 的吸引力之一是遗留 C 代码不需要了解任何关于 UTF-8 的信息就可以继续工作,但当然,一个字符是一个字形的假设是不成立的。(例如,如您所见,“?????? ???”中的字形?映射到两个字节——因此,两个chars -- "\320\277"。)

这显然不太理想,但表明如果您的代码不是特别关心字形语义,您可以将 UTF-8 视为“仅字节”。如果你这样做,你最好切换到wchar_t如这里概述的那样:http : //www.gnu.org/software/libc/manual/html_node/Extended-Char-Intro.html

但是,wchar_t当标准期望是 UTF-8 时,该标准并不理想。请参阅例如GNU libunistring 文档以了解侵入性较小的替代方案和一些背景知识。有了这一点,你应该能够取代charuint8_t和各种str*与功能u8_str*置换和完成。一个字形等于一个字节的假设仍然需要解决,但这在您的示例程序中成为一个次要的技术问题。http://ideone.com/p0VfXq提供了改编版本(尽管遗憾的是该库在http://ideone.com/上不可用,因此无法在那里演示)。