【问题标题】:Stack vs. heap pointers in CC 中的堆栈与堆指针
【发布时间】:2017-01-25 21:00:15
【问题描述】:

我目前正在学习 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 的 sn-p(在堆上分配),但值不同。

#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 不需要为自动变量使用堆栈。那是特定于实现的。而且数组不是指针。
  • @Olaf 它是特定于实现的,因为所有实现都专门在堆栈上分配;-)。
  • @PeterA.Schneider:有一些像 PIC 这样的嵌入式 CPU 没有硬件堆栈。并且有一些实现,例如HC08 允许出于性能原因静态分配自动变量。优化的 ABI 可能会在寄存器中传递数组并复制回来。一个实现可以做很多事情,并且认为太多是理所当然的编写代码的好方法,它会破坏好的编译器。世界比我们想象的要大...关于获取数组的地址:试试register int a[10];
  • @joop 当然:我知道,但从未说过。该标准实际上不包含“堆栈”一词。但当然,人类已知的所有实现都提供一个。请注意,您正在进入关于堆栈的本体论讨论:什么是堆栈?我并不是说堆栈具有寄存器 ESP,或 any 类型的硬件支持,或某种内存布局。也许我们需要一个堆栈图灵测试:如果它的行为像一个堆栈,它就是一个堆栈,但是它是如何实现的?您所描述的只是一个复杂的堆栈实现,因此不太可能使用更简单的堆栈实现。

标签: c pointers malloc heap-memory stack-memory


【解决方案1】:

原因是,与您可能被教导的不同,数组不是指针。在某些情况下,C 中的数组衰减为指针1。当您将数组传递给函数时,它会衰减为指向第一个元素的指针。该元素的地址与整个数组的地址相同(地址始终指向对象的第一个字节)。

你从malloc 得到的不是一个数组,而是一块内存的地址。您将地址分配给指针。但是指针和块是独立的实体。所以打印指针的值,而不是它的地址,会产生不同的结果。


(1) Decay 是一种隐式类型转换的花哨术语。当数组表达式用在大多数的地方时(例如作为参数传递给需要指针的函数),它会自动变成指向其第一个元素的指针。 “衰减”是因为您丢失了类型信息,即数组大小。

【讨论】:

  • 您能否详细说明“衰减为指针”的含义?我以前从未听说过这种行为。
  • 其实转换与作为参数传递无关,而是使用数组的表达式:port70.net/~nsz/c/c11/n1570.html#6.3.2.1p3
  • @Olaf - 谢谢。修改为更符合标准。
【解决方案2】:

您的两个打印调用打印相同的值,因为一个尝试打印数组,该数组衰减为指向数组的指针,另一个打印数组的地址。指向数组的指针包含数组的地址,因此它们的值相同。

在第二种情况下,一个打印x的值,另一个打印x的地址。由于x 是指向您分配的内存块的指针,因此它们必须是不同的值。

所以在第一种情况下,您所拥有的只是一个数组 (x)。在第二种情况下,您有一个分配的内存块(未命名)和一个指向该分配块的指针(x)。

【讨论】:

  • 更正:在大多数情况下,arra 的名称衰减为指针指向第一个元素(而不是数组)。
  • 我认为说名称衰减是一种误导。指向数组的指针和指向数组第一个元素的指针之间没有 value 区别。数组将所有对象保存在连续的内存中。但是有一个含义的区别,这里我要的是指向整个数组的含义。
  • 好吧,数组本身不会衰减。实际上,按照标准,转换的是“类型数组”的表达式。并且该标准不允许对不同类型使用不同的表示形式(它们不需要比较相等)。类型与值一起始终是表达式的一部分。您再次混淆了标准和实现细节。虽然它们与问题相关,但指出这些差异是个好主意。过度简化和混搭在一起并不能真正帮助初学者。
【解决方案3】:

确实可以获取整个数组的地址,这可能令人惊讶,部分原因是不需要经常这样做。数组在某种意义上是一个单一的对象,它有一个地址,也就是它的第一个字节的地址。与所有对象一样,地址是通过地址运算符&amp; 获得的。

数组的第一个元素(就像它的所有元素一样)也有一个地址,它是它的第一个字节的地址。指向其第一个元素的指针是数组类型在作为参数传递给函数时“调整”的对象。

这两个字节是相同的,并且具有相同的地址。但是它们有不同的类型,如果你给它们加上 1 并再次打印它们,就会变得很明显。

相比之下,指针y 是它自己的不同对象(大小可能为 4 或 8 个字节;足以在其中存储地址)。像任何对象一样,它有一个可以使用&amp; 运算符获得的地址。也许令人困惑的是,它还包含一个地址,在本例中是数组第一个字节的地址。两者当然不相等:指针对象与数组位于不同的位置(即在堆栈上紧挨着它,即使 Olaf 不喜欢那样)。

小提示:您使用%p 打印指针,这很好。如果你这样做,你应该严格地说将你打印的指针转换为 void 指针:printf("%p\n", (void *)x);

【讨论】:

    猜你喜欢
    • 2015-11-16
    • 2015-08-05
    • 2013-01-20
    • 1970-01-01
    • 2014-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多