4

printf/fprintf/sprintf ファミリは、その書式指定子で幅フィールドをサポートしています。(非ワイド) char 配列引数の場合には疑問があります。

幅フィールドはバイトまたは文字を意味するはずですか?

char 配列が (たとえば) 生の UTF-8 文字列に対応している場合、(正しい事実上の) 動作は何ですか? (通常、ワイド文字型を使用する必要があることはわかっていますが、それは重要ではありません)

たとえば、

char s[] = "ni\xc3\xb1o";  // utf8 encoded "niño"
fprintf(f,"%5s",s);

その関数は、5 バイト (プレーンな C 文字) だけを出力しようとすることになっていますか (2 バイトがテキスト文字になる場合は、ミスアラインメントやその他の問題の責任を負います) ?

それとも、配列の「テキスト文字」の長さを計算しようとすることになっていますか? (現在のロケールに従ってデコードしますか?)(この例では、文字列に 4 つの Unicode 文字があることがわかるため、パディング用のスペースが追加されます)。

更新:私は答えに同意します.printfファミリがプレーンC文字とバイトを区別しないのは論理的です. 問題は、ロケールが以前に設定されていて、(今日最も使用されている) LANG/LC_CTYPE=en_US.UTF-8 がある場合、私の glibc がこの概念を完全に尊重していないように見えることです。

適例:

#include<stdio.h>
#include<locale.h>
main () {
        char * locale = setlocale(LC_ALL, ""); /* I have LC_CTYPE="en_US.UTF-8" */
        char s[] = {'n','i', 0xc3,0xb1,'o',0}; /* "niño" in utf8: 5 bytes, 4 unicode chars */
        printf("|%*s|\n",6,s); /* this should pad a blank - works ok*/
        printf("|%.*s|\n",4,s); /* this should eat a char - works ok */
        char s3[] = {'A',0xb1,'B',0}; /* this is not valid UTF8 */
        printf("|%s|\n",s3);     /* print raw chars - ok */
        printf("|%.*s|\n",15,s3);     /* panics (why???) */
}

そのため、非 POSIX-C ロケールが設定されている場合でも、printfは幅をカウントするための正しい概念を持っているようです: バイト (c プレーン文字) であり、ユニコード文字ではありません。それはいいです。ただし、彼のロケールでデコードできない char 配列を指定すると、静かにパニックが発生します (中止 - 最初の '|' の後に何も出力されません - エラーメッセージなし)... 幅をカウントする必要がある場合のみ。必要な/必要なときに、utf-8 から文字列をデコードしようとする理由がわかりません。これは glibc のバグですか?

glibc 2.11.1 (Fedora 12) でテスト済み (glibc 2.3.6 も)

注:端末の表示の問題とは関係ありません- od にパイプすることで出力を確認できます:これ$ ./a.out | od -t cx1が私の出力です:

0000000   |       n   i 303 261   o   |  \n   |   n   i 303 261   |  \n
         7c  20  6e  69  c3  b1  6f  7c  0a  7c  6e  69  c3  b1  7c  0a
0000020   |   A 261   B   |  \n   |
         7c  41  b1  42  7c  0a  7c

UPDATE 2 (2015 年 5 月) : この疑わしい動作は、新しいバージョンの glibc で修正されました (2.17 以降のようです)。それは私にとってはうまくいきglibc-2.17-21.fc19ます。

4

6 に答える 6

4

これにより、5 バイトが出力されます。しかも五文字。ISO C では、文字とバイトの間に区別はありません。バイトは必ずしも8 ビットではなく、代わりに char の幅として定義されます。

8 ビット値の ISO 用語はオクテットです。

あなたの「niño」文字列は、実際にはC環境に関して5文字幅です(もちろん、nullターミネータはありません)。端末に 4 つのシンボルしか表示されない場合、それはほぼ確実に端末の機能であり、C の出力関数ではありません。

C の実装が Unicode を扱えないと言っているのではありません。CHAR_BITS が 32 として定義されている場合、UTF-32 を非常に簡単に実行できます。UTF-8 は可変長エンコーディングなので難しいですが、ほとんどすべての問題を回避する方法があります :-)


アップデートによると、問題が発生しているようです。ただし、同じロケール設定を使用したセットアップでは、説明されている動作が見られません。私の場合、最後の 2 つのprintfステートメントで同じ出力が得られます。

セットアップが最初の出力の後に出力を停止しているだけの場合|(それが中止の意味だと思いますが、プログラム全体が中止されることを意味する場合、それははるかに深刻です)、GNUで問題を提起します(最初に特定のディストリビューションのバグ手順を試してください)。 )。最小限のテスト ケースを作成するなどの重要な作業はすべて完了しているため、ディストリビューションが完全に到達していない場合 (ほとんどの場合は到達していない場合) は、誰かが喜んでそれを最新バージョンに対して実行する必要があります。


余談ですが、od出力を確認することで何を意味するのかわかりません。私のシステムでは、次のようになります。

pax> ./qq | od -t cx1
0000000   |       n   i 303 261   o   |  \n   |   n   i 303 261   |  \n
         7c  20  6e  69  c3  b1  6f  7c  0a  7c  6e  69  c3  b1  7c  0a
0000020   |   A 261   B   |  \n   |   A 261   B   |  \n
         7c  41  b1  42  7c  0a  7c  41  b1  42  7c  0a
0000034

出力ストリームに UTF-8 が含まれていることがわかります。つまり、これを解釈する必要があるのは端末プログラムです。C/glibc は出力をまったく変更していないので、あなたが言おうとしていることを誤解しただけかもしれません。

出力にはその行の開始バーしかないと言っているかもしれませんが問題がないように見える私のものとは異なります)、C/glibc内で何か問題があるのではなく、何か問題があることを意味ます端末は静かに文字を削除します(正直なところ、端末は行全体または問題のある文字(つまり、 output )だけを削除することを期待しています-あなたが得ているという事実は、端末の問題を排除しているようです)。それを明確にしてください。 od|A|

于 2010-05-08T01:48:12.060 に答える
3

バイト (文字)。Unicode セマンティクスの組み込みサポートはありません。fputcが少なくとも 5 回呼び出されると想像できます。

于 2010-05-08T01:23:41.040 に答える
1

元の質問 (bytes または chars?) は、何人かの人々によって正しく答えられました: 仕様とglibc実装の両方によれば、 printf C 関数の幅 (または精度)はバイト(または同じものである単純な C 文字) をカウントします。 )。したがって、fprintf(f,"%5s",s)私の最初の例では、間違いなく「配列 s から少なくとも5 バイト(プレーン文字) を出力してみてください。十分でない場合は、空白で埋めてください」という意味です。

文字列 (私の例では、バイト長 5) が UTF8 でエンコードされたテキストを表しているかどうか、および実際に 4 つの「テキスト (ユニコード) 文字」が含まれているかどうかは問題ではありません。printf()には、内部的には 5 つの (プレーンな) C 文字があり、それが重要です。

わかりました、これは非常に明確に見えます。しかし、それは私の他の問題を説明していません。それなら、何かが欠けているに違いない。

glibc バグ トラッカーで検索すると、関連する (かなり古い) 問題がいくつか見つかりました。

http://sources.redhat.com/bugzilla/show_bug.cgi?id=6530

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=208308

http://sources.redhat.com/bugzilla/show_bug.cgi?id=649

最後のリンクからのこの引用は、ここで特に関連しています。

ISO C99 requires for %.*s to only write complete characters that fit below the
precision number of bytes.  If you are using say UTF-8 locale, but ISO-8859-1
characters as shown in the input file you provided, some of the strings are
not valid UTF-8 strings, therefore sprintf fails with -1 because of the
encoding error. That's not a bug in glibc.

それがバグであるかどうか (おそらく解釈または ISO 仕様自体) については議論の余地があります。しかし、今ではglibcが何をしているのかは明らかです。

問題のあるステートメントを思い出してください: printf("|%.*s|\n",15,s3). ここで、glibc は の長さがs315 より大きいかどうかを調べ、15 より大きい場合は切り詰める必要があります。この長さを計算するために、エンコーディングをいじる必要はまったくありません。しかし、切り詰める必要がある場合、glibcは注意を払うよう努めています: 最初の 15 バイトだけを保持すると、マルチバイト文字が半分に分割され、無効なテキスト出力が生成される可能性があります (それで問題ありませんが、 glibc は、興味深い ISO C99 解釈に固執しています)。したがって、残念ながら、環境ロケールを使用して char 配列をデコードし、実際の文字境界がどこにあるかを調べる必要があります。したがって、たとえば、LC_TYPE が UTF-8 であり、配列が有効な UTF-8 バイト シーケンスでない場合、配列は中止されます (それほど悪くはありません。printf-1 を返します。とにかく文字列の一部を出力するため、きれいに復元するのは難しいため、あまりうまくいきません)。

どうやらこの場合のみ、文字列に精度が指定され、切り捨ての可能性がある場合、glibcはいくつかの Unicode セマンティクスをプレーンな文字/バイト セマンティクスと混合する必要があります。かなり醜い、IMOですが、そうです。

更新: この動作は、無効な元のエンコーディングの場合だけでなく、切り捨て後の無効なコードにも関連することに注意してください。例えば:

char s[] = "ni\xc3\xb1o";  /* "niño" in UTF8: 5 bytes, 4 unicode chars */
printf("|%.3s|",s); /* would cut the double-byte UTF8 char in two */

これは、無効な UTF8 文字列の出力を拒否するため、フィールドを 3 バイトではなく 2 バイトに切り捨てます。

$ ./a.out
|ni|
$ ./a.out | od -t cx1
0000000   |   n   i   |  \n
        7c 6e 69 7c 0a

更新 (2015 年 5 月) この (IMO) 疑わしい動作は、新しいバージョンの glib で変更 (修正) されました。主な質問を参照してください。

于 2010-05-09T03:07:03.710 に答える
1

あなたが見つけたのは、glibc のバグです。残念ながら、これは開発者が修正を拒否する意図的なものです。説明については、ここを参照してください。

http://www.kernel.org/pub/linux/libs/uclibc/Glibc_vs_uClibc_Differences.txt

于 2010-09-20T03:29:19.350 に答える
0

移植可能にするには、 を使用して文字列を変換し、 を使用mbstowcsして出力しprintf( "%6ls", wchar_ptr )ます。

%lsPOSIXに従ったワイド文字列の指定子です。

「事実上の」標準はありません。通常、stdoutOS とロケールが UTF-8 ファイルとして扱うように構成されている場合は UTF-8 を受け入れると思いますがprintf、マルチバイト エンコーディングはそれらの用語で定義されていないため、無知であると予想します。

于 2010-05-08T23:16:19.120 に答える
0

wchar_t の長さが少なくとも 32 ビットであることも確認しない限り、mbstowcs を使用しないでください。そうしないと、UTF-8 のすべての欠点と UTF-32 のすべての欠点を持つ UTF-16 になってしまう可能性があります。

mbstowcs を避けると言っているのではなく、Windows プログラマーに使用させないでくださいと言っているだけです。

iconv を使用して UTF-32 に変換する方が簡単かもしれません。

于 2010-05-13T09:51:13.033 に答える