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

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

  •  2
     
  •   mikewang · 18 天前 · 10935 次点击

    最近在把我用 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 条附言  ·  17 天前

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

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

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

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

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

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

    高版本的 windows 才有预览版本的 utf8 功能,但很多 bug , 而且市场上已经形成 windows 上使用 local 编码程序生态了,你切了 utf8 那些软件都显示乱码
    DOLLOR
        10
    DOLLOR  
       18 天前   ❤️ 2
    windows 自带的控制台也是一个坑,哪怕你 chcp 65001 之后 printf 的 utf-8 编码里的中文能正常显示了,但 scanf 接收你输入的文字,还依然是 gbk 编码。
    tool2dx
        11
    tool2dx  
       18 天前   ❤️ 1
    GBK 编码挺好的,中文汉字必定是 2 字节,第一个字节必定大于 128 (0~255),我都是单独把中文和英文先筛选出来,再做处理的。
    w568w
        12
    w568w  
       18 天前   ❤️ 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  
       18 天前   ❤️ 25
    我这两天好像突然穿越回到了 2010 年前后,有讨论 GBK 的,有讨论 跨域的,又看到了上古神兽 JQUERY 、TOMCAT 。
    CHTuring
        14
    CHTuring  
       18 天前
    @bbao #13 哈哈哈哈哈,同感
    ysc3839
        15
    ysc3839  
       18 天前 via Android
    不应该用 mbtowc ,这么做会产生更多问题。
    如果只需要支持 Windows 10 ,那可以在 manifest 中声明使用 UTF-8 编码,然后一律使用 UTF-8 。否则需要在调用系统 API 时手动把 UTF-8 转换成 UTF-16 ,然后调用 UTF-16 版本的 API 。
    rekulas
        16
    rekulas  
       18 天前
    12 楼说的对 虽然我不怎么写 c 但你这个判断一看就不正确
    fairytale
        18
    fairytale  
       18 天前 via Android
    Windows ?难道不应该用 CreateFileW ?
    atuocn
        19
    atuocn  
       18 天前
    #5

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

    这个说法是以前不了解的。感谢
    fairytale
        20
    fairytale  
       18 天前 via Android
    Windows 如果用 ucrt140 的话,就可以全套 utf8 了(别混用 win api )
    sagaxu
        21
    sagaxu  
       18 天前
    早年还有比 GBK 更坑爹的 GB2312 ,某个大领导的“镕”字不在 GB2312 范围中
    minami
        22
    minami  
       18 天前
    这代码给人看乐了,学艺不精也能算 bug ,就跟 tcp 粘包侠一样可乐
    adoal
        23
    adoal  
       18 天前
    Windows 老老实实用 wide 版本的 API ,不要用 C style stringsc 处理和 OS API 相关的字符串,不要听什么 utf8everywhere 的鼓吹。
    geelaw
        24
    geelaw  
       18 天前
    @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  
       18 天前
    std::filesystem::path {argv[1]}
    AoEiuV020JP
        26
    AoEiuV020JP  
       18 天前
    c/c++折腾跨平台就是很麻烦, 最近有一些代码需要跨平台编译出动态库, 纠结许久还是放弃 c/c++改用 go ,代价就是动态库大了一些,但代码真的很省心,很现代,
    w568w
        27
    w568w  
       18 天前
    @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  
       18 天前
    没有人提到 C23 的 char8_t 吗,最近学 C23 看到个大量采用这个的库 https://github.com/micl2e2/mcpc ,震惊! C 里面也可以全程 UTF8 !
    yk000123
        29
    yk000123  
       18 天前
    偏个题。不能用斜杠、反斜杠数量来判断文件目录层级。首先 Linux 里有`.`,`..`,其次同一个文件的相对路径和绝对路径的斜杠数量也可能不同。还有,Windows 里我不清楚,但只是 Linux 里,`/path/to/file`和`//path////to///file`指向的是同一个文件。
    aloxaf
        30
    aloxaf  
       18 天前
    @vinle 这不是 utf8 吧,只是单独提出了一个类型用来表示 unicode code unit ,语义上更明确了,但没有任何编码信息
    mikewang
        31
    mikewang  
    OP
       18 天前
    @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  
       18 天前
    @yk000123 其实楼主的代码加上相对路径的识别就好了,说白了缺少三种特殊处理,“.”、“..”、空白,经常和路径打交道的,这三种特殊情况,第一行 if 就开始处理了
    aloxaf
        33
    aloxaf  
       18 天前   ❤️ 2
    发现目录层级这玩意儿还是有些门道的

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

    Rust 和 Python 的实现都是正确的,只会删掉多余的 `/` 和 `.`,并且在文档中强调了这一点
    Go 和 NodeJS 都会把 `..` 也删掉,但 NodeJS 提到了它的行为并不严格遵守 POSIX 规范
    geelaw
        34
    geelaw  
       18 天前
    @mikewang #31 一个中国生活的、使用 Windows 二次元爱好者,很可能分区是 NTFS 格式,同一个文件的文件名里既有中文,又有日语。此时无论用户的代码页是 936 (简体中文) 还是 932 (日语) 都无法通过非 Unicode API 访问此文件。
    minami
        35
    minami  
       18 天前   ❤️ 1
    @mikewang 字符编码方式永远都是 trade off 的艺术,你不能光看一项优点,就忽略了它其他方面的缺点,GBK 作为定长编码,相比变长编码还是有独到的优势的。而且默认字符编码这个问题,尊重平台特性,尊重历史兼容性,才是正确的,就像 Apple 拼尽全力,也无法彻底去掉大小写不敏感一样
    mikewang
        36
    mikewang  
    OP
       18 天前
    @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  
       18 天前
    那个啥,我冒失了,原谅我,楼主是个 14 岁的初中生~~~~~~~~~~~~~ 是我不够 nice 。
    mk3s
        38
    mk3s  
       18 天前
    @bbao 这就有点尬黑了,tomcat 只是集成了,啊不,人家是进化了(
    mikewang
        39
    mikewang  
    OP
       18 天前
    @bbao 别啊,我现在工作了,虽然时间不长。或许是看到了我的历史帖子,那是我注册 v 站的十周年纪念,不是说今年(
    geelaw
        40
    geelaw  
       18 天前   ❤️ 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  
       18 天前   ❤️ 1
    如果用其他 char 本身就是 utf 字节的语言实现,op 会不会没有了这种感慨呢?

    要知道 c 的出现比 utf 早太多了,gbk 也比 utf 早很多。
    Shatyuka
        42
    Shatyuka  
       18 天前
    API 不一样那没办法,Windows API 要么就是本地 codepage (虽然现在能改成 UTF-8 ),要么就是 UTF-16 。
    简单移植方法是编译时添加 /utf-8 选项,入口点用 wmain ,手动把 UTF-16 转成 UTF-8 ,后面就可以和 Linux 用一套代码了。想要输出正常可能还要 SetConsoleOutputCP(CP_UTF8)
    本质上还是你代码本地化处理有问题,应该按字符遍历字符串,而不是字节。
    mikewang
        43
    mikewang  
    OP
       18 天前
    @bbao #13 其实 GB 系列编码不算老吧,GBK 有年代了,但是 GB18030-2022 是新出的,而且属于强制国标,国产化适配必须的。像不少系统原来只支持 UTF-8 ,现在要支持使用 GB18030 ,你说到底算进步还是退步哈哈
    ysc3839
        44
    ysc3839  
       18 天前 via Android
    @mikewang #36 大多数 POSIX 程序其实不管字符串编码,基本是原样传递。
    @geelaw #40 据我所知 Windows 并没有提供 UTF-8 单独的 API 。而且从 Vista 开始新增的大部分 API 都是只支持 UTF-16 的了。
    manifest 设置 UTF-8 就是为了方便移植,因为在此之前并没有一个官方的方案单独设置某个进程的代码页,要改只能改系统全局的,但改全局的又会让另一部分老 ANSI 应用炸掉。
    mikewang
        45
    mikewang  
    OP
       18 天前
    @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  
       18 天前 via Android
    @mikewang 但是很远古的 Linux 系统也有使用 GBK ,似乎没有炸掉的情况,可能是用户主动避免了
    unused
        47
    unused  
       18 天前
    @mikewang UTF-8 也不能避免文件名包含冒号,只能说编码有 0x3A 的目录名不能用于 PATH
    ysc3839
        49
    ysc3839  
       18 天前 via Android
    @geelaw 这是少部分特殊 API ,我之前没用过,不知道。其他绝大部分常用 API 都不支持 UTF-8 。
    datou
        50
    datou  
       18 天前
    @mikewang https://www.gov.cn/ 都是 utf-8 了,还有信创产品强制 GBK 的吗?
    fairytale
        51
    fairytale  
       18 天前 via Android
    如果楼主写的是纯 c/c++程序,不依赖系统 api ,比如用的 fopen 或者 std::filesystem ,而不是 open/CreateFileW ,那么在 Windows 设置链接 ucrt.lib 就是 utf8 (默认是 libcmtd.lib )。无需任何代码修改,跨平台。
    tool2dx
        52
    tool2dx  
       18 天前
    @ysc3839 “大多数 POSIX 程序其实不管字符串编码,基本是原样传递。”

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

    你使用了一个非跨平台的各自平台编译器 你想让程序跨平台能运行 那么默认就是你自己处理各个平台兼容性问题
    而你处理不好就开始怒喷了
    si
        65
    si  
       18 天前   ❤️ 1
    GB2312 制定的时候两个字节都是大于 0x80 的,微软搞 GBK 的时候为了塞下更多汉字把 0x40-0x80 也用了,GBK 随着 Windows 应用的太广了,变成事实上的标准,后面再制定 GB18030 的时候也只能选择兼容 GBK ,所以处理起来就比较麻烦了。
    如果没有特殊需求,最好别用 GBK ,遇到不支持的字符处理不了。
    mikewang
        66
    mikewang  
    OP
       18 天前
    @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  
       18 天前
    @datou #50 好像说明不了啥,因为 HTML 5 惟一合规的编码是 UTF-8 。
    mikewang
        68
    mikewang  
    OP
       18 天前
    @datou #50
    是需要支持的,有认证,但可以有配置使用其他字符集。对于信创看来,UTF-8 (或者准确说 Unicode )显然不够自主,万一 IRG 卡你脖子不收录新汉字怎么办
    si
        69
    si  
       18 天前
    @mikewang
    GBK 兼容 GB2312 ,所以已经取代 GB2312 成为事实上的标准,GB 编码的程序和数据都是以 GBK 为标准的。
    GB18030 只是为了扩展 GB 编码收录 Unicode 中的字符,再搞一种不兼容的编码没有太大意义。
    我觉得可能当时的想法是准备切换到 Unicode ,没有考虑扩展 GB2312 ,结果微软出个 GBK 把路卡死了,再制定标准也只能捏着鼻子兼容 GBK 了。
    shimanooo
        70
    shimanooo  
       18 天前
    你能保证 UTF-8 里没有 0x5c 吗?
    应该按字来处理,而不是字节。一个字可能占多个字节,要按编码规则整体递进。
    FeS
        71
    FeS  
       18 天前 via Android
    这个问题 sdl 有个解决方案,即用宏定义来替换 main 函数,自己再写一套 main 函数,在 Windows 平台上先进入重定向的 main 函数转换 argv 的编码
    不过这种方法挺丑陋的,而且也会失去 main 自动加 return 0 的特性,好处是不用改动代码(参考 sdlmain )

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


    0 开头只有 0yyyzzzz 的形式,是单字节。多字节都是 1 开头的。虽然多字节浪费了一些空间,但是处理起来高效呀。
    NPC666
        73
    NPC666  
       18 天前 via Android
    “你知道 string 有几种写法吗?”
    crab
        74
    crab  
       18 天前
    @shimanooo 有 0x5c 就是 ascii 范围内的了。其余字节都是大于等于 0x80 。
    ysc3839
        75
    ysc3839  
       17 天前 via Android
    @FeS POSIX 下没有通用的取进程参数的方法,Linux 下可以读/proc/self/cmdline ,但其他系统就不一定有/proc 了。
    另外 Linux 下可以通过修改 argv 指向的 buffer 来实现修改进程名,但不能通过/proc 修改。
    mark2025
        76
    mark2025  
       17 天前
    @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  
       17 天前
    @si 当年就是准备从 gb2312 进化到 unicode 的,然而微软大力推销它的 gbk ( gb2312 是国标,gbk 是行标)导致路线错误,zf 很生气于是是在 office2001 ( 2003 ?) 快要发布之前强制规定所有软件必须兼容 gb18030 狠狠地恶心了一把微软(返工增加对 gb18030 的兼容)。
    mark2025
        78
    mark2025  
       17 天前
    @mikewang 我有 GB18030
    tairan2006
        79
    tairan2006  
       17 天前
    Windows 系统的兼容性带来的坏处
    HTravel
        80
    HTravel  
       17 天前
    数文件层级居然能是简单的数斜杠数?那你这 linux 版本来质量也就不咋地。

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

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