爲何鳥哥說 int 再怎麼隨機也申請不到奇數地址

原文:個人我的博客 https://mengkang.net/1046.html
初中級 phper 有多久沒給本身充電了呢,安利一波個人直播 PHP 進階之路

鳥哥微博

DingTalk20171023172325.png

爲何要字節對齊

須要字節對齊的根本緣由在於CPU訪問數據的效率問題。由於CPU每次都是從以4字節(32位CPU)或是8字節(64位CPU)的整數倍的內存地址中讀進數據的。(更深刻的緣由,誰告知下),若是不對齊的話,頗有可能一個4字節int須要分兩次讀取。具體演示看下面的實驗。php

數據類型自身的對齊值

按各數據類型自身大小進行對齊。變量的內存地址正好位於它長度的整數倍html

實驗

#include <stdio.h>

int 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\n",&a,sizeof(a));
    printf("%p,sizeof(b):%lu\n",&b,sizeof(b));
    printf("%p,sizeof(c):%lu\n",&c,sizeof(c));
    printf("%p,sizeof(d):%lu\n",&d,sizeof(d));
    printf("%p,sizeof(e):%lu\n",&e,sizeof(e));

    return 0;
}
輔助以圖片說明,該圖左側是上面代碼的內存圖,灰色部分表示該程序未使用的內存。右側是在上面代碼的基礎上在 char a後面聲明瞭一個 short f

DingTalk20171023151746.png

從上面的實驗和圖上咱們能夠找出如下規律:segmentfault

  1. abcde 五個變量的內存地址從大到下依次分配的;
  2. 若是你細看,會發現它們的內存地址並非緊密挨着的;
  3. 並且int 類型的變量的內存地址都是偶數(這也就是爲何鳥哥微博中說的不可能存在奇數的 int 變量的地址了);
  4. 再細看,發現 int 變量的地址都是能夠被4整除,因此在棧上各變量是按各數據類型自身大小進行對齊的。
  5. 新增的short f 地址也並無緊挨着a,而是跟自身數據大小對齊,也就是偶數地址開始申請。
  6. 棧上各個變量申請的內存,返回的地址是這段連續內存的最小的地址。

反過來想,若是不對齊,好比上例中的 a,b,c 三個變量的內存地址緊挨着,而CPU每次只讀取8個字節,也就是說變量 c 還有最後一個字節沒有讀取進來。訪問數據效率就下降了。網絡

棧上各個變量申請的內存,返回的地址是這段連續內存的最小的地址。這是怎麼回事呢?框架

咱們仍是經過實驗來驗證下我上面畫的內存圖,假如我有一個int變量,它的值佔了滿了4個字節,那麼它的四個字節裏是怎麼存放數據的,咱們用十六進制來演示0x12345678spa

  1. 爲何用一個8位的十六進制來呢?由於int 4個字節,一個字節有8位,每位有0/1兩個狀態,那麼就是2^8=256,也就是16^2。因此用了一個8位的16進制數正好能夠填滿一個 int 的內存。
  2. 爲何用12345678,純屬演示方便。

我先存了變量 b,而後以 char 指針 p 來依次訪問 b 的四個字節的使用狀況。.net

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char a = 1;             // 0x7fff5fbff777
    int  b = 0x12345678;    // 0x7fff5fbff770
    char c = 1;             // 0x7fff5fbff76f
    printf("%p\n",&a);
    printf("%p\n",&b);
    printf("%p\n",&c);

    char *p = (char *)&b;
    
    printf("%x %x %x %x\n", p[0],p[1],p[2],p[3]); // 78 56 34 12
    printf("%p %p %p %p\n", &p[0],&p[1],&p[2],&p[3]); // 0x7fff5fbff770 0x7fff5fbff771 0x7fff5fbff772 0x7fff5fbff773
        
    return 0;
}

變量 b 0x12345678的最高位是0x12,最低位是0x78
針對實驗結果我又畫了內存圖,咱們能夠看到0x12存放在的內存地址要比0x78的大。指針

DingTalk20171023154959.png

這裏呢就必須說明下 大小端模式code

  1. 小端法(Little-Endian)就是低位字節排放在內存的低地址端即該值的起始地址,高位字節排放在內存的高地址端。
  2. 大端法(Big-Endian)就是高位字節排放在內存的低地址端即該值的起始地址,低位字節排放在內存的高地址端。

因此,我當前的環境是小端序的形式。htm

爲何會有大端小端之分?
這個就得問硬件廠商了,都比較任性,因此歷史就這樣了。

結構體裏的字節對齊

以成員中自身對齊值最大的那個值爲標準。

實驗

int main(int argc, char const *argv[])
{
    struct str1{
        char a;
        short b;
        int c;
    };
    
    printf("sizeof(f):%lu\n",sizeof(struct str1));
    
    struct str2{
        char a;
        int c;
        short b;
    };
    
    printf("sizeof(g):%lu\n",sizeof(struct str2));
    
    struct str1 a;
    printf("a.a %p\n",&a.a);
    printf("a.b %p\n",&a.b);
    printf("a.c %p\n",&a.c);
    
    struct str2 b;
    printf("b.a %p\n",&b.a);
    printf("b.c %p\n",&b.c);
    printf("b.b %p\n",&b.b);

    
    return 0;
}

結果

sizeof(f):8
sizeof(g):12
a.a 0x7fff5fbff778
a.b 0x7fff5fbff77a
a.c 0x7fff5fbff77c
b.a 0x7fff5fbff768
b.c 0x7fff5fbff76c
b.b 0x7fff5fbff770

原理

灰色表填充用來對齊,保證最後結構體大小是最長的成員的大小的整數倍。
DingTalk20171023165731.png

例外

實際工做中是否不按字節對齊的狀況呢?有的,好比咱們的 rpc 框架裏面進行數據傳輸的時候,會選擇設置爲緊湊型,這樣就能夠輕鬆作到跨平臺,跨語言了。
在網絡程序中採用#pragma pack(1),即變量緊縮,不但能夠減小網絡流量,還能夠兼容各類系統,不會由於系統對齊方式不一樣而致使解包錯誤。

實戰舉例 yar_header 中使用 #pragma pack(1) 和 attribute ((packed)) 的意義
相關文章
相關標籤/搜索