堆栈段中的意外内存分配
Unexpected memory allocation in stack segment
我试图看到对于给定的函数,内存堆栈段上的内存分配将以连续的方式发生。所以,我写了下面的代码,得到了下面的输出。
对于 int
分配,我看到内存地址按预期出现但不是字符数组。在内存地址 0xbff1599c
之后,我期望下一个地址是 0xbff159a0
而不是 0xbff159a3
。另外,由于 char
是 1 个字节,而我使用的是 4 个字节,所以在 0xbff159a3
之后我期望 0xbff159a7
而不是 0xbff159a8
如果我删除字符部分,所有内存位置都会按预期出现,但我无法使用字符数组获得预期的内存位置。
我的基本假设是在堆栈段上,内存将始终是连续的。希望没有错。
#include <stdio.h>
int main(void)
{
int x = 10;
printf("Value of x is %d\n", x);
printf("Address of x is %p\n", &x);
printf("Dereferencing address of x gives %d\n", *(&x));
printf("\n");
int y = 20;
printf("Value of y is %d\n", y);
printf("Address of y is %p\n", &y);
printf("Dereferencing address of y gives %d\n", *(&y));
printf("\n");
char str[] = "abcd";
printf("Value of str is %s\n", str);
printf("Address of str is %p\n", &str);
printf("Dereferencing address of str gives %s\n", *(&str));
printf("\n");
int z = 30;
printf("Value of z is %d\n", z);
printf("Address of z is %p\n", &z);
printf("Dereferencing address of z gives %d\n", *(&z));
}
输出:
Value of x is 10
Address of x is 0xbff159ac
Dereferencing address of x gives 10
Value of y is 20
Address of y is 0xbff159a8
Dereferencing address of y gives 20
Value of str is abcd
Address of str is 0xbff159a3
Dereferencing address of str gives abcd
Value of z is 30
Address of z is 0xbff1599c
Dereferencing address of z gives 30
Also, since char is 1 byte and I am using 4 bytes, so after 0xbff159a3 I was expecting 0xbff159a7 and not 0xbff159a8
char
占用 1
字节,但 str
是字符串,您没有计算 '[=13=]'
,它位于字符串的末尾,因此,char str[]="abcd"
占用 5
字节。
我认为这可能是因为地址与边界对齐(例如 8 字节边界)?
分配始终与边界对齐并按块分配
在一些 OS。您可以使用结构进行检查。例如,
结构A
{
字符;
字符 b;
诠释 c;
};
结构的大小在 UNIX/LINUX 平台上不会是 6 个字节。
但它可能从 OS 到 OS 不等。
类似的事情也适用于其他数据类型。
此外,一个字符串只是指向分配在一个地址
heap 如果使用 malloc 并且分配逻辑可能会有所不同
从 OS 到 OS。以下是 Linux 框的输出
对于同一个程序。
x 的值为 10
x的地址是0x7ffffa43a50c
x 的取消引用地址给出 10
y 的值为 20
y的地址是0x7ffffa43a508
y 的取消引用地址给出 20
str 的值为 abcd
str 的地址是 0x7ffffa43a500
取消引用 str 的地址给出 abcd
z 的值为 30
z 的地址是 0x7ffffa43a4fc
取消引用 z 的地址给出 30
@ameyCU 和@Umamahesh 的两个答案都很好,但 none 是自给自足的,所以我正在写我的答案并添加更多信息,以便进一步访问的人可以获得最大的知识。
我得到这个结果是因为 Data structure alignment 的概念。按照这个,计算机将始终尝试以这样一种方式分配内存块(无论是在堆段还是堆栈段或数据段,在我的例子中它是堆栈段)快速读写。
当现代计算机读取或写入内存地址时,它将以字大小的块(例如 32 位系统上的 4 字节块)或更大的块来执行此操作。数据对齐意味着将数据放在等于字大小的某个倍数的内存地址,由于 CPU 处理内存的方式,这会提高系统的性能。
在32位架构上,计算机字长为4字节,因此计算机总是会尝试分配地址为4的倍数的内存,以便能够以4字节为单位进行快速读写。当字节数较少时,计算机会在开始或结束时填充一些空字节。
在我的例子中,假设我使用 char str[] = "abc";
然后包括 EOL 字符 '[=14=]'
我有 4 个字节的要求,所以不会有填充。但是当我做 char str[] = "abcd";
然后包括 EOL 字符 '[=14=]'
我有 5 个字节的要求,现在计算机想要在 4 个块中分配所以它会添加 3 个字节的填充( 或者在开始或结束),因此完整的字符数组将在内存中跨越 8 个字节。
因为 int
,long
内存需求已经是 4 的倍数,所以没有问题,char
或 short
不是 4 的倍数。这解释了我报告的事情 - “如果我删除 char 部分,所有内存位置都会按预期出现,但我无法使用字符数组获得预期的内存位置。”
经验法则是,如果你的内存需求不是4的倍数(例如,1 short
,char
array of size 2 ) 然后添加额外的填充,然后进行内存分配,以便计算机可以快速读写。
下面是 this answer 的精彩摘录,它解释了数据结构对齐。
假设你有结构。
struct S {
short a;
int b;
char c, d;
};
如果没有对齐,它会像这样在内存中布局(假设是 32 位架构):
0 1 2 3 4 5 6 7
|a|a|b|b|b|b|c|d| bytes
| | | words
问题在于,在某些 CPU 体系结构上,从内存加载 4 字节整数的指令仅适用于字边界。因此,您的程序必须使用单独的指令获取 b 的每一半。
但是如果内存布局为:
0 1 2 3 4 5 6 7 8 9 A B
|a|a| | |b|b|b|b|c|d| | |
| | | |
然后访问 b 就变得简单了。 (缺点是需要更多内存,因为有填充字节。)
我试图看到对于给定的函数,内存堆栈段上的内存分配将以连续的方式发生。所以,我写了下面的代码,得到了下面的输出。
对于 int
分配,我看到内存地址按预期出现但不是字符数组。在内存地址 0xbff1599c
之后,我期望下一个地址是 0xbff159a0
而不是 0xbff159a3
。另外,由于 char
是 1 个字节,而我使用的是 4 个字节,所以在 0xbff159a3
之后我期望 0xbff159a7
而不是 0xbff159a8
如果我删除字符部分,所有内存位置都会按预期出现,但我无法使用字符数组获得预期的内存位置。
我的基本假设是在堆栈段上,内存将始终是连续的。希望没有错。
#include <stdio.h>
int main(void)
{
int x = 10;
printf("Value of x is %d\n", x);
printf("Address of x is %p\n", &x);
printf("Dereferencing address of x gives %d\n", *(&x));
printf("\n");
int y = 20;
printf("Value of y is %d\n", y);
printf("Address of y is %p\n", &y);
printf("Dereferencing address of y gives %d\n", *(&y));
printf("\n");
char str[] = "abcd";
printf("Value of str is %s\n", str);
printf("Address of str is %p\n", &str);
printf("Dereferencing address of str gives %s\n", *(&str));
printf("\n");
int z = 30;
printf("Value of z is %d\n", z);
printf("Address of z is %p\n", &z);
printf("Dereferencing address of z gives %d\n", *(&z));
}
输出:
Value of x is 10
Address of x is 0xbff159ac
Dereferencing address of x gives 10
Value of y is 20
Address of y is 0xbff159a8
Dereferencing address of y gives 20
Value of str is abcd
Address of str is 0xbff159a3
Dereferencing address of str gives abcd
Value of z is 30
Address of z is 0xbff1599c
Dereferencing address of z gives 30
Also, since char is 1 byte and I am using 4 bytes, so after 0xbff159a3 I was expecting 0xbff159a7 and not 0xbff159a8
char
占用 1
字节,但 str
是字符串,您没有计算 '[=13=]'
,它位于字符串的末尾,因此,char str[]="abcd"
占用 5
字节。
我认为这可能是因为地址与边界对齐(例如 8 字节边界)?
分配始终与边界对齐并按块分配 在一些 OS。您可以使用结构进行检查。例如, 结构A { 字符; 字符 b; 诠释 c; };
结构的大小在 UNIX/LINUX 平台上不会是 6 个字节。
但它可能从 OS 到 OS 不等。
类似的事情也适用于其他数据类型。 此外,一个字符串只是指向分配在一个地址 heap 如果使用 malloc 并且分配逻辑可能会有所不同 从 OS 到 OS。以下是 Linux 框的输出 对于同一个程序。
x 的值为 10 x的地址是0x7ffffa43a50c x 的取消引用地址给出 10
y 的值为 20 y的地址是0x7ffffa43a508 y 的取消引用地址给出 20
str 的值为 abcd str 的地址是 0x7ffffa43a500 取消引用 str 的地址给出 abcd
z 的值为 30 z 的地址是 0x7ffffa43a4fc 取消引用 z 的地址给出 30
@ameyCU 和@Umamahesh 的两个答案都很好,但 none 是自给自足的,所以我正在写我的答案并添加更多信息,以便进一步访问的人可以获得最大的知识。
我得到这个结果是因为 Data structure alignment 的概念。按照这个,计算机将始终尝试以这样一种方式分配内存块(无论是在堆段还是堆栈段或数据段,在我的例子中它是堆栈段)快速读写。
当现代计算机读取或写入内存地址时,它将以字大小的块(例如 32 位系统上的 4 字节块)或更大的块来执行此操作。数据对齐意味着将数据放在等于字大小的某个倍数的内存地址,由于 CPU 处理内存的方式,这会提高系统的性能。
在32位架构上,计算机字长为4字节,因此计算机总是会尝试分配地址为4的倍数的内存,以便能够以4字节为单位进行快速读写。当字节数较少时,计算机会在开始或结束时填充一些空字节。
在我的例子中,假设我使用 char str[] = "abc";
然后包括 EOL 字符 '[=14=]'
我有 4 个字节的要求,所以不会有填充。但是当我做 char str[] = "abcd";
然后包括 EOL 字符 '[=14=]'
我有 5 个字节的要求,现在计算机想要在 4 个块中分配所以它会添加 3 个字节的填充( 或者在开始或结束),因此完整的字符数组将在内存中跨越 8 个字节。
因为 int
,long
内存需求已经是 4 的倍数,所以没有问题,char
或 short
不是 4 的倍数。这解释了我报告的事情 - “如果我删除 char 部分,所有内存位置都会按预期出现,但我无法使用字符数组获得预期的内存位置。”
经验法则是,如果你的内存需求不是4的倍数(例如,1 short
,char
array of size 2 ) 然后添加额外的填充,然后进行内存分配,以便计算机可以快速读写。
下面是 this answer 的精彩摘录,它解释了数据结构对齐。
假设你有结构。
struct S {
short a;
int b;
char c, d;
};
如果没有对齐,它会像这样在内存中布局(假设是 32 位架构):
0 1 2 3 4 5 6 7
|a|a|b|b|b|b|c|d| bytes
| | | words
问题在于,在某些 CPU 体系结构上,从内存加载 4 字节整数的指令仅适用于字边界。因此,您的程序必须使用单独的指令获取 b 的每一半。
但是如果内存布局为:
0 1 2 3 4 5 6 7 8 9 A B
|a|a| | |b|b|b|b|c|d| | |
| | | |
然后访问 b 就变得简单了。 (缺点是需要更多内存,因为有填充字节。)