Unicode字符处理C代码优化咨询:_setmode与setlocale疑问
我编写了一段C代码,实现输入英文单词“blue”和瑞典语单词“blå”,分别存入不同文本文件后读取并在终端正确输出。目前代码可正常运行,但存在两点疑问,同时希望获得代码中Unicode、_setmode、宽字符相关部分的优化建议。运行环境为Windows 11、VSCode、MINGW-32。
疑问
- 微软文档指出调用
_setmode前需用fflush刷新流,我仅在set_mode_to_UTF16函数中执行了该操作,set_mode_to_default中未做,为何程序仍正常运行? - 很多资料使用
setlocale设置UTF-16 locale,但我的代码未使用该函数,是否存在潜在问题?
代码实现
#include <stdio.h> #include <locale.h> #include <wchar.h> #include <string.h> #include <io.h> #include <fcntl.h> #define _O_U16TEXT 0x20000 #define _O_DEFAULT 0x4000 #define SIZE 1000 typedef struct { wchar_t sweWord[SIZE]; //sweWord=Swedish Word char engWord[SIZE]; //engWord=English Word } Word; void set_mode_to_UTF16() { fflush(stdin); fflush(stdout); _setmode(_fileno(stdin), _O_U16TEXT); _setmode(_fileno(stdout), _O_U16TEXT); } void set_mode_to_default() { _setmode(_fileno(stdin), _O_DEFAULT); _setmode(_fileno(stdout), _O_DEFAULT); } Word enterWord() { Word aWord; printf("Enter english word \"blue\": "); scanf("%s", aWord.engWord); printf("You entered: %s\n", aWord.engWord); set_mode_to_UTF16(); wprintf(L"Enter swedish word \"blå\": "); wscanf(L"%ls", aWord.sweWord); wprintf(L"You entered: %ls\n", aWord.sweWord); set_mode_to_default(); return aWord; } void saveWord(Word aWord) { FILE *pFile1; FILE *pFile2; if(pFile1=fopen("ENGWORD.txt", "w")) { fprintf(pFile1, "%s\n", aWord.engWord); } else { printf("Failed to save ENGWORD!\n"); } fclose(pFile1); set_mode_to_UTF16(); if(pFile2=fopen("SWEWORD.txt", "w")) { _setmode(_fileno(pFile2), _O_U16TEXT); fwprintf(pFile2, L"%ls\n", aWord.sweWord); } else { wprintf(L"Failed to save SWEWORD!\n"); } fclose(pFile2); set_mode_to_default(); } Word loadWord() { Word aWord; FILE *pFile1; FILE *pFile2; if(pFile1=fopen("ENGWORD.txt", "r")) { fscanf(pFile1, "%s", aWord.engWord); } else { printf("Failed to load ENGWORD!\n"); } fclose(pFile1); set_mode_to_UTF16(); if(pFile2=fopen("SWEWORD.txt", "r")) { _setmode(_fileno(pFile2), _O_U16TEXT); fwscanf(pFile2, L"%ls\n", aWord.sweWord); } else { wprintf(L"Failed to save SWEWORD!\n"); } fclose(pFile2); set_mode_to_default(); return aWord; } int main(void) { int defaultMode; defaultMode=_setmode(_fileno(stdin), _O_BINARY); printf("Default mode is %d\n", defaultMode); _setmode(_fileno(stdin), defaultMode); //mode is now in default. Word wordToSave; Word wordToLoad; wordToSave=enterWord(); saveWord(wordToSave); wordToLoad=loadWord(); printf("Loaded english word is %s\n", wordToLoad.engWord); set_mode_to_UTF16(); wprintf(L"Loaded swedish word is %ls\n", wordToLoad.sweWord); set_mode_to_default(); printf("Done! Signing off...\n"); return 0; }
程序输出
Default mode is 16384 Enter english word "blue": blue You entered: blue Enter swedish word "blå": blå You entered: blå Loaded english word is blue Loaded swedish word is blå Done! Signing off...
疑问解答
1. 切换回默认模式未执行fflush为何正常?
微软要求调用_setmode前fflush,核心原因是避免流缓冲区中残留的未处理数据与新编码模式冲突:
- 切换到
_O_U16TEXT前:默认模式是ANSI,缓冲区可能残留ANSI编码的字符,若不fflush直接切换,这些字符会被当作UTF-16处理,必然导致乱码。 - 切换回默认模式时:你在切换前已经完成了所有UTF-16相关的输入输出操作(比如
wprintf已经把内容输出到控制台,缓冲大概率已清空),所以暂时没出现问题。但这是侥幸情况——如果缓冲中还残留未写入的UTF-16数据,直接切换模式会导致数据损坏或乱码。正确做法是在set_mode_to_default中也加上fflush(stdin); fflush(stdout);。
2. 未使用setlocale是否有问题?
setlocale和_setmode的作用完全不同:
_setmode是设置文件流(控制台/文件)的编码模式,决定输入输出的字节是按ANSI还是UTF-16解析。setlocale是设置程序的区域设置,影响字符分类、多字节与宽字符的转换(比如mbstowcs、wcstombs)、格式化输出的本地化规则等。
你的代码中直接使用宽字符函数(wprintf、wscanf)+_setmode切换控制台到UTF-16模式,不需要依赖多字节转宽字符的逻辑,所以暂时可以正常运行。但如果后续代码需要处理多字节与宽字符的转换,或者需要依赖区域设置的字符操作(比如判断字符是否为字母),就必须调用setlocale(比如setlocale(LC_ALL, "");或指定UTF-8/UTF-16的locale)。从代码扩展性考虑,建议添加setlocale调用。
优化建议
统一流模式切换的fflush操作
修改set_mode_to_default函数,添加fflush确保缓冲清空:void set_mode_to_default() { fflush(stdin); fflush(stdout); _setmode(_fileno(stdin), _O_DEFAULT); _setmode(_fileno(stdout), _O_DEFAULT); }优化文件编码模式设置
不必先打开文件再调用_setmode,可以直接在fopen时指定编码,更直观且避免潜在问题:// 保存瑞典语单词时 if(pFile2=fopen("SWEWORD.txt", "w,ccs=UTF-16LE")) { fwprintf(pFile2, L"%ls\n", aWord.sweWord); } // 读取时 if(pFile2=fopen("SWEWORD.txt", "r,ccs=UTF-16LE")) { fwscanf(pFile2, L"%ls", aWord.sweWord); }修复错误处理逻辑
当前代码中,无论文件是否打开成功都会调用fclose,若文件指针为NULL,fclose(NULL)会触发未定义行为。修改为:if(pFile1=fopen("ENGWORD.txt", "w")) { fprintf(pFile1, "%s\n", aWord.engWord); fclose(pFile1); } else { printf("Failed to save ENGWORD!\n"); }避免缓冲区溢出
scanf和wscanf未限制输入长度,容易导致缓冲区溢出,修改为:scanf("%999s", aWord.engWord); // SIZE=1000,留1位给终止符 wscanf(L"%999ls", aWord.sweWord);替换手动宏定义
_O_U16TEXT和_O_DEFAULT在MINGW的fcntl.h中已有定义,删除手动定义的宏,避免冲突。安全获取默认流模式
用_getmode直接获取当前模式,替代先设置为_O_BINARY再切回的操作:int defaultMode = _getmode(_fileno(stdin));
内容的提问来源于stack exchange,提问作者Alexander Jonsson

