ffmpeg源码分析(六)内存管理

news2024/11/25 11:35:00

系列文章目录

FFmpeg源码解析系列(一)目录和编译
FFmpeg源码解析系列(二)主要结构体
ffmpeg源码解析系列(四)结构体之AVIOContext 、URLContext、URLProtocol
ffmpeg源码解析系列(五)结构体之AVCodecContext

这是毕业一年跳槽时被面过的一道题,他问ffmpeg的内存管理是怎样的。当时支支吾吾没有回答出来。
如果再让我回到过去,我会和他说莫欺少年穷~

一、ffmpeg 内存管理相关api

1.1. av_mallocav_free

FFmpeg 提供了自定义的内存分配函数 av_malloc 和释放函数 av_free,它们封装了系统的内存分配接口,并提供了一些额外的内存检查功能。

  • av_malloc(size_t size): 用于分配指定大小的内存块。如果分配失败,会返回 NULL

  • av_free(void *ptr): 释放由 av_malloc 分配的内存块。

使用示例,

void *buffer = av_malloc(1024);
if (!buffer) {
    fprintf(stderr, "Memory allocation failed\n");
    return;
}
// 使用 buffer
av_free(buffer);

1.2. AVBufferAVBufferRef

AVBuffer 是 FFmpeg 中引用计数的核心结构,用于管理数据缓冲区的生命周期。AVBufferRefAVBuffer 的引用,使用引用计数机制来确保内存被正确释放。

主要API函数:

  • av_buffer_alloc(size_t size): 分配一个指定大小的缓冲区。

  • AVBufferRef *av_buffer_create(uint8_t *data, size_t size, void (*free)(void *opaque, uint8_t *data),void *opaque, int flags); 已申请的数据使用AVBufferRef来管理

  • av_buffer_ref(AVBufferRef *buf): 增加缓冲区的引用计数。

  • av_buffer_unref(AVBufferRef buf): 释放一个缓冲区的引用,当引用计数为0时,释放缓冲区。

以下是一个简单例子

#include <libavutil/buffer.h>
#include <stdio.h>

// 自定义释放函数
void custom_free(void *opaque, uint8_t *data) {
    printf("Freeing buffer: %p\n", data);
    // 如果有其他的释放逻辑,比如释放 opaque 指针,需要在这里进行
    av_free(data);
}

int main() {
    // 1. 使用 av_buffer_alloc 分配一个指定大小的缓冲区
    size_t size = 1024;  // 缓冲区大小
    AVBufferRef *buf1 = av_buffer_alloc(size);
    if (!buf1) {
        fprintf(stderr, "Failed to allocate buffer\n");
        return -1;
    }
    printf("Buffer allocated with size: %zu\n", size);

    // 2. 使用 av_buffer_create 管理已申请的缓冲区
    uint8_t *data = av_malloc(size);  // 手动分配数据
    if (!data) {
        fprintf(stderr, "Failed to manually allocate data\n");
        av_buffer_unref(&buf1);
        return -1;
    }
    
    // 使用自定义释放函数来管理 data
    AVBufferRef *buf2 = av_buffer_create(data, size, custom_free, NULL, 0);
    if (!buf2) {
        fprintf(stderr, "Failed to create buffer with custom free\n");
        av_free(data);  // 如果失败需要手动释放 data
        av_buffer_unref(&buf1);
        return -1;
    }
    printf("Buffer created with custom free function\n");

    // 3. 使用 av_buffer_ref 增加缓冲区的引用计数
    AVBufferRef *buf_ref = av_buffer_ref(buf2);
    if (!buf_ref) {
        fprintf(stderr, "Failed to reference buffer\n");
        av_buffer_unref(&buf2);
        av_buffer_unref(&buf1);
        return -1;
    }
    printf("Buffer reference count increased\n");

    // 4. 释放缓冲区的引用,引用计数减到 0 时,缓冲区被释放
    av_buffer_unref(&buf_ref);
    printf("Buffer reference count decreased\n");

    // 释放原始缓冲区
    av_buffer_unref(&buf2);
    av_buffer_unref(&buf1);
    printf("Buffers released\n");

    return 0;
}

1.3 AVPacket 和 AVFrame

AVPacketAVFrame 是 FFmpeg 中用于处理音视频数据的核心结构。AVPacket 通常用于存储编码的数据包(例如压缩的音视频数据),而 AVFrame 用于存储解码后的原始音视频数据。

下面是一些常用的内存管理相关 API 及其示例,包括如何分配、引用计数管理和释放 AVPacketAVFrame

  • av_packet_allocav_frame_alloc:用于分配 AVPacketAVFrame 结构。

  • av_new_packet:为 AVPacket 分配指定大小的数据缓冲区。

  • av_frame_get_buffer:为 AVFrame 分配缓冲区,用于存储图像或音频数据。

  • av_packet_refav_frame_ref:增加引用计数,使多个指针可以安全地共享相同的数据。

  • av_packet_unrefav_frame_unref:减少引用计数,当引用计数为 0 时,释放数据缓冲区。

  • av_packet_freeav_frame_free:释放 AVPacketAVFrame 结构本身。

以下是AVPacket的一个简单例子

#include <libavcodec/avcodec.h>
#include <stdio.h>

int main() {
    // 1. 分配一个 AVPacket
    AVPacket *pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "Failed to allocate AVPacket\n");
        return -1;
    }
    printf("AVPacket allocated\n");

    // 2. 为 AVPacket 分配数据
    int size = 1024;
    if (av_new_packet(pkt, size) < 0) {
        fprintf(stderr, "Failed to allocate data for AVPacket\n");
        av_packet_free(&pkt);
        return -1;
    }
    printf("AVPacket data allocated with size: %d\n", size);

    // 模拟填充数据
    for (int i = 0; i < size; i++) {
        pkt->data[i] = i % 256;
    }

    // 3. 增加引用计数,pkt_ref 和pkt共享一份数据
    AVPacket *pkt_ref = av_packet_alloc();
    if (!pkt_ref || av_packet_ref(pkt_ref, pkt) < 0) {
        fprintf(stderr, "Failed to reference AVPacket\n");
        av_packet_free(&pkt);
        av_packet_free(&pkt_ref);
        return -1;
    }
    printf("AVPacket reference count increased\n");

    // 4. 释放引用
    av_packet_unref(pkt_ref);
    printf("AVPacket reference count decreased\n");

    // 释放原始数据
    av_packet_unref(pkt);
    printf("AVPacket released\n");

    // 释放 AVPacket 结构本身,
    av_packet_free(&pkt);
    av_packet_free(&pkt_ref);
    printf("AVPacket structures freed\n");

    return 0;
}

以下是AVFrame的一个简单例子

#include <libavutil/frame.h>
#include <stdio.h>

int main() {
    // 1. 分配一个 AVFrame
    AVFrame *frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Failed to allocate AVFrame\n");
        return -1;
    }
    printf("AVFrame allocated\n");

    // 假设要处理 YUV420P 格式的图像
    frame->format = AV_PIX_FMT_YUV420P;
    frame->width = 640;
    frame->height = 480;

    // 2. 为 AVFrame 分配缓冲区
    if (av_frame_get_buffer(frame, 32) < 0) {  // 32 表示对齐要求
        fprintf(stderr, "Failed to allocate buffer for AVFrame\n");
        av_frame_free(&frame);
        return -1;
    }
    printf("AVFrame buffer allocated\n");

    // 3. 增加引用计数
    AVFrame *frame_ref = av_frame_alloc();
    if (!frame_ref || av_frame_ref(frame_ref, frame) < 0) {
        fprintf(stderr, "Failed to reference AVFrame\n");
        av_frame_free(&frame);
        av_frame_free(&frame_ref);
        return -1;
    }
    printf("AVFrame reference count increased\n");

    // 4. 释放引用
    av_frame_unref(frame_ref);
    printf("AVFrame reference count decreased\n");

    // 释放原始帧
    av_frame_unref(frame);
    printf("AVFrame released\n");

    // 释放 AVFrame 结构
    av_frame_free(&frame);
    av_frame_free(&frame_ref);
    printf("AVFrame structures freed\n");

    return 0;
}

二、av_mallocav_free

2.1 头文件

libavutil/mem.h

2.2 av_malloc

2.2.1 功能

av_malloc 分配指定大小的内存,并根据需要对齐内存。它首先检查分配的大小是否超过了最大允许的分配大小(max_alloc_size),然后根据平台可用的内存分配方法选择适当的对齐策略。

2.2.2 源码解析
void *av_malloc(size_t size)
{
    void *ptr = NULL;
    //检查请求的内存大小 size 是否超过了最大允许分配大小(max_alloc_size)。使用了 C11 标准库中的 atomic_load_explicit 函数,以确保读取是线程安全的。如果 size 超过 max_alloc_size,则函数返回 NULL。
    if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed))
        return NULL;
    //根据不同平台选择不同的内存对齐方式,如果没有使用malloc
    ...
      
    //如果 ptr 为 NULL 且 size 为 0,这段代码将尝试分配 1 字节的内存。这是为了处理某些系统上 malloc(0) 返回 NULL 的情况。
    if(!ptr && !size) {
        size = 1;
        ptr= av_malloc(1);
    }
  //在编译时,如果启用了内存填充选项(CONFIG_MEMORY_POISONING),这段代码会将分配的内存全部填充为特定的值(FF_MEMORY_POISON)。这通常用于调试,以便更容易检测未初始化或已释放的内存使用问题。
#if CONFIG_MEMORY_POISONING
    if (ptr)
        memset(ptr, FF_MEMORY_POISON, size);
#endif
    return ptr;
}

2.3 av_free

2.3.1 功能

释放内存

2.3.2 源码解析
//正常的free函数,有内存对齐的malloc的情况需要特殊处理
void av_free(void *ptr)
{
#if HAVE_ALIGNED_MALLOC
    _aligned_free(ptr);
#else
    free(ptr);
#endif
}

//对 av_free 的行为进行了封装,加入了一些额外的操作来管理内存的释放和指针的更新。这个函数特别适用于那些需要在释放内存的同时将指针设为 NULL 的场景。注意这里要传入二级指针,比如你想释放ptr,需要调用av_freep(&ptr);
void av_freep(void *arg)
{
    //定义一个 void * 类型的局部变量 val,用于存储 arg 指向的内存地址。
    void *val;
    //使用 memcpy 从 arg 指向的地址中拷贝数据到 val 中。这里拷贝的大小是 sizeof(val),即指针的大小。这样,val 就保存了 arg 指向的实际内存地址。
    memcpy(&val, arg, sizeof(val));
    //将 arg 指向的内存区域更新为 NULL。这通过 memcpy 实现,将 &(void *){ NULL }(一个临时的 NULL 指针)拷贝到 arg 指向的位置。此时,arg 指向的内存中的值被设置为 NULL。即如果传入&ptr,那么ptr将被置空
    memcpy(arg, &(void *){ NULL }, sizeof(val));
    //调用 av_free 释放 val 指向的内存块。这里的 val 是之前从 arg 拷贝过来的地址。
    av_free(val);
}

三、AVBuffer和AVBufferRef

3.1 头文件

libavutil/buffer.h

3.2 结构体

3.2.1 AVBuffer结构体

AVBuffer 是 FFmpeg 内部用于表示内存缓冲区的数据结构。它包含了实际的数据指针和相关的管理信息


struct AVBuffer {
    uint8_t *data;//这个buffer存储的数据
    size_t size; //这个buffer存储的数据大小

    atomic_uint refcount;//对该内存缓冲区的引用的数量
    //释放该数据的回调,类似析构函数
    void (*free)(void *opaque, uint8_t *data);
    void *opaque;
    int flags;
    int flags_internal;
};
3.2.2 AVBufferRef结构体

AVBufferRef 是一个引用计数的包装器,用于管理 AVBuffer 的生命周期。它包含一个指向 AVBuffer 的指针和一个引用计数器。

typedef struct AVBufferRef {
    AVBuffer *buffer;
    uint8_t *data; //和buffer中的data内容相同
    size_t   size; //和buffer中的size内容相同
} AVBufferRef;

3.3 引用计数机制

对于多个指针共享同一个缓存空间,FFmpeg使用的引用计数的机制(reference-count):

初始化引用计数为0,只有真正分配AVBuffer的时候,引用计数初始化为1;

当有新的Packet引用共享的缓存空间时,就将引用计数+1;

当引用计数为0时,释放AVBuffer中的内容。

相当于通过C语言实现了C++的智能指针。如果对该机制还不太了解可以了解下C++的智能指针的相关概念。

3.4 创建AVBufferRef

av_buffer_create 先申请了一个AVBuffer,然后再创建AVBufferRef

AVBufferRef *av_buffer_create(uint8_t *data, size_t size,
                              void (*free)(void *opaque, uint8_t *data),
                              void *opaque, int flags) {
    AVBufferRef *ret;
    AVBuffer *buf = av_mallocz(sizeof(*buf));
    if (!buf)
        return NULL;

    ret = buffer_create(buf, data, size, free, opaque, flags);
    if (!ret) {
        av_free(buf);
        return NULL;
    }
    return ret;
}


static AVBufferRef *buffer_create(AVBuffer *buf, uint8_t *data, size_t size,
                                  void (*free)(void *opaque, uint8_t *data),
                                  void *opaque, int flags)
{
    AVBufferRef *ref = NULL;

    buf->data     = data;
    buf->size     = size;
    buf->free     = free ? free : av_buffer_default_free;
    buf->opaque   = opaque;

    atomic_init(&buf->refcount, 1);

    buf->flags = flags;

    ref = av_mallocz(sizeof(*ref));
    if (!ref)
        return NULL;

    ref->buffer = buf;
    ref->data   = data;
    ref->size   = size;

    return ref;
}

3.5 增加引用

AVBufferRef *av_buffer_ref(const AVBufferRef *buf)
{
    AVBufferRef *ret = av_mallocz(sizeof(*ret));

    if (!ret)
        return NULL;
    //拷贝buf中的指针
    *ret = *buf;
    //将AVBuffer中的引用计数+1
    atomic_fetch_add_explicit(&buf->buffer->refcount, 1, memory_order_relaxed);

    return ret;
}

3.6 减少引用

void av_buffer_unref(AVBufferRef **buf)
{
    if (!buf || !*buf)
        return;
    //把buffer和null替换,那就是av_buffer_unref。
    buffer_replace(buf, NULL);
}

static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{
    AVBuffer *b;
    //获取 AVBuffer 指针:
    b = (*dst)->buffer;
    
    if (src) {
        //内存替换
        **dst = **src;
        av_freep(src);
    } else
        //释放dst及内存
        av_freep(dst);
    //减少 AVBuffer 的引用计数并检查是否需要释放:atomic_fetch_sub_explicit返回的是减少之前的值
    if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) == 1) {

        int free_avbuffer = !(b->flags_internal & BUFFER_FLAG_NO_FREE);
        b->free(b->opaque, b->data);
        if (free_avbuffer)
            av_free(b);
    }
}

四、AVPacket和AVFrame

4.1 头文件

libavcodec/packet.h

libavcodec/frame.h

4.2 依赖关系

在这里插入图片描述

4.3 结构体

4.3.1 AVPacket

AVPacket 结构体在 FFmpeg 中用于表示媒体数据包,包含音频或视频数据及其相关信息。结构体仅列出内存管理相关。

typedef struct AVPacket {
    //如果 buf 不为空,AVPacket 使用引用计数机制来管理其数据缓冲区的生命周期。
    //AVBufferRef 提供引用计数功能,当 AVPacket 不再使用时,引用计数会减少,并在引用计数为 0 时释放内存。
      //如果 buf 为 NULL,则 AVPacket 的数据不使用引用计数管理。这种情况下,内存管理由 AVPacket 本身负责。
    AVBufferRef *buf;
  
    //指向实际的数据缓冲区。data 是一个指针,指向存储音频或视频数据的内存区域。
    //如果 buf 不为空,data 指向的内存由 AVBufferRef 管理。data 只是 AVBufferRef 内部数据的指针,不直接负责内存管理。
    //如果 buf 为空,data 指向的内存可能需要显式释放,通常在不再使用 AVPacket 时释放 data 所指向的内存。
    uint8_t *data;
    int   size;
   
     //提供一个 AVBufferRef,供 API 用户自由使用。FFmpeg 不会检查这个缓冲区的内容,只在 AVPacket 被取消引用时调用 av_buffer_unref() 来处理。
    //用户可以使用 opaque_ref 存储附加的私有数据。
    //FFmpeg 会在 AVPacket 取消引用时调用 av_buffer_unref() 来释放 opaque_ref。
    //在 av_packet_copy_props() 调用时,会创建新的 AVBufferRef 引用到 opaque_ref。
    AVBufferRef *opaque_ref;

} AVPacket;
4.3.2 AVFrame

AVFrame 结构体在 FFmpeg 中用于表示解码后的音频或视频帧。该结构体的内存管理机制主要涉及几个字段,包括数据缓冲区的引用计数、私有数据处理和扩展数据管理。

typedef struct AVFrame {
    //指向包含音频或视频数据的缓冲区。对于视频帧,每个指针可能指向图像的一个平面,对于音频帧,每个指针可能指向一个通道的数据。
    //这些指针应指向在 buf 或 extended_buf 中管理的内存区域。
    //当AVFrame 被释放时,指针指向的内存会通过引用计数机制处理,具体取决于 buf 和 extended_buf 的内容。
    uint8_t *data[AV_NUM_DATA_POINTERS];
  
    //数组用于描述数据平面的每一行的大小。这可能包括额外的填充字节,用于对齐
    int linesize[AV_NUM_DATA_POINTERS];
    //extended_data 提供对 data 数组无法容纳的额外数据的访问。
    //需要注意的是,extended_data 中的指针也应指向在 buf 或 extended_buf 中管理的内存区域。
    uint8_t **extended_data;
  
    //管理data
    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
    //管理extended_data
    AVBufferRef **extended_buf;
    //和AVPacket中的opaque_ref类似,此处不赘述
    AVBufferRef *opaque_ref;

}

4.4 内存分配

avpacket和avframe差不多,此处不赘述。主要步骤有两步,一是申请一个packet或者frame,另外一个是申请packet和frame中的具体数据。packet的数据大小需要手动指定,frame中的数据大小可以使用**av_frame_get_buffer**计算出来。

//申请一个packet
AVPacket *av_packet_alloc(void)
{
    AVPacket *pkt = av_malloc(sizeof(AVPacket));
    if (!pkt)
        return pkt;
    //默认初始化
    get_packet_defaults(pkt);

    return pkt;
}

//给packet分配实际数据空间
int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    int ret = packet_alloc(&buf, size);
    if (ret < 0)
        return ret;

    get_packet_defaults(pkt);
    pkt->buf      = buf;
    pkt->data     = buf->data;
    pkt->size     = size;

    return 0;
}

int av_frame_get_buffer(AVFrame *frame, int align)
{
    if (frame->format < 0)
        return AVERROR(EINVAL);

    if (frame->width > 0 && frame->height > 0)
        //计算视频buffer
        return get_video_buffer(frame, align);
    else if (frame->nb_samples > 0 &&
             (av_channel_layout_check(&frame->ch_layout)))
        //计算音频buffer
        return get_audio_buffer(frame, align);

    return AVERROR(EINVAL);
}

4.5 增加引用

int av_packet_ref(AVPacket *dst, const AVPacket *src) {
    int ret;
    dst->buf = NULL;
    //拷贝packet中的属性信息
    ret = av_packet_copy_props(dst, src);
    if (ret < 0)
        goto fail;

    //如果src中的buf不存在,说明src中不是用引用计数来管理内存的,那只要把数据拷贝过去就可以了
    if (!src->buf) {
        ret = packet_alloc(&dst->buf, src->size);
        if (ret < 0)
            goto fail;
        av_assert1(!src->size || src->data);
        if (src->size)
            memcpy(dst->buf->data, src->data, src->size);

        dst->data = dst->buf->data;
    } else {
        //增加对src中buf的引用
        dst->buf = av_buffer_ref(src->buf);
        if (!dst->buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        dst->data = src->data;
    }

    dst->size = src->size;

    return 0;
fail:
    av_packet_unref(dst);
    return ret;
}

4.6 减少引用

void av_packet_unref(AVPacket *pkt)
{
    //...
    av_buffer_unref(&pkt->opaque_ref);
    av_buffer_unref(&pkt->buf);
    //将pkt的参数信息回复到默认,避免使用已被解引用的packet
    get_packet_defaults(pkt);
}

4.7释放内存

void av_packet_free(AVPacket **pkt)
{
    if (!pkt || !*pkt)
        return;
    //释放前先做一次解引用
    av_packet_unref(*pkt);
    av_freep(pkt);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092899.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

phpstudy怎么用

启动Apache 这是你的默认网站域名。点击物理路径 进入到目录&#xff0c;将你的php文件项目拖进去。如test.php 打开浏览器

python 天气与股票的关系--第2部分,清洗数据

先看一下股票信息 合并天气信息 合并2个数据 合并之后&#xff0c;会自动删除 周六和周日 节假日 下一篇&#xff0c;尝试建立数学模型

生产环境变态开启devtools(redux篇)

前沿 默认都安装了谷歌的redux-devtools插件哦 没有亮,说明关闭了生产环境的redux devtools工具, 接下来跟着博主一起变态启用它 如果看了我上一篇的小伙伴,应该会很熟练了,如果没有看上一篇的,也没关系,博主会手摸手的教你们打开它。 正常的解决方案(适用内部开发人员…

【c++】日期类相关实践:计算日期到天数转换、日期差值

相关文章&#xff1a;日期类&#xff08;运算符重载应用&#xff09;详细版 目录 前言 实践1&#xff1a;计算日期到天数转换 题目 方法 关键代码 完整代码 实践2&#xff1a;日期差值 题目 方法 关键代码 完整代码 &#x1f497;感谢阅读&#xff01;&#x1f49…

python网络爬虫(四)——实战练习

0.为什么要学习网络爬虫 深度学习一般过程:   收集数据&#xff0c;尤其是有标签、高质量的数据是一件昂贵的工作。   爬虫的过程&#xff0c;就是模仿浏览器的行为&#xff0c;往目标站点发送请求&#xff0c;接收服务器的响应数据&#xff0c;提取需要的信息&#xff0c…

Python 算法交易实验85 QTV200日常推进-钳制指标与交易量

说明 继续保持思考与尝试 最近挺有意思的&#xff0c;碰到很多技术上的问题&#xff0c;其解决方案都类似“阴阳两仪”的概念。 "阴阳两仪"是中国古代哲学中的一个重要概念&#xff0c;源自《易经》&#xff08;又称《周易》&#xff09;。它是对宇宙间最基本对立统一…

数据结构与算法 第5天(树和二叉树)

树形结构 一对多 只有一个前驱 可以有多个后继 树的定义 基本术语 有序树&#xff1a;树中结点的各子树从左至右有次序(最左边的为第一个孩子) 森林&#xff1a;是 m(m≥0)棵互不相交的树的集合。 一棵树可以看成特殊的森林 二叉树 每个节点最多有两个…

【王树森】BERT:预训练Transformer模型(个人向笔记)

前言 BERT&#xff1a;Bidirectional Encoder Representations from TransformerBERT是用来预训练Transformer模型的encoder的本节课只讲述主要思想BERT用两个主要思想来训练Transformer的encoder网络&#xff1a;①随机遮挡单词&#xff0c;让encoder根据上下文来预测被遮挡的…

2024年9月1日 十二生肖 今日运势

小运播报&#xff1a;2024年9月1日&#xff0c;星期日&#xff0c;农历七月廿九 &#xff08;甲辰年壬申月戊辰日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;鸡、猴、鼠 需要注意&#xff1a;龙、兔、狗 喜神方位&#xff1a;东南方 财神方位&#xff1a;正…

【系统架构设计师-2015年】综合知识-答案及详解

【第1~2题】 某航空公司机票销售系统有n个售票点&#xff0c;该系统为每个售票点创建一个进程Pi&#xff08;i1&#xff0c;2&#xff0c;…&#xff0c;n&#xff09;管理机票销售。假设Tj&#xff08;j1&#xff0c;2&#xff0c;…&#xff0c;m&#xff09;单元存放某日某…

2025届必看:如何用Java SpringBoot+Vue打造免费体育馆场地预约系统?

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

异常与使用

异常 一、C语言传统的错误处理机制二、异常1、概念2、关键字3、示例 三、异常的使用1、异常的抛出和匹配原则2、在函数调用链中异常栈展开匹配原则3、栈展开示意图4、示例代码5、运行结果 四、异常的重新抛出1、作用2、示例代码3、运行结果 五、异常安全六、异常规范1、概念2、…

CSS-浮动【看这一篇就够了】

目录 浮动布局 浮动是如何工作的 浮动的本质和要点 如何产生浮动 元素浮动的特性 1.元素添加浮动后&#xff0c;脱离文档流 2.如果父元素的宽度不够 3.浮动的顺序贴靠特性 4.元素浮动后&#xff0c;具有行内块级元素特性 5.浮动的元素会造成父元素高度塌陷 6.浮动对…

“无法连接打印机0X0000011B”原因分析及多种解决方法

在日常办公和生活中&#xff0c;打印机是不可或缺的重要设备。然而&#xff0c;有时在连接打印机的过程中&#xff0c;我们可能会遇到错误代码0x0000011b的提示。有更新补丁导致的、有访问共享打印机服务异常、有访问共享打印机驱动异常等问题导致的&#xff0c;针对访问共享打…

MySQL场景测试题

第一题 软件环境描述&#xff1a; Mysql V5.7.30 Innodb RR隔离级别 表结构以及数据描述&#xff1a; &#xff08;1&#xff09;t_user用户表&#xff0c;表格如下&#xff1a; CREATE TABLE t_user ( id int(10) NOT NULL, name varchar(100) DEFAULT NULL, PRIMARY KEY (id)…

240831-Gradio之RAG文档对话工具Kotaemon的安装与配置

A. 用户界面 该项目既可以作为功能性 RAG UI&#xff0c;既可以用于对文档进行 QA 的最终用户&#xff0c;也可以用作想要构建自己的 RAG 管道的开发人员。对于最终用户&#xff1a; - 一个干净且简约的用户界面&#xff0c;用于基于RAG的QA。 - 支持 LLM API 提供程序&#xf…

gethub-rrsf

一.FastCGI协议 1.来到127.0.0.1下发现404报错 2.这一关我们要借助一个叫Gopherus的工具&#xff0c;我这里是在kali虚拟机里面克隆的 git clone https://github.com/tarunkant/Gopherus.git 3.运行命令 由于一句话木马无法写入&#xff0c;所以我们使用base64编码&#xf…

将Google Chrome或Microsoft Edge浏览器的地址栏隐藏的方法

将Google Chrome或Microsoft Edge浏览器的地址栏隐藏的方法 目标效果示范 我们以百度首页为例&#xff0c;普通模式启动的页面通常会显示地址栏&#xff0c;如下图所示&#xff1a; 而本文要实现的效果是隐去地址栏和书签栏&#xff08;如果有的话&#xff09;&#xff0c;无…

重生奇迹MU 敏捷流梦幻骑士 真正的平民PK王

“梦幻骑士”这个职业已经存在于重生奇迹MU中很长时间了&#xff0c;虽然现在已经不算是新职业了&#xff0c;但玩家们对于梦幻骑士的研究和开发一直没有停止过。它作为一个特殊的职业&#xff0c;与传统职业截然不同&#xff0c;拥有着许多独特的玩法。其中&#xff0c;有一种…

JVM2-JVM组成、字节码文件、类的生命周期、类加载器

Java虚拟机的组成 Java虚拟机主要分为以下几个组成部分&#xff1a; 类加载子系统&#xff1a;核心组件类加载器&#xff0c;负责将字节码文件中的内容加载到内存中运行时数据区&#xff1a;JVM管理的内存&#xff0c;创建出来的对象、类的信息等内容都会放在这块区域中执行引…