Search code examples
cfunctionstructure

Pass a structure to a function


I know you can pass specific elements of a structure to a function, but is it possible to pass an entire structure, similar to how you could pass an entire 1 dimensional array? I'm trying to pass movie num[1,2,3] etc. to a function. I'm pretty sure you could pass it using a for loop, but is it possible to pass the structure in its entirety without a loop? My code is below.

#include <stdlib.h>
#include <stdio.h>

void InputData(struct movie *num[50]);

int main()
{
    struct movie
    {
        char name[50];
        int curatornum;
        int votes;
    };

    struct movie *num[50];

    for (int i = 0; i <= 50; i++)
    {
        num[i] = (struct movie *)malloc(1 * sizeof(struct movie *));
    }

    InputData(num);

    for (int i = 50; i > 0; i--)
    {
        free(num[i]);
    }
}

void InputData(struct movie *num[50])
{
    //Compiler gives an error: conflicting types for 'InputData'void InputData(struct movie *num[50]
}

Solution

  • Yes, you can return the entire structure, or just the address of one. You can even return an array of nested things and so on.

    You can use of a loop and pass each movie, but that would imply and calling the function for each element and I believe this is not what you want to do.

    About the provided code

    The are many problems in the code, and this is probably not the best way to write this in C.

    I will add below 2 examples: one with some changes to the original code and other using a common way to write this in C

    one-of errors

        for(int i=0; i<=50; i++)
        {
            num[i]=(struct movie *)malloc(1*sizeof(struct movie *));
        }
        InputData(num);
        for(int i=50; i>0; i--)
        {
            free(num[i]);
        }
    

    The compiler warned you about this: from 0 to 50 there are 51 values. These loops are wrong.

    And there is no reason to invert the order to free an array of pointers. You do this when you have pointers to dynamic data inside a dynamic structure: then you must free the areas allocated inside and then going up free'ing the struct itself.

    InputData()

        void InputData(struct movie *num[50]);
    

    No, do not return void: it is a waste, many times an error. Here you could simply return the adress of an array of struct movie.

    Here InputData gets and array of 50 pointers to struct movie. num is struct movie* [50] as your IDE can probably tell you.

    Not entering in religious discussion here, but in C you declare a name for a variable of a type. If the type is a pointer, so be it. If num is a pointer then it is clear that *num is a value of the thing num points to. It is called dereferencing and it is basic C. But it not what you declare.

    If num is int* then *num is an int. But you declare num.

    So it is easier to reason about this

        struct movie*                   num[50];
    

    than to read

        struct movie                    *num[50];
    

    because you are declaring num as struct movie* [50].

    making InputData() work in the original code

    Anyway, you have in main

        struct movie*                  num[50];
    

    And this is the static data you want InputData to act upon: an array of 50 pointers to a struct. So you need to pass its address to InputData and it is what your compiler is trying to tell you with something like

    warning C4133: 'function': incompatible types - from 'movie *[50]' to 'movie **'
    

    Changing the argument in InputData() to

        void   InputData(Movie* (*num)[50]);
    

    would tell the compiler that InputData will receive a pointer to the array of pointers.

    The common way of write this is to allocate the array inside InputData and return its address. See the second example

    This code is from the example I will left below:

        Movie* num[50] = {0};
        InputData(&num);
        printf(
            "\
        First movie is \"%s\", num = %6d, votes = %2d\n\
         Last movie is \"%s\", num = %6d, votes = %2d\n\
    \n",
            num[0]->name, num[0]->curatornum, num[0]->votes,
            (*num[49]).name, (*num[49]).curatornum, (*num[49]).votes);
    

    So you see that the array num is declared in main and its address is passed to InputData(). The function uses this area to write the data in. And you see here the 2 common notations in the printf to display the first and last movie of the array.

    Use a factory function

    When writing these kind of code never write interactive code. It makes testing a PITA. See this function:

    Movie* factory()
    {
        static int num   = 1;
        Movie*     movie = malloc(sizeof(Movie));
        if (movie == NULL) return NULL;  // malloc failed
        int value         = rand();
        movie->curatornum = num * 1000 + value % 1000;
        movie->votes      = value % 10;
        sprintf(movie->name, "Title %07d", num);
        num += 1;
        return movie;
    }
    

    Anytime you call it you get a Movie, repeatable, reliable. From the example:

    void InputData(Movie* (*num)[50])
    {
        for (size_t i = 0; i < 50; i += 1)
            (*num)[i] = factory();
    }
    

    this version of InputData() gets the data right away. Always. 50 or 50,000 movies.

    Here are the 1st and last movies in a run of the example:

        First movie is "Title 0000001", num =   1650, votes =  1
         Last movie is "Title 0000050", num =  50161, votes =  2
    

    And the math in factory() helps to see if the values are ok.

    use a typedef

    You may find it easier to use

    typedef struct
    {
        char name[50];
        int  curatornum;
        int  votes;
    } Movie;
    

    And reference Movie in code instead of struct movie every time. This is also a religious thing, anyway.

    The example, using the provided code with minimum changes

    // https://stackoverflow.com/questions/77809883/passing-structure-through-function
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct
    {
        char name[50];
        int  curatornum;
        int  votes;
    } Movie;
    
    Movie* factory();
    void   InputData(Movie* (*num)[50]);
    
    int main(void)
    {
        srand(241012);
        Movie* num[50] = {0};
        InputData(&num);
        printf(
            "\
        First movie is \"%s\", num = %6d, votes = %2d\n\
         Last movie is \"%s\", num = %6d, votes = %2d\n\
    \n",
            num[0]->name, num[0]->curatornum, num[0]->votes,
            (*num[49]).name, (*num[49]).curatornum, (*num[49]).votes);
    }
    
    void InputData(Movie* (*num)[50])
    {
        for (size_t i = 0; i < 50; i += 1)
            (*num)[i] = factory();
    }
    
    Movie* factory()
    {
        static int num   = 1;
        Movie*     movie = malloc(sizeof(Movie));
        if (movie == NULL) return NULL;  // malloc failed
        int value         = rand();
        movie->curatornum = num * 1000 + value % 1000;
        movie->votes      = 1 + value % 10;
        sprintf(movie->name, "Title %07d", num);
        num += 1;
        return movie;
    }
    

    output

        First movie is "Title 0000001", num =   1650, votes =  1
         Last movie is "Title 0000050", num =  50161, votes =  2
    

    A second example: use a container object

    Here is a common way to write this kind of program, one that is easier and extensible.

    Here we have a Movie thing and some functions to operate on Movie

    typedef struct
    {
        char name[50];
        int  curatornum;
        int  votes;
    } Movie;
    
    Movie* factory();
    int    show_movie(Movie*, const char*);
    

    But our data is not a Movie, it is a collection of Movie. java uses this name, C++ uses container.

    Here we have Movies and some functions to operate on the collection itself

    
    typedef struct
    {
        size_t  capacity;
        size_t  size;
        Movie** M;  // pointers to movies
    } Movies;
    
    Movies* InputData(size_t);
    int     show_movies(Movies*, const char*);
    Movies* free_movies(Movies*);
    

    It is easier to understand if InputData returns a set of Movie, but we do not see Movie here. It is the concept of encapsulation: we can change the content of Movies at will.

        Movies* cat = InputData(10);
    

    returns cat with 10 Movie using factory and it is very easy to follow

    Here is main for the second example

    int main(void)
    {
        srand(241012);
        Movies* cat = InputData(10);
        show_movies(cat, "***** A simple test *****\n\n");
        free_movies(cat);
        return 0;
    }
    
    • srand()is perfect for testing since it will give always the same sequence during testing
    • InputData will get the address of a collection of Movies.
    • show_movies will do his thing and accepts a handy message for testing
    • free_movies will erase the catalog

    output of the example

    ***** A simple test *****
      10 of 10 movies:
    
      0     "Title 0000001", num =   1650, votes =  1
      1     "Title 0000002", num =   2399, votes = 10
      2     "Title 0000003", num =   3107, votes =  8
      3     "Title 0000004", num =   4023, votes =  4
      4     "Title 0000005", num =   5744, votes =  5
      5     "Title 0000006", num =   6209, votes = 10
      6     "Title 0000007", num =   7350, votes =  1
      7     "Title 0000008", num =   8950, votes =  1
      8     "Title 0000009", num =   9856, votes =  7
      9     "Title 0000010", num =  10703, votes =  4
    

    complete code

    // https://stackoverflow.com/questions/77809883/passing-structure-through-function
    
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct
    {
        char name[50];
        int  curatornum;
        int  votes;
    } Movie;
    
    Movie* factory();
    int    show_movie(Movie*, const char*);
    
    
    typedef struct
    {
        size_t  capacity;
        size_t  size;
        Movie** M;  // pointers to movies
    } Movies;
    
    Movies* InputData(size_t);
    int     show_movies(Movies*, const char*);
    Movies* free_movies(Movies*);
    
    int main(void)
    {
        srand(241012);
        Movies* cat = InputData(10);
        show_movies(cat, "***** A simple test *****\n\n");
        free_movies(cat);
        return 0;
    }
    
    Movies* InputData(size_t cap)
    {
        Movies* cat = malloc(sizeof(Movies));
        if (cat == NULL) return NULL;
        cat->capacity = cap;
        cat->size     = cap;  // will return the catalog full
        cat->M        = (Movie**)malloc(cap * sizeof(Movie*));
        if (cat->M == NULL)
        {
            free(cat);
            return NULL;
        }
        // get the movies into the catalog
        for (size_t i = 0; i < cap; i += 1)
            cat->M[i] = factory();
        return cat;
    }
    
    int show_movies(Movies* cat, const char* msg)
    { 
        if (cat == NULL) return -1;
        if (msg != NULL) printf("%s", msg);  // optional title
        printf(
            "  %llu of %llu movies:\n\n", cat->size, cat->capacity);
        for (size_t i = 0; i < cat->size; i += 1)
        {
            char index[8];
            sprintf(index, "  %llu\t", i);
            show_movie(cat->M[i], index);
        }
        return 0;
    }
    
    Movies* free_movies(Movies* cat)
    {
        if (cat == NULL) return NULL;
        for (size_t i = 0; i < cat->size; i += 1)
            free(cat->M[i]);
        free(cat->M);
        free(cat);
        return NULL;
    }
    
    Movie* factory()
    {
        static int num   = 1;
        Movie*     movie = malloc(sizeof(Movie));
        if (movie == NULL) return NULL;  // malloc failed
        int value         = rand();
        movie->curatornum = num * 1000 + value % 1000;
        movie->votes      = 1 + value % 10;
        sprintf(movie->name, "Title %07d", num);
        num += 1;
        return movie;
    }
    
    int show_movie(Movie* m, const char* msg)
    {
        if (m == NULL) return -1;
        if (msg != NULL) printf("%s", msg);  // optional title
        printf(
            "\"%s\", num = %6d, votes = %2d\n", m->name,
            m->curatornum, m->votes);
        return 0;
    }