c - 当 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 的值。
解决方案
在您的代码中,&a
是指向由 表示的数组的指针a
,即它的类型是int (*)[5]
(“指向大小为 5 的数组的指针int
”)。
&a + 1
将sizeof a
字节添加到 的地址的偏移量a
。因此,结果是一个指向大小为 5 的数组的指针,该数组位于内存中a
.
然而,就 C 而言,这个数组不存在,因为没有创建它(通过 eg malloc
)。您可以创建指向它的指针(因为允许指针过去a
),但不允许取消引用它。这样做会导致未定义的行为,其影响通常是不可知的。
由于您已声明p
asint *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 行开始的堆栈帧(~ 函数范围)中声明的变量.
推荐阅读
- python - 如何计算最近 36 个月的股票方差(部分月度数据缺失)?
- spring-cloud - Spring Cloud 认证服务
- jmeter - Jmeter - 在运行共享点登录的登录脚本时,在自定义 api 上出现 403 禁止错误
- javascript - 用 ? 处理 undefined 或 null 抛出 SyntaxError: Unexpected token using webpack
- javascript - 引导网站重叠中的各个部分
- chromecast - 如何隐藏 CAF 通知
- amazon-web-services - AWS ALB 侦听器 - https 和 http
- javascript - 如何使用自定义 Vue 实例注入 Vue 组件
- networking - 将所有子域请求隧道传输到本地计算机
- ubuntu - Ubuntu WSL:sudo mv
给予许可被拒绝