51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

内存布局

  1. 内存分区 {#1-内存分区} =================

C代码经过预处理、编译、汇编、链接4步后生成一个二进制可执行程序。

在 Linux 下,可以在命令行中通过size命令查看二进制文件(可执行文件、静态库、动态库等)的大小和节(section)信息。

size 命令的基本语法是:

|-----------|-------------------------| | 1 | size [选项] [文件名] |

示例使用:

|-----------|---------------------------| | 1 | $ size my_program |

上述名为 my_program 的可执行文件的大小信息输出如下:

|-------------|--------------------------------------------------------------------------------------------------------| | 1 2 | text data bss dec hex filename 10024 464 24 10512 2900 my_program |

  • text:代码段(可执行文件)或只读数据段(库文件)的大小。
  • data:已初始化数据段的大小。
  • bss:未初始化数据段(bss)的大小。
  • dec:代码段、数据段和bss段的总大小。
  • hex:十六进制表示的 dec 的大小。
  • filename:文件的名称。

通过命令输出的信息可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)数据区(data)未初始化数据区(bss)3 个部分(有些人直接把 data 和 bss 合起来叫做静态区或全局区)。

  • 代码区(text segment)

    加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。

  • 未初始化数据区(BSS)

    加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。

  • 已初始化数据区(data segment)

    加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。

  • 栈区(stack)

    栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。

  • 堆区(heap)

    堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

下表是对变量类型、作用域、生命周期、存储位置的总结:

| 类型 | 作用域 | 生命周期 | 存储位置 | |:----------:|:-------:|:--------:|:-------------------:| | 局部变量 | 一对{}内 | 当前函数 | 栈区 | | static局部变量 | 一对{}内 | 整个程序运行期 | 初始化在data段,未初始化在BSS段 | | extern变量 | 整个程序 | 整个程序运行期 | 初始化在data段,未初始化在BSS段 | | static全局变量 | 当前文件 | 整个程序运行期 | 初始化在data段,未初始化在BSS段 | | extern函数 | 整个程序 | 整个程序运行期 | 代码区 | | static函数 | 当前文件 | 整个程序运行期 | 代码区 | | register变量 | 一对{}内 | 当前函数 | 运行时存储在CPU寄存器 | | 字符串常量 | 当前文件 | 整个程序运行期 | data段 |

在下面的示例程序中打印了不同类型的变量对应的数据存储位置:

|---------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <stdio.h> #include <stdlib.h> int e; static int f; int g = 10; static int h = 10; int main() { int a; int b = 10; static int c; static int d = 10; char* i = "test"; char* k = NULL; printf("&a\t %p\t // 局部未初始化变量\n", &a); printf("&b\t %p\t // 局部初始化变量\n", &b); printf("&c\t %p\t // 静态局部未初始化变量\n", &c); printf("&d\t %p\t // 静态局部初始化变量\n", &d); printf("&e\t %p\t // 全局未初始化变量\n", &e); printf("&f\t %p\t // 全局静态未初始化变量\n", &f); printf("&g\t %p\t // 全局初始化变量\n", &g); printf("&h\t %p\t // 全局静态初始化变量\n", &h); printf("i\t %p\t // 只读数据(文字常量区)\n", i); k = (char*)malloc(10); printf("k\t %p\t // 动态分配的内存\n", k); return 0; } |

  1. 内存操作函数 {#2-内存操作函数} =====================

2.1 memset() {#2-1-memset}

memset 是 C 语言标准库 <string.h> 中提供的一个函数,用于将一块内存区域填充为指定的值。

memset 函数的原型如下:

|-----------|---------------------------------------------------------| | 1 | void *memset(void *ptr, int value, size_t num); |

参数说明:

  • ptr:指向要填充的内存区域的指针。
  • value:要填充的值,以整数形式传递。
  • num:要填充的字节数。

memset 函数将指定内存区域 ptr 开始的 num 个字节设置为 value

以下是一个使用 memset 的示例:

|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 | #include <stdio.h> #include <string.h> int main() { char str[15]; // 填充 str 数组的前 15 个字节为字符 'A' memset(str, 'A', 15); printf("str 内容为:%s\n", str); return 0; } |

在上述示例中,我们定义了一个 char 类型的数组 str,长度为 15。使用 memset 函数将数组的前 15 个字节设置为字符 'A'。最后使用 printf 函数打印数组内容。

运行程序后的输出结果如下所示:

|-----------|---------------------------------| | 1 | str 内容为:AAAAAAAAAAAAAAA |

需要注意的是,memset 函数逐字节设置指定内存区域,适用于字符类型 (char) 或无符号字节类型 (unsigned char) 的数据。对于其他数据类型,如整数或自定义结构体,如果需要设置为其他具体的值,可以使用其他方法,如循环逐个赋值。此外,在使用 memset 时需要确保目标内存区域的大小不超过其分配的大小,以避免访问越界。

2.2 memcpy() {#2-2-memcpy}

memcpy 是 C 语言标准库 <string.h> 中提供的一个函数,用于将源内存区域的内容复制到目标内存区域。

memcpy 函数的原型如下:

|-----------|--------------------------------------------------------------| | 1 | void *memcpy(void *dest, const void *src, size_t n); |

参数说明:

  • dest:指向目标内存区域的指针,也就是要将源数据复制到的地方。
  • src:指向源内存区域的指针,也就是要复制的数据来源。
  • n:要复制的字节数。

memcpy 函数会将源内存区域 src 的前 n 个字节的内容复制到目标内存区域 dest 中。

以下是一个使用 memcpy 的示例:

|------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h> #include <string.h> int main() { char src[] = "Hello, Dabing!"; char dest[20]; // 将 src 数组的内容复制到 dest 数组 memcpy(dest, src, strlen(src) + 1); printf("dest 内容为:%s\n", dest); return 0; } |

在上述示例中,我们定义了一个 src 数组,内容为 "Hello, Dabing!",然后定义了一个 dest 数组作为目标。使用 memcpy 函数将 src 数组的内容复制到 dest 数组中。最后使用 printf 打印 dest 数组的内容。

运行程序后的输出结果如下所示:

|-----------|---------------------------------| | 1 | dest 内容为:Hello, Dabing! |

需要注意的是,memcpy 函数逐字节复制源内存区域的内容到目标内存区域,适用于任意类型的数据。但需要确保目标内存区域的大小足够容纳源数据,以避免访问越界。同时,为了防止内存重叠导致不确定的结果,建议源内存区域和目标内存区域不能重叠。

2.3 memmove() {#2-3-memmove}

memmove 是 C 语言标准库 <string.h> 中提供的一个函数,用于在内存中移动一块数据。

memmove 函数的原型如下:

|-----------|---------------------------------------------------------------| | 1 | void *memmove(void *dest, const void *src, size_t n); |

参数说明:

  • dest:指向目标内存区域的指针,也就是要将数据移动到的地方。
  • src:指向源内存区域的指针,也就是要移动的数据来源。
  • n:要移动的字节数。

memmove 函数会将源内存区域 src 的内容移动到目标内存区域 dest 中,并且可以处理内存重叠的情况。

以下是一个使用 memmove 的示例:

|------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h> #include <string.h> int main() { char str[] = "Hello, Dabing!"; char buffer[20]; // 将 str 数组的内容移动到 buffer 数组 memmove(buffer, str, strlen(str) + 1); printf("buffer 内容为:%s\n", buffer); return 0; } |

在上述示例中,我们定义了一个 str 数组,内容为 "Hello, Dabing!",然后定义了一个 buffer 数组作为目标。使用 memmove 函数将 str 数组的内容移动到 buffer 数组中。最后使用 printf 打印 buffer 数组的内容。运行程序后的输出结果如下所示:

|-----------|-----------------------------------| | 1 | buffer 内容为:Hello, Dabing! |

需要注意的是,与 memcpy 不同的是,memmove 函数能够处理源内存区域和目标内存区域重叠的情况,以确保正确的数据移动。因此,当需要在内存中进行数据移动时,并且源内存区域和目标内存区域可能重叠时,建议使用 memmove 函数而不是 memcpy 函数。

2.4 memcmp() {#2-4-memcmp}

memcmp 是 C 语言标准库 <string.h> 中提供的一个函数,用于比较两个内存区域的内容。

memcmp 函数的原型如下:

|-----------|---------------------------------------------------------------------| | 1 | int memcmp(const void *ptr1, const void *ptr2, size_t num); |

参数说明:

  • ptr1:指向第一个内存区域的指针。
  • ptr2:指向第二个内存区域的指针。
  • num:要比较的字节数。

memcmp 函数会比较 ptr1ptr2 指向的内存区域的前 num 个字节的内容,并根据比较结果返回一个整数值。返回值的含义如下:

  • 如果 ptr1 的内容小于 ptr2 的内容,返回一个负整数。
  • 如果 ptr1 的内容等于 ptr2 的内容,返回 0。
  • 如果 ptr1 的内容大于 ptr2 的内容,返回一个正整数。

以下是一个使用 memcmp 的示例:

|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h> #include <string.h> int main() { char str1[] = "Hello"; char str2[] = "World"; int result = memcmp(str1, str2, 5); if (result < 0) { printf("str1 小于 str2\n"); } else if (result == 0) { printf("str1 等于 str2\n"); } else { printf("str1 大于 str2\n"); } return 0; } |

在上述示例中,我们使用 memcmp 函数比较了两个字符串 str1str2 的前 5 个字符。根据比较结果,使用条件语句判断并打印了相应的结果。

运行程序后的输出结果如下:

|-----------|----------------------| | 1 | str1 小于 str2 |

需要注意的是,memcmp 函数是以字节为单位进行比较的,因此适用于比较任意类型的数据。但是,当比较字符串时,建议使用字符串比较函数 strcmp 来代替 memcmp,因为 strcmp 能够根据字符串的终止符'\0'自动确定比较的长度,而不需要显式指定比较的字节数。

  1. 堆内存的分配和释放 {#3-堆内存的分配和释放} ===========================

3.1 函数原型 {#3-1-函数原型}

在 C 语言中,使用标准库函数 <stdlib.h> 中的以下函数来进行堆内存的分配和释放:

  1. 分配内存有三种方式:

    • malloc:分配一块新的内存

      |-------------|--------------------------------------------------------| | 1 2 | #include <stdlib.h> void* malloc(size_t size); |

      • 参数size 表示欲分配的内存大小,以字节为单位。
      • 返回值:
        • 成功,返回一个指向分配内存的指针
        • 失败,则返回 NULL
    • calloc:与 malloc 类似,用于分配指定数量的内存块。不同之处在于,calloc 还会将分配的内存块初始化为0。

      |-------------|--------------------------------------------------------------------| | 1 2 | #include <stdlib.h> void* calloc(size_t num, size_t size); |

      • 函数参数
        • num 表示欲分配的内存块数目
        • size 表示每个内存块的大小(以字节为单位)
      • 函数返回值:
        • 成功,返回一个指向分配内存的指针
        • 失败,则返回 NULL
    • realloc:用于重新分配已分配内存的大小。

      |-------------|--------------------------------------------------------------------| | 1 2 | #include <stdlib.h> void* realloc(void* ptr, size_t size); |

      • 函数参数
        • ptr 是之前使用 malloccallocrealloc 函数分配的内存块的指针
        • size 表示需要重新分配的内存大小(以字节为单位)
      • 函数返回值:
        • 成功,返回一个指向重新分配后内存的指针
        • 失败,返回 NULL
  2. 释放内存:

    • free:用于释放之前通过 malloccallocrealloc 分配的内存。它接受一个指针作为参数,将该指针所指向的内存块释放,并使该指针不再有效。

      |-------------|---------------------------------------------------| | 1 2 | #include <stdlib.h> void free(void* ptr); |

3.2 malloc() {#3-2-malloc}

以下是一个使用 malloc 分配内存的示例:

|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <stdio.h> #include <stdlib.h> int main() { int* ptr; // 分配可以存储 5 个 int 值的内存块, 内存中的值是随机的 ptr = (int*)malloc(5 * sizeof(int)); if (ptr == NULL) { printf("内存分配失败\n"); return 1; } // 使用分配的内存块 for (int i = 0; i < 5; i++) { ptr[i] = i + 1; } // 打印内存中的值 for (int i = 0; i < 5; i++) { printf("%d ", ptr[i]); } printf("\n"); // 释放内存 free(ptr); return 0; } |

在上述示例中,我们首先定义了一个指向 int 类型的指针 ptr,然后使用 malloc 分配了可以存储 5 个 int 值的内存块。如果分配成功,则返回的指针被赋给 ptr。我们可以使用这块分配的内存来存储和访问数据。最后,使用 free 函数释放了分配的内存。

需要注意以下几点:

  • 使用 malloc 分配的内存需要在使用完毕后调用 free 函数进行释放,以避免内存泄漏。
  • 分配的内存大小应根据实际需要进行调整,大小不足可能导致程序运行错误,而过大可能浪费内存资源。
  • 当使用 malloc 分配内存失败时,返回的指针为 NULL,需要对其进行检查。

在实际开发中,使用 malloc 分配动态内存可以灵活地管理和利用内存资源,但也需要注意内存的释放和错误处理,以确保程序的正确性和健壮性。

3.3 calloc() {#3-3-calloc}

以下是一个使用 calloc 分配内存的示例:

|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <stdio.h> #include <stdlib.h> int main() { int* ptr; // 分配可以存储 5 个 int 值的内存块,并初始化为 0 ptr = (int*)calloc(5, sizeof(int)); if (ptr == NULL) { printf("内存分配失败\n"); return 1; } // 打印内存中的值 for (int i = 0; i < 5; i++) { printf("%d ", ptr[i]); } printf("\n"); // 释放内存 free(ptr); return 0; } |

在上述示例中,我们使用 calloc 分配了可以存储 5 个 int 值的内存块,并将所有元素初始化为 0。然后,我们通过循环打印了分配的内存块中的值。需要注意以下几点:

  • malloc 类似,使用 calloc 分配的内存需要在使用完毕后调用 free 函数进行释放,以避免内存泄漏。
  • 分配的内存大小应根据实际需要进行调整,大小不足可能导致程序运行错误,而过大可能浪费内存资源。
  • 当使用 calloc 分配内存失败时,返回的指针为 NULL,需要对其进行检查。

使用 calloc 可以方便地分配内存,并初始化为零。在需要使用零初始化的情况下,calloc 是一个很有用的函数。

3.4 realloc() {#3-4-realloc}

以下是一个使用 realloc 重新分配内存大小的示例:

|---------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include <stdio.h> #include <stdlib.h> int main() { // 分配可以存储 5 个 int 值的内存块 int* ptr = (int*)malloc(5 * sizeof(int)); if (ptr == NULL) { printf("内存分配失败\n"); return 1; } // 重新分配内存为可以存储 10 个 int 值的内存块 ptr = (int*)realloc(ptr, 10 * sizeof(int)); if (ptr == NULL) { printf("内存重新分配失败\n"); return 1; } // 打印重新分配内存后的值 for (int i = 0; i < 10; i++) { ptr[i] = i + 100; printf("%d ", ptr[i]); } printf("\n"); // 释放内存 free(ptr); return 0; } |

在上述示例中,我们首先使用 malloc 分配了可以存储 5 个 int 值的内存块,然后使用 realloc 将内存重新分配为可以存储 10 个 int 值的内存块。注意,当调用 realloc 后,我们需要将返回的指针重新赋值给原来的指针变量 ptr,以确保使用的是重新分配后的内存。

需要注意以下几点:

  • 使用 realloc 重新分配内存后,返回的指针可能是原有内存的地址,也可能是新分配内存的地址,因此在重新分配内存后要基于返回的地址进行后续的处理。
  • 当使用 realloc 重新分配内存失败时,返回的指针为 NULL,需要对其进行检查。
  • 在使用 realloc 时,尽量提供尽可能准确的新大小,避免频繁地重新分配内存,以提高效率。

使用 realloc 可以动态地调整已分配内存的大小,使其更适应程序的需求。在需要动态修改内存大小的情况下,realloc 是一个有用的函数。

赞(0)
未经允许不得转载:工具盒子 » 内存布局