C/C++内存分区和内存操作函数

在典型的 C++ 程序执行过程中,内存被大致划分为以下 4 个主要区域

C++ 程序运行时的 4 个内存区域

区域 说明 生命周期 增长方向
代码区 编译后的程序指令(程序的机器指令) 程序整个运行期间 固定,靠近低地址,通常只读、共享
全局/静态区 全局变量、静态变量、常量 程序整个运行期间 固定,紧随代码区,也靠近低地址
堆区 动态内存分配(手动管理) 动态分配的内存(new / malloc)手动分配与释放 低地址 → 高地址
栈区 局部变量、函数调用帧(自动管理) 调用时生成、调用完释放(局部变量、函数参数、返回地址等) 高地址 → 低地址

地址增长方向示意图

      高地址
        ↓
+----------------------+
|      栈区(Stack)    |  ← 函数调用时局部变量在这里
+----------------------+
|                      |
|       空闲区         |  ← 堆和栈之间可能还有未分配内存
|                      |
+----------------------+
|      堆区(Heap)     |  ← new/malloc 动态分配的数据
+----------------------+
| 静态/全局区(Data)   |  ← static/global 变量
+----------------------+
|      代码区(Text)    |  ← 编译后的指令
+----------------------+
        ↑
      低地址

区域详解

1. 代码区(Text Segment)

  • 包含程序的可执行指令(.text 段)
  • 通常是只读的(防止程序自我修改)
  • 可以多个进程共享(节省内存)

2. 全局/静态区(Data Segment / BSS Segment)

  • 包含:
    • 已初始化的全局变量、静态变量 → .data
    • 未初始化的全局/静态变量 → .bss
    • 字符串常量、只读常量 → .rodata
  • 生命周期:整个程序运行期间都存在

3. 堆区(Heap)

  • 程序运行时使用 new / malloc 动态分配的内存
  • 管理成本高(需手动释放或智能指针)
  • 地址从低向高增长

4. 栈区(Stack)

  • 函数调用产生的临时变量、参数、返回地址
  • 自动释放
  • 地址从高向低增长
  • 受限(栈空间有限,过深递归会栈溢出)

实验:打印地址观察

#include <iostream>

int global_var = 1;
static int static_var = 2;

int main() {
    int local_var = 3;
    static int static_local = 4;
    int* heap_var = new int(5);

    std::cout << "Code (main): " << (void*)main << "\n";
    std::cout << "Global var: " << &global_var << "\n";
    std::cout << "Static var: " << &static_var << "\n";
    std::cout << "Static local: " << &static_local << "\n";
    std::cout << "Heap var: " << heap_var << "\n";
    std::cout << "Stack var: " << &local_var << "\n";

    delete heap_var;
    return 0;
}

// 输出结果
Code (main): 0x5ef0cdcb9209
Global var: 0x5ef0cdcbc010
Static var: 0x5ef0cdcbc014
Static local: 0x5ef0cdcbc018
Heap var: 0x5ef0f2ed4eb0
Stack var: 0x7ffc537b7ebc

C语言内存分配和释放函数

在 C 语言中,动态内存管理是通过一组标准库函数来实现的,这些函数包括 malloccallocreallocfree。它们提供了对堆(heap)内存的手动分配和释放控制,这对于需要在运行时确定大小的数据结构或数组特别有用。下面详细介绍这四个函数的功能、用法及其区别。

1. malloc

  • 功能:分配指定字节数的内存块,并返回指向该内存块起始位置的指针。

  • 原型

    void *malloc(size_t size);
    
  • 参数size - 需要分配的内存大小(以字节为单位)。

  • 返回值:成功时返回一个指向分配内存的指针;如果内存不足,则返回 NULL

  • 注意事项:分配的内存不会被初始化,内容可能是随机的。

示例代码

int *ptr = (int *)malloc(5 * sizeof(int)); // 分配能存储5个整数的空间
if(ptr == NULL) {
    printf("Memory allocation failed\n");
}

2. calloc

  • 功能:分配并初始化指定数量和类型的元素组成的内存块。所有位均设置为零。

  • 原型

    void *calloc(size_t num, size_t size);
    
  • 参数

    • num - 要分配的元素数量。
    • size - 每个元素的大小(以字节为单位)。
  • 返回值:成功时返回一个指向分配内存的指针;如果内存不足,则返回 NULL

  • 注意事项:与 malloc 不同,calloc 会将分配的内存初始化为零。

示例代码

int *ptr = (int *)calloc(5, sizeof(int)); // 分配并初始化能存储5个整数的空间
if(ptr == NULL) {
    printf("Memory allocation failed\n");
}

3. realloc

  • 功能:调整之前已分配内存块的大小,可以增加也可以减少。

  • 原型

    void *realloc(void *ptr, size_t size);
    
  • 参数

    • ptr - 指向之前通过 malloc, calloc, 或 realloc 分配的内存块的指针。
    • size - 新的内存块大小(以字节为单位)。
  • 返回值:成功时返回一个指向新分配内存的指针;如果内存不足,则返回 NULL,但原内存保持不变。

  • 注意事项:如果新的大小比原来的要小,数据可能被截断;如果增大了,额外的空间内容未定义。

示例代码

int *ptr = (int *)malloc(5 * sizeof(int));
// 假设我们需要更多的空间...
ptr = (int *)realloc(ptr, 10 * sizeof(int)); // 尝试重新分配更大的空间
if(ptr == NULL) {
    printf("Memory reallocation failed\n");
}

4. free

  • 功能:释放之前通过 malloc, calloc, 或 realloc 分配的内存块。

  • 原型

    void free(void *ptr);
    
  • 参数ptr - 指向要释放的内存块的指针。

  • 注意事项:一旦调用了 freeptr 所指向的内存就不能再被访问。尝试这样做会导致未定义行为。此外,多次释放同一块内存也会导致错误。

示例代码

int *ptr = (int *)malloc(5 * sizeof(int));
// 使用 ptr ...
free(ptr); // 释放内存
ptr = NULL; // 设置为 NULL 是一个好的实践,防止悬挂指针

📝 总结

  • malloc:简单地分配内存而不进行初始化。
  • calloc:分配内存并将其初始化为零。
  • realloc:用于调整已有内存块的大小,同时保留原有数据(如果有足够的空间)。
  • free:释放之前分配的内存,避免内存泄漏。

C语言内存拷贝与设置函数

在 C 语言中,以 mem 为前缀的函数属于 内存操作函数,它们定义在标准头文件 <string.h> 中(注意:不是 <memory.h>,虽然某些系统支持)。这些函数用于对连续内存块进行操作,与字符串处理函数不同,它们不依赖于终止符 \0,而是直接根据给定的字节数进行操作。参数是dest指针在最前,size_t在最后。

✅ 1. void *memcpy(void *dest, const void *src, size_t n);

  • 功能:从 src 拷贝 n 个字节到 dest
  • 注意不能用于内存区域重叠的情况。如果源和目标内存区域有重叠,行为未定义。
  • 返回值:返回指向 dest 的指针。

示例:

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

int main() {
    char src[] = "Hello, World!";
    char dest[20];
    memcpy(dest, src, strlen(src) + 1); // 包括 '\0'
    printf("dest: %s\n", dest);
    return 0;
}

✅ 2. void *memmove(void *dest, const void *src, size_t n);

  • 功能:与 memcpy 类似,但可以安全地用于内存区域重叠的情况。
  • 返回值:返回指向 dest 的指针。

示例:

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

int main() {
    char str[] = "memmove can overlap";
    memmove(str + 7, str, 10); // 安全地复制重叠区域
    printf("%s\n", str);
    return 0;
}

✅ 3. int memcmp(const void *s1, const void *s2, size_t n);

  • 功能:比较两个内存块的前 n 个字节。
  • 返回值
    • s1 < s2 返回负整数;
    • 若相等返回 0;
    • s1 > s2 返回正整数。

示例:

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

int main() {
    char a[] = "abcde";
    char b[] = "abXde";

    int result = memcmp(a, b, 5);
    if (result == 0)
        printf("Equal\n");
    else if (result < 0)
        printf("a < b\n");
    else
        printf("a > b\n");

    return 0;
}

✅ 4. void *memset(void *s, int c, size_t n);

  • 功能:将 s 所指向的内存块的前 n 个字节设置为值 c(以 unsigned char 形式写入)。
  • 用途:常用于初始化内存、填充数组或结构体。

示例:

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

int main() {
    char buffer[10];
    memset(buffer, 'A', 5);   // 前5个字符设为 'A'
    memset(buffer + 5, 0, 5); // 后5个设为 0(空字符)

    for(int i = 0; i < 10; i++) {
        printf("%d ", buffer[i]);
    }
    return 0;
}

✅ 5. void *memchr(const void *s, int c, size_t n);

  • 功能:在内存块 s 的前 n 个字节中查找值为 c(转换为 unsigned char)的第一个出现位置。
  • 返回值:找到则返回指向该位置的指针;否则返回 NULL

示例:

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

int main() {
    char data[] = {0x10, 0x20, 0x30, 0x40, 0x20};
    void *p = memchr(data, 0x20, sizeof(data));

    if(p != NULL)
        printf("Found at position: %ld\n", (char*)p - data);

    return 0;
}

总结

mem 开头的函数是 C 语言中非常基础且重要的内存操作工具:

  • memcpy / memmove:用于复制内存块;
  • memcmp:用于比较;
  • memset:用于初始化;
  • memchr:用于查找指定字节。

C++中的内存操作方式

1. 使用 C 标准库函数

C++ 兼容所有 C 的内存函数,只需包含头文件如 <cstdlib><cstring>

2. newdelete 运算符

new

int* p = new int;
int* arr = new int[10];
  • 自动调用构造函数(对于类类型)。
  • 分配失败时抛出异常(除非使用 nothrow 版本)。

delete

delete p;
delete[] arr;  // 注意数组要用 delete[]
  • 自动调用析构函数。
  • 必须匹配 newdelete 类型。

在现代 C++ 编程中,应优先使用以下方式管理内存:

1. 智能指针

#include <memory>
std::unique_ptr<int> p(new int(5));  // 自动释放
std::shared_ptr<int> sp = std::make_shared<int>(10);

2. 容器类

#include <vector>
std::vector<int> vec(10);  // 自动管理内存

3. RAII 模式

资源获取即初始化(Resource Acquisition Is Initialization),确保资源安全释放。

posted @ 2025-06-13 17:09  aixueforever  阅读(37)  评论(0)    收藏  举报