如何将多字节字符串转换为glibc中fxprintf.c中的宽字符串?

目前, glibc perror源程序中的逻辑是这样的:

如果stderr是面向的,那么按原样使用它,否则使用dup()它并在dup() ‘ed fd上使用perror()

如果stderr是面向广域的,则使用stdio-common / fxprintf.c中的以下逻辑:

 size_t len = strlen (fmt) + 1; wchar_t wfmt[len]; for (size_t i = 0; i < len; ++i) { assert (isascii (fmt[i])); wfmt[i] = fmt[i]; } res = __vfwprintf (fp, wfmt, ap); 

通过以下代码将格式字符串转换为宽字符forms,我不明白:

 wfmt[i] = fmt[i]; 

此外,它使用isascii断言:

 assert (isascii(fmt[i])); 

但格式字符串在宽字符程序中并不总是ascii,因为我们可能使用UTF-8格式字符串,它可以包含非7位值。 为什么在运行以下代码时没有断言警告(假设UTF-8语言环境和UTF-8编译器编码)?

 #include  #include  #include  #include  int main(void) { setlocale(LC_CTYPE, "en_US.UTF-8"); fwide(stderr, 1); errno = EINVAL; perror("привет мир"); /* note, that the string is multibyte */ return 0; } 
 $ ./a.out привет мир: Invalid argument 

我们可以在面向广泛的stderr上使用dup()使其不是面向广泛的吗? 在这种情况下,可以在不使用这种神秘转换的情况下重写代码,考虑到perror()只接受多字节字符串(const char * s)和locale消息都是多字节的事实。

事实certificate我们可以。 以下代码演示了这一点:

 #include  #include  #include  int main(void) { fwide(stdout,1); FILE *fp; int fd = -1; if ((fd = fileno (stdout)) == -1) return 1; if ((fd = dup (fd)) == -1) return 1; if ((fp = fdopen (fd, "w+")) == NULL) return 1; wprintf(L"stdout: %d, dup: %d\n", fwide(stdout, 0), fwide(fp, 0)); return 0; } 
 $ ./a.out stdout: 1, dup: 0 

顺便说一句,是否值得向glibc开发人员提出有关此改进的问题?


注意

使用dup()在缓冲方面受到限制。 我想知道在glibc中是否在perror()的实现中考虑它。 以下示例演示了此问题。 输出不按写入流的顺序进行,而是按照写入缓冲区中数据的顺序进行。 注意,输出中的值的顺序与程序中的顺序不同,因为fprintf的输出首先被注销(因为“\ n”),并且当程序退出时fwprintf的输出被注销。

 #include  #include  #include  int main(void) { wint_t wc = L'b'; fwprintf(stdout, L"%lc", wc); /* --- */ FILE *fp; int fd = -1; if ((fd = fileno (stdout)) == -1) return 1; if ((fd = dup (fd)) == -1) return 1; if ((fp = fdopen (fd, "w+")) == NULL) return 1; char c = 'h'; fprintf(fp, "%c\n", c); return 0; } 
 $ ./a.out h b 

但是如果我们在fwprintf中使用\n ,则输出与程序中的输出相同:

 $ ./a.out b h 

perror()设法逃脱了,因为在GNU中,libc stderr是无缓冲的。 但它会在stderr手动设置为缓冲模式的程序中安全地工作吗?


这是我向glibc开发人员提出的补丁:

 diff -urN glibc-2.24.orig/stdio-common/perror.c glibc-2.24/stdio-common/perror.c --- glibc-2.24.orig/stdio-common/perror.c 2016-08-02 09:01:36.000000000 +0700 +++ glibc-2.24/stdio-common/perror.c 2016-10-10 16:46:03.814756394 +0700 @@ -36,7 +36,7 @@ errstring = __strerror_r (errnum, buf, sizeof buf); - (void) __fxprintf (fp, "%s%s%s\n", s, colon, errstring); + (void) _IO_fprintf (fp, "%s%s%s\n", s, colon, errstring); } @@ -55,7 +55,7 @@ of the stream. What is supposed to happen when the stream isn't oriented yet? In this case we'll create a new stream which is using the same underlying file descriptor. */ - if (__builtin_expect (_IO_fwide (stderr, 0) != 0, 1) + if (__builtin_expect (_IO_fwide (stderr, 0) < 0, 1) || (fd = __fileno (stderr)) == -1 || (fd = __dup (fd)) == -1 || (fp = fdopen (fd, "w+")) == NULL) 

注意:在这篇文章中找到具体问题并不容易; 总的来说,这篇文章似乎是试图参与讨论glibc的实现细节,在我看来,它会更好地针对专门用于开发该库的论坛,例如libc-alpha邮件列表 。 (或者参见https://www.gnu.org/software/libc/development.html获取其他选项。)这种讨论与StackOverflow,恕我直言并不是很好的匹配。 尽管如此,我试图回答我能找到的问题。

  1. wfmt[i] = fmt[i]; 从多字节转换为宽字符?

    实际上,代码是:

     assert(isascii(fmt[i])); wfmt[i] = fmt[i]; 

    这是基于ascii字符的数值与wchar_t相同的事实。 严格来说,情况并非如此。 C标准规定:

    如果实现没有定义__STDC_MB_MIGHT_NEQ_WC__则当用作整数字符常量中的单个字符时,基本字符集的每个成员应具有等于其值的代码值。 (§7.19/ 2)

    (gcc没有定义该符号。)

    但是,这仅适用于基本集中的字符,而不适用于isascii识别的所有字符。 基本字符集包含91个可打印的ascii字符以及空格,换行符,水平制表符,垂直制表符和换页符。 因此理论上可能无法正确转换其中一个剩余控制字符。 但是,对__fxprintf的调用中使用的实际格式字符串仅包含基本字符集中的字符,因此在实践中,这种迂腐的细节并不重要。

  2. 为什么在执行perror("привет мир");时没有断言警告perror("привет мир");

    因为只转换格式字符串,格式字符串( "%s%s%s\n" )仅包含ascii字符。 由于格式字符串包含%s (而不是%ls ),因此在窄字符和宽字符方向中,参数应该是char* (而不是wchar_t* )。

  3. 我们可以在面向广泛的stderr上使用dup()使其不是面向广泛的吗?

    那不是一个好主意。 首先,如果流具有方向,则它也可能具有非空的内部缓冲区。 由于该缓冲区是stdio库的一部分而不是底层Posix fd的一部分,因此它不会与重复的fd共享。 因此,perror打印的消息可能会在某些现有输出的中间进行插值。 另外,多字节编码可能具有移位状态,并且输出流当前不处于初始移位状态。 在这种情况下,输出ascii序列可能会导致输出乱码。

    在实际实现中,只在没有方向的流上执行dup; 这些流从来没有任何针对它们的输出,因此它们肯定仍然处于初始移位状态,并且具有空缓冲区(如果流被缓冲)。

  4. 是否值得向glibc开发人员提出有关此改进的问题?

    这取决于你,但不要在这里做。 这样做的正常方法是提交bug。 没有理由相信glibc开发人员阅读SO问题,即使他们这样做,也有人必须将问题复制到bug,并复制任何提议的补丁。

它使用isascii断言。

还行吧。 你不应该调用这个函数。 这是一个glibc内部。 请注意名称前面的两个下划线。 从perror调用时,有问题的参数是"%s%s%s\n" ,它完全是ASCII。

但格式字符串在宽字符程序中并不总是ascii,因为我们可能使用UTF-8

首先,UTF-8与宽字符无关。 其次,格式字符串始终是ASCII,因为该函数仅由知道它们正在做什么的其他glibc函数调用。

perror(“приветмир”);

这不是格式字符串,这是与实际格式字符串中的一个%s对应的参数之一。

我们可以在面向广泛的stderr上使用dup()

您不能在FILE*上使用dup ,它在没有方向的POSIX文件描述符上运行。

这是我向glibc开发人员提出的补丁:

为什么? 什么不起作用?