web-dev-qa-db-ja.com

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、ファイルの入力、出力、プログラムコードを含む)として扱いますか?

12
Igor Liferenko
#define SIZE 10

バッファサイズ10は、UTF-8文字列を格納するには不十分ですпривет мир。より大きな値に変更してみてください。私のシステム(Ubuntu 12.04、gcc 4.8.1)では、20に変更すると完全に機能しました。

UTF-8は、文字ごとに1〜4バイトを使用するマルチバイトエンコーディングです。したがって、上記のバッファサイズとして40を使用する方が安全です。 1つのUnicode文字は何バイト必要ですか? で大きな議論がありますが、これは興味深いかもしれません。

10

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を使用していました。

7

これは他の答えの結果の帰結ですが、これを少し異なる角度から説明しようと思います。

以下は、Jonathan Lefflerのコードのバージョンで、3つのわずかな変更があります。(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は常に正確に1バイトですが、UTF-8の1文字は通常、すべての文字が実際にASCIIである場合を除いて、正確に1バイトではありません。 UTF-8の魅力の1つは、レガシーCコードがUTF-8について何も知らなくても動作し続けることですが、もちろん、1つの文字が1つのグリフであるという仮定は成り立ちません。 (たとえば、ご覧のとおり、「приветмир」のグリフпは2バイトにマップされます。したがって、2つのchars-"\320\277"。)

これは明らかに理想的ではありませんが、コードがグリフのセマンティクスを特に気にしない場合は、UTF-8を「単なるバイト」としてcanで処理できることを示しています。もしそうなら、概説したようにwchar_tに切り替える方が良いでしょう。ここ: http://www.gnu.org/software/libc/manual/html_node/Extended-Char-Intro.html

ただし、標準の期待値がUTF-8の場合、標準のwchar_tは理想的ではありません。例参照 GNU libunistring documentation それほど邪魔にならない代替手段と、少しの背景。これにより、charuint8_tに、さまざまなstr*関数をu8_str*に置き換えて実行できるようになります。 1つのグリフが1バイトに等しいという前提に対処する必要がありますが、これはサンプルプログラムのマイナーな技術になります。適応は http://ideone.com/p0VfXq で利用可能です(ただし、残念ながらライブラリは http://ideone.com/ では利用できないため、実証できません)そこ)。

4
tripleee

おそらくあなたのtest.cファイルはUTF-8形式で保存されていないため、「приветмир」文字列はASCII-および比較に失敗しました。ソースファイルのテキストエンコーディングを変更して、再試行してください。

0
i486