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


我正在尝试从使用创建的流中读取宽字符 fmemopen 用一个 char *

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

wchar_t c = getwc(f);

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

我知道这是因为打开了流 fmemopen因为打电话 getwc 在正常打开的流上正常工作。

是否有广泛的char版本 fmemopen,还是有其他方法来解决这个问题?


9980
2017-08-10 22:12


起源

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


答案:


第二行应该是读 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 立即归还。

解释 fwide(f, 0) 来自linux手册页:

概要

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

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

一旦流具有方向,它就无法更改并持续直到流关闭。

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

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


7
2017-08-13 15:14



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


  1. 你的第二行没有使用正确数量的参数,是吗? 修正

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

  2. 的glibc的 fmemopen 才不是 (全) 支持广角AFAIK。还有 open_wmemstream(),它支持宽字符,但仅用于写作。

  3. _UNICODE 界定?看到 wchar_t阅读
    ,您是否将语言环境设置为支持Unicode的编码,例如, setlocale(LC_ALL, "en_US.UTF-8");?看到 这里

  4. 考虑使用临时的 文件。考虑使用 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
2017-08-13 06:28





  1. 您可以使用 getwc() 仅适用于无定向或面向广播的流。从 getwc()  手册页流不具有方向,或者是宽方向的。

  2. 如果流已经具有方向,则无法更改流方向。从 fwide()  手册页在已具有方向的流上调用此函数无法更改它。

  3. 用glibc打开流 fmemopen() 具有字节方向,因此不能以任何方式进行广泛定向。如上所述 这里  uClibc的 具有 fmemopen() 例程没有这个限制。

结论:您需要使用 uClibc的 或另一个图书馆或自己做 fmemopen()


1
2017-08-17 08:36