静态整型数组arr[10]的内存地址总是以060结尾。

18

我有一个类似于下面这样的C程序

main.c

#include <stdio.h>
#define SOME_VAR 10

static int heap[SOME_VAR];


int main(void) {
    printf("%p", heap);
    return 0;
}

当我运行编译后的程序几次时,它会输出这个

0x58aa7c49060
0x56555644060
0x2f8d1f8e060
0x92f58280060
0x59551c53060
0xd474ed6e060
0x767c4561060
0xf515aeda060
0xbe62367e060

为什么它总是以060结尾?而且数组存储在堆中吗?

编辑:我使用的是Linux,并且开启了ASLR。我使用gcc编译了程序。


2
什么操作系统?使用什么编译器? - Andrew Henle
2
该变量不在堆中,而是在程序地址空间的数据或bss部分中,参见https://en.wikipedia.org/wiki/Static_variable。我猜测程序总是会被放置在某个特定边界的内存地址上,例如可以被0x1000整除,编译器将变量放置在程序地址空间的固定偏移量处。 - Bodo
3个回答

17
地址之所以不同,是因为ASLR(地址空间布局随机化)。通过使用它,二进制文件可以映射到虚拟地址空间的不同位置。
变量“heap”与其名称相反,不位于堆上,而是位于“bss”上。因此,地址空间中的偏移量是恒定的。
页面以页面粒度进行映射,在许多平台上为4096字节(十六进制:0x1000)。这就是为什么地址的最后三个十六进制数字相同的原因。
当您在变量上执行相同操作时,在某些平台上(特别是具有最近内核的Linux),地址甚至可以在最后几个数字上变化,因为栈不仅映射到其他位置,还会在启动时接收一个随机偏移量。

ASLR(地址空间布局随机化)会随机化加载基址,我记得节的地址都是基于该地址的。 - Afshin
我正在使用Axel-Thobias Schreiner关于面向对象ANSI-C编程的书。这本书大约是在1993年写的。你知道那时内存布局是否不同吗?如果没有,他为什么会将变量命名为“heap”,而它并不在堆中呢? - linuxlmao
4096是否以某种方式转换为060,或者0x1000以其他方式转换为060,否则我不明白您所说的那个原因是什么?我认为这可能与数组大小有关,该大小从十进制转换为十六进制中的060。 - linuxlmao
2
@linuxlmao 偏移量例如为14060,因此当您添加页面大小(0x1000)的倍数时,最后三位数字保持为“060”。 - Ctx

4
如果您正在使用Windows,则原因是PE结构。
您的heap变量存储在文件的.data部分中,并且其地址是基于该部分的开头计算的。每个部分都独立加载在一个地址上,但其起始地址是页面大小的倍数。由于您没有其他变量,它的地址可能是.data部分的开头,因此其地址将是块大小的倍数。
例如,这是您的代码编译后Windows版本的表格: sections .text 部分是您编译代码的部分,.data 包含您的 heap 变量。当PE文件被加载到内存中时,各个部分被加载到不同的地址,并由 VirtualAlloc() 返回,将是页面大小的倍数。但每个变量的地址相对于现在是页面大小的初始部分。因此,您始终会看到较低数字上的固定数字。由于从部分开始到 heap 的相对地址基于编译器、编译选项等而异,因此使用相同代码但不同编译器将看到不同的数字,但每次打印的内容都是固定的。
当我编译代码时,我注意到 heap 位于 .data 部分开始后的 0x8B0 字节处。因此,每次运行此代码时,我的地址都以 0x8B0 结束。

我正在使用Axel-Thobias Schreiner所著的面向对象ANSI-C编程书籍。该书大约是在1993年左右编写的。你知道当时的内存布局是否有所不同吗?如果没有,为什么他会将变量命名为“heap”,而它并不在堆中呢? - linuxlmao
2
@linuxlmao 可能会有所不同。 1993年,Windows是一个16位操作系统,具有内存分段和各种令人困惑的东西。 它现在不是32位的平面内存架构。 但这些类型的事情是为什么询问/回答关于程序二进制文件在内存中的布局的一般性问题是没有用的原因。 理解C语言标准在一般情况下向您保证的内容,这就是您需要知道的全部内容。 如果您正在调试特定问题,请只担心实际布局,然后使用调试器。 - Cody Gray
不,即使在旧系统上,变量也不应该在堆上创建,因为它没有使用malloc分配,而是具有静态存储期。 - phuclv
@Afshin 我正在回应上面的评论 - phuclv
@phuclv 抱歉,因为你没有提到他,我以为你在和我说话。 :) - Afshin

4
编译器恰好将heap放在一个数据段的偏移量0x60字节处,可能是因为编译器在前0x60字节中放置了其他一些东西,例如用于启动main例程的数据。这就是为什么你看到“060”的原因,它只是刚好在那里,没有什么特别的意义。
地址空间布局随机化改变了程序内存各部分使用的基地址,但它总是以0x1000字节为单位进行更改(这样可以避免与对齐和其他问题造成的问题)。所以你会看到地址按0x1000的倍数波动,但最后三位数字不会变。
定义static int heap[SOME_VAR];定义了具有静态存储持续时间的heap。典型的C实现将它存储在通用数据段而不是堆中。所谓“堆”是指用于动态分配的内存,这是一个错误的称呼(因为malloc实现可以使用各种数据结构和算法,不限于堆。它们甚至可以在一个实现中使用多种方法)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接