Прочитайте широкий char поток, созданный с помощью fmemopen

Я пытаюсь прочитать широкий char из потока, который был создан с помощью 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, или есть ли другой способ исправить эту проблему?

Ответ 1

Вторая строка должна читать FILE *f = fmemopen(s, strlen(s), "r");. Как указано, fmemopen имеет поведение undefined и может возвращать 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 является широкосимвольным, то есть, если допускается широкополосный ввод-вывод, но char I/O запрещен. Он возвращает отрицательное значение, если stream является байт-ориентированным, то есть если char I/O разрешен, но широкоформатный ввод-вывод запрещен. Он возвращает ноль, если stream еще не имеет ориентации; в этом случае следующая операция ввода-вывода может изменить ориентацию (к байту, ориентированному, если это операция ввода/вывода char, или к широкосимвольной ориентации, если это широкоформатная операция ввода-вывода).

Как только поток имеет ориентацию, он не может быть изменен и сохраняется до тех пор, пока поток не будет закрыт.

Когда mode отличное от нуля, функция fwide() сначала пытается установить ориентацию stream (для широкосимвольной ориентации, если режим больше 0 или байт ориентирован, если mode меньше 0). Затем он возвращает значение, обозначающее текущую ориентацию, как указано выше.

Поток, возвращаемый 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");? См. здесь.

  • Рассмотрим использование временного 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() справочная страница: поток не должен иметь ориентацию или быть широко ориентированным.

  • Невозможно изменить ориентацию потока, если поток уже имеет ориентацию. Из fwide() man page: вызов этой функции в потоке, который уже имеет ориентацию, не может его изменить.

  • Поток, открытый с помощью glibc fmemopen(), имеет байтовую ориентацию и, следовательно, не может быть широко ориентирован. Как описано здесь uClibc имеет fmemopen() без этого ограничения.

Заключение: вам нужно использовать uClibc или другую библиотеку или создать свой собственный fmemopen().