繁体   English   中英

Memory 分配和基本 C 程序中未使用的字节

[英]Memory allocation and unused byte in basic C program

我对基本C程序上的 memory 分配有疑问。

#include <stdio.h>

int main()
{
    int  iarray[3];
    char carray[3];
    
    printf("%p\n", &iarray); // b8
    printf("%p\n", &iarray+1); // c4
    printf("%p\n", &carray); // c5
    
    return 0;
}

鉴于上面的代码,您可以看到&iarray+1&carray有一个字节的差异,我不确定它的用途或内容,为什么编译器会在两个 arrays 之间分配一个未使用的字节?

我想也许它用来知道数组大小,但我知道sizeof是一个编译时 function 知道大小而不分配真正的 memory,所以没有用存储数组大小

注: output 可以在每个printf的评论中看到。

b8
c4
c5

游乐场: https://onlinegdb.com/cTdzccpDvI

谢谢。

编译器可以以任何他们认为合适的方式自由排列 memory 中的变量。 通常,它们将放置在 memory 偏移处,其值是变量大小的倍数,例如,4 字节intint数组将从 4 的倍数的地址开始。

在这种情况下,您有一个int数组,起始地址是 4 的倍数,后跟一个未使用的字节,然后是一个大小为 3 的char数组。理论上,一个intlong可以紧跟 memory 中的char数组如果它被定义为下一个可用地址是 8 的倍数。

从您的 output 看来,这些局部变量的堆栈排列方式如下:

b8-bb: 1st integer of iarray
bc-bf: 2nd integer of iarray
c0-c3: 3rd integer of iarray
c4:    padding probably, only compiler knows
c5-c7: carray

现在,当您执行&iarray+1时,您将获取数组int[3]的地址,并将该数组类型的 +1 添加到其中。 换句话说,您将获得下一个int[3]数组的地址,该数组确实位于 c4 (但不是因为只有一个int[3] )。

这段代码实际上是有效的。 您不能取消引用此指针,但因为它正好指向iarray之后的 +1,所以拥有指针并打印其值是合法的(换句话说,不是未定义的行为,就像&iarray+2那样)。

如果你也打印这个:

printf("%p\n", iarray+1);

您应该得到结果bc ,因为现在您采用int类型的指针( iarray被视为指向int的指针),将其加 1 ,得到下一个int

此行为纯粹是(编译器)实现定义的。 可能发生的事情是这样的:

当调用具有局部变量的 function (在这种情况下为main()时,这些变量的 memory 将在堆栈上分配。 在这种情况下,需要 15 个字节,但很有可能堆栈分配需要 4 个字节的 alignment,因此分配了 16 个字节。

int -array 也可能必须是 4 字节对齐的。 因此int数组的地址是 4 的倍数。 char数组没有任何 alignment 要求,因此它可以放置在剩余 4 个字节中的任何位置。

所以简而言之,额外的字节是未使用的,而是由于 alignment 分配的。

发生这种情况的原因是您使用的特定编译器在这种特殊情况下将 memory 从高地址分配到低地址。

编译器分析main例程,发现carray需要 3 个int ,而iarray需要 3 个char 无论出于何种原因,它决定先处理carray

编译器从计划的堆栈帧开始,该堆栈帧需要在某些点具有 16 字节 alignment。 堆栈上需要额外的数据,结果编译器开始放置局部变量的点位于 8 模 16 的地址(因此其十六进制表示以 8 结尾)。 也就是说,从 7FFC565E90A8 及以上的某个地址开始,memory 用于管理堆栈帧。 本地对象的第一个字节将位于 7FFC565E90A7、7FFC565E90A6、7FFC565E90A5、7FFC565E90A4 等处。

编译器将该空间的前三个字节用于carray 回想一下,我们是从高地址到低地址工作的。 (由于历史原因,这是堆栈增长的方向;分配一些高地址作为起点,新数据放入低地址。)因此将carray放入地址7FFC565E90A5。 它填充 7FFC565E90A5、7FFC565E90A6 和 7FFC565E90A7 处的字节。

然后编译器需要为iarray分配 12 个字节。 下一个可用的 12 个字节是从 7FFC565E9099 到 7FFC565E90A4。 但是 iarray 中的int元素需要 4 字节的iarray ,所以不能从 7FFC565E9099 开始。 因此,编译器进行调整以使它们从 7FFC565E9098 开始。 然后iarray填充从 7FFC565E9098 到 7FFC565E90A3 的字节,而 7FFC565E90A4 未使用。

请注意,在其他情况下,编译器可能会以不同的方式排列本地对象。 当您有多个具有不同对齐方式的对象时,编译器可能会选择将所有具有相同 alignment 的对象聚集在一起,以减少需要插入填充的位置数量。 编译器还可以选择按名称的字母顺序为对象分配 memory。 或者它可以按照恰好将它们存储在其 hash 表中的顺序来执行此操作。 或者这些东西的某种组合,例如按照 alignment 要求对所有对象进行聚类,然后在每个聚类中按名称排序。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM