多彩奇利国际线上娱乐 当前位置:首页>多彩奇利国际线上娱乐>正文

多彩奇利国际线上娱乐

发布时间:2018-10-24

原标题:学习一门新语言需要了解的基础-11 参数传递

学习一门新语言需要了解的基础-11 参数传递


本节内容

  • C参数复制,返回值
  • Go参数复制,返回值[付费阅读]
  • 优化模式对参数传递的影响[付费阅读]

我们上节说了函数调用的时候,首先函数是被线程执行的,这个线程要执行函数调用的话必须要有内存分配,内存分为两块,一块称为堆,一块称为栈。每个线程都会有自己的栈内存,栈内存是个大整块,调用的时候通过BP或者SP这两个寄存器来维持当前函数需要操作哪块内存,当你都操作完了以后,直接来调整BP或者SP寄存器的位置就可以把你所调用函数的所分配的栈桢空间释放掉。这个释放和在堆上释放是不一样的,因为这里释放后内存完全可以用来干别的事情。但是栈上的内存释放了以后那个内存还在,因为整个栈内存是个整体。这就是整个一大块,我们只不过就是调用时候通过两个寄存器来确定当前操作的时候在这一大块中操作哪一个区域,所以这是有很大区别的。

栈上内存用BP和SP来操作一整块内存的一个区域,用完之后把SP寄存器指回去,那块空间接下来调用其它函数时候进行复用。也就是你的搞明白,首先整个栈内存是一大块,是一整块,它没有说释放某块内存这样的一个说法。除非就有一种可能,就是把整个栈空间释放掉。

但是在堆上我们申请了一段内存,我们不用的时候可以把这块释放掉,因为我们在一个函数里面可以多次调用堆内存分配,然后可以分块释放。栈上没有内存释放这种说法。所以这就有个好处在栈上只需要调整两个寄存器BP、SP的位置就可以来决定这个内存当前是正在用或者说是可以被其它函数调用来覆盖掉。所以有这样一个说法,我们尽可能把对象分配到栈上。因为不需要执行释放操作。因为现场恢复时候只需要调整寄存器,那块内存就变得可复用状态了。但是在堆上你必须要释放,在栈上的效率显然是要高很多。而且栈这种特性就决定了它是有顺序操作的机制,所以它的效率就高很多。那么你在堆上分配时候要么手动释放要么有垃圾回收器来释放。垃圾回收器只管堆上的东西,栈上它是不管的。所以我们在栈上分配的时候,一是效率比较高,第二不会给垃圾回收器带来负担。

我们现在知道了每个函数调用的时候都会在栈上用两个寄存器划出一个区域来存储参数、返回值、本地变量类似这样的一些内容,这个区域我们称之为叫栈桢。那么多级调用时候所有的栈桢串在一起我们称之为调用堆栈。

那么究竟有哪些东西分配在栈上呢?比如说在函数里面x=10这种东西默认情况下肯定分配在栈上,*p=malloc()这个时候这东西在堆上还是在栈上呢?这时候实际上有两种东西,第一malloc的确是在堆上分配一个内存空间,这个内存空间分配完了之后得有个指针指向它。所以这地方严格来说有两个东西。第一个是堆上的内存块,还有个指针变量,这个指针变量可能是在栈上。指针本身是个标准的变量,它是有内存空间的,它没有内存空间的话地址怎么写进去,因为我们知道我们可以给指针赋值的,能给它赋值肯定是个对象,没有对地址赋值这样一个说法,地址肯定不能赋值的。所以指针和地址不是一回事。指针是一个标准的变量,里面存了地址信息而已。所以指针和地址完全不是一个东西,不要混合一谈。复合对象是不是分配在堆上也未必,这得看不同的语言对复合对象怎么定义了,比如说结构体算不算复合对象,数组算不算复合对象,默认情况在栈上分配没有问题,当然里面可以用指针指向堆上其它的地址。你别忘了当里面有指针指向别的对象的时候,这个指针本身它依然是在栈上的。比如说我有个复合对象结构体,有个x和一个指针p,指针p指向堆上一个内存对象,堆上内存对象不属于结构体本身的内容。因为只有这个指针属于这个结构体,至于这个指针指向谁和这个结构体没关系,这结构体本身是完全分配在栈上的。只不过结构体里面有个东西记录了堆上的地址信息而已。

接下来了解对象参数究竟怎么去分配的。

C参数复制,返回值

$ cat test.c
#include <stdio.h>
#include <stdlib.h>

__attribute__((noinline)) void info(int x)
{
    printf("info %d
", x);
}

__attribute__((noinline)) int add(int x, int y)
{
    int z = x + y;
    info(z);
    
    return z;
}

int main(int argc, char **argv)
{
    int x = 0x100;
    int y = 0x200;
    int z = add(x, y);

    printf("%d
", z);

    return 0;
}

三个变量,x、y、z

$ gcc -g -O0 -o test test.c #编译,去掉优化

使用gdb调试

$ gdb test
$ b main #符号名上加上断点,mian函数加上断点
$ r #执行,这时在main函数上中断了
$ set disassembly-flavor intel #设置intel样式
$ disass #反汇编

main函数不是你程序真正的入口,而是你用户代码的入口,因为大部分程序在执行main函数之前它会有其它初始化的操作。

Dump of assembler code for function main:
   0x0000000000400570 <+0>: push   rbp
   0x0000000000400571 <+1>: mov    rbp,rsp
   0x0000000000400574 <+4>: sub    rsp,0x20 #给main函数分配了16进制20字节的栈桢空间。
   0x0000000000400578 <+8>: mov    DWORD PTR [rbp-0x14],edi
   0x000000000040057b <+11>:    mov    QWORD PTR [rbp-0x20],rsi
=> 0x000000000040057f <+15>:    mov    DWORD PTR [rbp-0xc],0x100
   0x0000000000400586 <+22>:    mov    DWORD PTR [rbp-0x8],0x200
   0x000000000040058d <+29>:    mov    edx,DWORD PTR [rbp-0x8]
   0x0000000000400590 <+32>:    mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000400593 <+35>:    mov    esi,edx
   0x0000000000400595 <+37>:    mov    edi,eax
   0x0000000000400597 <+39>:    call   0x400548 <add>
   0x000000000040059c <+44>:    mov    DWORD PTR [rbp-0x4],eax
   0x000000000040059f <+47>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004005a2 <+50>:    mov    esi,eax
   0x00000000004005a4 <+52>:    mov    edi,0x40064d
   0x00000000004005a9 <+57>:    mov    eax,0x0
   0x00000000004005ae <+62>:    call   0x400400 <printf@plt>
   0x00000000004005b3 <+67>:    mov    eax,0x0
   0x00000000004005b8 <+72>:    leave  
   0x00000000004005b9 <+73>:    ret    
End of assembler dump.

我们看到所有的空间都是基于BP寄存器的寻址。

Go参数复制,返回值[付费阅读]

$ cat test.go
package main

import "log"

func info(x int) {
    log.Printf("info %d
", x)
}

func add(x, y int) int {
    z := x + y
    info(z)

    return z
}

func main() {
    x, y := 0x100, 0x200
    z := add(x, y)

    println(z)
}
$ go build -gcflags "-N -l" -o test test.go
$ gdb test
$ b mian.main #打断点
$ r #运行
$ set disassembly-flavor intel #设置intel样式
$ disass #反汇编
=> 0x0000000000401140 <+0>: mov    rcx,QWORD PTR fs:0xfffffffffffffff8
   0x0000000000401149 <+9>: cmp    rsp,QWORD PTR [rcx+0x10]
   0x000000000040114d <+13>:    jbe    0x4011a9 <main.main+105>
   0x000000000040114f <+15>:    sub    rsp,0x30 #首先为这个空间分配了48字节栈桢空间
   0x0000000000401153 <+19>:    mov    QWORD PTR [rsp+0x28],0x100
   0x000000000040115c <+28>:    mov    QWORD PTR [rsp+0x20],0x200
   0x0000000000401165 <+37>:    mov    rax,QWORD PTR [rsp+0x28]
   0x000000000040116a <+42>:    mov    QWORD PTR [rsp],rax #x参数复制到rsp+0位置
   0x000000000040116e <+46>:    mov    rax,QWORD PTR [rsp+0x20]
   0x0000000000401173 <+51>:    mov    QWORD PTR [rsp+0x8],rax #y参数复制到rsp+8位置
   0x0000000000401178 <+56>:    call   0x4010f0 <main.add>
   0x000000000040117d <+61>:    mov    rax,QWORD PTR [rsp+0x10]
   0x0000000000401182 <+66>:    mov    QWORD PTR [rsp+0x18],rax
   0x0000000000401187 <+71>:    call   0x425380 <runtime.printlock>
   0x000000000040118c <+76>:    mov    rax,QWORD PTR [rsp+0x18]
   0x0000000000401191 <+81>:    mov    QWORD PTR [rsp],rax
   0x0000000000401195 <+85>:    call   0x425a10 <runtime.printint>
   0x000000000040119a <+90>:    call   0x4255b0 <runtime.printnl>
   0x000000000040119f <+95>:    call   0x425400 <runtime.printunlock>
   0x00000000004011a4 <+100>:   add    rsp,0x30
   0x00000000004011a8 <+104>:   ret    
   0x00000000004011a9 <+105>:   call   0x44b160 <runtime.morestack_noctxt>
   0x00000000004011ae <+110>:   jmp    0x401140 <main.main>
|---------+---sp
| 100     |
|---------|---+8
| 200     |
|---------|--+10
|         |
|---------|--+18
|         |
|---------|--+20
| y=200   |
|---------|--+28
| x=100   |
|---------|--+30

go语言所有东西都是基于SP做加法,因为在go语言里它不使用BP寄存器,它把BP寄存器当作普通寄存器来用。它不用BP寄存器来维持一个栈桢,它只用SP指向栈顶就可以了,这跟它的内存管理策略有关系。

在add函数执行之前,首先做了参数复制,就是说函数调用时候参数是被复制的,理论上所有参数都是复制的,传指针复制的是指针而不是指针指向的目标,指针本身是被复制的,通过这个代码我们就看到复制过程。

.......

优化模式对参数传递的影响[付费阅读]

这个系列的每篇文章有大半篇幅内容属于付费阅读。提供微信支付或支付宝支付打赏50元备注留言手动提供付费文章访问密码。

当前文章:http://www.radiokey.biz/content/2018-10-13/62252.html

发布时间:2018-10-24 00:10:29

sunbet9.95 老虎机最新 绑定银行卡自助彩金的老虎机 大发老虎机手机下载安装 老虎机游戏美女 水果老虎机更名网址6 mg老虎机注册送现金 pt老虎机注册体验金提现 传奇娱乐 

71918 33213 71764 67931 90041 2371288994 42821 49202

责任编辑:文陵