在C中,是数组指针还是用作指针?
我的理解是,数组只是指向一系列值的常量指针,并且当您在C中声明一个数组时,您声明了一个指针并为它指向的序列分配空间。
但是这使我困惑:下面的代码:
char y[20]; char *z = y; printf("y size is %lu\n", sizeof(y)); printf("y is %p\n", y); printf("z size is %lu\n", sizeof(z)); printf("z is %p\n", z);
当与苹果GCC编译给出以下结果:
y size is 20 y is 0x7fff5fbff930 z size is 8 z is 0x7fff5fbff930
(我的机器是64位,指针是8个字节长)。
如果'y'是一个常量指针,为什么它的大小是20,就像它指向的值序列一样? 在编译期间,variables名称“y”是否由内存地址replace? 那么,数组中的某种语法糖就是刚刚被编译时转换为指针的东西?
这是C标准( n1256 )的确切语言:
6.3.2.1左值,数组和函数指示符
…
3除了sizeof
运算符的操作数或一元运算符的操作数,或者是用于初始化数组的string字面值以外,将types为'''的数组expression式转换为types为''指针的expression式键入 “'指向数组对象的初始元素,而不是一个左值。 如果数组对象具有寄存器存储类,则行为是未定义的。
这里要记住的重要一点是,一个对象 (用C语言来表示占用内存的东西)和用于引用该对象的expression式之间是有区别的。
当你声明一个数组如
int a[10];
由expression式 a
指定的对象是一个数组(即,一个足够容纳10个int
值的连续内存块),而expression式 a的types是“10个元素的int
数组”或int [10]
。 如果expression式 a
出现在不是作为sizeof
或&
运算符的操作数的上下文中,则其types被隐式转换为int *
,其值是第一个元素的地址。
在sizeof
运算符的情况下,如果操作数是T [N]
types的expression式,那么结果是数组对象中的字节数,而不是指向该对象的指针: N * sizeof T
在&
运算符的情况下,该值是数组的地址,它与数组的第一个元素的地址相同,但是expression式的types是不同的:给定声明T a[N];
,expression式&a
的types是T (*)[N]
,或者指向T的N元素的数组。该值与a
或者&a[0]
(数组的地址与地址相同数组中的第一个元素),但types的差异很重要。 例如,给定的代码
int a[10]; int *p = a; int (*ap)[10] = &a; printf("p = %p, ap = %p\n", (void *) p, (void *) ap); p++; ap++; printf("p = %p, ap = %p\n", (void *) p, (void *) ap);
你会看到输出的顺序
p = 0xbff11e58, ap = 0xbff11e58 p = 0xbff11e5c, ap = 0xbff11e80
IOW,前进p
将sizeof int
(4)的值增加到原始值,而推进ap
增加了10 * sizeof int
(40)的值。
更多标准语言:
6.5.2.1数组下标
约束
1其中一个expression式应该有'指向对象types'的指针,另一个expression式应该是整数types,结果是' type ' types 。
语义
2后缀expression式后面跟着方括号
[]
的expression式是数组对象元素的下标。 下标运算符[]
的定义是E1[E2]
与(*((E1)+(E2)))
。 由于适用于二元运算符的转换规则,如果E1
是一个数组对象(等价地,指向一个数组对象的初始元素的指针),E2
是一个整数,E1[E2]
指定E2
第E2
个元素E1
(从零开始计数)。
因此,当你为一个数组expression式下标时,发生的情况是数组中第一个元素的地址偏移量被计算出来,结果被解除引用。 expression方式
a[i] = 10;
相当于
*((a)+(i)) = 10;
相当于
*((i)+(a)) = 10;
相当于
i[a] = 10;
是的,C中的数组下标是可交换的; 为了上帝的爱,决不要在生产守则中这样做。
由于数组下标是根据指针操作定义的,因此可以将下标运算符应用于指针types的expression式以及数组types:
int *p = malloc(sizeof *p * 10); int i; for (i = 0; i < 10; i++) p[i] = some_initial_value();
这里有一个方便的表格来记住这些概念:
声明:T a [N]; expression式types转换为值 ---------- ---- ------------ ----- 一个T [N] T *地址中的第一个元素; 与写作相同[0] &一个T(*)[N]数组的地址; 价值是一样的 如上所述,但types不同 sizeof size_t数组中包含的字节数 对象(N * sizeof T) *一个[0]的T值 a [i] T值在[i] &a [i] T *地址[i] 声明:T a [N] [M]; expression式types转换为值 ---------- ---- ------------ ----- 一个T [N] [M] T(*)[M]第一个子数组的地址(&a [0]) &T(*)[N] [M]数组地址(与 以上,但不同的types) sizeof size_t中包含的字节数 数组对象(N * M * sizeof T) * a T [M] T *作为地址的[0]的值 第一个子数组的第一个元素 (与&a [0] [0]相同) a [i] T [M] T * a [i]的值,即地址 我的子arrays的第一个元素 &a [i] T(*)[M]第i个子数组的地址; 相同的价值 以上,但types不同 sizeof a [i] size_t包含在第i个子数组中的字节数 对象(M * sizeof T) * i [i] T的第一个元素的值 子arrays(a [i] [0]) a [i] [j] T值在a [i] [j] &a [i] [j] T * [i] [j] 声明:T a [N] [M] [O]; expression式types转换为 ---------- ---- ----------- 一个T [N] [M] [O] T(*)[M] [O] &a T(*)[N] [M] [O] * a T [M] [O] T(*)[O] a [i] T [M] [O] T(*)[O] &a [i] T(*)[M] [O] * a [i] T [O] T * a [i] [j] T [O] T * &a [i] [j] T(*)[O] * a [i] [j] T a [i] [j] [k] T
从这里开始,高维数组的模式应该是清晰的。
所以,总之:数组不是指针。 在大多数情况下,数组expression式被转换为指针types。
数组不是指针,尽pipe在大多数expression式中,数组名称的计算结果是指向数组的第一个元素的指针。 所以使用数组名称作为指针是非常非常容易的。 你会经常看到用来描述这个的“衰变”这个词,就像“arrays衰变成指针”一样。
一个例外是作为sizeof
运算符的操作数,其中结果是数组大小(以字节为单位,而不是元素)。
还有一些与此有关的问题:
函数的数组参数是虚构的 – 编译器确实传递了一个普通指针(这不适用于C ++中的引用数组参数),所以你不能确定传递给函数的数组的实际大小 – 你必须以其他方式传递这些信息(也许使用一个明确的附加参数,或者使用一个sentinel元素 – 就像Cstring一样)
另外,获取数组中元素个数的常见习惯用法是使用如下macros:
#define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0]))
这有一个接受数组名称,它将工作的地方或者一个指针的问题,它会在没有编译器警告的情况下给出一个无意义的结果。 存在更安全的macros版本(特别是C ++),当它与指针而不是数组一起使用时,会生成警告或错误。 请参阅以下SO项目:
- C ++版本
- 一个更好的(虽然还不是非常安全的)C版本
注意:C99 VLAs(可变长度数组)可能不遵循所有这些规则(特别是,它们可以作为被调用函数已知数组大小的parameter passing)。 我对VLA几乎没有什么经验,据我所知他们没有被广泛使用。 不过,我想指出的是,上述讨论可能适用于VLA。
sizeof
是在编译时计算的,编译器知道操作数是数组还是指针。 对于数组,它给出了数组占用的字节数。 你的数组是一个char[]
( sizeof(char)
是1),所以sizeof
恰好给你一些元素。 为了得到一般情况下的元素个数,一个常见的习惯用法是(这里是int
):
int y[20]; printf("number of elements in y is %lu\n", sizeof(y) / sizeof(int));
对于指针sizeof
给出了原始指针types占用的字节数。
除了别人说的外,也许这篇文章有助于: http : //en.wikipedia.org/wiki/C_%28programming_language%29#Array-pointer_interchangeability
在
char hello[] = "hello there" int i;
和
char* hello = "hello there"; int i;
在第一种情况下(折扣alignment),将存储12个字节用于hello,将分配的空间初始化为hello,而在第二个hello中则存储在别处(可能是静态空间), hello
被初始化为指向给定的string。
然而, hello[2]
以及*(hello + 2)
会在两个实例中返回'e'。
如果'y'是一个常量指针,为什么它的大小是20,就像它指向的值序列一样?
因为z
是variables的地址,所以你的机器总是返回8。 您需要使用解引用指针(&)来获取variables的内容。
编辑:两者之间的一个很好的区别: http : //www.cs.cf.ac.uk/Dave/C/node10.html