C语言的存储类别,链接和内存管理

目录

1.1作用域

1.2链接

1.3存储期

1.4存储类别

1.4.1自动变量

1.4.2寄存器变量

1.4.3块作用域的静态变量

1.4.4外部链接的静态变量

1.4.5内部链接的静态变量

1.4.6存储类别说明符

1.5动态内存管理

1.5.1出现原因

栈内存

数据段与代码段

堆内存

1.5.2动态内存函数介绍

5.2.1malloc

5.2.2free

5.2.3calloc

11.5.3动态内存错误

5.3.1对NULL指针的解引用操作

5.3.2对动态开辟空间的越界访问

5.3.3对非动态开辟内存使用free释放

5.3.4使用free释放一块动态开辟内存的一部分

5.3.5对同一块动态内存多次释放

5.3.6动态开辟内存忘记释放(内存泄漏)


1.1作用域

作用域描述程序中可访问标识符的区域。一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域

块作用域:块是用一对花括号括起来的代码区域,块作用域变量的可见范围是从定义处到包含该定义的块的末尾。

以前,具有块作用域的变量都必须声明在块的开头。C99 标准放宽了这 一限制,允许在块中的任意位置声明变量。C99把块的概念扩展到包括for循环、while循环、 do while循环和if语句所控制的代码,即使这些代码没有用花括号括起来, 也算是块的一部分。

函数作用域:它只适用于语句标签,语句标签用于go语句。一个函数中的所有语句标签必须唯一。着即使一 个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数。如果在两个块中使用相同的标签会很混乱,标签的函数作用域防止了这样的事情发 生。

函数原型作用域:只适用于在函数原型中声明的参数。唯一可能出现的冲突就是在同一个原型中,不止一次地使用同一个名字。函数原型作用域的范围是从形参定义处到原型声明结束。这意味着,编 译器在处理函数原型中的形参时只关心它的类型,而形参名(如果有的话) 通常无关紧要。而且,即使有形参名,也不必与函数定义中的形参名相匹 配。

文件作用域:任何在代码块之外声明的标识符都具有文件作用域。但是在同文件中编写的通过include指令包含到其他文件中的声明,就好像直接写在那些文件中一样,它们的作用域不限于头文件的文件尾。具有文件作用域的变量,从它的定义处到该定义所在文件的末尾均可见。由于这样的变量可用于多个函数,所以文件作用域变量也称为全局变量。

1.2链接

C 变量有 3 种链接属性:外部链接、内部链接或无链接。具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数或原型私有。具有文件作用域的变量可以是外部链接或内部链接。外部链接变量可以在多文件程序中使用,内部链接变量只能在一个翻译单元中使用。

怎样修改链接属性?

关键字externstatic用于在声明修改标识符的链接属性

如果某个声明在正常情况下具有外部链接属性,在它前面加上static关键字可以使它的链接属性变为内部链接。

注:static只对外部链接的声明才有改变链接属性的效果。

extern关键字为一个标识符指定为外部链接属性,这样就可以访问在其他任何位置定义的这个实体

注: 当extern关键字用于源文件中一个标识符的第一次声明,它指定该标识符具有外部链接属性。但是,如果他用于该标识符的第2次或以后的声明,它并不会更改由第1声明所指定的链接属性

1.3存储期

作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问的对象的生存期。C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期

静态存储期如果对象具有静态存储期,那么它在程序的执行期间一直存在。

文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字 static表明了其链接属性,而非存储期。以 static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。

线程存储期线程存储期用于并发程序设计,程序执行可被划分多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。

自动存储期:块作用区域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。

动态分配存储期后面介绍

1.4存储类别

C提供了多种不同的模型或存储类别在内存中储存数据。目前所有编程示例中使用的数据都储存在内存中。从硬件方面来看,被储存的每个值都占用一定的物理内存,C 语言把这样的一块内存称为 对象)。对象可以储存一个或多个值。一个对象可能并未储存实际的值,但是它在储存适当的值时一定具有相应的大小(面向对象编程中的对象指的是类对象,其定义包括数据和允许对数据进行的操作,C不是面向对象编程语言,是面向过程编程语言)。

可以用存储期描述对象,所谓存储期是指对象在内存中保留了多长时间。标识符用于访问对象,可以用作用域和链接描述标识符,标识符的作用域和链接表明了程序的哪些部分可以使用它。不同的存储类别具有不同的存储期、作用域和链接。标识符可以在源代码的多文件中共享、可用于特定文件的任意函数中、可仅限于特定函 数中使用,甚至只在函数中的某部分使用。对象可存在于程序的执行期,也可以仅存在于它所在函数的执行期。对于并发编程,对象可以在特定线程的执行期存在。可以通过函数调用的方式显式分配和释放内存。

除动态分配存储期的5种存储类别如下:

5种存储类被
存储类别存储期作用域链接声明方式
自动自动块内
寄存器自动块内,使用关键register
静态外部链接静态文件外部所有函数外
静态内部链接静态文件内部所有函数外,使用关键字static
静态无链接静态块内,使用该关键字static

1.4.1自动变量

默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。

属于自动存储类别的变量具有自动存储期、块作用域且无链接

块作用域和无链接意味着只有在变量定义所在的块中才能通过变量名访问该变量(当然,参数用于传递变量的值和地址给另一个函数,但是这是间接的方法)。另一个函数可以使用同名的变量,但是该变量存储在不同内存位置的另一个变量。

变量具有自动存储期意味着,程序在进入该声明所在的块时变量存在,程序在退出该块时变量消失。原来该变量占用的内存位置现在可做他用。

关键字auto(存储类别说明符)用于修饰这种存储类别。

但它极少使用,因为代码块中的变量在缺省情况下就是自动变量。

auto 数据类型 数据变量名;

块中声明的变量仅限于该块及其包含的块使用。例如:

int loop(void)
{
    int m; // m的作用域
    scanf(“%d”, &m);
    {
        int i; // m和i的作用域
        for (i = m; i < n; i++)
            puts(“i is local to a sub-block\n”);
    }
    return m; // m的作用域,i已经消失了
}

上面的代码中,i仅在内层块可见。如果在内层块的前面或后面使用i,编译器会报错。变量n和m分别定义在函数头和外层块中,它们的作用域是整个函数,而且在调用函数到函数结束期间都一直存在。

如果内层块中声明的变量和外层块声明的变量同名会怎么样?

内层块会隐藏外层块的定义。但是离开内层块后,外层块变量的作用域又回到了原来的作用域。例如下面的示例:

int main(void)
{
    int x = 30;  //原始的x
    printf("x in outer block : % d at & %p\n", x, &x);
    {
        int x = 77; //新的x,隐藏了原始的x

        printf("x in inner block : % d at % p\n", x, &x);
    }

    printf("x in outer block : % d at % p\n", x, &x);

    while (x++ < 33) //原始的x
    {
        int x = 100; // 新的x,隐藏了原始的x
            x++;
        printf("x in while loop:% d at% p\n", x, & x);
        }
    printf("x in outer block : % d at % p\n", x, &x);

    return 0;
}

首先,程序创建了变量x并初始化为30,如第1条printf()语句所示。然后,定义了一个新的变量x,并设置为77,如第2条printf()语句所示。根据显示的地址可知,新变量隐藏了原始的x。第3条printf()语句位于第1个内层块后面,显示的是原始的x的值,这说明原始的x既没有消失也不曾改变。

while循环的测试条件中使用的是原始的x:while(x++ < 33)
在该循环中,程序创建了第3个x变量,该变量只定义在while循环中。所以,当执行到循环体中的x++时,递增为101的是新的x,然后printf()语句显示了该值。每轮迭代结束,新的x变量就消失。然后循环的测试条件使用并递增原始的x,再次进入循环体,再次创建新的x。在该例中,这个x被创建和销毁了3次。

注:该循环必须在测试条件中递增x,因为如果在循环体中递增x,那么递增的是循环体中的创建x,而非测试条件中使用的原始x。

自动变量的初始化

自动变量不会初始化,除非显式初始化它。考虑下面的声明:

int num;

int num1 = 10;

变量num1被初始化为10,但是num变量的值是之前占用会分配给num的空间的任意值,但大多数的情况下是任意值,而不是0

可以用 非常量表达式 初始化自动变量,前提是所用的变量已在前面定义过:

int num = 10;

int num2 = 10 * num1;

自动变量没有缺省的初始值,而显示初始化将在代码块的起始处插入一条隐式的赋值语句。

这个技巧造成4种结果:

1)自动变量的初始化较之赋值语句效率并无提高;

2)除了声明为const变量之外,在声明变量的同时进行初始化和先声明后赋值只有风格之差,并无效率之别。

3)这条隐式的赋值语句使自动变量在程序执行到它们所声明的函数(或代码块)时,每次都将重新初始化。

4)除非对自动变量进行显式的初始化,否则当自动变量创建时,它们的值总是垃圾(随机值)。

1.4.2寄存器变量

关键字register可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量

register int num;

变量通常存储在计算机内存中。

如果幸运的话,寄存器变量存储在CPU的寄存器中,或者概括来说,存储在最快的可用内存中。通常,寄存器变量比存储于内存的变量访问起来效率更高。

由于寄存器变量存储在寄存器而非内存中,所以无法获取寄存器变量的地址。

注:声明变量为register类别与直接命令相比更像是一种请求。编译器必须根据寄存器或最快可用内存的数量衡量你的请求,或者直接忽略你的请求,所以可能不会如你所愿。在这种情况下,寄存器变量就变成普通的变量。即使这样,仍然不能对该变量使用地址符。
在函数头使用关键字register,便可请求形参是寄存器变量:

void macho(register int n);

绝大多数方面,寄存器变量和自动变量都一样。也就是说,它们都是块作用域、无链接和自动存储期。 寄存器变量的创建、销毁时间和自动变量相同,但它需要一些额外的工作。在一个使用寄存器变量的函数返回之前,这些寄存器先前存储的值必须恢复,确保调用者的寄存器变量未被破坏。许多机器使用运行堆栈来完成这个任务。当函数开始执行时,它把需要使用的所有寄存器的内容都保存到堆栈中,当函数返回时,这些值再复制回寄存器中。

1.4.3块作用域的静态变量

可以创建具有静态存储期、块作用域的局部变量。这些变量和自动变量一样,具有相同的作用域,但是程序离开它们所在的函数,这些变量不会消失。这种变量具有块作用域、无链接,但是具有静态存储期。计算机在多次函数调用之间会记录它们的值。在块中(提供作用域和无链解)以存储类别说明符static(提供静态存储期)声明这种变量。

void test(void)
{
    int num = 1;
    static int stay = 1;
    printf("fade = %d and stay = %d\n", num++, stay++);
}

int main(void)
{

    test();
    test();
    test();

    return 0;
}

静态变量stay保存了它被递增1后的值,但是num变量每次都是1.这表明初始化不同,调用test()都会初始化fnum,但是stay只在编译test()时被初始化一次。如果未显式初始化静态变量,它们会被初始化为0。

不能在函数的形参中使用static:

int test(static int num); //error

1.4.4外部链接的静态变量

外部链接的变量具有文件作用域、外部链接和静态存储期。

该类别有时称为外部存储类别,属于该类别的变量称为外部变量。

外部变量的声明:把变量的定义性声明放在所有函数外面便创建了外部变量。

为了指出该函数使用了外部变量,可以在函数中使用关键字extern再次声明。

注:如果一个源代码文件使用的外部变量定义在另一个源代码文件中,则必须用extern在该文件中声明该变量。

int num;        // 外部定义的变量

int num[10];        // 外部定义的变量

extern int test;        //被定义在另一个文件种,利用extern声明

初始化外部变量

外部变量只能使用常量表达式初始化文件作用域变量,如果未初始化外部变量,它们会被自动初始化为0。

定义变量就是声明了一个变量并且计算机为其预留了存储空间。

声明变量就是单纯的声明一个变量,不管这个变量是否获得存储空间。

注意:定义只能有一次,而声明可以有多次

第1次声明被称为定义式声明,第2次声明被称为引用式声明。关键字extern表明该声明不是定义,因为它指示编译器去别处查询其定义。

1.4.5内部链接的静态变量

该存储类别的变量具有静态存储期、文件作用域和内部链接。

在所有函数外部用储存说明符static定义的变量具有这种存储类别:

static int svil = 1; // 静态变量、内部链接
int main(void){return 0;}

内部链接的静态变量只能用于同一个文件中的函数。可以使用存储类别说明符extern,在函数中重复声明任何具有文件作用域的变量。这样的声明并不会改变其链接属性。例如下面代码:

int traveler = 1;   // 外部链接
static int stayhome = 1; // 内部链接
int main(void)
{
    extern int traveler; // 使用定义在别处的 traveler
    extern int stayhome; // 使用定义在别处的 stayhome
    ...

    return 0;

}

对于该程序所在的翻译单元,traveler和stayhome都具有文件作用域,但是只有traveler可用于其它翻译单元(因为它具有外部链接)。这两个声明都使用了extern关键字,指明了main()中使用的这两个变量的定义在别处,但是这并未改变stayhome的内部链接属性。

总的来说,静态变量在程序运行之前创建,在程序的整个执行期间始终存在。它始终保持原先的值,除非给它赋一个不同的值或者程序结束。

1.4.6存储类别说明符

C语言有6个关键字作为存储类别说明符:auto、register、static、extern、_Thread_local和typedef

        1)auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于在块中声明的变量本身就具有自动存储期,所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。

        2)register说明符也只是用于块作用域的变量,它把变量归为寄存器存储类别,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取。

        3)static。当它用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性(从外部链接该为内部链接),但标识符的存储类型(静态存储)和作用域(文件作用域)不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中访问。

        当它用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕后销毁。

        4)extern说明符表明声明的变量定义在别处。如果包含extern的声明具有文件作用域,则引用的变量必须具有外部链接。如果包含extern的声明具有块作用域,则引用的变量可能具有外部链接或内部链接,这取决于该变量的定义声明式。

        5)typedef关键与任何内存存储无关,把它归于此类有一些语法上的原因。尤其是,在绝大多数情况下,不能在声明中使用多个存储类别的说明符,所以这意味着不能使用多个存储类别说明符作为tepedef的一部分。

        6)_Thread_local,它可以和static或extern一起使用

1.5动态内存管理

1.5.1出现原因

任何一个程序,正常运行都需要内存资源,用来存放诸如变量、常量、函数代码等等。这些不同的内容,所存储的内存区域是不同的,且不同的区域有不同的特性。因此我们需要研究财经处内存布局,逐个了解不同内存区域的特性。

每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程。例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。

  • PM:Physical Memory,物理内存。
  • VM:Virtual Memory,虚拟内存。

 

将其中一个C语言含如进程的虚拟内存放大来看,会发现其内部包下区域:

  • 栈(stack)
  • 堆(heap)
  • 数据段
  • 代码段

虚拟内存中,内核区段对于应用程序而言是禁闭的,它们用于存放操作系统的关键性代码,另外由于 Linux 系统的历史性原因,在虚拟内存的最底端 0x0 ~ 0x08048000 之间也有一段禁闭的区段,该区段也是不可访问的。

虚拟内存中各个区段的详细内容:

栈内存

  • 什么东西存储在栈内存中?
    • 环境变量
    • 命令行参数
    • 局部变量(包括形参)
  • 栈内存有什么特点?
    • 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。
    • 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。
    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。
  • 注意:
    栈内存的分配和释放,都是由系统规定的,我们无法干预。

数据段与代码段

  • 数据段细分成如下几个区域:
    • .bss 段:存放未初始化的静态数据,它们将被系统自动初始化为0
    • .data段:存放已初始化的静态数据
    • .rodata段:存放常量数据
  • 代码段细分成如下几个区域:
    • .text段:存放用户代码
    • .init段:存放系统初始化代码

堆内存

堆内存(heap)又被称为动态内存、自由内存,简称堆。堆是唯一可被开发者自定义的区段,开发者可以根据需要申请内存的大小、决定使用的时间长短等。但又由于这是一块系统“飞地”,所有的细节均由开发者自己把握,系统不对此做任何干预,给予开发者绝对的“自由”,但也正因如此,对开发者的内存管理提出了很高的要求。对堆内存的合理使用,几乎是软件开发中的一个永恒的话题。

  • 堆内存基本特征:
    • 相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。
    • 相比栈内存,堆内存从下往上增长。
    • 堆内存是匿名的,只能由指针来访问。
    • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。

1.5.2动态内存函数介绍

5.2.1malloc

C语言提供动态内存开辟的函数malloc

函数原型:
#include <stdlib.h>
void* malloc(size_t size);
函数作用:申请堆内存

参数:size:内存块的大小,以字节为单位

返回值:该函数返回一个指针,指向已分配大小的内存。如果请求失败,则返回NULL

 这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

5.2.2free

C语言提供另外一个函数free,专门是用来做动态内存的释放和回收的

函数原型:
#include <stdlib.h>
void free(viud* ptr);
函数功能:释放堆内存

参数:ptr:堆内存指针

返回值:无

 free函数用来释放动态开辟的内存。

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。
int main(void)
{
	int* ptr = NULL;
	ptr = (int*)malloc(10 * sizeof(int));
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;

	return 0;
}

5.2.3calloc

C语言还提供了一个函数calloc,calloc函数也用来动态内存分配。

函数原型:
#include <stdlib.h>
void* calloc(size_t num,size_t size);
函数功能:申请堆内存

参数:num:所申请的堆内存的块数,所有的内存块是连续分布的、无间隔的
     size:所申请的一块堆内存的大小,单位是字节

返回值:如果成功,则返回指向分配号的堆内存的指针,失败则返回NULL
  •  函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
  • malloc()申请的堆内存,默认情况下是随机值,一般需要用 bzero() 来清零。
  • calloc()申请的堆内存,默认情况下是已经清零了的,不需要再清零。
  • free()只能释放堆内存,并且只能释放整块堆内存,不能释放别的区段的内存或者释放一部分堆内存。
  • 释放内存的含义:

    • 释放内存意味着将内存的使用权归还给系统。
    • 释放内存并不会改变指针的指向。
    • 释放内存并不会对内存做任何修改,更不会将内存清零。
int main(void)
{
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL != p)
	{
		//使用空间
	}
	free(p);
	p = NULL;
	return 0;
}

5.2.4realloc

realloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。

函数原型:
#include <stdlib.h>
void* realloc(void* ptr,size_t size);
函数功能:重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小

参数:ptr:针指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。
        如果为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
    size:内存块的新的大小,以字节为单位。如果大小为 0,且 ptr 指向一个已存在的内存块,则 ptr 所指向的内存块会被释放,并返回一个空指针。

返回值:该函数返回一个指针 ,指向重新分配大小的内存。如果请求失败,则返回 NULL。

 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。

realloc在调整内存空间的是存在两种情况:

1)原有空间之后有足够大的空间

2)原有空间之后没有足够大的空间

当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。 当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。

int main(void)
{
    int* ptr = (int*)malloc(100);
    if (ptr != NULL)
    {
        printf("malloc succeed\n");
    }
    else
    {
        perror("malloc fail");
        return -1;
    }

    //扩展容量
    ptr = (int*)realloc(ptr, 1000);

    int* p = NULL;
    p = realloc(ptr, 1000);
    if (p != NULL)
    {
        ptr = p;
    }
    free(ptr);

    return 0;
}

11.5.3动态内存错误

5.3.1对NULL指针的解引用操作

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

int main(void)
{
	test();
	return 0;
}

5.3.2对动态开辟空间的越界访问

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		return;
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
}


int main(void)
{
	test();
	return 0;
}

5.3.3对非动态开辟内存使用free释放

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
}

int main(void)
{
	test();
	return 0;
}

5.3.4使用free释放一块动态开辟内存的一部分

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

int main(void)
{
	test();
	return 0;
}

5.3.5对同一块动态内存多次释放

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
}

int main(void)
{
	test();
	return 0;
}

5.3.6动态开辟内存忘记释放(内存泄漏)

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}

int main()
{
	test();
	while (1);

	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中,指针是一种非常重要的数据类型,用于存储内存地址。通过指针,程序可以访问和操作内存中的数据。指针在C语言中有着广泛的应用,包括动态内存分配、数组和函数调用等方面。 指针的使用使得程序员能够更直接地操作内存,但也带来了内存管理的责任。C语言中的内存管理是程序员需要关注的一个重要方面[1]。在C语言中,内存的分配和释放需要手动进行。如果不正确地管理内存,就容易出现内存泄漏、野指针等问题,导致程序崩溃或出现难以调试的错误。 动态内存分配是指在程序运行时根据需要分配内存空间。C语言提供了一些函数来实现动态内存分配,例如malloc、calloc和realloc函数。这些函数允许程序在运行时动态地请求所需的内存空间。 使用动态内存分配时,程序员需要负责在不再需要使用内存时手动释放已分配的内存空间,以免造成内存泄漏。释放内存的函数是free函数,通过调用free函数可以将先前分配的内存空间释放回系统。 除了动态内存分配外,C语言中还有一些其他的内存管理技术。例如,对于大型数据结构或数组,可以使用指针来减少内存的占用和提高程序的效率。此外,C语言中还有一些规则和约定来确保内存的正确使用,如避免野指针、空指针和越界访问等。 综上所述,C语言中的指针和内存管理密切相关。指针使程序能够直接操作内存,但也需要程序员正确地管理内存的分配和释放。通过动态内存分配和其他内存管理技术,可以有效地利用和管理内存,提高程序的性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值