C语言深入讲解内存操作问题

目录
  • 一、野指针
  • 二、野指针的由来
  • 三、基本原则
  • 四、小结-上
  • 五、常见的内存错误
  • 六、内存操作的规则
  • 七、小结-下

一、野指针

  • 指针变量中的值是非法的内存地址,进而形成野指针
  • 野指针不是 NULL 指针,是指向不可用内存地址的指针
  • NULL 指针并无危害,很好判断,也很好调试
  • C 语言中无法判断一个指针所保存的地址是否合法

二、野指针的由来

  • 局部指针变量没有被初始化
  • 指针所指向的变量在指针之前被销毁
  • 使用已经释放过的指针
  • 进行了错误的指针运算
  • 进行了错误的强制类型转换

下面看一个示例:

#include <stdio.h>
#include <malloc.h>

int main()
{
    int* p1 = (int*)malloc(40);
    int* p2 = (int*)1234567;    //p2 是一个野指针
    int i = 0;

    printf("%p\n", p1);
    for(i=0; i<40; i++)
    {
        *(p1 + i) = 40 - i; //由于指针运算产生了野指针,改写了非法内存地址
    }

    free(p1); 

    printf("%p\n", p1);

    for(i=0; i<40; i++)
    {
        p1[i] = p2[i];  //使用已经释放了的内存空间
    }

    return 0;
}
 

输出结果如下:

两个打印语句打印出来的地址值是完全相同的,这说明 free() 函数只负责释放 p1 所指向的内存空间,但是不负责将 p1 重置为空指针,或者说重置为任何地址值。

可以做以下修改:

#include <stdio.h>
#include <malloc.h>

int arr[40] = {1, 2, 3, 4, 5, 6, 7};

int main()
{
    int* p1 = (int*)malloc(40 * sizeof(int));
    int* p2 = arr;
    int i = 0;

    printf("%p\n", p1);
    for(i=0; i<40; i++)
    {
        *(p1 + i) = 40 - i;
    }

    free(p1);
    p1 = NULL;

    printf("%p\n", p1);

    for(i=0; i<40; i++)
    {
        p1[i] = p2[i];  //使用已经释放了的内存空间
    }

    return 0;
}

输出结果如下:

这里注意一个技巧,将释放之后的指针立即赋值成空指针。

三、基本原则

  • 绝不返回局部变量和局部数组的地址
  • 任何变量在定义后必须初始化
  • 字符数组必须确认 0 结束符后才能成为字符串
  • 任何使用与内存操作相关的函数必须指定长度信息

下面再来看一个示例:

#include <stdio.h>
#include <string.h>
#include <malloc.h>

struct Student
{
    char* name;
    int number;
};

char* func()
{
    char p[] = "AutumnZe";

    return p;
}

void del(char* p)
{
    printf("%s\n", p);

    free(p);
}

int main()
{
    struct Student s;   //由于没有初始化,产生了野指针
    char* p = func();   //产生了野指针

    strcpy(s.name, p);  //使用野指针,name 成员保存的地址值完全不知道

    s.number = 99;

    p = (char*)malloc(5);

    strcpy(p, "AutumnZe");   //产生内存越界,操作了野指针

    del(p);

    return 0;
}

输出结果如下:

四、小结-上

  • 内存错误是实际产品开发中最常见的问题,然而绝大多数的bug都可以通过遵循基本的编程原则和规范来避免。
  • 因此,在学习的时候要牢记和理解内存操作的基本原则,目的和意义。

五、常见的内存错误

  • 结构体成员指针未初始化
  • 结构体成员指针未分配足够的内存
  • 内存分配成功,但并未初始化
  • 内存操作越界

下面看一个示例:

#include <stdio.h>
#include <malloc.h>

void test(int* p, int size)
{
    int i = 0;

    for(i=0; i<size; i++)
    {
        printf("%d\n", p[i]);
    }

    free(p);    //这里多释放了一次内存
}

void func(unsigned int size)
{
    int* p = (int*)malloc(size * sizeof(int));
    int i = 0;

    if( size % 2 != 0 )
    {
        return;
    }

    for(i=0; i<size; i++)
    {
        p[i] = i;
        printf("%d\n", p[i]);
    }

    free(p);
}

int main()
{
    int* p = (int*)malloc(5 * sizeof(int));

    test(p, 5);

    free(p); 

    func(9);
    func(10);

    return 0;
}
 

输出结果如下,可以看到程序崩溃了:

0
0
0
0
0
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0969e008 ***
======= Backtrace: =========
/lib/libc.so.6(+0x6c0c1)[0x27b0c1]
/lib/libc.so.6(+0x6d930)[0x27c930]
/lib/libc.so.6(cfree+0x6d)[0x27fa1d]
./a.out[0x804851f]
/lib/libc.so.6(__libc_start_main+0xe7)[0x225ce7]
./a.out[0x8048391]
======= Memory map: ========
0013c000-00158000 r-xp 00000000 08:02 4629       /lib/ld-2.12.1.so
00158000-00159000 r--p 0001b000 08:02 4629       /lib/ld-2.12.1.so
00159000-0015a000 rw-p 0001c000 08:02 4629       /lib/ld-2.12.1.so
0020e000-0020f000 r-xp 00000000 00:00 0          [vdso]
0020f000-00366000 r-xp 00000000 08:02 4645       /lib/libc-2.12.1.so
00366000-00368000 r--p 00157000 08:02 4645       /lib/libc-2.12.1.so
00368000-00369000 rw-p 00159000 08:02 4645       /lib/libc-2.12.1.so
00369000-0036c000 rw-p 00000000 00:00 0 
00dbf000-00dd9000 r-xp 00000000 08:02 102        /lib/libgcc_s.so.1
00dd9000-00dda000 r--p 00019000 08:02 102        /lib/libgcc_s.so.1
00dda000-00ddb000 rw-p 0001a000 08:02 102        /lib/libgcc_s.so.1
08048000-08049000 r-xp 00000000 08:05 525125     /home/delphi/a.out
08049000-0804a000 r--p 00000000 08:05 525125     /home/delphi/a.out
0804a000-0804b000 rw-p 00001000 08:05 525125     /home/delphi/a.out
0969e000-096bf000 rw-p 00000000 00:00 0          [heap]
b7600000-b7621000 rw-p 00000000 00:00 0 
b7621000-b7700000 ---p 00000000 00:00 0 
b77db000-b77dc000 rw-p 00000000 00:00 0 
b77e9000-b77ec000 rw-p 00000000 00:00 0 
bfc94000-bfcb5000 rw-p 00000000 00:00 0          [stack]
已放弃

如果把多余的 free() 注释掉,程序就能正常运行了。

void test(int* p, int size)
{
    int i = 0;

    for(i=0; i<size; i++)
    {
        printf("%d\n", p[i]);
    }

    //free(p);    //这里多释放了一次内存
}

输出结果如下:

接上面的例子,讨论一下 free 问题,代码如下:

#include <stdio.h>
#include <malloc.h>

void test(int* p, int size)
{
    int i = 0;

    for(i=0; i<size; i++)
    {
        printf("%d\n", p[i]);
    }

    free(p);
}

int main()
{
    int* p = (int*)malloc(5 * sizeof(int));
    int a[2];

    test(a, 2);

    return 0;
}
 

输出结果如下:

因为 test(a, 2);  调用 test() 函数时 p 所指向的内存空间是栈上面的空间,但是 free 函数的作用是释放堆上面的空间,所以肯定会发生段错误。

下面再来看一个例子:

#include <stdio.h>
#include <malloc.h>

struct Demo
{
    char* p;
};

int main()
{
    struct Demo d1;
    struct Demo d2;

    char i = 0;

    for(i='a'; i<'z'; i++)
    {
        d1.p[i] = 0;
    }

    d2.p = (char*)calloc(5, sizeof(char));

    printf("%s\n", d2.p);

    for(i='a'; i<'z'; i++)
    {
        d2.p[i] = i; //内存越界
    }

    free(d2.p);

    return 0;
}

输出结果如下:

结构体变量里面包含指针,但是没有初始化,就会变成野指针。所以  d1.p[i] = 0; 就会产生 bug。

六、内存操作的规则

动态内存申请之后,应该立即检查指针值是否为 NULL,防止使用 NULL 指针

free 指针之后必须立即赋值为 NULL

任何与内存操作相关的函数都必须带长度信息

malloc 操作和 free 操作必须匹配,防止内存泄露和多次释放。

七、小结-下

内存错误的本质源于指针保存的地址为非法值

  • 指针变量未初始化,保存随机值
  • 指针运算导致内存越界

内存泄漏源于 malloc 和 free 不匹配

  • 当 malloc 次数多于 free 时,产生内存泄漏
  • 当 malloc 次数少于 free 时,程序可能崩溃

避免内存泄漏:哪个函数里面进行的 malloc ,就在哪个函数里面 free,不要跨函数去释放动态的内存空间。

到此这篇关于C语言深入讲解内存操作问题的文章就介绍到这了,更多相关C语言 内存操作内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言内存操作函数详解

    目录 头文件:#include<memory.h> 2.memmove 3.memcmp 4.memset 总结 头文件:#include<memory.h> 1.memcpy 作用:内存拷贝 函数原型: void *memcpy( void *dest, const void *src, size_t count ); 使用: 使用格式:memcpy(目的地,原,想操作内存大小(单位字节)) 把 "参数2" 起始的 "参数3" 个字节 内容

  • C语言全部内存操作函数的实现详细讲解

    memcpy内存拷贝函数 void* memcpy(void* destination, const void* source, size_t num); memcpy函数从source的位置开始向后拷贝num个字节的数据到destination的内存位置 这个函数在遇到\0的时候并不会停下来 如果source和destination有任何的重叠,复制的结果都是未定义的 使用方法: #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #in

  • C语言深入讲解内存操作问题

    目录 一.野指针 二.野指针的由来 三.基本原则 四.小结-上 五.常见的内存错误 六.内存操作的规则 七.小结-下 一.野指针 指针变量中的值是非法的内存地址,进而形成野指针 野指针不是 NULL 指针,是指向不可用内存地址的指针 NULL 指针并无危害,很好判断,也很好调试 C 语言中无法判断一个指针所保存的地址是否合法 二.野指针的由来 局部指针变量没有被初始化 指针所指向的变量在指针之前被销毁 使用已经释放过的指针 进行了错误的指针运算 进行了错误的强制类型转换 下面看一个示例: #in

  • C语言由浅入深讲解文件的操作上篇

    目录 为什么使用文件 什么是文件 文件名 关于文件的一些概念 文件函数 fopen fclose 实例代码 绝对路径 文件的打开方式 文件操作流程 为什么使用文件 前面写的通讯录,增加人数退出程序后,数据就会消失.此时数据是存放在内存中,下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受. 所以文件操作就应运而生.数据持久化的方法有两种:1.把数据存放在磁盘文件2.存放到数据库使用文件我们们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化. 什么是文件 但是在程序设计中

  • C语言由浅入深讲解文件的操作下篇

    目录 文件的顺序读写 字符输入输出fgetc和fputc 文本行输入输出函数fgets和fputs 格式化输入输出函数fscanf和fprintf 二进制输入输出函数fread和fwrite 文件的随机读写 fseek ftell fwind 文本文件和二进制文件 文件结束的判定 feof 文件缓冲区 第一篇讲了文件的基本概念,和文件如何打开和关闭.第二篇主要介绍文件的顺序读写和随机读写.外加文件缓冲区的知识点. 文件的顺序读写 字符输入输出fgetc和fputc fgetc:字符输入函数,也就

  • C语言全面讲解顺序表使用操作

    目录 一.顺序表的结构定义 二.顺序表的结构操作 1.初始化 2.插入操作 3.删除操作 4.扩容操作 5.释放操作 6.输出 三.示例 编程环境为 ubuntu 18.04. 顺序表需要连续一片存储空间,存储任意类型的元素,这里以存储 int 类型数据为例. 一.顺序表的结构定义 size 为容量,length 为当前已知数据表元素的个数 typedef struct Vector{ int *data; //该顺序表这片连续空间的首地址 int size, length; } Vec; 二.

  • C语言全面细致讲解文件操作

    目录 什么是文件 程序文件 数据文件 文件名 文件指针 文件的打开和关闭 文件的顺序读写 字符输入输出函数 字符串输入输出函数(fgets,fputs) 格式化输入输出函数(fscanf,fprintf) 二进制输入输出函数(fread,fwrite) 文件的随机读写 fseek ftell 文件读取结束的判定 什么是文件 磁盘上的文件是文件.但是在程序设计中,我们一般谈的文件有两种:程序文件.数据文件(从文件功能的角度来分类的). 程序文件 包括源程序文件(后缀为.c),目标文件(window

  • C语言深入讲解动态内存分配函数的使用

    目录 一.malloc 二.free(用于释放动态开辟的空间) 三.calloc 四.realloc 五.常见的动态内存分配错误 六.柔性数组 局部变量和函数的形参向栈区申请空间 全局变量和static静态变量向静态区申请空间 动态内存分配向堆区申请空间(位于<stdlib.h>或<cstdlib>头文件) 一.malloc void* malloc (size_t size); 分配内存块 分配一个连续可用的字节内存块,返回指向该内存块开头的指针. 新分配的内存块的内容未初始化,

  • R语言对CSV文件操作实例讲解

    在 R 语言中,我们可以从存储在 R 语言环境外的文件中读取数据. 我们还可以将数据写入将被操作系统存储和访问的文件. R 语言可以读取和写入各种文件格式,如​csv​,​excel​,​xml​等. 在本章中,我们将学习从​csv​文件读取数据,然后将数据写入​csv​文件. 该文件应该存在于当前工作目录中,以便 R 语言可以读取它. 当然我们也可以设置我们自己的目录并从那里读取文件. 获取和设置工作目录 您可以使用​getwd()​函数检查R语言工作区指向的目录. 您还可以使用​setwd(

  • C语言详细分析讲解内存管理malloc realloc free calloc函数的使用

    目录 C语言内存管理 一.动态空间申请 二.动态空间的扩容 三.释放内存 C语言内存管理 malloc && realloc && free && calloc c语言中为了进行动态内存管理,<stdlib.h>中提供了几个函数帮助进行内存管理. 我们知道,C语言中是没有C++中的容器或者说是python中list,set这些高级的数据结构的,我们一旦申请了一段内存空间以后这一段空间就归你了,比如我们举个例子,我们申请一个数组 int nums[

  • C语言全方位讲解数组的使用

    目录 一维数组的创建和初始化 1.数组的创建 2.数组创建方式 3.数组的初始化 一维数组的使用 一维数组的存储 二维数组的创建与初始化 1.二维数组的创建 2.二维数组的初始化 二维数组的存储 数组的越界 总结 接着上次的操作符的详解,让我们来简单了解C语言里的数组. 一维数组的创建和初始化 1.数组的创建 数组是一组相同类型的元素的集合. 2.数组创建方式 type_t(数组类型) arr_name(数组名) [const_n](用来指定数组大小) 3.数组的初始化 数组的初始化是在其定义的

随机推荐