V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mikewang
V2EX  ›  C

坑爹的 GBK:大家都应该去用 UTF-8

  •  2
     
  •   mikewang · 2025 年 5 月 21 日 · 13440 次点击
    这是一个创建于 239 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在把我用 C 写的一批 Linux 工具移植到 Windows 上,在字符编码上遇到了大坑。


    举个简单的例子:数文件层级。

    在 Linux 上,我们数斜杠数量就好。

    在 Windows 上,再加上反斜杠,应该就好了。——我是这样想的。

    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        int level;
        const char *p;
    
        if (argc < 2) {
            return 1;
        }
    
        for (level = 0, p = argv[1]; *p; p++) {
            if (*p == '/' || *p == '\\') {
                level++;
            }
        }
    
        printf("%d\n", level);
    
        return 0;
    }
    

    用 MinGW 的 GCC 编译一下,然后跑几个用例:

    gcc -o getlevel.exe getlevel.c
    
    C:\>getlevel C:\浙江省\宁波市\北仑区\小港街道.txt
    4
    
    C:\>getlevel C:\浙江省\宁波市\北仑区\大碶街道.txt
    5
    
    

    天塌了,这么简单的代码竟然出了 bug 。


    原来 的 编码是 {0xb4, 0x5c},其中 0x5c 和反斜杠的 ASCII 编码一模一样。

    GBK 的第一字节兼容 ASCII ,但第二字节的范围是 0x40 ~ 0xfe,与 ASCII 的 0x00 ~ 0x7f 重叠。BUG 就这么诞生了。

    UTF-8 没有这个问题的原因是:只要字节范围在 0x00 ~ 0x7f,那么就一定是 ASCII ,因为后续字节都避开了这个范围。虽然中文编码比 GB 系列长了,但是这个设计确实省了很多事。包括 strstr() strcmp() 之类的都不会出现奇奇怪怪的 bug 。


    或许我应该使用 wmain() 然后获取 wchar_t,但是 wmain() 是 Windows 特有的东西,这样做就没法和 Linux 公用同一套代码了。目前加上了 mbtowc() 作为修复。原本简洁的代码变得十分复杂:(

    说到这又不得不吐槽下 Windows 的各种奇怪 API 了,不知道它是如何存活到现在的...

    第 1 条附言  ·  2025 年 5 月 22 日

    代码是简化后的,这里不用关注路径处理的问题。

    原始代码中,输入的路径是先判断存在,再经过 POSIX 的 realpath() / Windows 上的 GetFullPathName() 一系列流程处理过的,所以可以直接数。因为这个不是重点,还请大家放过。

    99 条回复    2025-06-02 15:19:19 +08:00
    Thymolblue
        1
    Thymolblue  
       2025 年 5 月 21 日
    2025 年了 Visual Studio 中文语言的默认编码还是 GB2312 。同事改完代码一推到仓库全是乱码
    dearmymy
        2
    dearmymy  
       2025 年 5 月 21 日
    当年给公司写 mfc 程序,新手的我被 win 的各种字符串整的心理阴影。
    yolee599
        3
    yolee599  
       2025 年 5 月 21 日 via Android
    用 #if 宏来实现不同平台的条件编译就可以了啊
    henix
        4
    henix  
       2025 年 5 月 21 日
    我的处理方式是边界处全部转换成 UTF-8 ,这样内部的处理逻辑就可以保持一致了
    参考 https://utf8everywhere.org/#windows
    geelaw
        5
    geelaw  
       2025 年 5 月 21 日 via iPhone   ❤️ 13
    UTF-8 是自同步的,所以任何合法的 UTF-8 序列是另一个合法的 UTF-8 序列的子串时,必然是 Unicode 码位意义下的子串。

    无论如何 Windows 和 Linux 都没法共用一套代码,因为 Linux 上反斜线可用于文件名,因此 /a\b 在 Windows 上层数是 2 ,在 Linux 上层数是 1 。

    另外计算斜线和反斜线并不能正确得出层数,主流操作系统里 . 是本目录,.. 是上层目录(但对于根目录来说是本目录),这两个名称存在于所有目录里,需要特别处理。
    geelaw
        6
    geelaw  
       2025 年 5 月 21 日   ❤️ 16
    另外楼主似乎以为 Linux 上文件名是 UTF-8 编码的,这是错的。Linux 文件名是不含 '/' 也不含 '\0' 的任何 uint8_t 串,操作系统并不关心 U 不 UTF 的。这一点和 Win32 无甚差别:Win32 规定文件名是任何不含一些选定不可用字符的 uint16_t 串,路径分割符是 '\\' 和 '/'。

    楼主的代码在 Linux 上可用(排除上面 . 和 .. 的考虑的话),仅仅是因为 C 标准的传递参数的方式和 Linux 原生路径表示是一样的。
    wtks1
        7
    wtks1  
       2025 年 5 月 21 日
    就算写 shell 脚本现在也用 utf-8 ,gbk 保存的默认打开中文全是乱码
    hwdq0012
        8
    hwdq0012  
       2025 年 5 月 21 日
    utf8 显示到命令提示符上错误了,还不能直接用 utf82gbk 转换,因为系统会把一些乱码替换为 ”方块问号“
    跨平台必知必会的编码问题
    hwdq0012
        9
    hwdq0012  
       2025 年 5 月 21 日
    @geelaw #5 windows 的 unicode 前面可能有 bom ,不过系统默认设置是使用 local 编码,ide 也是

    高版本的 windows 才有预览版本的 utf8 功能,但很多 bug , 而且市场上已经形成 windows 上使用 local 编码程序生态了,你切了 utf8 那些软件都显示乱码
    AV1
        10
    AV1  
       2025 年 5 月 21 日   ❤️ 2
    windows 自带的控制台也是一个坑,哪怕你 chcp 65001 之后 printf 的 utf-8 编码里的中文能正常显示了,但 scanf 接收你输入的文字,还依然是 gbk 编码。
    tool2dx
        11
    tool2dx  
       2025 年 5 月 21 日   ❤️ 1
    GBK 编码挺好的,中文汉字必定是 2 字节,第一个字节必定大于 128 (0~255),我都是单独把中文和英文先筛选出来,再做处理的。
    w568w
        12
    w568w  
       2025 年 5 月 21 日   ❤️ 3
    > for (level = 0, p = argv[1]; *p; p++)

    这个处理方法是不对的,一个 char 代表「 UTF-8 编码序列中的一个字节」,不存在任何和文本相关的含义。尽管 UTF-8 有一些和 ASCII 兼容的假设,但存在很多 corner case (就像主帖提到的),所以不可靠。

    如果是高级语言,要枚举字符应当先枚举 Unicode 码点( runes )。

    用 mbtowc 转换其实也有问题。wc 指的是「空终止宽字符串」,它不等于 runes 。例如,Windows 上它代指的是经过 UTF-16LE [1] 编码的字符串,对高位字符也需要用多字节的 surrogate pairs 来占位。Linux 上可能是 UTF-32 ,但也不一定。总之,一般建议避免使用 wchar_t 。

    言而总之,如果你想枚举 UTF-8 字符串中的字符,最合规的做法是要么依赖 ICU 、utf-8 这样的字符处理库,要么用 C11 里的 mbrtoc32 ( mb -> UTF-32 )。

    [1] https://learn.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t
    bbao
        13
    bbao  
       2025 年 5 月 21 日   ❤️ 25
    我这两天好像突然穿越回到了 2010 年前后,有讨论 GBK 的,有讨论 跨域的,又看到了上古神兽 JQUERY 、TOMCAT 。
    CHTuring
        14
    CHTuring  
       2025 年 5 月 21 日
    @bbao #13 哈哈哈哈哈,同感
    ysc3839
        15
    ysc3839  
       2025 年 5 月 21 日 via Android
    不应该用 mbtowc ,这么做会产生更多问题。
    如果只需要支持 Windows 10 ,那可以在 manifest 中声明使用 UTF-8 编码,然后一律使用 UTF-8 。否则需要在调用系统 API 时手动把 UTF-8 转换成 UTF-16 ,然后调用 UTF-16 版本的 API 。
    rekulas
        16
    rekulas  
       2025 年 5 月 21 日
    12 楼说的对 虽然我不怎么写 c 但你这个判断一看就不正确
    fairytale
        18
    fairytale  
       2025 年 5 月 21 日 via Android
    Windows ?难道不应该用 CreateFileW ?
    atuocn
        19
    atuocn  
       2025 年 5 月 21 日
    #5

    ```
    UTF-8 是自同步的,所以任何合法的 UTF-8 序列是另一个合法的 UTF-8 序列的子串时,必然是 Unicode 码位意义下的子串。
    ```

    这个说法是以前不了解的。感谢
    fairytale
        20
    fairytale  
       2025 年 5 月 21 日 via Android
    Windows 如果用 ucrt140 的话,就可以全套 utf8 了(别混用 win api )
    sagaxu
        21
    sagaxu  
       2025 年 5 月 21 日
    早年还有比 GBK 更坑爹的 GB2312 ,某个大领导的“镕”字不在 GB2312 范围中
    minami
        22
    minami  
       2025 年 5 月 21 日
    这代码给人看乐了,学艺不精也能算 bug ,就跟 tcp 粘包侠一样可乐
    adoal
        23
    adoal  
       2025 年 5 月 21 日
    Windows 老老实实用 wide 版本的 API ,不要用 C style stringsc 处理和 OS API 相关的字符串,不要听什么 utf8everywhere 的鼓吹。
    geelaw
        24
    geelaw  
       2025 年 5 月 21 日
    @hwdq0012 #9 任何设计之初就没有打算适配 Windows ME 或者更低版本的 Windows 的 Windows 软件不用 UTF-16 调用 Windows API 都是自始错误的设计,因为 Windows NT 系列的最初版本 (3.1) 就是使用 UCS-2 (后来改为 UTF-16 )作为原生字符串表示的。

    @w568w #12 有必要提示其他读者:Unicode 码点的官方名字是 code point ,使用 rune 这种字母类型名字称呼 code point 似乎是 Go 引起的一种不必要的时尚潮流,而且这种时髦感也被 .NET 团队吸收了。
    kirory
        25
    kirory  
       2025 年 5 月 21 日
    std::filesystem::path {argv[1]}
    AoEiuV020JP
        26
    AoEiuV020JP  
       2025 年 5 月 21 日
    c/c++折腾跨平台就是很麻烦, 最近有一些代码需要跨平台编译出动态库, 纠结许久还是放弃 c/c++改用 go ,代价就是动态库大了一些,但代码真的很省心,很现代,
    w568w
        27
    w568w  
       2025 年 5 月 21 日
    @geelaw #24 是的,这里 [1] 也有人讨论这个问题。我用 rune 是因为我最常写的 Dart 里也吸收了这个名词。

    [1] https://learn.microsoft.com/en-us/answers/questions/2085971/why-is-system-text-rune-named-like-this
    1BF6oSYCD9ngBHo1
        28
    1BF6oSYCD9ngBHo1  
       2025 年 5 月 21 日
    没有人提到 C23 的 char8_t 吗,最近学 C23 看到个大量采用这个的库 https://github.com/micl2e2/mcpc ,震惊! C 里面也可以全程 UTF8 !
    yk000123
        29
    yk000123  
       2025 年 5 月 21 日
    偏个题。不能用斜杠、反斜杠数量来判断文件目录层级。首先 Linux 里有`.`,`..`,其次同一个文件的相对路径和绝对路径的斜杠数量也可能不同。还有,Windows 里我不清楚,但只是 Linux 里,`/path/to/file`和`//path////to///file`指向的是同一个文件。
    aloxaf
        30
    aloxaf  
       2025 年 5 月 21 日
    @vinle 这不是 utf8 吧,只是单独提出了一个类型用来表示 unicode code unit ,语义上更明确了,但没有任何编码信息
    mikewang
        31
    mikewang  
    OP
       2025 年 5 月 21 日
    @geelaw #5
    @yk000123 #29

    抱歉,其实是因为完整的代码逻辑很长,这里是我随手举的例子,没有完全说明清楚。传入的路径是标准化后的绝对路径(如 realpath() 处理后的字符串),所以不考虑 ./ ../ // 等情况了。移植到 Windows 上是做了 #ifdef _WIN32 处理的, Linux 上不做反斜杠判断。

    @geelaw #6
    Linux 上确实可以不是 UTF-8 ,正如中文 Windows 上也不一定是 GBK (可以手动改成实验状态的 UTF-8 ),但可以认为已经成为了事实上的标准。绝大多数用户使用默认配置就是这种情况了。

    @w568w #12
    在 UTF-8 上应该是可靠的(只要不是去数字符数的话)。这里的困境是:我也知道有问题,但是似乎没有办法简单解决。正如需求就是简单的数斜杠,那么真的需要引入一个 Unicode 库吗,其实我自己也是怀疑的(?)
    另外 mbtowc(),wc 是 widechar 吧,不是 NULL 空终止。

    @minami #22
    其实是说我的代码有 BUG 啦,这个代码确实学艺不精,其实我也想知道 *应该* 怎么写,或许你也可以举个例子 hhh 这是很多人都会犯的错误。但在 UTF-8 ,它是允许你这么遍历的。一个是方便我这种懒人,二是让那些欧美地区人写的这类代码也能正常跑在中文上。
    比如说 strstr() 找子串,GBK 是用不得的。utf-8 在不引入第三方库下就能这么找,是不是挺省事?;)
    lisxour
        32
    lisxour  
       2025 年 5 月 21 日
    @yk000123 其实楼主的代码加上相对路径的识别就好了,说白了缺少三种特殊处理,“.”、“..”、空白,经常和路径打交道的,这三种特殊情况,第一行 if 就开始处理了
    aloxaf
        33
    aloxaf  
       2025 年 5 月 21 日   ❤️ 2
    发现目录层级这玩意儿还是有些门道的

    `..` 其实不能被删掉,也就是说 a/c 和 a/b/../c 并不等价,因为 b 可能是一个符号链接,此时它的父目录就不是 a 。

    Rust 和 Python 的实现都是正确的,只会删掉多余的 `/` 和 `.`,并且在文档中强调了这一点
    Go 和 NodeJS 都会把 `..` 也删掉,但 NodeJS 提到了它的行为并不严格遵守 POSIX 规范
    geelaw
        34
    geelaw  
       2025 年 5 月 21 日
    @mikewang #31 一个中国生活的、使用 Windows 二次元爱好者,很可能分区是 NTFS 格式,同一个文件的文件名里既有中文,又有日语。此时无论用户的代码页是 936 (简体中文) 还是 932 (日语) 都无法通过非 Unicode API 访问此文件。
    minami
        35
    minami  
       2025 年 5 月 21 日   ❤️ 1
    @mikewang 字符编码方式永远都是 trade off 的艺术,你不能光看一项优点,就忽略了它其他方面的缺点,GBK 作为定长编码,相比变长编码还是有独到的优势的。而且默认字符编码这个问题,尊重平台特性,尊重历史兼容性,才是正确的,就像 Apple 拼尽全力,也无法彻底去掉大小写不敏感一样
    mikewang
        36
    mikewang  
    OP
       2025 年 5 月 21 日
    @geelaw #34 其实 936 是包含了平片假名的,只要没有生僻字勉强还行(
    所以我也很好奇其他 posix 程序是怎么移植过来的,毕竟大多数 API 都是 char *,到最后一步再转成 LPCWSTR 么,好像也有问题。

    好在 Windows 10 1903 往后可以通过 manifest 指定 code page 为 UTF-8 (65001)了,以后 ANSI API 应该还有发展空间:
    https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page
    bbao
        37
    bbao  
       2025 年 5 月 21 日
    那个啥,我冒失了,原谅我,楼主是个 14 岁的初中生~~~~~~~~~~~~~ 是我不够 nice 。
    mk3s
        38
    mk3s  
       2025 年 5 月 21 日
    @bbao 这就有点尬黑了,tomcat 只是集成了,啊不,人家是进化了(
    mikewang
        39
    mikewang  
    OP
       2025 年 5 月 21 日
    @bbao 别啊,我现在工作了,虽然时间不长。或许是看到了我的历史帖子,那是我注册 v 站的十周年纪念,不是说今年(
    geelaw
        40
    geelaw  
       2025 年 5 月 21 日   ❤️ 1
    @mikewang #36 我印象里见过 -U8 结尾的 Win32 API ,用这个比设置代码页为 65001 之后用 -A 要好,当然,-U8 和 -A 在面对目前的文件系统时,都不如 -W 好。

    -A 属于为了兼容性维持的 API ,内部操作都是转换为 UTF-16 之后调用 -W 的;我的理解是允许 manifest 设置 65001 是为了让 POSIX 程序最初的移植容易一点,而非作为主要存在的形式。

    因为文件系统的路径并不需要是合法的 UTF-16 ,所以直接用 -W 和文件系统交互依然是惟一正确的选择。
    buf1024
        41
    buf1024  
       2025 年 5 月 21 日   ❤️ 1
    如果用其他 char 本身就是 utf 字节的语言实现,op 会不会没有了这种感慨呢?

    要知道 c 的出现比 utf 早太多了,gbk 也比 utf 早很多。
    Shatyuka
        42
    Shatyuka  
       2025 年 5 月 21 日
    API 不一样那没办法,Windows API 要么就是本地 codepage (虽然现在能改成 UTF-8 ),要么就是 UTF-16 。
    简单移植方法是编译时添加 /utf-8 选项,入口点用 wmain ,手动把 UTF-16 转成 UTF-8 ,后面就可以和 Linux 用一套代码了。想要输出正常可能还要 SetConsoleOutputCP(CP_UTF8)
    本质上还是你代码本地化处理有问题,应该按字符遍历字符串,而不是字节。
    mikewang
        43
    mikewang  
    OP
       2025 年 5 月 21 日
    @bbao #13 其实 GB 系列编码不算老吧,GBK 有年代了,但是 GB18030-2022 是新出的,而且属于强制国标,国产化适配必须的。像不少系统原来只支持 UTF-8 ,现在要支持使用 GB18030 ,你说到底算进步还是退步哈哈
    ysc3839
        44
    ysc3839  
       2025 年 5 月 21 日 via Android
    @mikewang #36 大多数 POSIX 程序其实不管字符串编码,基本是原样传递。
    @geelaw #40 据我所知 Windows 并没有提供 UTF-8 单独的 API 。而且从 Vista 开始新增的大部分 API 都是只支持 UTF-16 的了。
    manifest 设置 UTF-8 就是为了方便移植,因为在此之前并没有一个官方的方案单独设置某个进程的代码页,要改只能改系统全局的,但改全局的又会让另一部分老 ANSI 应用炸掉。
    mikewang
        45
    mikewang  
    OP
       2025 年 5 月 21 日
    @ysc3839 #44
    原样传递是一部分,POSIX 程序还是会处理的字符串的。比如 musl 的 PATH 变量,就是通过 strchrnul() 直接分割冒号的。这个函数只按字节处理。看了一下 glibc 也是一样的。
    非 UTF-8 下就有出问题的风险。所以 UTF-8 的设计是很好的,GBK 和 GB18030 就差那么一点(其实我想说明的也就是这个意思)

    https://git.musl-libc.org/cgit/musl/tree/src/process/execvp.c
    ysc3839
        46
    ysc3839  
       2025 年 5 月 21 日 via Android
    @mikewang 但是很远古的 Linux 系统也有使用 GBK ,似乎没有炸掉的情况,可能是用户主动避免了
    unused
        47
    unused  
       2025 年 5 月 21 日
    @mikewang UTF-8 也不能避免文件名包含冒号,只能说编码有 0x3A 的目录名不能用于 PATH
    ysc3839
        49
    ysc3839  
       2025 年 5 月 21 日 via Android
    @geelaw 这是少部分特殊 API ,我之前没用过,不知道。其他绝大部分常用 API 都不支持 UTF-8 。
    datou
        50
    datou  
       2025 年 5 月 21 日
    @mikewang https://www.gov.cn/ 都是 utf-8 了,还有信创产品强制 GBK 的吗?
    fairytale
        51
    fairytale  
       2025 年 5 月 21 日 via Android
    如果楼主写的是纯 c/c++程序,不依赖系统 api ,比如用的 fopen 或者 std::filesystem ,而不是 open/CreateFileW ,那么在 Windows 设置链接 ucrt.lib 就是 utf8 (默认是 libcmtd.lib )。无需任何代码修改,跨平台。
    tool2dx
        52
    tool2dx  
       2025 年 5 月 21 日
    @ysc3839 “大多数 POSIX 程序其实不管字符串编码,基本是原样传递。”

    小米安卓系统,一般来说保存的文件名应该用的是 UTF8 ,不小心写成了 GBK ,结果就炸了,文件死活删不掉。V 站貌似还有帖子,一直解决不了。
    kneo
        53
    kneo  
       2025 年 5 月 21 日 via Android
    数斜杠就好?只能说你还有的坑。
    fairytale
        54
    fairytale  
       2025 年 5 月 21 日 via Android
    简单点,用这个 mingw-w64-ucrt-x86_64 啥都不改。mingw-w64 默认还是 codepage ,带 ucrt 的是 utf8 。编码转换在 Windows 系统 ucrtbase.dll 里自动转换。
    fairytale
        55
    fairytale  
       2025 年 5 月 21 日 via Android
    @tool2dx “大多数 POSIX 程序其实不管字符串编码,基本是原样传递。”是对的。编码只是 shell 输入输出用,内核不在乎编码。LC_ALL=xxx sh 启动新 shell 就能删了。
    c3de3f21
        56
    c3de3f21  
       2025 年 5 月 21 日
    @bbao #12 话说用小动物做 logo 是什么时候开始的?
    ysc3839
        57
    ysc3839  
       2025 年 5 月 21 日 via Android
    @fairytale #51 这么做并不行,链接 ucrt.lib 只是动态链接 ucrtbase.dll ,内部实现都是一样的,都是走 fopen->_open->CreateFileA ,仍然有编码问题
    @tool2dx #52 Android 有 fuse 进行了一层过滤,可能会存在一些问题。
    fairytale
        58
    fairytale  
       2025 年 5 月 21 日 via Android
    @mikewang 楼主你用 mingw-w64-ucrt-x86_64 彻底解决编码烦恼 utf8 一路畅行
    fairytale
        59
    fairytale  
       2025 年 5 月 21 日 via Android
    @ysc3839 ucrtbase.dll 内置了 utf8 支持,暴露的 api 的输入输出全是 utf8 ,翻译到系统就是 utf16 ,过程中没有任何 gbk 参与,emoj 兼容
    ysc3839
        60
    ysc3839  
       2025 年 5 月 21 日 via Android
    @fairytale 并不是。自从 VS2015 开始,MSVC CRT 中一些通用 C 语言函数被移了出来,作为系统组件,随系统升级,称为 Universal CRT 。只要是 VS2015 及之后版本编译的程序默认都会使用 UCRT ,但是仍然不会使用 UTF-8 编码。
    可以自己写个程序试试用 fopen 打开文件,我刚刚实测是无法打开的。
    ysc3839
        61
    ysc3839  
       2025 年 5 月 21 日 via Android
    @fairytale 我跟踪了一下 fopen ,内部是有转换机制的,会根据__acrt_get_utf8_acp_compatibility_codepage()返回的代码页进行转换,而这个函数是根据 C locale 来返回的。
    所以 UTF-8 转换功能确实存在,但是并不是默认启用的,必须修改 locale 才会启用。
    Alias4ck
        62
    Alias4ck  
       2025 年 5 月 21 日
    跨平台永远都是最麻烦的话题
    newtype0092
        63
    newtype0092  
       2025 年 5 月 21 日
    @bbao #13 还有几个讨论 Vim 和 Emacs 的哈哈哈
    realpg
        64
    realpg  
    PRO
       2025 年 5 月 21 日
    问题从来不在编码
    而在于你写的代码

    你使用了一个非跨平台的各自平台编译器 你想让程序跨平台能运行 那么默认就是你自己处理各个平台兼容性问题
    而你处理不好就开始怒喷了
    si
        65
    si  
       2025 年 5 月 21 日   ❤️ 1
    GB2312 制定的时候两个字节都是大于 0x80 的,微软搞 GBK 的时候为了塞下更多汉字把 0x40-0x80 也用了,GBK 随着 Windows 应用的太广了,变成事实上的标准,后面再制定 GB18030 的时候也只能选择兼容 GBK ,所以处理起来就比较麻烦了。
    如果没有特殊需求,最好别用 GBK ,遇到不支持的字符处理不了。
    mikewang
        66
    mikewang  
    OP
       2025 年 5 月 22 日
    @realpg #64
    > 问题从来不在编码
    我不赞成,编码可以分优劣。

    > 而在于你写的代码
    在 GBK 的条件下,代码确实是有问题的。

    > 你使用了一个非跨平台的各自平台编译器 你想让程序跨平台能运行 那么默认就是你自己处理各个平台兼容性问题
    > 而你处理不好就开始怒喷了
    是的,我正在写一个跨平台的 C 库,正在处理这些问题。与其说“处理不好”,倒不如说“很难处理好”。
    例如,很多人都说过,不要把 Windows 用户目录设置为中文,因为很多软件会报错。具体地说,我在上面举的 musl execvp() 函数,最终就是用 char * 遍历的。
    当一个问题普遍存在时,我们就要思考问题的根源,比如比较 GBK 和 UTF-8 在设计上的优劣。

    跨平台的东西也是人写出来的,方便了大家,但是写起来不舒服,请允许我吐槽一下。


    @si #65
    > GB2312 制定的时候两个字节都是大于 0x80 的,微软搞 GBK 的时候为了塞下更多汉字把 0x40-0x80 也用了。
    赞,这么看来 GB2312 倒是完全兼容 ASCII 的。我不认可 GBK 的原因主要就是第二字节侵占了 ASCII 码范围,产生了麻烦。最终 GB18030 还是拓宽到了四字节,当初不如直接加字节来的痛快。
    geelaw
        67
    geelaw  
       2025 年 5 月 22 日
    @datou #50 好像说明不了啥,因为 HTML 5 惟一合规的编码是 UTF-8 。
    mikewang
        68
    mikewang  
    OP
       2025 年 5 月 22 日
    @datou #50
    是需要支持的,有认证,但可以有配置使用其他字符集。对于信创看来,UTF-8 (或者准确说 Unicode )显然不够自主,万一 IRG 卡你脖子不收录新汉字怎么办
    si
        69
    si  
       2025 年 5 月 22 日
    @mikewang
    GBK 兼容 GB2312 ,所以已经取代 GB2312 成为事实上的标准,GB 编码的程序和数据都是以 GBK 为标准的。
    GB18030 只是为了扩展 GB 编码收录 Unicode 中的字符,再搞一种不兼容的编码没有太大意义。
    我觉得可能当时的想法是准备切换到 Unicode ,没有考虑扩展 GB2312 ,结果微软出个 GBK 把路卡死了,再制定标准也只能捏着鼻子兼容 GBK 了。
    shimanooo
        70
    shimanooo  
       2025 年 5 月 22 日
    你能保证 UTF-8 里没有 0x5c 吗?
    应该按字来处理,而不是字节。一个字可能占多个字节,要按编码规则整体递进。
    FeS
        71
    FeS  
       2025 年 5 月 22 日 via Android
    这个问题 sdl 有个解决方案,即用宏定义来替换 main 函数,自己再写一套 main 函数,在 Windows 平台上先进入重定向的 main 函数转换 argv 的编码
    不过这种方法挺丑陋的,而且也会失去 main 自动加 return 0 的特性,好处是不用改动代码(参考 sdlmain )

    不过话说回来,其实也没必要从 main 函数去收 argv ,直接调用系统 api 拿就好了,linux 好像是可以从某个路径读(忘了),这样可以脱离 main 函数从任意位置获取命令行参数了,编码也可以完全自己控制。
    mikewang
        72
    mikewang  
    OP
       2025 年 5 月 22 日
    @shimanooo #70 UTF-8 保证 `0x5c` 就是 `\`。正是我想说明的地方。
    可以看图:


    0 开头只有 0yyyzzzz 的形式,是单字节。多字节都是 1 开头的。虽然多字节浪费了一些空间,但是处理起来高效呀。
    NPC666
        73
    NPC666  
       2025 年 5 月 22 日 via Android
    “你知道 string 有几种写法吗?”
    crab
        74
    crab  
       2025 年 5 月 22 日
    @shimanooo 有 0x5c 就是 ascii 范围内的了。其余字节都是大于等于 0x80 。
    ysc3839
        75
    ysc3839  
       2025 年 5 月 22 日 via Android
    @FeS POSIX 下没有通用的取进程参数的方法,Linux 下可以读/proc/self/cmdline ,但其他系统就不一定有/proc 了。
    另外 Linux 下可以通过修改 argv 指向的 buffer 来实现修改进程名,但不能通过/proc 修改。
    mark2025
        76
    mark2025  
       2025 年 5 月 22 日
    @Thymolblue
    .editorconfig 文件解决

    # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
    # Editor configuration, see http://editorconfig.org

    root = true

    [*]
    charset = utf-8
    end_of_line = lf
    indent_style = space
    indent_size = 2
    insert_final_newline = true
    trim_trailing_whitespace = true

    [*.md]
    max_line_length = off
    trim_trailing_whitespace = false
    mark2025
        77
    mark2025  
       2025 年 5 月 22 日
    @si 当年就是准备从 gb2312 进化到 unicode 的,然而微软大力推销它的 gbk ( gb2312 是国标,gbk 是行标)导致路线错误,zf 很生气于是是在 office2001 ( 2003 ?) 快要发布之前强制规定所有软件必须兼容 gb18030 狠狠地恶心了一把微软(返工增加对 gb18030 的兼容)。
    mark2025
        78
    mark2025  
       2025 年 5 月 22 日
    @mikewang 我有 GB18030
    tairan2006
        79
    tairan2006  
       2025 年 5 月 22 日
    Windows 系统的兼容性带来的坏处
    HTravel
        80
    HTravel  
       2025 年 5 月 22 日
    数文件层级居然能是简单的数斜杠数?那你这 linux 版本来质量也就不咋地。

    至少命令行中,cd ..、cd ../../这类还是少吗?
    mikewang
        81
    mikewang  
    OP
       2025 年 5 月 22 日 via iPhone
    @HTravel #80
    是简化后的代码,因为这个不是重点。整块太长了,再加上异常处理流程等等,我估计大伙都不愿意看。

    入参是被 realpath()标准化后的路径,不存在这种了。
    HTravel
        82
    HTravel  
       2025 年 5 月 22 日
    @mikewang C 和 C++中,字符串从来就没一个统一标准啊。既然你都标准化路径了,按理说,在 C 家族这类把字符串看作字节数组的语言里,更应该能想到,要把各种字符串转换成同一种编码再处理啊。而且现在的文件命名,很可能碰到✨➔✅❌这类 emoji 字符,你怎么敢不转换成 UTF8 这种可以包含所有字符集的编码就处理的
    realJamespond
        83
    realJamespond  
       2025 年 5 月 22 日
    vs 基本概念不都是要 wchar 的么
    Bazingal
        84
    Bazingal  
       2025 年 5 月 22 日
    @Thymolblue @mark2025 vs2022 17.3 已经可以设置指定编码保存文件了,在工具-选项-Environment-文档-使用特定编码保存文件
    jackmod
        85
    jackmod  
       2025 年 5 月 22 日
    程序内部应当只使用 utf8 。和操作系统交互的边界需要特殊处理。
    win 下的边界是把 uint8_t 的 utf8 串和 uint16_t(wchar_t)的 utf16 互转。
    不仅文件名需要转换,打开文件的操作也需要包装一下 OpenFileW 。
    linux 下直接用 uint8_t 的 utf8 。另外 linux 下的 wchar_t 是 uint32_t 。
    YetToCome
        86
    YetToCome  
       2025 年 5 月 22 日
    编码转换就是屎中屎,之前看到某大厂出品的跨国代码时,codepage 相关的核心代码大概就有几千行还不包括重复的玩意。
    iugo
        87
    iugo  
       2025 年 5 月 22 日
    有一些比较老的硬件, 只能接受 GB 18030. 并且这一字符集是现行国家标准: https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=A1931A578FE14957104988029B0833D3
    mikewang
        88
    mikewang  
    OP
       2025 年 5 月 22 日
    @HTravel #82 那肯定是不敢,不然我也不会发帖了。所以回到标题:大家快点统一 UTF-8 ,少一层转换,大家都省事。
    对于 Windows ,应该把 ANSI API 的 UTF-8 支持好。事实上 POSIX 的大多 API 就是微软所谓的 ANSI API 。
    Unicode API 的想法是好,但是最后 UTF-16 还是变成了变长编码(代理对),实在是又吃了亏。(路线走错了)
    sapphire
        89
    sapphire  
       2025 年 5 月 22 日
    C 发展到现在,程序员还不区分字节流和字符串这两种东西吗?如果是做应用,那不应该出现字符串里数目录分隔符这种事情的,老老实实用库就好。
    chocotan
        90
    chocotan  
       2025 年 5 月 22 日
    说个曾经遇到的。
    apache httpclient 库发起 GET 请求,query 如果使用 GBK 编码,部分生僻字使用同为 apache 家的 tomcat 可以正常接收,使用 spring-cloud-gateway 接收就会变成乱码。
    实际原因也是一样——GBK 的一些生僻字复用了 ASCII ,不同的解码逻辑会导致不同的结果。
    后来把 tomcat 的解码逻辑复制到 gateway 中了。至于为什么要用 GBK ,是历史遗留问题。
    mikewang
        91
    mikewang  
    OP
       2025 年 5 月 22 日
    @sapphire #89
    不是应用,我在做一个基础的跨平台库,尽力兼顾简洁、性能、准确性。调库直接转换应该是省事,但是我不想搞得太臃肿(比如在 OpenWRT 路由器下面也能运行?)
    其实支持中文只是我的一个想法,当初程序只支持 ASCII 。后来我发现引入 UTF-8 代价很小,大多数代码都没问题。在后来我引入了 Windows 支持,就遇到了 GBK 。它对原先 ASCII 的代码兼容不好。然后我就吐槽了。
    sapphire
        92
    sapphire  
       2025 年 5 月 22 日   ❤️ 1
    @mikewang 如果你是库开发者,那数分隔符无可厚非,但是要分开字符串和字节流,UTF-8 和 GBK 都是字节流,如果你不打算像其他现代语言那样都处理成“真”字符串,希望直接处理 UTF-8 字节流,可以把其他编码的字节流转换到 UTF-8 上,而不是在逻辑层面还考虑多种编码的问题。
    fairytale
        93
    fairytale  
       2025 年 5 月 22 日 via Android
    @ysc3839 set 一下就行
    fairytale
        94
    fairytale  
       2025 年 5 月 22 日 via Android
    @jackmod 只用纯 c/c++标准库,用 mingw-w64-ucrt 编译环境就能一字不改在 Windows 全程 utf8 。
    fairytale
        95
    fairytale  
       2025 年 5 月 22 日 via Android
    @mikewang 新环境能统一,老环境算了。utf8 出现的太晚了。比如马屁股决定了火箭直径这事,换标准意味着基础设施全换。Windows 不可能舍弃兼容性的。不像 mac ,说不兼容就不兼容。
    xinyu391
        96
    xinyu391  
       2025 年 5 月 22 日
    c++17 的 filesystem 呢
    是不是 屏蔽平台差异?
    hsj1992
        97
    hsj1992  
       2025 年 5 月 22 日
    我也碰见过,情况是 windows 跟 wsl 后端的 docker 之间,我直接在 windows 系统上运行命令去备份 docker 里的数据库。就因为两边的编码不一致错误导致丢失字符,每个中文词结尾的文字,三位编码丢失了最后一位变成??,以及中文里的圆括号等丢失,最后自己磨了快一个月,靠着三个字符的前两个还在,整体词语也在,把这个上万行的数据库备份文件里的丢失靠猜的补齐了。
    Overfill3641
        98
    Overfill3641  
       2025 年 5 月 23 日
    UTF-8 BOM → UTF-16 BOM → UTF-8 → ANSI
    UTF-8 有些字符会出现奇奇怪怪的问题。
    bclerdx
        99
    bclerdx  
       2025 年 6 月 2 日
    @sagaxu 不在 GB2312 范围内,然后呢?
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   5711 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 65ms · UTC 06:39 · PVG 14:39 · LAX 22:39 · JFK 01:39
    ♥ Do have faith in what you're doing.