在 C 语言中,数组名不是指针吗? 如果不是,那么数组名和指针变量之间有什么区别?
数组就是数组,指针就是指针,但在大多数情况下,数组名会被“转换”为指针。常用的术语是它们会“退化”成指针。
这里有一个数组:
int a[7];
a
包含七个整数的空间,通过赋值可以在其中一个位置放入一个值,例如:
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)
。a
)起作用,a[3]
中的名称a
必须首先转换为一个指针(指向a
中第一个元素)。然后我们向前移动3个元素,并取出那里的任何内容。换句话说,就是取出数组中第3个位置的元素。(由于第一个元素编号为0,所以它是数组中的第4个元素。)sizeof
运算符时。如果在这种情况下将a
转换为指针,则sizeof a
将给出指针的大小,而不是实际数组的大小,这将相当无用。所以在这种情况下a
表示数组本身。functionpointer()
和(*functionpointer)()
两者的含义是相同的,这有些奇怪。 - Carl Norumsizeof()
以外,另一个不发生数组指针衰减的情况是运算符&
。在您上面的示例中,&a
将是指向7个int
数组的指针,而不是指向单个int
的指针;也就是说,它的类型将是int(*)[7]
,不能隐式转换为int*
。这样,函数实际上可以接受特定大小的数组指针,并通过类型系统强制执行限制。 - Pavel Minaev当数组作为值使用时,其名称表示第一个元素的地址。
当数组不作为值使用时,其名称表示整个数组。
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 */
char a[10];
你在内存中得到的是什么
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
a
表达式表示整个数组,但是除了数组元素本身外,没有单独的 a
对象。因此,sizeof a
给出整个数组的大小(以字节为单位)。表达式 &a
给出数组的地址,该地址与第一个元素的地址相同。 &a
和 &a[0]
之间的区别是结果类型1,在第一种情况下为 char (*)[10]
,而在第二种情况下为 char *
。a[i]
定义为 *(a + i)
的结果-给定一个地址值 a
,从该地址偏移 i
个元素(而不是字节)并解引用结果。a
不是指针或地址-它是整个数组对象。 因此,在 C 中的规则是,每当编译器看到数组类型的表达式(例如具有类型 char [10]
的 a
)并且该表达式不是 sizeof
或一元 &
运算符的操作数时,该表达式的类型转换为指针类型(char *
),并且表达式的值是数组的第一个元素的地址。 因此,表达式 a
具有与表达式 &a[0]
(以及由此推出的表达式 *a
具有与表达式 a[0]
相同的类型和值)。a
是一个单独的指针对象,而不是数组元素 a [0]
,a [1]
等。Ritchie 想保持 B 的数组语义,但他不想处理存储单独的指针对象。所以他将其删除了。相反,编译器会在必要时将数组表达式转换为指针表达式。gets
函数是如此的危险并最终被从库中删除的原因)。为了让函数知道数组有多少元素,您必须使用哨兵值(例如C字符串中的0终止符)或者将元素数量作为单独的参数传递。
sizeof
是一个运算符,它的值是操作数(可以是表示对象的表达式或用括号括起来的类型名称)中的字节数。因此,对于数组,sizeof
的值等于元素数量乘以单个元素的字节数。如果int
占用4个字节,则包含5个int
元素的数组占用20个字节。 - John Bodea[i]
的定义为*(a + i)
- 给定起始地址a
,找到数组中第i
个对象的地址,并对结果进行解引用。如果a
是数组类型的表达式,则在执行加法之前将其转换为指针。请记住,在指针算术中,a + 1
产生指向所指类型的下一个对象的地址,而不是下一个字节的地址。如果a
指向4字节的int
,那么a + 1
指向下一个4字节的int
。如果a
指向128字节的struct
,那么a + 1
指向下一个128字节的struct
。 - John Bodea
的类型为int [2][3]
,它会“衰减”为类型int (*)[3]
。表达式*(a + 1)
的类型为int [3]
,它会“衰减”为int *
。因此,*(*(a + 1) + 2)
的类型将是int
。a
指向int
的第一个3元素数组,a + 1
指向int
的第二个3元素数组,*(a + 1)
就是int
的第二个3元素数组,*(a + 1) + 2
指向int
的第二个数组中的第三个元素,所以*(*(a + 1) + 2)
就是int
的第二个数组中的第三个元素。如何将其映射到机器代码完全取决于编译器。 - John Bode&a[0]
和*a的值相同,如果你知道我的意思。 - Paralax01像这样声明的数组
int a[10];
为10个 int
分配内存。你不能修改 a
,但可以使用指针算术运算。
这样的指针只为指针 p
分配内存:
int *p;
它不分配任何int
。您可以修改它:
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]);
sizeof (int*) != sizeof (void*)
的情况下是否会导致 UB?公平地说,我不知道有任何系统出现过这种情况。 - 12431234123412341234123#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
糟糕的消息是,数组不是指针,它不被存储在内存中(甚至不是只读内存)作为一个指针,即使看起来是这样,因为你可以用 & 运算符获得它的地址。但是 - oops - 这个运算符不起作用 :-)),无论如何,已经警告过你了:
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;
}
尽管c
和a
都指向同一块内存,你可以获取c
指针的地址,但你无法获取a
指针的地址。
-std=c11 -pedantic-errors
来将其编译为适当的标准C,你会因为编写无效的C代码而得到一个编译器错误。原因是因为你试图将int (*)[3]
分配给int **
类型的变量,这两种类型完全没有任何关系。所以这个例子想要证明什么,我不知道。 - Lundinint **
类型并不是重点,最好使用void *
。 - Palotypedef 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
进行调用时,对象被复制了,因此:
line_as_pointer
仍然包含它原本所指向的地址line_as_array
被复制到一个新的地址,该地址不会超出函数作用域不是。数组名不是指针。你不能给数组名赋值或修改它,但可以对指针进行操作。
int arr[5];
int *ptr;
/* CAN assign or increment ptr */
ptr = arr;
ptr++;
/* CANNOT assign or increment arr */
arr = ptr;
arr++;
/* These assignments are also illegal */
arr = anotherarray;
arr = 0;
来自K&R书:
数组名和指针之间有一个必须记住的区别。指针是一个变量,但数组名不是变量。
sizeof是另一个重要的区别。
sizeof(arr); /* size of the entire array */
sizeof(ptr); /* size of the memory address */
在某些情况下,数组的行为类似于或会衰变成指针(&arr[0]
)。您可以查看其他答案以获取更多示例。以下是其中一些情况的重申:
void func(int *arr) { }
void func2(int arr[]) { } /* same as func */
ptr = arr + 1; /* pointer arithmetic */
func(arr); /* passing to function */
尽管您无法分配或修改数组名称,但当然可以修改数组内容
arr[0] = 1;
int v = 0;
| |
|-------|
100 | | Name 1 - v
| 0 | Name 2 - *100
| |
103 | |
|-------|
| |
Even though *100 is technically not a name, and is instead a unary operator
(i.e. *) along with its operand (i.e. 100), we can still think of it as a name
in order to understand how addresses/pointers work.
Now, &v means the starting address of the variable whose name is v (i.e. 100).
Also, the data type of 100 is int* (i.e. pointer-to-int).
1. printf("%d\n", v)
is equivalent to printf("%d\n", 0)
is equivalent to printf("%d\n", *100)
2. v = 10
is equivalent to *100 = 10
3. v + 1
is equivalent to 0 + 1
is equivalent to *100 + 1
4. scanf("%d", &v)
is equivalent to scanf("%d", &(*100))
is equivalent to scanf("%d", 100)
5. func(&v)
is equivalent to func(&(*100))
is equivalent to func(100)
and so on.
Since the data type of 10 is int, therefore it can be assigned to the variable
whose name is v because that variable's data type is also int.
Similarly, since the data type of 100 is int* (i.e. pointer-to-int), therefore
it can be assigned to a variable whose data type is also int*.
////////////////////////////////////////////////////////////////////////////////
int* ptr = &v; is equivalent to int* ptr = 100;
| |
|-------|
100 | | Name 1 - v
| 0 | Name 2 - *100
| |
103 | |
|-------|
| |
| |
|-------|
200 | | Name 1 - ptr
| | Name 2 - *200
| |
| 100 |
| |
| |
| |
207 | |
|-------|
| |
Now, for eg., when evaluating v + 1, v is equivalent to 0 because 0 is the value
which is stored in the variable whose name is v.
Similarly, when evaluating *ptr, ptr is equivalent to 100 because 100 is the
value which is stored in the variable whose name is ptr.
1. printf("%d\n", v)
is equivalent to printf("%d\n", 0)
is equivalent to printf("%d\n", *100)
is equivalent to printf("%d\n", *ptr)
2. v = 10
is equivalent to *100 = 10
is equivalent to *ptr = 10
3. v + 1
is equivlent to 0 + 1
is equivalent to *100 + 1
is equivalent to *ptr + 1
4. scanf("%d", &v)
is equivalent to scanf("%d", &(*100))
is equivalent to scanf("%d", 100)
is equivalent to scanf("%d", &(*ptr))
is equivalent to scanf("%d", ptr)
5. func(&v)
is equivalent to func(&(*100))
is equivalent to func(100)
is equivalent to func(&(*ptr))
is equivalent to func(ptr)
and so on.
So, we can also think that the variable whose name is v gets a third name, i.e.
*ptr.
| |
|-------|
100 | | Name 1 - v
| 0 | Name 2 - *100
| | Name 3 - *ptr
103 | |
|-------|
| |
Also, to get the address of this variable, we can use &v, 100 or ptr.
////////////////////////////////////////////////////////////////////////////////
int main(void)
{
int v;
...
func(&v);
...
}
void func(int* ptr)
{
...
*ptr = 5;
...
}
Here, the function call func(&v) is equivalent to func(100).
So, when func() is executed, the name v of the variable which is local to main()
goes out of scope.
| |
|-------|
100 | | Name 1 - xxxx
| 0 | Name 2 - *100
| |
103 | |
|-------|
| |
Now, a variable which is local to func() whose name is ptr and whose data type
is pointer-to-int is created.
| |
|-------|
200 | | Name 1 - ptr
| | Name 2 - *200
| |
| 100 |
| |
| |
| |
207 | |
|-------|
| |
Now, the variable which is local to main() gets a new name.
| |
|-------|
100 | | Name 1 - xxxx
| 0 | Name 2 - *100
| | Name 3 - *ptr
103 | |
|-------|
| |
And, to get the address of the variable local to main(), we can use 100 or ptr.
After executing *ptr = 5, when func() returns, the variable which is local to
func() and whose name is ptr is destroyed, and the name v of the variable which
is local to main() is restored.
Also, since the variable which is local to func() and whose name is ptr is
destroyed, therefore the name *ptr of the variable which is local to main() is
also destroyed.
| |
|-------|
100 | | Name 1 - v
| 5 | Name 2 - *100
| |
103 | |
|-------|
| |
////////////////////////////////////////////////////////////////////////////////
int arr[3] = {7, 8, 9};
arr is the name of the entire array.
| |
|-------|
300 | | Name - *300
| 7 |
| |
303 | |
|-------|
304 | | Name - *304
| 8 |
| |
307 | |
|-------|
308 | | Name - *308
| 9 |
| |
311 | |
|-------|
| |
When the expression v + 1.0 is evaluated, the data type of v is converted from
int to double only for the purpose of evaluating that expression.
Similarly, except for those 4 cases (&, sizeof, alignof, string literal used to
initialize an array), arr is converted to the address of the first element of
the array only for the purpose of evaluating the corresponding expression.
Also, for eg., if the array consists of elements of data type int, then the
data type of the resultant address is pointer-to-int. So, the resultant address
can be assigned to a variable of data type pointer-to-int.
For eg., int* ptr = arr;
So, in other words, except for those 4 cases, arr is converted to a pointer to
the first element of the array only for the purpose of evaluating the
corresponding expression.
When an integer is added to an address/pointer, the resultant address is
calculated according to the data type of the address/pointer.
For eg., 300 + 1 gives 304.
arr[i] is equivalent to *(arr + i).
Now, arr[1]
is equivalent to *(arr + 1)
is equivalent to *(300 + 1)
is equivalent to *304
And, &(arr[1])
is equivalent to &(*(arr + 1))
is equivalent to &(*(300 + 1))
is equivalent to &(*304)
is equivalent to 304
Also, arr + 1
is equivalent to 300 + 1
is equivalent to 304
1. printf("%d\n", arr[1])
is equivalent to printf("%d\n", 8)
is equivalent to printf("%d\n", *(arr + 1))
is equivalent to printf("%d\n", *304)
2. arr[1] = 10
is equivalent to *(arr + 1) = 10
is equivalent to *304 = 10
3. arr[1] + 1
is equivalent to 8 + 1
is equivalent to *(arr + 1) + 1
is equivalent to *304 + 1
4. scanf("%d", &(arr[1]))
is equivalent to scanf("%d", &(*(arr + 1)))
is equivalent to scanf("%d", &(*304))
is equivalent to scanf("%d", 304)
is equivalent to scanf("%d", arr + 1)
5. func(&(arr[1]))
is equivalent to func(&(*(arr + 1)))
is equivalent to func(&(*304))
is equivalent to func(304)
is equivalent to func(arr + 1)
and so on.
So, we can also think that the elements of the array have other names.
| |
|-------|
300 | | Name 1 - *300
| 7 | Name 2 - arr[0]
| | Name 3 - *(arr + 0)
303 | |
|-------|
304 | | Name 1 - *304
| 8 | Name 2 - arr[1]
| | Name 3 - *(arr + 1)
307 | |
|-------|
308 | | Name 1 - *308
| 9 | Name 2 - arr[2]
| | Name 3 - *(arr + 2)
311 | |
|-------|
| |
Also, to get the address of, for eg., arr[1], we can use &(arr[1]), 304 or
arr + 1.
////////////////////////////////////////////////////////////////////////////////
int main(void)
{
int arr[3];
...
func(arr);
...
}
void func(int* arr)
{
...
arr[1] = 6;
...
}
Here, the function call func(arr) is equivalent to func(300).
So, when func() is executed, the name arr of the entire array which is local to
main() goes out of scope, which means that Name 2 and Name 3 of every element of
the array also go out of scope.
| |
|-------|
300 | | Name 1 - *300
| 7 | Name 2 - xxxx
| | Name 3 - xxxx
303 | |
|-------|
304 | | Name 1 - *304
| 8 | Name 2 - xxxx
| | Name 3 - xxxx
307 | |
|-------|
308 | | Name 1 - *308
| 9 | Name 2 - xxxx
| | Name 3 - xxxx
311 | |
|-------|
| |
Now, a variable which is local to func() whose name is arr and whose data type
is pointer-to-int is created.
| |
|-------|
400 | | Name 1 - arr
| | Name 2 - *400
| |
| 300 |
| |
| |
| |
407 | |
|-------|
| |
It should be noted that when the expression arr[1] = 6 is evaluated inside
func(), arr isn't the name of the array which is local to main(), and is instead
the name of the variable which is local to func().
So, there is no need to convert arr to a pointer, as arr is already a pointer.
Now, similar to main(), inside func() also
arr[1]
is equivalent to *(arr + 1)
is equivalent to *(300 + 1)
is equivalent to *304
And, &(arr[1])
is equivalent to &(*(arr + 1))
is equivalent to &(*(300 + 1))
is equivalent to &(*304)
is equivalent to 304
Also, arr + 1
is equivalent to 300 + 1
is equivalent to 304
So, the elements of the array which is local to main() get new names.
| |
|-------|
300 | | Name 1 - *300
| | Name 2 - xxxx
| 7 | Name 3 - xxxx
| | Name 4 - arr[0]
303 | | Name 5 - *(arr + 0)
|-------|
304 | | Name 1 - *304
| | Name 2 - xxxx
| 8 | Name 3 - xxxx
| | Name 4 - arr[1]
307 | | Name 5 - *(arr + 1)
|-------|
308 | | Name 1 - *308
| | Name 2 - xxxx
| 9 | Name 3 - xxxx
| | Name 4 - arr[2]
311 | | Name 5 - *(arr + 2)
|-------|
| |
And, to get the address of, for eg., arr[1], we can use &(arr[1]), 304 or
arr + 1.
After executing arr[1] = 6, when func() returns, the variable which is local to
func() and whose name is arr is destroyed, and the name arr of the entire array
which is local to main() is restored, which means that Name 2 and Name 3 of
every element of the array are also restored.
Also, since the variable which is local to func() and whose name is arr is
destroyed, therefore Name 4 and Name 5 of every element of the array are also
destroyed.
| |
|-------|
300 | | Name 1 - *300
| 7 | Name 2 - arr[0]
| | Name 3 - *(arr + 0)
303 | |
|-------|
304 | | Name 1 - *304
| 6 | Name 2 - arr[1]
| | Name 3 - *(arr + 1)
307 | |
|-------|
308 | | Name 1 - *308
| 9 | Name 2 - arr[2]
| | Name 3 - *(arr + 2)
311 | |
|-------|
| |
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
对于机器来说,这两个打印语句将会给出完全相同的输出。在我的系统中,它输出了:
0x7fff6fe40bc0
&array[0]
返回的是指针,不是数组 ;) - jalf