ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OBS Studio 动态数组 darray 深度解析:C 风格可增长数组的 API、实现原理与源码级用法

OBS Studio 动态数组 darray 深度解析:C 风格可增长数组的 API、实现原理与源码级用法 OBS Studio 动态数组 darray 深度解析C 风格可增长数组的 API、实现原理与源码级用法【免费下载链接】obs-studioOBS Studio - Free and open source software for live streaming and screen recording项目地址: https://gitcode.com/GitHub_Trending/ob/obs-studio本文基于 OBS Studio 官方 API 参考文档reference-libobs-util-darray.rst系统讲解util/darray.h提供的动态数组dynamic array机制darray基础结构、DARRAY(type)类型宏、以及da_*系列宏的完整语义。结合libobs/util/darray.h的实现源码与libobs中的真实调用示例场景项管理、序列化缓冲、效果解析器等读者可以掌握这套“C 版 std::vector”的初始化/扩容/插入/移动语义以及为什么向函数传递动态数组时必须使用 typedef 而不是裸引用内部da成员。一、动态数组是什么C 语言中的 std::vectorOBS 的核心库 libobs 是纯 C 实现需要一种可动态增长的线性容器来承载源码中大量变化的数据集合场景项指针、效果参数、字节流缓冲等。官方文档docs/sphinx/reference-libobs-util-darray.rst对它的定义是Dynamically resizing arrays (a C equivalent to std::vector).引入方式只有一个头文件#include util/darray.h对应源码位于 libobs/util/darray.h。该头文件自身不依赖项目其他 CMake 目标仅包含bmem.h分配器、c99defs.h以及标准库string.h/stdlib.h/assert.h全部函数体都以static inline形式写在头文件内——这意味着所有操作都在编译期内联展开无调用开销。1.1 基础结构体 darray底层结构体极其简单struct darray { void *array; /* The array pointer. 元素缓冲区指针 */ size_t num; /* The number of items. 当前元素个数 */ size_t capacity;/* The capacity of the array. 已分配容量 */ };array指向以字节方式管理的一维缓冲区内部所有操作都按element_size * idx做偏移计算num当前有效元素数量capacity已分配容量元素个数。文档还定义了一个全局哨兵常量在 darray.h 中#define DARRAY_INVALID ((size_t)-1)凡是“按值查找”类操作da_find在找不到目标时返回DARRAY_INVALID即size_t的最大值调用方必须以此判断查找结果。1.2 DARRAY(type) 宏让容器“半类型安全”直接使用struct darray时所有元素都是void *编译器无法检查类型。因此头文件提供了DARRAY(type)宏darray.h#L438-L446#define DARRAY(type) \ union { \ struct darray da; \ struct { \ type *array; \ size_t num; \ size_t capacity; \ }; \ }这是一个匿名 union同一块内存既可以当作struct darray da交给底层darray_*内联函数操作又可以直接以type *array的形式按真实类型访问元素例如items.array[i]、items.num。各da_*宏的本质就是“计算sizeof(*(v).array)后转发给对应的darray_*内联函数”例如#define da_push_back(v, item) darray_push_back(sizeof(*(v).array), (v).da, item) #define da_erase(dst, idx) darray_erase(sizeof(*(dst).array), (dst).da, idx)源码注释也坦率地说明darray.h#L430-L436这种方式“仍然不是 100% 类型安全但比直接用 darray 好得多”并且作者刻意没有用巨型宏为每种类型生成类型安全的内联函数因为“那太乱”。此外头文件中还有一个可选的ENABLE_DARRAY_TYPE_TEST编译开关开启后da_push_back、da_find等写入型宏会在if (false)分支里构造一次“类型赋值”检查从而在不产生运行时代码的前提下让编译器在编译期发现类型不匹配C 使用autoC 使用typeof的 GNU 扩展见 darray.h#L468-L500。二、基本用法与官方示例文档给出的最小完整示例是“创建一个存放 0..9 的整数数组”/* creates an array of integers: 0..9 */ DARRAY(int) array_of_integers; da_init(array_of_integers); for (size_t i 0; i 10; i) da_push_back(array_of_integers, i); [...] /* free when complete */ da_free(array_of_integers);使用要点均来自原文档约定da_*宏的参数是动态数组值本身不要写array_of_integersda_push_back的第二个参数是指向数据的指针i宏内部执行一次memcpy因此不会保留你的局部变量地址生命周期结束必须调用da_free它会释放缓冲区并把三个成员全部归零数组可再次da_init复用。2.1 作为函数参数用 typedef 而不是取 da 成员的引用文档明确指出把动态数组作为参数传给函数时推荐先声明一个 typedeftypedef DARRAY(int) int_array_t; void generate_integers(int_array_t *integers, int start, int end) { for (int i start; i end; i) da_push_back(*integers, i); } [...] int_array_t array_of_integers; da_init(array_of_integers); generate_integers(array_of_integers, 0, 10); /* free when complete */ da_free(array_of_integers);替代方案是把动态数组装进一个普通结构体再传结构体指针。IMPORTANT NOTE原文档的重要警告必须遵守虽然技术上可以直接接收内部darray结构通过da成员并在函数内部再用DARRAY宏重新声明变量但这样做不安全且不被推荐。典型风险是函数内部的类型声明与调用方实际传入的动态数组类型不一致时编译器无法发现会造成内存访问错误越界、错误偏移。typedef 或容器结构体能让“数组类型”随指针一起传播从根本上消除这类错误。2.2 仓库中的真实 typedef 用法libobs 源码中大量使用了上述 typedef 模式可以印证文档的推荐写法场景项指针数组typedef DARRAY(struct obs_scene_item *) obs_scene_item_ptr_array_t;libobs/obs-scene.c#L36字节缓冲DARRAY(uint8_t) bytes;用于序列化输出libobs/util/array-serializer.h#L26-L29效果解析器的参数/变量数组libobs/graphics/effect-parser.h#L31-L32、效果参数数组libobs/graphics/effect.h#L27-L28属性数组typedef DARRAY(struct obs_property *) obs_property_da_t;libobs/obs-properties.c#L359性能分析器条目数组libobs/util/profiler.h#L62。一个非常典型的真实用法是obs-scene.c中的remove_all_itemslibobs/obs-scene.c#L206-L230先用da_init初始化一个指针数组持锁遍历场景项链表并da_push_back收集待删除项解锁后再按items.array[i]逐个释放最后da_free。这个“先收集、后在锁外释放”的模式正是指针型 DARRAY 最常见的用途。三、完整 API 参考按功能分组以下逐一继承官方文档中列出的全部da_*宏并在每条之后结合 libobs/util/darray.h 的实现补充语义细节。所有宏都遵循“传值不传引用”的约定。3.1 生命周期与容量管理宏原型说明da_initvoid da_init(da)初始化arrayNULL, num0, capacity0darray.h#L47-L52da_freevoid da_free(da)释放缓冲区并重置为初始状态释放走bfree见下da_alloc_sizesize_t da_alloc_size(v)返回sizeof(*(v).array) * v.num即已存元素占用的字节数num × 元素大小注意它不是capacity对应的已分配缓冲区总大小da_reservevoid da_reserve(da, size_t capacity)预留容量。实现上若新容量不大于当前capacity则直接返回否则bmalloc新缓冲区、memcpy已有num个元素后释放旧缓冲darray.h#L80-L95da_resizevoid da_resize(da, size_t new_size)调整num扩大时对新增部分memset清零文档表述为 “Resizes the dynamic array with zeroed values”缩小时只改num不动缓冲区关于分配器所有缓冲都通过 libobs 的通用内存接口bmalloc/bfree分配libobs/util/bmem.h#L34-L36。这使得动态数组能统一接入 libobs 的内存追踪/调试分配器。3.2 容量增长策略翻倍扩容理解da_push_back等写入操作必须先理解darray_ensure_capacitydarray.h#L97-L116new_cap (!dst-capacity) ? new_size : dst-capacity * 2; if (new_size new_cap) new_cap new_size;即首次分配时按需求量精确分配此后按容量翻倍增长且保证不小于需求量。这与std::vector的常见增长策略一致从而摊还了da_push_back的均摊 O(1) 复杂度。文档没有单独列出一个“ensure_capacity”宏但从源码结构看它是所有写入路径push/insert/resize的共同底层。另外注意darray_ensure_capacity中memcpy的长度用的是element_size * dst-capacity旧容量的满拷贝而darray_reserve用element_size * dst-num——前者在“容量大于数量”时多拷贝了尾部未初始化字节功能上无害新缓冲区随后按新容量使用属于实现层面的宽松处理。3.3 读取宏原型说明da_endvoid *da_end(da)返回最后一个元素的指针数组为空时返回NULLdarray.h#L72-L78da_findsize_t da_find(da, const void *item_data, size_t starting_idx)从starting_idx起按memcmp逐元素全量比较找不到返回DARRAY_INVALID。实现内含assert(idx da-num)darray.h#L170-L183直接按索引访问则完全绕开宏v.array[i]类型化视图或v.da.array 偏移裸视图。这也是为什么DARRAY(type)的 union 设计有价值——索引访问是类型安全的。3.4 追加push宏原型说明da_push_backsize_t da_push_back(da, const void *data)尾部追加返回新元素索引。实现是num后ensure_capacity再memcpy到darray_enddarray.h#L185-L191da_push_back_newvoid *da_push_back_new(da)追加一个清零元素并返回其指针适合“先取指针、再逐字段填充”的用法如构建obs_property列表da_push_back_arraysize_t da_push_back_array(da, const void *src_array, size_t item_count)一次性批量追加返回首批新元素的索引。注意item_count是元素个数实现走darray_resize扩容后单次memcpy且对dstNULL/src_arrayNULL/num0有防御darray.h#L204-L218da_push_back_dasize_t da_push_back_da(da, src)把一个 DARRAY 的全部元素追加到另一个后面源码提供等价于push_back_array(src.array, src.num)见 darray.h#L220-L223关于da_push_back_new有一段实现细节值得注意在 GCC 下它被重写为一个 GCC 语句表达式宏因为源码注释说明 “GCC 12 with -O2 generates a warning -Wstringop-overflow in da_push_back_new, which could be false positive”darray.h#L512-L525。这说明该宏需要处理真实的现代编译器告警场景。3.5 插入insert宏原型说明da_insertvoid da_insert(da, size_t idx, const void *data)在idx处插入若idx num则退化为da_push_back否则memmove右移后续元素。含assert(idx dst-num)darray.h#L225-L244da_insert_newvoid *da_insert_new(da, size_t idx)在idx处插入清零元素并返回指针idx num时等价da_push_back_newdarray.h#L246-L263da_insert_arrayvoid da_insert_array(dst, size_t idx, src, size_t n)批量插入先resize(num n)再memmove腾位、memcpy写入darray.h#L265-L280da_insert_davoid da_insert_da(da_dst, size_t idx, da_src)把一个 DARRAY 插到另一个的指定位置darray.h#L282-L286所有插入操作都会触发num - idx量级的memmove复杂度 O(n)对大数组频繁在中间插入应权衡改用“尾部追加 da_move_item/da_swap重排”的方式。3.6 删除erase / pop宏原型说明da_erasevoid da_erase(da, size_t idx)删除指定索引元素memmove前移后续元素idx num时静默返回另有assert(idx dst-num)保护darray.h#L288-L297da_erase_itemvoid da_erase_item(da, const void *item_data)先da_find再da_erase删除第一个值匹配的项darray.h#L299-L304da_erase_rangevoid da_erase_range(da, size_t start_idx, size_t end_idx)删除[start_idx, end_idx)半开区间单元素时退化为da_erase删除全部时直接num 0darray.h#L306-L330da_pop_backvoid da_pop_back(da)删除末尾元素实现上就是da_erase(num-1)da_pop_frontvoid da_pop_front(da)删除首元素源码提供darray.h#L332-L338da_clearvoid da_clear(da)仅置num 0保留缓冲区容量适合“清空复用”场景darray.h#L118-L121删除同样不回收容量反复 erase 不会缩小capacity只有da_free才真正归还内存。3.7 复制 / 移动 / 合并 / 拆分宏原型说明da_copyvoid da_copy(da_dst, da_src)值拷贝源为空时释放目标否则 resize 目标后整段memcpydarray.h#L145-L153da_copy_arrayvoid da_copy_array(da, const void *src_array, size_t size)从普通数组指针整体拷贝da_movevoid da_move(da_dst, da_src)无分配转移先da_free(dst)再把src的整个darray结构memcpy过来并将src三成员清零——“move 语义”在 C 中的实现darray.h#L161-L168da_joinvoid da_join(da_dst, da_src)把src全部追加到dst末尾并da_free(src)调用后src已释放不可再使用darray.h#L348-L352da_splitvoid da_split(da_dst1, da_dst2, da_src, size_t split_idx)按索引把src拆成两段dst1得到[0, split_idx)dst2得到[split_idx, num)。两个目标若非空会先被释放被拆分的src本身不受影响darray.h#L354-L3763.8 元素重排宏原型说明da_move_itemvoid da_move_item(da, size_t src_idx, size_t dst_idx)把元素从src_idx搬到dst_idx中间元素整体memmove。实现中需要一块element_size大小的临时内存分配失败会bcrash(darray_move_item: out of memory)darray.h#L378-L403da_swapvoid da_swap(da, size_t idx1, size_t idx2)交换两个索引处的值同样是malloc临时块 三次memcpy失败时bcrashdarray.h#L405-L427注意da_move_item/da_swap与其余所有操作不同——它们会临时malloc一块堆内存元素大小。从源码结构看这是为了避免元素自重叠memmove的复杂度。四、实现层面的几个关键事实综合 libobs/util/darray.h 全文可以提炼出以下对使用者最重要的实现事实全部为static inline 宏转发。头文件头部注释写道 “Specifying size per call with inline maximizes compiler optimizations”每次调用显式传入元素大小 内联最大化编译器优化。da_*宏每次都会计算sizeof(*(v).array)因此同一元素类型的访问路径完全静态化。字节寻址核心是darray_item(uint8_t *)da-array element_size * idxdarray.h#L67-L70。da_find的按值比较、da_erase_item的删除、da_insert的腾位全部建立在其上。调试断言内建da_find、da_insert*、da_erase、da_erase_range、da_pop_*都带assert边界检查如assert(idx dst-num)、assert(end start)在 Debug 构建中越界访问会立即暴露。内存归属缓冲经由bmalloc/bfreelibobs/util/bmem.h而da_move/da_join/da_split这类“跨数组”操作决定了调用方对释放责任的划分——尤其da_join会释放源数组、da_move会把源数组置零。C/C 双语言兼容extern C包裹加上da_type_test中针对__cplusplus的两套 GNU 语句表达式实现说明同一套头文件在 libobsC与 frontendC中都被使用。五、测试与验证仓库为 darray 提供了 cmocka 单元测试test/cmocka/test_darray.c。当前的基础用例验证da_push_back_array后num 1且缓冲区内容与源字节一致DARRAY(uint8_t) testarray; da_init(testarray); uint8_t t 1; da_push_back_array(testarray, t, sizeof(uint8_t)); assert_int_equal(testarray.num, 1); assert_memory_equal(testarray.array, t, 1); da_free(testarray);注意该测试中第二参数是sizeof(uint8_t)即把“1 个字节”当作批量长度传入的用法结合darray_push_back_array把num参数解释为元素个数的实现阅读测试时应以 darray.h#L204-L218 的语义为准。测试构建入口在 test/cmocka 目录可作为回归验证 darray 行为变更的参照。六、使用建议小结结合文档约定与 libobs 源码中的成熟用法给出实践清单声明一律DARRAY(T) name;da_init(name);退出路径保证da_free(name);da_free可重复安全调用释放后会置零传参用typedef DARRAY(T) T_array_t;传指针参考obs_scene_item_ptr_array_t等真实先例或用容器结构体封装不要把v.da当参数传递读取索引访问直接用v.array[i]/v.num取尾部指针用da_end(v)按值查找用da_find并以DARRAY_INVALID判失败构建复杂元素优先da_push_back_new/da_insert_new拿到清零指针后填字段避免栈上临时变量性能敏感路径已知规模时先da_reserve或批量da_push_back_array/da_insert_array减少翻倍扩容与多次memcpy需要“清空但保留容量”时用da_clear合并/拆分da_join会消耗源、da_move会清空源、da_split保留源——按各自语义管理释放责任避免 double-free 或悬挂使用。以上所有结论均以当前仓库 libobs/util/darray.h 的源码、docs/sphinx/reference-libobs-util-darray.rst 的官方说明以及 test/cmocka/test_darray.c 的测试用例为依据适用于本仓库OBS Studio 主分支中的 libobs 工具层若你在插件或前端代码中扩展此类容器建议沿用libobs既有的 typedef 容器结构体模式以保持与现有调用风格一致。【免费下载链接】obs-studioOBS Studio - Free and open source software for live streaming and screen recording项目地址: https://gitcode.com/GitHub_Trending/ob/obs-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表