草庐IT

c++ - WChars、编码、标准和可移植性

coder 2023-05-01 原文

以下可能不符合 SO 问题;如果超出范围,请随时告诉我离开。这里的问题基本上是,“我是否正确理解了 C 标准,这是正确的处理方式吗?”

我想就我对 C(以及 C++ 和 C++0x)中的字符处理的理解要求澄清、确认和更正。首先,一个重要的观察:

可移植性和序列化是正交的概念。

便携的东西是像 C 一样的东西,unsigned int , wchar_t .可序列化的东西是类似 uint32_t 的东西或 UTF-8。 “可移植”意味着您可以重新编译相同的源代码并在每个受支持的平台上获得工作结果,但二进制表示可能完全不同(或者甚至不存在,例如 TCP-over-carrier pig)。另一方面,可序列化的事物总是具有相同的表示,例如我可以在 Windows 桌面、手机或牙刷上阅读的 PNG 文件。可移植的东西是内部的、可序列化的东西,处理 I/O。可移植的东西是类型安全的,可序列化的东西需要类型双关。

当谈到 C 中的字符处理时,有两组分别与可移植性和序列化相关的事情:

  • wchar_t , setlocale() , mbsrtowcs()/wcsrtombs() : C 标准没有提及“编码” ;事实上,它与任何文本或编码属性完全无关。它只说“你的入口点是 main(int, char**);你得到一个类型 wchar_t,它可以保存你系统的所有字符;你得到函数来读取输入字符序列并将它们变成可用的 wstrings,反之亦然。
  • iconv()和 UTF-8,16,32:一个函数/库,用于在定义明确的、明确的、固定的编码之间进行转码。 iconv 处理的所有编码都得到普遍理解和认可,只有一个异常(exception)。

  • 可移植的、与编码无关的 C 世界与其 wchar_t 之间的桥梁可移植字符类型和确定性的外部世界是 WCHAR-T 和 UTF 之间的 iconv 转换。

    因此,我是否应该始终将字符串存储在与编码无关的 wstring 内部,通过 wcsrtombs() 与 CRT 接口(interface),并使用 iconv()序列化?从概念上讲:
                            my program
        <-- wcstombs ---  /==============\   --- iconv(UTF8, WCHAR_T) -->
    CRT                   |   wchar_t[]  |                                <Disk>
        --- mbstowcs -->  \==============/   <-- iconv(WCHAR_T, UTF8) ---
                                |
                                +-- iconv(WCHAR_T, UCS-4) --+
                                                            |
           ... <--- (adv. Unicode malarkey) ----- libicu ---+
    

    实际上,这意味着我会为我的程序入口点编写两个样板包装器,例如对于 C++:
    // Portable wmain()-wrapper
    #include <clocale>
    #include <cwchar>
    #include <string>
    #include <vector>
    
    std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc
    
    int wmain(const std::vector<std::wstring> args); // user starts here
    
    #if defined(_WIN32) || defined(WIN32)
    #include <windows.h>
    extern "C" int main()
    {
      setlocale(LC_CTYPE, "");
      int argc;
      wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
      return wmain(std::vector<std::wstring>(argv, argv + argc));
    }
    #else
    extern "C" int main(int argc, char * argv[])
    {
      setlocale(LC_CTYPE, "");
      return wmain(parse(argc, argv));
    }
    #endif
    // Serialization utilities
    
    #include <iconv.h>
    
    typedef std::basic_string<uint16_t> U16String;
    typedef std::basic_string<uint32_t> U32String;
    
    U16String toUTF16(std::wstring s);
    U32String toUTF32(std::wstring s);
    
    /* ... */
    

    这是仅使用纯标准 C/C++ 编写惯用的、可移植的、通用的、编码不可知的程序核心的正确方法,以及使用 iconv 的定义良好的 UTF I/O 接口(interface)吗? (请注意,Unicode 规范化或变音符号替换等问题不在范围之内;只有在您决定真正想要 Unicode(而不是您可能喜欢的任何其他编码系统)之后,才是处理这些细节的时候了,例如使用专用库像利比库。)

    更新

    继许多非常好的评论之后,我想补充几点意见:
  • 如果您的应用程序明确想要处理 Unicode 文本,您应该使 iconv -转换部分核心和使用uint32_t/char32_t -strings 在内部使用 UCS-4。
  • Windows:虽然使用宽字符串通常很好,但与控制台(任何控制台,就此而言)的交互似乎是有限的,因为似乎不支持任何合理的多字节控制台编码和 mbstowcs基本上没用(除了微不足道的扩大)。从 Explorer-drop 和 GetCommandLineW 接收宽字符串参数+ CommandLineToArgvW工作(也许应该有一个单独的 Windows 包装器)。
  • 文件系统:文件系统似乎没有任何编码概念,只是将任何以空字符结尾的字符串作为文件名。大多数系统采用字节字符串,但 Windows/NTFS 采用 16 位字符串。在发现存在哪些文件以及处理该数据时(例如 char16_t 不构成有效 UTF16(例如裸代理)的序列是有效的 NTFS 文件名),您必须小心。标准 C fopen无法打开所有 NTFS 文件,因为没有可能的转换映射到所有可能的 16 位字符串。使用 Windows 专用 _wfopen可能需要。作为推论,通常没有明确定义的“多少个字符”包含给定文件名的概念,因为首先没有“字符”的概念。买者自负。
  • 最佳答案

    Is this the right way to write an idiomatic, portable, universal, encoding-agnostic program core using only pure standard C/C++



    不,并且根本无法满足所有这些属性,至少如果您希望您的程序在 Windows 上运行。在 Windows 上,几乎所有地方都必须忽略 C 和 C++ 标准,而只能使用 wchar_t (不一定在内部,但在系统的所有接口(interface)上)。例如,如果您从
    int main(int argc, char** argv)
    

    您已经失去了对命令行参数的 Unicode 支持。你必须写
    int wmain(int argc, wchar_t** argv)
    

    相反,或使用 GetCommandLineW函数,在 C 标准中没有指定。

    进一步来说,
  • Windows 上任何支持 Unicode 的程序都必须主动忽略 C 和 C++ 标准,例如命令行参数、文件和控制台 I/O 或文件和目录操作。这当然不是惯用语。改用 Microsoft 扩展或包装器,如 Boost.Filesystem 或 Qt。
  • 可移植性极难实现,尤其是对于 Unicode 支持。你真的必须做好准备,你认为你知道的一切都可能是错误的。例如,您必须考虑用于打开文件的文件名可能与实际使用的文件名不同,并且两个看似不同的文件名可能代表同一个文件。创建两个文件 a 和 b 后,您可能会得到一个文件 c,或两个文件 d 和 e,它们的文件名与您传递给操作系统的文件名不同。您需要一个外部包装库或大量 #ifdef s。
  • 编码不可知性通常在实践中不起作用,特别是如果您想要便携。你要知道wchar_t是 Windows 上的 UTF-16 代码单元,char通常(bot 并不总是)Linux 上的 UTF-8 代码单元。编码意识通常是更理想的目标:确保您始终知道您使用哪种编码,或者使用将它们抽象出来的包装库。

  • 我想我必须得出结论,除非您愿意使用额外的库和系统特定的扩展,并在其中投入大量精力,否则完全不可能用 C 或 C++ 构建一个可移植的支持 Unicode 的应用程序。不幸的是,大多数应用程序在相对简单的任务上已经失败,例如“将希腊字符写入控制台”或“以正确的方式支持系统允许的任何文件名”,而这些任务只是实现真正支持 Unicode 的第一步。

    关于c++ - WChars、编码、标准和可移植性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6300804/

    有关c++ - WChars、编码、标准和可移植性的更多相关文章

    1. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

      我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

    2. ruby - 用逗号、双引号和编码解析 csv - 2

      我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

    3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    4. C# 到 Ruby sha1 base64 编码 - 2

      我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

    5. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

      我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

    6. ruby-on-rails - 标准化文件名的字符串,删除重音和特殊字符 - 2

      我正在尝试找到一种方法来规范化字符串以将其作为文件名传递。到目前为止我有这个:my_string.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.gsub(/[^a-z]/,'_')但第一个问题:-字符。我猜这个方法还有更多问题。我不控制名称,名称字符串可以有重音符、空格和特殊字符。我想删除所有这些,用相应的字母('é'=>'e')替换重音符号,并将其余的替换为'_'字符。名字是这样的:“Prélèvements-常规”“健康证”...我希望它们像一个没有空格/特殊字符的文件名:“prelevements_routin

    7. ruby - 使用 `+=` 和 `send` 方法 - 2

      如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

    8. ruby - 如何计算 Liquid 中的变量 +1 - 2

      我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

    9. ruby-on-rails - 有没有一种工具可以在编码时自动保存对文件的增量更改? - 2

      我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功

    10. c - Ruby - 源代码 - 编码风格 - 2

      查看Ruby代码,它具有以下proc_arity:staticVALUEproc_arity(VALUEself){intarity=rb_proc_arity(self);returnINT2FIX(arity);}更多的是C编码风格问题,但为什么staticVALUE在单独的一行而不是像这样的:staticVALUEproc_arity(VALUEself) 最佳答案 它来自UNIX世界,因为它有助于轻松grep函数的定义:$grep-n'^proc_arity'*.c或使用vim:/^proc_arity

    随机推荐