引用保留名称肯定是违法的吗

Is it definitely illegal to refer to a reserved name?

本文关键字:保留 引用      更新时间:2023-10-16

std提案列表上,给出了以下代码:

#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提案列表中,有人声称,仅引用这样一个保留名称,这段代码本身就不符合格式。我现在可以看到短语";不得以其他方式使用";来自CCD_ 7的数据可能确实表明了这一点。

给出的一个实用原理是,例如,宏_ALGORITHM可以扩展到一些擦除硬盘的代码。然而,考虑到该规则的可能意图,我认为这种可能性更多地与_ALGORITHM名称的明显实现定义*性质有关,而与引用它完全非法无关。

*";实现定义的";在其英语语言意义上,而不是短语的C++标准意义

我想说的是,只要我们很高兴我们将获得实现定义的结果,并且我们应该调查该宏对我们的实现意味着什么(如果它存在的话!),只要我们不试图修改它,引用这样的宏就不应该是固有的非法行为。

例如,下面这样的代码到处都使用来区分编译为C的代码和编译为C++的代码:

#ifdef __cplusplus
extern "C" {
#endif

我从来没有听到过这样的抱怨。

那么,你觉得呢?是否";不得以其他方式使用";包括简单地写这样一个名字吗?或者它可能不打算如此严格(这可能意味着有机会调整标准措辞)?

它是否合法取决于实现(和标识符)。

当标准赋予实现使用这些名称的唯一权利时,包括在用户代码中提供这些名称的权利。如果一个实现做到了,那就太好了。

但是,如果一个实施没有明确赋予你权利,那么从"不得以其他方式使用"中可以清楚地看出,该标准没有,你有未定义的行为。

重要部分是"保留给实现">。这意味着编译器供应商可能会使用这些名称,甚至记录它们。然后,您的代码可以使用文档中的这些名称。这通常用于像__builtin_expect这样的扩展,编译器供应商通过使用这些保留名称来避免与标识符(由代码声明的)发生任何冲突。即使是标准也将它们用于__attribute__之类的东西,以确保在添加新功能时不会破坏现有(合法)代码。

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1882

每个包含双下划线__或以下划线后跟大写字母开头的标识符都保留给实现以供任何使用。

任何使用。(在应用缺陷修复之前和之后都会出现类似的文本)

CCD_ 12由标准定义。CCD_ 13由标准保留以供实现使用。这些看起来很不一样?(该标准的两个部分确实存在冲突,其中一个声明__cplusplus是为任何用途保留的,另一个则专门使用它,但我认为冲突的赢家是明确的)。

根据该标准,_ALGORITHM标识符可以作为预处理步骤的一部分,表示"用硬盘删除代码替换此源代码"。它的存在(在预处理之前或之后)可能足以完全改变程序行为。

现在这不太可能,但我不认为这会导致不符合要求的实施。这只是执行质量的问题。

实现可以自由地记录和定义_ALGORITHM的含义。例如,它可以记录它是<algorithm>的头保护,并指示是否包含该头文件。将您当前的<algorithm>实现视为文档可能会走得更远。

我想在C模式下使用__cplusplus在技术上与使用_ALGORITHM"一样糟糕",但这个问题是一个C++问题,而不是C问题。我还没有深入研究c标准来寻找关于它的报价

[cpp预定义]中的名称不同。它们具有特定的含义,因此实现不能将它们保留用于任何用途,并且在程序中使用它们具有定义明确的可移植含义。使用特定于实现的标识符(例如_ALGORITHM)是格式错误的,因为它违反了shall规则。

是的,我完全知道库规范使用"shall"来表示"这是对用户代码的要求,违反行为是UB,而不是格式错误"的多个示例。

无论是UB还是定义的实现,运行一个格式错误的程序都会导致UB。标准的措辞清楚地表明该程序是格式错误的,如果实施仍然选择接受该程序并运行它,就会发生UB

因此,如果一个程序使用标识符_ALGORITHM,则该程序是格式错误的,并且运行这样的程序是UB,但这并不意味着它在使用_ALGORITHM作为包含保护的实现上不能正常工作,也不意味着它不能在不使用的实现上正常工作。

如果用户担心这种格式错误和潜在的UB,并表示用户希望编写可移植C++,则不应在可移植C++程序中使用保留标识符。如果用户接受,无论标准禁止这种使用,任何实际的实现都不会擦除你的硬盘驱动器,他们可以自由使用这种保留的标识符,但从标准的文字来看,这种使用仍然是不规范的。

从历史上看,使用此类标记"未定义行为"的目的是编译器可以自由地将他们想要的任何含义附加到C标准中未定义的任何此类标记上。例如,在一些嵌入式处理器上,使用__xdata作为变量的存储类会要求将其存储在RAM的一个区域中,该区域的访问速度比正常的变量存储区域慢,但要大得多。在该系列的典型处理器上,"正常"变量的存储将限制在大约100字节,但扩展数据变量的存储可能要大得多,最高可达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的平台上编写的现有代码时,前一种模式有时可能很有用,这样它既可以在使用/需要该指令的平台上使用,也可以在不使用该指令的平台上使用。

它是否合法是当地法律的问题。它是否意味着什么,如果是,意味着什么是语言定义的问题。当您使用为实现保留的名称时,程序的行为是未定义的。这意味着语言定义不会告诉你程序的作用。没有更多,没有更少。如果您使用的编译器记录了特定保留标识符的作用,那么您可以将该标识符与该编译器一起使用。如果你搜索标题并猜测各种未记录的标识符意味着什么,你可能可以使用它们,但如果你的代码在后续更新更改时中断,也不要感到惊讶。

不要挂断__cplusplus。它是核心语言,关于双下划线等的东西就是库。如果这不能令人信服,那就认为这是一个小故障。您可以在C++程序中使用__cplusplus;它的含义是明确的。