数组或对象的指针/引用句柄是否会影响其大小?

时间:2013-03-05 16:27:53

标签: c# c++ arrays memory-management size

我知道如果我有一个数组int A[512],那么引用A可以指向第一个元素。在指针运算中,内存引用为A + index

但是如果我没有弄错的话,指针/引用也会占用一个机器空间。假设一个int占用一个机器字,这是否意味着上述数组的512个整数占用了513个字的空间?

对于C ++或C#中的对象及其数据成员,是否为true / false?

更新:哇你们快点。为了澄清,我对C ++和C#如何处理感兴趣,以及如何调整对象的大小以适应缓存行(如果可能的话)。

更新:我已经意识到指针和数组之间的区别。我理解数组不是指针,上面引用的指针算法只有在数组转换为指针后才有效。但我不认为这种区别与整体问题有关。我对C ++和C#中的数组和其他对象如何存储在内存中感兴趣。

7 个答案:

答案 0 :(得分:1)

你似乎对C ++中的数组和指针有误解。

数组

int A[512];

此声明为您提供512 int个数组。没有其他的。没有指针,没有任何东西。只是一个int的数组。数组的大小为512 * sizeof(int)

名称

名称A指的是该数组。它不是指针类型。它是数组类型。它是一个名称,它指的是数组。名称只是编译时构造,用于告诉编译器您正在谈论的对象。运行时名称不存在。

转换

在某些情况下可能会发生一种称为数组到指针转换的转换。转换采用数组类型的表达式(例如简单表达式A)并将其转换为指向其第一个元素的指针。也就是说,在某些情况下,表达式A(表示数组)可能会转换为int*(指向数组中的第一个元素)。

指针

由数组到指针转换创建的指针在其所属表达式的持续时间内存在。它只是在这些特定情况下出现的临时对象。

情况

数组到指针的转换是标准转换,可能发生的情况包括:

  • 从数组转换为指针时。例如,(int*)A

  • 初始化指针类型的对象时,例如int* = A;

  • 每当引用数组的glvalue显示为需要prvalue的表达式的操作数时。

    当您下标数组时会发生这种情况,例如使用A[20]。下标运算符需要prvalue指针类型,因此A进行数组到指针的转换。

答案 1 :(得分:1)

请注意,当您谈论将数据拟合到缓存行时,包含引用的变量及其引用的实际数据不会位于附近。引用将最终在寄存器中结束,但它最初可能存储为内存中其他对象的一部分,或者作为堆栈中的局部变量存储。无论与“对象”相关联的其他任何开销如何,数组内容本身在操作时仍然可以适合高速缓存行。如果您对C#中的工作原理感到好奇,Visual Studio会有一个反汇编程序视图,显示为您的代码生成的实际x86或x64程序集。

数组引用在IL(中间语言)级别具有特殊的引入支持,因此您将发现加载/使用内存的方式与在C ++中使用数组的方式基本相同。在引擎盖下,索引到一个数组是完全相同的操作。您将开始注意到的区别在于,当您使用“foreach”索引数组时,或者当数组是对象类型数组时,必须开始“unbox”引用。

请注意,当您在方法中本地实例化对象时,C ++和C#之间的内存位置之间的差异可能会出现。 C ++允许您在堆栈上实例化数组,这会创建一个特殊情况,其中数组内存实际存储在“引用”和其他局部变量附近。在C#中,(托管)数组的内容总是最终在堆上分配。

另一方面,当引用堆分配的对象时,C#有时可以比C ++具有更好的内存位置,特别是对于短期对象。这是由于GC通过它们的'generation'存储对象的方式(它们活了多长时间)以及它所做的堆压缩。在不断增长的堆上快速分配短期对象;收集时,堆也会被压缩,从而防止可能导致非压缩堆中的后续分配分散在内存中的“碎片”。

您可以使用“对象池”技术(或通过避免频繁的小型短期对象)在C ++中获得类似的内存局部性优势,但这需要一些额外的工作和设计。当然,这样做的代价是GC必须运行,线程劫持,促进世代,压缩和重新分配引用,在某些不可预测的时间导致可测量的开销。在实践中,开销很少成为问题,尤其是Gen0集合,它针对频繁分配的短期对象的使用模式进行了高度优化。

答案 2 :(得分:0)

不,CLR中的对象映射到您引用的C++(我想象)的“简单”内存映射。请记住,您可以使用反射操作CLR中的对象,这意味着每个对象都必须在其中包含其他信息( manifest )。这已经添加了更多的内存,只是对象的简单内容,在多线程环境中为locking管理添加了指针,并且你在期望中走得很远 CLR对象的内存分配。

还要记住指针大小在3264位机器之间延迟。

答案 3 :(得分:0)

数组int A[512]占用512 * sizeof(int)(+编译器决定添加的任何填充 - 在这个特殊情况下,很可能没有填充)。

数组A可以转换为指向int A的指针并与A + index一起使用的事实使用了这样一个事实:在实现中A[index]几乎总是准确的与A + index相同的说明。在两种情况下都会转换为指针,因为要到达A[index],我们必须获取数组A的第一个地址,并添加indexsizeof(int) - 无论您是将其写为A[index]A + index没有任何区别。在这两种情况下,A指的是数组中的第一个地址,index指的是元素的数量。

这里没有额外的空间。

以上适用于C和C ++。

在C#和其他使用“托管内存”的语言中,跟踪每个变量需要额外的开销。这不会影响变量A本身的大小,但它当然必须存储在某个地方,因此每个变量,无论是单个整数还是非常大的数组,都会有一些开销,存储在某个地方,包括变量的大小和某种“引用计数”(变量的使用位置,以及是否可以删除)。

答案 4 :(得分:0)

我认为你在C ++中混淆数组和指针。

int数组就是这样,它是内存中的一系列位置,每个位置占用sizeof(int),您可以在其中存储N-1 int s。

指针是一种可以指向内存位置的类型,占用内存中的CPU寄存器大小,因此在32位机器上,sizeof(int*)将是32位。

如果要指向数组,请执行以下操作:int * ptr = &A[0];这指向数组中的第一个元素。现在你有指针占用内存(CPU字大小),你有你的int数组。

当您将数组传递给C或C ++中的函数时,它衰减指向数组中第一个元素的指针。这并不是说指针是一个数组,它说从数组到指针有衰减

在C#中,您的数组是引用类型,并且您没有指针,因此您不必担心它。它只占用了数组的大小。

答案 5 :(得分:0)

关于原生C ++

  

但是如果我没有弄错的话,指针/引用也会占用一个机器空间字

引用不一定占用内存空间。根据C ++ 11标准的第8.3.2 / 4段:

  

未指明引用是否需要存储(3.7)。

在这种情况下,您可以像指针一样使用 A,并且在必要时它确实衰减指针(例如,当它作为一个指针传递时)函数的参数),但A的类型是int[512],而不是int*:因此,A 不是指针。例如,你不能这样做:

int A[512];
int B;
A = &B;

不需要用于存储A的任何内存位置(即用于存储数组开始的内存地址),因此很可能您的编译器不会分配任何额外的内存字节用于保存地址A

答案 6 :(得分:0)

我们在这里有多个不同的例子,因为我们甚至有几种语言需要讨论。

让我们从简单的例子开始,一个C ++中的简单数组:

int array[512];

这里的内存分配会发生什么?在阵列的堆栈上分配512字的存储器。没有分配堆内存。没有任何开销;没有指向数组的指针,没有任何东西,只有512字的内存。

以下是在C ++中创建数组的另一种方法:

int * array = new int[512];

这里我们在堆上创建一个数组。它将分配512个字的内存,而不会在堆上分配额外的内存。然后,一旦完成,该数组的起始地址将被放置在堆栈中的变量中,占用额外的内存字。如果你看看整个应用程序的总内存占用量,是的它将是513,但值得注意的是,一个在堆栈上,其余的在堆上(堆栈内存分配要便宜得多,并且不会导致碎片,但如果你过度使用或误用它,你可以更容易地用完。

现在进入C#。在C#中,我们没有两种不同的语法,你只需要:

int[] array = new int[512];

这将在堆上创建一个新的数组对象。它将包含512个字的内存用于数组中的数据,以及一些额外的内存用于数组对象的开销。它需要4个字节来保存数组的计数,同步对象以及我们实际上不需要考虑的其他一些开销。开销很小,并且不依赖于数组的大小。

还有一个指针(或“引用”,因为它更适合在C#中使用)到放在堆栈上的数组,这将占用一个内存字。与C ++一样,堆栈内存可以非常快速地分配/解除分配,并且不会破坏内存,因此在考虑程序的内存占用时,分离它通常是有意义的。