下面我将分步介绍在 Windows 和 Linux/macOS 两种主流操作系统上如何实现。

(图片来源网络,侵删)
核心概念
在开始编码前,需要理解几个关键概念:
- 多字节字符:像 GB2312、GBK 这样的编码,一个字符可能占用 1 个或 2 个字节。
char类型可以直接存储它们。 - 宽字符:Unicode 字符,在 C 中用
wchar_t类型表示,在 Windows 上通常是 2 字节(UTF-16),在 Linux/macOS 上通常是 4 字节(UTF-32),一个wchar_t始终代表一个完整的字符。 - 转换流程:转换的核心思想是“解码 -> 再编码”。
- 第一步(解码):将源编码(GB2312)的字符串“解码”成操作系统内部通用的宽字符(Unicode)字符串。
- 第二步(编码):将宽字符(Unicode)字符串“编码”成目标编码(UTF-8)的字符串。
在 Windows 平台上实现
Windows API 对多字节和宽字符的转换提供了非常完善的函数。
关键函数
MultiByteToWideChar():将多字节字符串(如 GB2312)转换为宽字符串(UTF-16)。WideCharToMultiByte():将宽字符串(UTF-16)转换为多字节字符串(如 UTF-8)。
完整代码示例
#include <stdio.h>
#include <stdlib.h>
#include <windows.h> // Windows API 头文件
#include <locale.h> // 用于 setlocale
// 函数声明
char* gb2312_to_utf8(const char* gb2312_str);
int main() {
// 设置控制台代码页为 GB2312,确保 printf 能正确显示 GB2312 字符
// 如果你的控制台已经是 UTF-8,可以注释掉这行
system("chcp 936");
// GB2312 编码的字符串,"你好,世界!"
// 注意:在源代码文件中,如果保存为 GB2312,可以直接写字符串。
// 如果源文件是 UTF-8,需要用转义序列或宽字符字面量。
const char* gb2312_text = "你好,世界!This is a test.";
printf("原始 GB2312 字符串: %s\n", gb2312_text);
// 调用转换函数
char* utf8_text = gb2312_to_utf8(gb2312_text);
if (utf8_text) {
// printf 默认不支持直接输出 UTF-8 到控制台。
// 需要将控制台输出设置为 UTF-8 模式 (Windows 10 1903+)
// 或者使用支持 UTF-8 的终端。
// system("chcp 65001"); // 切换控制台为 UTF-8
printf("转换后的 UTF8 字符串: %s\n", utf8_text);
// 释放内存
free(utf8_text);
} else {
printf("转换失败!\n");
}
return 0;
}
/**
* @brief 将 GB2312 编码的字符串转换为 UTF-8 编码
* @param gb2312_str 输入的 GB2312 字符串
* @return 转换后的 UTF-8 字符串指针,需要调用者 free 释放,失败返回 NULL。
*/
char* gb2312_to_utf8(const char* gb2312_str) {
if (gb2312_str == NULL) {
return NULL;
}
// 1. 将 GB2312 字符串转换为宽字符串 (UTF-16)
int len = MultiByteToWideChar(
CP_ACP, // CodePage: CP_ACP 表示使用系统的 ANSI 代码页,在中国就是 GBK/GB2312
0, // dwFlags: 无特殊标志
gb2312_str, // lpMultiByteStr: 输入的 GB2312 字符串
-1, // cbMultiByte: -1 表示字符串以 '\0'
NULL, // lpWideCharStr: 先传 NULL,获取所需缓冲区大小
0 // cchWideChar: 0 表示获取大小
);
if (len == 0) {
// 获取大小失败
return NULL;
}
// 分配宽字符缓冲区
wchar_t* wide_str = (wchar_t*)malloc(len * sizeof(wchar_t));
if (wide_str == NULL) {
return NULL;
}
// 再次调用 MultiByteToWideChar 进行实际转换
len = MultiByteToWideChar(
CP_ACP,
0,
gb2312_str,
-1,
wide_str,
len
);
if (len == 0) {
free(wide_str);
return NULL;
}
// 2. 将宽字符串 (UTF-16) 转换为 UTF-8 字符串
int utf8_len = WideCharToMultiByte(
CP_UTF8, // CodePage: CP_UTF8 表示 UTF-8
0, // dwFlags: 无特殊标志
wide_str, // lpWideCharStr: 输入的宽字符串
-1, // cchWideChar: -1 表示字符串以 L'\0'
NULL, // lpMultiByteStr: 先传 NULL,获取所需缓冲区大小
0, // cbMultiByte: 0 表示获取大小
NULL, // lpDefaultChar: 不使用
NULL // lpUsedDefaultChar: 不使用
);
if (utf8_len == 0) {
free(wide_str);
return NULL;
}
// 分配 UTF-8 字符串缓冲区
char* utf8_str = (char*)malloc(utf8_len * sizeof(char));
if (utf8_str == NULL) {
free(wide_str);
return NULL;
}
// 再次调用 WideCharToMultiByte 进行实际转换
utf8_len = WideCharToMultiByte(
CP_UTF8,
0,
wide_str,
-1,
utf8_str,
utf8_len,
NULL,
NULL
);
// 释放中间的宽字符缓冲区
free(wide_str);
if (utf8_len == 0) {
free(utf8_str);
return NULL;
}
return utf8_str;
}
编译和运行 (使用 MinGW/g++):
gcc -o gb2312_to_utf8_win gb2312_to_utf8_win.c -lws2_32 ./gb2312_to_utf8_win
注意:在较新版本的 Windows 10/11 中,你可能需要先运行 chcp 65001 将控制台设置为 UTF-8 模式,才能正确看到转换后的中文输出。

(图片来源网络,侵删)
在 Linux/macOS 平台上实现
Linux 和 macOS 使用 POSIX 标准函数,这些函数依赖于系统的当前 locale。
关键函数
mbstowcs():多字节字符串转宽字符字符串。wcstombs():宽字符字符串转多字节字符串。setlocale():设置程序的 locale,这对于正确识别 GB2312 编码至关重要。
完整代码示例
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h> // 宽字符函数
#include <locale.h> // setlocale 函数
#include <errno.h> // errno
// 函数声明
char* gb2312_to_utf8(const char* gb2312_str);
int main() {
// 设置程序的 locale 为 "zh_CN.GB2312"
// 这告诉 C 标准库库,当前环境使用 GB2312 编码
// setlocale 失败,后续的转换函数将无法正确工作
if (setlocale(LC_ALL, "zh_CN.GB2312") == NULL) {
fprintf(stderr, "无法设置 locale 'zh_CN.GB2312',请确保系统支持此 locale,\n");
// 如果系统没有 zh_CN.GB2312,可以尝试 "zh_CN.GBK" 或 "C"
// setlocale(LC_ALL, "C"); // 回退到默认 C locale
return 1;
}
const char* gb2312_text = "你好,世界!This is a test.";
printf("原始 GB2312 字符串: %s\n", gb2312_text);
char* utf8_text = gb2312_to_utf8(gb2312_text);
if (utf8_text) {
// Linux/macOS 终端通常默认支持 UTF-8,可以直接打印
printf("转换后的 UTF8 字符串: %s\n", utf8_text);
free(utf8_text);
} else {
printf("转换失败!\n");
}
return 0;
}
/**
* @brief 将 GB2312 编码的字符串转换为 UTF-8 编码
* @param gb2312_str 输入的 GB2312 字符串
* @return 转换后的 UTF-8 字符串指针,需要调用者 free 释放,失败返回 NULL。
*/
char* gb2312_to_utf8(const char* gb2312_str) {
if (gb2312_str == NULL) {
return NULL;
}
// 1. 将 GB2312 字符串转换为宽字符串 (通常是 UTF-32)
size_t len = mbstowcs(NULL, gb2312_str, 0);
if (len == (size_t)-1) {
perror("mbstowcs 失败 (获取大小)");
return NULL;
}
// 分配宽字符缓冲区 (+1 用于宽字符的终止符 L'\0')
wchar_t* wide_str = (wchar_t*)malloc((len + 1) * sizeof(wchar_t));
if (wide_str == NULL) {
return NULL;
}
// 实际转换
len = mbstowcs(wide_str, gb2312_str, len + 1);
if (len == (size_t)-1) {
perror("mbstowcs 失败 (实际转换)");
free(wide_str);
return NULL;
}
// 2. 将宽字符串转换为 UTF-8 字符串
// wcstombs 在正确的 locale 下 (已通过 setlocale 设置),
// 会将宽字符 (内部 UTF-32) 转换为当前 locale 的多字节编码。
// 如果我们想得到 UTF-8,需要将 locale 设置为 "zh_CN.UTF-8"
// 但为了通用性,我们假设程序最终输出的 locale UTF-8。
// 或者,我们可以显式设置一个临时的 UTF-8 locale。
// 设置一个临时的 UTF-8 locale 进行转换
char* old_locale = setlocale(LC_CTYPE, NULL);
if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL) {
// en_US.UTF-8 不可用,尝试 C.UTF-8 或系统默认的 UTF-8 locale
setlocale(LC_CTYPE, "C.UTF-8");
}
len = wcstombs(NULL, wide_str, 0);
if (len == (size_t)-1) {
perror("wcstombs 失败 (获取大小)");
free(wide_str);
setlocale(LC_CTYPE, old_locale); // 恢复旧 locale
return NULL;
}
char* utf8_str = (char*)malloc(len + 1);
if (utf8_str == NULL) {
free(wide_str);
setlocale(LC_CTYPE, old_locale);
return NULL;
}
len = wcstombs(utf8_str, wide_str, len + 1);
if (len == (size_t)-1) {
perror("wcstombs 失败 (实际转换)");
free(wide_str);
free(utf8_str);
setlocale(LC_CTYPE, old_locale);
return NULL;
}
// 恢复原来的 locale
setlocale(LC_CTYPE, old_locale);
free(wide_str);
return utf8_str;
}
编译和运行 (使用 g++):
gcc -o gb2312_to_utf8_unix gb2312_to_utf8_unix.c ./gb2312_to_utf8_unix
注意: 你需要确保你的 Linux 系统安装了 zh_CN.GB2312 locale,如果没有,通常可以通过安装 glibc-i18n 包并运行 localedef -c -i zh_CN -f gbk zh_CN.GBK 来生成。
跨平台封装(推荐)
如果你需要编写跨平台的代码,可以创建一个封装函数,在编译时根据操作系统选择不同的实现。

(图片来源网络,侵删)
// cross_platform_convert.h #ifndef CROSS_PLATFORM_CONVERT_H #define CROSS_PLATFORM_CONVERT_H char* convert_gb2312_to_utf8(const char* input); #endif // CROSS_PLATFORM_CONVERT_H
// cross_platform_convert.c
#include "cross_platform_convert.h"
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <wchar.h>
#include <locale.h>
#endif
// Windows 实现
#ifdef _WIN32
char* convert_gb2312_to_utf8(const char* input) {
// ... (将上面 Windows 版本的 gb2312_to_utf8 函数体复制到这里)
// 确保函数名和参数一致
// ... (Windows 实现代码)
// 为了简洁,这里省略具体实现,请参考上面的 Windows 代码块
// 实现逻辑与上面的 "gb2312_to_utf8" 函数完全相同
int len = MultiByteToWideChar(CP_ACP, 0, input, -1, NULL, 0);
if (len == 0) return NULL;
wchar_t* wide_str = (wchar_t*)malloc(len * sizeof(wchar_t));
if (!wide_str) return NULL;
len = MultiByteToWideChar(CP_ACP, 0, input, -1, wide_str, len);
if (len == 0) { free(wide_str); return NULL; }
int utf8_len = WideCharToMultiByte(CP_UTF8, 0, wide_str, -1, NULL, 0, NULL, NULL);
if (utf8_len == 0) { free(wide_str); return NULL; }
char* utf8_str = (char*)malloc(utf8_len);
if (!utf8_str) { free(wide_str); return NULL; }
utf8_len = WideCharToMultiByte(CP_UTF8, 0, wide_str, -1, utf8_str, utf8_len, NULL, NULL);
free(wide_str);
if (utf8_len == 0) { free(utf8_str); return NULL; }
return utf8_str;
}
// Linux/macOS 实现
#else
char* convert_gb2312_to_utf8(const char* input) {
// ... (将上面 Linux 版本的 gb2312_to_utf8 函数体复制到这里)
// 确保函数名和参数一致
// ... (Linux 实现代码)
// 为了简洁,这里省略具体实现,请参考上面的 Linux 代码块
// 实现逻辑与上面的 "gb2312_to_utf8" 函数基本相同
if (setlocale(LC_ALL, "zh_CN.GB2312") == NULL) {
// 尝试备选方案
setlocale(LC_ALL, "zh_CN.GBK");
}
size_t len = mbstowcs(NULL, input, 0);
if (len == (size_t)-1) return NULL;
wchar_t* wide_str = (wchar_t*)malloc((len + 1) * sizeof(wchar_t));
if (!wide_str) return NULL;
len = mbstowcs(wide_str, input, len + 1);
if (len == (size_t)-1) { free(wide_str); return NULL; }
char* old_locale = setlocale(LC_CTYPE, NULL);
setlocale(LC_CTYPE, "en_US.UTF-8"); // 切换到 UTF-8 locale 进行编码
len = wcstombs(NULL, wide_str, 0);
if (len == (size_t)-1) { free(wide_str); setlocale(LC_CTYPE, old_locale); return NULL; }
char* utf8_str = (char*)malloc(len + 1);
if (!utf8_str) { free(wide_str); setlocale(LC_CTYPE, old_locale); return NULL; }
len = wcstombs(utf8_str, wide_str, len + 1);
setlocale(LC_CTYPE, old_locale); // 恢复 locale
free(wide_str);
if (len == (size_t)-1) { free(utf8_str); return NULL; }
return utf8_str;
}
#endif
然后在你的主程序中,只需要包含头文件并调用这个统一的函数即可,无需关心底层平台差异。
| 特性 | Windows (API) | Linux/macOS (POSIX) |
|---|---|---|
| 核心函数 | MultiByteToWideChar, WideCharToMultiByte |
mbstowcs, wcstombs |
| 关键设置 | CP_ACP (代表系统 ANSI, 即 GBK/GB2312), CP_UTF8 |
setlocale(LC_ALL, "zh_CN.GB2312") |
| 内部表示 | 宽字符为 UTF-16 | 宽字符为 UTF-32 |
| 优点 | 功能强大,直接支持 GB2312 到 UTF-8 的转换 | 标准,可移植性好 |
| 缺点 | 仅限 Windows | 依赖系统 locale 设置 |
最佳实践:始终使用操作系统提供的原生转换函数,它们是经过严格测试的,能处理各种边界情况,比任何自己实现的查找表或转换逻辑都更可靠、更高效。
