小言_互联网的博客

C提高~复合数据结构类型

256人阅读  评论(0)

引言

C语言中复杂的组合数据类型,如数组、字符串、结构体、共用体、枚举等其他组合类型,它们是如何在内存中开辟空间的,以及这些组合数据类型的特点又是如何在内存中体现出来的。现有的操作系统计算机上,为了实现内存的高效利用,操作系统对所有物理内存进行了统一的内存管理,所以应用程序表现出来的都是虚拟内存

管理方式

在C语言程序中,存放数据所能使用的内存空间大概分为四种情况:栈(stack)、堆(heap)、数据区(.data和.bss区)和常量区(.ro.data)

栈内存特点

空间实现自动管理运行时空间自动分配,运行结束空间自动回收。栈是自动管理的,程序员不需要手工干预,方便简单,因此栈又称为自动管理区

能够被反复使用:栈内存在程序中用的都是一块内存空间,程序通过自动开辟和自动释放,会反复使用这一块空间。

脏内存栈内存由于反复使用每次使用后程序不会去清空内容,当下一次该空间再次被分配时上一次使用的值会还在

临时性:函数不能返回栈变量的指针,因为该空间在函数运行结束之后就会被释放

/*函数不能返回局部变量的地址,因为该函数执行完后,存于函数栈的局部变量就已经不存了,如果返回地址
其访问该空间的话,该空间很有可能已经被别人获取正在使用了*/
#include <stdio.h>

int func(void){
  /*a是局部变量,分配在栈上又叫栈变量(临时变量)*/
  int a = 4;
  printf("&a = %p\n",&a);
  return &a;
}

void func(void){
  int a = 33;
  int b = 33;
  int c = 33;
  printf("int func2, &a = %p\n",&a);
}

int main(void){
  int *p = NULL;
  p = func();
  func2();
  func2();
  printf("p = %p\n");
  printf("*p = %d.\n",*p);    //运行之后*p等于33,证明栈内存用完之后是脏的,临时的

  return 0;
}

运行结果

&a = 000000000062FDDC
int func2, &a = 000000000062FDD4
int func2, &a = 000000000062FDD4
p = 00007FFDCD56FA30
*p = 0.

func()函数运行结束函数体内定义的局部变量自动释放,这时看出p地址和&a是不一致的,*p值为0,未初始化的变量默认值为0;

/*栈出溢出:因为操作系统事先给定了栈的大小,
  如果在函数中无穷尽的分配局部变量,栈内存总能用完*/
#include <stdio.h>

void stack_overflow(void){
  int a[10000000] = {0};
  a[10000000 -1]  = 12;
  return ;
}

//下面函数为递归函数
void stack_overflow2(void){
  int a = 2;
  stack_overflow2();
  return ;
}


int main(void){
  //stack_overflow(); //core dumped
  stack_overflow2();
  return 0;
}

上面两个函数的运行结果Segmentation fault(core dumped)证明栈溢出了。

堆内存特点

灵活:堆时另一种管理形式的内存区域,堆内存的管理灵活。

内存量大:堆内存空间很大,进程可以按需手动申请,使用完手动释放。

程序手动申请和释放:写代码去申请malloc和释放free。

脏内存:堆内存也是反复使用的,而且使用者用完释放前不会清除,因此惹事脏的。

临时性:堆内存在malloc后和free之前的这期间可以被访问。在malloc之前free之后不能访问,否则会有不可预料的后果。

#include <stdio.h>
#include <stdlib.h>

int main(void){
  //需要一个1000个int类型元素的数组
  //第一步:申请和绑定
  int *p = (int *)malloc(1000*sizeof(int));
  //第二步:检验申请是否成功
  if(p == NULL){
    printf("malloc error.\n");
    return -1;
  }
  //第三步:使用申请的内存
  *(p+0) = 1;
  *(p+1) = 2;
  
  //第四步:释放
  free(p);
  return 0;
}

如果最后没有将分配的堆内存空间释放的话,这块内存空间会被一直占用,只有当整个程序终止后才会释放。所以对堆内存来说,使用完后及时使用free释放空间就显得非常重要,否则会导致内存泄露,即内存空间都还在 ,但是该空间被之前程序使用后,后续不再使用了,但是它却一直占着

malloc

使用malloc分配空间时,返回实际上是一个void *类型的指针(地址),该地址是本次申请内存空间的首字节地址,失败返回NULL(使用前需检查是否为NULL),使用完需用free释放void类型(空类型)不表示没有类型,而表示万能类型,在需要时在具体指定void*表示是一个无类型指针,对于32位系统,指针本都是4个字节

值得注意的是:调用free归还p所指向的堆内存之前指向这段内存的指针p的指向需要发生改变指向了其他的地方的话,必须通过一个中间指针变量先记住p指向的堆空间,之后free时才能通过这个中间变量释放之前p所指向的堆空间,否则就会造成之前p所指的堆空间无法释放导致内存泄露的发生

内存中的各个段

代码段、数据段、bss段

编译器在编译程序时,程序会按照一定的结构被划分成各个不同的段进行组织,即.text、.bss、.data段等

代码段(.text):代码存放程序的代码部分,程序中各种函数的指令就存放在该段

数据段:又称数据区、静态数据区、静态区程序中的静态变量空间开辟于此,值得注意:全局变量是整个程序的公共财产,而局部变量只是函数的私有财产

.bss段:又叫ZI(Zero Initial)段,所有未初始化的静态变量的空间就开辟于此,这个段会自动将未初始化静态空间初始化为0

注意:数据段(.data)和.bss段实际上没有本质区别,都是用来存放程序中的静态变量,只是.data存放显式初始化为非0的静态数据,而.bss中存放那些显式初始化为0或者未显式初始化的静态数据

特殊数据会被放到代码段

#include <stdio.h>
int main(void){
  char *p = "linux";
  *(p+0) = 'f';        //运行报错,因为是字符串常量,不能被修改
  printf("p = %s.\n",p);

  return 0;
}

内存管理方式小结

栈、堆和静态这三种内存管理方式都可以为程序提供内存空间。栈空间用于开辟局部变量空间,实现自动内存管理;对于堆内存,程序中需要使用malloc进行手动申请,使用完后必须使用free进行释放,实现手动内存管理静态数据区的数据段,专门用于开辟全局变量和静态变量不需要程序员参与管理。

只是在函数内部临时使用作用范围希望被局限在函数内部,即定义局部变量

堆内存和数据段几乎拥有完全相同的属性,大部分时候是可以相互替换。但是他们生命周期不同堆内存的生命周期是从malloc开始到free结束,而静态变量程序一开始执行就被开辟,直到整个程序结束才回收,伴随程序运行一直存在。所以,若变量只是在程序的一个阶段期间有用适合使用堆内存空间;若变量需要在程序运行的整个过程中一直存在适合使用全局变量

字符串类型

 


转载:https://blog.csdn.net/qq_40025335/article/details/102475491
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场