VSCode 雖然可以使用 C/C++ 延伸模組開發 C/C++ 程式,不過因為 Windows 平台的特殊性,所以實務上你可能會遇到中文文字編碼的問題。
預設的輸出輸入編碼
我們以底下這個顯示終端機文字輸入輸出編碼的程式為例來觀察編碼的變化:
#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{
cout << "Console input encoding: " << GetConsoleCP() << endl;
cout << "Console output encoding: " << GetConsoleOutputCP() << endl;
return 0;
}
在 VSCode 中,我們先開啟終端機來觀察預設的編碼,首先是 PowerSehll 5:
PS C:\Users\meebo\code\cpp> $PSVersionTable.PSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 26100 2161
PS C:\Users\meebo\code\cpp> .\encoding.exe
Console input encoding: 950
Console output encoding: 950
接著是 PowerShell 7:
# $PSVersionTable.PSVersion
Major Minor Patch PreReleaseLabel BuildLabel
----- ----- ----- --------------- ----------
7 4 6
# .\encoding.exe
Console input encoding: 950
Console output encoding: 950
以及 cmd:
C:\Users\meebo\code\cpp>ver
Microsoft Windows [版本 10.0.26100.2605]
C:\Users\meebo\code\cpp>.\encoding.exe
Console input encoding: 950
Console output encoding: 950
你可以看到繁體中文版 Windows 上各種 shell 預設輸入輸出的文字編碼都是字碼頁 950 的 Big5 繁體中文。
C/C++ 延伸模組會強制採用 utf-8 編碼
不過 C/C++ 延伸模組執行程式時是透過 WindowsDebugLauncher,它會把終端機的輸出輸入編碼都改為 utf-8,不管你的 VSCode 預設採用哪一種 shell,執行結果都會是這樣:
# & 'c:\Users\meebo\scoop\apps\vscode\1.96.3\data\extensions\ms-vscode.cpptools-1.22.11-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-ftut4mzx.zwc' '--stdout=Microsoft-MIEngine-Out-su2vyt31.p3w' '--stderr=Microsoft-MIEngine-Error-3dspx1tz.n14' '--pid=Microsoft-MIEngine-Pid-cywhpozy.otf' '--dbgExe=C:\Users\meebo\scoop\shims\gdb.exe' '--interpreter=mi'
Console input encoding: 65001
Console output encoding: 65001
你可以看到終端機的輸出輸入都變成採用字碼頁 65001 的 utf-8 編碼,也就是說,如果你以為終端機預設使用 Big5 編碼,所以就採用 Big5 編碼的文字送出,就會被當成 utf-8 編碼解譯錯誤變亂碼。舉例來說,以下這個送出 "測試" 兩個字 Big5 編碼的程式檔:
#include <iostream>
using namespace std;
int main()
{
// "測試" 的 Big5 編碼為 0xB4FA 0xB8D5
cout << "\xB4\xFA\xB8\xD5" << endl;
return 0;
}
透過 C/C++ 延伸模組的執行結果如下:
# & 'c:\Users\meebo\scoop\apps\vscode\1.96.3\data\extensions\ms-vscode.cpptools-1.22.11-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-mryt1wru.tik' '--stdout=Microsoft-MIEngine-Out-y3eekozn.ws1' '--stderr=Microsoft-MIEngine-Error-ohiqqkkb.vhb' '--pid=Microsoft-MIEngine-Pid-iks4j4h0.j5l' '--dbgExe=C:\Users\meebo\scoop\shims\gdb.exe' '--interpreter=mi'
����
你可以看到輸出變成亂碼了,但如果你接著在同一個終端機中直接執行編譯好的檔案:
# .\print_big5.exe
測試
其實是正常的,這就是因為 C/C++ 延伸模組把 Big5 編碼的文字當成 uft-8 解讀的關係。如果換成以下這個輸出 "測試" 兩字 utf-8 編碼的程式:
#include <iostream>
using namespace std;
int main()
{
// "測試" 的 utf-8 編碼為 0xE6 0xB8 0xAC 0xR8 0xA9 0xA6
cout << "\xE6\xB8\xAC\xE8\xA9\xA6" << endl;
return 0;
}
就會變成在 C/C++ 延伸模組執行正常顯示,但是接著在終端機中直接執行會變亂碼了:
# & 'c:\Users\meebo\scoop\apps\vscode\1.96.3\data\extensions\ms-vscode.cpptools-1.22.11-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-v21amcfg.pjg' '--stdout=Microsoft-MIEngine-Out-k5iwq2gi.oev' '--stderr=Microsoft-MIEngine-Error-g4udotjf.znd' '--pid=Microsoft-MIEngine-Pid-cpnn5efq.cbc' '--dbgExe=C:\Users\meebo\scoop\shims\gdb.exe' '--interpreter=mi'
測試
# .\print_utf8.exe
皜祈岫
這就是因為 C/C++ 延伸模組採用 utf-8 編碼,但執行之後終端機的輸出編碼復原為 Big5 編碼的關係。
C/C++ 延伸模組不會復原輸入編碼
更麻煩的是,從剛剛的結果看起來,似乎 C/C++ 模組執行完後會復原終端機原本的編碼,不過實際上它只復原了輸出文字的編碼,輸入文字的編碼仍然是 utf-8,這可以透過再次執行剛剛看過的 encoding 程式或者下達指令來確認:
# .\encoding.exe
Console input encoding: 65001
Console output encoding: 950
# [Console]::InputEncoding.EncodingName
Unicode (UTF-8)
# [Console]::OutputEncoding.EncodingName
Chinese Traditional (Big5)
我們可以透過以下這個簡單的程式來確認輸入文字採用的編碼:
#include <stdio.h>
#include <unistd.h>
int main( ) {
unsigned char c;
int count;
while((count=read(0, &c, 1))>0) {
printf("\\x%02X", c);
if(c==0x0a) {
return 0;
}
}
return 0;
}
它會把輸入的文字一個一個位元組印出來,透過 C/C++ 延伸模組執行如下:
# & 'c:\Users\meebo\scoop\apps\vscode\1.96.3\data\extensions\ms-vscode.cpptools-1.22.11-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-uazxwyvw.wjc' '--stdout=Microsoft-MIEngine-Out-w0qz4rdi.rgw' '--stderr=Microsoft-MIEngine-Error-ptpraqag.z2k' '--pid=Microsoft-MIEngine-Pid-fauxn3rk.ra5' '--dbgExe=C:\Users\meebo\scoop\shims\gdb.exe' '--interpreter=mi'
測試
\xE6\xB8\xAC\xE8\xA9\xA6\x0A
你可以看到它輸出的是 "測試" 的 utf-8 編碼,即使在 C/C++ 延伸模組執行後接著直接執行:
# .\getch.exe
測試
\xE6\xB8\xAC\xE8\xA9\xA6\x0A
結果也是一樣。但是如果你在本文一開始檢視終端機輸出輸入編碼時的 PowerShell 5 中測試:
PS C:\Users\meebo\code\cpp> .\encoding.exe
Console input encoding: 950
Console output encoding: 950
PS C:\Users\meebo\code\cpp> .\getch.exe
測試
\xB4\xFA\xB8\xD5\x0A
就會看到輸出結果是 "測試" 的 Big5 編碼。所以如果你有一個像是以下這樣簡單的程式:
#include <iostream>
using namespace std;
char s[20];
int main()
{
cin >> s;
cout << s << endl;
return 0;
}
執行結果就會是這樣:
# & 'c:\Users\meebo\scoop\apps\vscode\1.96.3\data\extensions\ms-vscode.cpptools-1.22.11-win32-x64\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-e11e0kys.134' '--stdout=Microsoft-MIEngine-Out-zjw1kn0z.0mh' '--stderr=Microsoft-MIEngine-Error-2nzxpcy2.uhm' '--pid=Microsoft-MIEngine-Pid-0phnnj0p.icr' '--dbgExe=C:\Users\meebo\scoop\shims\gdb.exe' '--interpreter=mi'
測試
測試
# .\simple_echo.exe
測試
皜祈岫
在 C/C++ 延伸模組執行因為輸入與輸出的編碼是一致的,所以可以正常顯示;但是接著單獨執行時,就會因為現在輸入是 utf-8 但輸出是 Big5 而錯亂。如果我們強制設定輸出採用和輸入一樣的編碼:
# [Console]::OutputEncoding = [Console]::InputEncoding
# .\simple_echo.exe
測試
測試
就可以正常顯示了。
結語
我不知道為什麼 C/C++ 延伸套件要這樣設計,理論上應該是要套用終端機本身採用的輸入輸出編碼才對,否則只是要寫寫簡易的程式,可能就會被搞得大亂,尤其是初學者應該更是昏頭。在 github 上也有人提到了相同的問題。
在其他平台上,因為終端機預設的輸入輸出編碼都是 utf-8,不會像是 Windows 這樣終端機本身的編碼和 C/C++ 延伸模組採用的編碼不一致,就不會有這樣的問題。
Top comments (0)