1. 程式人生 > >C 語言變長陣列 struct 中 char data[0] 的用法

C 語言變長陣列 struct 中 char data[0] 的用法

1、結構體記憶體佈局(padding)

為了讓CPU能夠更舒服地訪問到變數,struct中的各成員變數的儲存地址有一套對齊的機制。這個機制概括起來有兩點:第一,每個成員變數的首地址,必須是它的型別的對齊值的整數倍,如果不滿足,它與前一個成員變數之間要填充(padding)一些無意義的位元組來滿足;第二,整個struct的大小,必須是該struct中所有成員的型別中對齊值最大者的整數倍,如果不滿足,在最後一個成員後面填充。

The following typical alignments are valid for compilers from MicrosoftBorland, and 

GNU when compiling for 32-bit x86:

  • char (one byte) will be 1-byte aligned.
  • short (two bytes) will be 2-byte aligned.
  • An int (four bytes) will be 4-byte aligned.
  • float (four bytes) will be 4-byte aligned.
  • double (eight bytes) will be 8-byte aligned on Windows and 4-byte aligned on Linux.
  • long double
     (twelve bytes) will be 4-byte aligned on Linux.
  • Any pointer (four bytes) will be 4-byte aligned on Linux. (eg: char*, int*)

The only notable difference in alignment for a 64-bit linux system when compared to a 32 bit is:

  • double (eight bytes) will be 8-byte aligned.
  • long double (Sixteen bytes) will be 16-byte aligned.
  • Any pointer (eight bytes) will be 8-byte aligned.

案例一:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct s1
{
     char ch,*ptr;
     union
     {
         short a,b;
         unsigned int c:2,d:1;
     };
     struct s1 *next;
};

int main()
{
      
        printf("%d\n",sizeof(struct s1));
        return 0;
}
struct s1
{
       char ch,*ptr;    //ch和*ptr各佔4bit,共8bit
       union   //按照最長的計算,union佔4bit
      {
            short a,b;
            unsigned int c:2,d:1;
       }
       struct s1 *next;  //4bit
}

案例二:

struct s2{
	char ch;
	char *ptr;
	union {
		short a,b;
		unsigned int c:2,d:1;
	};
	int x;
	double y;
	struct s2 *next;
};
struct s2{
	char ch;
	char *ptr; //ch和*ptr各佔4bit,共8bit
	union {   //按照最長的計算,union佔4bit
		short a,b;
		unsigned int c:2,d:1;
	};
	int x; //int佔4bit
	double y;//double佔8bit
	struct s2 *next;//padding 到8bit
};//32 bit


案例三:

struct s3{
	char ch;
	char *ptr;
	union {
		short a,b;
		unsigned int c:2,d:1;
	};
	int x;
	double y;
	struct s2 p;
};

struct s3{
	char ch;
	char *ptr; //ch和*ptr各佔4bit,共8bit
	union {   //按照最長的計算,union佔4bit
		short a,b;
		unsigned int c:2,d:1;
	};
	int x; //int佔4bit
	double y;//double佔8bit
	struct s2 p;//32
};//24+32=56 bit

變長陣列

摘要:在實際的程式設計中,我們經常需要使用變長陣列,但是C語言並不支援變長的陣列。此時,我們可以使用結構體的方法實現C語言變長陣列。

struct MyData 
{
   int nLen;
   char data[0];
}; 


在結構中,data是一個數組名;但該陣列沒有元素該陣列的真實地址緊隨結構體MyData之後,而這個地址就是結構體後面資料的地址(如果給這個結構體分配的內容大於這個結構體實際大小,後面多餘的部分就是這個data的內容);這種宣告方法可以巧妙的實現C語言裡的陣列擴充套件。
實際用時採取這樣:
struct MyData *p = (struct MyData *)malloc(sizeof(struct MyData )+strlen(str))
這樣就可以通過p->data 來操作這個str。

這樣就可以通過p->data 來操作這個str。

程式例項:

struct MyData 
{
int nLen;
char data[0];
};

int main()
{
 int nLen = 10;
char str[10] = "123456789";

cout << "Size of MyData: " <<sizeof(MyData) << endl;

MyData *myData = (MyData*)malloc(sizeof(MyData) +10);
memcpy(myData->data, str, 10);

cout << "myData's Data is: " << myData->data << endl;

free(myData);

return 0;
}


輸出:

/Size of MyData:
   4
/myData"s Data is: 123456789

以下是摘自:http://bbs.chinaunix.net/thread-1455677-1-1.html

我想舉一個自己最近在專案中犯的錯誤來說明要踏踏實實做人,不要做裝B青年 
在程式碼中,我需要在一個library和一個daemon之間通過socket傳送資料包,包的格式定義如下(為了簡化,我就用最簡單的資料型別舉例):

  1. typedef struct {
  2.         int head;
  3.         int size; //指明整個包的長度
  4.         char reply;
  5.         char data[0];
  6. } packet;
  7. packet*  cmd = malloc (sizeof(packet) + 20);
  8. memcpy (packet->data, some_data, 20);
複製程式碼

daemon將上面分配的cmd包傳送給library,library接收到包後,需要將data欄位中的資料取出來。size指明瞭整個包的長度,但沒有欄位指明資料的長度。我需要這麼一個指明資料長度的欄位嗎?作為一個裝B青年,我認為當然不需要,於是我這樣來計算資料的長度:
  1. #define offsetof(type, element) ((int)&((type *)0)->element)
  2. static inline size_t packet_data_len(packet* cmd) {
  3.     assert(cmd);
  4.     return cmd->size - offsetof(packet, data);
  5. }
  6. memcpy (buffer_to_receive_data, cmd->data, packet_data_len (cmd));
複製程式碼

於是乎,這段程式成功的給我帶來了無數的bug,莫名奇妙的segfault,奇怪的資料錯誤,還是有部分時間的正常工作。當然,最終我還是找到了問題:
sizeof (packet) == 12;
這是合理的,char reply被padding成了4個位元組,而char data[0]位元組為0。
但,offsetof(packet, data) == 9,在計算偏移時,char reply為一個位元組,沒有padding。
所以packet_data_len每次都會返回比真實的資料多3個位元組 ……

最後我還是老老實實加了個data_len欄位指明資料的長度。