C++中的存储分配

7
我正在阅读《Thinking in C++》第6章“初始化和清理”。作者说:
实际上,编译器更可能遵循C语言的做法,在该作用域的左花括号处分配全部存储空间。这并不重要,因为作为程序员,在定义之前你不能访问存储空间。尽管存储空间在块的开头分配,但构造函数的调用直到对象在序列点中被定义才发生,因为在此之前标识符不可用。编译器甚至会检查确保你不会把对象定义放在条件传递该序列点的位置,例如在switch语句中或某个goto可以跳过它的位置。
然后作者给出了以下示例:
class X {
public:
  X();
};

X::X() {}

void f(int i) {
  if(i < 10) {
   //! goto jump1; // Error: goto bypasses init
  }
  X x1;  // Constructor called here
 jump1:
  switch(i) {
    case 1 :
      X x2;  // Constructor called here
      break;
  // case 2 : // Error: case bypasses init
      X x3;  // Constructor called here
      break;
  }
} 

int main() {
  f(9);
  f(11);
}///:~

我不明白上面的代码为什么可以正常工作?根据我的理解,如果i不是1,x2就可以跳过初始化。
补充说明: 这句话 "实际上更可能的是编译器会遵循C中分配作用域存储的惯例,在该作用域的左括号处分配所有存储。" 也让我感到困惑。 根据作者的描述,在 switch 的左括号处,编译器已经为 x2x3 分配了空间。如果是这样,那么有机会使 x2 没有初始化(情况1不满足)。
4个回答

5
根据我的理解,如果i不等于1,x2可能会绕过初始化。
不,要么执行case 1,定义并在switch块的末尾销毁x2,要么不执行任何case,整个switch块都不起作用,因此x2不在范围内,因此它没有被初始化,但也无法引用。所以它存在并且可以安全使用,或者它不存在。

谢谢 Wakely。还有一个问题,如果我取消注释 case 2:,会出现错误。为什么? - Fihop
1
因为程序的控制流可能会跳到 case 2 并绕过 x2 的初始化,在 C++ 中是不允许的。正如书中所说: "编译器甚至会检查以确保您没有将对象定义放在条件通过它的序列点处,例如在 switch 语句或某个 goto 可以跳过它的地方。" - Jonathan Wakely
嗨,Janathan,我已经编辑了问题并提出了另一个问题。你能再帮我一次忙吗? - Fihop
"x2 有可能未被初始化"的机会,因为您无法引用该对象或使用它。如果已经为其保留了某些(未初始化的)内存,则无论如何也不能访问在 x2 不在作用域时保留的内存。这就是Eckel所说的:尽管编译器事先知道对象将在内存中的位置,但程序员在安全地初始化保留的内存之前不能使用该对象。 - Jonathan Wakely

2

C++对象初始化和销毁的语义是完全跳跃安全的(包括异常、goto、switch和循环结构)。

(唯一的例外来自于C标准库 (setjmp/longjmp) )

6.6 跳转语句

2 在离开一个作用域时(无论怎样实现),在该作用域中构造的具有自动存储期的对象按照它们的构造顺序进行销毁。[注意:对于临时对象,请参见12.2.] 从循环、块或者回退经过具有自动存储期的已初始化变量的传递,涉及到在传递点处处于范围内但不在传递点处的具有自动存储期的对象的销毁。(有关进入块的转移,请参见6.7)。[ 注意:然而,程序可以终止(例如通过调用std::exit() 或 std::abort() (18.5)),而不销毁具有自动存储期的类对象。 —end note ]


那个规则适用于C++03,可能也适用于C++98。它不会破坏C代码,因为所有的C++标准都允许在初始化对象具有C兼容类型时进行异常处理。 - aschepler
我不确定你关于“破坏C代码”的评论是什么意思?是的,内置函数(POD,我现在不确定)是一种不同的东西。在整个语言中。而且,是的,正如我明确展示的那样,在C++11之前,关于跳过初始化程序的规则更加宽松。 - sehe
等等,不对。某个东西不可能是C++03的POD类型,但却不符合C++11 6.7p3中的措辞,是吧? - aschepler
@aschepler 非常感谢。这就解释了一切 :) 我在脑海中把附录名称搞混了。谢谢。 - sehe
嗨,sehe,我已经编辑了这个问题。你能帮我看一下吗? - Fihop
显示剩余3条评论

0
问题在于,当您退出其作用域时,本地初始化变量会被销毁。C++认为switch、if、while和for都是它们自己的作用域,涉及创建变量。

好的,现在你甚至自己说了!我不明白你不理解什么,它只是将其视为自己的范围 - 当你初始化(而不是分配)一个局部变量时 - 它将在该范围的末尾被销毁。 - Ariel Pinchover

0
不,即使i是1,也无法绕过x2。使用goto的问题在于,如果它被执行,x1的构造函数将不会被调用(请记住,构造函数在X x1;语句处“调用”-因为对构造函数的调用在跳转目标之上,您将处于一个x1仍在作用域内的点)。
但是,您无法跳过X x2;这一行,并最终到达一个x2仍然在作用域内的点,所以这是可以的。

嗨,尼克。我已经编辑了这个问题。你能帮我看一下吗? - Fihop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接