學習一門新語言需要瞭解的基礎-11 參數傳遞

来源:http://www.cnblogs.com/lyj/archive/2017/10/30/foundation_11_args.html
-Advertisement-
Play Games

本節內容 - C參數複製,返回值 - Go參數複製,返回值 - 優化模式對參數傳遞的影響 ...


本節內容

  • 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\n", 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\n", 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\n", 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元備註留言手動提供付費文章訪問密碼。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、摘要 一說到ADO.NET大家可能立刻想到的就是增、刪、改、查(CRUD)操作,然後再接就想到項目中的SQLHelper。沒錯本課分享課阿笨給大家帶來的是來源於github上開源的DAO資料庫訪問組件DBHelpers。如果您對本次分享《.NET輕量級DBHelpers數據訪問組件》課程感興趣的 ...
  • 業務背景 在稍微複雜點業務系統中,不可避免會碰到做定時任務的需求,比如淘寶的交易超時自動關閉訂單、超時自動確認收貨等等。對於一些定時作業比較多的系統,通常都會搭建專門的調度平臺來管理,通過創建定時器來周期性執行任務。如剛纔所說的場景,我們可以給訂單創建一個專門的任務來處理交易狀態,每秒輪詢一次訂單表 ...
  • 有了SpringMVC和Mabatis的整合後,一個初步WEB框架就形成了,但是完整的SSM框架還需要Spring的支持! 目前為止,我們所添加的配置文件有核心配置文件web.xml,springmvc的配置文件spring-servlet.xml以及Mabatis的配置文件mybatis-conf ...
  • 前言 今天在掘金看到一篇關於講解的Spring框架的文章,文章提到了牛客網的面試題。於是乎我就下載了牛客網app,發現面試題目很豐富。我就挑了java方面的面試題做了一下。10個題目為一組面試題,做完後,我發現了自己錯了好多,大多數都是基礎題。俗話說:基礎的深度決定未來的高度。我感覺自己必須要做一個 ...
  • 公司自己塔建的半自動化代碼生成項目 用法: 把csv文件放到src/resources/csv下,運行GenMain.java,生成的代碼在target/output下。 對GenMain.java進行適當的修改,可以控制生成的代碼有哪些,Pojo,Dao,Srv,ISrv,Action,PageJ ...
  • yii2在使用的時候,訪問控制器的時候,如果控制器的名稱是駝峰命名法,那訪問的url中要改成橫線的形式。例如: 最近在做某渠道的直連的時候,他們提供的文檔上明確指出介面的形式: 剛開始以為YII2中肯定有這樣的設置,然後就去google了下,發現都說不行,自己去看了下,果然,框架裡面直接是寫死的:( ...
  • 主頁面代碼 處理頁面代碼 ...
  • 1、實現介面的抽象類——適配器 即用了介面,又用了抽象類,關鍵是Window win=new MyWindow(); MyWindow子類並沒有直接實現Window介面,而是通過中間的抽象類建立了橋梁 2、代理公司的方法——功能更強大的包裝類 自己要錢的能力太弱小,通過強大的代理來完成要錢,包裝類 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...