在回答您的问题之前,让我们看看在指针操作期间会发生什么。我正在使用一个非常简单的代码来演示这一点:
#include <stdio.h>
int main() {
int *p;
int **p2;
int x = 3;
p = &x;
p2 = &p;
return 0;
}
现在看反汇编:
(gdb) disassemble
Dump of assembler code for function main:
0x0000000000400474 <+0>: push rbp
0x0000000000400475 <+1>: mov rbp,rsp
0x0000000000400478 <+4>: mov DWORD PTR [rbp-0x14],0x3
0x000000000040047f <+11>: lea rax,[rbp-0x14]
0x0000000000400483 <+15>: mov QWORD PTR [rbp-0x10],rax
0x0000000000400487 <+19>: lea rax,[rbp-0x10]
0x000000000040048b <+23>: mov QWORD PTR [rbp-0x8],rax
=> 0x000000000040048f <+27>: mov eax,0x0
0x0000000000400494 <+32>: leave
0x0000000000400495 <+33>: ret
拆卸是不言而喻的。但是这里需要补充几点,
我的函数的堆栈帧从这里开始:
0x0000000000400474 <+0>: push rbp
0x0000000000400475 <+1>: mov rbp,rsp
所以让他们现在拥有的
(gdb) info registers $rbp
rbp 0x7fffffffe110 0x7fffffffe110
在这里,我们将值3
放入[rbp - 0x14]
's 地址。让我们看看内存映射
(gdb) x/1xw $rbp - 0x14
0x7fffffffe0fc: 0x00000003
重要的是要注意DWORD
使用了 32 位宽的数据类型。所以在旁注中,整数文字3
被视为 4 字节单位。
下一条指令用于lea
加载前面指令中刚刚保存的值的有效地址。
0x000000000040047f <+11>: lea rax,[rbp-0x14]
这意味着 now$rax
将具有价值0x7fffffffe0fc
。
(gdb) p/x $rax
$4 = 0x7fffffffe0fc
接下来我们将使用这个地址保存到内存中
0x0000000000400483 <+15>: mov QWORD PTR [rbp-0x10],rax
重要的是要注意QWORD
这里使用的 a。因为 64 位系统有 8 字节的本机指针大小。0x14 - 0x10 = 4
字节在前面的mov
指令中使用。
接下来我们有:
0x0000000000400487 <+19>: lea rax,[rbp-0x10]
0x000000000040048b <+23>: mov QWORD PTR [rbp-0x8],rax
这又是第二次间接。与地址相关的所有值总是QWORD
. 重要的是要注意这一点。
现在让我们来看看你的代码。
在调用 swaparray 之前,您有:
=> 0x00000000004004fe <+8>: mov DWORD PTR [rbp-0x10],0x1
0x0000000000400505 <+15>: mov DWORD PTR [rbp-0xc],0x3
0x000000000040050c <+22>: mov DWORD PTR [rbp-0x20],0x2
0x0000000000400513 <+29>: mov DWORD PTR [rbp-0x1c],0x4
0x000000000040051a <+36>: lea rdx,[rbp-0x20]
0x000000000040051e <+40>: lea rax,[rbp-0x10]
0x0000000000400522 <+44>: mov rsi,rdx
0x0000000000400525 <+47>: mov rdi,rax
这是非常微不足道的。您的数组已初始化,&
当数组开头的有效地址加载到$rdi
and时,运算符的效果可见$rsi
。
现在让我们看看它在里面做什么swaparray()
。
数组的开头保存在$rdi
and$rsi
中。所以让我们看看他们的内容
(gdb) p/x $rdi
$2 = 0x7fffffffe100
(gdb) p/x $rsi
$3 = 0x7fffffffe0f0
0x00000000004004c8 <+4>: mov QWORD PTR [rbp-0x18],rdi
0x00000000004004cc <+8>: mov QWORD PTR [rbp-0x20],rsi
现在按照以下说明执行第一条语句int *temp = *a
。
0x00000000004004d0 <+12>: mov rax,QWORD PTR [rbp-0x18]
0x00000000004004d4 <+16>: mov rax,QWORD PTR [rax]
0x00000000004004d7 <+19>: mov QWORD PTR [rbp-0x8],rax
现在到了决定性的时刻,你的*a
?
- 它加载到
$rax
存储在[rbp - 0x18]
. $rdi
保存值的位置。它又保存第一个数组的第一个元素的地址。
- 通过使用存储到的地址执行另一个间接
$rax
获取 aQWARD
并将其加载到$rax
. 那么它会返回什么呢?它将返回一个QWARD
from 0x7fffffffe100
。这实际上将由保存在那里的两个四字节数量形成一个 8 字节数量。详细说明,
那里的记忆如下。
(gdb) x/2xw $rdi
0x7fffffffe100: 0x00000001 0x00000003
现在如果你拿一个QWORD
(gdb) x/1xg $rdi
0x7fffffffe100: 0x0000000300000001
所以你实际上已经被搞砸了。因为您正在获取不正确的边界。
其余代码可以用类似的方式解释。
现在为什么它在 32 位平台上有所不同?因为在 32 位平台中,本机指针宽度为 4 个字节。所以这里的情况会有所不同。您的语义不正确代码的主要问题源于整数类型宽度和本机指针类型的差异。如果两者都相同,您仍然可以解决您的代码。
但是你永远不应该编写假定本机类型大小的代码。这就是标准适用的原因。这就是你的编译器给你警告的原因。
从语言的角度来看,它的类型不匹配已经在前面的答案中指出,所以我不打算讨论。