修改其声明语句中的变量是否定义明确?

时间:2018-04-19 02:41:06

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

例如:

#include<iostream>
using namespace std;
int main() {
    int i = i=0; //no warning
    cout << i << endl;
    return 0;
}

在vs2015中编译,没有警告和输出0。这个代码片段是否定义明确,虽然看起来有点奇怪?

但是,在此online compilerg++ prog.cc -Wall -Wextra -std=c++17)中,它会发出以下警告:

prog.cc: In function '`int main()`':  
prog.cc:8:12: warning: operation on '`i`' may be undefined [-Wsequence-point]
     `int i=i=0;`

2 个答案:

答案 0 :(得分:13)

有两种可能的情况,取决于对象的生命周期是否已经开始。这取决于[basic.life]中的第一条规则:

  

对象或引用的生命周期是对象或引用的运行时属性。如果一个对象是一个类或聚合类型,并且它或它的一个子对象是由一个初始化的,那么它被认为具有非空的初始化。   除了一个普通的默认构造函数之外的构造函数。 [注意:通过简单的复制/移动构造函数进行初始化是非空的初始化。 - 结束注释]类型为T的对象的生命周期始于:

     
      
  • 获得具有T类型的正确对齐和大小的存储,并且
  •   
  • 如果对象具有非空的初始化,则其初始化完成,除非对象是联合成员或其子对象,其生命周期仅在该联合成员是联合中的初始化成员时开始,或者如([class.union])。
  •   
  1. 类或聚合类型的对象

    std::string s = std::to_string(s.size()); // UB
    

    在这种情况下,对象的生命周期不会在初始化完成之前开始,因此[basic.life]中的此规则适用:

      

    类似地,在对象的生命周期开始之前但在对象将占用的存储之后已经分配,​​或者在对象的生命周期结束之后以及在重用或释放对象占用的存储之前,任何glvalue可以使用引用原始对象但仅限于有限的方式。对于正在建造或销毁的物体,请参阅([class.cdtor])。否则,这样的glvalue指的是分配的存储,并且使用不依赖于其值的glvalue的属性是很好的定义。该   程序具有未定义的行为,如果:

         
        
    • glvalue用于访问对象,或
    •   
    • glvalue用于调用对象的非静态成员函数,或
    •   
    • glvalue绑定到对虚拟基类的引用,或
    •   
    • glvalue用作dynamic_cast的操作数或typeid的操作数。
    •   

    在此示例中,glvalue用于访问非静态成员,从而导致未定义的行为。

  2. 原始类型的对象

    int i = (i=0); // ok
    int k = (k&0); // UB
    

    这里,即使有一个初始化器,由于类型的原因,初始化也不能是非空的。因此,对象的生命周期已经开始,上述规则不适用。

    但是,对象中的现有值是不确定的(除非对象具有静态存储持续时间,在这种情况下,静态初始化为其赋值为零)。引用具有不确定值的对象的glvalue绝不能进行左值到右值的转换。因此&#34;只写&#34;允许操作,但大多数读取不确定值的 1 操作会导致未定义的行为。

    适用的规则可在[dcl.init]中找到:

      

    如果没有为对象指定初始值设定项,则默认初始化该对象。 获取具有自动或动态存储持续时间的对象的存储时,该对象具有不确定的值,如果未对该对象执行初始化,则该对象将保留不确定的值,直到替换该值为止。 [注意:具有静态或线程存储持续时间的对象已初始化为零,请参阅([basic.start.static])。 - 结束说明]

         

    如果评估产生不确定的值,则行为未定义,但以下情况除外

         
        
    • 如果是无符号窄字符类型或std::byte类型的不确定值   评估产生:

           
          
      • 条件表达式的第二个或第三个操作数,
      •   
      • 逗号表达式的右操作数,
      •   
      • 转换或转换为无符号窄字符类型或std::byte类型的操作数,或
      •   
      • 废弃值表达式,
      •   
           

      然后操作的结果是一个不确定的值。

    •   
    • 如果通过评估一个简单赋值运算符的右操作数产生无符号窄字符类型或std::byte类型的不确定值,该操作符的第一个操作数是无符号窄字符类型或std :: byte类型的左值,一个不确定的值替换左操作数引用的对象的值。
    •   
    • 如果在初始化无符号窄字符类型的对象时通过初始化表达式的求值产生无符号窄字符类型的不确定值,则该对象被初始化为不确定的值。
    •   
    • 如果在初始化std::byte类型的对象时通过初始化表达式的计算产生无符号窄字符类型或std::byte类型的不确定值,则该对象将初始化为不确定的值。 / LI>   
  3. 1 使用字符类型复制不确定的值有一个很小的例外,使目标值也不确定。该值仍然无法用于其他操作,如按位运算符或算术。

答案 1 :(得分:1)

  

在声明语句中修改变量是否定义良好?

int i = i=0;//no warning

上面的陈述是初始化,并且定义明确,因为两个i在同一范围内。

根据basic.scope.pdecl#1

  

名称的声明点在完成后立即生效   声明者和初始化者(如果有的话),除非如下所述。   [实施例:

unsigned char x = 12; // Warning -Wunused-variable
{ unsigned char x = x; }
                ^ warning -Wuninitialized
     

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

在该示例中,第二个x位于不同的块范围内,其值不确定。并会发出警告:

warning: 'x' is used uninitialized in this function [-Wuninitialized]

鉴于具有自动存储功能的局部变量如果没有初始化将具有不确定的值,我相信会发生具有此订单的任务。

int (i = (i = 0));

示例

int x; // indeterminate
int i = i = x = 2; // x is assigned to two, then x's value is assigned to i
cout << x << " " << i; // i = 2, x = 2