【问题标题】:Reading non-ascii characters from a file in C从 C 中的文件中读取非 ascii 字符
【发布时间】:2021-04-26 21:08:58
【问题描述】:

我有一个文件,foo.txt,它只是:

” ’

char x = fgetc(myfile);

当我在文件上使用fgetc 时,我在两个字符上都得到226 的常量值。为什么是这样?我该如何解决这个问题?

这是我的代码:

FILE* f = fopen("./debate.txt", "rb");
int x = fgetc(f);
char y = (char)x;

【问题讨论】:

  • A) 什么编码。 B) 向我们展示更多代码,尤其是如何打开句柄。
  • 好像不对,它们不一样,你确定值是一样的吗?您如何看待这些价值观?需要更多代码。
  • I get a constant value 您如何检查该值? on both characters. 在哪些字符上?
  • fgetc() 不知道“宽”字符,顺便说一句。对于非 8 位编码,这将不起作用。
  • 那些“花引号”不是 ASCII。它们是 Unicode 201D 和 2019。这个“文本”可能是 UTF-8 编码的(即 E2 80 9D E2 80 99)。

标签: c ascii


【解决方案1】:

对于普通(便携式)软件,字符编码是一个痛苦的世界。问题(和潜在的解决方案)是:

A) 文本文件可以采用任何随机/“文本编辑器定义”编码。

要解决这个问题,有 4 个选项:

  • 期望以特定编码(例如 UTF-8)输入并拒绝支持其他任何内容(如果文件中的数据对于您选择的编码无效,则会生成错误消息)。这会惹恼一些用户(例如,国家标准与 CNS 11643 等不兼容的地方)。

  • 支持多种编码,并让用户选择期望的编码(例如,基于命令行参数)。这对用户来说有点不方便,对你来说很痛苦。

  • 支持多种编码,并尝试自动检测文件使用的编码。这对用户来说更方便一点,直到它猜错并成为一个主要的烦恼(并且你不能将猜错编码的机会减少到零)。

  • 支持多种编码,让用户根据需要选择编码,如果用户没有指定,则自动检测。这对用户来说是最好的选择(对软件开发者来说也是最糟糕的选择)。

对于这些选项,我会使用第一个(我会说“输入文件必须是 UTF-8”,部分原因是 UTF-8 已经变得非常普遍且得到很好的支持,部分原因是其他所有编码都被证明对技术而言更糟糕原因)。请注意(根据您的结果)您的输入文件极有可能是 UTF-8 格式。

B) 无论编译器对char 使用什么,都是实现定义的(可以是 ASCII,可以是 EBDIC,也可以是其他任何东西),并且可以是有符号的或无符号的。

在这种情况下,假设 ASCII 是“非常安全的”(对于可移植性)。假设 UTF-8 是第二个最佳选择,但它会在“可能已签名”char 值上执行任何数学运算(例如右移等)的任何代码产生问题。

C) stdinstdoutstderr 管道也是随机/实现定义的。

这与上一个问题类似,除了最佳解决方案(“假设 ASCII”)要困难得多(尤其是当您想要输出包含输入文件中的文本片段的错误消息等时)。为此,我很想尽可能多地使用 ASCII,但如果必须的话,我会作弊并输出 UTF-8。如果操作系统(或外壳)无法处理 UTF-8,它会造成混乱,但大多数用户会理解(并且可以通过将输出传递到文件来解决它)。最好的替代方案(用于用户输出)是使用 GUI 而不是使用stdout,但这会产生大量额外问题(并导致第二大额外问题 - 诸如错误消息等的国际化)。

D) 编译器对wchar 的假设是随机/实现定义的(可能是 UTF-16,可能是 UTF-32,也可能是其他任何东西;它甚至可能是一个 8 位编码而不是“宽”)。

这里唯一明智的选择是认识到wchar 是一个不可用的故障,绝不应该(在任何情况下)用于任何事情。

更具体地说,wchar 是基于以往历史错误的历史错误。本质上,在早期,微软和 Sun 决定采用 UCS-2(“所有 Unicode 代码点都适合 16 位”的假设),但很快就被打破了。为了解决这个问题,Microsoft 和 Sun 转而使用 UTF-16,但 Microsoft 主要在 little-endian 机器上运行并选择了 UTF-16LE,而 Sun (Java) 的目标是 big-endian 机器并选择了 UTF-16BE。 wchar 扩展于 1995 年被添加到 C 中,同时公司(Microsoft、Sun)做错了所有事情并且没有做任何相互兼容的事情;所以wchar 最终变成了一个“我们不知道标准是什么,所以我们的标准根本就不是标准”的笑话。对于 C(和 C++),这个问题在 2011 年得到修复,在 <uchar.h> 中引入了 char16_t (UTF-16) 和 char32_t (UTF-32),但采用速度很慢(例如,微软仍然懒得打扰与 C99)。

请注意,问题的另一部分是人们想假设一个 wchar 是一个完整的可打印字符,而这几乎从来不是这种情况(例如,即使对于 UTF-32,其中一个 wchar 是一个完整的字符Unicode代码点有组合代码点);这会破坏任何“宽字符”实现的任何好处(即使您的代码根本不可移植并且您知道 wchar 实际上是什么)。

最好的解决方案(特别是如果您选择“期望输入文件使用 UTF-8”来解决第一个问题)是使用存储在 uint8_t 中的 UTF-8(这样任何人都不会混淆 @987654339 @ 是)。

在这种情况下; “将文件中的输入转换为您的内部字符编码”可以变成“无所事事地将 UTF-8 转换为 UTF-8”;并且“将您的内部字符编码转换为stdout 想要的任何内容”变成“几乎什么都不做(从uint8_t 转换为char)将UTF-8 转换为ASCII(或UTF-8)”。换句话说,它可以非常接近“对所有东西使用相同的编码”。

【讨论】:

    猜你喜欢
    • 2012-05-09
    • 1970-01-01
    • 2015-02-06
    • 2015-12-08
    • 2014-12-09
    • 2017-12-16
    • 2012-08-06
    • 1970-01-01
    • 2020-10-21
    相关资源
    最近更新 更多