在 std-proposals 列表中,提供了以下代码:
#include <vector>
#include <algorithm>
void foo(const std::vector<int> &v) {
#ifndef _ALGORITHM
std::for_each(v.begin(), v.end(), [](int i){std::cout << i; }
#endif
}
为了这个问题的目的,让我们忽略为什么会给出这些代码以及为什么以这种方式编写代码(因为它有充分的理由,但它不相关这里)。它假设_ALGORITHM
是标准头<algorithm>
内的标头保护,与一些已知的标准库实现一起提供。这里没有可移植性的固有意图。
现在,_ALGORITHM
当然是保留名称,依据:
[C++11: 2.11/3]:
此外,一些标识符保留供C ++实现和标准库(17.6.4.3.2)使用,否则不得使用;无需诊断。
[C++11: 17.6.4.3.2/1]:
某些名称和功能签名集始终保留给实现:
- 包含双下划线
_ _
或以下划线后跟大写字母(2.12)开头的每个名称都保留给实现以供任何使用。- 以下划线开头的每个名称都保留给实现,以用作全局命名空间中的名称。
我一直认为这篇文章的目的是阻止程序员定义 / 变异 / 取消定义名称上述标准,以便标准库实现者可以使用这些名称而不必担心与客户端代码冲突。
但是,在 std-proposals 列表中,声称此代码本身就是格式错误,仅仅是指这样的保留名称。我现在可以看到如何不使用短语&#34;&#34;来自[C++11: 2.11/3]:
可能确实暗示了这一点。
一个实际的理由是,例如,宏_ALGORITHM
可以扩展为擦除硬盘驱动器的某些代码。但是,考虑到规则的可能意图,我要说这种可能性更多地与_ALGORITHM
名称明显的实现定义的*性质有关,而与它的关系不大。引用它是完全非法的。
*&#34;实施定义&#34;在其英语语言意义上,而不是C ++标准意义上的短语
我要说,只要我们很高兴我们将获得实施定义的结果,并且我们应该调查该宏对我们的实施意味着什么(如果它存在的话!) ,如果我们不试图修改它,那么引用这样一个宏本身并不违法。
例如,以下代码遍布以区分编译为C的代码和编译为C ++的代码:
#ifdef __cplusplus
extern "C" {
#endif
我从未听过有关此事的抱怨。
那么,您怎么看?是否&#34;不得以其他方式使用&#34;包括简单地写这样的名字?或者它可能不是那么严格(这可能指向调整标准措辞的机会)?
答案 0 :(得分:18)
它是否合法是特定于实现的(和特定于标识符的)。
当标准赋予实施使用这些名称的唯一权利时,包括在用户代码中提供名称的权利。如果实现这样做,那很好。
但如果某项实施并没有明确赋予您正确的权利,那么很明显,&#34;不得以其他方式使用&#34;标准没有,你有不明确的行为。
答案 1 :(得分:17)
重要的部分是&#34;保留给实施&#34; 。这意味着编译器供应商可以使用这些名称甚至记录它们。然后,您的代码可以使用这些名称。这通常用于SKSpriteNode
之类的扩展,其中编译器供应商通过使用这些保留名称避免与您的标识符(代码声明)发生冲突。即使是标准也会将其用于__builtin_expect
之类的内容,以确保在添加新功能时它不会破坏现有(合法)代码。
答案 2 :(得分:4)
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1882
每个包含双重大纲__的标识符或以下划线后跟大写字母开头的标识符将保留给实现以供任何使用。
任何使用。 (在应用缺陷修复之前和之后都会出现类似文本)
__cplusplus
由标准定义。 _ALGORITHM
由实现使用的标准保留。这些看起来很不一样? (标准的两个部分确实存在冲突,其中一个表示__cplusplus
保留用于任何用途,而另一部分则明确使用它,但我认为冲突的胜利者是明确的。)
_ALGORITHM
标识符可以在标准下用作预处理步骤的一部分,以说“用硬盘驱动器删除代码替换此源代码”。它的存在(在预处理之前或之后)可能足以完全改变你的程序行为。
现在这不太可能,但我认为这不会导致不合规的实施。这只是实施质量的问题。
实现可以自由地记录和定义_ALGORITHM
的含义。例如,它可以记录它是<algorithm>
的标题保护,并指示是否已包含该标头文件。将您当前的<algorithm>
实施视为文档可能会走得太远。
我猜在C模式下使用__cplusplus
在技术上“与使用_ALGORITHM
一样糟糕”,但这个问题是c++问题,而不是c题。我没有深入研究c标准来寻找关于它的报价。
答案 3 :(得分:4)
[cpp.predefined]中的名称不同。那些具有特定含义,因此实现不能将它们保留用于任何用途,并且在程序中使用它们具有明确定义的可移植含义。使用特定于实现的标识符(例如_ALGORITHM的示例)是错误的,因为它违反了一个规则。
是的,我完全了解库规范使用的多个示例&#34; will&#34;表示&#34;这是对用户代码的要求,违规是UB,而不是格式错误&#34;。
关于它是UB还是实现定义,运行格式错误的程序会导致UB。标准措辞清楚地表明程序格式不正确,如果实施仍然选择接受程序并运行它,则会发生UB。
因此,如果程序使用标识符_ALGORITHM,那么该程序格式错误,并且运行这样的程序是UB,但这并不意味着它在使用_ALGORITHM作为包含的实现上不能正常工作保护,也不意味着它在没有实施的情况下不能正常工作。
如果用户担心这种不良形式和潜在的UB,并且用户希望编写可移植的C ++,他们就不应该在便携式C ++程序中使用保留标识符。如果用户接受,无论标准禁止这样的使用,没有实际的实现将擦除您的硬盘驱动器,他们可以自由使用这样的保留标识符,但通过标准的字母,这样的用途仍然是不正确的。
答案 4 :(得分:2)
从历史上看,使用此类令牌“未定义行为”的目的是编译器可以自由地将他们想要的任何含义附加到未在C标准中定义的任何此类令牌。例如,在某些嵌入式处理器上,使用__xdata作为变量的存储类将要求将其存储在RAM区域中,该区域的访问速度比正常的可变存储区域慢,但要大得多。在该系列的典型处理器上,“普通”变量的存储将限制为大约100个字节,但xdata变量的存储可能要大得多 - 高达64K。该标准基本上没有说明允许哪些编译器使用这些指令,尽管通常(我不确定标准是否要求这种行为,尽管我不知道编译器违反了它)这样的标记通常在代码中被忽略。使用#if
或类似指令禁用。
某些库的头文件将以一个以两个下划线开头的内容开始它们自己的内部标识符,但包含一个不太可能被编译器用于任何目的的模式(例如,Foozle库的版本23可能在其标识符之前使用__FZ23
)。对于未来的编译器来说,将__FZ23开头的标识符用于其他目的是完全合理的,如果发生这种情况,则需要更改Foozle库以使用其他内容。但是,如果主要的编译器升级可能由于其他原因而可能需要重写Foozle库,那么与标识符与外部代码冲突的风险相比,这种风险可能是可接受的。
另请注意,某些针对需要__
指令的处理器的项目头文件在为其他处理器编译时可能会有条件地为这些名称定义宏,例如:
#ifndef USE_XDATA
#define __XDATA
#endif
虽然一个更好的模式通常是:
#ifdef USE_XDATA
#define XDATA __XDATA
#else
#define XDATA
#endif
在编写新代码时,后一种模式通常更好,但是在适应编写在需要__XDATA的平台上的现有代码时,前一种模式有时可能很有用,因此它可以在使用/要求该指令的平台上使用在没有的平台上。
答案 5 :(得分:0)
是否合法是当地法律的问题。它是否意味着什么,如果是,那么什么,是语言定义的问题。当您使用为实现保留的名称时,程序的行为是未定义的。这意味着语言定义不会告诉您程序的功能。没有更多,没有更少。如果您使用的编译器记录了特定保留标识符的作用,那么您可以将该标识符与该编译器一起使用。如果您通过标题搜索并猜测各种未记录的标识符意味着您可以使用它们,但如果您的代码在后续更新发生更改时中断,请不要感到惊讶。
不要挂在__cplusplus
上。它是核心语言,关于双重下划线等的东西是库。如果这不具说服力,那就认为这是一个小问题。您可以在C ++程序中使用__cplusplus
;它的含义很明确。