C 语言中的动态字符串数组

Dynamic string array in C

提问人:Louis VERAN 提问时间:4/14/2023 最后编辑:Louis VERAN 更新时间:4/15/2023 访问量:97

问:

我试图编写一些东西来练习 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(应该是同样的情况)

无论如何,我希望你们能帮助我更多地了解正在发生的事情,这样我就不会在未来犯这些错误:) 谢谢

malloc c-strings realloc 函数定义

评论

0赞 12431234123412341234123 4/15/2023
旁注:用于数组长度,例如用于存储 的返回值。C 有这么多类型的整数是有原因的。另外:被授权者是 1,无需乘以它。size_tstrlen()sizeof(char)
0赞 n. m. could be an AI 4/15/2023
建议:定义以避免成为三星程序员typedef struct { char ** data; size_t size; size_t capacity; } StringArray;

答:

1赞 Vlad from Moscow 4/14/2023 #1

对于初学者来说,函数至少应该像这样声明

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;
}

该函数看起来更清晰,没有逻辑上的不一致。

评论

0赞 Louis VERAN 4/15/2023
谢谢你花时间回答这个问题,我已经明白了问题所在,并花时间了解发生了什么。也感谢您提供有关幻数的提示,并且函数本身没有消息,我一定会记住这一点
0赞 Vlad from Moscow 4/15/2023
@LouisVERAN完全没有。不客气:)
1赞 Blindy 4/14/2023 #2

*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);
}

评论

0赞 Louis VERAN 4/15/2023
谢谢你的回答,它确实有效,实际上更接近我的原始代码。感谢您抽出宝贵时间来解诅咒我的代码!
1赞 Dmytro 4/15/2023 #3

首先,我想指出的是,你正在做三星级的编程。你所说的 Inventory,实际上希望成为对象的容器。你不必尊重这一点,但你应该写更多的文档来弥补复杂性,因为单个指针已经足够复杂了,因为它不清楚它是否指向堆、堆栈或其他任何东西,只是通过查看它。这种复杂性也使得对设计进行更改变得更加困难,部分原因是与指针争吵的“沉没时间成本”感,部分原因是与实现细节的严重耦合。InventoryInventoryItemInventoryItem

我想提出一个粗略的、更统一的设计,它最初看起来更复杂,但实际上更简单,因为:

  1. 一切都毫不含糊地在堆上。
  2. 每个人都有责任照顾好自己,而不会给构建和发布 s 的复杂细节带来负担。InventoryItemInventoryInventoryItem
  3. 长度与清单指针一起分组,这样可以更轻松地同时拥有多个清单,而无需执行诸如具有清单指针数组和另一个长度数组之类的操作。
#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);
}

评论

0赞 Louis VERAN 4/15/2023
谢谢你的帮助,我花了一些时间来理解你的代码是如何工作的(正如我所说,正如你注意到的,我是一个非常初学者的 C),感谢你花时间实际制作一个我可以学习的工作代码。