2989


UTF-8でのstrlen関数のようなものはありますか?
"こんにちは"という文字列の長さをstrlenで測ると5ではなく、15という数値が返ってきてしまいます。

[code]

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char *s = "こんにちは";
        printf("%lu\n", strlen(s)); // 15と表示される
    
        return 0;
    }
    
[/code]

どうすれば期待通りの5を得ることができるのでしょうか?


CC BY-SA 3.0
2015-01-03T12:52:15.193
2988
2015-01-06T11:06:47.790
2015-01-03T13:00:05.970
208
5246
question
c unicode
C言語でのUTF-8文字列の正確な長さを取得する


<https://stackoverflow.com/questions/5117393/utf-8-strings-length-in-linux-c>
より、  
`mbstowcs()` が使えるそうです。  
ロケールは正しく設定する必要があるようですが。

その他にもリンク先には自分で実装する場合の例もありますので、参考にされてはいかがでしょう。


CC BY-SA 3.0
2015-01-03T13:03:35.707
2989
2015-01-03T13:03:35.707
2017-05-23T12:38:56.083
-1
208
2988
answer


[mrubyのソースコード](https://github.com/mruby/mruby/blob/master/mrbgems/mruby-
string-utf8/src/string.c)を参考にした実装例です。

[code]

    #include <stdio.h>
    
    static const char utf8len_codepage[256] =
    {
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
      3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,
    };
    
    int utf8len(const char* p)
    {
        int len;
        int i;
    
        if (*p == 0) return 1;
        len = utf8len_codepage[(const unsigned char)*p];
        for (i = 1; i < len; ++i) {
            if ((p[i] & 0xc0) != 0x80) return 1;
        }
        return len;
    }
    
    int utf8strlen(const char* p)
    {
        int len;
        for (len = 0; *p; ++len) {
            p += utf8len(p);
        }
        return len;
    }
    
    int main() {
        const char *s = "こんにちは";
        printf("%lu\n", utf8strlen(s));
    
        return 0;
    }
    
[/code]


CC BY-SA 3.0
2015-01-03T13:08:12.397
2990
2015-01-03T13:08:12.397
3639
2988
answer


以下の様にします。

[code]

    #include <stdio.h>
    #include <string.h>
    #include <wchar.h>
    
    int main() {
        wchar_t *s = L"こんにちは";
        printf("%lu\n", wcslen(s));//%lu の代わりに %zuを使用する方が良いです
    
        return 0;
    }
    
[/code]

* * *

Visual studio を使用している場合、シフトコードそのまま（マルチバイト文字列）で以下の様にできます。

[code]

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char *s = "こんにちは";
        printf("%lu\n", _mbslen(s));
    
        return 0;
    }
    
[/code]


CC BY-SA 3.0
2015-01-03T13:12:14.447
2991
2015-01-03T13:12:14.447
5044
2988
answer


Unicode 文字列を扱うための `wchar_t`(wide char)という型が用意されています。

[code]

    #include <stdio.h>
    #include <string.h>
    #include <wchar.h>
    
    int main() {
      wchar_t *s = L"こんにちは";
      printf("%d\n", wcslen(s));
      return 0;
    }
    
[/code]

Wide char string の長さを得る関数は `wcslen()` です。また、文字列には `L`(Long) プレフィックスを付けて wide
char であることを示す必要があります。

まぁ、あまり使いやすいとは言えませんので、他のライブラリを使われた方が良いと思います。


CC BY-SA 3.0
2015-01-03T13:15:02.303
2992
2015-01-03T13:15:02.303
2988
answer


（今日びのことなので、 Unicode を使用する環境だけを対象とします。）

Unicode では、日本語の文字もそれぞれ Unicode 文字として扱われます。各文字で、「最初の２ビットが `11` で始まり、最初の 2ビットが
`10` であるバイトが続く限りそれは
文字の一部である」というルールがあり、１文字は可変長となります。各バイトの上位2ビットを見ながら数えていかなければなりません。

Unicode を数えるときは、以下の `strlen_utf8()` のような形になります。

※ strlen_utf8() は、”[UTF-8 & Unicode, what's with 0xC0 and
0x80?](https://stackoverflow.com/questions/3911536/utf-8-unicode-whats-
with-0xc0-and-0x80/3911566#3911566)" から持ってきています。

[code]

    #include <stdio.h>
    #include <string.h>
    
    long strlen_utf8(char *s)
    {
      long i = 0, j = 0;
      while (s[i])
      {
        if ((s[i] & 0xc0) != 0x80) j++;
        i++; }
      return j;
    }
    
    int main() {
        char *s = "こんにちは";
    
        printf("string  : %s\n", s);
    
        size_t i = 0;
        printf("bytes~16: ");
        while (i < 16) printf("%02X ",((const unsigned char *)s)[i++] & 0xff);
        printf("\n");
    
        printf("strlen  : %lu\n", strlen(s)); // 15
        printf("strlen_utf8: %lu\n", strlen_utf8(s)); // 5
    
        return 0;
    }
    
[/code]

出力:

[code]

    string  : こんにちは
    bytes~16: E3 81 93 E3 82 93 E3 81 AB E3 81 A1 E3 81 AF 00
    strlen  : 15
    strlen_utf8: 5
    
[/code]

今回、文字列は、

[code]

    E3 81 93 E3 82 93 E3 81 AB E3 81 A1 E3 81 AF 00
    
[/code]

> **こ** `e3` `81` `93`  
>  **ん** `e3` `82` `93`  
>  **に** `e3` `81` `ab`  
>  **ち** `e3` `81` `a1`  
>  **は** `e3` `81` `af`  
>  0x0 `00`

というデータとなっていました。これに対して、`strlen` 関数は、 「0x0 が発見されるまで1バイトづつ数えていく」動作で、Unicode
の５文字を、15とカウントしました。


CC BY-SA 3.0
2015-01-03T14:33:41.097
2995
2015-01-03T14:33:41.097
2017-05-23T12:38:56.083
-1
4978
2988
answer


苗字や地名の異体字や国旗、色違いの絵文字など、複数のコードポイントで構成される書記素クラスターを考慮する必要がある場合、ICU の
BreakIterator を使います。書記素クラスターに対応していない文字列関数を使うと、知らないうちに書記素クラスターを壊してしまうおそれがあります。

Twitter
のようにコードポイント単位で短い文字数制限をかける場合、ヒンドゥー語やチベット語など、結合文字を多用するアジア諸国の言語のユーザーにとって、使い勝手がわるくなる可能性があることも配慮する必要があります。

漢字の異体字をあらわすには基底字の次に異体字セレクターや Standardized Variant を続けます。葛飾区の「かつ」は <U+845B
U+E0101> であらわすことができます。国旗は2文字の国コードを該当するコードポイントであらわします。日本の国コードである JP は <U+1F1EF
U+1F1F5> になります。

BreakIterator のコードの例は次のようになります。

[code]

    #include <stdio.h>
    #include <unicode/ubrk.h>
    
    int len(const char *);
    
    int main(void)
    {
        const char* str = "葛\U000E0101飾区\U0001F1EF\U0001F1F5";
        printf("%s\n%d\n", str, len(str));
    
        return 0;
    }
    
    int len(const char *str)
    {
        UText *ut;
        UBreakIterator *bi;
        UErrorCode status = U_ZERO_ERROR;
        int len = 0;
    
        ut = utext_openUTF8(NULL, str, -1, &status);
        bi = ubrk_open(UBRK_CHARACTER, "ja_JP", NULL, 0, &status);
        ubrk_setUText(bi, ut, &status);
    
        while (ubrk_next(bi) != UBRK_DONE) {
            ++len;
        }
    
        utext_close(ut);
        ubrk_close(bi);
    
        return len;
    }
    
[/code]

書記素クラスターのサイズの上限はないので、バッファーオーバーフローに気をつける必要があります。 たとえば、「葛」の後ろに U+E0101
が1000個続いても1つの書記素クラスターとしてカウントされます。 もし書記素クラスターの上限値を設ける必要があれば、Unicode の [Stream-
Safe Text
Format](http://www.unicode.org/reports/tr15/#Stream_Safe_Text_Format)
をご参照ください。 基準値の例として128バイトもしくはコードポイント単位で32個前後という数値が挙げられています。

コードポイント単位で文字数を求めるには UText (utext.h) もしくは UnicodeString (ustring.h) の
u_countChar32 を使います。次のコードは UText の例です。

[code]

    #include <stdio.h>
    #include <unicode/utext.h>
    
    int len(const char*);
    
    int main(void)
    {
        // U+20BB7 U+91CE U+5BB6
        const char* str = "野家"; 
        printf("%d\n", len(str));
    
        return 0;
    }
    
    int len(const char* str)
    {
        UText *ut;
        UErrorCode status = U_ZERO_ERROR;
        UChar32 c;
        int len = 0;
    
        ut = utext_openUTF8(NULL, str, -1, &status);
    
        for (c = utext_next32From(ut, 0); c > -1; c = utext_next32(ut)) {
            ++len;
        }
    
        return len;
    }
    
[/code]

自分で書記素クラスターの境界を判定するライブラリを実装する場合、それぞれのコードポイントから Unicode プロパティを判定するために
[GraphemeBreakProperty.txt](http://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakProperty.txt)
に記載される対応表が必要になります。境界のルールは UAX#29 の [3.1.1 Grapheme Cluster Boundary
Rules](http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules)
をご参照ください。この節では、隣接する文字の Unicode プロパティの組み合わせが並べられており、国旗は GB8 (Regional_Indicator
× Regional_Indicator)、基底字と異体字セレクターの組み合わせは GB9 ( × Extend)、通常の文字が隣接し合う場合は GB10
(Any ÷ Any) に該当します。テストケースのデータは
[GraphemeBreakTest.txt](http://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.txt)
をご参照ください。


CC BY-SA 3.0
2015-01-06T11:06:47.790
3190
2015-01-06T11:06:47.790
568
2988
answer