在Visual Studio中设置项目属性的字符集。如果设置为“使用 Unicode 字符集“这会在编译器中添加 Unicode 宏,如果设置为 ”使用多字节字符集“ 这会在编译器中定义 _MBCS不定义 UNICODE/_UNICODE)。

而 Windows 提供的 API 会根据是否定义了 Unicode 宏 将函数进行转变,比如 CreateMutex

1
2
3
4
5
#ifdef UNICODE
#define CreateMutex CreateMutexW
#else
#define CreateMutex CreateMutexA
#endif // !UNICODE

后缀 W 表示宽字符,说明这个函数接收的字符串必须是宽字符wchar_t类型的,接收的字符串编码是宽字符编码。
宽字符仅代表一个字符所占的字节数,具体的编码由用户和编译器决定。
比如说用户定义wchar_t* str = L"你好;",MSVC看到wchar_t会给每个字符分配2个字节,看到前缀L会编码成UTF-16GCC会给每个字符分配4个字节,编码成UTF-32

平台 wchar_t 大小 实际编码
Windows (MSVC) 2 字节 UTF-16(支持代理对)
Linux/macOS (GCC/Clang) 4 字节 UTF-32(UCS-4)
类型 前缀 字符类型 编码(常见)
普通字符串 char ASCII / UTF-8(源码编码)
宽字符串 L wchar_t UTF-16(MSVC) 或 UTF-32(GCC/Clang)

后缀 A 表示ANSI,表示这个函数接收的字符串编码必须是ANSI编码(本地代码页 ,如中文GBK)。不同的编译器对char* str = “你好”字符串的编码会有自己的默认行为。
比如MSVC默认会把str本地代码页 (如中文GBK)编码,所以用Visual Studio编译这个函数用char*就可以传入参数,因为MSVC就是把char*字符串用ANSI编码。
但如果用GCC编译可能就会出错,因为gcc会默认将char*编码为UTF-8而这个函数只能处理ANSI编码。


所谓ANSI是Windows自己提出的概念,它实际上是 系统的本身的编码。也就是说,如果系统用的是GBK编码,那ANSI就是GBK;如果系统是Big5,那ANSI就是Big5。总的来说,ANSI是很多编码的统称。


实际上,Windows 内核底层只实现 W 版本(只能处理UTF-16编码字符串),A 版本只是一个转换层,它会:

  1. 将 ANSI 字符串转换为 UTF-16
  2. 调用对应的 W 函数
  3. 将结果转换回 ANSI(如果需要)

编译器如何编码字符串存储到可执行文件中

Tip: 这里说的编译器对字符编码都是类似char* str=”你好”,不加前缀的字符串编码

解码源文件(怎么读)和编码字符串(char)*(怎么存)是两套独立的逻辑,分别由 -finput-charset-fexec-charset 控制,互不干扰。编译器不会“继承”或“记住”源文件的编码方式去编码字符串,它只看你给它的选项。

两大主流编译器的默认行为

编译器 -finput-charset 默认值 -fexec-charset 默认值
GCC / Clang (Linux, macOS) UTF-8 UTF-8
MSVC (Windows) 本地代码页 (如中文GBK) 本地代码页 (如中文GBK)

当你“正常使用”而没有指定任何选项时:

  1. 在 Linux 上用 GCC:编译器默认按 UTF-8 去读你的源文件,也用 UTF-8 存你的字符串。两套逻辑一致,且都是 UTF-8。
  2. 在 Windows 上用 MSVC:编译器默认按系统的本地代码页(比如 GBK)去读你的源文件,也用同样的 GBK 存你的字符串。两套逻辑也一致,但取决于你系统的区域设置。

所以,回到你最开始的疑问——“编译器根据什么来编码字符串”——答案是:在不指定选项时,它依据的就是编译器自己内置的默认规则,GCC 是 UTF-8,MSVC 是本地代码页。