1. 程式人生 > >free函式在作業系統記憶體中的實現

free函式在作業系統記憶體中的實現

昨天在寫單項鍊表的時候碰到這樣一個問題。

我一次性malloc十個單位節點的記憶體空間出來賦值給L, 現在我想一次性刪除從第3個到第6個節點,我是這麼做的:

1.將第六個節點的next指標指向NULL

2.將L指標指向第三個單位空間的地址,再free(L)。

等到把程式碼寫完之後,才發現其中的問題:這裡我的free(L)用的對嗎?

編譯運行了一下才發現了問題所在。

為了清楚地看到這個錯誤,將這個問題簡化出來,請看下面的程式碼:

[cpp] view plaincopyprint?
  1. #include <stdio.h>
  2. #include <malloc.h>
  3. int main()  
  4. {  
  5.     char *p = (char*)malloc(sizeof(char) * 10);  
  6.     char *q = p;  
  7.     int i = 0;  
  8.     for (; i < 10; i++)  
  9.         printf("%08X\n", q + i);  
  10.     q = q + 2;  
  11.     free(q);  
  12.     return 0;  
  13. }  

編譯執行之後結果如下:

0863E008
0863E009
0863E00A
0863E00B
0863E00C
0863E00D
0863E00E
0863E00F
0863E010
0863E011
*** glibc detected *** ./free: free(): invalid pointer: 0x0863e00a

 ***
======= Backtrace: =========
/lib/libc.so.6[0x4481d9f2]
./free[0x8048487]
/lib/libc.so.6(__libc_start_main+0xf3)[0x447be6b3]
./free[0x8048391]
======= Memory map: ========
......

已放棄(吐核)

 在linux下面可以看到free這句程式碼是不能被執行的。

那麼為什麼執行會出錯呢?

這裡首先要談談malloc和free這兩個函式的用法了。

首先我們要知道的一個原則就是:malloc 和free是成對使用的。

你可能會奇怪為什麼malloc指定了記憶體分配的長度,而free的時候沒有指定釋放的記憶體長度。

因為是成對使用的,所以滿足下面兩個原則:

1.我們free的指標必須是malloc出來返回給你的指標。

2. 所以,free的長度是你malloc要求分配的長度。

看到這裡可能有人會問:你是怎麼得出這兩個結論的呢?

好吧,現在我們就來深究一下,free函式在作業系統記憶體中究竟是怎麼實現的。

但是畢竟自己實力有限,還是菜鳥一枚,如果讓我閱讀free 和malloc 的原始碼,肯定是不現實的,貌似有1500多行。。。。

CSDN下面這個帖子講了一點東西:

http://topic.csdn.net/u/20090714/07/9ee25af0-8ecf-42e9-9ae5-ec2bdd1f72b8.html

自己摘除了一點基本資訊:

malloc/free 是作業系統提供的介面
不同的系統可能使用不同的庫
介面形式相同,但是實現方式可能不同
這主要取決於作業系統記憶體管理模式

VC使用的CRT,而GCC使用的是libc  

下面這個帖子也講了一點東西

http://topic.csdn.net/u/20090714/07/f7ddd06a-917b-42d0-9c99-ac4deac08904.html?21758

裡面有幾段比較經典的話。雖然正確性還有待考證。摘錄如下:

1. 

free只是釋放了malloc所申請的記憶體,並不改變指標的值;
2)、由於指標所指向的記憶體已經被釋放,所以其它程式碼有機會改寫其中的內容,相當於該指標從此指向了自己無法控制的地方,也稱為野指標;
3)、為了避免失誤,最好在free之後,將指標指向NULL。

2.

核心通過一個紅黑樹來記錄了空閒的記憶體,malloc就是從樹中查詢一塊大小適合的記憶體並把地址給你,然後把這個節點從樹中摘除,避免被別人分配到產生衝突。這個記憶體現在歸你一個人用了。
free函式是把你的這個記憶體重新放回到紅黑樹中,讓別人可以申請到這個記憶體。從邏輯上來說,你現在不能在使用這個記憶體了,因為它已經不屬於你。但是系統的實現上目前沒有做到,所以你還是能訪問這個地址。
另外,系統也不會幫你覆蓋記憶體中的資料,因為做這一個操作浪費時間,沒有必要。

打一個簡單的比方。你租了一套房子,後來租期到了,房子回到房東手裡,或者又轉租給別人。但是你拿著原來的鑰匙還是能進入那套房子,雖然這個是不合法的。

3.

應該確切講是不變的。記憶體管理多數是通過一個MBC連結串列實現的,及你實際分配的記憶體空間為:(nSize + 3) / 4 * 4 + sizeof(MBC)的大小,在malloc之後,系統程式實際返回的是分配的MBC地址+sizeof(MBC),釋放記憶體時,free所做的第一個動作是ptr - sizeof(MBC)得到實際的MBC塊,在這個MBC塊中包含了該記憶體的大小,記憶體MBC鏈的指標等資訊;所以,如果你使用了超出實際分配記憶體大小的空間,會造成整個MBC鏈的混亂,最直接表現是程式在free時在另一個不相關的地方出現了異常;所以您可以看出來,在執行了free之後,該塊記憶體並沒有改變,即使該塊記憶體相鄰記憶體為空,而發生了記憶體塊的合併,您剛才使用的記憶體空間也沒有發生改變(你看到的),改變的是MBC連結串列;
詳細的記憶體管理程式您可以參考一些庫,如dlmalloc,這個庫除了使用了MBC鏈外,還是用了沙箱機制;事實上早期DOS的記憶體管理程式也是使用的MBC鏈;

4.

1)我注意到之上的回覆多數是基於系統端的,(如紅黑樹,我在Understand Linux kernal中還真沒有聽說過紅黑樹,最後居然在國內網站找到了 ---- 不知道是不是隻是國內學術名詞,是核心的記憶體管理);系統記憶體分配涉及屬性、記憶體頁面以及是否有MCU;但是注意一點malloc和free並不是直接使用系統記憶體管理程式,在多數Linux程式中malloc和free一般是通過標準庫,及我說過的DL記憶體管理程式實現的;因此,核心的記憶體管理和我們在應用層面的記憶體管理(如malloc/free)不要混為一談;
2)DL的記憶體管理從效能上要比直接使用系統的記憶體管理效能要高,如經過我們實測:在隨機大小、隨機順序申請釋放 ---- 目的在製造最差情況,DL記憶體管理程式是直接使用系統的3~3.9倍,考慮使用realloc,DL記憶體管理程式是系統的10~43.35倍;所以,實際情況下,我們並不是直接使用系統記憶體管理 ---- 只是我們並不知道。

唉。。。涉及到作業系統的記憶體管理跟資料結構的東西,自己也沒有學過,只能先這樣理解下。

相關推薦

free函式作業系統記憶體實現

昨天在寫單項鍊表的時候碰到這樣一個問題。 我一次性malloc十個單位節點的記憶體空間出來賦值給L, 現在我想一次性刪除從第3個到第6個節點,我是這麼做的: 1.將第六個節點的next指標指向NULL 2.將L指標指向第三個單位空間的地址,再free(L)。 等

除錯經驗——使用自定義函式在Oracle實現類似LISTAGG函式的行轉列(字串連線)功能

問題描述: LISTAGG函式是一個很實用的函式,但僅在Oracle 11.2以後的版本中才有。 生產環境中有個資料庫是Oracle 11.1,需要行轉列,但並不能使用LISTAGG函式。 解決方法: 參考以下文章: https://oracle-base.com/artic

String類在記憶體實現原理詳解

(1) ==  比較引用型別比較的是地址值是否相同 equals:比較引用型別預設也是比較地址值是否相同,而String類重寫了equals()方法,比較的是內容是否相同。 (2) 區分下面兩種語句在記憶體中的實現: <span style="font-size:14

c語言如何操作記憶體(資料型別、函式記憶體解析簡介)

1、用變數名來訪問記憶體 (c語言對記憶體地址的封裝:資料型別、函式名) ---【直接訪問記憶體(使用地址)】              資料型別:表示一個記憶體格子的長度和解析方法。(記憶體編址的單位是一個位元組)               (int *) 0; 

C++成員函式記憶體的儲存方式

        用類去定義物件時,系統會為每一個物件分配儲存空間。如果一個類包括了資料和函式,要分別為資料和函式的程式碼分配儲存空間。按理說,如果用同一個類定義了10個物件,那麼就需要分別為10個物件

函式實現過程記憶體的壓棧和出棧

    關於函式在呼叫過程中的壓棧和出棧問題在學習的時候就感覺很經典,對程式的把握可以提升一個臺階。    一.首先讓我們寫出一個簡單的函式。(我是在vc6.0中實現,並不表示vs編譯器底下不可以實現)

C#或unity實現正弦函式

C#或unity中實現正弦函式 本類用於第一,需要繪製一條正弦曲線的朋友;第二,需要根據正弦曲線控制物體運動的朋友;裡面都有註釋,程式碼如下: unity中使用的程式碼: public class Curvy_Sin { /// <summary> /// 週期

pytorch系列 -- 9 pytorch nn.init 實現的初始化函式 uniform, normal, const, Xavier, He initialization

本文內容: 1. Xavier 初始化 2. nn.init 中各種初始化函式 3. He 初始化 torch.init https://pytorch.org/docs/stable/nn.html#torch-nn-init 1. 均勻分佈 torch.nn.init.u

sublime實現Ctrl+滑鼠左鍵跳轉到定義函式的地方

在寫看一份Python程式碼的時候,可以使用notepad++,或者spyder,或者pycharm  但是這都有一定的不方便,notepad++能夠識別Python程式碼,但是不能支援Ctrl+滑鼠左鍵跳轉到函式定義的地方,而且當函式是在另一個py檔案中定義的時候,notepad++

Oracle和MySQL的不同函式的等價作用(在MySQL實現Rank高階排名函式)重點推薦

mysql與Oracle的區別 https://blog.csdn.net/qq686867/article/details/79355760 mysql試題 https://zhuanlan.zhihu.com/p/38047497 https://blog.csdn.net/Br

大資料平臺資源控制在不同作業系統上的實現

大資料平臺中資源控制在不同作業系統上的實現 在大資料迅速發展的今天,很大一部分支援來自於底層技術的不斷髮展,其中非常重要的一點就是系統資源的管理和控制,大資料平臺的核心就是對資源的排程管理,在排程和管理之後如何對這些資源進行控制便成了另一個重要的問題。大資料系統中使用者成千上萬的作業程序

定義棧的資料結構,請在該型別實現一個能夠得到棧所含最小元素的min函式(時間複雜度應為O(1))。

import java.util.Stack; public class Solution { private Stack<Integer> min_stack=new Stack<Integer>(); private Stack<Integer&

芭蕉樹上第十六根芭蕉-- QtUi名字空間以及setupUi函式的原理和實現

用最新的QtCreator選擇GUI的應用會產生含有如下檔案的工程 下面就簡單分析下各部分的功能。 .pro檔案是供qmake使用的檔案,不是本文的重點【不過其實也很簡單的】,在此不多贅述。 所以呢,還是從main開始, #include <

pycharm實現簡單的sin和cos函式曲線圖

1.需要下載相應的package,主要就是numpy和matplotlib;具體程式碼如下: #!/usr/bin/env python # encoding: utf-8 ''' @author: yangxin @contact: [email protect

類成員的可訪問性(不管怎麼設計,實現某一個類在記憶體只能呼叫一次)單態設計模式

為了控制建立物件的個數,需要收回建立物件的權利,下面想辦法設定Teacher為記憶體中唯一物件,在Text中建立並使用Teacher; Teacher package cn.net.sdkd.cise; public class Teacher { pri

Python 3實現cmp()函式的功能

本文由荒原之夢原創,原文連結:http://zhaokaifeng.com/?p=1088 cmp() 函式是Python 2中的一個用於比較兩個列表, 數字或字串等的大小關係的函式, 在Python 3中已經無法使用這個函數了: >>> a = [1, 2,

定義棧的資料結構,請在該型別實現一個能夠得到棧所含最小元素的min函式(時間複雜度應為O(1))

/** 思路:利用兩個棧來實現,一個主棧正常壓棧出棧,一個輔助棧用來儲存主棧所有值的最小值, 壓棧時,當壓入的值比輔助棧棧頂值大時,主棧正常壓棧,輔助棧不壓棧,小於等於二者都壓棧; 出棧時,當主棧和輔助棧棧頂元素不相等時,主棧正常出棧,輔助棧不出棧。 */ class Sol

Qt 學習之路 2(19):事件的接受與忽略(當重寫事件回撥函式時,時刻注意是否需要通過呼叫父類的同名函式來確保原有實現仍能進行!有好幾個例子。為什麼要這麼做?而不是自己去手動呼叫這兩個函式呢?因為我們無法確認父類的這個處理函式有沒有額外的操作)

版本: 2012-09-29 2013-04-23 更新有關accept()和ignore()函式的相關內容。 2013-12-02 增加有關accept()和ignore()函式的示例。 上一章我們介紹了有關事件的相關內容。我們曾經提到,事件可以依情況接受和忽略。現在,我們就

node之實現一個隨專案啟動將資料庫配置載入到記憶體

乾貨,直接上程式碼: 專案結構: 2.建立此快取介面,將快取封裝到一個物件中:dataCfg。 3.上圖中快取物件dataCfg = await getDataBaseCfg()是將這個方法封裝到了工具類中,如下圖: 4.在app.js中應用,啟動的時候會

作業系統虛擬記憶體的四種典型頁替換演算法(OPT,LRU,FIFO,Clock)

 頁面置換:在地址對映過程中,若在頁面中發現所要訪問的頁面不再記憶體中,則產生缺頁中斷(page fault)。當發生缺頁中斷時作業系統必須在記憶體選擇一個頁面將其移出記憶體,以便為即將調入的頁面