我正在学习c语言并练习自己。我想知道c是如何为数组分配内存的。因为我有一个这样的代码:
#include<stdio.h>
int main(void)
{
char a[] = {1,2,3,4,5,6,7,8,9,10};
int i = 578;
char * p1;
int * p2;
printf("nAddress of pointer p1 : %p", &p1);
printf("nAddress of pointer p2 : %p", &p2);
printf("nAddress of int i : %p", &i);
p1 = 0x0028FF34;
p2 = 0x0028FF35;
/* Print the Addresses */
for (i = 0; i < 10; i++)
{
printf("nAddress of a[%d] : %p", i, (void *) &a[i]);
}
printf("nThe content in the address 0x0028FF34: %x", *p1);
printf("nThe content in the address 0x0028FF35: %x", *p2);
return 0;
}
在我的计算机上运行此代码会给出类似的输出
Address of pointer p1 : 0028FF2C
Address of pointer p2 : 0028FF28
Address of int i : 0028FF30
Address of a[0] : 0028FF36
Address of a[1] : 0028FF37
Address of a[2] : 0028FF38
Address of a[3] : 0028FF39
Address of a[4] : 0028FF3A
Address of a[5] : 0028FF3B
Address of a[6] : 0028FF3C
Address of a[7] : 0028FF3D
Address of a[8] : 0028FF3E
Address of a[9] : 0028FF3F
The character content in the addresss 0x0028FF34: ffffff80
The character content in the addresss 0x0028FF35: 3020100
字符指针p占用32位,即4个字节。因此,指针p2与指针p1的距离是4字节长,指针p1与int i的距离也是4字节长。在我的系统中,int i也是4字节。在我看来,int i与数组a[]的距离应该是4个字节。数组a[]的起始地址应为0028FF34,为什么是0028FF36?
0028FF34和0028FF35的含量是什么意思?0028FF34指向一个字符,输出为ffffff80,0028ff35指向一个int,输出为3020100。
您想知道C是如何为数组分配内存的。。。以及其他数据类型。好的,这取决于数组(或其他变量)是如何定义的。在您的案例中,您已经将它们定义为自动分配的("本地")变量,因此它们存储在C运行时堆栈中。(从技术上讲,对于具有自动存储的变量,可能还有另一种分配机制,但在实践中,大多数系统都使用运行时堆栈。)如果您定义了array&在int main(void)
之上几行的其他变量,它们将是"全局变量",并且将被静态分配,并具有外部链接,因此它们对其他编译单元(其他.c文件)可见。如果你把关键字static
放在它们前面,它们仍然会被静态分配,但它们的链接是内部的,所以它们对其他编译单元(.c文件)不可见。最后一部分是真的,无论你是在函数体内部还是外部定义变量;如果在函数体之外将它们定义为static
,那么同一编译单元中的其他函数都可以使用它们;如果在函数体中将它们定义为static
,编译器只允许这一个函数访问它们;事实上,它们只有在定义的范围内才可见。
至于它们的确切位置,编译器/链接器可以自由地将它们放置在其选择的存储类区域内的任何位置;在这种情况下,编译器将它们放在运行时堆栈上,但它们的顺序和相对位置不是由语言定义的,因此编译器可以自由地以任何方式排列它们。
通常,变量之间会分配填充,以确保它们对齐,从而在数据大小的最佳边界上开始,因为(由于硬件问题)访问与其数据宽度匹配的字节边界上的数据通常更快,这正是这里发生的情况。你可能会说,"但是i
以4字节的边界结束,而a[]
没有对齐要求,那么为什么它在分配a[]
之前会在i
之后添加填充呢?"但这是因为你认为分配是按照增加内存地址的顺序进行的。您是否注意到,您的变量似乎是在内存中"向后"分配的?事实上他们不是!在大多数系统上,堆栈在内存中从高地址向低地址增长。考虑到这一点,很明显,分配的变量集中的第一个变量是a[]
,然后是i
(位于较低的地址)。因为a[]
后面的字节地址不是4的倍数,所以在编译器分配i
之前,它添加了2个字节的填充!
现在,关于您在0x0028FF34和0x0028FF35处读取的值:无论有什么,都只是上次写入这些地址时留下的垃圾。0x0028FF34处的字节是0x80,它通过...
varargs作为整数传递给"printf()",并且在您的系统上,char
必须经过签名,因此当它升级为整数时,它会进行符号扩展,由于0x80的高位是1,因此得到的值是0xFFFFFF80。您读取的整数0x0028FF35显示该地址的字节为0x00,其他字节为03、02和01,您应该注意到这是a[]
的前3个字节。我们还可以从中看出,您的系统是小端序(如x86),因为整数的高阶字节位于较高字节的可寻址位置。
没有连续分配变量变量的要求。
大多数数据类型需要在4的倍数(在某些情况下是8)的地址处对齐。char
是个例外,它不需要任何特殊的对齐。
正如您所注意到的,int
和指针变量是以4字节对齐的方式连续分配的(尽管有趣的是,它们的分配顺序与声明不同)。
char
数组是在它之后分配的,并且使用10个字节。堆栈中很可能还有其他内部数据使用数组之后的内存,并且需要4字节对齐。因此,某个地方会有2个浪费的字节——可能在数组之前,也可能在数组之后。编译器选择将它放在数组之前。