C語言中文網 目錄

結構體字節對齊,C語言結構體字節對齊詳解

問大家一個問題:
struct STUDENT
{
    char a;
    int b;
}data;
如上結構體變量 data 占多少字節?char 占 1 字節,int 占 4 字節,所以總共占 5 字節嗎?我們寫一個程序驗證一下:
# include <stdio.h>
struct STUDENT
{
    char a;
    int b;
}data;
int main(void)
{
    printf("%p, %p\n", &data.a, &data.b);  //%p是取地址輸出控制符
    printf("%d\n", sizeof(data));
    return 0;
}
輸出結果是:
00427E68, 00427E6C
8

我們看到 data 不是占 5 字節,而是占 8 字節。變量 a 的地址是從 00427E68 到 00427E6B,占 4字 節;變量 b 的地址是從 00427E6C 到 00427E6F,也占 4 字節。b 占 4 字節我們能理解,但 a 是 char 型,char 型不是占 1 字節嗎,這里為什么占 4 字節?其實不是它占了 4 字節,它占的還是 1 字節,只不過結構體中有一個字節對齊的概念。

什么叫字節對齊?我們知道結構體是一種構造數據類型,里面可以有不同數據類型的成員。在這些成員中,不同的數據類型所占的內存空間是不同的。那么系統是怎么給結構體變量的成員分配內存的呢?或者說這些成員在內存中是如何存儲的呢?通過上面這個例子我們知道肯定不是順序存儲的。

那么到底是怎么存儲的呢?就是按字節對齊的方式存儲的!即以結構體成員中占內存最多的數據類型所占的字節數為標準,所有的成員在分配內存時都要與這個長度對齊。我們舉一個例子:我們以上面這個程序為例,結構體變量 data 的成員中占內存最多的數據類型是 int 型,其占 4 字節的內存空間,那么所有成員在分配內存時都要與 4 字節的長度對齊。也就是說,雖然 char 只占 1 字節,但是為了與 4 字節的長度對齊,它后面的 3 字節都會空著,即:

a
b

所謂空著其實也不是里面真的什么都沒有,它就同定義了一個變量但沒有初始化一樣,里面是一個很小的、負的填充字。為了便于表達,我們就暫且稱之為空好了。

如果結構體成員為:
struct STUDENT
{
    char a;
    char b;
    int c;
}data;
那么這三個成員是怎么對齊的?a 和 b 后面都是空 3 字節嗎?不是!如果沒有 b,那么 a 后面就空 3 字節,有了 b 則 b 就接著 a 后面填充。即:

a b
c

所以這時候結構體變量 data 仍占 8 字節。我們寫一個程序驗證一下:
# include <stdio.h>
struct STUDENT
{
    char a;
    char b;
    int c;
}data;
int main(void)
{
    printf("%p, %p, %p\n", &data.a, &data.b, &data.c);  //%p是取地址輸出控制符
    printf("%d\n", sizeof(data));
    return 0;
}
輸出結果是:
00427E68, 00427E69, 00427E6C
8

這時我們發現一個問題:所有成員在分配內存的時候都與 4 字節的長度對齊,多個 char 類型時是依次往后填充,但是 char 型后面的 int 型為什么不緊接著后面填充?為什么要另起一行?也就是說,到底什么時候是接在后面填充,什么時候是另起一行填充?

我們說,所有的成員在分配內存時都要與所有成員中占內存最多的數據類型所占內存空間的字節數對齊。假如這個字節數為 N,那么對齊的原則是:理論上所有成員在分配內存時都是緊接在前一個變量后面依次填充的,但是如果是“以 N 對齊”為原則,那么,如果一行中剩下的空間不足以填充某成員變量,即剩下的空間小于某成員變量的數據類型所占的字節數,則該成員變量在分配內存時另起一行分配。

下面再來舉一個例子,大家覺得下面這個結構體變量data占多少字節?
struct STUDENT
{
    char a;
    char b;
    char c;
    char d;
    char e;
    int f;
}data;
首先最長的數據類型占 4 字節,所以是以 4 對齊。然后 a 占 1 字節,b 接在 a 后面占 1 字節,c 接在 b 后面占 1 字節,d 接在 c 后面占 1 字節,此時滿 4 字節了,e 再來就要另起一行。f 想緊接著 e 后面分配,但 e 后面還剩 3 字節,小于 int 類型的 4 字節,所以 f 另起一行。即該結構體變量分配內存時如下:

a b c d
e
f

即總共占 12 字節。我們寫一個程序驗證一下:
# include <stdio.h>
struct STUDENT
{
    char a;
    char b;
    char c;
    char d;
    char e;
    int f;
}data;
int main(void)
{
    printf("%p, %p, %p, %p, %p, %p\n", &data.a, &data.b, &data.c, &data.d, &data.e, &data.f);  //%p是取地址輸出控制符
    printf("%d\n", sizeof(data));
    return 0;
}
輸出結果是:
00427E68, 00427E69, 00427E6A, 00427E6B, 00427E6C, 00427E70
12

現在大家應該能掌握字節對齊的精髓了吧!下面給大家出一個題目試試掌握情況。我們將前面的結構體改一下:
struct STUDENT
{
    char a;
    int b;
    char c;
}data;
即將原來第二個和第三個聲明交換了位置,大家看看現在 data 變量占多少字節?沒錯,是 12 字節。首先最長類型所占字節數為 4,所以是以 4 對齊。分配內存的時候 a 占 1 字節,然后 b 想緊接著 a 后面存儲,但 a 后面還剩 3 字節,小于 b 的 4 字節,所以 b 另起一行分配。然后 c 想緊接著 b 后面分配,但是 b 后面沒空了,所以 c 另起一行分配。所以總共 12 字節。內存分配圖如下所示:

a
b
c

下面寫一個程序驗證一下:
# include <stdio.h>
struct STUDENT
{
    char a;
    int b;
    char c;
}data;
int main(void)
{
    printf("%p, %p, %p\n", &data.a, &data.b, &data.c);  //%p是取地址輸出控制符
    printf("%d\n", sizeof(data));
    return 0;
}
輸出結果是:
00427E68, 00427E6C, 00427E70
12

我們看到,同樣三個數據類型,只不過交換了一下位置,結構體變量data所占的內存空間就由8字節變成12字節,多了4字節。這就告訴我們,在聲明結構體類型時,各類型成員的前后位置會對該結構體類型定義的結構體變量所占的字節數產生影響。沒有規律的定義會增加系統給結構體變量分配的字節數,降低內存分配的效率。但這種影響對操作系統來說幾乎是可以忽略不計的!所以我們在寫程序的時候,如果有心的話,聲明結構體類型時就按成員類型所占字節數從小到大寫,或從大到小寫。但是如果沒有按規律書寫的話也不要緊,聲明結構體類型時并非一定要從小到大聲明,只是為了說明“字節對齊”這個概念!而且有時候為了增強程序的可讀性我們就需要沒有規律地寫,比如存儲一個人的信息:
struct STUDENT
{
    char name[10];
    int age;
    char sex;
    float score;
}data;
正常的思維是將“性別”放在“年齡”后面,但如果為了內存對齊而交換它們的位置,總讓人覺得有點別扭。所以我說“盡量”有規律地寫!

這時又有人會提出一個問題:“上面這個結構體變量 data 中有成員 char name[10],長度最長,是 10,那是不是要以 10 對齊?”不是,char a[10] 的本質是 10 個 char 變量,所以就把它當成 10 個 char 變量看就行了。所以結構體變量 data 中成員最長類型占 4 字節,還是以 4 對齊。該結構體變量分配內存時情況如下:

name[0] name[1] name[2] name[3]
name[4] name[5] name[6] name[7]
name[8] name[9]
age
sex
float

總共 24 字節,我們寫一個程序驗證一下:
# include <stdio.h>
struct STUDENT
{
    char name[10];
    int age;
    char sex;
    float score;
}data;
int main(void)
{
    printf("%p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p\n", &data.name[0], &data.name[1], &data.name[2], &data.name[3], &data.name[4], &data.name[5], &data.name[6], &data.name[7], &data.name[8], &data.name[9], &data.age, &data.sex, &data.score);
    printf("%d\n", sizeof(data));
    return 0;
}
輸出結果是:
00427E68, 00427E69, 00427E6A, 00427E6B, 00427E6C, 00427E6D, 00427E6E,
00427E6F, 00427E70, 00427E71, 00427E74, 00427E78, 00427E7C
24

精美而實用的網站,提供C語言、C++、STL、Linux、Shell、Java、Go語言等教程,以及socket、GCC、vi、Swing、設計模式、JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo
极速pk10开户