C中数组变量vs指针的地址和内容
我知道C中的数组本质上就像指针一样,除了在(C中数组变量vs指针的地址和内容,c,arrays,pointers,C,Arrays,Pointers,我知道C中的数组本质上就像指针一样,除了在(sizeof())这样的地方。除此之外,指针和数组变量除了在声明上没有区别 例如,考虑两个声明: int arr[] = {11,22,33}; int *arrptr = arr; 下面是它们的行为方式: printf("%d %d", arrptr[0], arr[0]); //11 11 printf("%d %d", *arrptr, *arr); //11 11 但我发现他们还有一个不同之处: //the outpu
sizeof()
)这样的地方。除此之外,指针和数组变量除了在声明上没有区别
例如,考虑两个声明:
int arr[] = {11,22,33};
int *arrptr = arr;
下面是它们的行为方式:
printf("%d %d", arrptr[0], arr[0]); //11 11
printf("%d %d", *arrptr, *arr); //11 11
但我发现他们还有一个不同之处:
//the outputs will be different on your machine
printf("%d %d", &arrptr, &arr); //2686688 2686692 (obviously different address)
printf("%d %d", arrptr, arr); //2686692 2686692 (both same)
这里的问题是最后一行。我知道
arrptr
包含arr
的地址。这就是为什么最后一行中打印的第一个地址是2686692
。我还了解到,arr
的地址(&arr
)和内容(arr
)在逻辑上应该与arrptr
相同。但是,究竟是什么(在实现级别内部)导致了这种情况发生?当一元&
运算符应用于数组时,它返回指向数组的指针。当应用于指针时,它返回指向指针的指针。此运算符与sizeof
一起表示数组不衰减为指针的少数上下文
换句话说,&arrptr
类型为int**
,而&arr
类型为int(*)[3]
&arrptr
是指针本身的地址,&arr
是数组的开头(如arrptr
)
微妙的部分:arrptr
和&arr
具有相同的值(都指向数组的开头),但类型不同。如果您对它们执行任何指针运算,则会显示此差异–使用arrptr
时,隐含偏移量将为sizeof(int)
,而使用&arr
时,隐含偏移量将为sizeof(int)*3
此外,您应该使用
%p
格式说明符打印指针,在强制转换到void*
后,当一元&
运算符应用于数组时,它返回指向数组的指针。当应用于指针时,它返回指向指针的指针。此运算符与sizeof
一起表示数组不衰减为指针的少数上下文
换句话说,&arrptr
类型为int**
,而&arr
类型为int(*)[3]
&arrptr
是指针本身的地址,&arr
是数组的开头(如arrptr
)
微妙的部分:arrptr
和&arr
具有相同的值(都指向数组的开头),但类型不同。如果您对它们执行任何指针运算,则会显示此差异–使用arrptr
时,隐含偏移量将为sizeof(int)
,而使用&arr
时,隐含偏移量将为sizeof(int)*3
此外,在转换到void*
之后,您应该使用%p
格式说明符打印指针
我知道C中的数组本质上就像一个指针,除了在(sizeof())这样的地方。除此之外,指针和数组变量除了在声明上没有区别
事实并非如此。在大多数情况下,数组表达式被视为指针表达式,但数组和指针是完全不同的
当您将数组声明为
T a[N];
它在记忆中被列为
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[N-1]
+---+
有一件事很明显——数组的第一个元素的地址与数组本身的地址相同。因此,&a[0]
和&a
将产生相同的地址值,尽管这两个表达式的类型不同(T*
vs.T(*)[N]
),并且该值可能会根据类型进行调整
这里有点让人困惑——除非它是sizeof
或一元&
运算符的操作数,或者是用于初始化声明中字符数组的字符串文字,否则“N-element array ofT
”类型的表达式将转换为(“decay”)类型的表达式“指向T
”的指针,表达式的值将是数组第一个元素的地址
这意味着表达式a
也会产生与&a[0]
和&a
相同的地址值,并且具有与&a[0]
相同的类型。综合起来:
Expression Type Decays to Value
---------- ---- --------- -----
a T [N] T * Address of a[0]
&a T (*)[N] n/a Address of a
*a T n/a Value of a[0]
a[i] T n/a Value of a[i]
&a[i] T * n/a Address of a[i]
sizeof a size_t n/a Number of bytes in a
那么,为什么这个转换规则首先存在呢
C源于早期的一种叫做B(go figure)的语言。B是一种无类型的语言
语言-一切基本上都被视为一个无符号整数
被视为固定长度“单元格”的线性数组。当您声明
数组中,留出一个额外的单元格来存储到第一个数组的偏移量
数组的元素:
+---+
a:| | ----+
+---+ |
... |
+-------+
|
V
+---+
| | a[0]
+---+
| | a[1]
+---+
...
+---+
| | a[N-1]
+---+
数组下标操作a[i]
被定义为*(a+i)
;也就是说,获取存储在a
中的偏移值,添加i
,并取消对结果的引用
当Ritchie设计C时,他想保留B的数组语义,但不知道如何处理指向第一个元素的显式指针,所以他放弃了它。因此,C保留了数组订阅定义a[i]=*(a+i)
(给定地址a
,从该地址偏移i
元素并取消对结果的引用),但不为指向数组第一个元素的单独指针留出空间-而是将数组表达式a
转换为指针值
这就是为什么在打印arr
和arrptr
的值时会看到相同的输出。请注意,应该使用%p
转换说明符打印指针值,并将参数强制转换为void*
:
printf( "arr = %p, arrptr = %p\n", (void *) arr, (void *) arrptr );
在C中,这几乎是唯一需要显式将指针值转换为void*
的地方
我知道C中的数组的行为本质上类似于po