C 中的堆栈指针与堆指针
Stack vs. heap pointers in C
我目前正在学习 C,我对内存布局和指针感到困惑。
在下面的代码中,我的理解是数组分配在栈上。
#include <stdio.h>
int main () {
int x[4];
x[0] = 3; x[1] = 2; x[2] = 1;
printf("%p\n",x);
printf("%p\n", &x);
}
我的问题是,为什么两次打印调用输出相同的值?
我使用 malloc(在堆上分配)尝试了类似的片段,但值不同。
#include <stdio.h>
#include <stdlib.h>
int main () {
int *x = malloc(sizeof(int) * 4);
x[0] = 3; x[1] = 2; x[2] = 1;
printf("%p\n",x);
printf("%p\n", &x);
}
原因是,与您可能被教导的不同,数组不是指针。 C 中的数组在某些情况下会退化为指针1。当您将数组传递给函数时,它会衰减为指向第一个元素的指针。该元素的地址与整个数组的地址相同(地址始终指向对象的第一个字节)。
你从malloc
得到的不是一个数组,而是一块内存的地址。您将地址分配给指针。但是指针和块是独立的实体。所以打印指针的值,而不是它的地址,会产生不同的结果。
(1) Decay 是一种隐式类型转换的奇特术语。当数组表达式用在 大多数 处时(例如作为参数传递给需要指针的函数),它会自动变成指向其第一个元素的指针。 "decay" 是因为您丢失了类型信息,即数组大小。
您的两个打印调用打印相同的值,因为一个尝试打印数组,该数组衰减为指向数组的指针,而另一个打印数组的地址。指向数组的指针包含数组的地址,因此它们是相同的值。
第二种情况,一个打印x
的值,另一个打印x
的地址。由于 x
是指向您分配的内存块的指针,因此它们必须是不同的值。
所以在第一种情况下,您所拥有的只是一个数组 (x
)。在第二种情况下,您有一个分配的内存块(未命名)和一个指向该分配块的指针(x
)。
令人惊讶的是,人们确实可以获取整个数组的地址,部分原因是人们不需要经常这样做。数组在某种意义上是一个单一的对象,它有一个地址,即它的第一个字节的地址。与所有对象一样,地址是通过地址运算符 &
.
获得的
数组的第一个 元素(就像它的所有元素一样)也有一个地址,这是它的第一个字节的地址。当它作为参数传递给函数时,指向其第一个元素的指针是数组类型"adjusted"。
这两个字节是相同的,并且具有相同的地址。但是它们有不同的类型,如果你给它们加1并再次打印它们就很明显了。
相比之下,指针 y
是它自己的独特对象(大小可能为 4 或 8 个字节;足以在其中存储地址)。像任何对象一样,它有一个可以使用 &
运算符获得的地址。可能令人困惑的是,它还 包含 一个地址,在本例中是数组第一个字节的地址。这两者当然不相等:指针对象驻留在与数组不同的位置(即在堆栈上紧挨着它,即使 Olaf 不喜欢那样)。
小提示:您使用 %p
打印指针,这很好。如果你这样做,你应该严格地说将你打印的指针转换为空指针:printf("%p\n", (void *)x);
。
我目前正在学习 C,我对内存布局和指针感到困惑。
在下面的代码中,我的理解是数组分配在栈上。
#include <stdio.h>
int main () {
int x[4];
x[0] = 3; x[1] = 2; x[2] = 1;
printf("%p\n",x);
printf("%p\n", &x);
}
我的问题是,为什么两次打印调用输出相同的值?
我使用 malloc(在堆上分配)尝试了类似的片段,但值不同。
#include <stdio.h>
#include <stdlib.h>
int main () {
int *x = malloc(sizeof(int) * 4);
x[0] = 3; x[1] = 2; x[2] = 1;
printf("%p\n",x);
printf("%p\n", &x);
}
原因是,与您可能被教导的不同,数组不是指针。 C 中的数组在某些情况下会退化为指针1。当您将数组传递给函数时,它会衰减为指向第一个元素的指针。该元素的地址与整个数组的地址相同(地址始终指向对象的第一个字节)。
你从malloc
得到的不是一个数组,而是一块内存的地址。您将地址分配给指针。但是指针和块是独立的实体。所以打印指针的值,而不是它的地址,会产生不同的结果。
(1) Decay 是一种隐式类型转换的奇特术语。当数组表达式用在 大多数 处时(例如作为参数传递给需要指针的函数),它会自动变成指向其第一个元素的指针。 "decay" 是因为您丢失了类型信息,即数组大小。
您的两个打印调用打印相同的值,因为一个尝试打印数组,该数组衰减为指向数组的指针,而另一个打印数组的地址。指向数组的指针包含数组的地址,因此它们是相同的值。
第二种情况,一个打印x
的值,另一个打印x
的地址。由于 x
是指向您分配的内存块的指针,因此它们必须是不同的值。
所以在第一种情况下,您所拥有的只是一个数组 (x
)。在第二种情况下,您有一个分配的内存块(未命名)和一个指向该分配块的指针(x
)。
令人惊讶的是,人们确实可以获取整个数组的地址,部分原因是人们不需要经常这样做。数组在某种意义上是一个单一的对象,它有一个地址,即它的第一个字节的地址。与所有对象一样,地址是通过地址运算符 &
.
数组的第一个 元素(就像它的所有元素一样)也有一个地址,这是它的第一个字节的地址。当它作为参数传递给函数时,指向其第一个元素的指针是数组类型"adjusted"。
这两个字节是相同的,并且具有相同的地址。但是它们有不同的类型,如果你给它们加1并再次打印它们就很明显了。
相比之下,指针 y
是它自己的独特对象(大小可能为 4 或 8 个字节;足以在其中存储地址)。像任何对象一样,它有一个可以使用 &
运算符获得的地址。可能令人困惑的是,它还 包含 一个地址,在本例中是数组第一个字节的地址。这两者当然不相等:指针对象驻留在与数组不同的位置(即在堆栈上紧挨着它,即使 Olaf 不喜欢那样)。
小提示:您使用 %p
打印指针,这很好。如果你这样做,你应该严格地说将你打印的指针转换为空指针:printf("%p\n", (void *)x);
。