C ++ Arguments Heap vs Stack,原始类型的数组

时间:2018-05-28 08:54:00

标签: c++

我发现了一些关于对象和STL的“Heap vs Stack”问题和答案。 (c++ arguments heap-vs-stack)(请告诉我,如果向量或队列等STL对象与自定义类对象的行为不同。)

在处理其中一个编程竞赛问题时,我发现原始类型的数组也作为参考传递而不是手动复制(例如,f(bool boo[10][10]))。

我通过使用基本类型的本地数组并将参数复制到本地数组来解决这个问题。

bool local_boo[10][10]; 
memcpy(local_boo, boo, sizeof(bool) * 10 * 10);

此外,可以安全地假设,当原始类型数组的大小已知且已修复时,我将只尝试每个函数调用boo的单独副本。

以下是我的问题。

1)有更好的方法吗?

2)有没有人知道在函数调用期间是否会通过Heap或Stack传递参数的任何好的引用?

3)是否有更好的方法来确定是否在堆或堆栈上声明了某些内容而不是打印变量的地址并通过检查地址来确定?

谢谢。

4 个答案:

答案 0 :(得分:5)

  

2)有没有人知道在函数调用期间是否会通过Heap或Stack传递参数的任何好的引用?

C ++标准中未提及call stack(这是一个实现细节;请参阅n3337或更新的标准)。 calling conventions特定于某些实现,因此请研究系统的ABIAutomatic variables可能不会坐在堆栈上(例如,它们只能在寄存器中,或者在编译时完全消失)。

在x86-64上,大多数ABI(参见this)定义了前几个(通常是6个)参数,当它们是“标量”时,通过寄存器传递(和细节,例如参数类型,问题)很多:浮点值的传递方式与整数不同。此外,两个标量组件的普通结构在返回时会在大多数ABI中通过两个寄存器。

此外,大多数C ++编译器都在实践中optimizing compilers,他们可以(并且确实)以不同的方式编译调用(例如,他们经常执行一些函数inlining - 即使没有任何inline注释),所以参数的传递实际上是特定于实现的(并且可以从一个调用站点到另一个相同的被调用函数)。

但是,原始arrays decay into pointers。另请阅读plain old data

你可能会想到“在堆上传递的参数”(但这是一种简化,而不是真实的)如果涉及一些复制构造函数,它通过堆分配的内存传递一些数据。另请阅读the rule of fiveRAII

在某些情况下,根据as-if rule,允许使用优化编译器来替换某些内部堆分配(包括一些“堆栈分配”)。

答案 1 :(得分:2)

与问题无关,但名称​​ STL 已被弃用多年,正确的名称是标准C ++库。

接下来,如何将对象传递给函数,您应该知道 arrays 不是C ++中的第一类元素。自C ++ 11(草案n3337):

  

数组到指针的转换[conv.array]

     

“N T数组”或“未知T的数组”类型的左值或右值可以转换为prvalue   类型“指针T”。结果是指向数组的第一个元素的指针。

因此,当您尝试将数组传递给函数时,它会衰减(如果是多维的递归)到指向其第一个元素的指针。然后传递该指针(按值),这几乎等同于通过引用调用该数组。

但是所有其他对象都是按值传递的,无论是自定义对象还是标准库中的对象。

对于堆栈与堆问题,它与标准无关,因为C ++没有head和stack的概念,只有自动变量和动态变量。简单的常见实现使用堆栈作为自动变量,堆使用动态内存。

让我们继续前进:

void f() {
    double arr_auto[1000];       // automatic array (stack on common implementations)
                                 //  will be automatically destroyed at end of block
    double* arr_heap = new int[1000]; // dynamically allocated array
                                      //  will require an explicit delete[]
    std::vector<int> vec(1000);  // vec is an automatic object (stack), 
                                 //  hosting a dynamic array of 1000 doubles
                    // everything (vector and content) will be destroyed at end of block

    g1(arr_auto);    // actually passes &(arr_auto[0]) a mere pointer to an automatic array
    g1(arr_heap);    // equally passes a pointer but to a dynamically allocated array
    g2(vec);         // assuming g2 is declared g2(vector<int>& v) passes a reference
    g3(vec);         // assuming g3 is declared g3(vector<int> v) constructs a copy of vec
    ...
}

对于最后一种情况(按值传递对象),通过复制构造函数构造对象的自动副本。标准库中的容器确实为其内容分配动态内存 - 它们的析构函数确保动态内存在其生命周期结束时被释放。因此,您可以获得动态内存(常见实现的堆)中分配的原始值的完整副本,而无需关心显式删除。

希望现在更清楚......

答案 2 :(得分:0)

  

1)有更好的方法吗?

声明数组时,不能通过值将其传递给函数,因为您没有复制构造函数和operator=。要解决此问题,最好使用std::array。 在你的情况下,它可以是:

typedef std::array<std::array<bool,10>,10> bool10x10Array;

你获得默认&amp;复制构造函数和operator=内存将在堆栈上分配用于对象声明。

  

2)有没有人知道任何专门针对的好参考   是否一个参数将在期间通过堆或堆栈传递   函数调用?

参数永远不会通过堆传递。传递的参数方式取决于calling convention,当您传递指针或引用时,只需传递内存块的地址,就不会复制所有内存。如果要处理副本,则必须复制内存块并传递复制地址。如果你使用structclass,你可以使用他们的复制构造函数,并按值传递它们,当它通常复制到堆栈时。

  

3)是否有更好的方法来确定是否宣布了某些内容   堆栈或堆栈,而不是打印变量的地址和   通过检查地址确定?

声明局部变量时,对象将始终在堆栈上分配。当您使用动态分配时,它将始终在堆上分配。但是你必须注意到原始类型标量和数组是正确的。类可以在堆栈上声明,也可以使用动态内存。

答案 3 :(得分:-1)

参数总是在堆栈上传递。唯一的时间参数“在堆上传递”(它们从未真正存在)是在创建对象的新副本时,对象是使用new / malloc在堆上分配内存。

通过值发送数组的两种方法是将数组封装在struct(C方式)中,或者使用std :: array(C ++方式)。

大部分内容已在评论中得到解答(感谢@Richard Critten,@ Boo Persson和@Lundin),但我想一个完整的答案会更好。