枚举与动态内存:从「编译时常量」到「运行时空间」

C++枚举序列化难题:magic_enum库的编译时反射解决方案 在C++开发中,枚举类型是表示状态码、配置选项等有限集合的常用工具。其核心原理是通过整数值映射到具名常量,提供类型安全。然而,枚举的序列化与反序列化一直是工程实践中的痛点,开发者需要手动编写大量switch-case或if-else代码进行字符串转换,这不仅降低开发效率,还容易引入维护错误。编译时反射技术为解决这一问题提供了技术价值,它允许在编译期获取类型的元信息,实现零开销的类型自省。magic_enum库正是基于C++17的编译时反射特性,通过解析__PRETTY_FUNCTION__等编译器宏,在编译 阅读详情

枚举与动态内存:从「编译时常量」到「运行时空间」

1. 一句话总结

枚举把散落的魔法数字变成有名字的常量,而动态内存让你在程序运行时按需索取堆空间——两者一个管编译时,一个管运行时,都是写出灵活 C 程序的基石。


2. 知识地图

枚举与动态内存
├── 枚举(enum)
│   ├── 基本概念:给整数常量起名字
│   ├── 默认编号:从 0 开始递增
│   ├── 手动赋值:可以打断默认编号,后续继续递增
│   └── 实战应用:配合 switch-case 避免魔法数字
│
├── 动态内存分配(malloc / calloc / realloc)
│   ├── malloc:分配未初始化内存
│   ├── calloc:分配并初始化为 0
│   ├── realloc:调整已分配内存的大小
│   └── 核心原则:谁申请谁释放,释放后指针置 NULL
│
├── 常见错误
│   ├── 忘记检查返回值(NULL 判断)
│   ├── 忘记 free(内存泄漏)
│   ├── free 之后继续使用(野指针)
│   ├── 对非动态内存调用 free(未定义行为)
│   └── realloc 返回值直接赋给原指针(丢失原始地址)
│
└── 柔性数组(Flexible Array Member)
    ├── 定义:结构体最后一个成员,不写元素个数
    ├── 特点:sizeof 不计算柔性数组大小
    ├── 分配:malloc(sizeof(struct) + N * sizeof(element))
    └── 优势:一次 malloc 同时管理结构体和数组,减少内存碎片

3. 核心概念深度解析

3.1 malloc 的内存布局:堆上的一次「圈地」

当你调用 malloc(20) 时,操作系统在堆上为你划出 20 个字节。整个过程可以用 ASCII 字符画来理解:

调用前──堆区空闲
┌──────────────────────────────────────────────┐
│  ... 空闲空间 ...                             │
└──────────────────────────────────────────────┘

int *pf = (int *)malloc(20);
// 在堆上分配 20 字节,返回起始地址 0x1000

调用后──堆区已分配
       pf = 0x1000
       │
       ▼
┌──────┬──────┬──────┬──────┬──────┬────────────┐
│  ?   │  ?   │  ?   │  ?   │  ?   │  空闲空间   │
│ 4B   │ 4B   │ 4B   │ 4B   │ 4B   │            │
└──────┴──────┴──────┴──────┴──────┴────────────┘
 0x1000 0x1004 0x1008 0x100C 0x1010 0x1014

写入数据后:
*(pf+0) = 1;  *(pf+1) = 2;  *(pf+2) = 3;  *(pf+3) = 4;  *(pf+4) = 5;

┌──────┬──────┬──────┬──────┬──────┬────────────┐
│  1   │  2   │  3   │  4   │  5   │  空闲空间   │
│ 4B   │ 4B   │ 4B   │ 4B   │ 4B   │            │
└──────┴──────┴──────┴──────┴──────┴────────────┘

free(pf);  // 归还给系统,但这块内存的旧数据可能还在
pf = NULL; // 切断指针联系,避免野指针

关键点:为什么用 *(pf+i) 而不是 pf++?

错误做法:pf++
┌──────┬──────┬──────┬──────┬──────┐
│  1   │  2   │  3   │  4   │  5   │
└──────┴──────┴──────┴──────┴──────┘
  ↑                           ↑
  pf 原来指向这里             pf 最终指向这里(第 5 个元素之后)

  free(pf);  ← 危险!pf 不是起始地址了,释放的是错误位置!

正确做法:*(pf+i)
  pf 始终指向起始地址 0x1000
  *(pf+0) → 访问第 0 个元素
  *(pf+1) → 访问第 1 个元素
  ...
  free(pf);  ← 安全!pf 还是起始地址

我的理解: pf 是你在堆上「圈地」的房产证,只有拿着原件(起始地址)才能把地还给系统。pf++ 相当于把房产证上的地址涂改了——系统不认了。*(pf+i) 则是你拿着原件去查册,不影响原件本身。


3.2 realloc 扩容:原地扩容 vs 异地搬迁

realloc 的逻辑比很多人想的复杂——它不是简单的「把原来的空间变大」:

场景一:原地扩容(理想情况,后面有连续空闲空间)

扩容前:
┌──────┬──────┬──────┬──────┬──────┬──────────────────┐
│  1   │  2   │  3   │  4   │  5   │   空闲空间        │
└──────┴──────┴──────┴──────┴──────┴──────────────────┘
  ↑                                   ↑
  ptr                             空闲空间足够大

realloc(ptr, 40*sizeof(int)) 后:
┌──────┬──────┬──────┬──────┬──────┬──────┬─────┬─────┐
│  1   │  2   │  3   │  4   │  5   │ 新空间...      │
└──────┴──────┴──────┴──────┴──────┴──────┴─────┴─────┘
  ↑
  ptr 不变!返回值 == 原指针


场景二:异地搬迁(后面空间不够,只能找新地方)

扩容前:
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│  1   │  2   │  3   │  4   │  5   │ 已占用 │ 已占用 │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
  ↑
  ptr(后面没空间了!)

realloc 内部做的事:
  1. 在堆上找一块新的 40*sizeof(int) 空间
  2. 把旧空间的 5 个 int 拷贝过去
  3. free 旧空间
  4. 返回新空间的地址

┌──────┬──────┬──────┬──────┬──────┬─────────────────────┐
│  1   │  2   │  3   │  4   │  5   │  新空间(35个int)   │
└──────┴──────┴──────┴──────┴──────┴─────────────────────┘
  ↑
  新指针!返回值 ≠ 原指针


⚠️ 踩坑:为什么不能直接 ptr = realloc(ptr, new_size)?

  int *ptr = malloc(20);
  ptr = realloc(ptr, 4000);  // 如果 realloc 失败返回 NULL
                              // ptr 变成 NULL,原来 20 字节的内存再也找不回来了!
                              // 这就是「内存泄漏」

正确做法:
  int *p = realloc(ptr, 4000);
  if (p != NULL) {
      ptr = p;  // 确认成功再赋值
  } else {
      free(ptr);  // 即使扩容失败,也要释放原来的内存
      ptr = NULL;
  }

3.3 柔性数组:一次 malloc 搞定结构体 + 变长数组

柔性数组是 C99 的特性,它把「结构体」和「变长数组」绑定在同一块堆内存中,结构非常优雅:

struct S {
    char c;
    int i;
    int arr[];  // 柔性数组,不占 sizeof
};

sizeof(struct S) = 8(仅 c + 对齐填充 + i)

分配时:malloc(sizeof(struct S) + 5 * sizeof(int))

┌──────┬──────────┬──────┬──────┬──────┬──────┬──────┐
│  c   │ 对齐填充  │  i   │ arr[0]│arr[1]│arr[2]│arr[3]│arr[4] │
│ 1B   │  3B      │ 4B   │  4B   │ 4B   │ 4B   │ 4B   │ 4B    │
└──────┴──────────┴──────┴──────┴──────┴──────┴──────┴──────┘
  ◀────────── sizeof(struct S) ──────▶◀──── 5 * sizeof(int) ────▶
  ◀────────────────── 一整块连续的堆内存 ────────────────────────▶
                                       ↑
                                pf->arr 直接指向这里

对比传统做法(指针方案):

  struct S {
      char c;
      int i;
      int *arr;  // 存指针,不是直接存数组
  };

  // 需要两次 malloc!
  struct S *pf = malloc(sizeof(struct S));
  pf->arr = malloc(5 * sizeof(int));

  // 也需要两次 free!
  free(pf->arr);
  free(pf);

  // 两块内存可能不连续,增加内存碎片

柔性数组的优势:

对比维度柔性数组方案指针方案
malloc 次数1 次2 次(结构体 + 数组)
free 次数1 次2 次(顺序还不能错)
内存连续性连续可能不连续
内存碎片少多
缓存局部性好(数据挨在一起)差(可能跨页)

4. 代码实战

4.1 枚举:让 switch-case 变得可读

#include <stdio.h>

// 枚举常量:给整数起有意义的名字
// 默认从 0 开始编号,也可以手动指定
enum Color
{
    RED,    // 0
    GREEN,  // 1
    BLUE    // 2
};

enum Sex
{
    FEMALE = 2,  // 手动指定为 2
    MALE,        // 自动编号为 3(接着上一个 +1)
    SECRET       // 4
};

// 实战:用枚举配合 switch 替代魔法数字
enum Option
{
    EXIT,  // 0
    ADD,   // 1
    SUB,   // 2
    MUL,   // 3
    DIV    // 4
};

void menu()
{
    printf("**************************\n");
    printf("****  1. add    2.sub ****\n");
    printf("****  3. mul    4.div ****\n");
    printf("****  0. exit         ****\n");
    printf("**************************\n");
}

int main()
{
    int input = 0;
    menu();
    printf("请选择>");
    scanf("%d", &input);

    // 可读性对比:
    // case 1:  → 这是什么?不翻文档根本不知道
    // case ADD: → 一看就知道是加法操作
    switch (input)
    {
        case ADD:
            // 执行加法逻辑
            break;
        case SUB:
            // 执行减法逻辑
            break;
        case MUL:
            // 执行乘法逻辑
            break;
        case DIV:
            // 执行除法逻辑
            break;
        case EXIT:
            printf("退出程序\n");
            break;
        default:
            printf("无效输入\n");
            break;
    }
    return 0;
}

4.2 malloc:动态内存的标准三步走

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

int main()
{
    // 第一步:申请内存
    // malloc(20) 在堆上分配 20 字节,返回 void*,需要强制转换
    int *pf = (int *)malloc(20);

    // 第二步:检查是否申请成功
    // malloc 失败时返回 NULL,必须检查,否则后续解引用 NULL 直接崩溃
    if (pf == NULL)
    {
        perror("malloc");  // 打印系统错误信息
        return 1;
    }

    // 第三步:使用内存
    // 用 *(pf+i) 而不是 pf++,保持 pf 指向起始地址
    for (int i = 0; i < 5; i++)
    {
        *(pf + i) = i + 1;
    }

    // 第四步:释放内存
    free(pf);

    // 第五步:指针置空——防止野指针
    // free 之后 pf 仍然存着原来的地址,但那里已经是「别人的地盘」了
    pf = NULL;

    return 0;
}

4.3 calloc:自带初始化功能的 malloc

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

int main()
{
    // calloc 的两个参数:元素个数、每个元素大小
    // 它会在分配内存后把所有字节初始化为 0
    int *pf = (int *)calloc(5, sizeof(int));

    if (pf == NULL)
    {
        perror("calloc");
        return 1;
    }

    // 此时 pf[0] ~ pf[4] 全部是 0,不需要手动初始化
    for (int i = 0; i < 5; i++)
    {
        *(pf + i) = i + 1;  // 覆盖默认值
    }

    free(pf);
    pf = NULL;
    return 0;
}

malloc vs calloc 对比:

特性malloccalloc
参数形式malloc(size)calloc(n, size)
初始化不初始化,内容是随机值全部初始化为 0
性能更快(不擦除数据)稍慢(多了清零操作)
适用场景马上覆盖全部数据时需要默认值 0 时(如计数器、标志位)

我的理解: calloc 的「清零」不是免费的午餐——它本质上是多了一次 memset。如果你接下来马上要把所有元素赋值一遍,用 malloc 更高效。但如果你需要「某些位置天然就是 0」的语义保证(比如用作标记数组),calloc 更省心。

4.4 realloc:安全扩容的标准写法

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

int main()
{
    // 先分配 5 个 int 的空间
    int *ptr = (int *)malloc(5 * sizeof(int));
    if (ptr == NULL)
    {
        perror("malloc");
        return 1;
    }

    // 初始化
    for (int i = 0; i < 5; i++)
    {
        *(ptr + i) = i + 1;
    }

    // 扩容到 40 个 int
    // 关键:用一个临时指针接收 realloc 的返回值
    int *p = (int *)realloc(ptr, 40 * sizeof(int));

    if (p != NULL)
    {
        // 扩容成功,才把新地址赋给 ptr
        ptr = p;

        // 继续使用扩容后的空间
        for (int i = 0; i < 40; i++)
        {
            *(ptr + i) = i + 1;
        }

        for (int i = 0; i < 40; i++)
        {
            printf("%d ", *(ptr + i));
        }
    }
    else
    {
        // 扩容失败,但原来的内存还在!
        // 不能直接放弃,要先处理原来的内存
        perror("realloc");
        // 注意:即使 realloc 失败,原来的 ptr 仍然有效,必须释放
    }

    free(ptr);
    ptr = NULL;
    return 0;
}

realloc 的一个特殊用法:

// 当第一个参数是 NULL 时,realloc 的行为等同于 malloc
int *p = (int *)realloc(NULL, 20);  // 等价于 malloc(20)

4.5 柔性数组:一次分配,全部搞定

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

struct S
{
    char c;
    int i;
    int arr[];  // 柔性数组:必须放在结构体最后一个成员,不写元素个数
};

int main()
{
    // sizeof(struct S) 不包括 arr 的大小
    // 分配时手动加上数组需要的空间
    struct S *pf = (struct S *)malloc(sizeof(struct S) + 5 * sizeof(int));
    if (pf == NULL)
    {
        perror("malloc");
        return 1;
    }

    // 像普通数组一样使用
    for (int i = 0; i < 5; i++)
    {
        pf->arr[i] = i + 1;
    }

    // 扩容柔性数组
    struct S *ps = (struct S *)realloc(pf, sizeof(struct S) + 20 * sizeof(int));
    if (ps != NULL)
    {
        pf = ps;
        for (int i = 0; i < 20; i++)
        {
            pf->arr[i] = i + 1;
        }
    }

    // 一次 free 就够了!arr 和结构体在同一块内存上
    free(pf);
    pf = NULL;
    return 0;
}

4.6 常见错误示范

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

// 错误 1:对非动态内存调用 free
void error_free_stack()
{
    int a = 10;
    int *p = &a;
    free(p);  // ❌ 未定义行为!a 在栈上,不是 malloc 出来的
    p = NULL;
}

// 错误 2:对动态内存的一部分调用 free
void error_free_middle()
{
    int *p = (int *)malloc(20);
    p++;       // p 指向了第二个元素
    free(p);   // ❌ free 必须传起始地址,否则行为未定义
}

// 错误 3:使用 assert 替代 NULL 检查(仅在 Debug 模式有效)
void error_assert_instead_of_check()
{
    int *p = (int *)malloc(sizeof(int));
    assert(p);  // Release 模式下 assert 会被编译器移除!
                // 生产环境应该用 if(p == NULL) 判断
    *p = 20;
    free(p);
}

// 正确:通过二级指针修改外部指针
void GetMemory(char **p)
{
    *p = (char *)malloc(100);  // 解引用二级指针,修改外部的 str
}

void Test(void)
{
    char *str = NULL;
    GetMemory(&str);  // 传指针的地址,让函数内部能修改 str 本身

    if (str == NULL)  // 记得检查!
    {
        perror("GetMemory");
        return;
    }

    strcpy(str, "hello world");
    printf("%s\n", str);

    free(str);
    str = NULL;
}

5. 注释提炼

知识注解类

「枚举常量通过枚举列出所有的可能情况,枚举是有编号的,从 0 开始」

枚举(enum)是 C 语言提供的一种定义「命名整数常量」的机制。编译器会为枚举中的每个标识符分配一个 int 类型的值,默认从 0 开始递增。你也可以手动指定某个值,后续的标识符会自动 +1:

enum Weekday { MON = 1, TUE, WED, THU, FRI };  // TUE=2, WED=3, ...

枚举的本质是编译时常量,不占用运行时内存。它的主要价值在于提高代码可读性——用 RED 代替 0,用 EXIT 代替 0,让代码自文档化。

「动态内存,向系统申请一块内存,用来存放。在收回时,需要主动释放,将这块内存还给系统,但是这块内存依旧存在」

这段注释包含两个关键信息:

  1. 主动释放:堆内存的生命周期由程序员手动管理。栈上的局部变量在函数返回时自动销毁,但 malloc 出来的内存在 free 之前一直存在,即使指针变量已经出了作用域。忘记 free 就是内存泄漏。

  2. 「这块内存依旧存在」:free 之后,内存被标记为「可用」归还给操作系统,但物理上的数据不会立刻被擦除。这就是为什么 free 后必须把指针置 NULL——指针还存着那个地址,如果误用,可能读到旧数据,也可能读到被其他代码覆盖的新数据,行为完全不可预测。

「calloc 会将所有内容初始化为 0」

calloc 的全称可以理解为 “clear allocation”。它的内部实现大致是:先 malloc,再 memset(ptr, 0, size)。所以初始化 0 是有性能代价的。另外,calloc 对浮点数初始化为 0.0 也是安全的(IEEE 754 中全零位模式就是 0.0),但对指针类型初始化为 NULL 在绝大多数平台上也是安全的,不过 C 标准并不严格保证这一点。

「柔性数组基于结构体,放在最后一个定义,且不写数组元素个数」

这是 C99 引入的 flexible array member。关键限制:

  • 必须是结构体的最后一个成员
  • 不能是唯一成员(结构体至少要有另一个成员)
  • 声明时不写元素个数(int arr[])或写 int arr[0](GCC 扩展)
  • sizeof 不计算柔性数组的大小
  • 必须通过动态内存分配来使用

「assert§」

assert 是一个宏,定义在 <assert.h> 中。当表达式为假时,它会打印错误信息并终止程序。重要: assert 只在 Debug 模式(未定义 NDEBUG)下生效。Release 版本中 assert 会被编译器完全移除,所以它不能替代生产代码中的 if(p == NULL) 检查。一般用于「在开发阶段尽早暴露逻辑错误」,而不是处理运行时可能发生的错误。

个人理解类

*「用 (pf+i) 比 pf++ 更好,不改变 pf 的值,因为等释放内存时需要释放的地址是起始申请空间的地址」

我的理解: 这就像你借了一本书,管理员在借阅登记表上写的是「第 3 排第 5 本」。如果你把书往前挪了一格,还书时管理员按登记表的位置去找,找到的是另一本书——系统就乱了。pf 就是那张登记表,你必须保持它指向原始位置。*(pf+i) 是「去第 3 排,往前数 i 格,找到那本书」,但不改登记表上的信息。

「释放后 pf 指向的内容是未知区域,变成了野指针,危险,所以要赋为空」

我的理解: free 相当于把房子的产权还给银行,但你的钥匙(指针值)还在口袋里。如果不把钥匙销毁(pf = NULL),以后你可能会下意识地掏出这把钥匙去开门——但门后面可能住着别人了。NULL 是一种「空钥匙」,系统约定「空钥匙开不了任何门」,解引用 NULL 至少会确定性地崩溃,比随机行为更容易排查。

踩坑感悟类

⚠️ 踩坑 1:realloc 返回值直接赋给原指针

// ❌ 错误写法
ptr = realloc(ptr, new_size);

如果 realloc 失败返回 NULL,ptr 被覆盖为 NULL,原来的内存地址丢失,造成内存泄漏且无法恢复。正确做法是用临时变量接收,判断成功后再赋值。

⚠️ 踩坑 2:忘记 free 导致内存泄漏

动态分配的内存不会自动回收。每次 malloc 都必须有对应的 free。在一个循环里不断 malloc 而不 free,程序的内存占用会持续增长,最终被系统 OOM Killer 杀掉。

⚠️ 踩坑 3:free 后继续使用指针

free(p);
printf("%d\n", *p);  // ❌ 未定义行为!p 已经是野指针

free 之后,那塊内存可能已经被分配给其他变量,读到的内容可能是任何值,写入更可能破坏其他数据。预防方法:free 后立即 p = NULL,后续使用前检查 if (p != NULL)。

⚠️ 踩坑 4:对非动态内存调用 free

int a = 10;
int *p = &a;
free(p);  // ❌ a 在栈上,不是堆内存!程序大概率崩溃

free 只能释放 malloc / calloc / realloc 返回的指针。栈变量、全局变量、静态变量的地址都不能传给 free。

⚠️ 踩坑 5:free 不传起始地址

int *p = malloc(20);
p++;        // 偏移了
free(p);    // ❌ 不是起始地址,行为未定义

堆管理器内部维护了每块内存的元数据(大小、状态等),这些信息通常存在起始地址之前。如果你传的不是起始地址,管理器会读取错误的内存管理信息,导致崩溃或堆损坏。

对比总结类

概念枚举宏定义
类型检查有(枚举是独立类型)无(纯文本替换)
调试信息调试器能看到符号名预处理后符号消失
作用域受作用域控制全局有效(除非 #undef)
适用场景一组相关的整数常量单个常量或任意代码片段
函数分配初始化释放
mallocmalloc(size)不初始化free
calloccalloc(n, size)初始化为 0free
reallocrealloc(ptr, size)保留旧数据,新空间不初始化free
柔性数组指针方案
一块连续内存,一次 malloc 一次 free两块内存,两次 malloc 两次 free
更好的缓存局部性可能内存不连续,缓存不友好
sizeof 不包含数组大小sizeof 包含指针大小(8 字节)
不能直接赋值(数组名是常量)可以重新赋值指针指向新数组

6. 代码修正

修正 1:原代码中的 printf 格式错误

修改前:

enum Color color1 = RED;
printf("%s\n", color1);  // ❌ 枚举值是 int,用 %s 会导致未定义行为

修改后:

enum Color color1 = RED;
printf("%d\n", color1);  // ✅ 枚举本质是 int,用 %d 打印

原因: 枚举常量在 C 语言中的类型是 int,%s 期望的是一个 char* 指针(字符串地址)。把 int 当作地址去访问字符串,轻则输出乱码,重则段错误。

修正 2:realloc 扩容后缺少原有数据的保护

修改前:

int *p = (int *)realloc(ptr, 4000);
if (p != NULL)
{
    ptr = p;
    // 直接覆盖了所有元素,包括前面 5 个已经赋过值的
    for (int i = 0; i < 40; i++)
    {
        *(ptr + i) = i + 1;
    }
}

虽然这段代码逻辑上没问题(覆盖了全部 40 个元素),但如果是「部分扩容」场景(只初始化新增部分),这样写就有问题了。

修改后(展示更健壮的写法):

int *p = (int *)realloc(ptr, 40 * sizeof(int));
if (p != NULL)
{
    ptr = p;
    // 只初始化新增的元素(第 6~40 个),前 5 个保留原值
    for (int i = 5; i < 40; i++)
    {
        *(ptr + i) = i + 1;
    }
}

说明: realloc 扩容后,旧数据会被保留。如果不需要覆盖全部数据,应该只初始化新增部分,这样既保留了原有数据,又避免了不必要的写入。

修正 3:assert 不应该替代 NULL 检查

修改前:

int *p = (int *)malloc(INT_MAX);
assert(p);  // Release 模式不会执行
*p = 20;

修改后:

int *p = (int *)malloc(INT_MAX);
if (p == NULL)
{
    perror("malloc");
    return 1;
}
*p = 20;

原因: assert 在定义 NDEBUG 宏后(通常是 Release 构建)会被编译器移除,变成空操作。如果 malloc 失败,程序会直接解引用 NULL 指针崩溃,而且没有任何错误提示。正确的做法是始终用 if 判断,perror 打印错误原因。


7. 小结

枚举给编译期常量命名,动态内存给运行时数据安家——一个让代码可读,一个让程序灵活,两条腿走路才能写出真正的 C 程序。


本文基于 C 语言复习笔记整理,代码示例已通过修正和注释增强。

巨正则蒙特卡洛—甲烷和二氧化碳的竞争吸附GCMC 阅读详情

相关推荐

计算机考研复试面试常问问题 数据结构篇(下)

计算机考研复试面试常问问题 数据结构篇(下) 使用前需知(拒绝白嫖,如果对你有帮助,你只需点个赞就行): 注意:有人在闲鱼上盗卖我的资料,而且还有很多同学买了,请各位擦亮双眼,我是整理出来免费分享给大家的! 大家请多点赞和评论,这样后面的同学更容易百度到,不至于被人盗卖,我就不加水印影响大家使用了! 第一次整理,度不好控制,有不好的地方希望大家理解,后面可能会改进,造福今年之后的同学们! ...

程序员宝藏的博客 1万+

C++枚举进阶:Better Enums库实现编译时反射与字符串转换

枚举是编程中用于定义命名常量集合的基础数据类型,在C++中通过enum和enum class提供类型安全支持。其核心原理是将符号常量映射到整数值,实现状态标识和类型限定。然而原生枚举缺乏运行时反射能力,无法直接获取枚举项的名称或进行字符串转换,这限制了其在日志输出、序列化、配置解析等场景的应用价值。通过编译时元编程技术,可以在不引入运行时开销的前提下为枚举赋能,实现类型安全的字符串转换、迭代遍历和序列化支持。Better Enums库正是基于这一思路,利用C++模板和宏在编译期生成包含完整元信息的枚举类,解

weixin_33675507的博客 322

OpenIddict实战指南:在.NET Core中构建安全的OAuth 2.0与OpenID Connect服务

本文详细介绍了如何在.NET Core中使用OpenIddict框架构建安全、标准的OAuth 2.0与OpenID Connect授权服务。内容涵盖从快速搭建授权服务器、理解核心配置与授权流程,到集成用户数据库、实现完整登录授权,以及生产环境下的安全加固与部署指南,为.NET开发者提供了一套现代、可维护的认证授权解决方案。

weixin_29229989的博客 355

C语言数组长度定义:从编译时常量到变长数组的演进与实践

在C语言中,数组是一种基础数据结构,其长度定义规则直接关系到内存分配机制。传统C语言要求数组长度必须是编译时常量,这源于编译器需要在编译阶段确定内存布局,以便在栈或数据区分配固定空间。随着C99标准的引入,变长数组(VLA)允许使用运行时常量指定数组长度,将动态内存分配的部分工作以更简洁的语法转移到栈上,提升了代码的灵活性。然而,VLA也带来了栈溢出风险和编译器兼容性问题,尤其在处理不可信输入或跨平台项目时需谨慎使用。在实际工程中,开发者需根据场景在编译时常量数组、变长数组和动态内存分配之间做出权衡,例如对

weixin_34357436的博客 392

MD01和MD01N比较

摘要:MD01(传统MRP)与MD01N(MRP Live)是SAP系统不同版本的物料需求计划事务码。核心差异包括:MD01N取消MRP列表、简化计划规则、合并MPS/MRP操作、采用BAdI技术等,体现S/4HANA平台简化、标准化和智能化的演进方向。MD01N通过优化参数配置、增强筛选功能、利用HANA实时计算能力,实现了更高效的物料计划流程,代表了新一代计划引擎的技术革新。

SAP_LES_COM的博客 891

浅谈声发射振动复合传感器

浅谈声发射振动复合传感器 随着全球经济和自动化工业的迅速发展,人们对设备故障特别是轴承类的故障诊断也日益重视起来了。 为满足客户对旋转仪器轴承异常的精准诊断,单一的依靠振动信号或声信号分析,是很难兼顾轴承的多种异常情况的。 为此,市面上开始着手研发出了可同时分析机械设备的振动信号和声信号的复合型传感器。 初期的复合型传感器主要有 2 种类型,下面以 A、B 来举例说明。 A 型是在被测物表面不同的 2 个位置分别放置声发射(AE)传感器和加速度(ACC)传感器,它虽能一定程度...

pxtech的博客 1152

学习笔记:枚举类型和动态内存分配

枚举顾名思义就是⼀⼀列举图片中展示的enum为枚举类型的关键字,而enum color为自定义的类型名关于枚举类型定义需要注意的几个点:1.枚举中定义的常量不能被更改;2.取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值注意:赋值在C语⾔中是可以的,但是在C++是不⾏的,C++的类型检查⽐较严格;赋予图片理解枚举类型的优点为什么使⽤枚举?我们可以使⽤ #define 定义常量,为什么⾮要使⽤枚举?枚举的优点:1. 增加代码的可读性和可维护性;

SEVEN_WIND的博客 936

今日头条面试——iOS开发面试题

layoutIfNeeded和setNeedsLayout的区别。* Pod update和pod install的区别。* 10、autoreleasepool的使用场景。* 如何hook一个对象的方法,而不影响其它对象。* 数组cop后里面的元素会复制一份新的吗。* 介绍项目,主要介绍自己强项一点的地方。* assing可以使用在对象中吗。* isEquel和hash的关系。* 4、谈谈runtime的理解。* 8、谈谈runloop的理解。* copy和strong的区别。

FPGA/MATLAB学习教程/源码/项目合作开发 594

C复习笔记-联合体/枚举/动态内存管理/文件操作

联合体和结构体一样,联合体也是由一个或者多个成员构成,这些成员可以是不同的类型。但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用一块内存空间。所以联合体也叫做共用体。联合体给一个成员赋值,其他成员的值也跟着变化。联合体的大小是最大值的大小。

weixin_47533318的博客 534

[Bug已解决] CUDA error 尽管 CUDA 可用且 GPU 受支持-通用排查 RuntimeError 解决方案

[Bug已解决] CUDA error 尽管 CUDA 可用且 GPU 受支持-通用排查 RuntimeError 解决方案

一键难忘的博客 2415

Unlocking Decoding-time Controllability: Gradient-FreeMulti-Objective Alignment with Contrastive Pr

将基于庞大网络语料库训练的大型语言模型(LLMs)(OpenAI,2023;Touvron 等,2023;Google,2023)与人类偏好对齐,是减少产生不安全(Wei 等,2023)、幻觉(Zhang 等,2023b)和有偏见(Gallegos 等,2023)内容的重要一步。随着偏好学习技术(如 PPO(Schulman 等,2017)、DPO(Rafailov 等,2023)及其他变体(Azar 等,2023;Ethayarajh 等,2024;

qq_52053775的博客 1169

C 语言进阶:结构体、枚举、联合体与动态内存管理全解析

本文系统梳理了C语言中复合自定义数据类型与动态内存管理的核心知识点。首先介绍了结构体的基础定义、成员访问及内存对齐规则,重点讲解了嵌套结构体和结构体数组的应用。其次,深入分析了枚举类型的类型安全优势、联合体的内存共享特性及其大小端检测应用。最后详细阐述了动态内存管理的四大函数(malloc/calloc/realloc/free)的使用规范及常见错误防范,并提供了动态可变长数组的完整实现示例。全文兼顾底层原理与工程实践,涵盖结构体传参优化、位段压缩等进阶技巧,是巩固C语言中高级特性的实用指南。

New_StarF的博客 347

完整复盘【避雷jd文职某岗位】潍坊青州hhj(据说未来搬到天津),助理实验师岗位,三人进面,面试全部不及格,空头岗位!像这种情况,该单位很多 | 附个人完整备考背诵题目 | 世间一场梦,不会再考这个了

本文以博主本人报考jun dui文职,从报考、备考、考试、查成绩到资格审查、面试,全程亲身经历,给想要报考该内容的朋友们一些小的参考。祝你顺利上岸!完整复盘【避雷jd文职某岗位】潍坊青州hhj(据说未来搬到天津),助理实验师岗位,三人进面,面试全部不及格,空头岗位!像这种情况,该单位很多 | 附个人完整备考背诵题目 | 世间一场梦,不会再考这个了

追光者♂:记录、分享、总结、提升,现象级专栏《Python从入门到人工智能》作者,无惧黑暗,坚信曙光 247

ETL高级模板技术:类型安全枚举与变体的实现原理

**Embedded Template Library(ETL)** 是一个专为嵌入式系统设计的C++模板库,它为资源受限环境提供了类型安全的容器和工具。在这篇面向新手和普通用户的完整指南中,我们将深入探讨ETL中两个核心高级模板技术:**类型安全枚举**和**变体类型**的实现原理,帮助您理解如何在嵌入式开发中实现更安全、更高效的代码设计。 ## ETL嵌入式模板库的核心价值 ETL库的设计

gitblog_00440的博客 923

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

【C语言】动态内存分配

本篇文章会详细给大家介绍C语言中关于动态内存分配的知识,超级详细,相信你看完会有新的收获,那就来狠狠回顾100万遍把~

Become Mad的博客 2551

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

【C语言】动态内存管理 [进阶篇_ 复习专用]

✨综上就是开辟动态内存函数啦~🍞三.✨综上就是柔性数组的内容啦~➡️相信大家对动态内存开辟有不一样的看法了吧🧡综上,我们基本了解了C语言中的“动态内存管理”🍭的知识啦~~恭喜你的内功又双叒叕得到了提高!!!后续还会继续更新💓,欢迎持续关注📌哟~💫如果有错误❌,欢迎指正呀💫httpshttpshttpshttpshttpshttpshttpshttpshttpshttpshttpshttpshttps。...

Dream_Y.ocean的博客 3665

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

万字解析动态内存从编译到运行

枚举把散落的魔法数字变成有名字的常量,而动态内存让你在程序运行时按需索取堆空间——两者一个管编译时,一个管运行时,都是写出灵活 C 程序的基石。概念枚举宏定义类型检查有(枚举是独立类型)无(纯文本替换)调试信息调试器能看到符号名预处理后符号消失作用域受作用域控制全局有效(除非#undef适用场景一组相关的整数常量单个常量或任意代码片段函数分配初始化释放malloc不初始化freecalloc初始化为 0freerealloc保留旧数据,新空间不初始化free。

DDXYcoder的博客 196

【C语言】动态内存管理

在上一章节讲解了自定义类型:联合体和枚举的使用,和注意事项。在本章节为大家带来动态内存管理的文章,希望有所帮助。在C语言中,动态内存管理是一项核心技能,它允许程序员在程序运行时根据需要分配和释放内存。与静态内存分配相比,动态内存管理提供了更大的灵活性,能够应对不确定或变化的数据大小。然而,动态内存管理也伴随着一些风险,如内存泄漏、野指针和悬挂指针等问题。本文将从动态内存管理的基本概念、常用函数、常见错误及其解决方法等方面进行详细讲解。本章节介绍了为什么需要动态内存管理?和动态内存管理的函数.关键要点。

2501_90303633的博客 800

C++类与动态内存分配

C++类与动态内存分配

Jasmine-Lily的博客 1108

【vc6学习笔记】error C2057: expected constant expression

在Visual C++ 6.0(VC6)中,错误 error C2057: expected constant expression 表示编译器期望一个常量表达式,但实际得到的是一个非常量或变量表达式。这个错误常见于以下几种情况:

ffffffeiyu的博客 1161

【动态内存管理】深入详解:malloc和free、calloc和realloc、常见的动态内存的错误、柔性数组、总结C/C++中程序内存区域划分

1.栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内 存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。2.堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统) 回收。分配方式类似于链表。3.数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放。4.

艾莉丝努力练剑的博客,感谢大家一直以来的对艾莉丝的支持和鼓励,uu们如果对C/C++方向感兴趣的话,可以通过艾莉丝主页的微信名片加艾莉丝的好友,和努力练剑的艾莉丝一起加油! 1904

【跟学C++】C++变量与常量(Study3)

跟学C++】C++变量与常量(Study3)

cqy阳的博客 288

C语言动态内存管理全解析

本文介绍了C语言动态内存管理的关键知识点。首先解释了为什么需要动态内存分配,以解决程序运行时空间需求不确定的问题。然后详细讲解了malloc、calloc和realloc三个核心函数的使用方法及注意事项:malloc返回void*指针需强制类型转换;calloc会自动初始化内存为0;realloc调整内存大小时需谨慎处理指针。文章还总结了常见错误,如空指针解引用、内存越界、重复释放和内存泄漏,并提供了解决方法,强调malloc和free必须成对使用。最后通过图书馆借书的比喻形象说明了内存泄漏的危害,提醒程序

xuedingbue的博客 1371

【动态内存管理】

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!像回顾上一节博客的,请点击这里 自定义类型:联合和枚举提示:以下是本篇文章正文内容,下面案例可供参考C/C++程序内存分配的几个区域:1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。

2301_79585944的博客 616

2015.12.14 宏定义 枚举 动态内存分配

宏定义 (#define)   宏定义的意义和用途:(习惯上,都是“k”开头)             1.为了让一些数据有意义。                          2.类似于内联函数(使用简便)。                          3.输出日志的开关。             (非零即真,0为“发布阶段”,1为“开发阶段”) 枚举 (enum)...

weixin_30901729的博客 192

C/C++编译错误E0028:常量表达式原理与实战解决方案

常量表达式是C/C++编程中确保编译时确定性的核心概念,它要求表达式的值在编译阶段即可计算完成,这是实现静态内存分配、模板实例化等高级特性的基础。其原理源于语言的静态类型系统,通过在编译期固定内存布局和跳转地址,为程序带来更高的执行效率和可预测性。在工程实践中,理解常量表达式对于避免E0028这类编译错误至关重要,尤其是在涉及数组定义、case标签和静态初始化等场景。现代C++通过引入constexpr关键字,进一步明确了编译时常量的语义,而std::vector等容器则为动态大小数据提供了更安全的替代方案

weixin_30480075的博客 326
上一篇: qsort 与通用排序:从「只能排 int」到「什么都能排」的进化之路
下一篇: 浮点数与自定义类型:从 IEEE 754 到位段联合体
DDXYcoder
博客等级 码龄1年 140粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值