1. 程式人生 > >結構體中記憶體對齊&&大端小端模式

結構體中記憶體對齊&&大端小端模式

(題目來自牛客網)

在一個64位的作業系統中定義如下結構體:

struct st_task
{
    uint16_t id;
    uint32_t value;
    uint64_t timestamp;
};
同時定義fool函式如下:

void fool()
{
    st_task task = {};
    uint64_t a = 0x00010001;
    memcpy(&task, &a, sizeof(uint64_t));
    printf("%11u,%11u,%11u", task.id, task.value, task.timestamp);
}
上述fool()程式的執行結果為()

A   1
00 B 110 C 011 D 001

變數含義:

按照posix標準,一般整形對應的*_t型別為:
1位元組     uint8_t
2位元組     uint16_t
4位元組     uint32_t
8位元組     uint64_t

memcpy函式:

void *memcpy(void*dest, const void *src, size_t n);

由src指向地址為起始地址的連續n個位元組的資料複製到以destin指向地址為起始地址的空間內。

大端模式和小端模式:

大端模式:認為第一個位元組是最高位位元組,也就說按照從低地址到高地址的順序存放資料的高位位元組到低位位元組。

小端模式:認為第一個位元組是最低位位元組,也就是說按照從低地址到高地址的順序存放資料的低位位元組到高位位元組。

一般的作業系統都是小端,而網絡卡抓取的和網路上傳輸的資料一般都大端。

在C99標準中,對於記憶體對齊的細節沒有作過多的描述,具體的實現交由編譯器去處理,所以在不同的編譯環境下,記憶體對齊可能略有不同,但是對齊的最基本原則是一致的,對於結構體的位元組對齊主要有下面兩點:

  1)結構體每個成員相對結構體首地址的偏移量(offset)是對齊引數的整數倍,如有需要會在成員之間填充位元組。編譯器在為結構體成員開闢空間時,首先檢查預開闢空間的地址相對於結構體首地址的偏移量是否為對齊引數的整數倍,若是,則存放該成員;若不是,則填充若干位元組,以達到整數倍的要求。

  2)結構體變數所佔空間的大小是對齊引數大小的整數倍。如有需要會在最後一個成員末尾填充若干位元組使得所佔空間大小是對齊引數大小的整數倍。

64位和32位平臺下C/C++結構記憶體對齊:

   1. 在 64 位 Linux 下,結構體欄位預設按 8 位元組對齊;32 位 Linux 下,預設 4 位元組對齊。

   2. 顯示指定對齊方式時,會受到機器字長的約束,即 64 位 Linux 下可以按 8 位元組及以下的任意位元組對齊,32 位只能按 4 位元組及以下任意位元組對齊。

在這個題目中,變數a佔8個位元組。
採用小端模式儲存時,低地址儲存低位元組。應為:
10 00 10 00 00 00 00 00
採用大端模式儲存時,低地址儲存高位元組。應為:
00 00 00 00 00 01 00 01

struct st_task
{
    uint16_t id;
    uint32_t value;
    uint64_t timestamp;
};

變數id佔2個位元組,偏移量為0,變數value佔4個位元組,偏移量為2個位元組,不是變數大小的整數倍,因此編譯器會在id後面補兩個位元組,使得value的偏移量變為4個位元組。timestamp佔8個位元組,偏移量為8個位元組,不需要調整,整個結構體大小為16個位元組,是所有成員大小的整數倍,因此不需要調整。

因此在使用memcpy函式賦值以後,在小端模式下,id的值為10 00,接下來的10 00為填充位元組,value的值為00 00 00 00,timestamp沒有接收到有效賦值,也為00 00 00 00 00 00 00 00。將使用小端模式儲存的位元組轉換為實際值,則id為0x0001,value為0x00000000,timestamp為0x0000000000000000。