从使用 fmemopen 创建的流中读取宽字符

Posted

技术标签:

【中文标题】从使用 fmemopen 创建的流中读取宽字符【英文标题】:Read wide char from a stream created with fmemopen 【发布时间】:2018-01-19 07:43:22 【问题描述】:

我正在尝试从使用fmemopenchar * 创建的流中读取宽字符。

char *s = "foo bar foo";
FILE *f = fmemopen(s,strlen(s),"r");

wchar_t c = getwc(f);

getwc 抛出分段错误,我使用 GDB 进行了检查。

我知道这是因为使用fmemopen 打开流,因为在打开的流上调用getwc 通常可以正常工作。

是否有fmemopen 的宽字符版本,或者是否有其他方法可以解决此问题?

【问题讨论】:

请发布正确的 MCVE,fmemopen 调用无效 @AnttiHaapala 哦,哎呀,我错过了那部分。对不起。 @MDXF:从示例中,人们可能会觉得iconv_open()iconv() 可能是解决潜在问题的更好方法。 @MDXF:事实上,至少 GNU libc 在后台使用了iconv——它为已经转换的数据使用了一个单独的缓冲区。设置区域设置(全部或LC_CTYPE)后,您可以使用nl_langinfo(CODESET) 以您可以提供给iconv_open() 的形式获取字符集。虽然这不是 ISO C,但它是 POSIX.1,并且应该非常便携。 (因为甚至还有 GNU libiconv,所以这种方法应该相对容易移植到使用标准 C 的任何系统,包括 Windows。) 【参考方案1】:

第二行应为FILE *f = fmemopen(s, strlen(s), "r");。正如所发布的,fmemopen 具有未定义的行为,可能会返回 NULL,这会导致 getwc() 崩溃。

更改 fmemopen() 行并添加对 NULL 的检查修复了崩溃,但不符合 OP 的目标。

使用fmemopen() 打开的流似乎不支持宽方向,至少对于 GNU C 库。请注意,fmemopen 未在 C 标准中定义,而是在 POSIX.1-2008 中定义,并且在许多系统(如 OS/X)上不可用。

这是您的程序的更正和扩展版本:

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>

int main(void) 
    const char *s = "foo bar foo";
    FILE *f = fmemopen((void *)s, strlen(s), "r");
    wchar_t c;

    if (f == NULL) 
        printf("fmemopen failed: %s\n", strerror(errno));
        return 1;
    
    printf("default wide orientation: %d\n", fwide(f, 0));
    printf("selected wide orientation: %d\n", fwide(f, 1));
    while ((c = getwc(f)) != WEOF) 
        printf("read %lc (%d 0x%x)\n", c, c, c);
    
    return 0;

在 linux 上运行:

default wide orientation: -1
selected wide orientation: -1

没有输出,WEOF 立即返回。

linux 手册页中对fwide(f, 0) 的解释:

概要

#include <wchar.h>
int fwide(FILE *stream, int mode);

mode 为零时,fwide() 函数确定stream 的当前方向。如果stream 面向宽字符,即允许宽字符I/O 但不允许字符I/O,则返回正值。如果 stream 是面向字节的,即允许 char I/O 但不允许宽字符 I/O,则返回负值。如果stream 还没有方向,则返回零;在这种情况下,下一个 I/O 操作可能会改变方向(如果是 char I/O 操作则变为面向字节,如果是宽字符 I/O 操作则变为面向宽字符)。

一旦一个流有一个方向,它就不能被改变并且一直持续到流被关闭。

mode 为非零时,fwide() 函数首先尝试设置stream 的方向(如果模式大于0,则为宽字符方向,如果mode 小于0,则为字节方向)。然后它返回一个表示当前方向的值,如上所述。

fmemopen() 返回的流是面向字节的,不能改成面向宽字符的。

【讨论】:

所以没有办法fmemopen 一个字符串并从中读取宽字符? @MDXF:确实,我担心 Glibc 实现不支持宽方向。 如果方向已定义,fwide 不会更改方向。所以第二次调用fwide 的效果为零。您可以尝试这种方式打开流fmemopen(s, strlen(s), "r,ccs=UNICODE"); @VadimHryshkevich:第一次调用fwide() 是查询当前方向。它返回面向字节的。第二次调用尝试将方向更改为宽,并且确实失败了。您提出的方法很有趣。它是非标准的,但在某些系统上是经典的。 @chqrlie:这来自fwide() 手册页:“一旦流具有方向,它就无法更改并持续存在,直到流关闭。”所以第二次调用fwide() 的效果为零。附: 1.我在我的linux发行版上查看了fwide()的源代码:如果流没有零方向fwide()就退出。 2.来自fmemopen()的源代码:在这个函数中没有机会以任何方式改变流的方向。 3. 可以使用函数freopen(NULL,"r",fmemopen(...)) 来获取没有方向的流,但我试过这个没有运气。【参考方案2】:

    您的第二行没有使用正确数量的参数,是吗? 已更正

    FILE *fmemopen(void *buf, size_t size, const char *mode);

    glibc 的 fmemopen(完全) 支持宽字符 AFAIK。还有open_wmemstream(),它支持宽字符,但只是为了写。

    _UNICODE 是否已定义?请参阅wchar_t reading。另外,您是否将语言环境设置为支持 Unicode 的编码,例如 setlocale(LC_ALL, "en_US.UTF-8");?见here。

    考虑使用临时的file。考虑改用fgetwc / 4。

我已经更改了我的代码并采用了来自 @chqrlie 的代码,因为它更接近 OP 代码但添加了语言环境,否则它无法为扩展/Unicode 字符生成正确的输出。

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <stdlib.h>
#include <locale.h>

int main(void)

    setlocale(LC_ALL, "en_US.UTF-8");
    const char *s = "foo $€ bar foo";
    FILE *f = fmemopen((void *)s, strlen(s), "r");
    wchar_t c;

    if (f == NULL) 
        printf("fmemopen failed: %s\n", strerror(errno));
        return 1;
    
    printf("default wide orientation: %d\n", fwide(f, 0));
    printf("selected wide orientation: %d\n", fwide(f, 1));
    while ((c = getwc(f)) != WEOF) 
        printf("read %lc (%d 0x%x)\n", c, c, c);
    
    return 0;

【讨论】:

【参考方案3】:

    您只能在无方向或面向宽的流上使用getwc()。来自getwc()man page:流应该还没有方向,或者是宽方向的。

    如果流已具有方向,则无法更改流方向。来自fwide()man page:在已经有方向的流上调用这个函数不能改变它。

    使用 glibc 的fmemopen() 打开的流具有字节方向,因此不能以任何方式面向宽。如所述here uClibc 具有fmemopen() 例程,没有此限制。

结论:你需要使用uClibc或者其他库或者自己制作fmemopen()

【讨论】:

以上是关于从使用 fmemopen 创建的流中读取宽字符的主要内容,如果未能解决你的问题,请参考以下文章

从低内存消耗的流中提取二进制值

宽字节注入

如何从我的流中获取数据并使用它?

javascript:从 mp3 流中读取 id3 标签

C语言 文件读写 fputs 函数

如何从使用 Futures/async/await 的流中正确生成?