深入研究:內存&字符串&結構體&共用體&枚舉


(1).malloc和free:
    0).正經步驟;
malloc申請:記得判斷p是否爲空
使用內存:只是用申請的,別超過範圍;不要改變p的值
free釋放內存:還要記得p=NULL,避免野指針;
    
    1).  void *指針指向類型不確定,在需要時可以轉換爲任意類型。int *p=(int *)malloc(1000*sizeof(int))返回一個void *可以在前面加上一個int(*)做轉換;若申請失敗,返回NULL;所以在用這段內存前一定要檢查(NULL == p).
2).在malloc和free期間不要改變p的值,否則程序會吃了內存(內存泄露),知道當前進程死掉,內存纔會被回收;在free後,事實上你申請的那段內存還能用,但是堆管理器告訴你不該使用了,free後最後來p =NULL;
3).gcc中malloc是以最小單位16字節的方式分配內存,當小於16字節(包括malloc(0))都會返回給你16字節內存;事實上假如你malloc了n個字節,超過返回地址的n字節仍能被操作,例如:int *p=(int *)malloc(30*sizeof(int));*(p+100)=10;printf("用的比申請的多,但第一百個仍能用,結果爲:\n",*(p+100));但這是極其危險的。



(2).編譯器在編譯程序時將程序分爲各個段:
代碼段:程序的可執行部分,直觀理解爲多個函數組成。(不可被改變)
數據段(.data):存放程序中的數據,存放那些被顯示初始化爲非0的全局變量和靜態局部變量(static)(普通局部變量不能算程序的數據,只能叫函數的數據,這放在棧裏面)
bss(ZI)段:存放那些被顯示初始化爲0或未被初始化的全局變量(沒被初始化的放在bss段裏就會被初始化爲0);本質上也屬於數據段。
補充:一些特殊的數據會放在代碼段,比如 char* p="badmer";
                                   比如const常量在單片機編譯器被放在代碼段;但是在gcc下還是被放在數據段,由編譯器做檢查。
  
  
(3).字符串:
    1).字符串的兩種存儲方式:
   字符串:char* p="badmer"; printf("p=%s\n",p);
字符數組:char a[]="bademr";printf("a=%s\n",a);

2)字符串與字符數組在存儲上本質區別:
   字符串:一個字符指針(棧裏,四字節),他指向內存中(代碼段裏)的一個字符串;字符串用法比較靈活,可定義在棧,堆上。
字符數組:定義一個數組,編譯器用字符串將其初始化然後捨棄掉。

3)他們在sizeof(), strlen()方面區別:
      
char *p="badmer";
        sizeof(p);           //4
        strlen(p);     //6    strlen()函數裏的參數是個字符指針

char a[]="badmer";
sizeof(a);           //7
        strlen(a);     //6

char a[3]="badmer";
sizeof(a);          //3
strlen(a);          //>=3;;;;;;20

  char d[6]="badmer";
 printf("%d\n",sizeof(d));          //6
      printf("%d\n",strlen(d));      //後面的可能是結束字符,也可能不是。>=6;;;;;;;12
 
 
char a[10]="badmer";
sizeof(a);          //10
   strlen(a);          //後面默認初始化爲0,ascll對應的就是'\0',所以是6

strlen():這個函數是檢測遇到了'/0',就結束。對那些你不造後面是啥的,結果也不確定。
        
       int mystrlen(const *p)
  {  
    int cnt=0;
     while(*p++ !='\0')
  cnt++;
  return cnt;
  }

(4).結構體對齊:
    1).對齊指令:
a.  #pragma pack(n) (n=1/2/4/8)  #pragma pack() //這兩個是成對出現的,前一個設置n字節對齊,後一個取消對齊,中間位作用域;這個廣泛支持C環境,但gcc下不建議。
  
b.  __attribute__((packed))   //使用時直接放在要進行內存對齊的類型定義的後面,然後它起作用的範圍只有加了這個東西的這一個類型  ;
         __attribute__((aligned(n)))使用時直接放在要進行內存對齊的類型定義的後面,然後它起作用的範圍只有加了這個東西的這一個類型。它的作用是讓整個結構體變量整體進行n字節對齊(不是結構體內各元素也要n字節對齊) 
 
2)結構體對齊要考慮:
 結構體整體本身必須安置在4字節對齊處,結構體對齊後的大小必須4的倍數(編譯器設置爲4字節對齊時,如果編譯器設置爲8字節對齊,則這裏的4是8)
 
      結構體中每個元素本身都必須對其存放,而每個元素本身都有自己的對齊規則,int型必須放在4倍數字節整數處(short爲2)。
 
文檔:http://www.cnblogs.com/dolphin0520/archive/2011/09/17/2179466.html
http://blog.csdn.net/sno_guo/article/details/8042332

3)測試代碼:
#include<stdio.h>

#define offsetof(type,member)       ((int)&((type *)0)->member)
#define container_of(ptr,type,member)      ({const typeof(((type *)0)->member) *__mptr=(ptr);(type *)((char *)__mptr-offsetof(type,member));})

//#define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) );})

 struct mystruct1
{                           //四字節對齊
	short a;                //4
	int b;                  //4
	char c;                 //4
};

typedef struct 
{                          //四字節對齊
	short a;               //2
	short b;               //2
	char c;                //2                 若爲int c ;那麼就是4
}ms2;

typedef struct 
{                          //128字節對齊
	short a;               //2
	short b;               //2
	int c;                //124              
}__attribute__((aligned(128)))ms3;


int main(void)
{   
     //測試兩個宏;測試ms1的對齊。
	struct mystruct1 my1;
	printf("the offset of  b is %d\n",offsetof(struct mystruct1,b));
	printf("the head pointer is %p\n",container_of(&(my1.c),struct mystruct1,c));
	printf("mystruct1 address is %p\n",&my1);
	
	 //測試ms2的對齊。
	 printf("the offset of  b is %d\n",offsetof(ms2,b));
	printf("the offset of  c is %d\n",offsetof(ms2,c));
	printf("the sizeof of ms2 is %d\n",sizeof(ms2));
	
	//測試ms3對齊
	
	 printf("the offset of  b is %d\n",offsetof(ms3,b));
	printf("the offset of  c is %d\n",offsetof(ms3,c));
	printf("the sizeof of ms3 is %d\n",sizeof(ms3));
	
	return 0;
}


(5).結構體相關宏
    1).結構體成員的兩種訪問方式本質上都是用指針訪問(./->)。
  代碼:struct.c
  
2)offsetof:
定義: #define offsetof(TYPE,MEMBER)      ((int)&((TYPE *)0)->MEMBER)

作用:用宏來計算結構體中某個元素和結構體首地址的偏移量

原理:我們虛擬一個type類型結構體變量,然後用type.member的方式來訪問那個member元素,繼而得到member相對於整個變量首地址的偏移量。
  
3).container_of
定義:#define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) );})

作用:知道一個結構體中某個元素的指針,反推這個結構體變量的指針。有了container_of宏,我們可以從一個元素的指針得到整個結構體變量的指針,繼而得到結構體中其他元素的指針。

原理:先用typeof得到member元素的類型定義成一個指針,然後用這個指針減去該元素相對於整個結構體變量的偏移量(偏移量用offsetof宏得到的),減去之後得到的就是整個結構體變量的首地址了,再把這個地址強制類型轉換爲type *即可

(6).共用體union
    1).共用體內的成員對應的是相同的一塊內存單元,不同類型的成員意味着對這一塊內存中內容的不同解析方式;本質上例如:int a=123;printf("%f\n",*((float *)&a));
2).sizeof(union xx)的大小與xx內最大變量的大小相同;其內成員的首地址都相同;
3).共用體的定義,操作方法與結構體類似;

4).大小端模式(只是方式不同):
a.最初是在計算機通信中,由於串口一次只能傳8位,所以通信是按byte 0,1,2,3發送,還是按byte 3,2,1,0發送
b.現在用於存儲時  按高地址存高字節(小端,現在PC,arm一般用這個)還是按高地址存低字節(大端,c51用這個)方式存儲。

兩種方法鑑別大小端:
	#include<stdio.h>
typedef union my_union
{
	int a;
	char b;
}mu;
//返回值爲1時表示是小端模式;爲0時是大端模式;
static int identify_little_endian()
{
	mu mu1;
	mu1.a=1;
	return mu1.b;
}
 int main(void)
 {
	 	//方法一,,,,本質方法
	 int j=0;
	 mu mu2;
	 mu2.a=1;
	 j=(int)*((char *)&mu2.b);
	 if(1==j)
		 printf("little endian mode.\n");
	 else
		 printf("big endian mode .\n");
	    //方法二,,,,
	  int i=0;
	 i=identify_little_endian();             
	 if(1==i)
		 printf("little endian mode.\n");
	 else
		 printf("big endian mode .\n");
	 return 0;	 
 }
	
補充:利用位運算移位和位與是不行的,他們始終是操作高/低字節。

十分有趣的東西:

typedef union MyTest
{
	int a;
	double b;
}test;

int main() {

	test c;
	c.a = 14;
	c.b = 12.13;
	printf("%d\n",c.a);    //1546188227
	printf("%d\n", c.b);   //1546188227
	printf("%lf\n", c.a);  //0.000000
	printf("%lf\n", c.b);  //12.130000

	printf("-----------\n");
	test d;
	d.b = 12.13;
	d.a = 14;
	printf("%d\n", d.a);    //14
	printf("%d\n", d.b);   //14
	printf("%lf\n", d.a);  //0.000000
	printf("%lf\n", d.b);  //12.129997
	return 0;
}


還可以嘗試分別賦給a,b與上面結果對比。

printf("-----------\n");
test e;
e.a = 14;
printf("%d\n", e.a);    //14
printf("%d\n", e.b);   //14
printf("%lf\n", e.a);  //0.000000
printf("%lf\n", e.b);  //-92559592117432153556203825612673749385392370145622614280765440.000000

printf("-----------\n");
test e;
e.b = 12.13;
printf("%d\n", e.a);    //1546188227
printf("%d\n", e.b);   //1546188227
printf("%lf\n", e.a);  //0.000000
printf("%lf\n", e.b);  //12.130000

似乎int是決不可能用%lf解析出來,但是double類型的可用%d解析出來。。。上面僅有先賦double,再賦int能打出兩個值。猜測double能完全覆蓋int的內存空間,int只能部分覆蓋,因爲損失部分精度。


(7).枚舉enum(enumeration)(把能取的值一一列舉出來作爲一個集合)
     1).枚舉在c語言中就是一個符號常量集(成員間用的是,,不是;了);其中的常量是int類型;一個枚舉變量值爲成員中的某個值;
 
2).枚舉值常量是全局變量,你可以自己賦值(值不要相同,編譯不出錯,程序會錯),也可以是默認值(默認從0開始。。的異世界,若有自定義的,從自定義的值開始)
3).多個枚舉間,若是有相同的成員(名字)是不可以的。但是宏定義可以,取最後一次值。
4).enum的定義是可以沒有類型直接來變量的;不可存在與成員同樣的全局變量,局部變量卻沒關係(gcc下都沒關係)。(一開始說了咯,成員是全局變量)
 
5).當我們定義一個常量的可取值是一個有限集合,存在多選一的關係,辣麼就用枚舉;沒關係的、不存在多選一的還是用宏定義吧。
 
#include<stdio.h>

enum 
{
	SUN,
	MON,
	TUE,
	WEN,
	THU,
	FRI,
	SAT,
}today,yesterday;

//int SUN=5;                 // error: ‘SUN’ redeclared as different kind of symbol
int a=12;
int main(int argc,char **argv)
{
//	int SUN=10;              //沒警告,沒錯誤,值取10
	today=SUN;               //值取0
	int a=13;
	printf("today is %d\n",SUN);
	printf("%d\n",a);
	return 0;
	
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章