最近写函数突然联想到很多问题,比如我们一般说函数传参传的是值不是地址,想要通过函数来修改一个参数的值我们得用指针,然后我想从汇编的角度来理解,就用了非常经典的swap然后反汇编。但是感觉汇编代码不管是传值还是传指针感觉差不多啊。大佬们能不能解释一下,明明函数里的堆栈使用完都会释放嘛,为什么传指针就可以修改呢
#include<stdio.h>
int main()
{
int a=1;
int b=2;
swap(a,b);
}
void wap(int a,int b)
{
int t=a;
a=b;
b=t;
}
汇编:
main:
10 .LFB0:
11 .cfi_startproc
12 lea ecx, [esp+4]
12 lea ecx, [esp+4]
13 .cfi_def_cfa 1, 0
14 and esp, -16
15 push DWORD PTR [ecx-4]
16 push ebp
17 .cfi_escape 0x10,0x5,0x2,0x75,0
18 mov ebp, esp
19 push ecx
20 .cfi_escape 0xf,0x3,0x75,0x7c,0x6
21 sub esp, 20
22 mov DWORD PTR [ebp-16], 1
23 mov DWORD PTR [ebp-12], 2
24 sub esp, 8
25 push DWORD PTR [ebp-12]
26 push DWORD PTR [ebp-16]
27 call swap
28 add esp, 16
29 sub esp, 4
30 push DWORD PTR [ebp-12]
31 push DWORD PTR [ebp-16]
32 push OFFSET FLAT:.LC0
33 call printf
34 add esp, 16
35 mov eax, 0
36 mov ecx, DWORD PTR [ebp-4]
37 .cfi_def_cfa 1, 0
38 leave
39 .cfi_restore 5
40 lea esp, [ecx-4]
41 .cfi_def_cfa 4, 4
42 ret
43 .cfi_endproc
44 .LFE0:
45 .size main, .-main
46 .globl swap
47 .type swap, @function
48 swap:
49 .LFB1:
50 .cfi_startproc
51 push ebp
52 .cfi_def_cfa_offset 8
53 .cfi_offset 5, -8
54 mov ebp, esp
55 .cfi_def_cfa_register 5
56 sub esp, 16
57 mov eax, DWORD PTR [ebp+8]
58 mov DWORD PTR [ebp-4], eax
59 mov eax, DWORD PTR [ebp+12]
60 mov DWORD PTR [ebp+8], eax
61 mov eax, DWORD PTR [ebp-4]
62 mov DWORD PTR [ebp+12], eax
63 nop
64 leave
65 .cfi_restore 5
66 .cfi_def_cfa 4, 4
nop
leave
ret
我觉得引用和指针也差不多,,,
#include<stdio.h>
int main()
{
int a=1;
int b=2;
swap(&a,&b);
}
void wap(int *a,int *b)
{
int t=*a;
*a=*b;
*b=t;
}
汇编:
main:
.LFB0:
.cfi_startproc
lea ecx, [esp+4]
.cfi_def_cfa 1, 0
and esp, -16
push DWORD PTR [ecx-4]
push ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
mov ebp, esp
push ecx
.cfi_escape 0xf,0x3,0x75,0x7c,0x6
sub esp, 20
mov eax, DWORD PTR gs:20
mov DWORD PTR [ebp-12], eax
xor eax, eax
mov DWORD PTR [ebp-20], 1
mov DWORD PTR [ebp-16], 2
sub esp, 8
lea eax, [ebp-16]
push eax
lea eax, [ebp-20]
push eax
call swap
add esp, 16
mov edx, DWORD PTR [ebp-16]
mov eax, DWORD PTR [ebp-20]
sub esp, 4
push edx
push eax
push OFFSET FLAT:.LC0
call printf
add esp, 16
mov eax, 0
mov ecx, DWORD PTR [ebp-12]
xor ecx, DWORD PTR gs:20
je .L3
call __stack_chk_fail
.L3:
mov ecx, DWORD PTR [ebp-4]
.cfi_def_cfa 1, 0
leave
.cfi_restore 5
lea esp, [ecx-4]
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.globl swap
.type swap, @function
swap:
.LFB1:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
sub esp, 16
mov eax, DWORD PTR [ebp+8]
mov eax, DWORD PTR [eax]
mov DWORD PTR [ebp-4], eax
mov eax, DWORD PTR [ebp+12]
mov edx, DWORD PTR [eax]
mov eax, DWORD PTR [ebp+8]
mov DWORD PTR [eax], edx
mov eax, DWORD PTR [ebp+12]
mov edx, DWORD PTR [ebp-4]
mov DWORD PTR [eax], edx
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
你那个不是引用,而是传指针
从汇编的角度看,当然是差不多的,都是传了一个数字嘛。
只是这个数字代表的含义不同,前者是具体的值,后者是一个地址。(当然,从指针的指针int **的角度看,int 也是一个具体的值,站在 int **的角度看,int **也是具体的值)
因为int *是指针,如果你把它当作具体的值看待,那么和int是一样的。
swap(int * a, int * v)
{
int t;
a = &t;
b = a;
a = t;
}
这个代码就和你
swap(int a, int b)
一样,虽然修改了a, b,但是不会有效果。
而swap(int * a, int * b)
{
int t;
t = *a;
*a = *b;
*b = t;
}
之所以能成功,是因为它并没有修改a,b,而是修改的是a b指向的对象
你仔细对比这两个程序,就明白了。
简单总结下就是,无论是int 还是int *,编译器产生的传参数代码(也就是push)是差不多的。
任何时候修改函数的参数,都不会作用到原来的调用者(除非用c++的引用)
这里说的修改参数是指参数本身。
int * a,对它的修改是 a = xxx,也就是让它指向另一个对象。
*a = xxx,并没有修改a,修改的是a指向的对象。这其实和函数参数没关系。
这就看你对指针和引用的区别了,从语言层面看,引用好像没有分配内存,只是被引用变量的别名,但是从汇编底层实现看,其实引用和指针是一样的,所以
从汇编看你觉得引用和指针没什么区别是对的,其实引用&p = a; 就相当于 *p = &a,只是语言层面,编译器做了一层透明,对我们开发人员来说是透明的,
就可以理解为引用就是别名。
从C言角度来分析,函数参数都是在栈里分配一个临时变量存着传过来的值,如果传过来是指针,变量存放着的是一个指针,指向传过来的参数地址;如果传过来
是引用,变量存放着的是一个地址,这个地址是传过来的参数地址。
指针:是一个变量,可以指向任何变量的地址
地址:是一个常量,是唯一的,不可修改的
所以是不同的,但是作用是相同的,因为引用是直接操作,指针是间接操作,要经过指针指向的地址。从内存角度来看,都是相同的,从速度来看,引用优于指针。