Я пытаюсь прочитать текстовый файл в кодировке UTF8, используя потоки Unicode. Это работает нормально, но, похоже, есть ошибка с fseek
, продемонстрированная с помощью следующего простого файла и программы:
Текстовый файл, который я читаю:
ABC
Сырое содержимое файла
EF BB BF 41 42 43 0D 0A
Как видите, файл содержит спецификацию UTF-8 и символы ABC, за которыми следует конец строки.
Программа открывает файл, используя поддержку UNICODE, затем читает одну строку и отображает необработанное содержимое буфера, что и ожидалось. Затем он ищет начало и снова читает строку, но на этот раз содержимое буфера другое; в начале буфера есть два байта, которые на самом деле являются спецификацией для файлов в кодировке UTF-16 с прямым порядком байтов.
Программа
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *input = _wfopen(L"utf8filewithbom.txt", L"r, ccs=UTF-8");
if (input == NULL)
{
printf("Can't open file\n");
return 1;
}
unsigned char buffer[100];
fgetws((wchar_t*)buffer, _countof(buffer) / 2, input);
printf("First 4 bytes of buffer: %02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
fseek(input, 0, SEEK_SET);
fgetws((wchar_t*)buffer, _countof(buffer) / 2, input);
printf("First 4 bytes of buffer: %02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
fclose(input);
}
Ожидаемый результат:
First 4 bytes of buffer: 41 00 42 00
First 4 bytes of buffer: 41 00 42 00
Фактический результат:
First 4 bytes of buffer: 41 00 42 00
First 4 bytes of buffer: ff fe 41 00
Это ошибка в Microsoft CRT или я что-то не так делаю?
Я использую Visual Studio 2019 16.4.3.
Вещи, которые я пробовал, но это ничего не изменило:
"rt, ccs=UTF-8"
вместо "r, ccs=UTF-8"
@SimonMourier тот же результат
Из примечаний здесь: learn.microsoft.com/en-us/cpp/c-runtime-library/reference/… это ожидаемо. Вам придется использовать ftell сразу после первого fgetws, чтобы знать, куда вернуться к началу.
Кажется, они упустили трюк, не удосужившись поддержать UTF-16BE (с спецификацией или без нее).
@IanAbbott Да, мне тоже было интересно, почему они не поддерживают UTF-16BE.
Согласно документации Microsoft по fseek:
Когда CRT открывает файл, который начинается с метки порядка байтов (BOM), указатель файла располагается после BOM (то есть в начале фактического содержимого файла). Если вам нужно
fseek
в начало файла, используйтеftell
для получения начальной позиции иfseek
для нее, а не для позиции 0.
По сути, просто настройте свой код (комментарии к измененным/добавленным строкам):
FILE *input = _wfopen(L"utf8filewithbom.txt", L"r, ccs=UTF-8");
if (input == NULL)
{
printf("Can't open file\n");
return 1;
}
const long postbomoffset = ftell(input); // Store post-BOM offset
unsigned char buffer[100];
fgetws((wchar_t*)buffer, _countof(buffer) / 2, input);
printf("First 4 bytes of buffer: %02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
fseek(input, postbomoffset, SEEK_SET); // Seek to post-BOM offset, not raw beginning
fgetws((wchar_t*)buffer, _countof(buffer) / 2, input);
printf("First 4 bytes of buffer: %02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
fclose(input);
Имеет смысл, я пропустил это в документации. Но TBH это должно быть прозрачным, IMO это недостаток дизайна.
@Jabberwocky: я в основном согласен. Недостатком прозрачной обработки является то, что она лишает программиста возможности перемотать назад, чтобы посмотреть спецификацию (у них может быть определенная кодировка ccs
, но они хотят проверить, есть ли у файла спецификация, которая переопределяет ее, или при перезаписи файла на месте, может захотеть заменить его и т.д.). Фундаментальная проблема заключается в том, что они привязывают поддержку кодирования к набору API, разработанному почти 50 лет назад, во времена, когда Unicode буквально не существовало, а кодирование текста было «всем, что использует текущая машина» (потому что кто перемещает данные между машины?). Обязательно будут бородавки.
@Jabberwocky: Примечание: вероятно, лучше использовать fgetpos
и fsetpos
, а не ftell
и fseek
; хотя в этом случае вы, вероятно, в порядке, fgetpos
и fsetpos
обрабатывают произвольно большие файлы (где fseek
и ftell
основаны на long
, что означает, что у них есть проблемы с файлами размером более 2 ГБ, поскольку Windows использует 32-битные long
даже в 64-битных режим; специфичные для MS функции _fseeki64
и _ftelli64
могут исправить эту часть), и они (теоретически) имеют некоторые функции безопасности для работы с текстовыми файлами в многобайтовых кодировках (где fseek
и ftell
в основном основаны на байтах).
Пробовали ли вы использовать «rt, ccs=UTF-8» вместо «r, ccs=UTF-8»?