2026/1/10 18:02:22
网站建设
项目流程
沈阳养老保险网站,装饰公司网站模板,建设公司取名字大全最新,建设银行辽宁分行报名网站注#xff1a;该文用于个人学习记录和知识交流#xff0c;如有不足#xff0c;欢迎指点。 一、指针和内存大小对齐是什么#xff1f;
1. 内存分配时的指针对齐#xff08;必须#xff09;#xff1a;也称为内存对齐
结论#xff1a;malloc/posix_memalign 等分配函数…注该文用于个人学习记录和知识交流如有不足欢迎指点。一、指针和内存大小对齐是什么1. 内存分配时的指针对齐必须也称为内存对齐结论malloc/posix_memalign 等分配函数返回的指针必须对齐无例外。核心依据① 硬件底层CPU 无法安全 / 高效访问非对齐地址x86 性能暴跌ARM 直接崩溃② C 语言标准强制要求所有合规的内存分配函数都需满足。补充默认对齐粒度32 位系统 8 字节64 位 16 字节posix_memalign 可手动指定更大粒度如内存池可用 32 / 64字节。2. 内存访问时的指针对齐看场景结论访问指针不是 “绝对必须对齐”取决于「数据类型 CPU 架构」。核心依据访问 char1 字节任何地址都满足 1 字节对齐无需额外要求访问 int4 字节/double8 字节x86 非对齐可访问但性能暴跌可能跨缓存行ARM 非对齐直接崩溃本质对齐要求 CPU “原生访问粒度”如 char对应1字节int 对应 4 字节double 对应 8 字节。3. 内存大小对齐非必须纯优化结论内存大小无需强制对齐仅高性能场景内存池 / SIMD/DMA需要。核心依据普通场景malloc (4) 大小就是 4 字节系统靠 “内部碎片” 管理不影响访问优化场景大小对齐到缓存行32/64 字节能让内存块结束地址落在对齐边界后续分配无碎片且数据不跨缓存行。4. 缓存行与内存访问的关系性能补充结论缓存行是 “缓存↔内存” 的传输单位32/64 字节和 “指针 / 大小对齐” 是互补关系而非冲突。核心依据① CPU 访问内存时先按缓存行批量加载数据到缓存不管数据大小比如读 1 字节 char 也会加载整行② 再从缓存按 “数据类型粒度”如 char1 字节、int4 字节读取到 CPU对齐的指针能让这一步无额外拆分开销。总结分配指针必须对齐硬件 标准要求访问指针char 随便int/double 建议对齐x86 慢、ARM 崩内存大小不用对齐高性能场景对齐到缓存行更优缓存行批量加载数据的单位对齐能让缓存访问无额外开销。疑问问1原生访问粒度是什么核心维度具体说明定义CPU 从缓存 / 内存读取单个基础数据类型时硬件层面的最小字节单位由 CPU 架构 数据类型决定。本质CPU 指令操作数据的 “天然粒度”是硬件级的访问规则而非软件层面的优化。典型示例x86char→1 字节、int→4 字节、double→8 字节和数据类型的大小 / 对齐要求完全一致。关键关联访问指针需匹配对应数据类型的原生访问粒度即对齐否则 x86 性能可能暴跌、ARM 直接崩溃tips对齐表示是整数倍的意思问2内存分配时的指针对齐跟内存访问时的指针对齐规则不同对比维度内存分配时的指针对齐内存访问时的指针对齐规则目标兜底性要求保证指针适配所有数据类型的访问基础精准性要求仅匹配当前访问的数据类型需求对齐粒度统一粗粒度32 位 8 字节 / 64 位 16 字节或手动指定如 32 字节按需细粒度char1 字节、int4 字节、double8 字节随数据类型变化强制程度绝对强制malloc/posix_memalign 等函数必满足违反则分配失败 / 行为未定义非绝对强制在x86中-char 无对齐限制-int/double 非对齐访问不导致程序崩溃比如x86中:char arr[1024];*(int*)(arr[1]) 5;违规后果直接导致内存分配失败如 posix_memalign 返回错误码或指针不可用x86仅性能暴跌针对 int/double可能存在跨行读取ARM直接崩溃针对 int/doublechar 无任何后果问3结构体中隐含的指针对齐成员级对齐结构体每个成员按自身类型的对齐要求对齐如 int→4 字节、double→8 字节编译器会在成员间补 “填充字节”整体级对齐结构体总大小向上对齐到 “最大成员对齐数” 的整数倍保证结构体数组访问时每个元素都对齐#include stdio.h #include stddef.h // 用于offsetof宏验证偏移量 // 核心结构体数组前/后都补填充结构体末尾也补填充 struct AlignFullExample { // 成员1char触发数组前填充 char c; // 对齐数1占0x001字节 // 数组前补3字节填充0x01~0x03→ 让int数组满足4字节对齐 // 核心int数组前后都有填充 int arr[2]; // 对齐数4起始0x04占0x04~0x0B2×48字节内部连续无填充 // 数组后补4字节填充0x0C~0x0F→ 让double满足8字节对齐 // 成员2double触发数组后填充 double d; // 对齐数8起始0x10占0x10~0x178字节 // 成员3char触发结构体末尾填充 char e; // 对齐数1起始0x18占0x181字节 // 结构体末尾补7字节填充0x19~0x1F→ 让总大小对齐到最大对齐数8 }; int main() { // 验证各成员的内存偏移量起始地址 printf( 成员偏移量字节\n); printf(char c : %zu\n, offsetof(struct AlignFullExample, c)); printf(int arr[2] : %zu\n, offsetof(struct AlignFullExample, arr)); printf(double d : %zu\n, offsetof(struct AlignFullExample, d)); printf(char e : %zu\n, offsetof(struct AlignFullExample, e)); // 验证结构体总大小 printf( 结构体总大小 \n); printf(AlignFullExample size: %zu 字节\n, sizeof(struct AlignFullExample)); return 0; }sizeof(struct AlignFullExample )会得到 32 问4CPU跨缓存行读取时什么意思当你要访问的数据跨越了两个缓存行的边界时CPU 需要从内存加载两个缓存行才能拿到完整数据这种情况就叫 “跨行读取”。举例缓存行 64 字节缓存行 1 范围0x1000 ~ 0x1040 不含0x1040缓存行 2 范围0x1040 ~ 0x1080若要访问的数据是 “0x103C ~ 0x1043”比如一个 8 字节的 double0x103C 不对齐8字节x86不会崩溃ARM会崩溃这个数据同时占了缓存行 1 和缓存行 2 → CPU 必须加载这两个缓存行就是 “跨行读取”。若要访问的数据是 “0x103C ~ 0x1040”比如一个 4 字节的 int0x103C 对齐4字节x86和ARM都不会崩溃这个数据同时占了缓存行 1 和缓存行 2 → CPU 必须加载这两个缓存行就是 “跨行读取”。#include stdio.h #include stdint.h // 强制按64字节对齐保证起始地址是64的整数倍先锚定缓存行边界 __attribute__((aligned(64))) char buf[128]; int main() { // 1. 确定缓存行边界buf起始地址0x100064字节对齐 // 缓存行10x1000 ~ 0x1040缓存行20x1040 ~ 0x1080 // 2. 取跨缓存行的对齐地址0x103C4字节对齐满足int访问要求 uint32_t* ptr (uint32_t*)(buf 60); // buf60 0x1000 60 0x103C // 3. 跨行读取ptr指向的int4字节范围是0x103C ~ 0x1040 // 前3字节在缓存行10x103C~0x1040最后1字节在缓存行20x1040 *ptr 0x12345678; // 写入操作触发跨行读取写入 printf(写入值0x%x\n, *ptr); // 读取验证 return 0; }二、内存池是什么内存池Memory Pool是一种预分配 复用的内存管理机制程序启动 / 初始化阶段提前向操作系统申请一块连续的大块内存内存池后续程序需要分配内存时直接从内存池中 “切割” 小块使用无需频繁调用系统调用如malloc/free、new/delete内存释放时并非立即归还给操作系统而是放回内存池的空闲列表供后续复用当内存池耗尽时可按需扩容再次向系统申请大块内存程序退出时一次性释放整个内存池。本质是将 “频繁的小内存系统调用” 转化为 “用户态的内存块复用”核心目标是提升效率、减少碎片。三、内存池解决了什么问题频繁使用malloc/free等原生内存分配方式会带来以下问题内存池可针对性解决问题类型原生分配的痛点内存池的解决方案内存碎片频繁分配 / 释放不同大小的内存块会导致内存空间被分割成大量不连续的小碎片外部碎片即使总空闲内存足够也无法分配大块连续内存预分配大块连续内存内部按规则分割 / 复用避免内存空间碎片化定长池甚至可完全消除外部碎片分配效率低malloc/free需要陷入内核态系统调用且需遍历空闲链表、计算分配位置频繁调用开销极大内存池的分配 / 释放是用户态操作仅操作空闲链表指针无需内核态切换效率提升 10~100 倍缓存友好性差原生分配的内存地址随机CPU 缓存命中率低预分配的连续内存更符合 CPU 缓存的局部性原理提升内存访问速度内存泄漏难排查原生分配的内存分散难以跟踪分配 / 释放轨迹内存池可统一管理所有分配的内存块便于统计、监控、排查泄漏分配粒度失控原生分配可能因对齐、元数据占用导致实际分配内存大于需求如分配 1B 实际占 8B可定制化分配粒度如定长池严格按固定大小分配减少内存浪费虚拟内存四、内存池的实现一等长内存池频繁分配 / 释放固定大小的对象如链表节点、线程池任务、网络连接对象是最简单、最高效的内存池实现。每次分配相同大小的内存块疑问问1怎么知道下一块可用内存的地址内存块的前8/4个字节指针大小存放下一块可用的内存块起始地址。*(char **)ptr (char*)ptr block_size;问2释放完的内存块如何再次使用释放完之后将当前内存块的首部指向free_ptr,然后将free_ptr指向当前内存块void memp_free(mempool_t *m, void *ptr) { *(char **)ptr m-free_ptr; m-free_ptr (char *)ptr; m-freecount; }问3block_size 应该取多大答依据内存对齐规则取2的n次幂最小取8。二大小块分配的内存池小块小于内存页MP_PAGE_SIZE4096大块大于等于内存页直接调用malloc分配malloc会直接调用mmap分配整页内存无碎片、管理简单。1. 内存地址对齐/** * 指针对齐宏将指针p按alignment字节对齐转换为size_t计算后转回void* * 作用保证分配的内存地址是对齐数的整数倍提升访问效率 */ #define mp_align_ptr(p, alignment) (void *)((((size_t)p) (alignment - 1)) ~(alignment - 1))2. 内存分配大小对齐指针对齐了内存大小的分配也跟着对齐了毕竟后面也用不到/** * 数值对齐宏将数值n向上取整到alignment的整数倍 * 原理(n 对齐数-1) 抵消余数再 ~(对齐数-1) 清零余数位 * 示例mp_align(24,32) (2431) ~31 55 0xffffffe0 32 */ #define mp_align(n, alignment) (((n) (alignment - 1)) ~(alignment - 1))3. 小块结构体字段名字段类型核心含义lastunsigned char *当前内存页中已分配内存的末尾地址下一次小内存分配的起始位置endunsigned char *当前内存页的结束地址内存页最大可用地址end - last为剩余可用空间nextstruct mp_node_s *链表指针串联多个小内存页首个页不足时新建页failedsize_t当前小块分配失败次数没有足够的可用空间提供给外部失败4 次时后续遍历小块分配跳过小块提升遍历效率4. 大块结构体字段名字段类型核心含义nextstruct mp_large_s *链表指针串联所有大内存块管理节点复用节点避免频繁 malloc/freeallocvoid *指向通过malloc分配的大内存块4095 字节的起始地址大块结构体创建所需内存由小块提供 alloc指向的内存由malloc提供5. 内存池结构体字段名字段类型核心含义maxsize_t小内存块最大阈值取创建时指定size和4095的较小值currentstruct mp_node_s *指向当前可分配小内存的页节点优化分配效率避免从头遍历largestruct mp_large_s *指向大内存块管理链表的头节点head[0]struct mp_node_s柔性数组指向首个小内存页节点内存池创建时初始化的第一个页疑问问1指针要对齐1. 极致提升 CPU 访问效率内存池的核心诉求内存池的典型场景是「高频分配、高频访问」比如 Nginx 每秒处理数万请求每个请求都要分配 / 访问内存而 CPU 访问内存的效率完全依赖地址对齐CPU 读取内存不是 “逐字节”而是按「缓存行 / 字长」批量读取比如 64 位 CPU 一次读 8 字节AVX2 指令一次读 32 字节若指针地址是 32 字节对齐MP_ALIGNMENT32CPU 一次就能读取完整的 32 字节数据无需拼接若地址不对齐CPU 需要读 2 次内存再拼接数据耗时翻倍高频场景下这个损耗会被无限放大。举个具体例子缓存行为32字节的情况下地址 0x100032 对齐读 32 字节 → 1 次完成地址 0x1008非 32 对齐读 32 字节需要从 0x1008 读到 0x1028 → 跨 2 个 32 字节块CPU 读 2 次 拼接耗时翻倍。2. 满足硬件 / 指令的强制要求内存池常被用于需要和硬件交互的场景如网络编程、音视频处理SIMD 指令如 Intel AVX2、ARM NEON强制要求内存地址是 32/64 字节对齐否则指令执行失败DMA 控制器网卡 / 磁盘 IO从内存读取数据时要求地址是 32 字节对齐否则拒绝传输多核缓存对齐的地址更容易命中 CPU 缓存缓存行通常是 32/64 字节进一步提升性能。不对齐访问ARM会崩溃如果指针地址不对齐这些硬件 / 指令会直接报错程序崩溃 —— 这不是 “性能问题”而是 “可用性问题”。问2alignment的取值维度8 字节对齐malloc默认16 字节对齐32 字节对齐64 字节对齐CPU 访问效率满足 64 位 CPU 基础字长8 字节普通访问高效但跨缓存行概率高匹配部分 CPU 缓存行16 字节缓存命中率略高匹配主流 CPU 缓存行32 字节缓存命中率最高数据小于32字节时无跨行匹配高端 CPU 缓存行64 字节极致缓存效率但小数据也占整行内存利用率最高碎片最少分配 10 字节仅浪费 6 字节较高分配 10 字节浪费 6 字节24 字节浪费 8 字节中等分配 10/24 字节均浪费 22 字节最低碎片最多分配 10/24/32 字节均浪费 54/40/32 字节硬件兼容性仅满足基础要求x86 普通程序不支持 SIMD / 高端 DMA支持部分 ARM 架构 / 简单 DMA不支持 AVX2支持主流 SIMDAVX2、DMA网卡 / 磁盘、ARM 高性能场景支持极致硬件AVX512、高端显卡 / AI 芯片兼容性最全代码复杂度无额外复杂度无额外复杂度无额外复杂度无额外复杂度典型适用场景普通小程序、嵌入式低内存场景一般高性能程序如普通后台服务主流高性能组件Nginx/Redis/ 数据库极致高性能场景AI / 音视频编解码 / 高频金融交易问3内存分配的大小必须要对齐吗不是必须的只需满足指针对齐内存对齐就可以让大部分系统不崩溃了。这里只是提供接口代码中实际上未使用。五、代码实现1. 等长分配// 等长分配 #include stdio.h #include stdlib.h #include string.h #define MEM_PAGE_SIZE 0x1000 // typedef struct mempool_s { int blocksize; int freecount; char *free_ptr; char *mem; } mempool_t; // sdk -- varchar(32); // // 2^n, page_size 4096, block_size: 16, 32, 64, 128 int memp_create(mempool_t *m, int block_size) { if (!m) return -1; m-blocksize block_size; m-freecount MEM_PAGE_SIZE / block_size; m-mem (char *)malloc(MEM_PAGE_SIZE); // if (!m-mem) { // NULL return -2; } memset(m-mem, 0, MEM_PAGE_SIZE); // m-free_ptr m-mem; int i 0; char *ptr m-mem; for (i 0; i m-freecount; i) { *(char **)ptr ptr block_size; ptr ptr block_size; } *(char **)ptr NULL; return 0; } void memp_destory(mempool_t *m) { if (!m) return; free(m-mem); } void *memp_alloc(mempool_t *m) { if (!m || m-freecount 0) return NULL; void *ptr m-free_ptr; m-free_ptr *(char **)ptr; m-freecount--; return ptr; } void memp_free(mempool_t *m, void *ptr) { *(char **)ptr m-free_ptr; m-free_ptr (char *)ptr; m-freecount; } // memp_strcpy // memp_memcpy int main() { mempool_t m; memp_create(m, 32); char *p1 memp_alloc(m); printf(memp_alloc : %p\n, p1); sprintf(p1, hello); printf(%s\n, p1); memp_free(m, p1); void *p2 memp_alloc(m); printf(memp_alloc : %p\n, p2); void *p3 memp_alloc(m); printf(memp_alloc : %p\n, p3); memp_free(m, p2); void *p4 memp_alloc(m); printf(memp_alloc : %p\n, p4); memp_free(m, p2); }2. 大小块分配部分接口介绍1. mp_alloc返回的地址是对齐的对last指针取整了2. mp_nalloc: 返回的地址不一定是对齐的3. mp_alloc_large使用malloc分配对齐字节数默认为84. mp_memalign使用posix_memalign分配对齐字节数可指定5. mp_reset_pool内存池重置large-alloc全部释放并置为NULL同时将pool-large置为NULLlarge节点是由小块分配的现在小块复位了相当于large被释放了)node节点小块的last全部复位表示节点还有size空间可用。current节点置为head同时各node节点的fail全部置为0#include stdlib.h #include stdio.h #include string.h #include unistd.h #include fcntl.h // 大小快分配 #define MP_ALIGNMENT 32 #define MP_PAGE_SIZE 4096 #define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE-1) #define mp_align(n, alignment) (((n)(alignment-1)) ~(alignment-1)) #define mp_align_ptr(p, alignment) (void *)((((size_t)p)(alignment-1)) ~(alignment-1)) struct mp_large_s { struct mp_large_s *next; void *alloc; }; struct mp_node_s { unsigned char *last; unsigned char *end; struct mp_node_s *next; size_t failed; }; struct mp_pool_s { size_t max; struct mp_node_s *current; struct mp_large_s *large; struct mp_node_s head[0]; }; struct mp_pool_s *mp_create_pool(size_t size); void mp_destory_pool(struct mp_pool_s *pool); void *mp_alloc(struct mp_pool_s *pool, size_t size); void *mp_nalloc(struct mp_pool_s *pool, size_t size); void *mp_calloc(struct mp_pool_s *pool, size_t size); void mp_free(struct mp_pool_s *pool, void *p); struct mp_pool_s *mp_create_pool(size_t size) { struct mp_pool_s *p; int ret posix_memalign((void **)p, MP_ALIGNMENT, size sizeof(struct mp_pool_s) sizeof(struct mp_node_s)); if (ret) { return NULL; } p-max (size MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL; p-current p-head; p-large NULL; p-head-last (unsigned char *)p sizeof(struct mp_pool_s) sizeof(struct mp_node_s); p-head-end p-head-last size; p-head-failed 0; return p; } void mp_destory_pool(struct mp_pool_s *pool) { struct mp_node_s *h, *n; struct mp_large_s *l; for (l pool-large; l; l l-next) { if (l-alloc) { free(l-alloc); } } h pool-head-next; while (h) { n h-next; free(h); h n; } free(pool); } void mp_reset_pool(struct mp_pool_s *pool) { struct mp_node_s *h; struct mp_large_s *l; for (l pool-large; l; l l-next) { if (l-alloc) { free(l-alloc); } } pool-large NULL; for (h pool-head; h; h h-next) { h-last (unsigned char *)h sizeof(struct mp_node_s); h-failed 0; //新增 } pool-current pool-head; // 新增 } static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) { unsigned char *m; struct mp_node_s *h pool-head; size_t psize (size_t)(h-end - (unsigned char *)h); int ret posix_memalign((void **)m, MP_ALIGNMENT, psize); if (ret) return NULL; struct mp_node_s *p, *new_node, *current; new_node (struct mp_node_s*)m; new_node-end m psize; new_node-next NULL; new_node-failed 0; m sizeof(struct mp_node_s); m mp_align_ptr(m, MP_ALIGNMENT); new_node-last m size; current pool-current; for (p current; p-next; p p-next) { if (p-failed 4) { // current p-next; } } p-next new_node; pool-current current ? current : new_node; return m; } static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) { void *p malloc(size); if (p NULL) return NULL; size_t n 0; struct mp_large_s *large; for (large pool-large; large; large large-next) { if (large-alloc NULL) { large-alloc p; return p; } if (n 3) break; } large mp_alloc(pool, sizeof(struct mp_large_s)); if (large NULL) { free(p); return NULL; } large-alloc p; large-next pool-large; pool-large large; return p; } void *mp_memalign(struct mp_pool_s *pool, size_t size, size_t alignment) { void *p; int ret posix_memalign(p, alignment, size); if (ret) { return NULL; } struct mp_large_s *large mp_alloc(pool, sizeof(struct mp_large_s)); if (large NULL) { free(p); return NULL; } large-alloc p; large-next pool-large; pool-large large; return p; } void *mp_alloc(struct mp_pool_s *pool, size_t size) { unsigned char *m; struct mp_node_s *p; if (size pool-max) { p pool-current; do { m mp_align_ptr(p-last, MP_ALIGNMENT); if ((size_t)(p-end - m) size) { p-last m size; return m; } p p-next; } while (p); return mp_alloc_block(pool, size); } return mp_alloc_large(pool, size); // return mp_memalign(pool, size, alignment); 指定 posix_memalign 的分配内存地址对齐的字节数malloc默认对齐8 } void *mp_nalloc(struct mp_pool_s *pool, size_t size) { unsigned char *m; struct mp_node_s *p; if (size pool-max) { p pool-current; do { m p-last; if ((size_t)(p-end - m) size) { p-last msize; return m; } p p-next; } while (p); return mp_alloc_block(pool, size); } return mp_alloc_large(pool, size); } void *mp_calloc(struct mp_pool_s *pool, size_t size) { void *p mp_alloc(pool, size); if (p) { memset(p, 0, size); } return p; } // 仅支持大块释放 void mp_free(struct mp_pool_s *pool, void *p) { struct mp_large_s *l; for (l pool-large; l; l l-next) { if (p l-alloc) { free(l-alloc); l-alloc NULL; return ; } } } int main(int argc, char *argv[]) { int size 1 12; struct mp_pool_s *p mp_create_pool(size); int i 0; for (i 0;i 10;i ) { void *mp mp_alloc(p, 512); // mp_free(mp); } //printf(mp_create_pool: %ld\n, p-max); printf(mp_align(123, 32): %d, mp_align(17, 32): %d\n, mp_align(24, 32), mp_align(17, 32)); //printf(mp_align_ptr(p-current, 32): %lx, p-current: %lx, mp_align(p-large, 32): %lx, p-large: %lx\n, mp_align_ptr(p-current, 32), p-current, mp_align_ptr(p-large, 32), p-large); int j 0; for (i 0;i 5;i ) { char *pp mp_calloc(p, 32); for (j 0;j 32;j ) { if (pp[j]) { printf(calloc wrong\n); } printf(calloc success\n); } } //printf(mp_reset_pool\n); for (i 0;i 5;i ) { void *l mp_alloc(p, 8192); mp_free(p, l); } mp_reset_pool(p); //printf(mp_destory_pool\n); for (i 0;i 58;i ) { mp_alloc(p, 256); } mp_destory_pool(p); return 0; }