【发布时间】:2017-02-18 13:30:21
【问题描述】:
目前glibc source of 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);
格式字符串通过以下代码转换为宽字符形式,我看不懂:
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)
【问题讨论】:
-
请注意,
wchar_t与 UTF-8 中的字符串无关,因为它是一个 宽 字符,能够将大于char的编码空间表示为单一值。似乎假设perror()的参数是全ASCII,不知道为什么。 -
@unwind
wchar_t本身使用内部编码(glibc中的UCS-4),但也有编译器编码(UTF-8在我的例子),它用于多字节字符串常量。但是fxprintf.c中的代码以某种方式将格式字符串从 UTF-8 转换为 UCS-4(将其传递给__vfwprintf)。我完全不明白它是如何成功的。但最重要的是我想知道为什么会这样做。 -
嗯...有趣,这种区别很少使用。我同意,
for循环不可能进行任何类型的转换,而不是“扔掉一些位”。
标签: c file-io io wchar-t widechar