数组名是指针吗?

在C语言中数组的名字是指针吗? 如果不是,数组的名称和指针变量之间的区别是什么?< / p >

97166 次浏览

数组是数组,指针是指针,但在大多数情况下,数组名是转换指向指针。一个经常使用的术语是它们衰变指向指针。

这是一个数组:

int a[7];

a包含7个整数的空格,你可以在其中一个整数中赋值,如下所示:

a[3] = 9;

这里有一个指针:

int *p;

p不包含任何整数的空格,但它可以指向整数的空格。例如,我们可以将它设置为指向数组a中的一个位置,例如第一个:

p = &a[0];

让人困惑的是,你也可以这样写:

p = a;

将数组a的内容复制到指针p(不管这意味着什么)。相反,数组名a被转换为指向其第一个元素的指针。这个分配和之前的一样。

现在你可以像使用数组一样使用p:

p[3] = 17;

这样做的原因是C语言中的数组解引用操作符[ ]是用指针定义的。x[y]的意思是:从指针x开始,在指针指向的位置之后向前移动y元素,然后取那里的任何元素。使用指针算术语法,x[y]也可以写为*(x+y)

要使用普通数组,例如aa[3]中的名称a必须首先转换为指针(指向a中的第一个元素)。然后我们向前推进3个元素,取其中的所有元素。换句话说:取数组中位置为3的元素。(它是数组中的第四个元素,因为第一个元素编号为0。)

因此,总的来说,C程序中的数组名(在大多数情况下)被转换为指针。一个例外是在数组上使用sizeof操作符。如果在这种情况下a被转换为指针,sizeof a将给出指针的大小,而不是实际数组的大小,这将是相当无用的,所以在这种情况下a意味着数组本身。

像这样声明的数组

int a[10];

为10个__abc分配内存。你不能修改a,但是你可以用a做指针算术。

这样的指针仅为指针p分配内存:

int *p;

它不分配任何__abc。你可以修改它:

p = a;

并像使用a一样使用数组下标:

p[2] = 5;
a[2] = 5;    // same
*(p+2) = 5;  // same effect
*(a+2) = 5;  // same effect

数组名本身会产生一个内存位置,所以你可以像对待指针一样对待数组名:

int a[7];


a[0] = 1976;
a[1] = 1984;


printf("memory location of a: %p", a);


printf("value at memory location %p is %d", a, *a);

你还可以对指针做其他漂亮的事情(例如增加/减去偏移量),你也可以对数组做:

printf("value at memory location %p is %d", a + 1, *(a + 1));

在语言方面,如果C语言没有将数组公开为某种“指针”(学究地说,它只是一个内存位置。它不能指向内存中的任意位置,也不能由程序员控制)。我们总是需要这样编码:

printf("value at memory location %p is %d", &a[1], a[1]);

当数组用作值时,其名称表示第一个元素的地址 当一个数组不被用作值时,它的名称代表整个数组

int arr[7];


/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */


/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */

如果数组类型的表达式(例如数组名)出现在更大的表达式中,并且它不是&sizeof操作符的操作数,则数组表达式的类型将从“N-element array of T”转换为“指向T的指针”,并且表达式的值是数组中第一个元素的地址。

简而言之,数组名不是指针,但在大多数上下文中,它被处理为好像,它是指针。

编辑

在评论中回答这个问题:

如果我使用sizeof,我只计算数组元素的大小吗?然后数组“head”也占用了关于长度和指针的信息的空间(这意味着它占用了更多的空间,比一个正常的指针会)?

当你创建一个数组时,唯一分配的空间是元素本身的空间;没有为单独的指针或任何元数据具体化存储。鉴于

char a[10];

你在记忆中得到的是

   +---+
a: |   | a[0]
+---+
|   | a[1]
+---+
|   | a[2]
+---+
...
+---+
|   | a[9]
+---+

表达式 a指向整个数组,但数组元素本身没有对象 a。因此,sizeof a给出整个数组的大小(以字节为单位)。表达式&a给出了数组a0的地址。&a&a[0]之间的区别是第一种情况下的__abc11 - char (*)[10]和第二种情况下的char *结果的类型。

奇怪的是,当你想访问单个元素时——表达式a[i]被定义为*(a + i)的结果——给定一个地址值a,从该地址偏移i元素(不是字节)并解引用结果。

问题是a既不是指针也不是地址——它是整个数组对象。因此,C语言中的规则是,每当编译器看到数组类型的表达式(例如a,其类型为char [10]) a0时,该表达式不是sizeof或一元&操作符的操作数,该表达式的类型将被转换(“衰变”)为指针类型(char *),并且表达式的值是数组第一个元素的地址。因此,a1 a与表达式&a[0]具有相同的类型和值(通过扩展,表达式*a与表达式a[0]具有相同的类型和值)。

C语言源自早期的一种名为B的语言,在B中a 是一个独立于数组元素a[0]a[1]等的指针对象。Ritchie想要保留B的数组语义,但他不想在存储单独的指针对象时搞得一团糟。所以他把它扔掉了。相反,编译器会在必要时将数组表达式转换为指针表达式。

记住,我说过数组不存储任何关于其大小的元数据。一旦数组表达式“衰减”为指针,您所拥有的就是一个指向单个元素的指针。该元素可以是元素序列的第一个,也可以是单个对象。根据指针本身是无法知道的。

当你将一个数组表达式传递给一个函数时,函数接收到的只是一个指向第一个元素的指针——它不知道数组有多大(这就是为什么gets函数如此危险并最终从库中删除的原因)。为了让函数知道数组中有多少个元素,必须使用哨兵值(例如C字符串中的0结束符),或者必须将元素数量作为单独的参数传递。


  1. 这*可能*影响地址值的解释方式-取决于机器。

数组名的行为类似于指针,指向数组的第一个元素。例子:

int a[]={1,2,3};
printf("%p\n",a);     //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0

这两个print语句将为一台机器提供完全相同的输出。在我的系统中,它给出:

0x7fff6fe40bc0

我认为这个例子可以说明这个问题:

#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;


printf("a == &a: %d\n", a == b);
return 0;
}

它在gcc 4.9.2中编译良好(有2个警告),并打印以下内容:

a == &a: 1

哦:-)

所以,结论是不,数组不是指针,它不是作为指针存储在内存中(甚至不是只读的),即使它看起来像,因为你可以通过&操作符。但是-哎呀-操作符不起作用:-)),不管怎样,你已经被警告了:

p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);

c++拒绝在编译时出现错误的任何此类尝试。

编辑:

这就是我想要证明的:

#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;


void *b = &a;
void *d = &c;


printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}

即使ca "指向"相同的内存,你可以获得c指针的地址,但你不能获得a指针的地址。

下面的示例提供了数组名和指针之间的具体区别。假设你想用给定的最大维数表示一条一维线,你可以用数组或指针来实现:

typedef struct {
int length;
int line_as_array[1000];
int* line_as_pointer;
} Line;

现在让我们看看下面代码的行为:


void do_something_with_line(Line line) {
line.line_as_pointer[0] = 0;
line.line_as_array[0] = 0;
}


void main() {
Line my_line;
my_line.length = 20;
my_line.line_as_pointer = (int*) calloc(my_line.length, sizeof(int));


my_line.line_as_pointer[0] = 10;
my_line.line_as_array[0] = 10;


do_something_with_line(my_line);


printf("%d %d\n", my_line.line_as_pointer[0], my_line.line_as_array[0]);
};


这段代码将输出:

0 10

这是因为在do_something_with_line函数调用中,对象被复制,因此:

  1. 指针line_as_pointer仍然包含它所指向的相同地址
  2. 数组line_as_array被复制到一个没有超出函数作用域的新地址

因此,当你直接将数组输入到函数时,数组不是由值给出的,当你将它们封装在结构中时,它们是由值给出的(即复制),这概述了与使用指针实现相比,在行为上的主要区别。