在C中,如果我们有一个类似a[10]
的数组,那么a
和&a
具有相同的指针值(但不是相同的类型)。我想知道为什么C设计得像这样?
这是为了节省存储&a
所需的额外空间吗? ......当你想到a
永远不能指向任何其他位置的事实时,这是有道理的,因此存储&a
毫无意义。
答案 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个字节)是始终传递给每个数组的地址。函数f
和g
。这是在机器代码级别完成的唯一方法,以便引用在另一个函数的一个函数的堆栈中分配的一块内存,而无需实际复制数组的内容。
外卖是数组不与指针相同,但同时也是引用赋值运算符右侧数组的唯一有效方法,或者将它传递给函数是通过引用传递它,这意味着引用数组的名称实际上是在机器级别,与获取指向数组的指针完全相同。因此,在机器代码级别,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的其他历史信息。