ESP32 碰上内存分配问题

1、背景

看图片

_calloc_r ->_malloc_r ->heap_caps_malloc_default->heap_caps_malloc->multi_heap_malloc->multi_heap_malloc_impl->get_next_block

/* Return the next sequential block in the heap.*/
static inline heap_block_t *get_next_block(const heap_block_t *block)
{intptr_t next = block->header & NEXT_BLOCK_MASK;if (next == 0) {return NULL; /* last_block */}assert(next > (intptr_t)block);return (heap_block_t *)next;
}

在分配堆内存时,碰上了这个断言,触发应用程序反复重启。

 1.1 参考文档

ESP32 程序的内存模型 - 知乎MCU 中的内存资源可能是其最宝贵的资源,因为它在芯片中占据最大的面积。更新的应用程序对内存的需求正在不断增长。为了充分利用硬件资源,理解内存架构并能针对应用程序的实际用例进行内存优化变得至关重要。特别…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/345915256?utm_id=0

https://www.cnblogs.com/sinferwu/p/17253138.htmlicon-default.png?t=N7T8https://www.cnblogs.com/sinferwu/p/17253138.html

esp32 heap 内存管理简析-CSDN博客文章浏览阅读1.2w次,点赞2次,收藏29次。嵌入式系统运行时的内存情况是非常值得关注的。本文档用于分析乐鑫ESP32 SDK(版本esp-idf-v3.0-rc1) Heap (堆内存)管理的实现。 1:Heap管理主要函数接口与数据结构 1.1主要函数接口ESP32的SDK对于heap部分管理的源码位于路径\esp-idf-v3.0-rc1\components\heap下,可以简单的认为分为两层:heap_caps_init.c与hea..._esp32 heaphttps://blog.csdn.net/abc517789065/article/details/79680214https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/mem_alloc.htmlicon-default.png?t=N7T8https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/mem_alloc.html

2.理论知识

2.0 内存管理

 ESP-IDF 应用程序使用常见的计算机架构模式:由程序控制流动态分配的内存(即 )、由函数调用动态分配的内存(即 )以及在编译时分配的 静态内存

由于 ESP-IDF 是一个多线程 RTOS 环境(FreeRTOS的变种),因此每个 RTOS 任务都有自己的栈,这些栈默认在创建任务时从堆中分配。在FreeRTOS的实现中,每个任务用到两个内存块,其中一块用来保存任务自身的数据结构--类似Task_t(process control block);另一块内存块用作任务的栈。xTaskCreate()两个内存块都是内部实现从堆中动态分配的,而xTaskCreateStatic的两个内存块都需要程序控制流作为参数传递给它。

2.1 ESP32内存类型和特性

ESP32包含多种类型的RAM

DRAM:是连接到 CPU 数据总线上的内存,用于存储数据。这是作为堆访问最常见的一种内存(用作bss段、data段和堆)。

IRAM:是连接到 CPU 指令总线上的内存,通常仅用于存储可执行数据(即指令,text段)。如果作为通用内存访问,则所有访问必须为 32 位可访问内存。---4字节地址严格对齐

D/IRAM:连接到 CPU 数据总线和指令总线的 RAM,因此可用作指令 RAM 或数据 RAM

ESP32 还可外扩SPI RAM,通过缓存将 片外 RAM 集成到 ESP32 的内存映射中,访问方式与 DRAM 类似。

2.2 ESP32内存图

520K SRAM = 192KSRAM0 + 128KSRAM1 +200KSRAM2

由上图可知,SRAM0被用作IRAM,SRAM2被用作DRAM,SRAM1既可被用作IRAM,也可被用作DRAM。SRAM1默认被用作DRAM,以补充应用程序中数据空间的紧缺。

2.3 基于内存属性的堆内存分配器

ESP32 使用多种类型的 RAM,因此具备不同属性的堆,即基于属性的内存分配器允许应用程序按不同目的进行堆分配。

2.3.0 堆内存分配器管理的DRAM大小和初始化过程

启动时,DRAM 堆包含应用程序未静态分配的所有数据内存,减少静态分配的缓冲区将增加可用的空闲堆空间。静态分配和其他特殊目的保留的内存外,其他内存均受到堆内存分配器的管理。

/* Initialize the heap allocator to use all of the memory notused by static data or reserved for other purposes*/
void heap_caps_init()

 系统调用heap_caps_init实现Heap空间初始化的过程,实质就是初始heap_t数据结构的过程。简单的看可以分为三个步骤:

<1> 遍历当前系统的可用内存区域,并对类型相同的相邻区域进行合并

<2> 将可用内存区域通过register_heap初始化为Heap分区的结构(建立Meta Data头以及数据区的链表)

<3> 将所有Heap分区信息通过链表连接到registered_heaps

2.3.0.1 步骤1

heap_caps_init中的关键代码段1

    /* Get the array of regions that we can use for heaps(with reserved memory removed already.)*/size_t num_regions = soc_get_available_memory_region_max_count();soc_memory_region_t regions[num_regions];num_regions = soc_get_available_memory_regions(regions);//The heap allocator will treat every region given to it as separate. In order to get bigger ranges of contiguous memory,//it's useful to coalesce adjacent regions that have the same type.for (int i = 1; i < num_regions; i++) {soc_memory_region_t *a = &regions[i - 1];soc_memory_region_t *b = &regions[i];if (b->start == a->start + a->size && b->type == a->type ) {a->type = -1;b->start = a->start;b->size += a->size;}}/* Count the heaps left after merging */size_t num_heaps = 0;for (int i = 0; i < num_regions; i++) {if (regions[i].type != -1) {num_heaps++;}}

 其中有一个关键的数据结构体soc_memory_region_t,其成员包括region的起始地址、大小、类型。

/* Region descriptor holds a description for a particular region of memory on a particular SoC.*/
typedef struct
{intptr_t start;  ///< Start address of the regionsize_t size;            ///< Size of the region in bytessize_t type;             ///< Type of the region (index into soc_memory_types array)intptr_t iram_address; ///< If non-zero, is equivalent address in IRAM
} soc_memory_region_t;

什么叫类型相同的相邻区域,就是前region和当前region的内存类型相同,前region的起始地址+大小=当前region的起始地址,那么就是类型相同的相邻区域,可以合并。

2.3.0.2 步骤2
/* Type for describing each registered heap */
typedef struct heap_t_ {uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; ///< Capabilities for the type of memory in this heap (as a prioritised set). Copied from soc_memory_types so it's in RAM not flash.intptr_t start;intptr_t end;portMUX_TYPE heap_mux;multi_heap_handle_t heap;SLIST_ENTRY(heap_t_) next;
} heap_t;

关键数据结构heap_t,描述注册heap的类型,成员有caps--功能,起始地址和结束地址、互斥锁等等

  ESP_EARLY_LOGI(TAG, "Initializing. RAM available for dynamic allocation:");for (int i = 0; i < num_regions; i++) {soc_memory_region_t *region = &regions[i];const soc_memory_type_desc_t *type = &soc_memory_types[region->type];heap_t *heap = &temp_heaps[heap_idx];if (region->type == -1) {continue;}heap_idx++;assert(heap_idx <= num_heaps);memcpy(heap->caps, type->caps, sizeof(heap->caps));heap->start = region->start;heap->end = region->start + region->size;vPortCPUInitializeMutex(&heap->heap_mux);if (type->startup_stack) {/* Will be registered when OS scheduler starts */heap->heap = NULL;} else {register_heap(heap);}SLIST_NEXT(heap, next) = NULL;ESP_EARLY_LOGI(TAG, "At %08X len %08X (%d KiB): %s",region->start, region->size, region->size / 1024, type->name);}

关键在于register_heap(),注册这些可用region

2.3.0.3 步骤3

找到第一个DRAM,分配永久堆数据,用作运行时的链表--数组。设置锁并将之加入到链表中。

 heap_t *heaps_array = NULL;for (int i = 0; i < num_heaps; i++) {if (heap_caps_match(&temp_heaps[i], MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)) {/* use the first DRAM heap which can fit the data */heaps_array = multi_heap_malloc(temp_heaps[i].heap, sizeof(heap_t) * num_heaps);if (heaps_array != NULL) {break;}}}assert(heaps_array != NULL); /* if NULL, there's not enough free startup heap space */memcpy(heaps_array, temp_heaps, sizeof(heap_t)*num_heaps);/* Iterate the heaps and set their locks, also add them to the linked list. */for (int i = 0; i < num_heaps; i++) {if (heaps_array[i].heap != NULL) {multi_heap_set_lock(heaps_array[i].heap, &heaps_array[i].heap_mux);}if (i == 0) {SLIST_INSERT_HEAD(&registered_heaps, &heaps_array[0], next);} else {SLIST_INSERT_AFTER(&heaps_array[i-1], &heaps_array[i], next);}}

 有一个疑问,这个heap_t 链表是不能扩容的,用来表述可用的堆空间。这个链表和后面分配的总链表和free链表有什么关系?

2.3.0.4  void register_heap(heap_t *region)

register_heap(heap)

        ->multi_heap_register((void *)region->start, heap_size)

                ->multi_heap_register_impl(start, size)

register_heap将普通的内存区域初始化为Heap分区结构,multi_heap_register_impl是其最终实现功能的接口. 


multi_heap_handle_t multi_heap_register_impl(void *start_ptr, size_t size)
{uintptr_t start = ALIGN_UP((uintptr_t)start_ptr);uintptr_t end = ALIGN((uintptr_t)start_ptr + size);heap_t *heap = (heap_t *)start;size = end - start;if (end < start || size < sizeof(heap_t) + 2*sizeof(heap_block_t)) {return NULL; /* 'size' is too small to fit a heap here */}heap->lock = NULL;heap->last_block = (heap_block_t *)(end - sizeof(heap_block_t));/* first 'real' (allocatable) free block goes after the heap structure */heap_block_t *first_free_block = (heap_block_t *)(start + sizeof(heap_t));first_free_block->header = (intptr_t)heap->last_block | BLOCK_FREE_FLAG;first_free_block->next_free = heap->last_block;/* last block is 'free' but has a NULL next pointer */heap->last_block->header = BLOCK_FREE_FLAG;heap->last_block->next_free = NULL;/* first block also 'free' but has legitimate length,malloc will never allocate into this block. */heap->first_block.header = (intptr_t)first_free_block | BLOCK_FREE_FLAG;heap->first_block.next_free = first_free_block;/* free bytes is:- total bytes in heap- minus heap_t header at top (includes heap->first_block)- minus header of first_free_block- minus whole block at heap->last_block*/heap->free_bytes = size - sizeof(heap_t) - sizeof(first_free_block->header) - sizeof(heap_block_t);heap->minimum_free_bytes = heap->free_bytes;return heap;
}

发现了吧返回值类型是multi_heap_handle_t,在函数实现中用的是heap_t,怎么回事?

typedef struct multi_heap_info *multi_heap_handle_t;

/* Block in the heapHeap implementation uses two single linked lists, a block list (all blocks) and a free list (free blocks).'header' holds a pointer to the next block (used or free) ORed with a free flag (the LSB of the pointer.) is_free() and get_next_block() utility functions allow typed access to these values.'next_free' is valid if the block is free and is a pointer to the next block in the free list.
*/
typedef struct heap_block {intptr_t header;                  /* Encodes next block in heap (used or unused) and also free/used flag */union {uint8_t data[1];              /* First byte of data, valid if block is used. Actual size of data is 'block_data_size(block)' */struct heap_block *next_free; /* Pointer to next free block, valid if block is free */};
} heap_block_t;/* These masks apply to the 'header' field of heap_block_t */
#define BLOCK_FREE_FLAG 0x1  /* If set, this block is free & next_free pointer is valid */
#define NEXT_BLOCK_MASK (~3) /* AND header with this mask to get pointer to next block (free or used) *//* Metadata header for the heap, stored at the beginning of heap space.'first_block' is a "fake" first block, minimum length, used to provide a pointer to the first used & free block inthe heap. This block is never allocated or merged into an adjacent block.'last_block' is a pointer to a final free block of length 0, which is added at the end of the heap when it isregistered. This block is also never allocated or merged into an adjacent block.*/
typedef struct multi_heap_info {void *lock;size_t free_bytes;size_t minimum_free_bytes;heap_block_t *last_block;heap_block_t first_block; /* initial 'free block', never allocated */
} heap_t;

 一段可用的连续内存被初始化为Heap分区时,分区起始地址会写入一个MetaData头(即是multi_heap_info类型)。MetaData头后紧接着是第一个真正可以用来分配的内存块block0,block0包含一个信息头heap_block_t以及实际数据区。

在Heap分区的最后,有一个heap_block_t的信息头作为整个分区的结尾。heap_block_t结构中有指向下个节点的指针,这样分区所有block组成链表用于访问

最后可用的就是Heap_Block0_DATA的。 

由此可知,管理内存就通过这两级。先通过遍历heap_t链表查找符合申请内存类型的heap_t,在从heap遍历以找到最优满足大小的Heap_Block。

2.3.1 Heap空间分配

Heap空间是系统运行期间用户通过函数申请的,如何分配与释放是关键问题。本节描述关于Heap空间如何分配的问题?

2.3.1.3 C标准函数库malloc()和free()----MALLOC_CAP_DEFAULT属性

调用 malloc() 时,ESP-IDF malloc() 内部调用 heap_caps_malloc_default(size),使用属性 MALLOC_CAP_DEFAULT 分配内存。该属性可实现字节寻址功能,即存储空间的最小编址单位为字节。

对于Heap空间的分配过程,通过对代码的理解,可以简单的概括为三个步骤:

当用户向系统通过malloc申请Heap内存空间时:

<1>系统会遍历每个Heap分区(通过registered_heaps),找到满足申请内存类型(如MALLOC_CAP_DEFAULT属性)的分区;再依次遍历分区下所有可用的Heap Block,找到可用空间满足申请内存大小的Heap Block。

<2>遍历所有Heap Block期间,会出现几种可能:

        A:若Heap Block的可用空间小于用户申请的内存大小,显然不能使用,继续查找下一个Heap Block;

        B:若Heap Block的可用空间正好等于用户申请的内存大小,这时系统出现了最佳的分配选择,策略选择该Heap Block作为本次分配的Best Heap Block,停止遍历

        C:若Heap Block的可用空间大于用户申请的内存大小,则先行记录下这个Heap Block的位置以及它的实际可用空间大小,然后继续遍历查找;在找到下一个满足条件的Heap Block时,比较两者,选取可用空间较小的记录下来。策略的目的即是保证最终得到一个既满足用户申请需求又最不浪费空间的Best Heap Block。

<3>对应步骤<2>选出的Best Heap Block:

        A:若Best HeapBlock不存在,意味着用户申请失败;

        B:若Best HeapBlock空间正好满足用户申请的需求,则无需另做处理,直接给到用户即可;

        C:若Best HeapBlock的空间除去用户申请的需求还有盈余,则需要进行分割。盈余部分需要分割成为新的可用(空闲)Heap Block,等待下次内存申请。

三次分配,从初始化、分配SpaceA、SpaceB、SpaceC。

3、问题分析

回归到背景中问题,malloc分配中出现问题_calloc_r,从中看出调用关系

_calloc_r ->

        _malloc_r ->

                heap_caps_malloc_default->

                        heap_caps_malloc->

                                multi_heap_malloc->

                                        multi_heap_malloc_impl->

                                                split_if_necessary->

                                                        get_next_block

void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size)
{heap_block_t *best_block = NULL;heap_block_t *prev_free = NULL;heap_block_t *prev = NULL;size_t best_size = SIZE_MAX;size = ALIGN_UP(size);if (size == 0 || heap == NULL) {return NULL;}multi_heap_internal_lock(heap);/* Note: this check must be done while holding the lock as bothmalloc & realloc may temporarily shrink the free_bytes valuebefore they split a large block. This can result in false negatives,especially if the heap is unfragmented.*/if (heap->free_bytes < size) {MULTI_HEAP_UNLOCK(heap->lock);return NULL;}/* Find best free block to perform the allocation in */prev = &heap->first_block;for (heap_block_t *b = heap->first_block.next_free; b != NULL; b = b->next_free) {MULTI_HEAP_ASSERT(b > prev, &prev->next_free); // free blocks should be ascending in addressMULTI_HEAP_ASSERT(is_free(b), b); // block should be freesize_t bs = block_data_size(b);if (bs >= size && bs < best_size) {best_block = b;best_size = bs;prev_free = prev;if (bs == size) {break; /* we've found a perfect sized block */}}prev = b;}if (best_block == NULL) {multi_heap_internal_unlock(heap);return NULL; /* No room in heap */}prev_free->next_free = best_block->next_free;best_block->header &= ~BLOCK_FREE_FLAG;heap->free_bytes -= block_data_size(best_block);split_if_necessary(heap, best_block, size, prev_free);if (heap->free_bytes < heap->minimum_free_bytes) {heap->minimum_free_bytes = heap->free_bytes;}multi_heap_internal_unlock(heap);return best_block->data;
}/* Split a block so it can hold at least 'size' bytes of data, making any sparespace into a new free block.'block' should be marked in-use when this function is called (implementation detail, this functiondoesn't set the next_free pointer).'prev_free_block' is the free block before 'block', if already known. Can be NULL if not yet known.(This is a performance optimisation to avoid walking the freelist twice when possible.)
*/
static void split_if_necessary(heap_t *heap, heap_block_t *block, size_t size, heap_block_t *prev_free_block)
{const size_t block_size = block_data_size(block);MULTI_HEAP_ASSERT(!is_free(block), block); // split block shouldn't be freeMULTI_HEAP_ASSERT(size <= block_size, block); // size should be validsize = ALIGN_UP(size);/* can't split the head or tail block */assert(!is_first_block(heap, block));assert(!is_last_block(block));heap_block_t *new_block = (heap_block_t *)(block->data + size);heap_block_t *next_block = get_next_block(block);if (is_free(next_block) && !is_last_block(next_block)) {/* The next block is free, just extend it upwards. */new_block->header = next_block->header;new_block->next_free = next_block->next_free;if (prev_free_block == NULL) {prev_free_block = get_prev_free_block(heap, block);}/* prev_free_block should point to the next block (which we found to be free). */MULTI_HEAP_ASSERT(prev_free_block->next_free == next_block,&prev_free_block->next_free); // free blocks should be in order/* Note: We have not introduced a new block header, hence the simple math. */heap->free_bytes += block_size - size;
#ifdef MULTI_HEAP_POISONING_SLOW/* next_block header needs to be replaced with a fill pattern */multi_heap_internal_poison_fill_region(next_block, sizeof(heap_block_t), true /* free */);
#endif} else {/* Insert a free block between the current and the next one. */if (block_data_size(block) < size + sizeof(heap_block_t)) {/* Can't split 'block' if we're not going to get a usable free block afterwards */return;}if (prev_free_block == NULL) {prev_free_block = get_prev_free_block(heap, block);}new_block->header = block->header | BLOCK_FREE_FLAG;new_block->next_free = prev_free_block->next_free;/* prev_free_block should point to a free block after new_block */MULTI_HEAP_ASSERT(prev_free_block->next_free > new_block,&prev_free_block->next_free); // free blocks should be in orderheap->free_bytes += block_data_size(new_block);}block->header = (intptr_t)new_block;prev_free_block->next_free = new_block;
}

看看heap_block这个结构体,包括一个总的block 链表和一个空闲block列表

/* Block in the heapHeap implementation uses two single linked lists, a block list (all blocks) and a free list (free blocks).'header' holds a pointer to the next block (used or free) ORed with a free flag (the LSB of the pointer.) is_free() and get_next_block() utility functions allow typed access to these values.'next_free' is valid if the block is free and is a pointer to the next block in the free list.
*/
typedef struct heap_block {intptr_t header;                  /* Encodes next block in heap (used or unused) and also free/used flag */union {uint8_t data[1];              /* First byte of data, valid if block is used. Actual size of data is 'block_data_size(block)' */struct heap_block *next_free; /* Pointer to next free block, valid if block is free */};
} heap_block_t;

block的信息被损坏,查看block信息。

3.1 问题原因和解决方法

原因在于内存溢出,申请的空间不足放下,强制填入导致后一个block的信息被破坏。

检查每个内存申请。

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

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

相关文章

【10套模拟】【8、9】

关键字&#xff1a; 建立有序单链表、多维数组是特殊线性结构、直接选择排序、哈夫曼树高度及wpl、中序最后叶子先序最后也是、堆是完全二叉树、分块查找 统计二叉树结点数、统计二叉树值和、奇偶数划分、判断链表是否递增

MySQL慢查询

快捷查看指令 ctrlf 进行搜索会直接定位到需要的知识点和命令讲解&#xff08;如有不正确的地方欢迎各位小伙伴在评论区提意见&#xff0c;博主会及时修改&#xff09; MySQL慢查询 MySQL 慢查询是指查询语句执行速度过慢&#xff0c;影响系统整体性能和查询效率的情况。MySQL…

详解Python中哈希表的使用。站在开发者角度,与大家一起探究哈希的世界。

文章目录 1. 前言2. 哈希表2.1 哈希函数2.2 哈希算法2.3 常见哈希算法2.4 哈希冲突 3.总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面…

Python安装入门

目录 1 从应用商店安装2 通过官方安装3 验证安装是否成功4 打印hello world总结 1 从应用商店安装 推荐使用微软的应用商店安装&#xff0c;打开开始菜单 在应用商店搜索python 选择最新的版本下载并安装即可 2 通过官方安装 也可以使用官网的安装包&#xff0c;输入如下网…

以太坊铭文聚合交易平台 Scorpio,铭文爆发的新推手?

在今年 3 月&#xff0c;Ordinals 凭空问世&#xff0c;定义了一套在比特币网络运行的序数协议&#xff0c;使得 Token 和 NFT 能在比特币网络上实现并稳定运行&#xff0c;拉来了比特币铭文市场的新序幕。而在此后&#xff0c;在包括 BRC20 等在内的一系列应用的出现&#xff…

智慧城市内涝积水监测仪功能,提升城市预防功能

内涝积水监测仪不仅改变了人们应对城市内涝的老办法&#xff0c;还让智慧城市往前迈了一大步。这个监测仪是怎么做到的呢&#xff1f;就是靠它精准的数据监测和预警&#xff0c;让城市管理有了更科学高效的解决妙招。它就像有了个聪明又负责任的助手&#xff0c;让城市管理更加…

FPGA设计时序约束九、others类约束之Group Path

目录 一、序言 二、Group Path 2.1 基本概念 2.2 设置界面 2.3 命令语法 2.4 命令示例 三、工程示例 四、参考文件 一、序言 在Vivado的时序约束窗口中&#xff0c;存在一类特殊的约束&#xff0c;划分在others目录下&#xff0c;可用于设置忽略或修改默认的时序路径分…

【EI会议征稿】2024年智慧城市与信息系统国际学术会议 (ICSCIS 2024)

2024年智慧城市与信息系统国际学术会议 (ICSCIS 2024) 2024 International Conference on Smart City and Information System 随着互联网技术的发展&#xff0c;城市化进程的深入&#xff0c;智慧城市的研究与发展越来越普遍&#xff0c;运用物联网、云计算、大数据等先进信…

【考研数学】数学一“背诵”手册(一)| 高数部分(2)

文章目录 引言一、高数级数空间解析几何球坐标变换公式零碎公式 写在最后 引言 高数一篇文章还是写不太下&#xff0c;再分一些到这里来吧 一、高数 级数 阿贝尔定理&#xff1a;若级数 ∑ a n x n \sum a_nx^n ∑an​xn 当 x x 0 xx_0 xx0​ 时收敛&#xff0c;则适合不…

QT搭建的Ros/librviz的GUI软件

1.前言 开发初期学习了下面博主的文章&#xff0c;也报了他在古月局的课&#xff0c;相当于感谢吧。 ROS Qt5 librviz人机交互界面开发一&#xff08;配置QT环境&#xff09;-CSDN博客​​​​​​​r 软件前期也是参考他的开源项目 GitHub - chengyangkj/Ros_Qt5_Gui_App …

Linux-编译器

编译器 gcc-arm-linux-gnueabihf gcc-arm-linux-gnueabihf 是一个针对 ARM 架构 Linux 系统的交叉编译工具链&#xff0c;它包括了 C、C、Objective-C 和 Fortran 编译器以及一些辅助工具&#xff0c;用于将源代码编译成可在 ARM 架构的 Linux 系统上运行的二进制程序。arm架…

2024贵州大学计算机考研分析

24计算机考研|上岸指南 贵州大学 贵州大学计算机科学与技术学院&#xff08;贵州大学省级示范性软件学院&#xff09;位于贵州省贵阳市花溪区贵州大学东校区。 计算机科学与技术学院&#xff08;软件学院&#xff09;自1972年创办计算机软件本科专业开始&#xff0c;至今已有…

算法刷题-动态规划-1

算法刷题-动态规划-1 不同路径不同路径||方法一&#xff1a;方法二 第N个泰波那契数递归写法滚动数组 三步问题递归操作滚动数组 使用最小画法爬楼梯递归 解码方法方法一方法二&#xff1a;&#xff08;大佬讲解&#xff09; 不同路径 //机器人不同的路径进入到指定的地点 publ…

人工智能-循环神经网络的简洁实现

循环神经网络的简洁实现 如何使用深度学习框架的高级API提供的函数更有效地实现相同的语言模型。 我们仍然从读取时光机器数据集开始。 import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2lbatch_size, num_steps 32, 35…

最常用的5款报表系统

在这个信息化飞速发展的时代&#xff0c;报表系统已经成为了企业管理和决策的重要工具。随着市场的需求不断增长&#xff0c;报表系统也在不断地更新和完善。如今&#xff0c;市面上有数不尽的报表系统&#xff0c;但是哪款才是最常用的呢&#xff1f;接下来&#xff0c;我们将…

开源之夏 2023 | Databend 社区项目总结与分享

开源之夏是由中科院软件所“开源软件供应链点亮计划”发起并长期支持的一项暑期开源活动&#xff0c;旨在鼓励在校学生积极参与开源软件的开发维护&#xff0c;培养和发掘更多优秀的开发者&#xff0c;促进优秀开源软件社区的蓬勃发展&#xff0c;助力开源软件供应链建设。 官…

【正点原子STM32连载】第五十六章 DSP BasicMath实验 摘自【正点原子】APM32F407最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html## 第五…

ChinaSoft 论坛巡礼 | 新兴系统软件论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

算法 全排列的应用

#include <iostream> #include <string>using namespace std;// 交换字符串中两个字符的位置 void swap(char& a, char& b) {char temp a;a b;b temp; }void fun(string str) {string a str.substr(0,4); int aa;sscanf(a.c_str(), "%d",…

Harmony 应用开发之size 脚本

作者&#xff1a;麦客奥德彪 在应用开发中&#xff0c;最终呈现在用户面前的UI&#xff0c;是用户能否继续使用应用的强力依据之一&#xff0c;在之前的开发中&#xff0c;Android 屏幕碎片化严重&#xff0c;所以出现了很多尺寸适配方案。 最小宽适配、百分比适配等等。 还有一…