【问题标题】:C++ function called without object initialization在没有对象初始化的情况下调用 C++ 函数
【发布时间】:2011-07-18 15:29:23
【问题描述】:

为什么下面的代码会运行?

#include <iostream>
class A {
    int num;
    public:
        void foo(){ num=5; std::cout<< "num="; std::cout<<num;}
};

int main() {
    A* a;
    a->foo();
    return 0;
}

输出是

num=5

我使用 gcc 编译它,我在第 10 行只收到以下编译器警告:

警告:“a”在此函数中未初始化

但根据我的理解,这段代码不应该根本不运行吗?当 num 不存在时,它为什么将值 5 分配给 num 因为还没有创建 A 类型的对象?

【问题讨论】:

  • +1。另一个问题:如果您没有成员num,您应该可以期待这项工作吗?例如,如果它只包含std::cout &lt;&lt; "num=";,那么它就是本地无国籍的(我是在合理地询问,而不仅仅是提供思考的食物)
  • @Merlyn Morgan-Graham:不。这里的代码取消引用了一个未初始化的指针。这是未定义的行为(即程序可以做任何事情(包括看起来可以工作))。
  • @Martin:我想我可以看到规范没有定义这个,所以你不应该依赖它,但是编译器可能会做什么会导致它不起作用(假设没有继承,并且没有数据成员)
  • @Martin:别担心,我不打算这样做。我只是想了解在哪些情况下编译器可能会生成可能导致其中断的代码。我了解 UB 并取消引用未初始化的数据。但是,这个“取消引用运算符”不会(必然)取消引用任何内容。它仅用于初始化this 指针,而this 指针在我的场景中未使用。当然,第二个任何人添加/使用对象状态都会中断,但这不是我感兴趣的。除了 vtables,为什么任何 C++ 编译器都会取消引用未使用的 this 指针?

标签: c++ pointers object initialization


【解决方案1】:

代码会产生未定义的行为,因为它试图取消引用未初始化的指针。未定义的行为是不可预测的,并且不遵循任何逻辑。出于这个原因,任何关于你的代码为什么做某事或不做某事的问题都是没有意义的。

你问它为什么运行?它不运行。它会产生未定义的行为

您是在问它如何将 5 分配给一个不存在的成员?它不会给任何东西分配任何东西。它会产生未定义的行为

你是说输出是5?错误的。输出不是5。没有有意义的输出。代码会产生未定义的行为。仅仅因为它在你的实验中以某种方式碰巧打印了5,这绝对没有任何意义,也没有任何有意义的解释。

【讨论】:

    【解决方案2】:

    你还没有初始化*a

    试试这个:

    #include <iostream>
    
    class A
    {
        int num;
        public:
            void foo(){ std::cout<< "num="; num=5; std::cout<<num;}
    };
    
    int main()
    {
        A* a = new A();
        a->foo();
        return 0;
    }
    

    未(正确)初始化指针可能会导致未定义的行为。如果幸运的话,您的指针会指向堆中的某个位置,该位置可用于初始化*。 (假设您这样做时没有引发异常。)如果您不走运,您将覆盖用于其他目的的一部分内存。如果你真的很倒霉,这将被忽视。

    这不是安全代码; “黑客”可能会利用它。

    *当然,即使您访问该位置,也不能保证以后不会“初始化”。


    “幸运”(实际上,“幸运”会让你的程序更难调试):

    // uninitialized memory 0x00000042 to 0x0000004B
    A* a;
    // a = 0x00000042;
    *a = "lalalalala";
    // "Nothing" happens
    

    “不幸”(让调试程序更容易,所以我不认为它“不幸”,真的):

    void* a;
    // a = &main;
    *a = "lalalalala";
    // Not good. *Might* cause a crash.
    // Perhaps someone can tell me exactly what'll happen?
    

    【讨论】:

    • 我故意没有初始化 *a。关键是我可以在 foo() 中设置 'num' 的值,而无需初始化 *a!
    【解决方案3】:

    A* a; 是一个未初始化的指针。

    你看到的值是垃圾,你很幸运没有崩溃。

    这里没有初始化。

    这里没有作业。

    您的课程恰好足够简单,没有出现更严重的问题。

    A* a(0); 会导致崩溃。在某些情况下,未初始化的指针会导致崩溃,并且更容易使用更复杂的类型进行复制。

    这是处理未初始化的指针和对象的结果,它指出了编译器警告的重要性。

    【讨论】:

    • 我怀疑这个值是垃圾。如果我将 foo 中 num 的值从 num = 5 更改为 num = any number,我会在输出中得到该数字。
    • 它肯定垃圾 - 通过写入 num 的值,您正在覆盖内存中附近其他东西的地址。
    • Apoorva Iyer,您显然不了解“未定义行为”的概念。
    • 哦,那样。所以我正在覆盖其他一些垃圾地址!我以为您的意思是 num 的值是垃圾。这更有意义。谢谢!
    • 另外,由于缺少赋值而“获得”的值是垃圾(特别是)。除此之外,您正在访问有效或无效的内存地址(== 崩溃或非常神秘的行为)。
    【解决方案4】:
    A* a;
    a->foo();
    

    这会调用未定义的行为。最常见的情况是它会导致程序崩溃。

    C++03 标准的第 4.1/1 节说,

    a 的左值 (3.10) 非函数,非数组类型 T 可以是 转换为右值。如果 T 是一个 不完整的类型,一个程序 需要这种转换是 格式不正确。如果对象 左值引用不是类型的对象 T 并且不是类型的对象 派生自 T,或 如果对象是 未初始化,一个程序 需要这种转换有 未定义的行为。如果 T 是一个 非类类型,右值的类型 是 T 的 cv 不合格版本。 否则,右值的类型是 T.

    查看此类似主题:Where exactly does C++ standard say dereferencing an uninitialized pointer is undefined behavior?


    为什么在 num 不存在的情况下将值 5 分配给 num,因为尚未创建 A 类型的对象。

    这叫幸运。但它不会总是发生。

    【讨论】:

      【解决方案5】:

      这就是我认为会发生的事情。

      a-&gt;foo(); 因为你只是打电话给A::foo(a).

      a 是一个指针类型变量,位于 main 的调用堆栈中。 foo() 函数在访问位置 a 时可能会抛出分段错误,但如果没有,则 foo() 只是从 a 中跳转一些位置并用值 5 覆盖 4 个字节的内存。然后读取相同的价值。

      我是对还是错?请让我知道,我正在学习调用堆栈,如果对我的回答有任何反馈,我将不胜感激。

      也看下面的代码

      #include<iostream>
      class A {
          int num;
          public:
              void foo(){ num=5; std::cout<< "num="; std::cout<<num;}
      };
      
      int main() {
      
          A* a;
          std::cout<<"sizeof A is "<<sizeof(A*)<<std::endl;
          std::cout<<"sizeof int is "<<sizeof(int)<<std::endl;
          int buffer=44;
          std::cout<<"buffer is "<<buffer<<std::endl;
          a=(A*)&buffer;
      
          a->foo();
          std::cout<<"\nbuffer is "<<buffer<<std::endl;
          return 0;
      }
      

      【讨论】:

        【解决方案6】:

        在创建对象时,即使您不使用关键字new,也会为该特定对象分配类成员,因为该对象是指向类的指针。因此,您的代码运行良好,并为您提供了 num 的值,但 GCC 发出警告,因为您没有显式实例化该对象。

        【讨论】:

          【解决方案7】:

          我会向您指出(呵呵)我之前对一个非常相似的问题的回答:Tiny crashing program

          基本上,您正在用指针覆盖envs 堆栈变量,因为您尚未将envs 添加到main 声明中。

          由于envs 是一个数组(字符串)数组,它实际上被分配了很多,并且您正在用5 覆盖该列表中的第一个指针,然后再次读取它以使用cout 打印。

          现在这是为什么会发生的答案。你显然不应该依赖这个。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-07-20
            • 2015-08-10
            • 1970-01-01
            • 2021-01-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多