为什么数组变量的地址与它自身相同?

时间:2011-05-12 18:03:14

标签: c arrays pointers

在C中,如果我们有一个类似a[10]的数组,那么a&a具有相同的指针值(但不是相同的类型)。我想知道为什么C设计得像这样?

这是为了节省存储&a所需的额外空间吗? ......当你想到a永远不能指向任何其他位置的事实时,这是有道理的,因此存储&a毫无意义。

5 个答案:

答案 0 :(得分:11)

  

a永远不能指向任何其他位置的事实

但这不是事实。如果a是数组,则a不指向任何位置,因为a不是指针。给定int a[42];a命名一个由42个int个对象组成的数组;它不是指向42 int个对象(即int (*a)[42];)数组的指针。

&x为您提供对象x的地址;如果x是数组类型变量,则&x会为您提供数组的地址;如果不出意外,这与&对任何其他对象的行为一致。

一个更好的问题是“为什么数组(如a)在使用它时大多数情况下都会衰减到指向其初始元素的指针?”虽然我不确定为什么语言是这样设计的,但它确实使许多事情的规范变得更加简单,特别是,使用数组的算法实际上与使用指针的算术相同。

答案 1 :(得分:3)

当您考虑如何在装配级别引用阵列时,设计非常优雅且非常必要。使用x86程序集,请考虑以下C代码:

void f(int array[]) { return; }
void g(int (*array)[]) { return; }

int main()
{
    int a[5];

    f(a);
    g(&a);

    return 0;
}

数组a将在堆栈上占用20个字节,因为在大多数平台上,int通常占用4个字节。如果寄存器EBP指向堆栈激活记录的基础,那么您将查看以上main()函数的以下程序集:

//subtract 20 bytes from the stack pointer register ESP for the array
sub esp, 20

//the array is now allocated on the stack

//get the address of the start of the array, and move it into EAX register   
lea eax, [ebp - 20]

//push the address contained in EAX onto the stack for the call to f()
//this is pretty much the only way that f() can refer to the array allocated
//in the stack for main()
push eax
call f

//clean-up the stack
pop eax

//get a pointer to the array of int's on the stack
//(so the type is "int (*)[]")
lea eax, [ebp - 20]

//make the function call again using the stack for the function parameters
push eax
call g

//...clean up the stack and return

汇编命令LEA或“加载有效地址”,从其第二个操作数的表达式计算地址,并将其移动到第一个操作数指定的寄存器中。因此,每当我们调用该命令时,它就像是地址运算符的C等价物。您会注意到数组开始的地址(即[ebp - 20],或从位于重新EBP中的堆栈指针地址的基础中减去的20个字节)是始终传递给每个数组的地址。函数fg。这是在机器代码级别完成的唯一方法,以便引用在另一个函数的一个函数的堆栈中分配的一块内存,而无需实际复制数组的内容。

外卖是数组与指针相同,但同时也是引用赋值运算符右侧数组的唯一有效方法,或者将它传递给函数是通过引用传递它,这意味着引用数组的名称实际上是在机器级别,与获取指向数组的指针完全相同。因此,在机器代码级别,a&a甚至&a[0]在这些情况下会转换为同一组指令(在此示例中为lea eax, [ebp - 20]。)但是,数组类型不是指针,a&a不是同一类型。但由于它指定了一块内存,最简单,最有效获取对它的引用的方法是通过指针。

答案 2 :(得分:1)

实际上,a[0]实际上与a的内存位置相同。 &a代表存储a的地址。

表示相同符号的方式不同。

转到数组的索引3(a[2])与执行a + sizeof( typeof(a) ) * 3相同,其中typeof(a)是变量的类型。

答案 3 :(得分:1)

你的解释是在正确的轨道上,虽然我怀疑空间的金额是否是问题,而是需要分配它的特殊情况。通常,C处理的每个对象都有一个值(或多个值)和一个地址。因此,实际分配的指针本身已经有了一个地址,并且对于实际指针而言,值和地址都可用是有意义的。

但数组引用已经是一个地址。对于C来说,通过& amp;来制作一个双间接指针。运算符需要在somwhere分配空间,这对简单的早期dmr C编译器来说代表了哲学上的巨大分歧。

它存储这个新指针的位置是一个很好的问题。使用与阵列相同的存储类?如果是参数怎么办?它是Pandora's box,解决它的最简单方法是定义操作。如果开发人员想要一个间接指针,他总是可以声明一个。

另外,&返回数组对象的地址是有意义的,因为这与其他地方的使用一致。

查看此对象的一个​​好方法是查看对象具有值和地址,并且数组引用只是一种简写语法。实际上要求 &a会有点迂腐,因为参考a无论如何都不会有另一种解释。

答案 4 :(得分:0)

B是C的直接祖先。它是一种无类型语言,其语法为

tab[10];

或多或少具有

的含义
Word tab_[10];
Word tab = (Word)&tab_;

在C.I.E.它保留了10个字的内存,并用内存区的地址初始化变量。

当C进化出来时,认为数组(BTW不仅是数组变量,任何数组值,你可以用指针和多维数组看到它)在指向第一个元素的指针中衰减这一事实被认为是有用的。

B Manual中的

Dennis Ritchie home page,其中包含有关C和Unix的其他历史信息。