【问题标题】:Is it undefined behavior or illegal, when I use a name that is both already declared outside of the class and later declared inside the class?当我使用已经在类外声明并随后在类内声明的名称时,它是未定义的行为还是非法的?
【发布时间】:2020-10-04 14:18:35
【问题描述】:

什么时候发生:

  1. 我在全局范围内声明并定义了一个名称 X(对象或类型)。
  2. 我开始写一堂课。在类内部,但在函数体等外部,我使用 X。
  3. 稍后在课堂上,我再次声明名称 X。

the Class Scope page on cppreference.com 上,这被视为未定义行为。代码 sn-p 如下所示:

typedef int c; // ::c
enum { i = 1 }; // ::i
class X {
    char v[i]; // Error: at this point, i refers to ::i
               // but there is also X::i
    int f() {
         return sizeof(c); // OK: X::c, not ::c is in scope inside a member function
    }
    char c; // X::c
    enum { i = 2 }; // X::i
};
 
typedef char* T;
struct Y {
    T a; // Error: at this point, T refers to ::T
         // but there is also Y::T
    typedef long T;
    T b;
};

但是在 Stanley B. Lippman 的书Inside the C++ Object Model的第 3.1 章中,编译器应该会引发错误

他的cmets:

以下面的代码片段为例,两者中的长度类型 成员函数签名解析为全局 typedef 的签名,即 int。当遇到嵌套类型定义的后续声明时, 标准要求将较早的绑定标记为非法

他的代码 sn-p 是这样的:

typedef int length;
class Point3d {
public:
  // oops: length resolves to global
  // ok: _val resolves to Point3d::_val
  void mumble( length val ) { _val = val; }
  length mumble() { return _val; }

private:
  // length must be seen before its first reference within the class.
  // This declaration makes the prior reference illegal.
  typedef float length;
  length _val;
};

我用 clang 7.0.0 测试,没有警告或错误,length 似乎绑定到int。我知道编译器测试结果不能用于分析 UB,所以我问这个问题。

谁是对的?或者如果他们都是正确的,我错过了什么?现行标准对此有何规定?

【问题讨论】:

  • 不,没有 UB。编译器可以明确地解析所有 typedef 引用。 c 甚至没有用作类型 BTW。
  • 有些编译器比其他编译器抱怨更多,直播 - godbolt.org/z/s7e4Er
  • 该页面上没有任何地方表明这是 UB。格式错误的 NDR 是不同的。不要随意换词!!
  • 关于格式错误的 NDR 是否为 UB 的争论,请参阅答案和 cmets here。我会说它把讨论带到了错误的地方......主要问题是代码是否合法,如果不合法,clang 是否有编译它的错误。底线是the code is illegal but clang is ok for compiling it
  • @AsteroidsWithWings 感谢您指出!我不知道有一个概念叫做 ill-formed NDR,点击链接会跳转到未定义行为的页面,所以我只是假设它们是相同的含义。我的错。

标签: c++ language-lawyer undefined-behavior


【解决方案1】:

问题已经指向正确的 cppreference sn -p [Class Scope]:

类中声明的名称的潜在范围从声明点开始,包括类主体的其余部分和所有函数主体...

然后在同一个 cppreference 页面上:

如果在声明之前在类主体中使用了名称,并且该名称的另一个声明在范围内,则程序格式错误,不需要诊断。

根据上面的说法,听起来 gcc 对错误是正确的,clang 也是正确的(因为不需要诊断),但是过于宽容,允许形成错误编译代码。

规范中的相关措辞[basic.scope.pdecl] 6.4.2/1 - Point of Declaration

... 枚举的声明点紧跟在其枚举说明符 ([dcl.enum]) 或其第一个不透明枚举声明 ([dcl.enum]) 中的标识符(如果有)之后, 以先到者为准。别名或别名模板的声明点紧跟在别名所指的定义类型标识之后。

还有[basic.scope.declarative] 6.4.1/4.2:

确切地说,一个声明应声明一个不是 typedef 名称的类名或枚举名,而其他声明都应引用相同的变量、非静态数据成员或枚举数,或者都引用函数和函数模板;在这种情况下,类名或枚举名是隐藏的([basic.scope.hiding])。

然后[basic.scope.class] 6.4.7/2 - Class scope:

在类 S 中使用的名称 N 应在其上下文中引用相同的声明,并且在 S 的完整范围内重新评估时。违反此规则不需要诊断。

底线:此代码格式错误(因此如果编译为can be viewed as undefined behavior),但编译器可以忽略它,因为不需要诊断。

【讨论】:

  • 当您从标准中引用的确切短语是“不需要诊断”时,clang 如何“错误地”过于宽容?这正是格式错误的 NDR
  • ^ 这意味着 GCC 和 Clang 都是正确的。 (但是是的,编译器报告此错误可能会更好。)
  • @AsteroidsWithWings 我相信当代码不遵循规范规则时,即 IFNDR,代码是 UB
  • 好吧,当然有一些重叠,但它可能很模糊,我认为没有理由偏离标准语言。
  • @AsteroidsWithWings 如果某物不是 C++ 程序,那么 C++ 标准如何定义其行为?它不能,因此,它的行为是未定义的。
【解决方案2】:

他们都是对的。

正如您引用的文章所述,这确实没有具有未定义的行为,而是格式错误,不需要诊断

这是标准中一个非常具体的短语,具有非常具体的含义,与您所看到的完全相关:程序格式错误,但实现没有如果它不想诊断它。

因此,您可能会收到错误,也可能不会。一切都很好。

但是修复你的代码。 ?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-17
    • 2017-07-29
    • 2020-06-21
    • 2011-08-25
    • 1970-01-01
    • 1970-01-01
    • 2011-01-07
    相关资源
    最近更新 更多