欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解C語言的指針

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

起源

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

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

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

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

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

  • 內(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或者空指針初始化,程序加載器在加載程序時為BSS段分配內(nèi)存
  • ds:初始化的數(shù)據(jù)塊
    • 包含顯式初始化的全局變量和靜態(tài)變量
    • 此段的大小由程序源代碼中值的大小決定,在運(yùn)行時不會更改
    • 它具有讀寫權(quán)限,因此可以在運(yùn)行時更改此段的變量值
    • 該段可進(jìn)一步分為初始化只讀區(qū)和初始化讀寫區(qū)
  • text:也稱為文本段
    • 該段包含已編譯程序的二進(jìn)制文件。
    • 該段是一個只讀段,用于防止程序被意外修改
    • 該段是可共享的,因此對于文本編輯器等頻繁執(zhí)行的程序,內(nèi)存中只需要一個副本

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

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

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

在上述代碼中,obj就是在棧上進(jìn)行分配,當(dāng)出了fun作用域的時候,會自動調(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)存分配和釋放,是一個進(jìn)棧和出棧的過程(對于編譯器只是一個移動指針的過程),所以相比于堆上的內(nèi)存分配,棧要快的多。

雖然棧的訪問速度要快于堆,每個線程都有一個自己的棧,棧上的對象是不能跨線程訪問的,這就決定了棧空間大小是有限制的,如果棧空間過大,那么在大型程序中幾十乃至上百個線程,光??臻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臨時修改為100m,可以通過下面的命令:

/etc/security/limits.conf

分配方式

靜態(tài)分配

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

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

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

動態(tài)分配

可能很多人認(rèn)為只有堆上才會存在動態(tài)分配,在棧上只可能是靜態(tài)分配。其實(shí),這個觀點(diǎn)是錯的,棧上也支持動態(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)存需求在時間和大小塊上沒有規(guī)律(操作系統(tǒng)上運(yùn)行著幾十甚至幾百個進(jìn)程,這些進(jìn)程可能隨時都會申請或者是釋放內(nèi)存,并且申請和釋放的內(nèi)存塊大小是隨意的)。

堆這種內(nèi)存管理方式的特點(diǎn)就是自由(隨時申請、隨時釋放、大小塊隨意)。堆內(nèi)存是操作系統(tǒng)劃歸給堆管理器(操作系統(tǒng)中的一段代碼,屬于操作系統(tǒng)的內(nèi)存管理單元)來管理的,堆管理器提供了對應(yīng)的接口_sbrk、mmap_等,只是該接口往往由運(yùn)行時庫進(jìn)行調(diào)用,即也可以說由運(yùn)行時庫進(jìn)行堆內(nèi)存管理,運(yùn)行時庫提供了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)存大小限制,這個其實(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的空間,從這個角度來看堆內(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ù),里面涉及復(fù)雜的邏輯,申請和釋放效率低于棧

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

擴(kuò)展

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

在前面的內(nèi)容中,我們對比了棧和堆,雖然棧效率比較高,且不存在內(nèi)存泄漏、內(nèi)存碎片等,但是由于其本身的局限性(不能多線程、大小受限),所以在很多時候,還是需要在堆上進(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;
}

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

總結(jié)

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

相關(guān)文章

  • 詳解c++ 靜態(tài)成員變量

    詳解c++ 靜態(tài)成員變量

    這篇文章主要介紹了c++ 靜態(tài)成員變量的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)c++,感興趣的朋友可以了解下
    2020-09-09
  • C、C++線性表基本操作的詳細(xì)介紹

    C、C++線性表基本操作的詳細(xì)介紹

    這篇文章主要給大家介紹了關(guān)于C、C++線性表基本操作的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • C++浮點(diǎn)數(shù)在內(nèi)存中的存儲詳解

    C++浮點(diǎn)數(shù)在內(nèi)存中的存儲詳解

    大家好,本篇文章主要講的是C++浮點(diǎn)數(shù)在內(nèi)存中的存儲詳解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • 對C語言中指針的理解與其基礎(chǔ)使用實(shí)例

    對C語言中指針的理解與其基礎(chǔ)使用實(shí)例

    這篇文章主要介紹了對C語言中指針的理解與其基礎(chǔ)使用實(shí)例,文中援引了知乎熱門問題"為什么說指針是 C 語言的精髓?"中的精彩回答,需要的朋友可以參考下
    2016-03-03
  • 探討++i與i++哪個效率更高

    探討++i與i++哪個效率更高

    i++總是要創(chuàng)建一個臨時對象,在退出函數(shù)時還要銷毀它,而且返回臨時對象的值時還會調(diào)用其拷貝構(gòu)造函數(shù)
    2013-10-10
  • C++ 命名空間詳解

    C++ 命名空間詳解

    這篇文章主要介紹了C++ 命名空間的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解下
    2021-11-11
  • opencv3/C++ FLANN特征匹配方式

    opencv3/C++ FLANN特征匹配方式

    今天小編就為大家分享一篇opencv3/C++ FLANN特征匹配方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-12-12
  • 解析C++的線性表鏈?zhǔn)酱鎯υO(shè)計與相關(guān)的API實(shí)現(xiàn)

    解析C++的線性表鏈?zhǔn)酱鎯υO(shè)計與相關(guān)的API實(shí)現(xiàn)

    這篇文章主要介紹了解析C++中的線性表鏈?zhǔn)酱鎯υO(shè)計與相關(guān)的API實(shí)現(xiàn),文中的實(shí)例很好地體現(xiàn)了如何創(chuàng)建和遍歷鏈表等基本操作,需要的朋友可以參考下
    2016-03-03
  • 利用C++11原子量如何實(shí)現(xiàn)自旋鎖詳解

    利用C++11原子量如何實(shí)現(xiàn)自旋鎖詳解

    當(dāng)自旋鎖嘗試獲取鎖時以忙等待(busy waiting)的形式不斷地循環(huán)檢查鎖是否可用,下面這篇文章主要給大家介紹了關(guān)于利用C++11原子量如何實(shí)現(xiàn)自旋鎖的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2018-06-06
  • C語言中對數(shù)組賦值的三種形式

    C語言中對數(shù)組賦值的三種形式

    這篇文章主要給大家介紹了關(guān)于C語言中對數(shù)組賦值的3種形式,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用C語言具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09

最新評論