1. 程式人生 > >結構體、結構體記憶體對齊

結構體、結構體記憶體對齊

1、結構體

1.1、概述

在C語言中,除了常見的基本資料型別(整數型別short、int、long和浮點型別float、double)外,還有派生型別,如指標型別、陣列型別、結構型別、共用體型別等。 結構體是基本資料型別不能滿足需求時,使用者自己指定的一種資料結構,由不同型別的資料組合成一個整體,以便引用,這些組合在一個整體中的資料是互相聯絡的,這樣的資料結構稱為結構體。

宣告一個結構休型別的一般形式如下:

struct 結構體名
{成員列表};

結構體名,用作結構體型別的標誌,它又稱 結構體標記,大括號內是該結構體中的各個成員,由它們組成一個結構體,對各成員都應進行型別宣告如:

型別名 成員名;

也可以成員列表稱為域表,第一個成員也稱為結構體中的一個域。成員名定名規則與定義變數名規則相同。

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};

1.2、定義結構體型別變數的方法

前面只是指定了一個結構體型別,它相當於一個模型,但其中並無具體資料,系統對之也不分配實際記憶體單元,為了能在程式中使用結構型別的資料,應當定義結構體型別的變數,並在其中存放具體的資料,可以採取以下3種方法定義結構體型別變數。

(1)先宣告結構體型別再定義變數名

如上面已定義了一個結構體型別 struct student,可以用它來定義變數。如:

struct student{  //結構體型別名
    ...
    ...
    ...
}student1, student2 //結構體變數名

定義了 student1, student2 為 struct student 型別的變數。

(2)在宣告型別的同時定義變數

例如:

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}student1,
student2;

它的作用與第一種方法相同,即定義了兩個 struct student 型別的變數 student1, student2 這種形式的定義的一般形式為

struct 結構體名
{
    成員表列
}變數名錶列;

(3)直接定義結構型別變數

其一般形式為:

struct
{
    成員表列
}變數名錶列;

即不出現結構體名。

關於結構體型別,有幾點要說明:

  1. 型別與變數是不同的概念,不是混同,只能對變數賦值,存取或運算,而不能對一個型別賦值,存取或運算。在編譯時,對型別是不分配空間的,只對變數分配空間。
  2. 對結構體中的成員(即 域)可以單元使用,它的作用與地位相當於普通變數。
  3. 成員也可以是一個結構體變數。如:
struct date // 宣告一個結構體型別
{
    int month;
    int day;
    int year;
}

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    struct date birthday;
    char addr[30];
}student1, student2;

先宣告一個 struct date 型別,它代表 日期 包括3個成員 month, day, year。然後在宣告 struct student 型別時,將成員 birthday 指定為 struct date 型別。

  1. 成員名可以與程式中的變數名相同,二者不代表同一物件。

1.3、結構體變數的引用

1.4、結構體變數的初始化

1.5、結構體陣列

1.6、指向結構體型別資料的指標

一個結構體變數的指標就是該變數所佔據的記憶體段的起始地址,可以設一個指標變數,用來指向一個結構體變數,此時該指標變數的值是結構體變數的起始地址。指標變數也可以用來指向結構體陣列中的元素。

2、結構體記憶體對齊

2.1、簡述

有沒有想過一個問題,某些時候我想4位元組對齊,有些時候我又想1位元組或者8位元組對齊,那麼怎麼解決這個問題呢? 此時,#pragma pack(push) 和#pragma pack(pop) 以及#pragma pack()應運而生。 #pragma pack(push) #pragma pack(n) //自定義按n位元組對齊 …… ……//結構體 …… #pragma pack() //取消自定義的對齊方式,恢復預設對齊。 或者使用 #pragma pack(pop) //pop會讓編譯器回到push之前的對齊方式

注意,#pragma pack() 取消自定義對齊方式,恢復預設方式,而push之後pop是回到push指令之前的對齊方式。

2.5、#pragma pack()語法

語法:

#pragma pack( [show] | [push | pop] [, identifier], n )

說明: 1,pack提供資料宣告級別的控制,對定義不起作用; 2,呼叫pack時不指定引數,n將被設成預設值; 3,一旦改變資料型別的alignment,直接效果就是佔用memory的減少,但是performance會下降;

語法具體分析: 1,show:可選引數;顯示當前packing aligment的位元組數,以warning message的形式被顯示; 2,push:可選引數;將當前指定的packing alignment數值進行壓棧操作,這裡的棧是the internal compiler stack,同時設定當前的packing alignment為n;如果n沒有指定,則將當前的packing alignment數值壓棧; 3,pop:可選引數;從internal compiler stack中刪除最頂端的record;如果沒有指定n,則當前棧頂record即為新的packing alignment數值;如果指定了n,則n將成為新的packing aligment數值;如果指定了identifier,則internal compiler stack中的record都將被pop直到identifier被找到,然後pop出identitier,同時設定packing alignment數值為當前棧頂的record;如果指定的identifier並不存在於internal compiler stack,則pop操作被忽略; 4,identifier:可選引數;當同push一起使用時,賦予當前被壓入棧中的record一個名稱;當同pop一起使用時,從internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier沒有被找到,則忽略pop操作; 5,n:可選引數;指定packing的數值,以位元組為單位;

注意:

#pragma pack(push) 
#pragma pack(4)	
//上面兩句程式碼等價於下面這句程式碼:
#pragma pack(push,4) 

3、結構體經典面試題

(1)、什麼是結構體? (2)、一般在什麼情況下用到結構體? (3)、什麼是結構體記憶體對齊?為什麼要對齊?怎樣對齊? (4)、對齊引數如何設定?可以設定為按照任意位元組數對齊嗎? (5)、如何知道結構體某個成員相對於結構體起始位置的偏移量?