C和C++的精髓是指针,指针的本质是内存地址。 普通变量或者类的成员变量都有内存地址,普通变量或者类的成员变量能像指针变量一样使用指针操作。(指针的*操作和->操作)。
1.定义一个普通的变量a,fun1()函数:借用指针变量p,通过*操作,给变量a给赋值。 代码:
int a = 0;
void fun1()
{
int* p = &a;
*p = 1;
}
底层汇编:
a:
.zero 4
fun1():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], OFFSET FLAT:a
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 1
nop
pop rbp
ret
2.fun1()函数:不借助指针变量,直接对变量a的地址进行*操作。 代码:
void fun2()
{
*(int*)&a = 1;
}
底层汇编:
fun2():
push rbp
mov rbp, rsp
mov DWORD PTR a[rip], 1
nop
pop rbp
ret
总结:上述可以看出,普通变量也能做指针操作,只要知道变量a的地址,就能进行指针的*操作。还省掉了指针变量的开销,更简单,直接。
3.修改fun2(),直接使用a的地址。
int a = 0;
void fun2()
{
*(int*)&0x1234 = 1;
}
疑问:这还是读写变量吗?这不是读写内存吗?
4.fun3(),直接给a赋值,对比汇编指令。 代码:
void fun3()
{
a = 1;
}
底层汇编:
fun3():
push rbp
mov rbp, rsp
mov DWORD PTR a[rip], 1
nop
pop rbp
ret
总结:对比fun2()和fun3(),发现其底层汇编一模一样,变量读写都是对变量地址的指针*操作。变量不过是内存地址的别名。
5.不借助指针变量,使用->指针操作,对类成员变量进行赋值操作。 代码:
class B{
public:
int x;
}b;
void fun4()
{
b.x = 1;
}
void fun5()
{
(&b)->x = 1;
}
底层汇编:
b:
.zero 4
fun4():
push rbp
mov rbp, rsp
mov DWORD PTR b[rip], 1
nop
pop rbp
ret
fun5():
push rbp
mov rbp, rsp
mov DWORD PTR b[rip], 1
nop
pop rbp
ret
总结:从上述对成员变量的赋值操作,fun4()和fun5()底层汇编是一致的。
总结 1.指针操作不仅仅是对指针变量而言,对普通变量,立即数都可以使用指针操作(*、->)。 2.计算机的世界里面,万物皆有地址,万物皆是指针。可以通过变量名,或者函数接口读/写变量,也可以通过指针操作随时随地的读写变量。除了0x1234,指针操作可以读写任意的内存地址。除了内存管理单元MMU。 3.上述代码,仅仅通过一个栈变量a的地址,配合指针操作就可以看出整个函数的调用轨迹。 4.但是指针变量也有弊端,你得到了x私有变量的内存地址,就意味着你可以通过指针操作不受限制的读写该变量。那成员变量和普通变量的访问就被误解了。
|