13
\$\开始组\$

这是我在大约30分钟内编写的第一个C程序,我想知道您是否可以给我一些改进方法的建议。我很擅长编程(大约5年的经验),但没有C语言的经验,所以我想我没有遵循正确的习惯用法和/或正确地做事。

注:我没有把它变成“通用的”,因为学习模板超出了这个小冒险的范围。

#包括<assert.h>#包括<stddef.h>#包括<stdlib.h>#包括<string.h>/**“int”的动态大小数组*/typedef结构{size_t容量;size_t长度;int*数据;}DynamicArray;/**将“value”附加到“DynamicArray”*/void dynamic_array_push(DynamicArray*arr,int值){if(arr->长度==arr->容量){int*new_data=malloc(arr->capacity*2*sizeof(int));memcpy(新数据,arr->data,arr->容量*大小(int));自由(arr->数据);arr->data=新数据;arr->容量*=2;}arr->data[arr->length]=值;arr->length++;}/**清理“DynamicArray”*/void dynamic_array_cleanup(DynamicArray*arr){free(arr->data);}DynamicArray dynamic_array_new(大小上限){DynamicArray arr={.容量=上限,.长度=0,.data=malloc(cap*sizeof(int)),};返回arr;}整型main(){DynamicArray arr=dynamic_array_new(2);//输入一些测试值对于(int i=0;i<30;i++){动态数组推送(&arr,i);}//测试数据输入是否正确for(int i=0;i<arr.length;i++){断言(i==arr.data[i]);}dynamic_array_cleanup(\arr);返回0;}
\$\端组\$
5
  • 6
    \$\开始组\$ C没有模板,这只是在C++中。 \$\端组\$ 评论 2023年12月12日23:07
  • 6
    \$\开始组\$ 将其转换为泛型的一种快速而肮脏的方法是将其包装在一个接受类型的宏中,您可以轻松创建动态数组适合你喜欢的任何类型。 \$\端组\$ 评论 2023年12月12日23:42
  • \$\开始组\$ 潜在溢出cap*sizeof(int)是一个漏洞。 \$\端组\$ 评论 2023年12月13日16:11
  • 2
    \$\开始组\$ @G.公司。Sliepen C确实有通用的它可以以类似C++模板的方式使用。 \$\端组\$ 评论 2023年12月14日22:29
  • \$\开始组\$ 我知道你不应该有像“谢谢”这样的评论,但认真地说,伙计们,感谢你们提供了大量非常有用的评论和答案。我希望我能接受多个答案!这真的是我在所有stackexchange中收到的最好的帖子,这真的让我很高兴! \$\端组\$
    – 扎奇亚
    评论 2023年12月15日15:54

5个答案5

重置为默认值
15
\$\开始组\$

谢谢你对多西根的评论。考虑将结构重命名为动态IntArray,然后我甚至不必阅读评论。或者,在您正在处理的用例中,有一个概念在业务领域中,例如Widget,可以是表示为int。然后我们会有一个DynamicWidgetArray(动态控件阵列).

幻数

一些代码库,例如绑定9,选择开始(某些)结构具有明显的魔术数字。这是一个调试帮助,因此库代码可以选择断言传入了预期类型的对象。

这完全是可选的,但您可以考虑采用这种做法。它倾向于使用gdb公司稍微容易一点。

libc函数已重新实现

int*new_data=malloc(arr->capacity*2*sizeof(int));memcpy(新数据,arr->data,arr->容量*大小(int));自由(arr->数据);arr->data=新数据;

这是复制realloc()呼叫。为了作者的缘故,不妨重用一个经过良好测试、有文档记录的函数。为了温和的读者,用熟悉的方式写一行而不是几行合同.

清理

dynamic_array_cleanup(动态阵列清理)free()非常好。考虑另外将这三个字段归零,所以我们没有悬空的指针一些应用程序尝试了一个无需使用的错误。当然,这是准确的长度容量free()后为零。

或者写0x牛肉为了某人检查记忆gdb公司.

公共API

您定义了一个已损坏的公共API,它不适合使用。

有时malloc()返回NULL。我知道,这很可悲。但这是生活的事实。图书馆作者必须应付的问题。C不通过异常提供错误报告。

每次调用时,代码必须验证malloc()是否成功。您应该记录malloc失败将被破坏调用进程,或者您应该将其状态返回给调用者,所以现在是打电话者的问题,要决定做什么。

乘以零

如果调用方无法为new()提供正数那么坏事就有可能发生。在free()之后回收旧结构也可能与此冲突。@马蒂厄姆。注意到

生长函数不适用于cap==0,因为0*2==0。max(上限*2,1)将起作用。

偶数上限*2+1就足够了。

\$\端组\$
  • \$\开始组\$ 谢谢你的建议。将田地归零是有意义的。在Rust中,如果我需要创建一个清理函数,我会直接获得对象的所有权,这将解决这个问题,所以我不习惯考虑它,哈哈。对于魔法数字,你是说每个结构都会在数字的开头有一个字段,然后我们可以在收到指针后检查魔法数字以检查类型,以验证它的类型是否正确?这很有趣。。。至于malloc的事,是的,我完全忘记了。。。 \$\端组\$
    – 扎奇亚
    评论 2023年12月13日15:58
  • 4
    \$\开始组\$ malloc公司/副本/自由的不仅仅是重新实现重新分配,它迫使人们做出最坏的行为。实际重新分配吸C的方式不需要函数++标准::矢量是(除非库+编译器在处理C++分配器API方面非常聪明),尤其是对于大型重新分配。 \$\端组\$ 评论 2023年12月14日1:33
  • \$\开始组\$ 我不想仅仅为一句小小的话添加一个答案,所以如果您能在您的答案中包含它,我将不胜感激:增长函数不适用于上限==0,因为0 * 2 == 0.最大值(上限*2,1)将起作用。 \$\端组\$ 评论 2023年12月14日8:23
14
\$\开始组\$

使用重新分配-它可以比malloc+copy+free更高效

与C++不同,C的分配器API并不完全糟糕:它有重新分配.
(不幸的是,没有便携式对齐的_分配,但是malloc公司/重新分配为您提供足够的内存,可用于任何标准类型最大对齐时间.MSVC有_对齐的_分配_对齐_分配,但我不知道有POSIX或GNU等效程序。)

如果现有分配结束后有空闲内存,重新分配只需增加现有分配,而无需复制任何内容。或者C库知道如何使用像Linux这样的系统调用mremap(mremap_MAYMOVE)(请参见手册页),它可以将其移动到新的虚拟地址,而无需实际复制物理页面。(这可能只有在页面开始分配时才有效,但这就是glibcmalloc公司用于大型分配。实际上,它保留了页面的前16个字节用于记帐数据,所以不幸的是,您得到了一个指针,该指针对齐16,但未对齐32,并且更宽。但没有不相关的数据表明埃玛先生会从它应该的地方消失。)格利布重新分配实际上使用mremap(mremap_MAYMOVE); 我与核实过斯特拉斯/a.输出在为测试增加循环计数之后主要的到20亿。希望是其他malloc公司实现在它们使用的平台上也同样聪明。

除了复制时间外,alloc+copy的另一个缺点是需要同时分配两个副本。对于一个5GiB的int数组,您将暂时拥有10GiB的“脏”匿名页面,这迫使操作系统查找大量额外页面。充其量只是丢弃一些可能有用的磁盘缓存,最坏的情况是在从其他进程中分页时会破坏交换空间。

打电话重新分配最糟糕的情况是手动调用malloc公司/会员/自由的,但可以许多的更好,尤其是对于大型分配。不要落入C的陷阱++标准::矢量陷入了增长时必须始终分配+复制+释放的困境。

作为GNU扩展,有一个重新登记它检查乘法溢出,如分配确实如此(但在增长时,新分配的空间不会为零)。(在glibc 2.26中提供。OpenBSD 5.6,FreeBSD 11.0。)Portable C应该手动执行此操作。

一些实现,包括GCC,将最大对象大小限制为PTRDIFF_max字节。如果您要手动检查,这可能是一个很好的限制,除非您真的关心如何弯曲实现限制,以及在32位程序中使用2GiB或更大的数组,例如,直到最大尺寸(_M)(格利布)malloc公司例如,如果您要求,32位x86 Linux将分配大于2GiB的阵列。在64位内核下,您拥有完整的4GiB地址空间。减法整数*首先减去原始指针,然后进行算术右移以缩放大小(int)(地脚螺栓),这样你可以得到一个负值ptrdiff时间而不是例如10亿,但如果您不运行任何依赖于它的代码,则可能不会发生任何中断。)

@查克斯指出ptrdiff时间可能比尺寸_t在希望允许对象达到SIZE_ MAX字节的实现中。(这样的实现必须在减去指针、除法或右移之前有效地加宽指针,也许可以借助于具有进位标志的ISA。)

所以也许#定义DYNAMIC_ARRAY_MAXBYTES(SIZE_MAX/2)作为实现定义极限的保守下限。我想如果你要用最大尺寸/2,没有必要涉及PTRDIFF_最大值,因为我怀疑有任何系统ptrdiff时间窄于尺寸_t但最大对象大小仍然是PTRDIFF_最大值如果你想对此感到疑惑,#定义DYNAMIC_ARRAY_MAXBYTES((PTRDIFF_MAX<=SIZE_MAX/2)?PTRDIFF_MAX:SIZE_MAX/2)如果需要,C实现可以任意设置一个较小的最大对象大小限制,但没有标准的宏来公开它。

带溢出和分配失败检查

#包括<stdint.h>#包括<stdbool.h>bool dynamic_array_push(DynamicArray*arr,int值){size_t cap=arr->容量;if(帽==arr->长度){上限*=2;//如果sizet的宽度至少与ptrdifftt的宽度一样,则不能溢出,因为它是无符号的。//但这在技术上并没有保证,所以在加倍之前可能会检查cap<PTRDIFF_MAX/sizeof(int)/2。(sizeof(int)==1在某些DSP上是可能的)如果(cap>PTRDIFF_MAX/sizeof(int)){cap=PTRDIFF_MAX/sizeof(int);//增长到典型C实现的最大对象大小if(盖<=arr->长度)返回false;}int*new_data=realloc(arr->data,cap*sizeof(int));if(!new_data){返回false;//原始数据仍然存在,但我们没有增长}arr->data=新数据;arr->容量=上限;}arr->data[arr->length]=值;arr->length++;返回true;}

我用了两次相同的常数(PTRDIFF_MAX/sizeof(int))而不是对照PTRDIFF_MAX/sizeof(int)/2在容量翻倍之前。这有望帮助编译器像x86-64 GCC那样只将一个64位常量放入寄存器(地脚螺栓).

在返回的现有API中空隙,你可以打电话中止()或是失败,扼杀整个过程。这可能适合于一个简单的程序,但最好是通过类似于动态数组推送或中止因此,失败原因行为的文档就在使用它的代码中,很容易在一个决定它有时想做其他事情的代码库中逐个搜索并开始替换它。


微基准测试:对于大型阵列,速度快2倍以上

将阵列大小从30亿增加到20亿整数s(8 GiB),然后将其读回,使用重新分配在我的系统上速度是原来的两倍多,页面错误减少了70倍,TLB未命中减少了9倍。(所有的页面错误都是“软错误”,而不是分页到磁盘。TLB未命中只是统计导致页面遍历的二级TLB未达标。)额外时间的很大一部分花费在内核中,将新页面归零

我在我的i7-6700k Skylake上使用双通道DDR4-2666 RAM,运行Arch Linux,内核6.5,用GCC 13.2.1编译,对这个旧版本进行了计时-O3-fno-plt(否-三月选项,尽管它不使用任何新指令,即使使用编译-march=本地。每次推送的大小分支都会破坏填充模式的自动矢量化。)我的能量_性能_参考平衡性能所以CPU只能运行3.9GHz。透明大页面已启用(与默认设置一样),碎片整理延迟+madwise(我们不会马德维斯调用,尽管我的系统有32 GiB RAM,但其中大部分是免费的,所以希望它能生成巨大的页面。)

$taskset-c 1 perf stat-etask-clock、上下文开关、cpu迁移、页面故障、周期、指令、uops_issued.any、dtlb_load_misses.miss_causes_a_walk、dtlb_store_misses.miss_causes.a_walk/发电机流量'的性能计数器统计信息/发电机-流量':5616.15毫秒任务时钟#已使用0.999个CPU17个上下文开关#3.027/秒0个cpu迁移#0.000/sec779398页故障#138.778 K/秒21628006730周期#3.851 GHz31442121299指令#1.45 insn/周期29540890921 uops_issued.any#5.260 G/秒1710792 dtlb_load_misses.miss_causes_a_walk#304.620 K/秒16175526 dtlb_store_misses.miss_causes_a_walk#2.880米/秒5.624499953秒3.514993000秒用户2.093666000秒系统
$taskset-c 1 perf stat-etask-clock、上下文开关、cpu迁移、页面故障、周期、指令、uops_issued.any、dtlb_load_misses.miss_causes_a_walk、dtlb_store_misses.miss_causes.a_walk/无发电机'的性能计数器统计信息/防发电机”:2459.69毫秒task-clock#1.000 CPU已使用8个上下文开关#3.252/秒0 cpu迁移#0.000/秒10601页故障#4.310 K/秒9517533410周期#3.869 GHz27308580581指令#2.87 insn/周期22551679420 uops_issued.any#9.169 G/秒1831019 dtlb_load_misses.miss_causes_a_walk#744.412 K/秒116378 dtlb_store_misses.miss_causes_a_walk#47.314 K/秒2.459971145秒1.57425.8万秒用户0.881689000秒系统

测试主要的两者都是一样的。的起点意味着当我们退出时,我们离增长点只有一半的距离。(我只是随机地使用不同的值,这不是一个建议,只是我碰巧用它进行了测试。)

int main(无效){DynamicArray arr=dynamic_array_new(3);//输入一些测试值对于(int i=0;i<2000000000;i++){动态数组推送(&arr,i);}//测试数据输入是否正确for(int i=0;i<arr.length;i++){断言(i==arr.data[i]);}dynamic_array_cleanup(&arr);返回0;}

它进行的系统调用是:

…慢速版本brk(空)=0x560b599ad000brk(0x560b599ce000)=0x560b 599ce1000brk(0x560b599fe000)=0x560b 599fe1000mmap(空,200704,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1,0)=0x7f3717dd9000brk(0x560b599ce000)=0x560b 599ce1000mmap(空,397312,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1,0)=0x7f3717b87000蒙图(0x7f3717dd9000200704)=0mmap(空,790528,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1,0)=0x7f3717ac6000蒙图(0x7f3717b87000,397312)=0...
…快速版本brk(空)=0x5567b45c9000brk(0x5567b45ea000)=0x5567b 45ea000mmap(NULL,200704,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1,0)=0x7fbc9f7d4000#切换到mmap,此时必须复制mremap(0x7fbc9f7d4000,200704,397312,mremap_MAYMOVE)=0x7fbc9f582000mremap(0x7fbc9f582000、397312、790528,mremap_MAYMOVE)=0x7fbc9f4c1000mremap(0x7fbc9f4c1000,790528,1576960,mremap_MAYMOVE)=0x7fbc9f340000mremap(0x7fbc9f340000,1576960,3149824,mremap_MAYMOVE)=0x7fbc9f03f000...

Glibc的默认值malloc公司从休息时的小分配开始(布雷克). 这大概只是在继续重新分配与alloc+复制导致一些堆转移(正如@Davislor的答案所描述的)相比,在空闲列表上留下一些分配的+脏空间,而不是返回给操作系统。(但哪些其他小的拨款可以使用。)所以这就是为什么我们看到两个布雷克分配(在初始brk(空)查找中断地址)从慢速版本与仅从重新分配切换到之前的版本甲基丙烯酸甲酯(复制一次,我想大小为150万B)。

有趣的是,埃玛先生会继续移动虚拟地址,从而强制对阵列的现有部分执行TLB无效。由于我们的工作负载一直附加到最后,因此这些失效不会导致额外的TLB未命中,除非是在最后一次增长后的最终读取过程中(在这种情况下,当它太大时,阵列的起点可能已经从TLB中移出)。它是一个单线程进程,因此不需要对其他内核进行TLB停机(处理器间中断)。在其他情况下,不利因素可能更大。

检查一下可能会很有趣/proc/<PID>/映射查看映射后是否有空闲的虚拟地址空间。如果没有,我们将看到@Davislor的回答中提到的堆转移效应,除非相反。如果有空间,那么不使用它就会错过优化。

对于512 MiB以上的大小,将增长因子降低到1.5可能是有意义的,尤其是在GNU/Linux等已知具有高效重新分配可以使用分页技巧。或者,根据您的使用情况,甚至可以使增长线性化,而不是指数化地超过某个阈值,如1 GiB或10亿整数的。更新N个可分页条目的工作量仍然是O(N),但常数因子是微小的但是,在每次使用,如果它像您希望的那样内联,以在非增长情况下提高效率。


可以进行部分内联:

在.h中静态内联//bool dynamic_array_push(DynamicArray*arr,int值){if(arr->容量==arr->长度){return dynamic_array_push__growth_needed(arr,value);//在单独的.c文件中定义}arr->data[arr->length]=值;arr->length++;返回true;}

这使得每个调用端的机器代码较小。它使每个调用方都成为非叶函数,但这已经是真的,因为增长路径调用重新分配.

我们可以将增长代码拆分为单独的函数,而不是复制arr->data[arr->length]=值;/arr->length++;那里没有经过价值作为一个隐语。但这是常见的在调用端是临时的且在推送后不需要的值。将其作为arg传递可以让调用方避免需要呼叫预留寄存器对于它,只需在一个call-clubered寄存器中计算它,因为它在函数调用后就死了。(我实际上还没有查看循环中的示例调用程序,看它有什么不同。这使得内联存储/增量代码的条件是内联比较,而不是__生长_需要慢速路径,我想大概是相等的。简单的调用者只推送随机数或循环另一个数组,无论如何可能不太具有代表性。)

\$\端组\$
  • \$\开始组\$ cap>PTRDIFF_MAX/sizeof(int)是错误的测试。考虑ptrdif_t可能具有2倍于尺寸_t.使用cap>SIZE_MAX/sizeof(int).prtdiff_t此任务中不需要。 \$\端组\$ 评论 2023年12月14日18:00
  • \$\开始组\$ @chux-ReinstateMonica:理论上ptrdiff时间可以更宽,但似乎不现实,除非某些实现为指向不同对象的指针定义指针减法行为,但将任何单个对象的大小限制为远小于地址空间。(或者使用非平面内存模型)。也许最小值(PTRDIFF_MAX,大小_MAX),因为有一些实现(如GCC),其中实现定义的最大对象大小为PTRDIFF_max字节。(这意味着编译器可以假设指针减法从未有符号溢出,包括之前合成孔径雷达对于sizeof>1) \$\端组\$ 评论 2023年12月15日23:01
  • \$\开始组\$ A类更广的 ptrdiff时间尺寸_t确保指针减法永远不会有符号溢出,即使对象大小接近最大尺寸. \$\端组\$ 评论 2023年12月16日2:37
9
\$\开始组\$

检查是否溢出

目前,扩大矢量的算法没有检测到容量是否会发生变化。实际上,在某些体系结构上,溢出尺寸_t得到内存不足错误之前(通常在32位内存模型上分配超过2GiB)。要么将旧尺寸与最大尺寸/2,或(因为它是无符号的,并且溢出被保证截断)检查新大小是否小于旧大小。

当心堆移位

假设您正在处理大数据,并将第一个兆字节读入向量。到目前为止,一切都很好。堆里有很多内存。空间不足,需要重新调整大小。算法检查堆并找到一个空闲块,超过您分配的第一个块的末尾。它分配2 MB,其中将整个阵列复制到新位置,并释放原始的1 MB。

您有超过2MB的数据,因此需要重新调整大小。该算法检查并找到一个大小为1MB的空闲块,然后是分配的2MB块,最后是一个大的空闲块。它需要4 MB,大于1 MB,因此它在当前块的右侧分配4 MB,将整个阵列复制到该块,然后释放2 MB块。现在有一个3 MB的空闲块,然后是一个4 MB的分配块。您需要重新调整大小。您现在需要8 MB,这对于第一个可用块来说太大了,所以您再次将阵列移到右侧,并释放前一个块以获得一个7 MB的块。每次都会发生这种情况,您将永远不会重用任何释放的内存。而且复制大型阵列的成本很高!

如果每次只将大小增加50%,而不是100%,会发生什么情况?经过五次迭代后,您将获得一个大小为原始大小8.125倍的空闲块,并且只需要一个大小是原始大小7.59倍的块,这样您就可以回到堆的开头,而不必继续将其进一步向右扩展。

当然,这里真正的解决方案是使用realloc(),它可以检测当前块是否可以在不移动它的情况下被放大。但这是一个需要注意的问题,尤其是在使用没有此功能的C++风格的内存管理时。

\$\端组\$
4
  • \$\开始组\$ 你解释原因的方式重新分配更好地讲得通。如果可以的话,在不移动内存的情况下重用现有内存似乎很棒。谢谢! \$\端组\$
    – 扎奇亚
    评论 2023年12月14日18:01
  • \$\开始组\$ 3条评论:1)解释得很好。2) 需要从分配开始2以获得50%的牵引力。3) “增长而不移动”可能只适用于访问堆的“纯”情况。干预代码(就像无害的打印())无论如何都可能导致必须重新定位块。 \$\端组\$ 评论 2023年12月14日22:49
  • \$\开始组\$ 除非在自由列表中保留大块(而不是将它们返回到操作系统),否则在再次使用之前,“堆开始”的虚拟地址空间将被取消映射一段时间。当您再次映射这些虚拟页面时,很可能会分配不同的物理页面来映射它们,因此重用对三级缓存命中没有帮助。(这对TLB命中没有帮助,因为这些TLB条目必须在取消映射时失效)。 \$\端组\$ 评论 2023年12月15日23:44
  • \$\开始组\$ 如果您担心使用一个这样的阵列无法增长到超过一半的虚拟地址空间,而这是您唯一要分配的东西,那么是的,一个好的重新分配,例如可以使用mremap(梅莫夫)或等效的是重要的,就像我在回答中所示,避免双重分配和复制的实际内存流量。或者就像你所说的,如果分配后没有任何内容,则增加现有的分配。(或者如果分配后+之前有足够的总空间,则分配+内存移动). 但是,如果你每次都照样复制,那么较小的增长因子似乎并不好。 \$\端组\$ 评论 2023年12月15日23:48
8
\$\开始组\$

几点(不要重复realloc()将比DIY版本或当前答案中的其他要点更好地满足需求。)

  1. int*new_data=malloc(arr->capacity*2*sizeof*new_ddata);如果/当数组成为长的属于双重的.不太可能忽略更改大小(int)从而产生难以发现的错误。

  2. …推()需要返回成功或失败,并且调用方需要检查返回代码。

  3. 此代码混合传递结构(来自dynamic_array_new())通过指针结构。我希望看到代码遵循一种做法或另一种做法;不能两者混合。

  4. (窃笑)更好用尺寸_t(而不是整数)编码数组索引时(如main()).

  5. 阅读部分realloc()文档说明类似“行为就像malloc()如果原始指针为NULL。“换句话说,不需要在…新建(_N).让第一个…推送(_P)使用NULL指针执行初始分配。也许调用者只会在数组中创建一个元素。你可能已经感觉到了2是任意的初始分配。


附言:修改为使用上面的#5,将排除#3。当您想要/需要阵列时,只需:

array_t arrN={0};

会完成这项工作。所有结构成员都设置为零(或NULL),第一个…推送(_P)处理初始化长度(增加1)、容量(设置为1,然后在每次连续调用时加倍)和数据指针初始化后使用。简单。。。

\$\端组\$
5
  • \$\开始组\$ 使用void*new_data=。。。而不是int*new_data=。。。是较少的维护,因为它支持“不太可能忽略更改sizeof(int)…”问题。 \$\端组\$ 评论 2023年12月14日19:07
  • \$\开始组\$ @chux-ReinstateMonica是的,那是另一种选择。我倾向于选择“在左边”的数据类型sizeof变量名因为更容易看到。你建议的版本有什么优点(我没有看到)吗?干杯!:-) \$\端组\$ 评论 2023年12月14日20:40
  • 2
    \$\开始组\$ 这个答案提倡“不太可能忽略更改sizeof(int),从而创建难以发现的错误。”空隙*在左边,如果成员类型为.数据通过类似的推理改变。 \$\端组\$ 评论 2023年12月14日22:44
  • 1
    \$\开始组\$ @chux-ReinstateMonica谢谢:-)我还在malloc()心态,而不是实际位置(),在写评论时。。。谢谢你:-)... \$\端组\$ 评论 2023年12月14日22:52
  • 1
    \$\开始组\$ 我应该建议void*new_data=重新分配(arr->data,sizeof arr->date[0]*arr->capacity*2);以获得更完整的想法。 \$\端组\$ 评论 2023年12月15日12:20
5
\$\开始组\$

澄清尺寸_t溢出问题等。

缩放前测试,注意0容量,使用realloc()和引用对象的大小,而不是类型,检查分配是否成功:

//防止溢出if(arr->容量>SIZE_MAX/2/sizeof arr->数据[0]){//处理TBD代码溢出}size_t new_capacity=(arr->capacity>0)?arr->容量*2:1;void*new_data=realloc(arr->data,new_capacity*sizeof arr->date[0]);if(new_data==NULL){//使用TBD代码处理内存外}arr->data=新数据;arr_capacity=新容量;
\$\端组\$

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.