【问题标题】:Can a variable be used while being declared?声明时可以使用变量吗?
【发布时间】:2023-03-26 16:05:01
【问题描述】:

为什么下面的编译没有错误?:

int main()
{
    int x = x;  //I thought this should cause an error
    return 0;
}

标准在哪里解释了为什么允许这样做?

【问题讨论】:

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


【解决方案1】:

这个问题在 C 中的答案与在 C++ 中的答案略有不同。

在这两种情况下,int x = x; 都会尝试使用自身初始化 x


在 C++ 中:[dcl.init]/12 (N3936) 表示对具有不确定值的对象的任何评估都会导致未定义的行为,除了某些涉及无符号的情况char。其实有一个例子

int f(bool b) {
    unsigned char c;
    unsigned char d = c; // OK, d has an indeterminate value
    int e = d;        // undefined behavior
    return b ? d : 0; // undefined behavior if b is true
}

在 C 中: 比较复杂。这与int b; foo(b - b); 的行为非常相似,即covered in full here

我不会重复该文本,但结论是,在 C11 中:

  • 当且仅当系统具有int 的陷阱表示时,int a = a; &a; 才会导致 UB
  • int a = a;,没有后续出现 &a,导致 UB。

历史记录:在 C90 中,这导致了 UB。在 C99 中引入了 trap 表示,在 C11 中引入了寄存器陷阱的可能性(对于 Itanium)。 C++ 标准根本不处理陷阱表示,在bitwise operators 生成负零之类的情况下似乎未指定。

【讨论】:

【解决方案2】:

C++ 的答案——我引用的是 C++11 标准:

3.3.3.1:

在块 (6.3) 中声明的名称是该块的本地名称;它具有块范围。它的潜在作用域从其声明点 (3.3.2) 开始,并在其块的末尾结束。

并且声明点在3.3.2中定义为

名称的声明点紧跟在其完整的声明符(第 8 条)之后和其初始化程序(如果有)之前,除非如下所述。

[示例:

 int x = 12;
 { int x = x; }

这里第二个 x 用它自己的(不确定的)值初始化。 —结束示例]

显然,在初始化之前使用x 的值是未定义的行为。

【讨论】:

  • “使用”x 可以包括sizeof x&x。这两者都不是明显未定义的行为。
  • @chux 同意 - 我修改了答案。
  • 是的,我怀疑即使int x = x - x; 也不安全。
  • @chux see here 讨论类似的事情
  • my comment above 这并不能真正解释为什么它是未定义的行为。
【解决方案3】:

C++14 草案 N4140 [basic.scope.pdecl]/1:

名称的声明点紧接在其完整声明符之后(第 8 条)和其之前 initializer(如果有的话),除非如下所述。 [ 例子:

unsigned char x = 12;
{ unsigned char x = x; }

这里第二个x 用它自己的(不确定的)值初始化。 ——结束示例 ]

【讨论】:

  • 您引用的大部分内容都是不规范的注释,它并没有真正解释这是否是未定义的行为。请参阅 my comment above 了解两个问题,这些问题详细回答了 C++ 14 之前和 C++14 之前的这个问题。
  • @ShafikYaghmour:问题不在于这是否是未定义的行为,而在于该变量是否可以在其自己的初始化中使用。有很多情况使用变量而不使用它的值,例如void* p = &p; 所以这是一个完全合理的问题。
  • @BenVoigt 在 C++ 案例中,这个例子显然是未定义的行为,恕我直言,不这样说是一个糟糕的答案。当然有警告,但没有一个解释。现代优化编译器的工作方式,我们需要非常清楚什么是 UB b/c,优化器可以用它认为调用 UB 的代码做非常激进的事情。
  • 请注意,C++14 修改了此示例以使用 unsigned char,因为 int 案例调用了 UB。
【解决方案4】:

这个问题真的有两个部分,一个问:

变量在声明时可以使用吗?

由于其他答案中引用的声明规则,明确的答案是肯定的。

而且,同样重要但没有被问到:

变量在声明期间的哪些用途是安全的?

好吧,在初始化器中,变量还没有完成初始化(对象生命周期的问题),实际上构建还没有开始。对象生命周期规则(标准第 3.8 节)规定,允许对此类变量进行部分但并非所有操作:

在对象的生命周期开始之前但在对象将占用的存储空间已分配之后,或者在对象的生命周期结束之后并且在对象所占用的存储空间被重用或释放之前,任何指向该对象的指针可以使用对象将或曾经位于的存储位置,但只能以有限的方式使用。对于正在建造或破坏的物体,见 12.7。否则, 这样的指针指的是分配的存储空间,并且将指针用作void* 类型的指针是明确定义的。允许通过这样的指针进行间接访问,但生成的左值只能以有限的方式使用,如下所述。如果出现以下情况,则程序具有未定义的行为:

  • 对象将是或曾经是具有非平凡析构函数的类类型,并且指针用作删除表达式的操作数,
  • 指针用于访问非静态数据成员或调用对象的非静态成员函数,或
  • 指针被隐式转换为指向虚拟基类的指针,或者
  • 指针用作static_cast的操作数,除非转换为指向cv的指针,或指向cv的指针 em> void 和随后指向 cv charcv unsigned char
  • 的指针
  • 指针用作dynamic_cast 的操作数。

实际上,对于具有非平凡初始化的类型,内存位置尚不包含对象,因此它没有动态类型,并且尝试以除charunsigned char 之外的任何类型访问它会立即发生冲突严格的别名。

对于初始化简单的类型,包括int,只要获得正确对齐的存储,对象就会存在。但是,如果该存储具有自动或动态存储持续时间,则在写入变量之前,该值是不确定的。 8.5 节中的这条规则适用:

如果没有为对象指定初始化器,则该对象是默认初始化的。当获得具有自动或动态存储持续时间的对象的存储时,该对象具有一个不确定的值,如果没有对该对象执行初始化,则该对象保留一个不确定的值,直到该值被替换。 [ 注意:具有静态或线程存储持续时间的对象是零初始化的,请参见 3.6.2。 — end note ] 如果评估产生不确定的值,则行为未定义,但以下情况除外:

列出的所有例外情况都特定于unsigned char

乍一看,这条规则似乎不适用,因为指定了一个初始化程序。但是,在初始化器的评估过程中,我们恰好处于“当获得具有自动或动态存储持续时间的对象的存储时”的情况,该规则确实适用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    • 2022-01-08
    • 1970-01-01
    • 2016-09-03
    • 1970-01-01
    • 2016-02-19
    相关资源
    最近更新 更多