从使用 fmemopen 创建的流中读取宽字符
Posted
技术标签:
【中文标题】从使用 fmemopen 创建的流中读取宽字符【英文标题】:Read wide char from a stream created with fmemopen 【发布时间】:2018-01-19 07:43:22 【问题描述】:我正在尝试从使用fmemopen
和char *
创建的流中读取宽字符。
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 创建的流中读取宽字符的主要内容,如果未能解决你的问题,请参考以下文章