繁体   English   中英

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

[英]How multibyte string is converted to wide-character string in fxprintf.c in glibc?

当前, glibc的错误源中的逻辑是这样的:

如果stderr是定向的, dup()原样使用它,否则使用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);

格式字符串通过以下代码转换为宽字符形式,我不理解:

wfmt[i] = fmt[i];

此外,它使用isascii断言:

assert (isascii(fmt[i]));

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

#include <stdio.h>
#include <errno.h>
#include <wchar.h>
#include <locale.h>
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)并且语言环境消息都为多字节的事实,而无需使用这种神秘的转换就可以重写代码。

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

#include <stdio.h>
#include <wchar.h>
#include <unistd.h>
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 <wchar.h>
#include <stdio.h>
#include <unistd.h>
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)

它使用isascii断言。

还行吧。 您不应该调用此函数。 它是内部的glibc。 请注意名称前面的两个下划线。 从perror调用时,所讨论的参数是"%s%s%s\\n" ,它完全是ASCII。

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

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

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

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

我们可以在面向宽幅的stderr上使用dup()吗

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

这是我建议给glibc开发人员的补丁:

为什么? 什么不起作用?

  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开发人员发布有关此改进的问题?

    这取决于您,但是请不要在这里执行。 正常的做法是提交错误。 没有理由相信glibc开发人员会阅读SO问题,即使他们这样做,也有人必须将问题复制到错误中,并复制任何建议的补丁程序。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM