为什么GCC的wprintf与printf无法同时正常输出?

为什么GCC的wprintf与printf无法同时正常输出?

背景

在GNU Linux平台上,有的用户出于好奇在同一程序中混合使用了wprintfprintf,结果发现只有其一能正常工作,百思不得其解。

的确,这与Windows端的表现不一致。在Windows平台上,下面的代码可以正常输出:

wprintf(L"Wide char output.\n");
printf("Char output.\n"):

而在Linux平台上,只会输出Wide char output.

本文将解释为何Linux平台中GCC编译的程序wprintf与printf无法正常输出

原因分析

C运行时库的实现将宽字符和窄字符共用同一输出流也即stdout;而stdout只能处于宽字符输出模式字节输出模式两种状态之一。如果首先调用了printf,则输出流被定向为字节输出模式,后续只能输出窄字符;而如果首先调用了wprintf,输出流被定向为宽字符输出模式,后续只能输出宽字符。

这就造成在Linux平台上使用GCC编译后,程序中wprintf和printf函数只有其中一个能正常输出的现象。

题外话,在Linux平台上使用std::cout/std::wcout不存在类似问题,因为它们俩在实现上不共用同一输出流,因而在同一程序中均可正常输出。参考This is different from the model of the C++ runtime library where separate streams for wide and normal I/O are used.

实验测试

用GCC编译下面的代码并运行,应该得到什么结果?

#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int main ()
{
	wchar_t wstring[50];

	printf("Word to delete: ");

	wscanf(L"%50S", &wstring);

	wprintf(L"From wprintf: %S\n", wstring);
	printf("From printf: %ls\n", wstring);
	
	return 0;
}

效果如下:

Word to delete: Hello GCC
From printf: Hello GCC

这是因为代码中首先调用了printf打印Word to delete,输出流定向为窄字符,因此wprintf不能输出内容;只有printf输出了From printf: Hello GCC

接下来,我们将代码中的printf("Word to delete: ");删掉,再次编译,测试,效果如下:

Word to delete: Hello GCC
From wprintf: Hello GCC

这是因为代码中首先调用了wprintf,输出流定向为宽字符,因此在它之后调用的printf不能输出任何内容;因此只输出了From wprintf: Hello GCC

解决方法?

首先,博主推荐大家不要在同一个程序中混用wprintf和printf。
Linux系统的C/C++编译器默认以utf-8编码字符串,也就是说在Linux系统中编程只需要调用printf即可正常打印中文(宽字符)内容,根本没有wprintf的用武之地。

其次,真要混用这两个函数也是有办法的。在每次(w)printf调用前使用freopen函数重定向输出流stdout即可。
请看例程:

#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int main ()
{
	wchar_t wstring[50];

	printf("Word to delete: ");

	wscanf(L"%50S", &wstring);
    freopen("/dev/tty", "w",stdout);
	wprintf(L"From wprintf: %S\n", wstring);
	freopen("/dev/tty", "w",stdout);
	printf("From printf: %ls\n", wstring);
	
	return 0;
}

(用户输入内容为“adfa数据”)此时输出为:

Word to delete: adfa数据
From wprintf: adfa数据
From printf: adfa数据

参考

RTFM: wprintf

RTFM: freopen

还可以利用函数:fwide来检查当前输出流状态。
调用方式:fwide(stdout,0),返回值>0,说明当前为宽字符输出模式;返回值<0,说明当前为字节输出模式。

使用C语言、vscode软件、g++编译器、utf-8编码环境 WJ文件中有三个文件,运行WJ文件 WJ文件的三个子文件有以下内容: Recipe.h文件:#ifndef RECIPE_H #define RECIPE_H #define MAX_NAME_LEN 50 #define MAX_CATEGORY_LEN 30 #define MAX_INGREDIENTS_LEN 200 #define MAX_STEPS_LEN 500 #define MAX_RECIPES 100 #define FILENAME "recipes.dat" // 菜谱结构体 typedef struct { int id; // 唯一标识符 char name[MAX_NAME_LEN]; char category[MAX_CATEGORY_LEN]; char ingredients[MAX_INGREDIENTS_LEN]; char steps[MAX_STEPS_LEN]; } Recipe; // 函数声明 void init_system(); int add_recipe(const char *name, const char *category, const char *ingredients, const char *steps); int delete_recipe(int id); int modify_recipe(int id, const char *name, const char *category, const char *ingredients, const char *steps); Recipe* find_recipe_by_id(int id); Recipe* find_recipe_by_name(const char *name); void list_all_recipes(); Recipe* get_random_recipe(); void save_to_file(); void load_from_file(); #endif Recipe.c文件:#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "Recipe.h" // 全局变量 Recipe recipes[MAX_RECIPES]; int recipe_count = 0; int next_id = 1; // 初始化系统 void init_system() { load_from_file(); } // 添加菜谱 int add_recipe(const char *name, const char *category, const char *ingredients, const char *steps) { if (recipe_count >= MAX_RECIPES) return 0; Recipe *r = &recipes[recipe_count]; r->id = next_id++; strncpy(r->name, name, MAX_NAME_LEN); strncpy(r->category, category, MAX_CATEGORY_LEN); strncpy(r->ingredients, ingredients, MAX_INGREDIENTS_LEN); strncpy(r->steps, steps, MAX_STEPS_LEN); recipe_count++; save_to_file(); return r->id; } // 删除菜谱 int delete_recipe(int id) { for (int i = 0; i < recipe_count; i++) { if (recipes[i].id == id) { // 移动数组元素 for (int j = i; j < recipe_count - 1; j++) { recipes[j] = recipes[j + 1]; } recipe_count--; save_to_file(); return 1; } } return 0; } // 修改菜谱 int modify_recipe(int id, const char *name, const char *category, const char *ingredients, const char *steps) { for (int i = 0; i < recipe_count; i++) { if (recipes[i].id == id) { if (name) strncpy(recipes[i].name, name, MAX_NAME_LEN); if (category) strncpy(recipes[i].category, category, MAX_CATEGORY_LEN); if (ingredients) strncpy(recipes[i].ingredients, ingredients, MAX_INGREDIENTS_LEN); if (steps) strncpy(recipes[i].steps, steps, MAX_STEPS_LEN); save_to_file(); return 1; } } return 0; } // 通过ID查找 Recipe* find_recipe_by_id(int id) { for (int i = 0; i < recipe_count; i++) { if (recipes[i].id == id) { return &recipes[i]; } } return NULL; } // 通过名称查找 Recipe* find_recipe_by_name(const char *name) { for (int i = 0; i < recipe_count; i++) { if (strstr(recipes[i].name, name) != NULL) { return &recipes[i]; } } return NULL; } // 列出所有菜谱 void list_all_recipes() { printf("\n==== 所有菜谱 (%d) ====\n", recipe_count); for (int i = 0; i < recipe_count; i++) { printf("[ID:%d] %s (%s)\n", recipes[i].id, recipes[i].name, recipes[i].category); } printf("=====================\n"); } // 随机推荐菜谱 Recipe* get_random_recipe() { if (recipe_count == 0) return NULL; srand(time(NULL)); return &recipes[rand() % recipe_count]; } // 保存到文件 void save_to_file() { FILE *file = fopen(FILENAME, "wb"); if (!file) return; // 写入数据 fwrite(&recipe_count, sizeof(int), 1, file); fwrite(&next_id, sizeof(int), 1, file); fwrite(recipes, sizeof(Recipe), recipe_count, file); fclose(file); } // 从文件加载 void load_from_file() { FILE *file = fopen(FILENAME, "rb"); if (!file) return; // 读取数据 fread(&recipe_count, sizeof(int), 1, file); fread(&next_id, sizeof(int), 1, file); fread(recipes, sizeof(Recipe), recipe_count, file); fclose(file); } main.c文件:#include <stdio.h> #include <string.h> #include "Recipe.h" #include <locale.h> #include <wchar.h> #include <windows.h> int main() { // 设置控制台输出为UTF-8编码 SetConsoleOutputCP(65001); // 设置本地化环境,用于宽字符处理 setlocale(LC_ALL, "zh_CN.UTF-8"); init_system(); int choice; do{ printf("\n==== 家庭菜谱管理系统 ====\n"); printf("1. 添加菜谱\n"); printf("2. 删除菜谱\n"); printf("3. 修改菜谱\n"); printf("4. 查找菜谱(ID)\n"); printf("5. 查找菜谱(名称)\n"); printf("6. 列出所有菜谱\n"); printf("7. 随机推荐菜谱\n"); printf("8. 退出系统\n"); printf("请选择操作: "); canf("%d", &choice); getchar(); // 清除输入缓冲区 } while (choice != 8); return 0; { /* code */ } } // 显示菜单 // 添加菜谱界面 void add_recipe_ui() { char name[MAX_NAME_LEN]; char category[MAX_CATEGORY_LEN]; char ingredients[MAX_INGREDIENTS_LEN]; char steps[MAX_STEPS_LEN]; printf("\n--- 添加新菜谱 ---\n"); printf("菜谱名称: "); fgets(name, MAX_NAME_LEN, stdin); name[strcspn(name, "\n")] = 0; // 移除换行符 printf("菜谱类别: "); fgets(category, MAX_CATEGORY_LEN, stdin); category[strcspn(category, "\n")] = 0; printf("所需食材: "); fgets(ingredients, MAX_INGREDIENTS_LEN, stdin); ingredients[strcspn(ingredients, "\n")] = 0; printf("制作步骤: "); fgets(steps, MAX_STEPS_LEN, stdin); steps[strcspn(steps, "\n")] = 0; int id = add_recipe(name, category, ingredients, steps); if (id) { printf("添加成功! 菜谱ID: %d\n", id); } else { printf("添加失败! 已达最大菜谱数量\n"); } } // 显示菜谱详情 void display_recipe(Recipe *r) { if (!r) { printf("未找到菜谱!\n"); return; } printf("\n==== 菜谱详情 ====\n"); printf("ID: %d\n", r->id); printf("名称: %s\n", r->name); printf("类别: %s\n", r->category); printf("食材: %s\n", r->ingredients); printf("步骤: %s\n", r->steps); printf("=================\n"); } int main() { init_system(); int choice; do { display_menu(); scanf("%d", &choice); getchar(); // 清除输入缓冲区 switch (choice) { case 1: // 添加 add_recipe_ui(); break; case 2: { // 删除 int id; wprintf("输入要删除的菜谱ID: "); scanf("%d", &id); getchar(); if (delete_recipe(id)) { wprintf("删除成功!\n"); } else { wprintf("删除失败! 无效ID\n"); } break; } case 3: { // 修改 int id; wprintf("输入要修改的菜谱ID: "); scanf("%d", &id); getchar(); Recipe *r = find_recipe_by_id(id); if (!r) { wprintf("菜谱不存在!\n"); break; } char name[MAX_NAME_LEN]; char category[MAX_CATEGORY_LEN]; char ingredients[MAX_INGREDIENTS_LEN]; char steps[MAX_STEPS_LEN]; wprintf("新名称(回车保持原值): "); fgets(name, MAX_NAME_LEN, stdin); name[strcspn(name, "\n")] = 0; wprintf("新类别(回车保持原值): "); fgets(category, MAX_CATEGORY_LEN, stdin); category[strcspn(category, "\n")] = 0; wprintf("新食材(回车保持原值): "); fgets(ingredients, MAX_INGREDIENTS_LEN, stdin); ingredients[strcspn(ingredients, "\n")] = 0; wprintf("新步骤(回车保持原值): "); fgets(steps, MAX_STEPS_LEN, stdin); steps[strcspn(steps, "\n")] = 0; modify_recipe(id, *name ? name : NULL, *category ? category : NULL, *ingredients ? ingredients : NULL, *steps ? steps : NULL); wprintf("修改成功!\n"); break; } case 4: { // 按ID查找 int id; wprintf("输入菜谱ID: "); scanf("%d", &id); getchar(); display_recipe(find_recipe_by_id(id)); break; } case 5: { // 按名称查找 char name[MAX_NAME_LEN]; wprintf("输入菜谱名称: "); fgets(name, MAX_NAME_LEN, stdin); name[strcspn(name, "\n")] = 0; display_recipe(find_recipe_by_name(name)); break; } case 6: // 列出所有 list_all_recipes(); break; case 7: // 随机推荐 display_recipe(get_random_recipe()); break; case 8: // 退出 wprintf("感谢使用!\n"); break; default: wprintf("无效选择!\n"); } } while (choice != 8); return 0; } 在运行输入:g++ -o WJ Recipe.c main.c 出现以下错误:Microsoft Windows [版本 10.0.26100.4652] (c) Microsoft Corporation。保留所有权利。 C:\Users\34171\Desktop\code\WJ>g++ -o WJ Recipe.c main.c main.c: In function 'int main()': main.c:28:9: error: 'canf' was not declared in this scope; did you mean 'scanf'? 28 | canf("%d", &choice); | ^~~~ | scanf main.c: At global scope: main.c:89:5: error: redefinition of 'int main()' 89 | int main() { | ^~~~ main.c:8:5: note: 'int main()' previously defined here 8 | int main() | ^~~~ main.c: In function 'int main()': main.c:94:9: error: 'display_menu' was not declared in this scope; did you mean 'display_recipe'? 94 | display_menu(); | ^~~~~~~~~~~~ | display_recipe main.c:105:25: error: cannot convert 'const char*' to 'const wchar_t*' 105 | wprintf("杈D: "); | ^~~~~~~~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:110:29: error: cannot convert 'const char*' to 'const wchar_t*' 110 | wprintf("錸"); | ^~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:112:29: error: cannot convert 'const char*' to 'const wchar_t*' 112 | wprintf("錎\n"); | ^~~~~~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:119:25: error: cannot convert 'const char*' to 'const wchar_t*' 119 | wprintf("杈D: "); | ^~~~~~~~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:125:33: error: cannot convert 'const char*' to 'const wchar_t*' 125 | wprintf("鑞"); | ^~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:134:25: error: cannot convert 'const char*' to 'const wchar_t*' 134 | wprintf("鎚); | ^~~~~~~~~~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:138:25: error: cannot convert 'const char*' to 'const wchar_t*' 138 | wprintf("鎚); | ^~~~~~~~~~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:142:25: error: cannot convert 'const char*' to 'const wchar_t*' 142 | wprintf("鎚); | ^~~~~~~~~~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:146:25: error: cannot convert 'const char*' to 'const wchar_t*' 146 | wprintf("鎚); | ^~~~~~~~~~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:156:25: error: cannot convert 'const char*' to 'const wchar_t*' 156 | wprintf("淇n"); | ^~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:162:25: error: cannot convert 'const char*' to 'const wchar_t*' 162 | wprintf("杈D: "); | ^~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:172:25: error: cannot convert 'const char*' to 'const wchar_t*' 172 | wprintf("杈m); | ^~~~~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:189:25: error: cannot convert 'const char*' to 'const wchar_t*' 189 | wprintf("鎛"); | ^~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ main.c:193:25: error: cannot convert 'const char*' to 'const wchar_t*' 193 | wprintf("鎛"); | ^~~~~~~~~~~~~ | | | const char* In file included from main.c:1: D:/TDM-GCC/x86_64-w64-mingw32/include/stdio.h:1093:29: note: initializing argument 1 of 'int wprintf(const wchar_t*, ...)' 1093 | int wprintf (const wchar_t *__format, ...) | ~~~~~~~~~~~~~~~^~~~~~~~ C:\Users\34171\Desktop\code\WJ>
07-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值