从动态数组中删除元素
所以,我有这个:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void remove_element(int* array, int sizeOfArray, int indexToRemove)
{
int* temp = malloc((sizeOfArray - 1) * sizeof(int*)); // allocate an array with a size 1 less than the current one
memcpy(temp, array, indexToRemove - 1); // copy everything BEFORE the index
memcpy(temp+(indexToRemove * sizeof(int*)), temp+((indexToRemove+1) * sizeof(int*)), sizeOfArray - indexToRemove); // copy everything AFTER the index
free (array);
array = temp;
}
int main()
{
int howMany = 20;
int* test = malloc(howMany * sizeof(int*));
for (int i = 0; i < howMany; ++i)
(test[i]) = i;
printf("%d\n", test[16]);
remove_element(test, howMany, 16);
--howMany;
printf("%d\n", test[16]);
return 0;
}
这是相当不言自明的,remove_element 删除动态数组的给定元素。
正如您所看到的,test 的每个元素都被初始化为一个递增的整数(即 test[n] == n)。然而,程序输出
16
16
. 删除了 test 的一个元素后,人们会期望调用 test[n],其中 n >= 删除的元素将导致 test[n+1] 在删除之前的结果。所以我期望输出
16
17
。出了什么问题?
编辑:问题现已解决。这是固定代码(带有粗略的调试 printfs),如果其他人发现它有用的话:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int remove_element(int** array, int sizeOfArray, int indexToRemove)
{
printf("Beginning processing. Array is currently: ");
for (int i = 0; i < sizeOfArray; ++i)
printf("%d ", (*array)[i]);
printf("\n");
int* temp = malloc((sizeOfArray - 1) * sizeof(int)); // allocate an array with a size 1 less than the current one
memmove(
temp,
*array,
(indexToRemove+1)*sizeof(int)); // copy everything BEFORE the index
memmove(
temp+indexToRemove,
(*array)+(indexToRemove+1),
(sizeOfArray - indexToRemove)*sizeof(int)); // copy everything AFTER the index
printf("Processing done. Array is currently: ");
for (int i = 0; i < sizeOfArray - 1; ++i)
printf("%d ", (temp)[i]);
printf("\n");
free (*array);
*array = temp;
return 0;
}
int main()
{
int howMany = 20;
int* test = malloc(howMany * sizeof(int*));
for (int i = 0; i < howMany; ++i)
(test[i]) = i;
printf("%d\n", test[16]);
remove_element(&test, howMany, 14);
--howMany;
printf("%d\n", test[16]);
return 0;
}
So, I have this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void remove_element(int* array, int sizeOfArray, int indexToRemove)
{
int* temp = malloc((sizeOfArray - 1) * sizeof(int*)); // allocate an array with a size 1 less than the current one
memcpy(temp, array, indexToRemove - 1); // copy everything BEFORE the index
memcpy(temp+(indexToRemove * sizeof(int*)), temp+((indexToRemove+1) * sizeof(int*)), sizeOfArray - indexToRemove); // copy everything AFTER the index
free (array);
array = temp;
}
int main()
{
int howMany = 20;
int* test = malloc(howMany * sizeof(int*));
for (int i = 0; i < howMany; ++i)
(test[i]) = i;
printf("%d\n", test[16]);
remove_element(test, howMany, 16);
--howMany;
printf("%d\n", test[16]);
return 0;
}
It's reasonably self-explanatory, remove_element removes a given element of a dynamic array.
As you can see, each element of test is initialised to an incrementing integer (that is, test[n] == n). However, the program outputs
16
16
.
Having removed an element of test, one would expect a call to to test[n] where n >= the removed element would result in what test[n+1] would have been before the removal. So I would expect the output
16
17
. What's going wrong?
EDIT: The problem has now been solved. Here's the fixed code (with crude debug printfs), should anyone else find it useful:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int remove_element(int** array, int sizeOfArray, int indexToRemove)
{
printf("Beginning processing. Array is currently: ");
for (int i = 0; i < sizeOfArray; ++i)
printf("%d ", (*array)[i]);
printf("\n");
int* temp = malloc((sizeOfArray - 1) * sizeof(int)); // allocate an array with a size 1 less than the current one
memmove(
temp,
*array,
(indexToRemove+1)*sizeof(int)); // copy everything BEFORE the index
memmove(
temp+indexToRemove,
(*array)+(indexToRemove+1),
(sizeOfArray - indexToRemove)*sizeof(int)); // copy everything AFTER the index
printf("Processing done. Array is currently: ");
for (int i = 0; i < sizeOfArray - 1; ++i)
printf("%d ", (temp)[i]);
printf("\n");
free (*array);
*array = temp;
return 0;
}
int main()
{
int howMany = 20;
int* test = malloc(howMany * sizeof(int*));
for (int i = 0; i < howMany; ++i)
(test[i]) = i;
printf("%d\n", test[16]);
remove_element(&test, howMany, 14);
--howMany;
printf("%d\n", test[16]);
return 0;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我在发布的代码中看到了几个问题,每个问题都可能导致问题:
返回新数组
您的函数正在采用
int* 数组
,但随后您尝试将其与您的temp< /code> 变量位于返回新数组之前的末尾。这不起作用,因为您只是替换
int* array
的本地副本,该副本在您从函数返回后会消失。您要么需要将数组指针作为
int**
传递,这将允许您在函数中设置指向数组的实际指针,或者,我建议只返回一个值int* 为您的函数,并返回新数组。
另外,正如这个答案中提到的,从数组中删除元素时,您甚至不需要重新分配,因为原始数组足够大,可以容纳所有内容。
大小和偏移量计算
您正在使用
sizeof(int*)
来计算数组元素大小。这可能适用于某些类型,但是,例如,对于short
数组,sizeof(short*)
不起作用。您不需要指向数组的指针的大小,您需要元素的大小,对于您的示例来说应该是sizeof(int)
尽管在这种情况下它可能不会导致问题。< /p>数组偏移量的长度计算看起来不错,但您忘记将元素数量乘以 memcpy 的大小参数的元素大小。例如memcpy(temp, array, indexToRemove * sizeof(int));。
对 memcpy 的第二次调用使用
temp
加上偏移量作为源数组,但它应该是array
加上偏移量。您对 memcpy 的第二次调用使用
sizeOfArray - indexToRemove
作为要复制的元素数量,但您应该只复制SizeOfArray - indexToRemove - 1
元素(或>(sizeOfArray - indexToRemove - 1) * sizeof(int)
字节无论在哪里计算临时数组和数组的偏移量,都不需要乘以 sizeof(int),因为指针算术已经考虑了元素的大小(我一开始就错过了这一点,这要归功于:这个答案。)
查看不正确的元素
您正在打印
test[16]
(第 17 个元素)进行测试,但您正在删除第 16 个元素,这将是测试[15]
。极端情况
另外(感谢这个答案)你应该处理这些情况其中
indexToRemove == 0
和indexToRemove == (sizeOfArray - 1)
,您可以一次完成整个删除操作内存拷贝。另外,您还需要担心
sizeOfArray == 1
的情况。在这种情况下,可能要么分配 0 大小的内存块,要么返回 null。在我更新的代码中,我选择分配一个 0 大小的块,只是为了区分具有 0 个元素的数组与未分配的数组。返回 0 大小的数组还意味着无需对代码进行任何额外更改,因为处理前面提到的前两种情况的每个 memcpy 之前的条件将阻止任一 memcpy 发生。
顺便提一下,代码中没有错误处理,因此存在隐式先决条件:
indexToRemove
在边界内、array
不为 null 以及array
的大小作为sizeOfArray
传递。示例更新了代码
关于内存管理/抽象数据类型的几句话
最后,需要考虑的事情:使用
malloc
将内存返回给预计可以免费的用户< /code>由用户分配,并
释放
用户malloc
分配的内存。一般来说,如果您设计的代码单元使得内存分配在单个逻辑代码单元内处理,那么内存管理就不太可能变得混乱和难以处理。例如,您可以创建一个抽象数据类型模块,该模块允许您使用保存指针和长度的结构创建整数数组,然后对该数据的所有操作都通过将该结构作为第一个参数的函数进行。除了在该模块内之外,这还允许您避免执行诸如
elemNumber * sizeof(elemType)
之类的计算。类似这样:等等。
这基本上是在 C 中实现一些类似 C++ 的功能,在我看来这是一个非常好的主意,特别是如果您从头开始并且您想要创建的不仅仅是一个非常简单的应用程序。我知道一些 C 开发人员确实不喜欢这个习惯用法,但它对我来说效果很好。
这种实现方式的好处是,代码中使用该函数删除元素的任何内容都不会直接接触指针。这将允许代码的几个不同部分存储指向抽象数组结构的指针,并且当删除元素后重新分配指向数组实际数据的指针时,指向抽象数组的所有变量都会自动更新。
一般来说,内存管理可能非常令人困惑,而这是一种可以减少这种混乱的策略。只是一个想法。
I see several issues in the posted code, each of which could cause problems:
returning the new array
Your function is taking an
int* array
but then you are trying to swap it with yourtemp
variable at the end prior to returning the new array. This will not work, as you are simply replacing the local copy ofint* array
which will disappear after you return from the function.You either need to pass your array pointer in as an
int**
, which would allow you to set the actual pointer to the array in the function, or, I would suggest just returning a valueof int* for your function, and returning the new array.
Also, as mentioned in this answer, you really don't even need to reallocate when deleting an element from the array, since the original array is big enough to hold everything.
size and offset calculations
You are using
sizeof(int*)
for calculating the array element size. This may work for some types, but, for instance, for ashort
arraysizeof(short*)
does not work. You don't want the size of the pointer to the array, you want the size of the elements, which for your example should besizeof(int)
although it may not cause problems in this case.Your length calculation for the offsets into the arrays looks ok, but you're forgetting to multiply the number of elements by the element size for the size parameter of the memcpy. e.g.
memcpy(temp, array, indexToRemove * sizeof(int));
.Your second call to memcpy is using
temp
plus the offset as the source array, but it should bearray
plus the offset.Your second call to memcpy is using
sizeOfArray - indexToRemove
for the number of elements to copy, but you should only copySizeOfArray - indexToRemove - 1
elements (or(sizeOfArray - indexToRemove - 1) * sizeof(int)
bytesWherever you are calculating offsets into the temp and array arrays, you don't need to multiply by sizeof(int), since pointer arithmetic already takes into account the size of the elements. (I missed this at first, thanks to: this answer.)
looking at incorrect element
You are printing
test[16]
(the 17th element) for testing, but you are removing the 16th element, which would betest[15]
.corner cases
Also (thanks to this answer) you should handle the cases where
indexToRemove == 0
andindexToRemove == (sizeOfArray - 1)
, where you can do the entire removal in one memcpy.Also, you need to worry about the case where
sizeOfArray == 1
. In that case perhaps either allocate a 0 size block of memory, or return null. In my updated code, I chose to allocate a 0-size block, just to differentiate between an array with 0 elements vs. an unallocated array.Returning a 0-size array also means there are no additional changes necessary to the code, because the conditions before each memcpy to handle the first two cases mentioned will prevent either memcpy from taking place.
And just to mention, there's no error handling in the code, so there are implicit preconditions that
indexToRemove
is in bounds, thatarray
is not null, and thatarray
has the size passed assizeOfArray
.example updated code
a few words on memory management/abstract data types
Finally, something to consider: there are possible issues both with using
malloc
to return memory to a user that is expected to befree
d by the user, and withfree
ing memory that a usermalloc
ed. In general, it's less likely that memory management will be confusing and hard to handle if you design your code units such that memory allocation is handled within a single logical code unit.For instance, you might create an abstract data type module that allowed you to create an integer array using a struct that holds a pointer and a length, and then all manipulation of that data goes through functions taking the structure as a first parameter. This also allows you, except within that module, to avoid having to do calculations like
elemNumber * sizeof(elemType)
. Something like this:etc.
This is a basically implementing some C++-like functionality in C, and it's IMO a very good idea, especially if you are starting from scratch and you want to create anything more than a very simple application. I know of some C developers that really don't like this idiom, but it has worked well for me.
The nice thing about this way of implementing things is that anything in your code that was using the function to remove an element would not ever be touching the pointer directly. This would allow several different parts of your code to store a pointer to your abstract array structure, and when the pointer to the actual data of the array was reallocated after the element was removed, all variables pointing to your abstract array would be automatically updated.
In general, memory management can be very confusing, and this is one strategy that can make it less so. Just a thought.
您实际上并没有更改传递的指针。您只是更改了
数组
的副本。因此,在函数之后,数组仍然指向它曾经指向的位置,但是,您也释放了它,这雪上加霜。
尝试这样的操作:
还有一个 C FAQ:更改传递的指针。
You don't actually change the passed pointer. You're only changing your copy of
array
.So after the function the array still points to where it used to point BUT, you've also freed it, which adds insult to injury.
Try something like this:
There is also a C FAQ: Change passed pointer.
@cnicutar 是对的(+1),但是,你也写了:
虽然它应该是:
因为乘以
int*
的大小是由编译器完成的(这是指针算术)另外,当移动时覆盖内存区域,请使用
memmove
而不是memcpy
。@cnicutar is right (+1), but also, you write:
while it should be:
Since the multiplication by the size of
int*
is done by the compiler (that's pointer arithmetic)Also, when moving overlaying memory areas, use
memmove
and notmemcpy
.此外:第二个
memcpy
调用的第二个参数应该基于array
,而不是temp
,对吧?既然您的数组存储的是整数而不是指针,那么您不应该基于sizeof int
而不是基于sizeof int*
进行内存分配和复制吗?难道您不需要将要复制的字节数(memcpy
的最后一个参数)乘以sizeof int
吗?另外,请注意
indexToRemove == 0
的情况。Further: the second argument to your second
memcpy
call should be based onarray
, not ontemp
, right? And shouldn't you be mallocing and copying based onsizeof int
and not based onsizeof int*
, since your arrays store integers and not pointers? And don't you need to multiply the number of bytes you're copying (the last argument tomemcpy
) bysizeof int
as well?Also, watch the case where
indexToRemove == 0
.该代码存在一些问题:
(a) 分配内存时,您需要确保使用
sizeof
的正确类型。例如,对于int
数组,您分配一个大小为sizeof(int)
倍数的内存块。所以:应该是:
(b) 您没有在
main
末尾释放数组的内存。(c)
memcpy
将要复制的字节数作为第三个参数。因此,您需要再次确保传递sizeof(int)
的倍数。所以:应该是:
(d) 将项目从旧数组复制到新数组时,请确保复制正确的数据。例如,索引
indexToRemove
处的项目之前有indexToRemove
项目,一个都不能少。同样,您需要确保在需要删除的项目之后复制正确数量的项目。(e) 当增加指针时,您不需要乘以
sizeof(int)
- 这是隐式为您完成的。所以:实际上应该是:
(f) 在您的
remove_element
函数中,您将一个值分配给局部变量array
。对局部变量的任何更改在函数外部都是不可见的。因此,在对remove_element
的调用结束后,您将不会在main
中看到更改。解决此问题的一种方法是从函数返回新指针,并将其分配到main
中:There are a few problems with that code :
(a) When allocating memory, you need to make sure to use the correct type with
sizeof
. For an array ofint
eg., you allocate a memory block with a size that is a multiple ofsizeof(int)
. So :should be :
(b) You don't free the memory for the array at the end of
main
.(c)
memcpy
takes the amount of bytes to copy as the third parameter. So, you need to again make sure to pass a multiple ofsizeof(int)
. So :should be :
(d) when copying items from the old array to the new array, make sure to copy the correct data. For example, there are
indexToRemove
items before the item at indexindexToRemove
, not one less. Similarly, you'll need to make sure that you copy the correct amount of items after the item that needs to be removed.(e) When incrementing a pointer, you don't need to multiply with
sizeof(int)
- that's done implicitly for you. So :should really be :
(f) In your
remove_element
function, you assign a value to the local variablearray
. Any changes to local variables are not visible outside of the function. So, after the call toremove_element
ends, you won't see the change inmain
. One way to solve this, is to return the new pointer from the function, and assign it inmain
:所有其他答案都很好地说明了代码中的各种问题/错误。
但是,为什么要重新分配(并不是所有错误都与重新分配有关)? “较小”的数组将很好地适合现有的内存块:
All the other answers make good points about the various problems/bugs in the code.
But, why reallocate at all (not that the bugs are all related to reallocation)? The 'smaller' array will fit fine in the existing block of memory: