7

在下面的两行中,

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;

显示警告。

4

5 回答 5

12

数组不是指针!阅读comp.lang.c 常见问题的第 6 部分以获取更多信息。

让我们先看看你的第二种情况,因为它看起来更“正常”和惯用。逐行:

  1. a您声明一个包含 5 个char元素的数组。
  2. 数组 ( a) 的名称在此上下文中衰减为指向其第一个元素的指针。您添加1到它并将结果分配给ptr. ptr指向2. 不需要演员表,尽管你有一个。
  3. 你减去然后取消引用和打印 - 因此你1得到.ptr1

现在,让我们逐行处理第一种情况:

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

最后,char *ptr=&a+1;给出警告的原因是因为 C 需要指针类型之间的转换才能进行显式转换。如上所述,&ais 类型char (*)[5]not char *,因此要将值分配给char *变量,您需要显式转换。

于 2013-05-01T03:28:43.383 回答
10

既然您似乎对它完全陌生,那么让我用简单的术语向您解释一下,而不是进行严格的解释。

你看,对于你上面的程序,a并且&a会有相同的数值,我相信这就是你的全部困惑所在。你可能想知道如果它们相同,下面应该在这两种情况下给出下一个地址a,过去指针算法:

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

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

但是当你增加这两个指针/地址时,即 as(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)。在第一种情况下,在你来到递减部分之前,你应该知道在它上面的语句中发生了什么,它被强制转换为 type char*

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

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

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

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

最后,将其与打印的第二种情况进行1对比。看看我使用符号^给出的插图。当您增加aasa+1时,它指向数组的第二个元素,即2您已将此地址分配给。因此,ptr当您将其减少ptras时(ptr-1),它会跳回一个元素,现在指向数组的第一个元素,即 1。所以ptr在第二种情况下取​​消引用给出1.

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

希望这一切都清楚了。

于 2013-05-01T08:19:15.863 回答
2

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

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

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

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

于 2013-05-01T03:36:03.650 回答
0

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

于 2013-05-01T03:32:19.643 回答
0

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

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

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

这就是指针算法的工作原理。向指针添加 1 会使它增加它作为指针的类型的大小。

当然,有趣的是,在评估“a”或“&a”的值时,在取消引用时,它们都评估到相同的地址。这就是为什么你会看到你所做的价值观。

于 2013-05-01T03:56:04.503 回答