【问题标题】:C++ Visual Studio character encoding issuesC++ Visual Studio 字符编码问题
【发布时间】:2010-12-23 21:08:35
【问题描述】:

不能把我的头包裹在这个周围是一个真正的耻辱......

我正在使用法语版 Windows (XP) 中的法语版 Visual Studio (2008)。发送到输出窗口的字符串中的法语口音已损坏。同上输入输出窗口。典型的字符编码问题,我输入 ANSI,得到 UTF-8 作为回报,或者类似的东西。在向输出窗口显示“硬编码”字符串时,什么设置可以确保字符保留在 ANSI 中?

编辑:

例子:

#include <iostream>

int main()
{
std:: cout << "àéêù" << std:: endl;

return 0;
}

将显示在输出中:

óúÛ¨

(此处编码为 HTML 以供您观赏)

我真的很想展示它:

咩咩

【问题讨论】:

  • 你能给我们多一点意见吗?这是否发生在构建输出、所有输出或其他方面?你能给我们一个发生这种情况的具体操作吗(构建、调试等......)
  • 如果你使用 wcout 会发生什么?
  • 我不确定,但我认为您应该使用 _T() 或 L"" 在 Visual Studio 中指定 unicode 字符串。你可以用 wcout 尝试一次吗?
  • 你为什么不使用宽字符串?这就是 Windows 实现 Unicode 支持的方式
  • 自从这个答案在 2018 年的谷歌搜索中出现,我会发表评论。 C++11 或更高版本支持使用 u"..." 执行字符集的 UTF-8。 Visual C++ 2008 支持使用 BOM 作为源字符集的 UTF-8,当前版本通过 /UTF-8 开关支持不使用 BOM 的它。其他编译器,包括 gcc、clang 和 icc,也支持它。多年来,该语言标准允许编译器支持他们想要的任何源和执行字符集,只要它们包含最少的基本字符集。

标签: c++ visual-studio character-encoding


【解决方案1】:

我试过这段代码:

#include <iostream>
#include <fstream>
#include <sstream>

int main()
{
    std::wstringstream wss;
    wss << L"àéêù";
    std::wstring s = wss.str();
    const wchar_t* p = s.c_str();
    std::wcout << ws.str() << std::endl;

    std::wofstream file("C:\\a.txt");
    file << p << endl;

    return 0;
}

调试器显示 wss、s 和 p 都具有预期值(即“àéêù”),输出文件也是如此。然而,控制台中出现的是óúÛ¨。

因此问题出在 Visual Studio 控制台,而不是 C++。使用 Bahbar 的出色回答,我补充道:

    SetConsoleOutputCP(1252);

作为第一行,然后控制台输出按原样出现。

【讨论】:

    【解决方案2】:

    在我继续之前,我应该提一下,您所做的不符合 c/c++ 标准。 specification 在 2.2 中说明了哪些字符集在源代码中有效。里面的内容不多,所有使用的字符都是ascii。所以...下面的所有内容都是关于一个特定的实现(碰巧是美国语言环境机器上的 VC2008)。

    首先,cout 行上有 4 个字符,输出中有 4 个字形。所以问题不在于 UTF8 编码,因为它会将多个源字符组合成更少的字形。

    从您的源字符串到控制台上的显示,所有这些都起作用:

    1. 您的源文件采用何种编码方式(即编译器将如何看待您的 C++ 文件)
    2. 您的编译器对字符串字面量做了什么,它可以理解什么源编码
    3. &lt;&lt; 如何解释您传入的编码字符串
    4. 控制台需要什么编码
    5. 控制台如何将该输出转换为字体字形。

    现在……

    1 和 2 是相当容易的。看起来编译器猜测源文件的格式,并将其解码为其内部表示。无论源编码是什么,它都会在当前代码页中生成字符串文字对应的数据块。我没有找到明确的细节/控制。

    3 更容易。除了控制代码,&lt;&lt; 只是将数据向下传递给 char *。

    4 由SetConsoleOutputCP 控制。它应该默认为您的默认系统代码页。您还可以通过GetConsoleOutputCP 确定您拥有哪一个(输入的控制方式不同,通过SetConsoleCP

    5 很有趣。我用 CP1252(西欧,windows)敲了敲头,想弄清楚为什么我不能让 é 正确显示。事实证明,我的系统字体没有该字符的字形,并且有用地使用了我的标准代码页的字形(大写 Theta,如果我不调用 SetConsoleOutputCP,我会得到相同的字形)。为了解决这个问题,我不得不将我在控制台上使用的字体更改为 Lucida Console(一种真正的字体)。

    我从中学到了一些有趣的东西:

    • 源的编码无关紧要,只要编译器能搞定(值得注意的是,将其更改为UTF8不会更改生成的代码。我的“é”字符串仍然使用CP1252编码为233 0
    • VC 正在为我似乎无法控制的字符串文字选择一个代码页。
    • 控制控制台显示的内容比我预期的更痛苦

    那么...这对您意味着什么?以下是一些建议:

    • 不要在字符串文字中使用非ascii。使用资源,控制编码。
    • 确保您知道您的控制台需要什么编码,并且您的字体具有代表您发送的字符的字形。
    • 如果您想弄清楚您的情况使用的是什么编码,我建议您将字符的实际值打印为整数。 char * a = "é"; std::cout &lt;&lt; (unsigned int) (unsigned char) a[0] 确实为我显示 233,这恰好是 CP1252 中的编码。

    顺便说一句,如果您得到的是“ÓÚÛ¨”而不是您粘贴的内容,那么看起来您的 4 个字节在某处被解释为 CP850

    【讨论】:

    • 使用资源.. 一定要调查一下。不过,这里变得更难了:控制台充当某种过滤器,因为如果我“cin>>”一些重音字母,你瞧,有趣的字符会出现在另一边!我现在不在那台机器上,但我会尝试重新输出我从 cin 获得的内容,看看它是否会进一步乱码或恢复。
    • 优秀的答案。我一定会记下这一点。
    • 这个答案对于理解在编译过程和运行时系统中字符串文字的源代码文件的原始字节发生了什么非常有用。或许你可以看看stackoverflow.com/questions/27871124/…
    • 具体来说-也许,如果您有时间,您可以解决编译器的 internal represenation 的原始字符串文字(您在回答中提到的)字节如何对应于 C++ 标准的 execution character set ?谢谢!
    • @DanNissenbaum:好吧,碰巧的是,您似乎对可能性的研究比我实际知道的要深得多。仅供参考,在输入此答案之前,我对该主题一无所知。我只是好奇而已 :)。但我会坚持我的第一个建议:不要在字符串文字中使用非 ascii - 不仅因为你不控制编码,还因为如果它不是 ascii,很可能是你想要本地化的东西未来
    【解决方案3】:

    试试这个:

    #include <iostream>
    #include <locale>
    
    int main()
    {
     std::locale::global(std::locale(""));
     std::cout << "àéêù" << std::endl;
    
     return 0;
    }
    

    【讨论】:

    • 很好,但这似乎只适用于输出,从控制台接收到的输入仍然是随机的乱码。
    【解决方案4】:
    //Save As Windows 1252
    #include<iostream>
    #include<windows.h>
    
    int main()
    {
        SetConsoleOutputCP(1252);
        std:: cout << "àéêù" << std:: endl;
    }
    

    Visual Studio 不支持 C++ 的 UTF 8,但部分支持 C:

    //Save As UTF8 without signature
    #include<stdio.h>
    #include<windows.h>
    
    int main()
    {
        SetConsoleOutputCP(65001);
        printf("àéêù\n");
    }
    

    【讨论】:

      【解决方案5】:

      确保您不要忘记将控制台的字体更改为 Lucida Consolas,正如 Bahbar 所述:这对我来说至关重要(French win 7 64 bit with VC 2012)。

      然后正如其他人提到的那样,将 SetConsoleOutputCP(1252) 用于 C++,但它可能会失败,具体取决于可用页面,因此您可能希望使用 GetConsoleOutputCP() 检查它是否有效,或者至少检查 SetConsoleOutputCP(1252) 是否返回零.更改全局语言环境也有效(出于某种原因,无需执行 cout.imbue(locale()); 但它可能会破坏一些库!

      在 C 中,SetConsoleOutputCP(65001);或者基于语言环境的方法对我有用一旦我将源代码保存为没有签名的 UTF8(向下滚动,无签名选项在页面列表的下方)。

      输入使用 SetConsoleCP(65001);显然,由于 Windows 中第 65001 页的实施不当,我失败了。语言环境方法在 C 和 C++ 中也都失败了。似乎需要一个更复杂的解决方案,而不是依赖原生字符而是 wchar_t。

      【讨论】:

        【解决方案6】:

        使用_setmode()works¹ 可以说比更改代码页或设置区域设置更好,因为它实际上会使您的程序以 Unicode 输出,因此将保持一致 - 无论当前设置了哪个代码页或区域设置.

        例子:

        #include <iostream>
        #include <io.h>
        #include <fcntl.h>
        
        int wmain()
        {
            _setmode( _fileno(stdout), _O_U16TEXT );
            
            std::wcout << L"àéêù" << std::endl;
        
            return 0;
        }
        

        在 Visual Studio 中,确保为 Unicode 设置项目(右键单击 *Project* -> 单击 *General* -> *Character Set* = *Use Unicode Character Set*)。

        MinGW 用户:

        1. 同时定义UNICODE_UNICODE
        2. -finput-charset=iso-8859-1 添加到编译器选项 以解决此错误:“转换为执行字符集:无效参数
        3. -municode 添加到链接器选项 以绕过“对`WinMain@16 的未定义引用”(read more)。

        **编辑:** 设置 unicode *input* 的等效调用是:`_setmode( _fileno(stdin), _O_U16TEXT );`

        编辑2:一条重要信息,特别考虑到问题使用std::cout。这是不支持的。 MSDN Docs 状态(强调我的):

        Unicode 模式适用于宽打印功能(例如 wprintf),适用于 不支持窄打印功能。使用窄版印刷 Unicode 模式流上的函数触发断言。

        所以,当控制台输出模式为_O_U16TEXT时,不要使用std::cout;同样,当控制台输入为_O_U16TEXT 时,不要使用std::cin。您必须使用这些工具的宽版本(std::wcoutstd::wcin)。
        请注意,不允许在同一输出中使用mixing cout and wcout(但我发现如果您在切换窄操作和宽操作之前先调用flush(),然后调用_setmode(),它会起作用)。

        【讨论】:

        • @Nikos SetConsoleCP() 没有实际意义,因为如果输入是 unicode,则代码页并不重要。您可以在 Joel post 中阅读有关代码页与 unicode 的信息。检查我的编辑以了解如何设置 unicode 输入。
        【解决方案7】:

        因为我被要求,我会做一些死灵术。其他的答案都是 2009 年的,但这篇文章还是在我 2018 年的一次搜索中出现的。今天的情况非常不同。此外,即使在 2009 年,接受的答案也不完整。

        源字符集

        每个编译器(包括 Microsoft 的 Visual Studio 2008 及更高版本,gcc、clang 和 icc)都会毫无问题地读取以 BOM 开头的 UTF-8 源文件,而 clang 不会读取除 UTF-8 之外的任何内容,因此 UTF-8带有 BOM 是 C 和 C++ 源文件的最低公分母。

        语言标准没有说明编译器需要支持哪些源字符集。一些现实世界的源文件甚至保存在与 ASCII 不兼容的字符集中。 2008 年的 Microsoft Visual C++ 支持带有字节顺序标记的 UTF-8 源文件,以及两种形式的 UTF-16。如果没有字节顺序标记,则假定文件以当前 8 位代码页编码,该代码页始终是 ASCII 的超集。

        执行字符集

        2012 年,编译器将/utf-8 开关添加到CL.EXE。今天,它还支持/source-charset/execution-charset 开关,以及/validate-charset 来检测您的文件是否实际上不是UTF-8。 This page on MSDN has a link to the documentation on Unicode support for every version of Visual C++.

        当前版本的 C++ 标准规定编译器必须同时具有一个执行字符集,该字符集确定字符常量的数值,例如 'a',以及一个执行宽字符集,该字符集确定宽字符常量的值,例如L'é'.

        对于语言律师来说,标准中关于如何编码的要求很少,但 Visual C 和 C++ 设法打破了它们。它必须包含大约 100 个不能有负值的字符,并且数字 '0''9' 的编码必须是连续的。大写或小写字母都不必是,因为它们不在一些旧的大型机上。 (也就是说,'0'+9 必须与'9' 相同,但是今天在实际使用中仍有一个编译器,其默认行为是'a'+9 不是'j' 而是'«',这是合法的.) 宽字符执行集必须包括基本执行集并且有足够的位来容纳任何支持的语言环境的所有字符。每个主流编译器都至少支持一种 Unicode 语言环境,并且可以理解使用 \Uxxxxxxxx 指定的有效 Unicode 字符,但没有声称符合标准的编译器。

        Visual C 和 C++ 违反语言标准的方式是使它们的 wchar_t UTF-16,它只能将某些字符表示为代理对,而标准规定 wchar_t 必须是固定宽度编码。这是因为微软在 1990 年代将wchar_t 定义为 16 位宽,当时 Unicode 委员会发现 16 位对于整个世界来说都不够用,而且微软也不会破坏 Windows API。它也支持标准的char32_t 类型。

        UTF-8 字符串文字

        这个问题提出的第三个问题是如何让编译器在内存中将字符串文字编码为 UTF-8。从 C++11 开始,你就可以写出这样的东西了:

        constexpr unsigned char hola_utf8[] = u8"¡Hola, mundo!";
        

        无论源字符集是 UTF-8、UTF-16、Latin-1、CP1252 还是 IBM EBCDIC 1047(这是一个愚蠢的理论示例,但为了向后兼容,IBM 的 Z 系列大型机编译器的默认设置)。也就是相当于用{ 0xC2, 0xA1, 'H', /* ... , */ '!', 0 }初始化数组。

        如果输入字符太不方便,或者如果您想区分表面上相同的字符,例如空格和不间断空格或预组合和组合字符,您还可以使用通用字符转义:

        constexpr unsigned char hola_utf8[] = u8"\u00a1Hola, mundo!";
        

        无论源字符集如何,无论您将文字存储为 UTF-8、UTF-16 还是 UCS-4,您都可以使用这些。它们最初是在 C99 中添加的,但 Microsoft 在 Visual Studio 2015 中支持它们。

        编辑: 正如 Matthew 所报告的,u8" 字符串在 MSVC 的某些版本中存在错误,包括 19.14。事实证明,文字非 ASCII 字符也是如此,即使您指定 /utf-8/source-charset:utf-8 /execution-charset:utf-8。上面的示例代码在 19.22.27905 中正常工作。

        还有另一种在 Visual C 或 C++ 2008 中有效的方法:八进制和十六进制转义码。您将在该版本的编译器中使用以下代码对 UTF-8 文字进行编码:

        const unsigned char hola_utf8[] = "\xC2\xA1Hello, world!";
        

        【讨论】:

        • 无论源字符集如何,您似乎不能使用 UCE; VS 将它们放入 mojibake even 用于 UTF-X 文字。 (OTOH,这几乎可以肯定是一个编译器错误......)
        • @Matthew 您报告的错误已在 MSVC 19.22.27905 中修复。谢谢!
        • @Matthew 我添加了一条关于我能够重现错误的编译器版本和有效版本的注释,但如果你有更多信息,我将不胜感激。
        【解决方案8】:

        中文输入也有同样的问题。我的源代码是 utf8,我在编译器选项中添加了 /utf-8。它在 c++ 宽字符串和宽字符下工作正常,但在窄字符串/字符下无法工作,它在 Visual Studio 2019 调试器和我的 SQL 数据库中显示乱码字符/代码。由于转换为 SQLAPI++ 的 SAString,我必须使用窄字符。最终,我发现检查以下选项(控制面板->区域->管理->更改系统区域设置)可以解决问题。我知道这不是一个理想的解决方案,但它确实对我有帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多