【问题标题】:Portable literal strings in C source filesC 源文件中的可移植文字字符串
【发布时间】:2013-03-28 18:28:27
【问题描述】:

好的,我有这个:

AllocConsole();
SetConsoleOutputCP(CP_UTF8);
HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(consoleHandle, "aΕλληνικά\n", 10, NULL, NULL);
WriteConsoleW(consoleHandle, L"wΕλληνικά\n", 10, NULL, NULL);
printf("aΕλληνικά\n");
wprintf(L"wΕλληνικά\n");

现在,问题是根据编码文件被保存为这些作品中的一部分。 wprintf 从不工作,但我已经知道原因(损坏的 Microsoft stdout 实现,它只接受窄字符)。然而,我和其他三个人有问题。如果我将文件保存为不带签名 (BOM) 的 UTF-8 并使用 MS Visual C++ 编译器,则只有最后一个 printf 有效。如果我想让 ANSI 版本正常工作,我需要将字符(?)计数增加到 18:

WriteConsoleA(consoleHandle, "aΕλληνικά\n", 18, NULL, NULL);

我认为,WriteConsoleW 不起作用,因为字符串被保存为 UTF-8 字节序列,即使我明确要求将其存储为带有 L 前缀的宽字符(UTF-16),并且实现很可能需要 UTF-16编码字符串不是 UTF-8。

如果我将它保存为带有 BOM 的 UTF-8(应该如此),那么 WriteConsoleW 开始以某种方式工作(???)并且其他一切都停止(我得到 ? 而不是一个字符)。我需要将 WriteConsoleA 中的字符数减少回 10 以保持格式相同(否则我会得到 8 个额外的矩形)。基本上,WTF?

现在,让我们转到 UTF-16(Unicode - 代码页 1200)。仅适用于 WriteConsoleW。 WriteConsoleA 中的字符数应为 10,以保持格式精确。

以 UTF-16 Big Endian 模式(Unicode - 代码页 1201)保存不会改变任何内容。再说一次,WTF?存储到文件时不应该反转字符串中的字节顺序吗?

结论是字符串被编译成二进制形式的方式取决于所使用的编码。因此,存储字符串的可移植且独立于编译器的方式是什么?是否有一个预处理器可以在编译之前将一种字符串表示形式转换为另一种表示形式,所以我可以将文件存储在 UTF-8 中,并且只通过包装一些宏来预处理我需要在 UTF-16 中具有的字符串。

【问题讨论】:

  • 您的编译器是否支持 C99 的 \uXXXX 和 \UXXXXXXXX 转义以用于 unicode?虽然不可读,但它肯定更便携,因为只需要基本的 C 字符集。
  • @Jens Visual C++ 不符合 C99,只有 ANSI,但我可以试试。
  • 这听起来有点令人困惑,我不能 100% 确定您要在这里做什么。您是否只想为窄字符和宽字符字符串保留一个字符串文字?或者您想在保持编码正常工作的同时混合/匹配两者?
  • @Mario 我想让源代码中的字符串可移植(独立于编码文件保存为)。如果我将它保存在 UTF-16 或 UTF-8 下并带有 BOM printf 停止工作,如果我将它保存在 UTF-8 下而没有 BOM printf 工作但 WriteConsoleW 停止会导致它们中的每一个都需要不同的编码字符串(我想)。
  • 试图在下面解释您的一些问题。如果您仍然无法正确输出,请在答案下方的评论中告诉我,我会尝试仔细查看。

标签: c string utf-8 utf-16


【解决方案1】:

答案是here

引用:

编译器不可能混合 UTF-8 和 UTF-16 字符串到编译的输出!所以你必须决定一个来源 代码文件:

  • 要么将 UTF-8 与 BOM 一起使用,要么仅生成 UTF-16 字符串(即始终使用 L 前缀),
  • 或不带 BOM 的 UTF-8,仅生成 UTF-8 字符串(即从不使用 L 前缀),
  • 不涉及 7 位 ASCII 字符,可以带或不带 L 前缀使用

唯一可移植且独立于编译器的方法是使用 ASCII 字符集和转义序列,因为无法保证任何编译器都会接受 UTF-8 编码的文件,并且编译器对这些多字节序列的处理可能会有所不同。

【讨论】:

    【解决方案2】:

    据我所知,我认为您在这里至少有一些假设是错误的,或者不是 100% 正确:

    现在,问题是根据编码文件被保存为只有这些作品的一部分。

    当然,因为编码决定了如何解释字符串字面量。

    wprintf 从不工作,但我已经知道原因(损坏的 Microsoft 标准输出实现,它只接受窄字符)。

    我从未听说过那个,但我很确定这取决于为您的程序设置的语言环境。我有一些工作项目,其中设置了语言环境,并且使用德语变音符号等输出就可以了。

    如果我将文件保存为不带签名 (BOM) 的 UTF-8 并使用 MS Visual C++ 编译器,则只有最后一个 printf 有效。如果我想让 ANSI 版本正常工作,我需要将字符(?)计数增加到 18:

    这是因为 ANSI 版本需要 ANSI 字符串,而您传递的是 UTF-8 编码字符串(基于文件的编码)。输出仍然有效,因为控制台会为您处理 UTF-8 转换 - 您实际上是在此处打印原始 UTF-8。

    我认为,WriteConsoleW 不起作用,因为字符串被保存为 UTF-8 字节序列,即使我明确要求将其存储为带有 L 前缀的宽字符(UTF-16),并且实现很可能需要 UTF-16编码字符串不是 UTF-8。

    我不这么认为(尽管我不确定为什么它也不起作用)。您是否尝试过设置一些易于查找的字符串并在生成的二进制文件中查找它?我很确定它确实是使用 UTF-16 编码的。我假设由于缺少 BOM,编译器可能会将整个内容解释为一个窄字符串,因此会将 UTF-8 内容转换为错误的。

    如果我将它保存为带有 BOM 的 UTF-8(应该如此),那么 WriteConsoleW 开始以某种方式工作(???)并且其他一切都停止(我得到 ? 而不是一个字符)。我需要将 WriteConsoleA 中的字符数减少回 10 以保持格式相同(否则我会得到 8 个额外的矩形)。基本上,WTF?

    这正是我上面描述的。现在宽字符串被正确编码,因为编译器现在知道文件是 UTF-8,而不是 ANSI(或某些代码页)。窄字符串也被正确转换为正在使用的语言环境。


    总体而言,没有独立于编码的方法,除非您提前使用正确的代码页和/或 UTF 代码对所有内容进行转义。我只是坚持使用带有 BOM 的 UTF-8,因为我认为所有当前的编译器都能够正确读取和解释文件(除了 Microsoft 的资源编译器;虽然我还没有尝试使用 UTF-8 提供 2012 版本)。

    编辑:

    打个比方:

    您实际上是将原始图像保存到文件中,并且您希望它能够正常工作,无论其他程序是否尝试将其读取为灰度图像、调色板图像或全彩色图像。这不起作用(尽管差异较小)。

    【讨论】:

    • 关于 stdout 和 wprint:stackoverflow.com/questions/15827607/… 你真的用 wprint 得到了那些变音符号吗?会尝试您的建议并做出回应。
    • 是的,我们只使用wprintf()。不过,老实说,我注意到如果您尝试在同一程序中混合使用 printf()wprintf() 可能会出现问题。
    • 是的,而且答案是标准输出具有一个字节字符或宽字符的方向。有改变方向的功能 - fwide() 或者您可以使用 freopen() 并在第一次调用 printf 或 wprintf 后设置方向。 Microsoft stdout 似乎只有字节方向,而我的 wprintf 什么也不输出。如果您有兴趣,请尝试在上一个链接中打印我的字符串。
    • 所以我们在这里讨论的是两个不同的问题?您想使用一种“适合所有”的编码(或“不关心”的编码?)并混合/匹配窄字符和宽字符输出?
    • 考虑有两个函数需要两个不同的输入:enterUTF16string() 和 enterUTF8string(),我想在其中使用文字。看来我不能在同一个源文件中同时使用两者。我以为用 L 前缀可以解决它,但它不起作用。
    猜你喜欢
    • 2016-10-06
    • 1970-01-01
    • 1970-01-01
    • 2013-03-29
    • 2016-11-21
    • 2011-01-23
    • 2016-07-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多