從記憶體角度深入看結構體(window/linux)大小

Sun_Shine_999發表於2018-01-15

今天我們來看一下windows(32, 64)(dev-c++,vc),linux(32, 64)不同系統下,

它們求結構體大小時,編譯器到底給它們分配了哪些記憶體,又為什麼這樣分配,為啥子編譯器給它們有時空閒3個記憶體塊,有時候又空閒7個記憶體塊,為什麼啊,為什麼啊

當你們讀了上面的內容,還想繼續往下看的時候,就說明你開始關注記憶體的分配問題了,哈哈!!!

關於記憶體對齊:

簡單地理解就是:程式中,資料結構中的變數等等都需要佔用記憶體,系統採用記憶體對齊,就可以提高訪問速度(為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問,而經過記憶體對齊一次就可以了)

大家看下面這個程式:

#include <stdio.h>  

struct stu{  
    char a;  
    int b;  
    char c;  
};  

int main(){  
    struct stu ss;  

    printf("%d\n", sizeof(ss));  
    printf("%p\n", &ss.a);  
    printf("%p\n", &ss.b);  
    printf("%p\n", &ss.c);  

    return 0;  
}  

當前結構體裡面的成員型別是:(char , int, char)

下面大家看程式執行結果:

結果一:

win7,32位,vc中
這裡寫圖片描述

win7,64位,vc中

這裡寫圖片描述

結果二:

win7,32位,dev-c++中

這裡寫圖片描述

win7,64位,dev-c++中

這裡寫圖片描述

我們知道這涉及一個概念——偏移量

網上給出的解釋是:它指的是結構體變數中成員的地址和結構體變數地址的差。

結構體大小的定義是:結構體的大小等於最後一個成員的偏移量加上最後一個成員的大小

哈哈,聽饒了吧!

其實,當前偏移量的大小就是編譯器為前面變數所分配的記憶體個數。

大家先看一下上面結構體在記憶體中(下面的是在dev中分配的)的情況

這裡寫圖片描述

首先,我們知道編譯器給一些變數分配記憶體的時候,它是一個連續的塊,它是以一個4位元組簡單對齊的

如上圖所示:

編譯器首先分配了一塊地址也就是:0022FEB4給了結構體的首地址(也就是結構體中的成員a)因為它是一個字元,它佔用一個記憶體地址,第二個是int型別,它佔用4個位元組,第一組已經用掉一格,還剩3格,肯定無法放下int型,考慮到記憶體對齊,所以不得不把它放到第二個組塊(0022feb8),第三個型別是char型別,跟第一個類似,佔用一個儲存塊(0022febc),又因為它是4位元組簡單對齊方式),

所以空閒的三個儲存塊也同樣分給了當前這個結構體。

好,我們接下來繼續分析:

看程式碼2:

#include <stdio.h>  

struct stu{  
    char a;  
    char b;  
    int c;  
};  

int main(){  
    struct stu ss;  

    printf("%d\n", sizeof(ss));  
    printf("%p\n", &ss.a);  
    printf("%p\n", &ss.b);  
    printf("%p\n", &ss.c);  

    return 0;  
}  

哈哈,可別看錯了,我可跟上面的程式碼不一樣哎!!!!!
(結構體裡面定義的成員是: char, char , int)

好,下面大家看一下執行結果:

win7,32位,vc中

這裡寫圖片描述
win7,64位,vc中

這裡寫圖片描述

結果二:

win7,32位,dev-c++中

這裡寫圖片描述

win7,64位,dev-c++中

這裡寫圖片描述
來,不要急,我們接著看記憶體,分析分析它:

這裡寫圖片描述
同樣的道理:當前結構體中,第一個型別是char型別,它佔用一個位元組(也就是0022FEB8),然後,接下來第二個型別還是char,佔用一個位元組,又因為上一組4個位元組(其中0022FEB9,0022FEBA, 0022FEBB均空著)還空著3個,可以放下第二個char,所以第二個char就放在了記憶體(0022FEB9)處,然後碰到int型別中,佔用4個位元組,上一組4個位元組中還剩下兩個空閒(其中0022FEBA, 0022FEBB),不夠放int型別,所以,新找了另一組連續的4個位元組。所以說:當前結構體總總共佔用8個位元組。

哈哈,所以說:結構體中,編譯器為所分配的所有空閒記憶體(滿足4位元組簡單對齊)都算在內,總的結構體大小也就相應的出來了。

簡單的說:結構體的成員變數型別(如1:char, int , char 如2:char, char, int),它們成員一樣,但順序不一樣,編譯器給分配的記憶體也不一樣,我們應儘可能的使其記憶體佔用的少一點,這樣,總的來說對我們的程式執行只有好處。

這回大家明白的差不多了吧!接下來我們分析一個不一樣的東西

看程式碼3:

#include <stdio.h>  

struct stu{  
    char a;  
    double b;  
    char c;  
};  

int main(){  
    struct stu ss;  

    printf("%d\n", sizeof(ss));  
    printf("%p\n", &ss.a);  
    printf("%p\n", &ss.b);  
    printf("%p\n", &ss.c);  
    return 0;  
}  

我們可以很清楚的看明白上面這個結構體的成員變數是(char , double , char)
所以我們的執行結果是:

win7,32位,vc中:

這裡寫圖片描述

win7,64位,vc中:

這裡寫圖片描述

結果二:

win7,32位,dev-c++中

這裡寫圖片描述
win7,64位,dev-c++中
這裡寫圖片描述

接下來再看一個小的程式:

程式碼4:

#include <stdio.h>  

struct stu{  
    char a;  
    int e;  
    double b;  
    char c;  
};  

int main(){  
    struct stu ss;  

    printf("%d\n", sizeof(ss));  
    printf("%p\n", &ss.a);  
    printf("%p\n", &ss.e);  
    printf("%p\n", &ss.b);  
    printf("%p\n", &ss.c);  
    return 0;  
}  

我們也可以直接看到:這個結構體成員是(char , int , double , char )
好,我們在看一下簡單地執行結果:

win7, 32, dev:

這裡寫圖片描述

win7, 64, dev:

這裡寫圖片描述
下面是win7,32,vc

這裡寫圖片描述

下面是win7,64,vc
這裡寫圖片描述

哈哈,不要害怕它們為什麼都是24,,接下來我們需要在看一下他們的記憶體分配情況,

下面這個圖是程式碼3的圖:

這裡寫圖片描述

程式碼4的這個記憶體圖(將程式碼1和程式碼2的記憶體圖給結合起來了),

哈哈,太難畫了,我在這裡就不畫了啊,大家應該可以理解的

好,接著繼續分析記憶體情況

我們前面提到的在程式碼1,程式碼2中記憶體是簡單地4位元組對齊,所以我們一直在找4個位元組來存放他們(也就是簡單地說,char後面如果跟著int型別,那麼就會出現3個空閒),

而到了我們這個程式碼3, 4, 記憶體就是簡單地8位元組對齊,所以我們一直在找8個位元組來存放他們,(也就是簡單地說,char後面如果跟著double型別,那麼就會出現7個空閒)

這又是為了什麼?

這裡我們又要提一下記憶體的自然對齊了
自然對齊就是說:每一種資料型別都必須放在記憶體地址中的整數倍上,舉一個簡單地例子
(地址4)可以放char型別的,也可以放int型別,也可以放short,但是但是就是不可用存放double型別,僅僅只是因為它不是8的整數倍,OK, 就是這麼簡單。
(地址3)可以用來存放char型別,但是不可用用來存放int, short, double ,也只是因為它們不是整數倍關係
(宣告一點,地址0是任何型別整數倍的, 這一點,我們可以這樣記著,理解為,可以存放任何資料型別)

所以說:在windows下:

所以,此時也就很好解釋上面的幾種情況了,哪些空閒的記憶體塊,就是因為它們本身不是資料型別的整數倍,所以被留了下來,在加上為了訪問便利,所以才出現了記憶體對齊這麼一說的吧!(個人簡單理解),其實我們這樣想想,也就這麼回事

那麼在linux下又是什麼樣子的呢????

接下來我們繼續看:

程式碼5(在linux下):

#include <stdio.h>  
#include <sys/cdefs.h>  
struct str{  

        char a;  
        int  b;  
        char c;  
};  

int main(){  
        struct str A;  
        printf("%u\n", sizeof(A));  
        printf("%p\n", &(A.a));  
        printf("%p\n", &(A.b));  
        printf("%p\n", &(A.c));  

        return 0;  

}  

這樣看,我們知道此時結構體裡面的成員型別是: char ,int , char
好,我們大家一起看執行結果:

linux(ubuntu),64位,:

這裡寫圖片描述

linux(ubuntu),32位

這裡寫圖片描述

這時,我們可以看到上面的記憶體分配是一樣的。就是說他們遵從的基本準則是一樣的(可以簡單的理解為:跟window下面是一樣的,(簡單的四位元組對齊))

好了,我們接下來繼續看另外一種情況:

程式碼6(在linux下)

#include <stdio.h>  
#include <sys/cdefs.h>  
struct str{  

        char a;  
        double  b;  
        char c;  
};  

int main(){  
        struct str A;  
        printf("結構體總大小位:%lu\n", sizeof(A));  
        printf("成員a的初始地址:%p\n", &(A.a));  
        printf("成員b的初始地址:%p\n", &(A.b));  
        printf("成員c的初始地址:%p\n", &(A.c));  

        return 0;  

}  

這時,我們也可以很清楚的看到當前結構體的成員變數型別是: char , double , char
好了,我們接著往下看:執行結果:

liunx(ubuntu下),64位:

這裡寫圖片描述

linux(ubuntu下), 32位:

這裡寫圖片描述

哈哈,不一樣了吧,懵了嗎?不要緊,接著繼續看記憶體分配:

(下圖是64位的簡單記憶體分配圖)

這裡寫圖片描述

下面是(32位的簡單記憶體分配圖)

這裡寫圖片描述

好了,這回我們也看到了,在編譯器的分配下,

linux(64位) :它是跟window底下的說法是一樣的,對於double這個型別,它的初始地址就是僅僅只能在8的倍數的地方,也就是上面我們所說的遵從的是記憶體的自然對齊,所以(char後面是double的話,它會出現7個空閒區)

linux(32位):它是跟window是不一樣的,但是也很好理解,就是說,它的所有東西都是遵從(基本的4位元組對齊),而32位的編譯器,將其(double)處理成了兩個4位元組,所以才會出現上面的情況,或者說對於double而言編譯器不考慮它的自然對齊,(所以說char後面出現double的話,它會出現3個空閒區)

好了,大傢伙:

現在我們來總結一下:

對於windows,關於結構體也就是說,我們不用考慮什麼偏移量,什麼加什麼啊,那些都太麻煩了,只需要明白記憶體的自然對齊就ok了,(不管什麼32,64位)
然而,對於linux下,對於64位:跟window一樣就ok了,
而linux下,32位:就用簡單的4位元組對齊就0k了,遇到double的話,簡單的將其當作兩個4位元組就ok了。

(ps,哎呀嗎,太累了,畫圖太多了,真心不容易啊,但是麼事,心不累,哈哈!!!)

轉載自這位大俠:http://blog.csdn.net/msdnwolaile/article/details/50158463

相關文章