我是靠谱客的博主 激昂网络,最近开发中收集的这篇文章主要介绍4.5、数组&字符串&结构体&共用体&枚举4.5.1程序中的内存从哪里来1:4.5.5、C语言的字符串和字符数组类型4.5.7、结构体的本质4.5.8结构体的对齐访问:4.5.9_offsetof宏与container_of宏:4.5.10、共用体union。4.5.15.枚举,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
4.5.1程序中的内存从哪里来1:
(1)在C中获取内存的三种情况:栈(stack)、堆(heap)、数据区(data)。
(2)栈的详解:运行时自动分配&自动回收:栈是自动管理的,程序员不需要手动干预,方便简单。
反复使用
:栈内存在程序中其实就是那一块空间,程序反复使用这一块空间。
脏内存
,栈内存由于反复使用,每次使用后程序不会去清理,因此分配到时保留原来的值。
临时性
(函数不能返回栈变量的指针,因为这个空间是临时的),
栈会溢出
(因为操作系统事先给定了栈的大小,如果在函数中无穷尽的分配栈内存总能用完)。
#include <stdio.h>
//函数不能返回函数内部局部变量的地址,因为这个函数执行完返回后这个局部变量已经不在了。
//释放了,但是栈内存还在还可以访问,但是访问时实际上这个
//内存地址已经和当时那个变量无关了。
int *func(void)
{
int a = 4;//a是局部变量,分配在栈上又叫栈变量,又叫临时变量。
printf("&a = %p.n",&a);
return &a;
//这个函数返回的是一个指针。
}
void func2(void)
{
int a =33;
int b = 33;
int c = 33;
printf("in func2,&a = %p.n",&a);
}
void stack_overflow2(void)
{
int a = 2;
stack_overflow2();//没有递归尽头,程序崩掉了。
}
int main(void)
{
int *p = NULL;
p = func();
func2();
func2(); //重改了地址。
printf("p = %p.n",p);
printf("*p= %d.n",*p); //栈的值是脏的。这个变量的地址都被上一次访问的改变了。*p本来是4,现在输出的值是随机的。因为一个函数的局部变量的地址是不能在函数外被访问的
stack_overflow2();
return 0;
}
(3)
堆的详解:堆管理器是操作系统的一个模块,堆管理内存分配灵活,按需分配。堆内存也是反复使用的,而且使用者用完释放前不会清除,因此也是脏的。临时性:堆内存只在malloc和free之间属于我这个进程,而可以访问,在malloc之前和free之后都不能再访问,否则会有不可预料的后果。
(4)堆内存使用的范例:void *
是个指针类型,malloc返回的是一个void *类型的指针,实质上malloc返回的是堆管理器分配给我本次申请的那段内存空间的首地址(malloc返回的值其实是一个数字,这个数字表示一个内存地址),为什么要使用void *作为类型?主要原因是malloc帮我们分配内存时只是分配了内存空间,至于这段空间将来用来存储什么类型的元素malloc是不关心的,具体的指向类型是我们程序员自己来决定的。
(5)什么是void类型:不是表示没有类型,而是表示万能类型,意思是说这个数据类型当前是不确定的,在需要的时候可以再去指定它的具体类型。Void *类型是一个指针类型,指针本身占4字节,但是指针指向的类型是不确定的,换句话说这个指针在需要的时候可以被强制转化成其他任何一种确定类型的指针,也就是说这个指针可以指向任何类型的元素。
(6)malloc的返回值:成功申请空间后返回这个内存空间的指针,申请失败时返回NULL。所以malloc获取的内存指针使用前一定要检查是否为NULL。.。Malloc申请的内存使用完后要free释放,,不要在free()之前对这个内存空间重新赋值,否则这片内存就丢失了,但是你联系不到这段内存,堆管理器也无法通过地址找到,会造成内存泄漏(通俗点就是吃内存)。,Free(p);会告诉堆管理器这段内存我用完了你可以回收了。堆管理器回收了这段内存后当前进程就不应该再使用这段内存了,因为释放后堆管理器就可能把这段内存再次分配给别的进程,所以你就不能再使用了。
(7)再调用free归还这段内存之前,指向这段内存的指针p一定不能丢(也就是不能给p另外赋值)。因为p一旦丢失这段malloc来的内存就永远的丢失了,内存泄漏,直到当前
程序结束
时操作系统才会回收这段内存。 十六进制———》二进制(四位一体)——》十进制。
(8)gcc中的malloc默认最小是以16byte为分配单位的。Malloc(20)去访问25,200,2500.。会怎么样,继续往后访问,总有一个数字处于段错误。
#include <stdio.h>
#include <stdlib.h>//使用malloc的头文件。
int main(void)
{
//第一步:申请和绑定 //申请的内存就像一个数组,以数组形式来访问
int *p = (int *)malloc(20 * sizeof(int)); //malloc默认申请最少内存为16字节。
//int *p = (int *)malloc(100*sizeof(int));
//第二步使用返回值校验分配是否成功
if(NULL == p)
{
printf("malloc error.n");
return -1;
}
//第三步使用申请到的内存
*(p+3) = 12;
*(p+300) = 1234;
printf("*p3 = %d.n",*(p+3));
printf("*p300 = %d.n",*(p+300)); //但是加到一定程度后,会出现段错误。
free(p);
/*
int *p1 = (int *)malloc(4); //p2 - p1 = //malloc默认申请最少内存为16字节。
int *p2 = (int *)malloc(4); //申请一块4字节的大小,返回这块内存的首地址。
printf("p1 = %p.n",p1);
printf("p2 = %p.n",p2);
/*
//需要一个1000个int类型的元素的数组
//第一步:申请和绑定
int *p = (int *)malloc(1000 * sizeof(int));
//第二步使用返回值校验分配是否成功
if(NULL == p)
{
printf("malloc error.n");
return -1;
}
//第三步使用申请到的内存
*(p+0) = 1;
*(p+1) = 2;
printf("*(p+0) = %d.n",*(p+0));
printf("*(p+1) = %d.n",*(p+1));
//p=NULL;
//p = &a; //如果在free之前给p另外赋值,那么malloc申请的那段内存就丢失掉了
//malloc后p和返回的内存相绑定,p是那段内存在当前进程的唯一联系人,
//如果p在free之前就丢了,那么这段内存就永远丢失了,丢了的概念就是在
//操作系统的堆管理器中这段内存是当前进程拿着的,但是你也用不了,所以你想
//所以你想申请新的内存来替换使用,这就叫程序的 吃内存。即内存泄漏。
//相当于你挖了一块地,在下面埋了黄金,然后忘记这块地的位置了。
//第四步释放内存
free(p);
*(p+0) = 345;
*(p+1) = 567;
printf("*(p+0) = %d.n",*(p+0));
printf("*(p+1) = %d.n",*(p+1));
*/
return 0;
}
4.5.4、数据段:(程序在加载的时候,内容已经确定了。)
(1)编译器在编译程序时,将程序中的所有元素分成了一些组成部分,各部分构成一个段。所以说段是可执行程序的组成部分。
(2)代码段:代码段就是程序中的可执行部分,直观理解代码段就是函数堆叠组成的。
代码段:
代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些
架构
也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
(3)数据段(也被称为数据区,静态数据区,静态区):数据段就是程序中的数据,直观理解就是C语言中全局变量(注意局部变量不算是程序的数据,只能算是函数的数据)。
(4)bss段(又叫ZI(zero initial)段),bss段的特点就是被初始化为0,bss段本质上也是属于数据段,bss段就是被初始化为0的数据段。
注意区分:数据段(data)和bss段的区别和联系:二者本来没有本质区别,都是用来存放C程序中的全局变量的。区别在于把显示初始化为非0的全局变量存在data段中,而把显式初始化为0或者并未显式初始化(C语言规定未显式初始化的全局变量默认为0)的全局变量存在bss段。
(5)特殊的数据放在代码段:C语言中使用char *p =“Linux”;定义字符串时,字符串“linux”实际被分配在代码段。这个“Linux”字符串实际上是一个
常量字符串
而不是变量字符串。
(6)const的实现方法有两种:1:、const修饰的放在代码段(常见的单片机的编译器)2:const修饰的放在数据段(gcc就是这样实现的。)
(7)显式初始化为非0的全局变量和静态局部变量放在数据段。。。未初始化或显式初始化为0的全局变量放在bss段。
总结:C语言中所有变量和常量所使用的内存无非以上三种情况。
(1)不同点:栈内存对应C中的普通局部变量(栈是自动的,程序员无法手工控制),堆内存完全是独立于我们的程序存在和管理的,程序需要内存时,手工申请malloc和释放free,数据段在C程序中对应着全局变量和static静态局部变量。
(2)不同的存储方式有不同的特点:
•
堆内存和数据段几乎拥有完全相同的属性,但是二者的生命周期不同。如果你这个变量只在程序的某一阶段有用,用完就不用了,适合堆内存,如果这个变量是要和程序共生共灭的则应该用全局变量,实际中,堆内存的使用比全局变量广泛。
#include <stdio.h>
#include <stdlib.h>//使用malloc的头文件。
#include <string.h>
char str[] = "linux"; //第二种方法:定义成全局变量,放在数据段中
int main(void)
{
/*
char a[] = "linux"; //第一种方法:定义成局部变量,放在栈上。
char *p = (char *)malloc(10); //申请10个char类型大小的内存。
//char *p = (char *)malloc(10);
if(NULL == p) //申请完一定要判断一下。
{
printf("malloc error.n");
return -1;
}
memset(p,0,10); //memset内存空间初始化函数,第三种方法:放在malloc申请的堆内存中。
strcpy(p,"linux");
printf("%sn",a);
printf("%sn",str);
printf("%sn",p);
printf("%pn",a);
printf("%pn",str);
printf("%pn",p);
*/
char *p = "linux"; //分配在代码段的。特殊数据。就相当于const char *p = "linux";
//*(p+0) = 'f'; //flinux (这句执行后会有段错误)
printf("p = %s.n",p);
return 0;
}
4.5.5、C语言的字符串和字符数组类型
附加:
#include <stdio.h>
int size(char b[100]) //数组做函数参数时退化成指针
{
return sizeof(b);
}
int main(void)
{
char a[] = {'C','h','i','n','a','