C 中的宽字符输入/输出是不是总是从正确的(系统默认)编码读取/写入?
Posted
技术标签:
【中文标题】C 中的宽字符输入/输出是不是总是从正确的(系统默认)编码读取/写入?【英文标题】:Does wide character input/output in C always read from / write to the correct (system default) encoding?C 中的宽字符输入/输出是否总是从正确的(系统默认)编码读取/写入? 【发布时间】:2013-03-05 12:57:44 【问题描述】:我主要对类 Unix 系统(例如可移植 POSIX)感兴趣,因为 Windows 似乎对宽字符做了一些奇怪的事情。
读取和写入宽字符函数(如getwchar()
和putwchar()
)始终“做正确的事”,例如从 utf-8 读取并在设置区域设置时写入 utf-8,或者我是否必须手动调用wcrtomb()
并使用例如打印字符串fputs()
?在我的系统(openSUSE 12.3)上,$LANG
设置为en_GB.UTF-8
,它们似乎做了正确的事情(检查输出我看到看起来像 UTF-8,即使字符串是使用 wchar_t 存储并使用宽字符编写的函数)。
但是我不确定这是否得到保证。例如cprogramming.com 声明:
[宽字符] 不应用于输出,因为伪零 字节和其他具有常见含义的低 ASCII 字符(例如 '/' 和 '\n') 可能会散布在整个数据中。
这似乎表明输出宽字符(大概使用宽字符输出函数)会造成严重破坏。
由于 C 标准似乎根本没有提到编码,我真的不知道在使用 wchar_t 时谁/何时/如何应用编码。所以我的问题基本上是,当我的应用程序不需要知道所使用的编码时,如果只读取、写入和使用宽字符是正确的做法。我只需要字符串长度和控制台宽度 (wcswidth()
),所以对我来说,在处理文本时到处使用 wchar_t 似乎很理想。
【问题讨论】:
【参考方案1】:不要将fputs
与 ASCII 以外的任何内容一起使用。
如果你想写下来让我们说 UTF8,然后使用一个函数返回 utf8 字符串使用的实际大小并使用 fwrite 写入好的字节数,而不用担心字符串内部的恶性 '\0
' .
【讨论】:
欢迎来到 Stack Overflow。fputs()
输出一个字节串,直到第一个零字节。 UTF-8 仅包含一个零字节的字符值,即 U+0000(在 UTF-8 中编码为 '\0'
)。所以fputs()
不会错误处理以空字符结尾的 UTF-8 字符串。确实,UTF-8 的优点之一是即使如此,一个不知道 UTF-8 的天真的程序通常也能正确处理字符串。 (并非总是 — 有很多方法会造成麻烦;但通常...)此外,fputs()
适用于单字节代码集,例如 ISO 8859-1 或 8859-15(8859-2,...) .将其限制为 ASCII 是不合理的严格。
嗨,他没有编译为完整的 utf8。他在 ascii 编译源中使用 utf8 字符串。
而且 fputs 会失败,因为 utf8 不是一个字节编码的字符串。
他应该使用 wchar 和 fputws(const wchar_t *restrict, FILE *restrict);
fputs()
不会因为 UTF8 是多字节代码集而失败。事实上,UTF8 设计的目标之一是让不知道 UTF8 的幼稚程序仍然能够成功处理它。您断言fputs()
仅适用于 ASCII 是公然错误的,即使您的意思是“基于 ASCII 的单字节代码集,例如 8859-1”的慈善解释也是如此。请注意,UTF8 是一种多字节代码集(或字符编码),而不是使用宽字符的代码集;您不会使用宽字符函数处理 UTF8。 UTF16 和 UTF32 是 Unicode 的宽字符表示。【参考方案2】:
只要语言环境设置正确,使用宽字符函数在使用 UTF-8 的系统上处理 UTF-8 文件应该不会有任何问题。他们将能够正确解释事物,即他们会根据需要将字符视为 1-4 个字节(在输入和输出中)。您可以通过以下方式对其进行测试:
#include <stdio.h>
#include <locale.h>
#include <wchar.h>
int main()
setlocale(LC_CTYPE, "en_GB.UTF-8");
// setlocale(LC_CTYPE, ""); // to use environment variable instead
wchar_t *txt = L"£Δᗩ";
wprintf(L"The string %ls has %d characters\n", txt, wcslen(txt));
$ gcc -o loc loc.c && ./loc
The string £Δᗩ has 3 characters
如果你不小心在多字节字符串上使用标准函数(特别是字符函数),事情就会开始崩溃,例如等价物:
char *txt = "£Δᗩ";
printf("The string %s has %zu characters\n", txt, strlen(txt));
$ gcc -o nloc nloc.c && ./nloc
The string £Δᗩ has 7 characters
字符串在这里仍然可以正确打印,因为它本质上只是一个字节流,并且由于系统需要 UTF-8 序列,因此它们被完美地翻译了。当然strlen
报告的是字符串中的字节数,7(加上\0
),不知道一个字符和一个字节是不等价的。
在这方面,由于 ASCII 和 UTF-8 之间的兼容性,只要小心,您通常可以将 UTF-8 文件视为简单的多字节 C 字符串。
还有一定程度的灵活性。可以轻松地将标准 C 字符串(作为多字节字符串)转换为宽字符串:
char *stdtxt = "ASCII and UTF-8 €£¢";
wchar_t buf[100];
mbstowcs(buf, stdtxt, 20);
wprintf(L"%ls has %zu wide characters\n", buf, wcslen(buf));
Output:
ASCII and UTF-8 €£¢ has 19 wide characters
在流上使用宽字符函数后,它会设置为宽方向。如果以后要使用标准字节 i/o 函数,则需要先重新打开流。这可能就是为什么建议不要在stdout
上使用它的原因。但是,如果您只在 stdin
和 stdout
上使用宽字符函数(包括您链接到的任何代码),则不会有任何问题。
【讨论】:
'Break' 不太对。描述应该是“字符串占用7个字节”,这是准确的。它只包含 3 个字符也是正确的。这是多字节字符串(mbs*
函数)和宽字符串(wcs*
函数)之间的部分区别。但是,这是吹毛求疵的。你的核心答案很好。
@JonathanLeffler - 我只是在编辑以解决你写的内容。
@JonathanLeffler - 呵呵,没关系。我已经填了一点。
@teppic:谢谢你的例子,你的回答也很好。
使用strnlen
,而不是strlen
。【参考方案3】:
控制宽字符 stdio 函数的行为及其与语言环境的关系的相关文本来自 POSIX XSH 2.5.2 Stream Orientation and Encoding Rules:
http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_05_02
基本上,当FILE
流变为面向宽时,宽字符 stdio 函数总是写入有效的编码(根据 LC_CTYPE
语言环境类别);这意味着第一次在其上调用宽 stdio 函数,或者使用fwide
将方向设置为宽。因此,当您开始使用流时,只要正确的 LC_CTYPE
区域设置实际上与所需的“系统”编码(例如 UTF-8)匹配,一切都应该没问题。
但是,您不应忽略的一个重要注意事项是,您不得在同一 FILE
流上混合使用字节和宽向操作。不遵守此规则不是可报告的错误;它只会导致未定义的行为。由于大量库代码假定stderr
是面向字节的(有些甚至对stdout
做出相同的假设),我强烈反对永远在标准流上使用面向宽的函数。如果这样做,则需要非常小心使用哪些库函数。
真的,我想不出任何理由使用面向广泛的函数。 fprintf
完全能够使用 %ls
说明符将宽字符串发送到面向字节的 FILE
流。
【讨论】:
我假设使用 putwchar(wc) 比使用 printf("%lc", wc) 产生更好的性能,但对于我目前的使用,性能差异可能并不重要。但为了清楚起见,只有在库函数实际写入标准输出时,将标准输出设置为宽方向才会有问题,对吗? @Quantumboredom:是的。stdout
开始时没有方向,但是一旦你使用宽字符函数编写,它就会被设置为宽,你不能再在它上面使用字节函数(stderr
不受影响)。我想不出任何标准库函数会使用stdout
,但外部库可能会。
@teppic:好的,在我的应用程序中,任何其他写入标准输出的东西无论如何都是一个错误,我测量到使用 printf("%lc", wc) 与 putwchar(wc) 时性能下降到一半所以我想我会在标准输出上坚持广泛的输出。顺便感谢您在回答中链接到相关标准:-)
@Quantumboredom - 那是 R.. :) 我用你的问题更新了我的答案以供完成。
@teppic:啊,我没注意到。谢谢你们两个:-)以上是关于C 中的宽字符输入/输出是不是总是从正确的(系统默认)编码读取/写入?的主要内容,如果未能解决你的问题,请参考以下文章