简体   繁体   中英

How to allocate an array of structs in c?

I am writing a function that adds items to the shopping list. I understand how to limit the size of the shopping list, but what I want to do is that I want to use dynamic memory allocation by using malloc ().

I want to get rid of the size and store the data as it comes in. Thus if 15 fields of data come in, I want to store 15 and not a specific size. I'm assuming that I don't know how much data is coming into my program.

I am still a beginner, so let me know if I presented the question in a wrong way.

Thank you in advance, I appreciate your opinions.

#define _CRT_SECURE_NO_WARNINGS
#include"ShoppingList.h"
#include<stdio.h>
#include<stdlib.h> // For malloc() and free()



void addItem(struct ShoppingList* list)
{
    
    
    if (list->length > 4)
    {
        return 0;
    }
    
    


        printf("Name for product: ");
        scanf("%s", list->itemList[list->length].productName);

        do
        {
            printf("Enter the amount: ");
            scanf("%f", &list->itemList[list->length].amount);

            if (list->itemList[list->length].amount <= 0.0)
            {
                printf("Input is invalid.\n");
            }

        } while (list->itemList[list->length].amount <= 0.0);


        printf("Enter unit of item: ");
        scanf("%s", list->itemList[list->length].unit);

        printf("%s was added to the shoppinglist.", list->itemList[list->length].productName);

        list->length++;

}
#ifndef SHOPPING_LIST_H
#define SHOPPING_LIST_H


// Struct definitions

struct GroceryItem
{
    char productName[20];
    float amount;
    char unit[10];
};

struct ShoppingList
{
    int length;
    struct GroceryItem itemList[5];
};

// Function declarations

void addItem(struct ShoppingList *list);
void printList(struct ShoppingList *list);
void editItem(struct ShoppingList *list);
void removeItem(struct ShoppingList *list);
void saveList(struct ShoppingList *list);
void loadList(struct ShoppingList* list);

#endif
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include "ShoppingList.h"

int main(void)
{
    struct ShoppingList shoppingList;
    shoppingList.length = 0; // The shopping list is empty at the start

    int option;

    do
    {
        printf("\n\nWelcome to the shopping list manager!\n");
        printf("=====================================\n\n");

        printf("1. Add an item\n");
        printf("2. Display the shopping list\n");
        printf("3. Remove an item\n");
        printf("4. Change an item\n");
        printf("5. Save list\n");
        printf("6. Load list\n");
        printf("7. Exit\n");

        printf("What do you want to do? ");
        scanf("%d", &option);

        switch (option)
        {
        case 1: addItem(&shoppingList); break;
        case 2: printList(&shoppingList); break;
        case 3: removeItem(&shoppingList); break;
        case 4: editItem(&shoppingList); break;
        case 5: saveList(&shoppingList); break;
        case 6: loadList(&shoppingList); break;
        case 7: break;
        default:
            printf("Please enter a number between 1 and 7");
        }
    } while (option != 7);

    return 0;
}

I would use flexible array members.

typedef struct GroceryItem
{
    char productName[20];
    float amount;
    char unit[10];
}GroceryItem;

typedef struct ShoppingList
{
    size_t length;
    GroceryItem itemList[];
}ShoppingList;


ShoppingList *additem(ShoppingList  *sh, const GroceryItem *item)
{
    size_t newsize = sh ? sh -> length + 1 : 1;
    sh = realloc(sh, newsize * sizeof(sh -> itemList[0]) + sizeof(*sh));
    if(sh)
    {
        sh -> length = newsize;
        sh -> itemList[newsize - 1] = *item;
    }
    return sh;
}

But personally, I would rather use a linked list instead of an array for this task.

There are several ways to achieve your target:

  • The first involves no machinery but malloc(3) use, and it is used only when you know in advance (in advance to allocate, but at run time, this is, not when the program is started, but for this usage of the array)
    struct the_thing {
         /* .... */
    };

    ...

    struct the_thing *array_of_things = malloc(number_of_things * sizeof(array_of_things[0]);

This will return a block of memory of size of the product of one element ( array_of_things[0] is not a real expression this time, but the argument to the operator sizeof ) that calculates the size of the type of the evaluation of the used expression, and doesn't evaluates it ---this is important because you cannot evaluate that expression yet, as the pointer doesn't point yet to the allocated memory--- by the number of elements you are going to use) This converts your pointer (and the pointer arithmetic induced by using the [index] notation) into an usable array (indeed it is a pointer to an array of elements. Important to say that the array is uninitialized and the data contents can be rubish and you have to initialize the elements, one by one, to the desired values. In the case you want an initialized array, you can use, instead, calloc(3) that was defined specifically to allocate arrays of things.

    struct the_thing *array_of_things = calloc(number_of_things, sizeof(array_of_things[0]));

look at one detail, we have used a comma this time to specify two quantities as parameters to calloc() , instead of passing the product of both. The result is that calloc(3) returns an initialized array (everything is initialized to zeros) and also, you specify separately the number of elements and the size of one element. This call is specific (but not mandatory specific) to create dynamic arrays. Both calls take as arguments the size of the total block of memory to allocate, and the number of elements and the element size, respectively.

  • The second way occurs when you need to make the array grow dynamically as you require more and more elements. This can be solved with the previous functions, but there's a function realloc() that does the dirty work for your.
    struct the_thing *array_of_things = NULL;

    /* now I need n1 elements */
    array_of_things = realloc(array_of_things, n1 * sizeof(array_of_things[0]));

with this, array_of_things has passed to hold no element (because it was assigned NULL ) to allocate n1 elements. The normal workings allow you to work with this array, until you get to it's limit, now things get more interesting:

    /* now I'll need n2 elements (n2 can be larger or smaller than n1 before) */
    array_of_things = realloc(array_of_things, n2 * sizeof(array_of_things[0]));

this call (with an already valid pointer) will try to see if the allocation can be solved in place, but if it cannot, a new array will be allocated with enough space to hold n2 elements, the n1 previous elements will be copied into the second array, and the new allocated pointer will be returned, and the previous one will be freed.

This has a possibly complicated drawback, is that you cannot have pointers pointing to element (or element fields) inside the array, because if the new array is repositioned in memory this will make all those pointers invalid (they will still be pointing to the old places, where now nothing exists) But if you can handle that, everything is fine.

I wrote a macro function to handle this situation and it works fine (except when I have pointers pointing inside the array that I have to rearrange all those pointers when I use the macro) The macro requires an array A , with two associated variables, A_len and A_cap (both of type size_t ) to be declared with the array:

#define DYNARRAY_GROW(_array _elem_type, _need, _increment) do { \
            if (_array##_len + (_need) > _array##_cap) {         \
                _array##_cap += (_increment);                    \
                _array = (_elem_type *) realloc(                 \ 
                       _array, _array##_cap * sizeof _array[0]); \
            }                                                    \
        } while (0)

that you declare your array as:

      struct the_thing *array_of_things;
      size_t array_of_things_len = 0;
      size_t array_of_things_cap = 0;

and then, you use it:

      /* I need n1 elements */ 
      DYNARRAY_GROW(array_of_things,  /* the array */
                    struct the_thing, /* the type of element (see note below) */
                    1,                /* only one element more needed */
                    10);              /* in case of grow, 10 elements are to be added */

In which the parameters

  • array_of_things is the array name. See from the macro definition that the _cap suffixed variable and the _len suffixed variables are derived from the name used here, so names are tied together (this makes less error prone the code)
  • struct the_thing is the type of an array element. You will see that it is used only to cast the returned pointer from realloc() to the proper type (thing that normally is discouraged in C, but for environments in which C code is tested using C++ testing code ---like GTest/GMock--- this is required, to avoid the resulting error from the C++ compiler) So while this cast is not only unnecessary in C (and even dangerous to use) it is forbidden in C++, and to allow compatibility with C++ (and being part of a tested macro) has been provided for convenience.
  • 1 is the amount of required elements. It is supposed that you call this macro to add some number of elements (normally one) to it, one shot, this is the amount of such elements you are going to add.
  • 10 is the amount of elements to grow in case a grow is required. Despite the number of elements to grow you need, it is often far more efficient to allocate a bigger ammount so not every time we need one element we allocate only one, but a bunch of them, so the number of total calls to realloc() is decreased, adding efficiency to the overal code.

In your case, for example:

struct ShoppingList *
       shopping_list     = NULL;
size_t shopping_list_cap = 0,
       shopping_list_len = 0;

void addItem(
        struct ShoppingList *element)
{
    DYNARRAY_GROW(shopping_list,
                  struct ShoppingList,
                  1,   /* what is needed */
                  10); /* what we get in that case. */

    /* after that we're able to add it (we have one 
     * free slot at least to add an element) */
    shopping_list[shopping_list_len++] = *element; /* copy the data */
}

Of course, when you are done with the shopping list, you need to set all three global variables to NULL , 0 and 0 .

    free(shopping_list);
    shopping_list = NULL;
    shopping_list_len = shopping_list_cap = 0;

Note: I have deliberately not done error checking, because is not clear in your homework how it is to be handled the occasional (and rare) case that you get NULL from realloc() . This case is a bit cumbersome because it requires you to make a second copy of the pointer before calling realloc() (as the value stored in shopping_list will be destroyed by the assignment of NULL to the pointer variable, in case the realloc() fails)

Normally, a memory allocation fail means you have some memory leak somewhere, as:

  • systems have today enough virtual memory allocatable for each process (this means amount of physical memory plus swap space), so it is improbable that you allocate the total amount of memory of your machine in a single process.
  • In case you need to be limited the amount of data memory for your process, it is assumed you know how to deal with memory allocation errors to write a working solution for that case, just make a copy of the pointer, run the above code and if you got a NULL return value, then continue (if you still call) without the allocation (eg try a lower increment, wait some time so we have more memory, etc.) by recovering the copy into the array pointer.
  • If you run in an embedded system with small memory, it is many times undesired to dynamically allocate the memory because there's almost no heap space to do dynamic memory allocation. Then you have to preassign static arrays and run this way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM