数组a和&a的起始地址

在以下两行中,

char a[5]={1,2,3,4,5}; char *ptr=(char *)(&a+1); printf("%d",*(ptr-1)); 

这在屏幕上打印5个。当使用a而不是&a时,

 char a[5]={1,2,3,4,5}; char *ptr=(char *)(a+1); printf("%d",*(ptr-1)); 

这打印1

a和&a都是数组的起始地址。所以为什么这有区别?

char * ptr =&a + 1;

显示警告。

既然你看起来很陌生,那么让我用简单的语言向你解释,而不是去做严谨的解释。

你看,对于你上面的程序, a&a将具有相同的数值 ,我相信这就是你的整个混乱所在。你可能想知道如果它们是相同的,下面的内容应该在两种情况下给出下一个地址 ,通过指针算术:

 (&a+1) and (a+1) 

但事实并非如此! 数组的基址 (此处为a )和数组的地址不相同! a&a可能在数字上相同,但它们不是同一类型a的类型为char*&a的类型为char (*)[5] ,即&a是指向(地址)和大小为5的数组的指针。但是,如您所知,a是第一个元素地址。数字 。在数量上,它们与你在下面使用^的插图中看到的相同。

但是当你递增这两个指针/地址时,即(a+1)(&a+1) ,算法完全不同。而在第一种情况下它会“跳转”到数组中下一个元素的地址,在后一种情况下,它会跳过5个元素,就像5个元素的数组大小一样! 。现在明白了?

  1 2 3 4 5 ^ // ^ stands at &a 1 2 3 4 5 ^ // ^ stands at (&a+1) 1 2 3 4 5 ^ //^ stands at a 1 2 3 4 5 ^ // ^ stands at (a+1) 

以下将给出关于未指定数组绑定的错误,因为未明确指定大小,如下所示,当遇到类似(&a + 1)之类的程序时,程序将不知道“跳转”到多少元素。

 char a[]={1,2,3,4,5}; char *ptr=(char *)(&a+1); //(&a+1) gives error as array size not specified. 

现在到你将指针/地​​址递减为(ptr-1) 。在第一种情况下,在你来到减量部分之前,你应该知道在它上面的语句中发生了什么,它被转换为类型char*

 char *ptr=(char *)(&a+1); 

这里发生的是你“剥离” typechar (*)[5]的原始type (&a+1) ,然后将其转换为类型char* ,它与a的类型相同,即基数数组的地址。(再次注意数组的基地址和数组的地址之间的区别。在上面的语句中进行强制转换和赋值后,接着是printf()的递减, ptr现在给出了内存位置数组的最后一个元素,即5

  1 2 3 4 5 ^ // ^ stands at location of 5, so *ptr gives 5 

因此,当您将指针ptr递减为*(ptr-1)后取消引用时,它会按预期打印值5

最后,将其与第二种情况进行对比,其中印有1看看我使用符号^给出的插图。 当你将a增加为a+1 ,它指向数组的第二个元素,即2并且你已将此地址分配给ptr .So当你将ptr减为(ptr-1) ,它会跳回一个元素,现在指向数组的第一个元素,即1在第二种情况下解除引用ptr给出1

  1 2 3 4 5 ^ // ^ stands at address of 1, so *ptr gives 1 

希望这一切都清楚。

数组不是指针! 有关更多信息,请阅读comp.lang.c FAQ的第6部分 。

让我们先来看看你的第二个案例吧,因为它更“正常”而且惯用。 逐行:

  1. 声明a包含5个char元素的数组。
  2. 数组的名称( a )在此上下文中衰减为指向其第一个元素的指针。 你添加1并将结果分配给ptrptr指向2 。 虽然你有一个演员阵容,但是没有必要。
  3. 你从ptr减去1然后取消引用并打印 – 因此你得到1

现在,让我们逐行解决第一个案例:

  1. 声明a包含5个char元素的数组。
  2. 你取a的地址,产生一个char (*)[5]类型的指针。 然后你将1添加到这个指针 – 由于指针算术,这个新指针会在内存中的5之后传递到字节。 然后你进行类型转换(必需,这次)并将此值赋给ptr
  3. 你从ptr减去1 ,然后是dreference并打印。 ptr是一个char * ,所以这个减法只是将指针从“一个过去的a ”移回一个指向一个的最后一个元素。 因此你得到了5

最后,原因char *ptr=&a+1; 发出警告是因为C要求指针类型之间的转换具有显式强制转换。 如上所述, &a的类型为char (*)[5]而不是 char * ,因此要将该值赋给char *变量,您需要显式强制转换。

不同之处在于您获得的指针类型:

  • 数组名称a本身表示指向数组初始元素的指针。 当以这种方式解释时,例如在表达式a+1 ,指针被认为指向单个字符。
  • 另一方面,当你拿&a ,指针指向一个包含五个字符的数组。

向指针添加整数时,指针移动的字节数由指针指向的对象指针的类型决定。 如果指针指向char ,则添加N会使指针前进N个字节。 如果指针指向一个包含五个char的数组,则添加N会使指针前进5*N字节。

这正是你得到的差异:你的第一个例子将指针推进到一个超过数组末尾的元素(这是合法的),然后将其移回最后一个元素。 另一方面,您的第二个示例将指针前进到第二个元素,然后将其移回指向数组的初始元素。

你遇到的是指针算术的微妙之处。

编译器将“a”视为指向char的指针 – 一个大小为1字节的实体。 向此处添加1会产生一个指针,该指针会增加实体的大小(即1)。

编译器将“&a”视为指向字符数组的指针 – 一个大小为5字节的实体。 向此处添加1会产生一个指针,该指针会增加实体的大小(即5)。

这就是指针算法的工作原理。 将一个指针添加到指针会使其增加指针所指定类型的大小。

当然,有趣的是,当评估“a”或“&a”的值时,在解除引用时,它们都会评估到相同的地址。 这就是你看到你所做的价值的原因。

数组“衰减”成指向第一个元素的指针。 因此,获取a的地址会给出一个指向5个字符数组的指针,就像声明char[][5] 。 并且递增此指针前进到char[][5]数组的下一个元素 – 一次是5个字符。 这与递增从char[5]数组衰减的指针(即一次只有一个字符)不同。