为什么 LC_ALL setlocale 设置会影响 Powershell 中的 cout 输出?

Why does LC_ALL setlocale setting affect cout output in Powershell?

我正在尝试了解我所看到的一些行为。

我有这个 C++ 程序:

// Outputter.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>


int main()
{
    // UTF-8 bytes for "日本語"
    std::cout << (char)0xE6 << (char)0x97 << (char)0xA5 << (char)0xE6 << (char)0x9C << (char)0xAC << (char)0xE8 << (char)0xAA << (char)0x9E;
    return 0;
}

如果我 运行 在 Powershell 中执行以下操作:

[System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
.\print_it.exe # This is the above program ^
日本語 # This is the output as displayed in Powershell

然后 日本語 被打印并在 Powershell 中正确显示。

但是,如果我将 setlocale(LC_ALL, "English_United States.1252"); 添加到代码中,如下所示:

int main()
{
    setlocale(LC_ALL, "English_United States.1252");

    // UTF-8 bytes for "日本語"
    std::cout << (char)0xE6 << (char)0x97 << (char)0xA5 << (char)0xE6 << (char)0x9C << (char)0xAC << (char)0xE8 << (char)0xAA << (char)0x9E;
    return 0;
}

程序现在将垃圾打印到 Powershell(日本語 准确地说,这是代码页 1252 对那些字节的错误解释)。

但是,如果我将输出通过管道传输到一个文件,然后对该文件进行 cat,它看起来不错:

.\print_it.exe > out.txt
cat out.txt
日本語 # It displays fine, like this, if I redirect to a file and cat the file.

此外,Git bash 无论我 setlocale 做什么,都能正确显示输出。

有人可以帮我理解为什么 setlocale 会影响输出在 Powershell 中的显示方式,即使将相同的字节写入 stdout 也是如此?似乎 Powershell 能够以某种方式访问​​程序的语言环境并使用它来解释输出?

Powershell 版本为 5.1.17763.592。

一切都与编码有关。使用 > 重定向获得正确字符的原因是 > 重定向默认使用 UTF-16LE。所以你设置的编码1252会自动转换为UTF-16。

根据您的 PowerShell 版本,您可以或不能更改重定向的编码。

如果您将 Out-File-Encoding 开关一起使用,您可以更改目标文件的编码(再次取决于您的 PowerShell 版本)。

我建议阅读有关此主题的优秀 mklement0 post

根据评论编辑

取自cppreference

std::setlocale C++ Localizations library Defined in header <clocale>

char* setlocale( int category, const char* locale);

The setlocale function installs the specified system locale or its portion as the new C locale. The modifications remain in effect and influences the execution of all locale-sensitive C library functions until the next call to setlocale. If locale is a null pointer, setlocale queries the current C locale without modifying it.

您发送到 std::cout 的字节是相同的,但 std::cout 是一个区域设置敏感函数,因此它优先于您的 PowerShell UTF-8 设置。如果您省略 setlocale() 函数,则 std::cout 遵循 shell 编码。

如果您有 Powershell 5.1 及更高版本,>Out-File 的别名。您可以通过 $PSDefaultParameterValues:

设置编码

像这样:

$PSDefaultParameterValues['Out-File:Encoding'] = 'UTF8'

然后你会得到一个 UTF-8 文件(带有 BOM,这很烦人!)而不是默认的 UTF-16LE。

编辑 - 根据 OP

的要求添加一些细节

PowerShell 使用 OEM 代码页,因此默认情况下您将获得在 windows 中设置的内容。我建议阅读 上的优秀 post。关键是,如果你的 UTF8 设置没有设置为 powershell,你就在你的代码页上。

output.exe 在 c++ 程序中将语言环境设置为 English_United States.1252output_original.exe 未对其进行任何更改:

这里是 没有 UTF8 PowerShell 设置的输出:

c:\t>.\output.exe
æ-¥æo¬èªz  --> nonsese within the win1252 code page
c:\t>.\output.exe | hexdump
0000000 97e6 e6a5 ac9c aae8 009e --> both hex outputs are the same!
0000009
c:\t>.\output_original.exe
日本語  --> nonsense but different one! (depens on your locale setup - my was English)
c:\t>.\output_original.exe | hexdump
0000000 97e6 e6a5 ac9c aae8 009e  --> both hex outputs are the same!
0000009

那么这里发生了什么?您的程序根据程序本身设置的区域设置或 windows(在我的虚拟机上是 OEM 代码 1252)给出输出。请注意,在两个版本中,hexdump 是相同的,但输出不同(带有编码)。

如果您使用 [System.Text.Encoding]::UTF8:

将 PowerShell 设置为 UTF8
PS C:\t> [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
PS C:\t> .\output.exe 
日本語  --> the english locales 1252 set within program notice that the output is similar to the above one (but the hexdump is different)
PS C:\t> .\output.exe | hexdump
0000000 bbef 3fbf 3f3f 0a0d  -> again hex dump is same for both so they are producing the same output!
0000008
PS C:\t> .\output_original.exe
日本語 --> correct output due to the fact you have forced the PowerShell encoding to UTF8, thus removing the output dependence on the OEM code (windows)
PS C:\t> .\output_original.exe | hexdump
0000000 bbef 3fbf 3f3f 0a0d -> again hex dump is same for both so they are producing the same output!
0000008

这里发生了什么?如果您在 C++ 应用程序中强制设置语言环境,std:cout 将使用该语言环境 (1252) 进行格式化,然后这些字符将转换为 UTF8 格式(这就是第一个和第二个示例略有不同的原因)。当您不在您的 C++ 应用程序中强制使用语言环境时,将采用 PowerShell 编码,现在是 UTF8,您将获得正确的输出。

我觉得有趣的一件事是,如果您将 windows 系统区域设置更改为与中文兼容的区域(中国、澳门、柴湾、香港等),那么在不强制使用 UTF8 时您将获得一些中文字符, 但不同的。这意味着那些字节只是 Unicode,因此只有在那里它才有效。如果您在 PowerShell 中强制使用 UTF8,即使使用中文 windows 系统语言环境,它也能正常工作。

我希望这能在更大程度上回答您的问题。

咆哮: 我花了很长时间才调查,因为 VS 2019 社区版已过期(WFT MS?),我无法注册它,因为寄存器 window 完全空白。谢谢 MS 但不用谢。