当前位置: 首页 > news >正文

【动态内存管理助力程序优化与性能飞升】

本章重点

为什么存在动态内存分配

动态内存函数的介绍

  • malloc
  • free
  • calloc
  • realloc

常见的动态内存错误

几个经典的笔试题

柔性数组

1. 为什么存在动态内存分配

我们已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有两个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了,空间大小不容易设计合理。 这时候就只能试试动态存开辟了。

2. 动态内存函数的介绍

2.1 malloc和free

C语言提供了一个动态内存开辟的函数:

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己 来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

malloc申请的内存空间,当程序退出时,还给操作系统,如果不退出,动态申请的内存不会主动释放,因此C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 stdlib.h 头文件中。

#include <stdio.h>
int main()
{//代码1int num = 0;scanf("%d", &num);int arr[num] = { 0 };//c90不支持这种写法,error//为了程序运行过程中开辟更合理的空间,需要动态开辟内存//代码2int* ptr = NULL;ptr = (int*)malloc(num * sizeof(int));if (NULL == ptr)//判断ptr指针是否为空{perrof("malloc");return 1;}int i = 0;for (i = 0; i < num; i++){*(ptr + i) = 0;printf("%d ", *(ptr + i));}free(ptr);//释放ptr所指向的动态内存ptr = NULL;//是否有必要?return 0;
}

运行结果:

结论:malloc申请空间后直接返回这块空间的起始位置,不会初始化空间 

free释放ptr所指向的动态内存,ptr = NULL是否有必要呢?

2.2 calloc

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);
  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL == p)//判断p指针是否为空{perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;//p置为空指针return 0;
}

运行结果:

所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

2.3 realloc

  • realloc函数的出现让动态内存管理更加灵活。
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。
void* realloc (void* ptr, size_t size);
  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。 realloc在调整内存空间的是存在两种情况:
  1. 情况1:原有空间之后有足够大的空间
  2. 情况2:原有空间之后没有足够大的空间

情况1

        当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。返回的是原来旧的内存地址。

情况2

        当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另开辟一个合适大小的连续空间来使用,把原来的数据拷贝到这个新的空间,再把旧的空间释放。这样函数返回的是一个新的内存地址。

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = (int*)malloc(10);if (ptr != NULL){//业务处理}else{perror("malloc");return 1;}//扩展容量//代码1ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?) - 内存泄露//代码2int* p = (int*)realloc(ptr, 1000);if (p != NULL){ptr = p;//赋值之后,realloc自己将ptr释放}else{perror("realloc");return 1;}//业务处理free(ptr);ptr = NULL;return 0;
}

情况1:

情况2:

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

void test()
{int* p = (int*)malloc(INT_MAX / 4);//malloc函数开辟失败就会返回NULL*p = 20;//如果p的值是NULL,就会有问题free(p);
}

3.2 对动态开辟空间的越界访问

void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);
}

3.3 对非动态开辟内存使用free释放

void test()
{int a = 10;int* p = &a;free(p);//ok?
}

3.4 使用free释放一块动态开辟内存的一部分

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}

3.5 对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);free(p);//重复释放
}

3.6 动态开辟内存忘记释放(内存泄漏)

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏。

切记:动态开辟的空间一定要释放,并且正确释放 。

4. 几个经典的笔试题

demo1:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}
int main()
{Test();return 0;
}

问题:对NULL解引用以及没有释放malloc申请的空间

 解释:

  1. GetMemory 函数: 这个函数接受一个字符指针(char*)作为参数,并尝试使用 malloc 来分配 100 字节的内存。然而,需要理解的是,在C语言中,函数参数是通过值传递的,这意味着 GetMemory 函数内部的 p 是从 Test 函数传递过来的指针的一个拷贝。对于拷贝的指针所做的更改不会影响 Test 函数中的原始指针。

  2. Test 函数: 在 Test 函数中,声明并初始化了一个 char* 变量 str,并将其设置为 NULL。然后,调用 GetMemory 函数,并将 str 作为参数传递进去。由于参数是通过值传递的,GetMemory 函数只会修改它自己的指针拷贝,并不会改变 Test 函数中的原始 str 指针。

  3. 内存分配问题: 在 GetMemory 函数内部,内存被分配给局部指针 p,这是从 Test 函数的 str 指针拷贝过来的。这意味着在 Test 函数中,原始的 str 指针仍然是 NULL,并没有被赋予新分配的内存地址。

  4. 缓冲区溢出: 在调用 GetMemory 函数后,有一个 strcpy 函数调用,试图将字符串 "hello world" 复制到 str 指针中。然而,由于 str 仍然是 NULL(没有指向已分配的内存),这将导致未定义行为,并可能导致段错误或其他错误,因为访问了无效的内存。

修改:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>void GetMemory(char** p)
{*p = (char*)malloc(100); // 分配 100 字节内存,并将地址存储在原始指针中
}void Test(void)
{char* str = NULL;GetMemory(&str); // 传递指向指针的指针(双重指针),以修改原始指针strcpy(str, "hello world");printf("%s", str);free(str); // 使用完内存后别忘了释放它str == NULL;
}int main()
{Test();return 0;
}

demo2:

#include<stdio.h>
char* GetMemory(void)
{char p[] = "hello world";return p;//返回局部变量的地址
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}

问题:返回局部变量的地址

解释:

  1. GetMemory 函数: 这个函数声明了一个字符数组 p 并初始化为 "hello world"。然后它试图返回 p 的地址。但是,需要注意的是,p 是一个局部变量,它在函数结束时会被销毁。因此,将局部变量的地址返回给调用者是不安全的,因为在调用者函数中访问返回的地址将指向无效的内存区域。

  2. Test 函数: 在 Test 函数中,声明了一个字符指针 str 并将其初始化为 NULL。然后,调用 GetMemory 函数,将返回的地址赋值给 str

  3. 错误的返回局部变量地址: 在 GetMemory 函数中,由于返回局部变量 p 的地址,str 指针现在指向了一个不再有效的内存地址,因为 pGetMemory 函数返回后已经被销毁。

  4. printf 函数: 在 printf 中尝试打印 str 指向的字符串时,由于 str 指向无效内存地址,代码的行为将是未定义的。这可能导致程序崩溃、输出奇怪的字符或其他不确定的结果。

修改:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>char* GetMemory(void)
{char* p = (char*)malloc(12); // 在堆上分配内存以容纳 "hello world" 和空结束符strcpy(p, "hello world"); // 将 "hello world" 复制到新分配的内存块中return p; // 返回指向分配内存的指针
}void Test(void)
{char* str = NULL;str = GetMemory();printf("%s", str);free(str); // 使用完内存后别忘了释放它
}int main()
{Test();return 0;
}

demo3:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}

问题:malloc申请的空间没有释放

修改:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);free(str);str = NULL;
}
int main()
{Test();return 0;
}

demo4:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}

问题:

        在这段C代码中,首先使用 malloc 动态地分配了 100 字节的内存来存储字符串 "hello"。然后,立即使用 strcpy 将 "hello" 复制到分配的内存块中。接着,使用 free 释放了分配的内存。

        然后,代码尝试检查指针 str 是否为 NULL。然而,这是一个错误的做法。因为在调用 free 之后,指针 str 指向内存地址虽然不会发生改变,但是进行指针进行任何操作都是不安全的,并且会导致未定义的行为。

修改:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str); // 释放内存后,str 成为了悬挂指针str = NULL;// 不要在释放内存后使用指针// 这里不再使用 str 指针
}int main()
{Test();return 0;
}

5. C/C++程序的内存开辟

C/C++程序内存分配的几个区域: 

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

有了这幅图,我们就可以更好的理解在《C语言初识》中讲的static关键字修饰局部变量的例子了。

        实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁 所以生命周期变长。

6. 柔性数组

        也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

typedef struct st_type
{int i;int a[];//柔性数组成员//int a[0];//也可以写成这个
}type_a;

6.1 柔性数组的特点:

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。

  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
type_a* ps = (type_a*)malloc(sizeof(type_a) + 40);

6.2 柔性数组的使用

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
int main()
{type_a* ps = (type_a*)malloc(sizeof(type_a) + 40);if (!ps){perror("malloc");return 1;}ps->i = 10;int i = 0;for (i = 0; i < ps->i; i++){ps->a[i] = i;}//空间不够,realloc增容/*ps 是要调整的内存地址size 调整之后新大小返回值为调整之后的内存起始位置。*/type_a* p = (type_a*)realloc(ps, sizeof(type_a) + 60);if (!p){perror("realloc");return 1;}ps = p;ps->i = 15;for (i = 0; i < ps->i; i++){printf("%d ", ps->a[i]);}free(ps);ps = NULL;return 0;
}

运行结果:

6.3 柔性数组的优势

上述的 type_a 结构也可以设计为指针类型:

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int* a;
}type_a;
int main()
{type_a* ps = (type_a*)malloc(sizeof(type_a));//与柔性数组保持一致if (!ps){perror("malloc");return 1;}ps->i = 10;ps->a = (int*)malloc(40);if (!ps->a){perror("malloc");return 1;}int i = 0;for (i = 0; i < ps->i; i++){ps->a[i] = i;}//空间不够,realloc增容/*ps 是要调整的内存地址size 调整之后新大小返回值为调整之后的内存起始位置。*/int* p = (int*)realloc(ps->a, 60);if (!p){perror("realloc");return 1;}ps->a = p;ps->i = 15;for (i = 0; i < ps->i; i++){printf("%d ", ps->a[i]);}free(ps->a);ps->a = NULL;free(ps);ps = NULL;return 0;
}

上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:

第一个好处是:方便内存释放

        如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

第二个好处是:这样有利于访问速度.

        连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

扩展阅读:C语言结构体里的成员数组和指针

相关文章:

【动态内存管理助力程序优化与性能飞升】

本章重点 为什么存在动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的动态内存错误 几个经典的笔试题 柔性数组 1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈…...

电动汽车设计、制造、研发的学科、技术和前沿科技综述

引言&#xff1a;电动汽车作为替代传统燃油汽车的一种先进交通工具&#xff0c;不仅具有环保、低噪音等优势&#xff0c;而且对于能源消耗和气候变化等全球性问题也具有重要意义。本文将综述与电动汽车设计、制造、研发相关的学科、技术和前沿科技&#xff0c;以期对电动汽车领…...

NsPack3.x脱壳手记

发现是NsPack3.x的壳 使用ESP守恒快速脱壳 F9遇到popfd后下面的jmp就是通往OEP了 打开LordPE准备转储映像, 首先调整下ImageSize, 接着dump full 接着不要退出目前的调试, 打开Scylla修复IAT, 把OEP的VA地址输入到OEP处, 接着按照如下图所示步骤 完成后如下, 但NsPack3.x…...

在.net 6.0中 调用远程服务器web服务,Webservices(xxx.asmx) ,RESTful 风格,2种解决方案。

1.使用 Connected Services&#xff1a; 右键单击您的项目&#xff0c;选择 "Add"&#xff08;添加&#xff09;-> "Connected Services"&#xff08;已连接的服务&#xff09;。 在 "Connected Services" 对话框中&#xff0c;选择 "W…...

深度学习基础01-深度学习简介

什么是深度学习&#xff1f; 深度学习是机器学习的一个分支&#xff0c;是一种基于人工神经网络的学习方法。它是一种模仿人脑神经元之间信息传递和学习的过程的机器学习算法。深度学习的核心思想是通过构建多层神经网络来学习从输入到输出的复杂映射关系&#xff0c;从而实现各…...

Flink DataStream API详解

DataStream API 参考&#xff1a;https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/datastream_api.html Data Sources Source是程序读取其输入的位置&#xff0c;您可以使用env.addSource&#xff08;sourceFunction&#xff09;将Source附加到程序中。Fl…...

【如何使用cv::erode()函数对图像进行腐蚀操作】

文章目录 `cv::erode()`函数主要用途`cv::erode()`函数的参数卷积核cv::erode()函数主要用途 cv::erode()函数主要用于进行图像的腐蚀操作。这是一种图像处理技术,通常用于消除图像中的噪声、分离两个连接在一起的物体、或者使物体的边界变得更加明显。 腐蚀操作的基本思想是…...

C++数据结构之BST(二叉搜索树)的实现

目录 BST 的方法摘要查找节点四个引用&#xff0c;都有妙用递归版非递归版 插入节点利用search的返回值更新高度的注意事项插入算法的完整代码 删除节点框架单分支&#xff0c;直接替代双分支&#xff0c;化繁为简代码 code BST 预告&#xff1a;本文是后续实现各种各样平衡二叉…...

QT以管理员身份运行

以下配置后&#xff0c;QT在QT Creator调试时&#xff0c;或者生成的.exe程序&#xff0c;都将会默认以管理员身份运行。 一、MSVC编译器 1、在Pro文件中添加以下代码&#xff1a; QMAKE_LFLAGS /MANIFESTUAC:\"level\requireAdministrator\ uiAccess\false\\" …...

java中的缓冲流

Java.io.BufferedOutputStream 字节缓冲输出流&#xff0c;继承自OutputStream 构造方法&#xff1a; BufferedOutputStream (OutputStream out) 创建一个新的缓冲输出流&#xff0c;将数据写入指定的底层输出流BufferedOutputStream (OutputStream out, int size) 创建一个新…...

【小吉带你学Git】idea操作(1)_配置环境并进行基本操作

&#x1f38a;专栏【Git】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f354;环境准备⭐配置Git忽略文件&#x1f384;方法&#x1f33a;创…...

DP-GAN-生成器代码

首先看一下数据生成&#xff1a; 在预处理阶段会将label经过ont-hot编码转换为35个通道&#xff0c;即每个通道都是由&#xff08;0,1&#xff09;组成。 在train文件中&#xff0c;对生成器和判别器分别进行更新&#xff0c;根据loss的不同&#xff0c;分别计算对于的损失&a…...

2020-2023中国高等级自动驾驶产业发展趋势研究

1.1 概念界定 2020-2023中国高等级自动驾驶产业发展趋势研究Trends in China High-level Autonomous Driving from 2020 to 2023自动驾驶发展过程中&#xff0c;中国出现了诸多专注于研发L3级以上自动驾驶的公司&#xff0c;其在业界地位也越来越重要。本报告围绕“高等级自动…...

JDK19 - synchronized关键字导致的虚拟线程PINNED

JDK19 - synchronized关键字导致的虚拟线程PINNED 前言一. PINNED是什么意思1.1 synchronized 绑定测试1.2 synchronized 关键字的替代 二. -Djdk.tracePinnedThreads的作用和坑2.1 死锁案例测试2.2 发生原因的推测2.3 总结 前言 在 虚拟线程详解 这篇文章里面&#xff0c;我们…...

用msys2安装verilator并用spinal进行仿真

一 参考 SpinalHDL 开发环境搭建一步到位(图文版) - 极术社区 - 连接开发者与智能计算生态 (aijishu.com)https://aijishu.com/a/1060000000255643Setup and installation of Verilator — SpinalHDL documentation...

【ARM64 常见汇编指令学习 13 -- ARM 汇编 ORG 伪指令学习】

文章目录 ARM ORG 指令介绍UEFI 中对 ORG 指令的使用 ARM ORG 指令介绍 在ARM汇编中&#xff0c;"org"是一个汇编器伪指令&#xff0c;用于设置下一条指令的装入地址。"org"后面跟着的是一个表达式&#xff0c;这个表达式的值就是下一条指令的装入地址。如…...

Vue使用QuillEditor富文本编辑器问题记录

1.内容绑定的问题 绑定内容要使用 v-model:content"xxx" 的形式。 2.设置字体字号 字体以及字号大小的设置需要先注册。 <script> import { QuillEditor,Quill } from vueup/vue-quill import vueup/vue-quill/dist/vue-quill.snow.css; // 设置字体大小 c…...

spring AOP学习

概念 面向切面编程横向扩展动态代理 相关术语 动态代理 spring在运行期&#xff0c;生成动态代理对象&#xff0c;不需要特殊的编译器 Spring AOP的底层就是通过JDK动态代理或者CGLIb动态代理技术为目标Bean执行横向织入 目标对象实现了接口&#xff0c;spring使用JDK的ja…...

16.M端事件和JS插件

16.1移动端 移动端也有自己独特的地方 ●触屏事件touch (也称触摸事件)&#xff0c;Android 和I0S都有。 ●touch对象代表一个触摸点。触摸点可能是一根手指&#xff0c;也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。 ●常见的触屏事件如下: …...

Zebec APP:构建全面、广泛的流支付应用体系

目前&#xff0c;流支付协议 Zebec Protocol 基本明确了生态的整体轮廓&#xff0c;它包括由其社区推动的模块化 Layer3 构架的公链 Nautilus Chain、流支付应用 Zebec APP 以及 流支付薪酬工具 Zebec payroll 。其中&#xff0c;Zebec APP 是原有 Zebec Protocol 的主要部分&a…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...