深入研究:内存&字符串&结构体&共用体&枚举


(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;
	
}

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