Cygwin环境下如何禁止MSVC编译程序在tty中的输出转码
问题描述
在Cygwin环境下,gcc与MSVC编译的程序在tty中运行时存在输出差异:仅MSVC生成的二进制文件在tty输出8位高位被置1的字符时,会出现异常字符集转码,相关终端输出现象复现如下:
! pwd /tmp/demo_dir ! echo $LC_ALL "," $LANG "," $LC_CTYPE , , ! ./compiled_with_gcc.exe | hexdump 0000000 cece cece 0000004 ! ./compiled_with_msvc.exe | hexdump 0000000 cece cece 0000004 ! ./compiled_with_gcc.exe ▒▒▒▒! ! ./compiled_with_msvc.exe ╬╬╬╬!
两款程序输出的原始字节完全一致,理论上最后两行输出应当相同,但只有MSVC编译的程序在无管道的tty场景下输出被修改,gcc编译的程序输出经过tty时无改动,该现象在Cygwin终端和xterm中均可复现。
经排查转码发生在tty层而非终端层:独立测试显示运行gcc编译的程序时,tty正常返回预期的0xce字节;运行MSVC编译的程序时,相同tty返回的是0x8ec3序列;而使用管道时二者均输出0xce字节。MSVC编译程序通过tty输出的字节长度明显增加,结合Cygwin默认使用UTF-8的特性,推测Cygwin对MSVC程序做了额外的转码处理,而gcc程序不会触发该逻辑。
已尝试的无效方案包括:
- 设置LC_ALL等环境变量为C locale
- 在程序入口调用
setlocale(0,"") - 配置所有
_fmode、setmode()等二进制模式组合
测试用输出程序为同一份C源码,仅调用printf或write输出指定字节,MSVC版本使用Windows10环境下的Visual Studio 2019 cl.exe编译。
复现代码
测试源码code.c
#ifndef __CYGWIN__ #include <windows.h> #else #include <unistd.h> #endif #include <io.h> #include <fcntl.h> #include <locale.h> int main() { if(!setlocale(LC_ALL, "")) { return 77; //historically: non-filesystem permission-denied exit-code } #ifndef __CYGWIN__ _setmode(1,_O_BINARY); _set_fmode(_O_BINARY); #endif char *dat="\316\316\316\316"; write(1,dat,4); // printf/fflush here gives same results. return 0; }
MSVC编译脚本ms_cl.bat
@echo off :: Load compilation environment call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvars64.bat" :: Invoke compiler with any options passed to this batch file "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30037\bin\Hostx64\x64\cl.exe" /std:c17 %* kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib
编译运行步骤
# 编译MSVC版本 ! ms_cl.bat code.c # 运行测试 ! ./code.exe | hexdump ! ./code.exe # 编译gcc版本 ! gcc code.c # 运行测试 ! ./a.exe | hexdump ! ./a.exe
该现象是Cygwin对原生Windows程序(非Cygwin-gcc编译、未链接cygwin1.dll的程序)的默认输出转码逻辑导致的:Cygwin的PTY会默认将原生Windows程序的输出从系统默认ANSI代码页转码为UTF-8,你输出的0xce在GBK/CP936代码页中对应字符╬,转成UTF-8就是0xc3 0x8e,和观测到的转码结果完全一致。
要关闭该转码,可通过以下两种方案实现:
方案1:全局设置Cygwin禁用原生程序输出转码
在Cygwin shell的启动配置文件(~/.bashrc或~/.zshrc)中添加如下环境变量:
export CYGWIN="$CYGWIN disable_pcon"
该参数会禁用Cygwin的ConPty适配逻辑,同时关闭对原生Windows程序的默认字符集转码。配置完成后重启Cygwin终端即可生效。
方案2:单程序运行时临时关闭转码
如果不想全局修改配置,可以在运行MSVC编译的程序时,直接通过管道跳过tty转码逻辑:
./code.exe | cat
该方案可以让MSVC编译的程序直接输出原始字节,不再触发Cygwin的自动转码逻辑。
内容的提问来源于stack exchange,提问作者user12

