【问题标题】:Why does QCoreApplication call `setlocale(LC_ALL, "")` by default on Unix/Linux?为什么 QCoreApplication 在 Unix/Linux 上默认调用 `setlocale(LC_ALL, "")`?
【发布时间】:2014-10-28 22:56:52
【问题描述】:

我认为可以肯定地说 C 语言环境被普遍认为是一个坏主意。

如果您必须考虑将语言环境设置为与"C" 不同的任何内容,那么使用 C 标准库函数编写一个尝试解析或编写基于文本的机器格式(这种情况经常发生)的应用程序几乎是不可能的。由于语言环境通常是每个进程的(并且setlocale 通常不是线程安全的),如果您正在编写一个库或者您有一个多线程程序,那么即使执行setlocale(LC_ALL, "C") 并在完成您的工作后恢复它也是不安全的。

现在,由于这些原因,规则通常是“避免setlocale,句号”; 但是:过去我们曾多次被QCoreApplication 和派生类的特殊行为所困扰; documentation 说:

在 Unix/Linux 上,Qt 默认配置为使用系统区域设置。这可能会在使用 POSIX 函数时引起冲突,例如,在浮点数和字符串等数据类型之间进行转换时,因为不同语言环境的表示法可能不同。要解决此问题,请在初始化 QApplicationQCoreApplication 后立即调用 POSIX 函数 setlocale(LC_NUMERIC,"C") 以将用于数字格式化的语言环境重置为“C”-locale。

此行为已在another question 中描述;我的问题是:这种显然愚蠢的行为的基本原理是什么?尤其是 Unix 和 Linux 有什么特别之处,才促使做出这样的决定只在这些平台上进行?

(顺便说一句,如果我在创建QApplication 之后只执行setlocale(LC_ALL, "C");,一切都会中断吗?如果没问题,他们为什么不直接删除他们的setlocale(LC_ALL, "");?)

【问题讨论】:

  • 在 linux 范围内的 char 函数(例如:wcstok)上有一个额外的参数来保证多线程安全。 QT 肯定会在 linux 上使用标准的 libc 宽字符函数...

标签: c++ linux qt localization


【解决方案1】:

根据@Phil Armstrong 和我对 Qt 源代码的调查(参见 the chat log),似乎从版本 1 开始就有 setlocale 调用,原因如下:

  • XIM,至少在古代,没有这样的调用就不能正确地“获取”当前的语言环境。
  • 在 Solaris 上,它甚至在默认的 C 语言环境下崩溃。
  • 在 Unix 系统上,它用于(在其他系统中,在复杂的后备游戏中)“嗅探”“系统字符集”(无论在 Unix 上是什么意思),因此能够在 QString 之间进行转换表示和“本地”8 位编码(这对于文件路径尤其重要)。

确实,它已经检查了LC_* 环境变量,就像检查QLocale 一样,但我想如果应用程序显式更改它,让nl_langinfo 解码当前LC_CTYPE 可能很有用(但要查看是否有显式更改,必须从系统默认值开始)。

有趣的是,他们确实setlocale(LC_ALL, "") 之后立即添加了setlocale(LC_NUMERIC, "C"),但是this was removed in Qt 4.4。这个决定的理由似乎在于旧 Qt bugtracker 的任务 #132859(它在 TrollTech、诺基亚和 QtSoftware.com 之间移动,然后消失而没有留下任何痕迹,甚至在 Wayback Machine 中也没有),它在 @ 中被引用987654324@ bugs 关于这个话题。我认为关于该主题的权威答案是存在的,但我找不到恢复它的方法。

我的猜测是它引入了一些微妙的错误,因为环境看起来是原始的,但实际上除了 LC_NUMERIC 类别(这是最明显);可能他们删除了调用以使语言环境设置更加明显,并让应用程序开发人员采取相应的行动。

【讨论】:

  • 很好的总结 Matteo。我个人认为,一个表现良好的 unix 应用程序应该在应用程序初始化阶段调用setlocale(LC_ALL, "")(可能在main() 的开头附近)。然而,在像 Qt 这样的动态加载库中并不是一个好地方,因为如果没有别的原因,程序员会感到惊讶。我们发现的历史表明 Qt 开发人员有充分的理由将其最初包含在内,而删除代码的影响可能会使 Qt 开发人员不愿意删除它。
  • "C" 语言环境是 Apple 的默认设置(由于 setlocale 带有空字符串)会导致应用程序因无效字符串错误而崩溃。在 Qt 程序中尝试使用 POSIX 宽字符函数也是不明智的,而框架为相同的功能提供了可移植的接口
【解决方案2】:

Qt 调用setlocale(LC_ALL, ""),因为这是正确的做法:从cat 开始的每个标准Unix 程序都调用setlocale(LC_ALL, "")。该调用的结果是将程序区域设置为用户指定的区域。请参阅 setlocale() 联机帮助页:

在主程序启动时,选择可移植的“C”语言环境 默认。可以通过调用使程序可移植到所有语言环境:

setlocale(LC_ALL, "");

程序初始化后...

鉴于 Qt 既生成供用户阅读的文本,又解析用户生成的输入,拒绝让用户以自己特定于语言环境的方式与用户交流是非常不友好的。因此调用了 setlocale()。

我希望用户友好不会引起争议!当您尝试解析由在不同语言环境下运行的程序创建的数据文件时,当然会出现问题。显然,如果您使用基于 sscanf 和朋友的解析器的特殊基于文本的格式,而不是使用“真实”解析器的指定数据格式,那么如果在不考虑的情况下这样做,就会导致数据损坏区域设置。解决方案是 a) 使用真正的序列化库来为您处理这些内容,或者 b) 在写入和读取数据时将语言环境设置为特定的内容(可能是“C”)。

如果线程安全是一个问题,那么在现代 POSIX 实现(或任何具有 GNU libc 版本 >= 2.3 的 Linux 系统,此时几乎是“所有这些”)上,您可以调用 uselocale() 来设置所有 I/O 的线程本地语言环境。或者,您可以调用将语言环境对象作为补充参数的常用函数的 _l 版本。

如果您致电setlocale(LC_ALL, "C");,一切都会中断吗?不,但正确的做法是让用户设置他们喜欢的语言环境,或者以明确指定的格式保存数据,或者指定在运行时读取和写入数据的语言环境。

【讨论】:

  • 无论如何,C语言环境的优点大多无关紧要; Qt 有自己的(更好的)设施来处理本地化(参见QLocale 和翻译框架),它们似乎不以任何方式使用 C 语言环境。此外,关于将“正确的事情”强加给 C 函数的论点也站不住脚,因为在 Windows 上完全避免了 setlocale 调用。 Qt 可能需要调用一些仅在 POSIX 上才需要的奇怪副作用,但我无法准确指出它是什么。
  • 那就是strtod_l()。或者直接拨打uselocale()
  • 在我编写的 Linux 机器上没有一个可用,它们都不是可移植的 C; Ruby 解释器甚至继续捆绑他自己的 - 略微损坏 - 版本的 strtod,因为没有可移植的安全替代方案。即使我要在 my 代码中使用非标准函数,我当然也无法修复任何可能使用strtod 的第三方库。说真的,进入 C 语言的唯一安全方法是坚持使用 C 语言环境。但是,我们又跑题了,重点是“为什么 Qt 会进行这个可能会破坏很多东西的调用,为什么只在 POSIX 上”?
  • strtod_l()uselocale() 自 2002 年左右开始出现在每个 Linux 发行版中。出于某种原因,strtod_l 没有手册页,但 uselocale() 和朋友的手册页非常好。它们还在 POSIX.1-2008 中进行了标准化:pubs.opengroup.org/onlinepubs/9699919799/functions/… 并具有您可以调用的不错的功能测试宏。
  • 那我很糟糕,我被看不到手册页所欺骗;无论如何,其他反对意见仍然存在(同样,C 语言环境的丑陋不是我的问题的重点)。
【解决方案3】:

POSIX 系统(包括您提到的 Unix/Linux 系统)的独特之处在于 OS 接口和 C 接口混合在一起。 C setlocale 调用尤其会干扰操作系统。

相比之下,在 Windows 上,语言环境明确地是每个线程的属性 (SetThreadLocale),但更重要的是,GetNumberFormat 等函数接受语言环境参数。

请注意,您的问题很容易解决:使用 Qt 时,请使用 Qt。所以这意味着reading your text input into a QString,处理它,然后写回它。

【讨论】:

  • setlocale 与 Windows 相比有何改变?它们都只影响 C 标准库函数(内核对语言环境一无所知),无论如何 AFAICT 似乎被 Qt 绕过了(出于本地化目的,它似乎有自己的 QLocale,与损坏的 C/C++ 设施无关)。此外,不幸的是,这个问题并不是那么容易解决 - 我们有几个库必须从“常规”C++、Qt-C++、“常规”Python(通过 SIP)和 Python+PyQt 中使用,始终在内部使用 Qt 既不是一个选项,实际上也没有必要。
  • POSIX 具有 C 标准库语言环境并以此为基础。除此之外,Linux 内核没有语言环境。另一方面,Windows 具有本地语言环境支持,即使对于非 C 语言也是如此。所以并不是setlocale 在 Linux 上的变化更大,而是在 Windows 上有些东西无法改变。
  • 但是这些东西似乎对 Qt 不感兴趣,在整个 Qt 源代码树中,只有两个调用 SetThreadLocale 和一个调用 SetLocaleInfo,它们都在单元测试中。此外,如果 Qt 在创建 QApplication 时需要“通用语言环境设置”,那么在同一个地方找到它们是合理的,但它只发生在基于 Unix 的操作系统上。这就是我感到困惑的原因。
猜你喜欢
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
  • 2020-01-28
  • 1970-01-01
  • 2012-09-19
  • 1970-01-01
  • 1970-01-01
  • 2019-07-26
相关资源
最近更新 更多