【问题标题】:Why C++ primitive types are not initialized like the rest of types?为什么 C++ 原始类型不像其他类型那样初始化?
【发布时间】:2014-06-09 10:29:38
【问题描述】:

我知道,在 C++ 中,当你编写时

int i;

在您有效地为其赋值之前,您不能对变量将持有的值做出任何假设。但是,如果你写

int i = int();

那么您可以保证i 将是0。所以我的问题是,这实际上不是语言行为的不一致吗?我的意思是,如果我定义了一个类 MyClass 并写了

MyClass myInstance;

我可以放心,将调用不带参数的类的默认构造函数来初始化myInstance(如果没有,编译器将失败),因为这就是 RAII 原则的运作方式。但是,对于原始类型,似乎资源获取不再是初始化。这是为什么呢?

我不认为更改从 C 继承的这种行为会破坏任何现有代码(世界上是否有任何代码假设不能对变量的值做出假设?),所以我想到的主要原因可能是性能,例如在创建原始类型的大数组时;但是,我想知道是否有一些官方对此的解释。

谢谢。

【问题讨论】:

  • "世界上是否有任何代码假设不能对变量的值做出假设?"是的,对于熵池。
  • 认为你从错误的角度看它。从历史上看,变量是未初始化的,所以问题应该是为什么(某些)C++ 的新类类型会改变,这个问题更容易回答。您在问为什么旧类型偏离规范,但旧类型规范。
  • 你可以试着问为什么 MyClass();不需要调用构造函数,因为它使您的视图更加一致。
  • @Étienne Touché,很酷的例子(虽然我希望他们不要依赖未初始化的变量)。
  • @user2672165 你是对的,但这只是因为编译器会将对象构造与函数声明混淆。

标签: c++ constructor primitive raii


【解决方案1】:

没有。这不是矛盾。

如果你的类被定义为:

struct MyClass
{
    int x;
    float y;
    char *z;
};

那么这行并没有你认为的那样:

MyClass myInstance; 

假设上面是在函数内部声明的,则与:

int x; //assuming declared inside a function

在 C++ 中,类型大致分为 3 种,即。 POD、非 POD、聚合——它们之间有明显的区别。请阅读它们及其初始化规则(关于它们的主题太多。在此站点上搜索)。另请阅读static initialization and dynamic initialization

【讨论】:

  • 灰色阴影——标准布局,聚合... ;-)
  • 我不确定聚合是否经过特殊处理;聚合初始化遵循其成员的规则。这不是 POD 与非 POD;具有虚函数但没有用户定义的构造函数的类不是 POD,但它将被初始化为一个。而且您错过了最重要的区别:静态生命周期与其他生命周期。
  • @JamesKanze:在 C++03 中引入了值初始化(作为唯一的新语言功能,AFAIK),以清理聚合的初始化行为。特别是非 POD 的。所以有特殊待遇。
  • @Nawaz 谢谢。我刚刚在stackoverflow.com/questions/4178175/… 找到了这个参考资料,涵盖了关于 POD 和聚合的几个方面,另一个关于初始化在这里stackoverflow.com/questions/620137/…
  • @Alf 引入值初始化的概念是为了澄清 C++98 中不明确的问题。它实际上并没有改变任何关于 intent 的东西(编译器在实践中实际上做了什么)。
【解决方案2】:

真正的原因,至少在最初,是 C++ 想要所有 与 C 兼容的对象的行为与它们完全相同 会在 C 中。C 中的原因 (现在仍然是)性能; 具有静态生命周期的对象的零初始化是免费的 (因为操作系统必须初始化它提供的所有内存 无论如何处理,出于安全原因);零初始化 否则会花费运行时间。 (性能原理较少 今天比原来强大,因为编译器很多 更好地确定变量将被初始化 稍后,并在这种情况下抑制零初始化; 但它们仍然存在;特别是在以下情况下:

char buffer[1000];
strcpy( buffer, something );

如果需要零初始化,我不知道有任何 编译器可以在这里跳过它,即使它 没有必要。)

【讨论】:

  • 静态初始化的性能损失也可能小于局部变量,因为静态初始化只发生一次。
  • @user2672165 静态零初始化的性能损失为 0,至少在经典通用机器上是这样。
  • 如果 strcpy() 在您的特定 C++ 变体中内联,一个好的编译器可能能够优化初始化。否则:发生这种情况的可能性很小。
  • @Tonny 它还必须证明你永远不会访问后面的'\0',因为strcpy 不会初始化这些。
【解决方案3】:

如果你写

int i;

然后初始化与否取决于上下文。

  • 命名空间范围 → 零初始化。
  • 本地函数范围 → 未初始化。
  • 类成员:取决于构造函数,如果有的话。

局部变量没有初始化只是为了提高效率。对于在最低级别重复调用的非常简单的函数,这可能很重要。而 C 和 C++ 是用来构建事物底层的语言。

【讨论】:

  • 初始化依赖于对象生命周期,不是范围。具有静态生命周期的对象(即使它们具有本地范围)在其他任何东西之前初始化为零(这包括类成员,即使该类具有构造函数)。
  • @JamesKanze:关于问题中的声明int i; 并在答案中讨论,您是错误。它不能是静态的,除非它在命名空间范围内,或者在静态类类型对象中(在这种情况下,它取决于构造函数,如果有的话)。我知道您通常指的是或意指变量,但是通过将您的评论作为对此答案的评论,读者看起来就像它与答案中的某些内容相矛盾。
  • void f() { static int i; /*...*/ }。初始化依赖于上下文;它取决于对象的生命周期和对象的类型。当然,即使在class MyClass { int i; MyClass() {} }; 中,MyClass:i 也会在MyClass 的静态实例中初始化为零。
  • @JamesKanze:请注意,您引入了关键字static,该关键字未出现在问题或答案中。同样,通过引入关键字unsigned,您可以更改变量的某些属性。等等。通常,声明会随着您添加各种关键字而改变含义。
【解决方案4】:

当您将函数中的局部变量设置为某个值时,每次调用该函数时,都会进行赋值并将该值加载到堆栈中。

例如:

void func()
{
    int i = 0; // Every time `func` is called, '0' is loaded into the stack
    ...
}

这是您可能希望避免的事情,特别是因为 C 和 C++ 语言也被指定用于实时系统,在这些系统中,每个操作都很重要。

顺便说一句,当你声明 MyClass myInstance 时,你确实可以放心调用默认构造函数,但你可以选择是否要在该构造函数中执行任何操作。

因此,C 和 C++ 语言也允许您对原始类型变量做出相同的选择。

【讨论】:

  • 重要的不是实时系统;它是 CPU 密集型的(并且需要大量时间)。
  • @JamesKanze 我认为你从来没有在嵌入式实时东西上编程过?在严格的条件下,其中一些可能会在能够使用高级编程语言和必须使用汇编程序之间产生重大影响。有时几个时钟周期就是你所拥有的全部余地。你不希望任何不必要的事情发生在你背后。诚然:C++ 通常不是实时内容的首选语言。它通常是 C 语言,针对特定平台进行了额外的调整和优化。
  • @Tonny 我早期职业生涯的大部分时间都在嵌入式实时领域。如果事情那么紧张,我们会使用更快的处理器。 (也有例外,我不得不在其中进行处理,但当我到了那个地步时,我正在用汇编程序编写。)关于实时的重要事情是响应时间有一个确定性的上限。所以我们会使用堆排序,而不是快速排序(假设我们必须进行排序,这是我从未见过的),尽管事实上堆排序通常要慢得多。
  • @JamesKanze 看来我们当时都是从同一个行业开始的。但是如果我在工作,通常不存在更快的 CPU。所以我们经常最终查看由 C 编译器生成的汇编程序,然后手动优化它。 (我做了很多 Hitachi H8 的东西。编译器在优化方面非常糟糕。)或者我们一直在寻找真正关键的部分。我同意确定性(和可预测性)是嵌入式实时的一切。从来不需要排序:-)
猜你喜欢
  • 1970-01-01
  • 2011-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-05
  • 2022-01-07
  • 2019-06-13
  • 2013-10-14
相关资源
最近更新 更多