首页 > 解决方案 > 当 p 指向数组时 (*p-1) 的值

问题描述

我最近在学习 C/C++ 的指针,但是有一个指针问题让我很困惑。

下面的代码,假设 p 是一个指向数组 a 的 int 指针。

int a[5] = {1, 2, 3, 4, 5};
int *p = (int*)(&a+1);
printf("(*p-1)=%d\n",(*p-1)); // 32765

我认为(int*)(&a+1)等于数组a的结束地址。p 指向地址。

但是为什么(*p-1)显示32765?我什至不知道 *p 的值。

标签: cpointers

解决方案


在您的代码中,&a是指向由 表示的数组的指针a,即它的类型是int (*)[5](“指向大小为 5 的数组的指针int”)。

&a + 1sizeof a字节添加到 的地址的偏移量a。因此,结果是一个指向大小为 5 的数组的指针,该数组位于内存中a.

然而,就 C 而言,这个数组不存在,因为没有创建它(通过 eg malloc)。您可以创建指向它的指针(因为允许指针过去a),但不允许取消引用它。这样做会导致未定义的行为,其影响通常是不可知的。

由于您已声明pasint *p而不是int (*p)[5],它现在指向 address 处不存在的数组的第一个元素&a + 1。这是允许的:指向某个类型的指针可以为指向该类型数组的指针起别名。但是那个数组仍然不存在,所以你不能取消引用,就像p你不能取消引用数组一样&a + 1

然而*p - 1(与(*p) - 1! 相同)就是这样做的:它尝试取消引用p并从取消引用的值中减去 1。由于这是不允许的,因此实现有许可证可以“随心所欲”。在您的情况下,实现恰好返回一个随机的、无意义的值。或者,您的代码也可能崩溃或做了一些完全不同的、看似无关的事情。

有一系列高级工具可以捕获此类错误。一种工具是Google 的 AddressSanitizer。它内置在 GCC 中,因此,如果您使用 GCC 编译应用程序,您可以通过将标志传递-fsanitize=address给编译器来使用它:

cc -fsanitize=address -g -pedantic -Wall -Wextra -Werror ub.c -o ub

现在,当您执行应用程序时,您将收到一条(字面意思)彩色错误消息:

⟩⟩⟩ ./ub
=================================================================
==81812==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffeed29b314 at pc 0x000102964cd7 bp 0x7ffeed29b2d0 sp 0x7ffeed29b2c8
READ of size 4 at 0x7ffeed29b314 thread T0
    #0 0x102964cd6 in main ub.c:6
    #1 0x7fff602883d4 in start (libdyld.dylib:x86_64+0x163d4)

Address 0x7ffeed29b314 is located in stack of thread T0 at offset 52 in frame
    #0 0x102964b8f in main ub.c:3

  This frame has 1 object(s):
    [32, 52) 'a' (line 4) <== Memory access at offset 52 overflows this variable
…

还有更多,但这是要点:ub.c第 6 行(the printf)行中有一个错误,这是由于内存访问溢出了a在第 3 行开始的堆栈帧(~ 函数范围)中声明的变量.


推荐阅读