程序与内存:
内存用来存放程序运行之中的一些临时变量,是程序运行的地方。
内存的管理由操作系统统一完成。程序根据实际情况获取内存,使用内存,释放内存。
在64位操作系统内,指针所占的内存为8个字节,而在32位操作系统中,指针占4个字节。
三种内存来源:栈(stack),堆(heap),数据区(.data)
栈的详解:
(先进后出) (小块内部内存,且内存大小固定)
1.运行时自动分配,自动回收,栈可以自动管理,不需要程序员手工干预。
2.反复使用:栈内存就是内存中是一个固定的空间区域,
3.由于栈的反复使用,导致栈内存是脏内存,每次使用完程序不会去清理,因此二次分配使用时保留的是原本的值。示例体现:定义局部变量时局部变量的值是随机的,所以定义局部变量的时候必须初始化。
4.临时性:函数不能返回栈变量的指针(局部变量的指针)。因为这个函数执行完返回后这个局部变量已经不在了,但是由于局部变量是分配在栈上面的,栈内的内存仍然可以使用,但是此时的内存地址和之前的局部变量已经没有联系了。
堆内存:
1.由堆管理器(操作系统的一个模块)管理,堆的内存分配方式灵活,按需分配。各进程手动按需申请,使用完手动释放。手工的意思是写代码去申请 malloc 释放 free.
2.脏内存:使用者使用完释放前不会清除,因此也是脏的。
3.临时性:仅在malloc和free之间属于本内存,可以访问。
注:malloc 返回的是一个 void *类型的指针(void早期被翻译为空型,表示万能类型),实质上 malloc返回的是堆管理器分配给程序此次申请的那段内存空间的首地址(以数字形式表示,这个数字代表内存地址)。malloc为程序申请的内存空间可以存放各种类型的变量(malloc为程序申请内存空间时,不关注这段内存是用来存储什么类型的变量,由程序自己决定)
使用的格式:1.申请和绑定 int(char float....)*p int(*)malloc(内存空间大小)
2.检验是否成功,if(p==NULL) //检验是否成功
{
printf("malloc error .\n");
return -1;
}
malloc的返回值:成功申请之后返回这个内存空间的指针,申请失败时返回NULL。所以malloc获取的内存指针使用前必须先检验是否为NULL。
3.使用堆内存:*(p+x)=123; *(p+y)=564;
4.释放内存:free(p).
malloc申请的内存空间在使用完之后要free释放掉,在回收之后就不应该再使用了。
注意:在free归还这段内存之前,指向这段内存的指针p一定不能丢,(不能给p另外赋值)。
数据段,代码段,bss段:
编译器在编译程序时,将程序中的所有元素分成一些组成部分,各部分构成一个段,所以段是可执行程序的组成部分。
1.代码段:程序中可以被执行的部分,直观理解为函数堆叠组成的;
2.数据段(数据区,静态数据区,静态区):C语言程序中的数据,例如全局变量,(注意:局部变量算是函数的数据,不能算是程序的数据)
3.bss段(ZI(zero initial)段):bss段是被初始化为0的数据段。
备注:数据段(.data)和bss段二者没有本质的区别,都是用来存放C语言程序中的全局变量。
只不过将显示初始化为0或者未初始化的全局变量放在了bss段,
而将初始化为非0的全局变量和静态局部变量存放在数据段(.data)
静态局部变量:static修饰的局部变量。(普通的局部变量分配在栈上面,静态局部变量分配在data段)。
总结:栈内存对应普通的局部变量,而且栈是由编译器自动生成的。
堆内存独立于程序存在和管理,当程序需要内存的时候就会使用malloc主动申请内存,用完后free主动释放。
数据段对应程序之中的全局变量和静态局部变量。
字符串:
指针指向头,固定尾部(\0),各个字符地址相连的一段内存。
字符串在内存之中就是多个字节,字符连续分布构成(类似于数组)。
sizeof(数组名)永远得到的就是数组的元素个数(也就是数组的大小,和数组的初始化条件无关)。
strlen(数组名)用来计算字符串的长度,只能传递合法的字符串,不能讲字符指针作为字符进行传递。
当定义一个数组之后没有对数组的大小进行初始化,sizeof会自动检测数组内的元素的个数,并将元素个数+1输出。
字符串的初始化: char *p="linux";
sizeof(p)测出来的是字符指针p的长度,测出来永远是一个8(64操作系统)或者4(32位)。
**********************************************************
经典案例:char*p="linux"此时p叫做字符串,但是实际上p是一个字符指针(实质上是一个指针变量,只是p指向了一个字符串的起始地址),但是按照指针的理解,p应该表示字符串首字符的地址(l的地址)。
************************************************************************************************ 字符数组和字符串的差别:
字符数组 自带容器,将变量存放在数组所分配的内存之中。但是字符数组的 char a[5]="inux"来说,定义了一个数组a,数组占6个字节,编译器将a[]用完之后就会将其丢弃, 相当于char a[5]={'l','i','n','u','x'}。
字符串:字符串本身就是指针,本身永远占4个字节,且四个字节不能用来存放有效数据,指针指向真正数据的地方。
char*p="iinux",定义了一个字符指针p,p占8(4)个字节,分配在栈上面,同时定义了一个字符串“linux”,分配在代码段,然后将代码段中的字符串的(6个字节)的首地址(也就是'l'的地址)赋值给p。
结构体:
结构体定义时要先定义结构体类型,然后类型定义变量,也可以在定义结构体时同时定义结构变量。
结构体变量.或者->访问元素的实质还是利用指针访问。
结构体对齐访问:
结构体元素的偏移量要考虑到元素对齐访问,所以每个元素占的字节数和自己本身类型所占的字节数不完全一样。但是一般来说用.访问时不用考虑对齐访问。
对齐访问的主要原因是为了配合硬件,对齐访问可以大大提高效率,但是浪费了内存空间。
结构体对齐的排布整个结构体变量的第一个元素的开始地址就是整个结构体的开始,所以自然是4字节对齐,但是第一个元素的结束地址由下一个元素说了算。编译器在尽可能节省内存的原则下4字节对齐。
例:struct people { char *name; short b; int a;}p1;
内存分布情况:name 占1字节 short占2字节 int占4字节
所以:name字节实际占2字节(1+1)1个填充字节,short占2字节,name和b总计占四字节一行;
a 单独占四字节, 总计占8字节
struct people { char *name; int a; short b;}p1;
此时name占4字节(1+3)3个填充字节,a单独占4字节,b占4字节(2+2)2个填充字节。
设置对齐字节:(1) #pragma pack(n)(开始) ......#pragma pack()(结束) gcc 不建议使用
(2) __attribute__((packed)) 加在要进行内存对齐的类型定义的后面。 __attribute__((aligned(n))) 加在要进行内存对齐的类型定义的后面,
__attribute__((aligned(n)))的作用是让结构体变量整体n字节对齐,而不是每一个元素进行对齐。
宏offsetof(用前需要声明宏):用来计算结构体中某个元素和结构体首地址的偏移量。
#define offsetof(TYPE, MEMBER) ((int)&(((TYPE *)0)->MEMBER))
//type是结构体的类型, member是结构体内一个元素的元素名
//宏返回的是member元素相对于整个结构体变量的首地址的偏移量,类型时int型。
例:printf("offsetof(s1,c) = %d, \n",(offsetof(mystruct_type,c)));
宏container(使用前需要声明宏):得到指向整个结构体变量的指针,从而可以得到结构体首元素的首地址,也 就能得到结构体内每一个元素的地址。
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
//type 是结构体的类型,member是结构体中的一个元素,ptr是指向结构体元素member的指针。
//宏返回的是指向整个结构体变量的指针,类型是(type *)
printf("container_of(&s1.a, mystruct_type, a) = %x \n" ,container_of(&s1.a, mystruct_type, a));
共用体union:共用体的定义形式和结构体完全相同。
结构体与共用体的不同:结构体内各元素独立存在,分布在不同的内存之中;共用体中各个元素成员是一体的,彼此不独立,他们使用相同的内存单元,同一个内存空间有多种解释方法。
共用体就是对同一个内存空间的中储存的二进制的不同解析方式。且union不涉及内存对齐问题(union中只有一个内存空间,开始的地址就是整个union占有内存的首地址)。
用处:对同一个内存单元进行多种不同的规则解析。也就是对元素进行强制类型转换,使得同一个元素输出不同类型的变量。
若共用体的内存字节为0,1,2,3,则共用体内个元素的地址访问都是从低地址(也就是0)开始访问。
大小端模式:大端模式(big endian)高位(高字节)对应高地址
小段模式(little endian)高位对应低地址。大小端没有对错,但是存储和读取必须按照同样的大小端模式进行。
代码测试机器的大小端模式:(利用union共用体)
枚举:符号常量集。枚举定义了一些符号,这些符号的本质就是int型的常量,每个符号和一个常量绑定,这个 符号就表示一个自定义的识别码。
定义的时候元素之间用,(逗号)隔开。
枚举值变量是全局变量,可自主单独使用。
宏定义和枚举在大部分是可以互换的。
宏定义和枚举的区别:枚举是将多个有关联的符号封装在一个枚举之中,而宏定义是散的,也就是说枚举是多选 一(例如定义一个月的枚举,里面包括表示12个月的字符);宏定义用来定义无关联,无限个数的符号。