背景
程序使用系统区域设置setlocale(LC_ALL, "");
。通常在Windows上,这意味着一些单字节编码(取决于Windows的语言版本):Windows-1250、Windows-1252、拉丁美洲、....
现在输入文件是在不同的编码(以免假设UTF-8)。程序应正确加载此文件到内存中,并使用系统区域设置提供的编码打印它。
MCVE
以下是最小完全可验证示例:
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
void copyTextFiles(FILE* in, FILE* out)
{
char buff[1024];
while(fscanf(in, "%1023[^\n]%*[\n]", buff) == 1) {
fprintf(out, "%s\n", buff);
}
}
int main(int argc, char* argv[])
{
char * l = setlocale(LC_ALL, "");
if (!l) {
perror("setlocale");
return 1;
}
printf("Locale selected: %s\n", l);
FILE *file = fopen("example_utf-8.txt", "rt+, ccs=UTF-8");
if (!file) {
perror("example_utf-8.txt");
return 2;
}
printf("File was opened\n");
copyTextFiles(file, stdout);
fclose(file);
printf("Done\n");
}
一些随机输入文件example_utf-8.txt
:
ąłóę
ąłóę
+−-😀😃
ÀÁÂÃĀĂȦÄẢ
ḂƁƇȨɃÆǼĶɈǨỒỐŔṘȟḧƕ
ɋƥṗ
问题
此代码与MSVC文档一致:
fopen,_wfopen|微软学习
Unicode支持
fopen
支持Unicode文件流。要打开Unicode文件,请将指定所需编码的ccs=encoding
标志传递给fopen
,如下所示。
FILE *fp = fopen("newfile.txt", "rt+, ccs=UTF-8");
ccs
编码的允许值为UNICODE
、UTF-8
和UTF-16LE
。
但程序崩溃,调用堆栈如下:
> ucrtbased.dll!__acrt_stdio_char_traits<char>::validate_stream_is_ansi_if_required(_iobuf * const stream) Line 495 C++
ucrtbased.dll!__crt_stdio_input::stream_input_adapter<char>::validate() Line 98 C++
ucrtbased.dll!__crt_stdio_input::input_processor<char,__crt_stdio_input::stream_input_adapter<char>>::process() Line 1092 C++
ucrtbased.dll!common_vfscanf::__l2::<lambda>() Line 47 C++
ucrtbased.dll!__crt_seh_guarded_call<int>::operator()<void <lambda>(void),int <lambda>(void) &,void <lambda>(void)>(__acrt_lock_stream_and_call::__l2::void <lambda>(void) && setup, common_vfscanf::__l2::int <lambda>(void) & action, __acrt_lock_stream_and_call::__l2::void <lambda>(void) && cleanup) Line 202 C++
ucrtbased.dll!__acrt_lock_stream_and_call<int <lambda>(void)>(_iobuf * const stream, common_vfscanf::__l2::int <lambda>(void) && action) Line 297 C++
ucrtbased.dll!common_vfscanf<char>(const unsigned __int64 options, _iobuf * const stream, const char * const format, __crt_locale_pointers * const locale, char * const arglist) Line 36 C++
ucrtbased.dll!__stdio_common_vfscanf(unsigned __int64 options, _iobuf * stream, const char * format, __crt_locale_pointers * locale, char * arglist) Line 60 C++
read_file.exe!_vfscanf_l(_iobuf * const _Stream, const char * const _Format, __crt_locale_pointers * const _Locale, char * _ArgList) Line 1068 C
read_file.exe!fscanf(_iobuf * const _Stream, const char * const _Format, ...) Line 1210 C
read_file.exe!copyTextFiles(_iobuf * in, _iobuf * out) Line 8 C
read_file.exe!main(int argc, char * * argv) Line 28 C
[External Code]
调用堆栈顶部指向_VALIDATE_STREAM_ANSI_RETURN
:
template <>
struct __acrt_stdio_char_traits<char> : __crt_char_traits<char>
{
static int_type const eof = EOF;
static bool validate_stream_is_ansi_if_required(FILE* const stream) throw()
{
_VALIDATE_STREAM_ANSI_RETURN(stream, EINVAL, false);
return true;
}
};
因此,在C库下面使用的是C++,但我不清楚这种检查到底是做什么的,以及为什么会失败(崩溃)。
CPP版本
#include <iostream>
#include <fstream>
#include <string>
#include <locale>
int main()
{
std::locale sysLocale("");
std::locale utfLocale(".utf-8");
std::locale::global(utfLocale);
std::cout.imbue(sysLocale);
std::cout << "Locale: " << sysLocale.name() << '\n';
std::ifstream file("example_utf-8.txt");
if (!file) {
std::perror("example_utf-8.txt");
return 2;
}
file.imbue(utfLocale);
std::string line;
while (getline(file, line)) {
std::cout << line << '\n';
}
std::cout << "\nDONE\n";
}
它工作得很好,如果系统语言环境不支持特定字符,它可以转换字符,下面是cmd.exe
的输出:
C:\repo\mixEncodingsInC\build\Debug>chcp 65001
C:\repo\mixEncodingsInC\build\Debug>cpp_read_file.exe
Locale:
ąłóę
ąłóę
+−-😀😃
ÀÁÂÃĀĂȦÄẢ
ḂƁƇȨɃÆǼĶɈǨỒỐŔṘȟḧƕ
ɋƥṗ
DONE
C:\repo\mixEncodingsInC\build\Debug>chcp 437
Active code page: 437
C:\repo\mixEncodingsInC\build\Debug>cpp_read_file.exe
Locale:
alóe
alóe
+--????
AAAAAA?Ä?
?????Æ?K?K??R????
???
DONE
C:\repo\mixEncodingsInC\build\Debug>chcp 1252
Active code page: 1252
C:\repo\mixEncodingsInC\build\Debug>cpp_read_file.exe
Locale:
alóe
alóe
+--????
ÀÁÂÃAA?Ä?
?????Æ?K?K??R????
???
DONE
提问
为什么会崩溃?我是不是误解了文件中的一些内容?我能做点什么让它工作吗?
注意事项
- Microsoft(R)C/C++优化编译器版本19。34.31933(x64)
- Window 10 Ent版本10。0.19045构建19045
- git repo如果有人想试试:https://github.com/MarekR22/mixEncodingsInC.git
2条答案
按热度按时间x6492ojm1#
这样修改C代码很有趣:
即使系统区域设置(代码页)发生更改,也可以在控制台中打印正确的字符。
所以看起来
printf
考虑了系统区域设置和程序使用的setlocale
集编码(所以它类似于cpp示例中的std::locale::global(utfLocale);
)。问题仍然存在,我想读取不同编码的文件,然后由应用程序使用。
fhity93d2#
MSVC文档和库中的所有UTF-8似乎都是由一群猴子编写的。例如:
FILE *fp = fopen(“newfile.txt”,“rt+,ccs=UTF-8”);
ccs编码允许的值为UNICODE、UTF-8和UTF-16 LE。
当以Unicode模式打开文件时,输入函数将从文件中读取的数据转换为UTF-16数据,存储为类型wchar_t。写入以Unicode模式打开的文件的函数需要包含UTF-16数据的缓冲区,这些数据存储为wchar_t类型。如果文件编码为UTF-8,那么UTF-16数据在写入时将转换为UTF-8。文件的UTF-8编码内容在读取时被转换为UTF-16。尝试在Unicode模式下读取或写入奇数字节会导致参数验证错误。
这看起来像是wfopen文档中的直接复制粘贴。UTF-16这一点在狭义的字符环境中没有任何意义。
但是如果我们仔细观察,我们可以推断出
ccs=UTF-8
的意思是“读取为UTF-8 * 并转码为我当前的编码,无论它是什么 *”(如果使用宽流,则可能是UTF-16,如果不是,则可能是任何“ANSI”代码页)。但是为什么代码转换失败会导致崩溃是我无法理解的。这使得ccs=UTF-8
,至少在读取模式下,在任何现实生活中的软件中都是不可用的。问问微软他们是怎么想的无论如何,这种行为不是你想要的。文档中的下一句话告诉您要做什么(但是考虑到文档的整体质量,请自己验证)。若要读取或写入以UTF-8格式存储在程序中的数据,请使用文本或二进制文件模式而不是Unicode模式。您负责任何所需的编码翻译。