C中的动态字符串数组

kkih6yb8  于 2023-04-19  发布在  其他
关注(0)|答案(3)|浏览(131)

我想写一些代码来练习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]);
    }

}

结果是

  • 第一件:巨剑
  • 项目2:(NULL)

当调用函数一次,没有问题,我认为我的函数的第一部分(Case when size = 0)工作正常。当调用第二次时,它输出“(null)”作为结果,就像数组中没有任何东西一样。当调用第三次(或更多次)时,情况更糟,它崩溃了“分段错误”。
这是我发现的,第二部分按预期工作,当printf("%s", *Inv_content[1])在函数内部时,即使在它的末尾,它也会输出“Big bow”,就像预期的那样,但是一旦它离开函数,当打印inv_content[1]时,它会输出(null),就像它在函数退出时被擦除一样。
最后,我不明白为什么我在调用3+次时会出现分段错误,因为它不会与2(这应该是相同的情况)
无论如何,我希望你们能帮助我了解更多发生了什么,这样我就不会在未来犯这些错误:)谢谢

z2acfund

z2acfund1#

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

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

例如,在函数write中

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

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

wj8zmpe1

wj8zmpe12#

*Inv_content[*Inv_length]正在查找您的数组旁边的下一个数组(可能已分配,也可能未分配)。您希望使用(*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);
}
v6ylcynt

v6ylcynt3#

我想先指出你正在做three star programming。你所谓的库存,实际上是一个InventoryItem对象的Inventory容器。你不必尊重这一点,但你应该写更多的文档来弥补复杂性,因为单个指针已经足够复杂了,因为不清楚它是否指向堆,堆栈,这种复杂性也使得更改设计变得更加困难,部分原因是与指针争论的“沉没时间成本”,部分原因是与InventoryItem的实现细节的严重耦合。
我想提出一个粗略的,更统一的设计,最初似乎更复杂,但实际上更简单,因为:
1.所有的东西都明确地放在堆上。
1.每个InventoryItem都有责任照顾好自己,而不会让Inventory承担构建和释放InventoryItem的复杂细节。
1.长度与库存指针一起分组,使得一次拥有多个库存变得更简单,而无需执行诸如拥有库存指针数组和另一个长度数组之类的操作。

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

相关问题