提问人:Louis VERAN 提问时间:4/14/2023 最后编辑:Louis VERAN 更新时间:4/15/2023 访问量:97
C 语言中的动态字符串数组
Dynamic string array in C
问:
我试图编写一些东西来练习 C 语言。 我想做的(同样只是为了练习,而不是任何用途)是创建某种库存系统,您可以在其中添加物品或取出物品。
我决定用动态字符串数组来实现它。由于我对 C 语言很陌生,所以很难真正理解指针和东西是怎么回事,但我最终明白了。
我设法编写了一些代码。但是我有一个似乎无法调试的问题,我不明白它来自哪里,这是我的代码(可能真的被诅咒了)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int addItemToInventory(char ***Inv_content,int *Inv_length,char* Item)
{
int item_name_size=strlen(Item);
if (*Inv_length==0) //If The size of the inventory is 0, I have to create the array.
{
*Inv_content = malloc((1) * sizeof(char*));
if (*Inv_content) // malloc succeed
{
*Inv_content[0] = malloc((item_name_size+1) * sizeof(char));
if (*Inv_content[0]) // malloc succeed
{
strcpy(*Inv_content[0], Item);
*Inv_length+=1;
}
else // malloc failed
{
printf("Impossible to allocate memory for item '%s' in InvContent[0] \n", Item);
return(2);
}
}
else // malloc failed
{
printf("Impossible to allocate memory for Inv. (Create New Inv)\n");
return(1);
}
}
else //the size of the inventory is greater than zero, I have to expand the current array and add the item to the new slot
{
char **tmp_pnt;
tmp_pnt= realloc(*Inv_content, (*Inv_length+1) * sizeof(char*));
if (tmp_pnt)
{
*Inv_content=tmp_pnt;
*Inv_content[*Inv_length] = malloc((item_name_size+1) * sizeof(char));
if (*Inv_content[*Inv_length])
{
strcpy(*Inv_content[*Inv_length], Item);
*Inv_length+=1;
}
else // malloc failed
{
printf("unable to allocate memory for item '%s' in InvContent[%d]\n", Item, *Inv_length);
return(2);
}
}
else // realloc failed
{
printf("Impossible to allocate memory for Inv. (Add Item (realloc))\n");
return(1);
}
}
return(0);
}
所以,我希望代码足够清晰,正如你所看到的,这个函数需要 3 个参数:
指向清单本身的指针(以便直接对其进行更改)
指向清单大小的指针,原因相同
以及我们要添加的项目名称
现在问题来了,这是我用来测试我的函数的主要函数:
int main()
{
//int run = 1;
char **Inv_content = NULL;
int Inv_length=0;
//int Item_index=-1;
char Item1[12]="Great Sword";
char Item2[8]="Big Bow";
addItemToInventory(&Inv_content, &Inv_length, Item1);
addItemToInventory(&Inv_content, &Inv_length, Item2);
printf("\n");
for (int i=0; i<=Inv_length-1;i++)
{
printf ("Item %d : %s \n", i, Inv_content[i]);
}
}
结果如下:
- 第 1 项:大剑
- 项目 2:(NULL)
调用函数一次时,没有问题,我认为我的函数的第一部分(大小 = 0 时的情况)工作正常。 第二次调用时,它会输出“(null)”作为结果,就像数组中什么都没有一样。 而当第三次(或更多次)调用时,情况更糟,它会因“分段错误”而崩溃。
这是我发现的,第二部分按预期工作,当函数内部甚至在它结束时,它输出“大弓”,就像预期的那样,但是一旦它离开函数,当打印inv_content[1]时,它输出(null),就像它在函数退出时被擦除一样。printf("%s", *Inv_content[1])
最后,我不明白为什么在调用 3+ 次时会出现分段错误,因为它没有 2(应该是同样的情况)
无论如何,我希望你们能帮助我更多地了解正在发生的事情,这样我就不会在未来犯这些错误:) 谢谢
答:
对于初学者来说,函数至少应该像这样声明
int addItemToInventory( char ***Inv_content, size_t *Inv_length, const char *Item );
其次,根据条件将函数的主体分成两部分是没有意义的
if (*Inv_length==0)
该函数在语句中包含错误,例如
*Inv_content[*Inv_length] = malloc((item_name_size+1) * sizeof(char));
你需要写
( *Inv_content )[*Inv_length] = malloc((item_name_size+1) * sizeof(char));
该函数可以如下所示
int addItemToInventory( char ***Inv_content, size_t *Inv_length, const char *Item )
{
char **tmp_pnt = realloc( *Inv_content, ( *Inv_length +1 ) * sizeof( char* ) );
int result = tmp_pnt != NULL ? 0 : 1;
if ( result == 0 )
{
*Inv_content = tmp_pnt;
( *Inv_content )[*Inv_length] = malloc( strlen( item ) + 1 );
if ( ( *Inv_content )[*Inv_length] == NULL ) result = 2;
if ( result == 0 )
{
strcpy( ( *Inv_content )[*Inv_length], Item );
++*Inv_length;
}
}
return result;
}
请注意,该函数不应发出任何消息。
函数的调用方将根据函数的返回值决定是否输出消息。
此外,您可以在函数中引入枚举,而不是在函数中使用 0、1 和 2 等幻数
enum { Success = 0, ReallocFailure = 1, MallocFailure = 2 };
在函数中写入例如
int result = tmp_pnt != NULL ? Success : ReallocFailure;
if ( result == Success )
//... and so on
该功能存在一些严重问题。如果未为字符串分配字符数组,则指针数组已重新分配。因此,函数行为在逻辑上是不一致的。item
最好按以下方式重写函数
enum { Success = 0, ReallocFailure = 1, MallocFailure = 2 };
int addItemToInventory( char ***Inv_content, size_t *Inv_length, const char *Item )
{
char *s = malloc( strlen( item ) + 1 );
int result = s != NULL ? Success : MallocFailure;
if ( result == Success )
{
char **tmp_pnt = realloc( *Inv_content, ( *Inv_length +1 ) * sizeof( char* ) );
if ( tmp_pnt == NULL ) result = ReallocFailure;
if ( result == Success )
{
strcpy( s, Item );
tmp_pnt[*Inv_length] = s;
*Inv_content = tmp_pnt;
++*Inv_length;
}
else
{
free( s );
}
}
return result;
}
该函数看起来更清晰,没有逻辑上的不一致。
评论
*Inv_content[*Inv_length]
在数组旁边查找内存中的下一个数组(可能分配也可能不分配)。您希望改用在数组中查找所选项的 Copy。(*Inv_content)[*Inv_length]
另外,这里有一个不太受诅咒的代码版本,你至少在代码中重写并使用错误的大小类型:strdup
typedef struct { const char* Name; } Item;
int addItemToInventory(Item** Inv_content, size_t* Inv_length, const char* Item)
{
if (*Inv_length == 0)
{
*Inv_content = malloc(sizeof(struct Item*));
if (*Inv_content)
(*Inv_content)->Name = _strdup(Item);
*Inv_length = 1;
}
else
{
char** tmp_pnt;
tmp_pnt = realloc(*Inv_content, (*Inv_length + 1) * sizeof(struct Item*));
if (tmp_pnt)
{
*Inv_content = tmp_pnt;
(*Inv_content)[(*Inv_length)++].Name = _strdup(Item);
}
else // realloc failed
{
printf("Impossible to allocate memory for Inv. (Add Item (realloc))\n");
return(1);
}
}
return(0);
}
int main()
{
Item* Inv_content = NULL;
size_t Inv_length = 0;
const char Item1[] = "Great Sword";
const char Item2[] = "Big Bow";
addItemToInventory(&Inv_content, &Inv_length, Item1);
addItemToInventory(&Inv_content, &Inv_length, Item2);
printf("\n");
for (int i = 0; i <= Inv_length - 1; i++)
printf("Item %d : %s \n", i, Inv_content[i].Name);
}
评论
首先,我想指出的是,你正在做三星级的编程。你所说的 Inventory,实际上希望成为对象的容器。你不必尊重这一点,但你应该写更多的文档来弥补复杂性,因为单个指针已经足够复杂了,因为它不清楚它是否指向堆、堆栈或其他任何东西,只是通过查看它。这种复杂性也使得对设计进行更改变得更加困难,部分原因是与指针争吵的“沉没时间成本”感,部分原因是与实现细节的严重耦合。Inventory
InventoryItem
InventoryItem
我想提出一个粗略的、更统一的设计,它最初看起来更复杂,但实际上更简单,因为:
- 一切都毫不含糊地在堆上。
- 每个人都有责任照顾好自己,而不会给构建和发布 s 的复杂细节带来负担。
InventoryItem
Inventory
InventoryItem
- 长度与清单指针一起分组,这样可以更轻松地同时拥有多个清单,而无需执行诸如具有清单指针数组和另一个长度数组之类的操作。
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#pragma region InventoryItem
/**
* A heap-string representing an inventory item.
* This object is intended to always be on the heap.
*/
typedef struct InventoryItem {
char *name;
int length;
} InventoryItem;
InventoryItem *InventoryItem_fromCString(const char *name)
{
InventoryItem *tmp = malloc(sizeof(InventoryItem));
assert((tmp != NULL) && "Critical error: malloc failed allocating memory for an InventoryItem object.");
tmp->length = strlen(name) + 1;
tmp->name = malloc(strlen(name) + 1);
assert((tmp->name != NULL) && "Critical error: malloc failed allocating memory for an InventoryItem's internal string.");
strcpy(tmp->name, name);
return tmp;
}
/**
* Weak contract: do not use the InventoryItem object after release.
*/
void InventoryItem_release(InventoryItem *This)
{
free(This->name);
}
#pragma endregion
#pragma region Inventory
/**
* A container for a heap-array of InventoryItem objects.
* This object is intended to always be on the heap.
*/
typedef struct Inventory {
InventoryItem **items;
int length;
} Inventory;
void Inventory_init(Inventory *This)
{
This->items = NULL;
This->length = 0;
}
Inventory *Inventory_create()
{
Inventory *tmp = malloc(sizeof(Inventory));
assert((tmp != NULL) && "Critical error: malloc failed allocating memory for an Inventory object.");
Inventory_init(tmp);
return tmp;
}
void Inventory_addItem(Inventory *This, const char *ItemToAdd)
{
InventoryItem *item = InventoryItem_fromCString(ItemToAdd);
This->items = realloc(This->items, (This->length + 1) * sizeof(Inventory *));
assert((This->items != NULL) && "Critical error: realloc failed expanding memory of the specified Inventory object's items array while trying to add an item to it.");
This->items[This->length] = item;
++(This->length);
}
void Inventory_dump(Inventory *This, FILE *stream)
{
fprintf(stream, "The inventory has %d items.\n", This->length);
for (int i = 0; i < This->length; ++i)
{
fprintf(stream, "Item %d: %s.\n", i, This->items[i]->name);
}
}
/**
* Weak contract: do not use the Inventory object after it has been released.
*/
void Inventory_release(Inventory *This)
{
for (int i = 0; i < This->length; ++i) {
InventoryItem_release(This->items[i]);
}
free(This->items);
}
#pragma endregion
int main()
{
Inventory *inv = Inventory_create();
Inventory_addItem(inv, "Great Sword");
Inventory_addItem(inv, "Big Bow");
Inventory_addItem(inv, "Excalibur");
Inventory_dump(inv, stdout);
Inventory_release(inv);
}
评论
size_t
strlen()
sizeof(char)
typedef struct { char ** data; size_t size; size_t capacity; } StringArray;