为什么要使用'array =(int*)calloc(len,sizeof(int))'而不是仅仅声明'int array[len];''?

3df52oht  于 2022-12-03  发布在  其他
关注(0)|答案(6)|浏览(111)
// Why would I use this
odd = (int *) calloc( nOdd, sizeof(int) );
even = (int *) calloc( nEven, sizeof(int) );

// When i can just use this
int odd[nOdd];
int even[nEven];

我不明白当你需要输入数组中有多少项时,它是如何动态分配内存的。
我已经习惯了python,在python中,你可以只追加到一个数组。所以我会认为它会是这样的

hpxqektj

hpxqektj1#

如果我写下面的代码,那么这个内存只在函数调用的过程中有效。它有自动存储的时间。我们不需要担心释放它,但是它不能在其他地方使用,否则会引发可怕的未定义行为。

void foo(void) {
  int bar[100];

  // ...
}

如果我们动态地分配这个数组,那么我们必须记住清理它,但是我们可以返回一个指向它的指针,并且这个指针一直保持有效,直到用free释放内存。

int *foo(void) {
  int *bar = malloc(sizeof(int) * 100);

  // ...

  return bar;
}
f0ofjuux

f0ofjuux2#

callocmalloc的意义在于,程序可以在执行时指定要分配多少内存,而不是在编译时。
是的,当你想添加更多的元素时,C不会自动调整数组的大小。这是Python为你做的事情。但是必须有人写代码来做这件事;内存永远不会管理自己。在Python中,这可以通过编写C代码来完成,就像大多数Python实现一样。在C中,你必须自己完成。

ycl3bljg

ycl3bljg3#

C和Python不一样。
C代码通常不会在解释器中运行--它通常被编译成本机代码,并作为独立的可执行文件运行。固定长度数组必须在编译时知道其大小,并且不能在运行时调整大小。可变长度数组可以在运行时确定其大小,但一旦定义就不能调整大小;可变长度中的“变量”仅意味着它们的大小在每次示例化时可以不同。
因此,如果我们想使用可以根据需要调整大小的内存,我们必须使用动态内存例程malloccallocrealloc。当程序被加载时,会留出一段内存(通常称为 heap)来管理动态分配;每个X1 M3 N1 X例程跟踪已经分配了多少存储器以及哪些区域仍然空闲。
malloccalloc分配您指定字节数-calloc将清空内存。
realloc允许您根据需要增大或缩小动态分配的缓冲区。
这基本上就是Python在幕后的工作方式,只是语言的设计使所有这些对你来说都是透明的。

laik7k3q

laik7k3q4#

calloc的意义是什么
关键是分配一个内存块,让程序可以随意使用。calloc()还将该内存块清零,这很好; malloc()做同样的事情,但是块将包含之前碰巧在该内存中的任何垃圾。
我不明白当你需要输入数组中有多少项时,它是如何动态分配内存的。
在这种情况下,动态分配意味着您不必知道在编译时需要多大的块。这并不意味着您要创建一个块,其大小根据块中的内容而变化。您 * 可以 * 调整您以前使用realloc()分配的块的大小,但您仍然需要知道您最终想要的大小。
注意,实际上不需要知道动态分配的数组中有多少项;你只需要知道你可以放进数组的最大数字。例如,你可以为100个数字分配空间,然后实际上只加75。这个块仍然足够容纳100个数字,如果你以后想加更多,这很好。您的责任是确保添加的数字不超过分配的数组所能容纳的数字。Python可能在幕后做了类似的事情:每次添加或删除内容时都更改数组的大小是非常低效的,因此它可能会使数组比需要的大一些,并在超出可用空间时实际调整数组大小。
我已经习惯了python,在python中,你可以只追加到一个数组。所以我会认为它会是这样的
C不是Python,它是一种低得多的语言,AFAIK的速度要快得多,部分原因是它不会在每次添加或删除数据时调整内存块的大小。

ryoqjall

ryoqjall5#

大家说:

// Why would I use this
odd = (int *) calloc( nOdd, sizeof(int) );
even = (int *) calloc( nEven, sizeof(int) );

在很久以前(又过了一千年),如果你想在运行时确定数组的大小,并且不能冒险分配一个固定大小的数组并检测nOddnEven何时大于预先分配的数组大小,那么使用calloc()是必要的。

// When I can just use this
int odd[nOdd];
int even[nEven];

这在C99中得到强制性支持;在C11和C18中是可选的。这些都是VLA(可变长度数组)在堆栈上配置(与那些对“标准没有提到”堆栈“”吹毛求疵的人步调一致)。您必须以某种方式确定堆栈上是否有足够的可用空间来存储这些VLA。这是他们最大的异议。在C23中,我理解像这样的VLA分配是可选的,但是必须要有将VLA传递给函数的能力。这样的数组要么静态分配(定义时是固定大小的,但是被调用的函数可以处理不同的数组大小),要么动态分配(通过malloc()等)。
如果堆栈上没有足够的空间(如果nOddnEven或两者都很大),那么你需要回到动态内存分配。这意味着动态内存分配总是有效的,但是你必须确保在完成时释放所分配的内存。使用自动分配的VLA意味着你不必显式地释放它们,这很方便。
当您检查数组大小是否合理时,使用VLA也是合理的。但是如果数组大小太大,则需要使用动态内存分配。

7uzetpgm

7uzetpgm6#

最显著的区别在于保存时间,这是一整章的主题,所以最好是选择一本好书。我可以说,我会给予一下,但你不能指望我写的文字比一本像样的教科书更好,这本教科书是由那些 * 实际上你知道他们在说什么 * 的人同行评审的(无意冒犯少数这样做的人,但任何人都可以在StackOverflow上发表任何胡言乱语,而且对 * 撒谎 * 也没有太多的惩罚)。
下面是C语言标准的摘录,从根本上理解了四个存储持续时间,这四个存储持续时间被选为Python式垃圾收集的替代方法。引号中有一些链接,可以把你带到我引用的文档。我还将包括一个类比,把真实的生活与每个概念联系起来。
1.对象的存储期限决定了它的生存期。存储期限有四种:静态的、线程的、自动的和分配的。分配的存储在7.22.3中描述。
想象一下,你在厨房和其他人一起工作,根据你的同事,可能会有一些建立设备保管链的系统,以确保没有人争夺资源,没有人使用太不安全的东西。我们有四个经理:Steve(静态)、Theresa(线程)、Autumn(自动)和Allan(已分配)。每个管理器都有不同的管理系统。
2.对象存在、具有恒定地址33)并在其整个生存期内保持其最后存储的值。34)如果对象在其生存期之外被引用,指针的行为是未定义的。2当指针指向的对象(或刚刚过去的对象)到达其生命周期的终点时,指针的值就变得不确定了。
设备可能会被丢进垃圾桶或Flume,我们可以说,任何对此类设备的访问都将超出其使用寿命。换句话说,如果你使用一个超出其使用寿命的对象,这有点像使用脏的和/或有缺陷的厨房用具......通常是有风险的,而且不受欢迎。所有的经理都同意这一点。但是设备在变脏/损坏之前的使用范围是变化的。
我将在下面的所有示例中使用这个简单的函数。

void use(int *item) { item[0]++; }

3.一个对象的标识符声明时不带存储类说明符_Thread_local,或者带有外部或内部链接,或者带有存储类说明符static,它具有静态存储持续时间。它的生存期是程序的整个执行过程。
我们稍后会回到_Thread_local的问题;这是Theresa姓名标签(在您的问题中没有提到),在本段中,我们实际上讨论的是Steve的静态设备。
在Steve的厨房中,一些设备与标记为“静态”的功能相关联,并保留了该功能的值。你有一把刀和一个切洋葱的砧板,还有一把刀和一个切蘑菇的砧板,等等。它们总是在同一个地方,总是用于同一个任务。这些设备就在那里,所以当你使用该功能时,它总是同样的设备。在厨房关闭之前,这些设备不会被放进Flume或垃圾箱。把这个类比应用到编程中,这些设备的价值在不同的使用中持续存在;第一个函数调用设置值,下一个函数调用访问第一个函数调用的值,等等。

int *chop_shrooms_man(void) {
    static int knife;
    use(&knife); // knife is 1 after the first call, 2 after the second, etc
    return &totally_a_knife;
}

在上面的代码片段中,可以返回一个指向刀的指针,因为刀总是在同一个位置,总是一把刀,总是有最后一个赋值,等等。
Steve的其他“静态”设备(这可能类似于您的声明)是普通人所说的“全局变量”。您可以在工作场所中找到它,* 在任何函数之外 ,但每个人都天生知道它要重用的目的, 只要它没有附加Theresa的_Thread_local标签 *。换句话说,如果您的声明出现在函数之外,并且没有使用_Thread_local关键字,则它们是 static 的。

int I_am_static[42];
int *chop_something(void) {
    use(I_am_static); // 1 after first call, 2 after second call, 3 after third call, etc
    return I_am_static;
}

1.对象的识别项是以储存类别规范_Thread_local宣告,它具有执行绪储存持续时间。它的存留期是建立它的执行绪的整个执行时间,而且它的储存值会在执行绪启动时初始化。每个执行绪都有不同的对象,而且在运算式中使用宣告的名称会指涉与评估运算式之执行绪相关的对象。
对于Theresa,每件标记为_Thread_local的设备在连接到一根 * 线 * 上时都是可用的。如果线已经被 * 终止 *(切断或切断),那么该设备就是垃圾,我们不能使用它。可以说,您询问的设备没有标记为_Thread_local,所以您不是在询问Theresa的线存储时间。

_Thread_local int Theresas_equipment; // static and extern here distinguish between internal & external linkage, not storage duration

我将把这个作为一个练习留给你在你的时间里研究,因为很明显它与这里无关。
声明标识符时没有链接且没有存储类说明符static的对象具有自动存储持续时间,某些复合文字也是如此。
最后,我们来看看秋天。她的设备总是局限于一个功能,就像我们讨论过的史蒂夫的设备的第一种形式,除了它没有标记为“静态”。一旦功能(切碎东西,就像它)完成,该设备然后被扔在Flume。

int *chop_something(void) {
    int knife[42]; // automatic because it's in a function without any of those previously discussed keywords
    use(knife); // all good so far
    return knife; // THIS IS BAD!!!
}

在上面的例子中,当函数返回时,指向knife的指针变成了垃圾,因此调用者很可能会访问该垃圾。这是一个常见的错误。
就艾伦而言,他可能是他们中最自由,但也最乏味的一个。在你可以使用设备之前,你必须让它“分配”。当你完成后,你需要“释放”分配。任何被“释放”的东西都变成了垃圾。

int *example(void) {
    int *knife = malloc(sizeof *knife);
    use(knife);
    return knife; // all good so far
}

在上面的代码中,函数在生存期内返回一个已分配的对象,因此调用者可以使用返回值。OTOH,

int *example(void) {
    int *knife = malloc(sizeof *knife);
    free(knife);
    return knife; // THIS IS RETURNING GARBAGE!
}

相关问题