日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

通过 汇编了解C语言 指针 悬垂指针概念

發布時間:2024/3/12 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通过 汇编了解C语言 指针 悬垂指针概念 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

棧是 高地址向低地址 遞增的結構,擁有后入先出的特點。
在程序運行時,程序段代碼是不可變的,所以 需要借助棧來動態的 取出存放計算結果.
每個程序 在運行時 都需要一個棧來協助存儲運行時的動態數據,C語言 對每次 調用(Call) 一個方法時,都會用 ebp 保存調用者的棧 位置 esp ,這樣每次 方法調用完畢了 將 esp 設置為上一個棧所在位置 就可以 恢復棧了 通俗的說 就像存檔點一樣 每一次 打怪前 存一下檔 只不過 不一樣的是 打完怪了 計算機里面 我們每次打完怪了 要恢復成存檔點 前的狀態,但是 打怪的成果 怎么辦呢? 通常C語言 通過eax 傳遞 返回參數 也就是 調用完子程序后,其他東西都沒有變化只有 eax 寄存器 可能保存返回的值 或返回值的指針。
了解了棧 我們 還得 了解 局部變量的
假設我們在一個方法里面 定義了 一個變量

int main(){ int a = 0; int b = *a; }

定義了 總要有個地方存放這個值吧 ,得知道他 放在哪 才能知道 去哪找到它 并 使用它。

在 C語言里面 局部變量 是由作用域的,作用域 就是 被{} 鎖包裹的范圍內,
當我們 在main 函數內 聲明一個變量,那么它的作用域 就 限制于 main 生命周期 當然main 是整個程序的運行生命周期 所以 main 函數內部的 也就是全局變量 ,
就像上圖 所展示的 一樣 局部變量會被分配在 棧上。

棧方法調用

void swap(int * a, int *b) { int c; c = *a; *a = *b; *b = c; }int main(){int a, b;a=16;b=32;swap(&a, &b);return (a - b); }

上面 是一個 普通的C語言 程序,但是 通過匯編 來分析上面的程序 可以加深我們對計算機內部原理的認識,同時 也可以更好的 了解棧 指針在程序里面到底是怎樣的存在.


我們都知道CPU的計算器 就那么幾個,然而在運算時 那么多復雜的計算 寄存器是不夠的用的 所以 我們要借助內存可以是棧 把 寄存器的值 暫時 保存一下,然后再去干其它計算。然后用到 這個值的時候再 恢復出來使用。這樣 即使靠 幾個寄存器 就能完成各種計算任務, 所以 棧就是用來 保存運行時 數據的一個結構。

上面的程序 我們定義了 a b 所以 我們需要在棧里申請空間去存放它,esp 指向的是棧頂 32 位下 棧每次 壓入4個字節 往下 壓棧 就是 - 4 往上 出棧 就是 + 4。 當我們視試圖調用 一個方法 時 ,代碼是沒有狀態的 它并不知道 誰調用了它,但是 調用一個方法一定是要執行 一定的運算 然后給調用者 答案 啊,那怎么辦呢 我們說過棧是用來存儲運行時數據的地方,那么 我調用后 把 調用者 記錄下 在棧內 計算完成后 我到棧里面找到調用者 返回給它不就行了嘛,所以當Call 被執行后 CPU會把 調用者的棧ip(near調用)記錄在棧內。 當我們 要掉用 一個棧里面的數據時 我們希望棧方法調用完成后 要把 被調用方法產生的 運算時 棧數據 清除 我們只要結果,要不然 每調用一個方法 就要消耗一點棧內存 這多大內存都挺不住啊,。
那 其實只要記錄下,方法調用前 棧的位置 調用完成后 咱們就不要管后面的數據了 當做垃圾數據覆蓋掉就好了,所以每次調用方法 記錄下 棧的位置 方法調用完成后 把棧的位置 恢復下就好了。
所以 就有了常見的

保存 棧信息

push ebp ;保存上一個棧的位置
mov ebp,esp ;保存當前棧的位置

恢復棧信息:

mov ebp,esp ;保存 esp的值
pop ebp ;恢復上一個棧的位置

當我們 執行完后 只需要 恢復上一個棧的位置,這樣 我們 就可以在子程序中使用 棧 當程序運行完畢后只要恢復成 上一個調用棧的地址,有點像鏈表的意思,每個鏈表掛一個方法,執行完成后 返回上一個鏈表所在位置。

懸垂指針

int * getstackval(int * args) {(*args) = 1;int c = 0;return &c; }int main(){int m = 0;//當 getstackval() 運行完成后 棧就會被回收 之后函數運行 運行時數據 就可能覆蓋 指針指向的值int * val = getstackval(&m);//每次打印出不一樣的值printf("\n%d\n",val);//被子函數 修改printf("\n%d\n",m); }

由于 局部變量是 存放在 棧上的,假設A方法 內部聲明了一個局部變量 然后 返回指向這個 變量的指針 ,但是 當這個方法 運行完畢后 這個被分配的棧內存空間 就會被回收 再次使用 那么 我們返回的這個指針 指向地址 如果被其他方法 給 覆蓋掉的話,那么 這個值 就毫無異議了,但在像GO 語言 會使用 變量逃逸分析,如果這個值 在作用域 范圍外 被使用 那么 它會捕獲 這個變量 就會 丟到堆里面去 堆比 棧 要慢很多。
而在Rust 中 就顯示的 引入了 聲明周期 這個概念 如果你 返回的指針 的生命周期 要大于等于 傳入參數的生命周期 否則編譯器 就會報錯。


上圖 演示了當call
一個函數棧發生的變化 ,可以看到 當棧return 后 棧 的esp
,ebp 變成了 第一幅圖的 一開始的值,并且 返回的指針 被賦予val 其實是 指向運行時棧的地址,當方法返回后 這塊棧 就可能會被后面的方法重新利用所以,就會產生 懸垂指針 得到不可預料的值。

從以上分析可知,C 語言在調用函數時是在堆棧上臨時存放被調函數參數的值,即 C 語言是傳值類語 言,沒有直接的方法可用來在被調用函數中修改調用者變量的值。因此為了達到修改的目的就需要向函數 傳遞變量的指針(即變量的地址)。

總結

以上是生活随笔為你收集整理的通过 汇编了解C语言 指针 悬垂指针概念的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。