C中的宽字符输入/输出是否始终读取/写入正确的(系统默认)编码?

Qua*_*dom 8 c linux posix wchar-t

我主要对类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到处都是理想的.

R..*_*R.. 9

管理宽字符stdio函数行为及其与语言环境关系的相关文本来自POSIX XSH 2.5.2流方向和编码规则:

http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_05_02

基本上,宽字符stdio函数总是写入在流变为面向广播时生效的编码(根据LC_CTYPE区域设置类别)FILE; 这意味着第一次调用宽stdio函数,或者fwide用于将方向设置为宽.因此,只要LC_CTYPE在开始使用流时,正确的区域设置实际匹配所需的"系统"编码(例如UTF-8),一切都应该没问题.

但是,您不应忽视的一个重要考虑因素是您不能在同一个FILE流上混合使用字节和广泛的操作.不遵守此规则不是可报告的错误; 它只会导致未定义的行为.由于库代码一个很好的协议假定stderr是面向字节的(有的甚至使大约相同的假设stdout),我强烈建议曾经使用的标准流广为导向的功能.如果这样做,您需要非常小心您使用的库函数.

真的,我根本无法想到使用面向广泛的功能.fprintf完全能够FILE使用说明%ls符将宽字符串发送到面向字节的流.


tep*_*pic 8

只要正确设置了语言环境,使用宽字符函数在使用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
Run Code Online (Sandbox Code Playgroud)

如果你不小心使用多字节字符串上的标准函数(特别是字符函数),事情就会开始破坏,例如:

char *txt = "£??";
printf("The string %s has %zu characters\n", txt, strlen(txt));

$ gcc -o nloc nloc.c && ./nloc
The string £?? has 7 characters
Run Code Online (Sandbox Code Playgroud)

字符串仍然在这里正确打印,因为它基本上只是一个字节流,并且由于系统期望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
Run Code Online (Sandbox Code Playgroud)

在流上使用宽字符函数后,它将设置为宽方向.如果您以后想要使用标准字节i/o函数,则需要首先重新打开流.这可能是建议不要使用它的原因stdout.但是,如果你只使用在宽字符函数stdinstdout(包括您链接到任何代码),你不会有任何问题.