摘要:棧上各個(gè)變量申請的內(nèi)存,返回的地址是這段連續(xù)內(nèi)存的最小的地址。為什么用一個(gè)位的十六進(jìn)制來呢因?yàn)閭€(gè)字節(jié),一個(gè)字節(jié)有位,每位有兩個(gè)狀態(tài),那么就是,也就是。為什么用,純屬演示方便。結(jié)構(gòu)體里的字節(jié)對齊以成員中自身對齊值最大的那個(gè)值為標(biāo)準(zhǔn)。
原文:我的個(gè)人博客 https://mengkang.net/1046.html鳥哥微博 為什么要字節(jié)對齊
初中級 phper 有多久沒給自己充電了呢,安利一波我的直播 PHP 進(jìn)階之路
需要字節(jié)對齊的根本原因在于CPU訪問數(shù)據(jù)的效率問題。因?yàn)镃PU每次都是從以4字節(jié)(32位CPU)或是8字節(jié)(64位CPU)的整數(shù)倍的內(nèi)存地址中讀進(jìn)數(shù)據(jù)的。(更深入的原因,誰告知下),如果不對齊的話,很有可能一個(gè)4字節(jié)int需要分兩次讀取。具體演示看下面的實(shí)驗(yàn)。
數(shù)據(jù)類型自身的對齊值按各數(shù)據(jù)類型自身大小進(jìn)行對齊。變量的內(nèi)存地址正好位于它長度的整數(shù)倍
實(shí)驗(yàn)#includeint main(int argc, char const *argv[]) { char a = 1; // 0x7fff5fbff77f,sizeof(a):1 int b = 1; // 0x7fff5fbff778,sizeof(b):4 int c = 1; // 0x7fff5fbff774,sizeof(c):4 char d = 1; // 0x7fff5fbff773,sizeof(e):1 int e = 1; // 0x7fff5fbff76c,sizeof(f):4 printf("%p,sizeof(a):%lu ",&a,sizeof(a)); printf("%p,sizeof(b):%lu ",&b,sizeof(b)); printf("%p,sizeof(c):%lu ",&c,sizeof(c)); printf("%p,sizeof(d):%lu ",&d,sizeof(d)); printf("%p,sizeof(e):%lu ",&e,sizeof(e)); return 0; }
輔助以圖片說明,該圖左側(cè)是上面代碼的內(nèi)存圖,灰色部分表示該程序未使用的內(nèi)存。右側(cè)是在上面代碼的基礎(chǔ)上在char a后面聲明了一個(gè)short f。
從上面的實(shí)驗(yàn)和圖上我們可以找出以下規(guī)律:
abcde 五個(gè)變量的內(nèi)存地址從大到下依次分配的;
如果你細(xì)看,會(huì)發(fā)現(xiàn)它們的內(nèi)存地址并不是緊密挨著的;
而且int 類型的變量的內(nèi)存地址都是偶數(shù)(這也就是為什么鳥哥微博中說的不可能存在奇數(shù)的 int 變量的地址了);
再細(xì)看,發(fā)現(xiàn) int 變量的地址都是可以被4整除,所以在棧上各變量是按各數(shù)據(jù)類型自身大小進(jìn)行對齊的。
新增的short f 地址也并沒有緊挨著a,而是跟自身數(shù)據(jù)大小對齊,也就是偶數(shù)地址開始申請。
棧上各個(gè)變量申請的內(nèi)存,返回的地址是這段連續(xù)內(nèi)存的最小的地址。
反過來想,如果不對齊,比如上例中的 a,b,c 三個(gè)變量的內(nèi)存地址緊挨著,而CPU每次只讀取8個(gè)字節(jié),也就是說變量 c 還有最后一個(gè)字節(jié)沒有讀取進(jìn)來。訪問數(shù)據(jù)效率就降低了。
棧上各個(gè)變量申請的內(nèi)存,返回的地址是這段連續(xù)內(nèi)存的最小的地址。這是怎么回事呢?
我們還是通過實(shí)驗(yàn)來驗(yàn)證下我上面畫的內(nèi)存圖,假如我有一個(gè)int變量,它的值占了滿了4個(gè)字節(jié),那么它的四個(gè)字節(jié)里是怎么存放數(shù)據(jù)的,我們用十六進(jìn)制來演示0x12345678。
為什么用一個(gè)8位的十六進(jìn)制來呢?因?yàn)閕nt 4個(gè)字節(jié),一個(gè)字節(jié)有8位,每位有0/1兩個(gè)狀態(tài),那么就是2^8=256,也就是16^2。所以用了一個(gè)8位的16進(jìn)制數(shù)正好可以填滿一個(gè) int 的內(nèi)存。
為什么用12345678,純屬演示方便。
我先存了變量 b,然后以 char 指針 p 來依次訪問 b 的四個(gè)字節(jié)的使用情況。
#includeint main(int argc, char const *argv[]) { char a = 1; // 0x7fff5fbff777 int b = 0x12345678; // 0x7fff5fbff770 char c = 1; // 0x7fff5fbff76f printf("%p ",&a); printf("%p ",&b); printf("%p ",&c); char *p = (char *)&b; printf("%x %x %x %x ", p[0],p[1],p[2],p[3]); // 78 56 34 12 printf("%p %p %p %p ", &p[0],&p[1],&p[2],&p[3]); // 0x7fff5fbff770 0x7fff5fbff771 0x7fff5fbff772 0x7fff5fbff773 return 0; }
變量 b 0x12345678的最高位是0x12,最低位是0x78
針對實(shí)驗(yàn)結(jié)果我又畫了內(nèi)存圖,我們可以看到0x12存放在的內(nèi)存地址要比0x78的大。
這里呢就必須說明下 大小端模式
小端法(Little-Endian)就是低位字節(jié)排放在內(nèi)存的低地址端即該值的起始地址,高位字節(jié)排放在內(nèi)存的高地址端。
大端法(Big-Endian)就是高位字節(jié)排放在內(nèi)存的低地址端即該值的起始地址,低位字節(jié)排放在內(nèi)存的高地址端。
所以,我當(dāng)前的環(huán)境是小端序的形式。
為什么會(huì)有大端小端之分?結(jié)構(gòu)體里的字節(jié)對齊
這個(gè)就得問硬件廠商了,都比較任性,所以歷史就這樣了。
以成員中自身對齊值最大的那個(gè)值為標(biāo)準(zhǔn)。
實(shí)驗(yàn)int main(int argc, char const *argv[]) { struct str1{ char a; short b; int c; }; printf("sizeof(f):%lu ",sizeof(struct str1)); struct str2{ char a; int c; short b; }; printf("sizeof(g):%lu ",sizeof(struct str2)); struct str1 a; printf("a.a %p ",&a.a); printf("a.b %p ",&a.b); printf("a.c %p ",&a.c); struct str2 b; printf("b.a %p ",&b.a); printf("b.c %p ",&b.c); printf("b.b %p ",&b.b); return 0; }
結(jié)果
sizeof(f):8 sizeof(g):12 a.a 0x7fff5fbff778 a.b 0x7fff5fbff77a a.c 0x7fff5fbff77c b.a 0x7fff5fbff768 b.c 0x7fff5fbff76c b.b 0x7fff5fbff770原理
灰色表填充用來對齊,保證最后結(jié)構(gòu)體大小是最長的成員的大小的整數(shù)倍。
實(shí)際工作中是否不按字節(jié)對齊的情況呢?有的,比如我們的 rpc 框架里面進(jìn)行數(shù)據(jù)傳輸?shù)臅r(shí)候,會(huì)選擇設(shè)置為緊湊型,這樣就可以輕松做到跨平臺(tái),跨語言了。
在網(wǎng)絡(luò)程序中采用#pragma pack(1),即變量緊縮,不但可以減少網(wǎng)絡(luò)流量,還可以兼容各種系統(tǒng),不會(huì)因?yàn)橄到y(tǒng)對齊方式不同而導(dǎo)致解包錯(cuò)誤。
實(shí)戰(zhàn)舉例 yar_header 中使用 #pragma pack(1) 和 attribute ((packed)) 的意義
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/26255.html
目錄 前言 一、 什么是指針? 引例 計(jì)算機(jī)是怎么對內(nèi)存單元編號(hào)的呢? 內(nèi)存空間的地址如何得到 想存地址怎么辦? ? 本質(zhì)目的不是為了存地址 ?二、指針和指針類型 為什么有不同類型的指針 1.指針的解引用 2.指針+-整數(shù) 三、野指針 造成野指針的原因 1.未主動(dòng)初始化指針 ?2.指針越界訪問 3.指針指向的空間釋放 規(guī)避野指針 四、指針運(yùn)算 1.指針+-整數(shù) ?2.指針-指針 ?3.指針的關(guān)系運(yùn)...
摘要:另外棧內(nèi)存出了作用域就會(huì)自動(dòng)釋放掉,所以不需要手動(dòng)去回收的。,其中指針變量的聲明有如下三種形式其中第一種是被推薦的寫法。數(shù)據(jù)類型 C語言中的基本數(shù)據(jù)類型,對于它分為兩種: 1、signed 有符號(hào)的類型,也就是支持正負(fù)號(hào)的。 2、unsigned 無符號(hào)的類型,也就是沒有負(fù)號(hào),取值從0開始。 有符號(hào)和無符號(hào)的數(shù)據(jù)類型有啥區(qū)別呢?其實(shí)就是取值范圍不一樣,下面看一張對照表: showImg(ht...
摘要:還在上班很無聊數(shù)字華容道暢玩地址開發(fā)源碼地址這個(gè)叫前言年末了。光隨機(jī)生成一個(gè)亂序數(shù)列是不夠的,還得保證這個(gè)數(shù)列的逆序數(shù)為偶數(shù),嗦嘎。所以,我們直接將交換的次數(shù),記為數(shù)列逆序數(shù)個(gè)數(shù),就達(dá)到了想要的效果。 還在上班?很無聊?數(shù)字華容道暢玩地址 開發(fā)源碼地址 這個(gè)叫前言 年末了。哦,不,要過年了。以前只能一路站到公司的我,今早居然是坐著過來的。新的一年,總要學(xué)一個(gè)新東西來迎接新的未來吧,所以...
摘要:在學(xué)習(xí)語句的時(shí)候,對編程的基礎(chǔ)知識(shí)了解的還不是很多,或許沒有做什么太復(fù)雜的東西。可以通過一個(gè)內(nèi)置函數(shù)來判斷一個(gè)條件的結(jié)果還是。有朋友需要看完整教程內(nèi)容,請點(diǎn)擊零基礎(chǔ)學(xué),這里會(huì)及時(shí)更新,并且有完整的目錄結(jié)構(gòu),更吸納了朋友們提出的意見和建議。 看官是否記得,在上一部分的時(shí)候,有一講專門介紹if語句的:從if開始語句的征程。在學(xué)習(xí)if語句的時(shí)候,對python編程的基礎(chǔ)知識(shí)了解的還不是很多,...
摘要:是規(guī)則的瀑布流。普通的尺寸會(huì)出現(xiàn)錯(cuò)位的問題索引這個(gè)是右邊這個(gè)是左邊間距解決辦法,可以通過里的來判斷,這個(gè)方法不管你高度怎樣,他都是左右左右開始排列的。 目錄介紹 01.規(guī)則瀑布流實(shí)現(xiàn)02.不規(guī)則瀑布流實(shí)現(xiàn)2.1 實(shí)現(xiàn)方式2.2 遇到問題03.瀑布流上拉加載04.給瀑布流設(shè)置分割線05.自定義Manager崩潰06.如何避免刷新抖動(dòng)07.為何有時(shí)出現(xiàn)跳動(dòng)08.瀑布流圖片優(yōu)化09.onBi...
閱讀 2766·2021-11-11 16:54
閱讀 2405·2021-10-09 09:44
閱讀 2668·2019-08-30 15:54
閱讀 1986·2019-08-30 11:24
閱讀 1251·2019-08-29 17:03
閱讀 2168·2019-08-29 16:22
閱讀 2144·2019-08-29 13:11
閱讀 1117·2019-08-29 12:14