最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解C語言的指針

 更新時(shí)間:2022年01月14日 15:56:02   作者:高性能架構(gòu)探索  
這篇文章主要為大家介紹了C語言的指針,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

起源

之前在知乎上看了一句話,指針是C的精髓,也是初學(xué)者的一個(gè)坎。換句話說,內(nèi)存管理是C的精髓,C/C++可以直接跟OS打交道,從性能角度出發(fā),開發(fā)者可以根據(jù)自己的實(shí)際使用場景靈活進(jìn)行內(nèi)存分配和釋放。雖然在C++中自C++11引入了smart pointer,雖然很大程度上能夠避免使用裸指針,但仍然不能完全避免,最重要的一個(gè)原因是你不能保證組內(nèi)其他人不適用指針,更不能保證合作部門不使用指針。

那么為什么C/C++中會存在指針呢?

這就得從進(jìn)程的內(nèi)存布局說起。

進(jìn)程內(nèi)存布局

上圖為32位進(jìn)程的內(nèi)存布局,從上圖中主要包含以下幾個(gè)塊:

  • 內(nèi)核空間:供內(nèi)核使用,存放的是內(nèi)核代碼和數(shù)據(jù)
  • stack:這就是我們經(jīng)常所說的棧,用來存儲自動變量(automatic variable)
  • mmap:也成為內(nèi)存映射,用來在進(jìn)程虛擬內(nèi)存地址空間中分配地址空間,創(chuàng)建和物理內(nèi)存的映射關(guān)系
  • heap:就是我們常說的堆,動態(tài)內(nèi)存的分配都是在堆上
  • bss:包含所有未初始化的全局和靜態(tài)變量,此段中的所有變量都由0或者空指針初始化,程序加載器在加載程序時(shí)為BSS段分配內(nèi)存
  • ds:初始化的數(shù)據(jù)塊
    • 包含顯式初始化的全局變量和靜態(tài)變量
    • 此段的大小由程序源代碼中值的大小決定,在運(yùn)行時(shí)不會更改
    • 它具有讀寫權(quán)限,因此可以在運(yùn)行時(shí)更改此段的變量值
    • 該段可進(jìn)一步分為初始化只讀區(qū)和初始化讀寫區(qū)
  • text:也稱為文本段
    • 該段包含已編譯程序的二進(jìn)制文件。
    • 該段是一個(gè)只讀段,用于防止程序被意外修改
    • 該段是可共享的,因此對于文本編輯器等頻繁執(zhí)行的程序,內(nèi)存中只需要一個(gè)副本

由于本文主要講內(nèi)存分配相關(guān),所以下面的內(nèi)容僅涉及到棧(stack)和堆(heap)。

棧一塊連續(xù)的內(nèi)存塊,棧上的內(nèi)存分配就是在這一塊連續(xù)內(nèi)存塊上進(jìn)行操作的。編譯器在編譯的時(shí)候,就已經(jīng)知道要分配的內(nèi)存大小,當(dāng)調(diào)用函數(shù)時(shí)候,其內(nèi)部的遍歷都會在棧上分配內(nèi)存;當(dāng)結(jié)束函數(shù)調(diào)用時(shí)候,內(nèi)部變量就會被釋放,進(jìn)而將內(nèi)存歸還給棧。

class Object {
  public:
    Object() = default;
    // ....
};
void fun() {
  Object obj;
  // do sth
}

在上述代碼中,obj就是在棧上進(jìn)行分配,當(dāng)出了fun作用域的時(shí)候,會自動調(diào)用Object的析構(gòu)函數(shù)對其進(jìn)行釋放。

前面有提到,局部變量會在作用域(如函數(shù)作用域、塊作用域等)結(jié)束后析構(gòu)、釋放內(nèi)存。因?yàn)榉峙浜歪尫诺拇涡蚴莿偤猛耆喾吹?,所以可用到堆棧先進(jìn)后出(first-in-last-out, FILO)的特性,而 C++ 語言的實(shí)現(xiàn)一般也會使用到調(diào)用堆棧(call stack)來分配局部變量(但非標(biāo)準(zhǔn)的要求)。

因?yàn)闂I蟽?nèi)存分配和釋放,是一個(gè)進(jìn)棧和出棧的過程(對于編譯器只是一個(gè)移動指針的過程),所以相比于堆上的內(nèi)存分配,棧要快的多。

雖然棧的訪問速度要快于堆,每個(gè)線程都有一個(gè)自己的棧,棧上的對象是不能跨線程訪問的,這就決定了??臻g大小是有限制的,如果??臻g過大,那么在大型程序中幾十乃至上百個(gè)線程,光??臻g就消耗了RAM,這就導(dǎo)致heap的可用空間變小,影響程序正常運(yùn)行。

設(shè)置

在Linux系統(tǒng)上,可用通過如下命令來查看棧大?。?/p>

ulimit -s
10240

在筆者的機(jī)器上,執(zhí)行上述命令輸出結(jié)果是10240(KB)即10m,可以通過shell命令修改棧大小。

ulimit -s 102400

通過如上命令,可以將??臻g臨時(shí)修改為100m,可以通過下面的命令:

/etc/security/limits.conf

分配方式

靜態(tài)分配

靜態(tài)分配由編譯器完成,假如局部變量以及函數(shù)參數(shù)等,都在編譯期就分配好了。

void fun() {
  int a[10];
}

上述代碼中,a占10 * sizeof(int)個(gè)字節(jié),在編譯的時(shí)候直接計(jì)算好了,運(yùn)行的時(shí)候,直接進(jìn)棧出棧。

動態(tài)分配

可能很多人認(rèn)為只有堆上才會存在動態(tài)分配,在棧上只可能是靜態(tài)分配。其實(shí),這個(gè)觀點(diǎn)是錯(cuò)的,棧上也支持動態(tài)分配,該動態(tài)分配由alloca()函數(shù)進(jìn)行分配。棧的動態(tài)分配和堆是不同的,通過alloca()函數(shù)分配的內(nèi)存由編譯器進(jìn)行釋放,無序手動操作。

特點(diǎn)

  • 分配速度快:分配大小由編譯器在編譯器完成
  • 不會產(chǎn)生內(nèi)存碎片:棧內(nèi)存分配是連續(xù)的,以FIFO的方式進(jìn)棧和出棧
  • 大小受限:棧的大小依賴于操作系統(tǒng)
  • 訪問受限:只能在當(dāng)前函數(shù)或者作用域內(nèi)進(jìn)行訪問

堆(heap)是一種內(nèi)存管理方式。內(nèi)存管理對操作系統(tǒng)來說是一件非常復(fù)雜的事情,因?yàn)槭紫葍?nèi)存容量很大,其次就是內(nèi)存需求在時(shí)間和大小塊上沒有規(guī)律(操作系統(tǒng)上運(yùn)行著幾十甚至幾百個(gè)進(jìn)程,這些進(jìn)程可能隨時(shí)都會申請或者是釋放內(nèi)存,并且申請和釋放的內(nèi)存塊大小是隨意的)。

堆這種內(nèi)存管理方式的特點(diǎn)就是自由(隨時(shí)申請、隨時(shí)釋放、大小塊隨意)。堆內(nèi)存是操作系統(tǒng)劃歸給堆管理器(操作系統(tǒng)中的一段代碼,屬于操作系統(tǒng)的內(nèi)存管理單元)來管理的,堆管理器提供了對應(yīng)的接口_sbrk、mmap_等,只是該接口往往由運(yùn)行時(shí)庫進(jìn)行調(diào)用,即也可以說由運(yùn)行時(shí)庫進(jìn)行堆內(nèi)存管理,運(yùn)行時(shí)庫提供了malloc/free函數(shù)由開發(fā)人員調(diào)用,進(jìn)而使用堆內(nèi)存。

分配方式

正如我們所理解的那樣,由于是在運(yùn)行期進(jìn)行內(nèi)存分配,分配的大小也在運(yùn)行期才會知道,所以堆只支持動態(tài)分配,內(nèi)存申請和釋放的行為由開發(fā)者自行操作,這就很容易造成我們說的內(nèi)存泄漏。

特點(diǎn)

  • 變量可以在進(jìn)程范圍內(nèi)訪問,即進(jìn)程內(nèi)的所有線程都可以訪問該變量
  • 沒有內(nèi)存大小限制,這個(gè)其實(shí)是相對的,只是相對于棧大小來說沒有限制,其實(shí)最終還是受限于RAM
  • 相對棧來說訪問比較慢
  • 內(nèi)存碎片
  • 由開發(fā)者管理內(nèi)存,即內(nèi)存的申請和釋放都由開發(fā)人員來操作

堆與棧區(qū)別

理解堆和棧的區(qū)別,對我們開發(fā)過程中會非常有用,結(jié)合上面的內(nèi)容,總結(jié)下二者的區(qū)別。

對于棧來講,是由編譯器自動管理,無需我們手工控制;對于堆來說,釋放工作由程序員控制,容易產(chǎn)生memory leak

  • 空間大小不同
    • 一般來講在 32 位系統(tǒng)下,堆內(nèi)存可以達(dá)到4G的空間,從這個(gè)角度來看堆內(nèi)存幾乎是沒有什么限制的。
    • 對于棧來講,一般都是有一定的空間大小的,一般依賴于操作系統(tǒng)(也可以人工設(shè)置)
  • 能否產(chǎn)生碎片不同
    • 對于堆來講,頻繁的內(nèi)存分配和釋放勢必會造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。
    • 對于棧來講,內(nèi)存都是連續(xù)的,申請和釋放都是指令移動,類似于數(shù)據(jù)結(jié)構(gòu)中的進(jìn)棧和出棧 
  • 增長方向不同
    • 對于堆來講,生長方向是向上的,也就是向著內(nèi)存地址增加的方向
    • 對于棧來講,它的生長方向是向下的,是向著內(nèi)存地址減小的方向增長
  • 分配方式不同
    • 堆都是動態(tài)分配的,比如我們常見的malloc/new;而棧則有靜態(tài)分配和動態(tài)分配兩種。
    • 靜態(tài)分配是編譯器完成的,比如局部變量的分配,而棧的動態(tài)分配則通過alloca()函數(shù)完成
    • 二者動態(tài)分配是不同的,棧的動態(tài)分配的內(nèi)存由編譯器進(jìn)行釋放,而堆上的動態(tài)分配的內(nèi)存則必須由開發(fā)人自行釋放
  • 分配效率不同
    • 棧有操作系統(tǒng)分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高
    • 堆內(nèi)存的申請和釋放專門有運(yùn)行時(shí)庫提供的函數(shù),里面涉及復(fù)雜的邏輯,申請和釋放效率低于棧

截止到這里,棧和堆的基本特性以及各自的優(yōu)缺點(diǎn)、使用場景已經(jīng)分析完成,在這里給開發(fā)者一個(gè)建議,能使用棧的時(shí)候,就盡量使用棧,一方面是因?yàn)樾矢哂诙?,另一方面?nèi)存的申請和釋放由編譯器完成,這樣就避免了很多問題。

擴(kuò)展

終于到了這一小節(jié),其實(shí),上面講的那么多,都是為這一小節(jié)做鋪墊。

在前面的內(nèi)容中,我們對比了棧和堆,雖然棧效率比較高,且不存在內(nèi)存泄漏、內(nèi)存碎片等,但是由于其本身的局限性(不能多線程、大小受限),所以在很多時(shí)候,還是需要在堆上進(jìn)行內(nèi)存。

我們先看一段代碼:

#include <stdio.h>
#include <stdlib.h>
int main() {
  int a;
  int *p;
  p = (int *)malloc(sizeof(int));
  free(p);
  return 0;
}

上述代碼很簡單,有兩個(gè)變量a和p,類型分別為int和int *,其中,a和p存儲在棧上,p的值為在堆上的某塊地址(在上述代碼中,p的值為0x1c66010),上述代碼布局如下圖所示:

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • C指針原理教程之語法樹及其實(shí)現(xiàn)

    C指針原理教程之語法樹及其實(shí)現(xiàn)

    本文給大家分享的是如何使用C語言的指針原來來實(shí)現(xiàn)語法樹,并給大家提供了詳細(xì)的實(shí)例代碼,希望大家能夠喜歡
    2019-02-02
  • C/C++中的OpenCV讀取視頻與調(diào)用攝像頭

    C/C++中的OpenCV讀取視頻與調(diào)用攝像頭

    這篇文章主要介紹了C/C++中的OpenCV讀取視頻與調(diào)用攝像頭,具有很好的參考價(jià)值,希望對大家有所幫助。
    2022-11-11
  • C++學(xué)習(xí)筆記之類與對象詳解

    C++學(xué)習(xí)筆記之類與對象詳解

    這篇文章主要為大家介紹了C++類與對象,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12
  • C語言實(shí)現(xiàn)銷售管理系統(tǒng)設(shè)計(jì)

    C語言實(shí)現(xiàn)銷售管理系統(tǒng)設(shè)計(jì)

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)銷售管理系統(tǒng)設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C++ Primer Plus 第四章之C++ Primer Plus復(fù)合類型學(xué)習(xí)筆記

    C++ Primer Plus 第四章之C++ Primer Plus復(fù)合類型學(xué)習(xí)筆記

    數(shù)組(array)是一種數(shù)據(jù)格式,能夠存儲多個(gè)同類型的值。每個(gè)值都存儲在一個(gè)獨(dú)立的數(shù)組元素中,計(jì)算機(jī)在內(nèi)存中依次存儲數(shù)組的各個(gè)元素,今天給大家重點(diǎn)介紹C++ Primer Plus復(fù)合類型的實(shí)例詳解,感興趣的朋友一起看看吧
    2021-07-07
  • C語言中字符串常用函數(shù)strcat與strcpy的用法介紹

    C語言中字符串常用函數(shù)strcat與strcpy的用法介紹

    以下是對C語言中字符串常用函數(shù)strcat與strcpy的使用方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以參考下
    2013-07-07
  • C語言實(shí)現(xiàn)linux網(wǎng)卡連接檢測的方法

    C語言實(shí)現(xiàn)linux網(wǎng)卡連接檢測的方法

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)linux網(wǎng)卡連接檢測的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • 詳解C++循環(huán)創(chuàng)建多級目錄及判斷目錄是否存在的方法

    詳解C++循環(huán)創(chuàng)建多級目錄及判斷目錄是否存在的方法

    這篇文章主要介紹了C++循環(huán)創(chuàng)建多級目錄及判斷目錄是否存在的方法,文中代碼有一個(gè)針對各種系統(tǒng)進(jìn)行判斷來加載不同頭文件的方法,需要的朋友可以參考下
    2016-03-03
  • VScode如何調(diào)用KEIL-MDK

    VScode如何調(diào)用KEIL-MDK

    這篇文章主要介紹了VScode如何調(diào)用KEIL-MDK問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 淺析操作系統(tǒng)中的虛擬地址與物理地址

    淺析操作系統(tǒng)中的虛擬地址與物理地址

    本文主要介紹了操作系統(tǒng)中的虛擬地址與物理地址。在早期的計(jì)算機(jī)中,要運(yùn)行一個(gè)程序,會把這些程序全都裝入內(nèi)存,程序都是直接運(yùn)行在內(nèi)存上的,也就是說程序中訪問的內(nèi)存地址都是實(shí)際的物理內(nèi)存地址。那當(dāng)程序同時(shí)運(yùn)行多個(gè)程序時(shí),操作系統(tǒng)是如何為這些程序分配內(nèi)存的呢
    2021-06-06

最新評論

昌黎县| 冷水江市| 全南县| 中方县| 九台市| 鹿邑县| 永清县| 城固县| 镇沅| 东丽区| 大余县| 泰兴市| 通州区| 仲巴县| 万源市| 郧西县| 射阳县| 靖西县| 安溪县| 同心县| 农安县| 浦江县| 旬阳县| 浮梁县| 普安县| 古蔺县| 涿鹿县| 额济纳旗| 榆林市| 巴楚县| 汉沽区| 陇川县| 江达县| 楚雄市| 新丰县| 射洪县| 南召县| 河南省| 铜陵市| 家居| 屏山县|