zl程序教程

您现在的位置是:首页 >  IT要闻

当前栏目

C语言:内存管理

2023-02-18 16:40:17 时间

内存四区

一个C\C++编译的程序占用内存的四个区:

  1. 栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  2. 堆区(heap): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事。
  3. 数据区(Data):主要包括静态全局区和常量区,如果要站在汇编角度细分的话还可以分为很多小的区。全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量。 4、代码区(Code):存放函数体的二进制代码。
  4. 代码区(Code):存放函数体的二进制代码。

栈(stack):向下生长(填坑)

堆(heap):向上生长(柴火)

Heap、stack生长方向和内存存放方向是两个不同概念

内存四区和函数调用变量传递:一个单进程主程序有n个函数组成,C/C++编译器只会分配一个堆区,一个栈区。

内存相关函数

C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。

calloc()

分配 num 个长度为 size 的连续空间,并返回一个指向它的指针。calloc 会将分配内存地址上的值初始化为0。

void *calloc(int num, int size);

参数

  • num:分配的元素个数
  • size:元素的大小

返回:返回一个指针,指向以分配的内存,如果请求失败,则返回 NULL

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

int main(void) {
    int i, n;
    int *a;
    printf("要输入元素的个数:");
    scanf("%d", &n);

    // 因为calloc返回的是指针,所以要用指针去接,因为a是整型的还要强制转换成整型指针,
    // 分配 n*sizeof(int) 的内存大小
    a = (int *) calloc(n, sizeof(int));
    printf("输入%d个数字", n);
    for (i = 0; i < n; i++) {
        scanf("%d", &a[i]); // 格式化输入后面的形参需要 指针变量a的地址
    }

    printf("输入的数字为:");
    for (i = 0; i < n; i++) {
        printf("%d", a[i]);
    }
    free(a);    // 释放内存
    return 0;
}

malloc()

分配size个字节的内存空间(这块内存空间在函数执行完成后不会被初始化),并返回一个指向它的指针。

void *malloc(size_t size)

参数

  • size:字节,内存块的大小

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

realloc()

重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。把内存扩展到 newsize 

void *realloc(void *ptr, size_t size)

参数

  • ptr:malloc、calloc 或 realloc 返回的指针
  • size:字节,内存块的新大小

返回:返回一个指针,指向重新分配大小的内存

free()

释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。

void free(void *ptr)

参数

  • ptr:指针,指向一个要释放内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果传递的参数是一个空指针,则不会执行任何动作。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    char *str;
    
    /* 最初的内存分配 */
    // 分配15字节内存,返回指向它的指针
    str = (char *) malloc(15);
    str = (char *) malloc(15 * sizeof(char));      // 一般这么写,一目了然
    strcpy(str, "Never");
    printf("String = %s,  Address = %p\n", str, str);
    // String = Never,  Address = 0000000000701440

    /* 重新分配内存 */
    str = (char *) realloc(str, 25 * sizeof(char));    // 重新分配25字节内存,返回指向它的指针
    strcat(str, ".Ling");
    printf("String = %s,  Address = %p\n", str, str);
    // String = Never.Ling,  Address = 0000000000701440

    free(str);

    return (0);
}

memcpy()

  从存储区(地址上的数据;指针指向的地址上的数据) str2 复制 n 个字节到存储区 str1

void *memcpy(void *str1, const void *str2, size_t n)

参数

  • str1:目标数组
  • str2:数据源
  • n:字节数

返回:返回一个指向目标存储区 str1 的指针

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

int main ()
{
    const char src[20] = "Never.Ling";
    char dest[20];

    memcpy(dest, src, strlen(src)+1);
    printf("dest = %s\n", dest);    //dest = Never.Ling

    return(0);
}

将 s 中第 11 个字符开始的 6个连续字符复制到 d 中:

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

int main() {
    char *s="Never.Ling";   // s指向字符串的首地址
    char d[20];
    memcpy(d, s+6, 4);// 从第 6 个字符(r)开始复制,连续复制 4 个字符
    d[4]='\0';
    printf("%s", d);    // Ling
    return 0;
}

memmove()

void *memmove(void *str1, const void *str2, size_t n)

  从 str2 复制 n 个字节到 str1

  在重叠内存块这方面,memmove() 是比 memcpy() 更安全的方法。如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同。

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

int main (){
    const char dest[] = "oldstring";
    const char src[]  = "newstring";

    printf("dest = %s, src = %s\n", dest, src); // dest = oldstring, src = newstring
    memmove(dest, src, 9);
    printf("dest = %s, src = %s\n", dest, src);// dest = newstring, src = newstring

    return(0);
}

memset()

void *memset(void *str, int c, size_t n)

复制字符 c(一个无符号字符)到 str 所指向 指针的前 n 个字符。常用于清除内存数据

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

int main (){
    char str[20];

    strcpy(str,"Never.Ling");
    puts(str);      // Never.Ling

    memset(str, '$', 3);
    // memset(str, 0, sizeof(str));    // 清除内存位置
    puts(str);      // $$$er.Ling

    return(0);
}

动态分配内存

注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

编程时,如果您预先知道数组的大小,那么定义数组时就比较容易。例如,一个存储人名的数组,它最多容纳 100 个字符,所以您可以定义数组,如下所示:

char name[100];

但是,如果我们预先不知道需要存储的文本长度,在这里,我们需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存,如下所示:

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

int main() {
    char name[100];
    char *description;

    strcpy(name, "Zara Ali");

    /* 动态分配内存 */
    description = (char *)malloc( 200 * sizeof(char) );
    strcpy( description, "Zara ali a DPS student in class 10th");

    printf("Name = %s\n", name );       // Name = Zara Ali
    printf("Description: %s\n", description );
    // Description: Zara ali a DPS student in class 10th
}

上面的程序也可以使用 calloc() 来编写,只需要把 malloc 替换为 calloc 即可,如下所示:

calloc(200, sizeof(char));

当动态分配内存时,您有完全控制权,可以传递任何大小的值。而那些预先定义了大小的数组,一旦定义则无法改变大小。