代码之家  ›  专栏  ›  技术社区  ›  NNguyen

C++采访关于返回int*值的范围问题

  •  -3
  • NNguyen  · 技术社区  · 7 年前

    我想澄清一下,这是一个亚马逊采访问题。所以要有建设性!

    这是从一个网站,我正在审查面试。 问题是以函数的形式附加2个数组。

    // a1(size1) and a2(size2) are arrays and you
    // have to append them.
    int* Append(int* a1,int* a2,int size1,int size2) 
    

    我的问题是。从下面的代码可以看出,为什么数组a实际上位于函数Append中,而一旦离开函数,该数组的作用域就结束了?

    #include <iostream>
    #include <string>
    using namespace std;
    
    
    int* Append(int* a1,int* a2,int size1,int size2)
    {
        int a[size1+size2],i;
    
        for(i=0;i<size1;i++)
        {
             a[i]=a1[i];
        }
    
        for(i=0;i<size2;i++)
        {
             a[i+size1]=a2[i];
        }
    
        return a;
    }
    
    int main()
    {
      int a[] = {1,2,3};
      int b[] = {4,5,6,7};
      int* c;
      c = Append(a,b,3,4);
      for(i=0;i<7;i++)
         cout << c[i] << endl;
      cout << "abc";
    }
    

    “我告诉他,上面的代码实际上失败了。他问我为什么失败。我这样回答他。数组a实际上在函数Append中,一旦你离开函数,这个数组的作用域就结束了。他问我当时怎么做。我不知道。后来他告诉我,我们必须使用malloc为数组分配内存。后来他向我解释了如何使用堆内存。”

    1. 我的问题是。从上面的代码可以看出,为什么数组a实际上在函数Append中,而一旦离开函数,这个数组的作用域就结束了?

    2. 我的第二个问题是,这与堆和堆栈内存有关吗?

    3. 堆和堆栈内存在Java中是如何工作的?Java中的堆和堆栈与C++有何不同?(我用Java编写了更多程序,因此我想了解更多关于Java内存的信息)

    请附上有关该材料的任何链接/url,我想了解更多信息。

    2 回复  |  直到 7 年前
        1
  •  1
  •   Tommy Andersen Gopal Rao    7 年前

    C++支持C所支持的基本数据结构,例如简单数组,但这并不意味着鼓励它这样做。在C++中,通常最好不要编写基本数组,而是使用 std::vector 类或类似的数据结构。

    也就是说,这个问题是一个访谈问题,因此,尽管不鼓励使用这种方法,但总的来说,让我们看看函数以及数组的初始化和分配。

    首先要注意的是,代码没有编译。main函数用一个循环变量保存一个循环 i 未声明。这是一件小事,我们可以很快解决它。编译器输出:

    prog.cpp: In function ‘int main()’:
    prog.cpp:29:7: error: ‘i’ was not declared in this scope
       for(i=0;i<7;i++)
           ^
    

    修复编译器错误后,我们可以运行代码,但编译器仍会提供一些警告,我强烈建议您查看:

    prog.cpp: In function ‘int* Append(int*, int*, int, int)’:
    prog.cpp:8:9: warning: address of local variable ‘a’ returned [-Wreturn- local-addr]
         int a[size1+size2],i;
             ^
    

    警告包含了我们目前需要的所有信息。(我确实知道,在面试时,你可能无法访问编译器来查看这些内容,但当你回到家,想知道为什么你会得到这样的回复时,它们会很有帮助)。

    警告基本上是说功能正在返回 住址 局部变量的。局部变量一旦离开作用域就会被释放,这就是堆栈的工作方式。当我们声明变量时,我们将其推到堆栈中,一旦声明变量的范围离开,我们就会从范围中弹出变量(或者更确切地说是从变量中分配的内存)。

    所以局部变量 a ,在堆栈上函数的开头声明,函数返回后,将返回此局部变量的地址,但释放变量保留的内存。

    是的,这与堆和堆栈有关,因为堆栈上的元素是活动的,而它们在其中声明的范围是活动的,而堆上的元素则保持活动状态,直到它们被明确告知不再活动为止(使用 delete free )。

    当您被告知需要在堆上分配数组时,可以使用 new[] 操作员:

    int* a = new a[size1 + size2];
    

    记住,使用 新建[] 必须使用解除分配 delete[] 。我不同意使用 malloc 但这可能是对代码的一些要求,这将在某个时候发布代码 自由的 ,此时您别无选择,只能使用 马洛克

    我个人也不会这样做,我会使用 标准::矢量 相反,既然可以这么简单,为什么还要担心这个问题呢: std::vector<int> a ?不过,作为一个访谈问题,它被要求测试您对核心语言的理解,虽然能够回答如何在堆上分配数组会很好,但它将显示出更强大的能力,能够反思代码,并推荐一种更好的替代方法。例如不使用 new / 马洛克

    通常,C++返回值并按值解析参数(当我们不使用 通过引用 选项)。这意味着我们返回返回的值的副本。这就是我们如何返回简单整数、double或甚至 std::string (为了简单起见,我们忽略了move构造函数以及它们的工作方式),因为我们复制了它们并返回。实际上,我们还复制了变量 然后把它还给我。所以你可能会想,为什么我们复制 。但正如我们已经确定的那样,从编译器警告中,我们实际返回的值是

    当我们在C++(或C)中声明一个数组时,我们在内存中分配一个区域,该区域足够大,可以容纳请求的元素数。然后,变量将是指向第一个元素地址的指针。我们实际上复制的是那个地址,而不是它所指的实际区域。因此,函数将返回一个指向不再分配的区域的指针。局部变量 ,已不存在,但 被复制,并由函数返回。

    Java是比C++更高级的语言,它允许您以同样的方式操作内存。在Java中,不是原语的所有内容( int ,则, double ,…)保存在堆上。当不再有对Java对象的任何引用时,Java对象将被释放。

        2
  •  1
  •   MonzUn    7 年前

    1: 是的,这是正确的。这通常还会触发编译器发出的警告,即您正在“返回临时地址”或类似的内容。

    2: 是的。在函数作用域中声明的所有变量都将放置在堆栈上,并在作用域(函数)结束时自动销毁。要使其保持到您告诉它不要(使用delete/free)为止,您必须使用new或malloc分配内存。

    3: 我只写了很少的Java,所以我不能完全回答这个问题,但在用Java编程时,你不必考虑堆栈和堆内存(尽管知道它是如何工作的可能会很有好处)。Java有一个在后台运行的“垃圾收集器”,当不再需要时,它会清理它为您分配的任何堆内存(除int、float等原语外的所有变量都在Java中的堆上分配)。当然,这是以性能为代价的。

    在这里,您还可以看到C++中处理内存的不同方法的简要摘要:) Proper stack and heap usage in C++?

    编辑1:我刚刚注意到这个程序甚至没有编译。 在主要功能中;有一个for循环,它在不首先将i声明为整数(或他们想要的任何类型)的情况下将i=0。

    编辑2:也;正如另一位评论家指出的那样;int a[大小1+大小2]不合法。使之合法;size1和size2必须是常量非参数变量,或者必须在堆上分配。