一、零長數組(另一篇文章參考這裡)
在標準 C 或者 C++ 中由於不支援 0 長度的數組,所以 int array[0]; 這樣的定義是非法的。不過有些編譯器(如GCC)的擴充功能支援 0 長度的數組。
在 C 中,0 長度的數組的主要用途是用來作為結構體的最後一個成員,然後用它來訪問此結構體對象之後的一段記憶體(通常是動態分配的記憶體)。由於其非標準性,在程式中盡量避免使用 0 長度的數組。作為替換,可以使用 C99 標準中的不完整數組來替換 0 長度的數組定義。如:
typedef struct _X {
int a;
char array[]; //注意,因為是不完整數組,因此不可以計算sizeof(X),無法編譯通過
} X;
在GNU的gcc-4.4.0的官方指南中的5.14節Arrays of Length Zero一節中,它是如此寫道:
struct line {
int length;
char contents[0];
};
//...ommit code here
struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length);
thisline->length = this_length;
這個用法主要用於變長Buffer,struct line的大小為4,結構體中的contents[0]不佔用任何空間,甚至是一個指標的空間都不佔(待會兒下面的範例程式碼會驗證),contents在這兒只是表示一個常量指標,這個特性是用編譯器來實現的,即在使用 thisline->contents的時候,這個指標就是表示分配記憶體位址中的某塊buffer,比如malloc (sizeof (struct line) + this_length)返回的是0x8f00a40,thisline->contents指向的位置就是(0x8f00a40 + sizeof(struct line)),而這兒sizeof(struct line)僅僅是一個int的四位元組。
對於這個用法,我們定義的結構體指標可以指向任意長度的記憶體buffer,這個技巧在變長buffer中使用起來相當方便。可能有朋友說,為什麼不把最後的contents直接定義為一個指標呢?這兒的差別是這樣的,如果定義為一個指標,它需要佔用4Bytes,並且在申請好記憶體後必須人為賦地址才可以。如果使用這個用法,這個常量指標不佔用空間,並且無需賦值。
但是,方便並不是絕對的,在釋放分配的記憶體的時候,由於函數free會認為*thisline 只是指向一個4位元組的指標,即只會釋放length的空間,而對於後面佔據大頭的buffer卻視而不見,這個就需要人為幹預;而對於後面的聲明指標的方式,則可以直接用Free(thisline->contents)的方式釋放掉分配的記憶體。(這地方說的不明白,讓我理解之後,感覺此話是錯誤的。)
如果將零長數組array換成指標*array來使用的話,指標必須重新分配一段記憶體之後才能使用,那麼當想要用socket發送結構體指標的時候,並不會將指標array申請的記憶體發送過去,因為是不連續的,所以接受socket發送來的資料後,會發現該資料並不是自己想要的。
ASSERT:除非必要,不要輕易使用這個功能,GNU C下可以編譯通過,所以你在使用vc++,那就不用嘗試了,編譯都無法通過。
C語言: 驗證0長數組和__attribute__((packed))
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <string.h>
04
05 #define offsetof(S,t) (size_t)&(((S *)0)->t) //求結構體中位移量的宏,C++中存在此宏,C中需自己定義
06
07 struct zero_arry_t{
08 unsigned int i;
09 char arry[];
10 };
11
12 struct Test{
13 int len;
14 char content[0];
15 };
16
17 typedef struct _S1{
18 char a;
19 char b;
20 double c;
21 }S1;
22
23 typedef struct _S2{
24 char a;
25 char b;
26 double c;
27 }__attribute__((packed)) S2; //__attribute__((packed))的作用就是告訴編譯器取消結構在編譯過程中的最佳化對齊
28
29 typedef struct _Y
30 {
31 int a;
32 int b;
33 char c;
34 char content[0];
35 } Y;
36
37
38 int main()
39 {
40 //驗證0長度數組
41 char c0 = 'a', c1 = 'b', c2='c', c3='d';
42 printf("c0=%c, c1=%c, c2=%c, c3=%c\n&c0=%p, &c1=%p, &c2=%p, &c3=%p\n", c0, c1, c2, c3, &c0, &c1, &c2, &c3);
43 struct Test t;
44 t.len = 0x01020304;
45 char *q = t.content;
46 printf("sizeof(t)=%u, sizeof(t.content)=%u\n", sizeof(t),sizeof(t.content)); //列印4 0, content本身不佔空間
47 printf("&t=%p, &t.len=%p, t.content=%p, &t.content=%p\n", &t, &t.len, t.content, &t.content);//
48 strcpy(t.content, "123");
49 //發現 c0 c1 c2 c3的位置的內容被p->content所修改
50 printf("c0=%c, c1=%c, c2=%c, c3=%c\n&c0=%p, &c1=%p, &c2=%p, &c3=%p\n", c0, c1, c2, c3, &c0, &c1, &c2, &c3);
51
52 char buf[1024] = {0};
53 struct Test *p = (struct Test *)buf;
54 p->len = 0x01020304;
55 strcpy( p->content, "abcd");
56 printf("\np=&buf=%p, p->content=%p, p->content=%s\n", buf, p->content, p->content);
57 int k;
58 for(k=0; k<10; ++k) //注意觀察這十個位置的值
59 printf("address %p: buf[%d]=%d\n", buf+k, k, buf[k]);
60
61 //關於offsetof宏,以及 __attribute__((packed))屬性
62 printf("\nsizeof(S1)=%u, offsetof(S1,c)=%u\n", sizeof(S1),offsetof(S1,c));
63 //使用__attribute__後,結構體大小和成員的位移量都發生變化
64 printf("sizeof(S2)=%u, offsetof(S2,c)=%u\n", sizeof(S2),offsetof(S2,c));
65 //對於有padding(補齊)的結構體Y,其sizeof(Y)和offsetof(Y, content)的大小不一致,參考這個文章
66 printf("sizeof(Y)=%u, offsetof(Y, content)=%u, offsetof(Y, c)=%u\n", sizeof(Y), offsetof(Y, content), offsetof(Y, c));
67
68 getchar();
69 return 0;
70 }
運行結果如下:
二、__attribute__
1. __attribute__ ((packed)) 的作用就是告訴編譯器取消結構在編譯過程中的最佳化對齊,按照實際佔用位元組數進行對齊,是GCC特有的文法。這個功能是跟作業系統沒關係,跟編譯器有關,gcc編譯器不是緊湊模式的,我在windows下,用vc的編譯器也不是緊湊的,用tc的編譯器就是緊湊的。例如:
在GCC下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非緊湊模式)
在GCC下:struct my{ char ch; int a;}__attrubte__ ((packed)) sizeof(int)=4;sizeof(my)=5
2. __attribute__關鍵字主要是用來在函數或資料聲明中設定其屬性。給函數賦給屬性的主要目的在於讓編譯器進行最佳化。函式宣告中的 __attribute__((noreturn)),就是告訴編譯器這個函數不會返回給調用者,以便編譯器在最佳化時去掉不必要的函數傳回碼。
GNU C的一大特色就是__attribute__機制。__attribute__可以設定函數屬性(Function Attribute)、變數屬性(Variable Attribute)和類型屬性(Type Attribute)。
__attribute__書寫特徵是:__attribute__前後都有兩個底線,並且後面會緊跟一對括弧,括弧裡面是相應的__attribute__參數。
__attribute__文法格式為:
__attribute__ ((attribute-list))
其位置約束:放於聲明的尾部“;”之前。
函數屬性(Function Attribute):函數屬性可以協助開發人員把一些特性添加到函式宣告中,從而可以使編譯器在錯誤檢查方面的功能更強大。__attribute__機制也很容易同非GNU應用程式做到相容之功效。
GNU CC需要使用 –Wall編譯器來擊活該功能,這是控制警告資訊的一個很好的方式。
packed屬性:使用該屬性可以使得變數或者結構體成員使用最小的對齊,即對變數是一位元組對齊,對域(field)是位對齊。
舉例:
/* __attribute__ ((packed)) 的位置約束是放於聲明的尾部“;”之前 */
struct str_struct{
__u8 a;
__u8 b;
__u8 c;
__u16 d;
} __attribute__ ((packed));
/* 當用到typedef時,要特別注意__attribute__ ((packed))放置的位置,相當於:
* typedef struct str_stuct str;
* 而struct str_struct 就是上面的那個結構。
*/
typedef struct {
__u8 a;
__u8 b;
__u8 c;
__u16 d;
} __attribute__ ((packed)) str;
/* 在下面這個typedef結構中,__attribute__ ((packed))放在結構名str_temp之後,其作用是被忽略的,注意與結構str的區別。*/
typedef struct {
__u8 a;
__u8 b;
__u8 c;
__u16 d;
}str_temp __attribute__ ((packed)); //這樣不起作用,還可能編譯不通過