1

I need to get the number of elements in a particular array of structs that has been initialised in a separate function, how can i do this?

function initialising array:

    Hashtbl* init_hashtbl(){
       Hashtbl* hashtbl;
       hashtbl = calloc(SIZE, sizeof(Hashtbl));
       for(int i = 0; i<SIZE; i++){
           hashtbl[i].subscript = malloc(MAX_LENGTH + 1);
           strcpy(hashtbl[i].subscript, "ZERO\n");

           hashtbl[i].value = malloc(MAX_LENGTH + 1);
           strcpy(hashtbl[i].value, "ZERO\n");
       }
       return hashtbl;
   }

the main function (this is just an example of a typical use i'd need):

    int main() {
        Hashtbl* nums;
        nums = init_hashtbl();
        insert(nums, "example", "example");
        /*insert returns hashtable same as init_hashtbl but if array is full
        carries out a realloc*/
        printf("%d", /*number of elements*/);

    }

the Hashtable structure:

    typedef struct Hashtbls{
        char *subscript;
        char *value;
    } Hashtbl;

and a further question. In most cases such as the case of the above function intialising the hashtable, I return something where malloc or calloc has been used. How can i free this to avoid memory leaks, keeping in mind that I am working with header files and that the main function is just a test driver where the user isn't expected to free from his part?

John
  • 71
  • 1
  • 1
  • 10
  • 1
    Generally it's impossible to get the size of the memory pointed to by a pointer. In your case it's extremely simple as it will always be `SIZE`. – Some programmer dude Jan 14 '18 at 16:15
  • sorry needed to edit that part as what i wanted was to illustrate an example without having to have people review too much code (trying to make life easier) – John Jan 14 '18 at 16:23
  • @JohnParnis Try to ask only one question per thread, otherwise the answers can be very long. It's better to create a thread per question. – Pablo Jan 14 '18 at 17:23
  • 1
    One option not mentioned previously (AFAICS) is to make a structure that encapsulates both the pointer to the array and the size of the array: `struct HashTable { size_t size; Hashtbl *table; };` and you can pass that type around appropriately. – Jonathan Leffler Jan 14 '18 at 19:14

1 Answers1

1

There are some options you have.

1. Pass the number of elements to init_hashtbl() function:

Hashtbl* init_hashtbl(size_t num_of_elements){
   Hashtbl* hashtbl;
   hashtbl = calloc(num_of_elements, sizeof(Hashtbl));
   for(int i = 0; i<num_of_elements; i++){
       hashtbl[i].subscript = malloc(MAX_LENGTH + 1);
       strcpy(hashtbl[i].subscript, "ZERO\n");

       hashtbl[i].value = malloc(MAX_LENGTH + 1);
       strcpy(hashtbl[i].value, "ZERO\n");
   }
   return hashtbl;

}

int main() {
    Hashtbl* nums;
    nums = init_hashtbl(SIZE);
    ...
}

This way you control how many elements a table is initialized with.

2. Pass a pointer to size_t and let the init function write it

Hashtbl* init_hashtbl(size_t *size){
   Hashtbl* hashtbl;
   hashtbl = calloc(SIZE, sizeof(Hashtbl));
   for(int i = 0; i<SIZE; i++){
       hashtbl[i].subscript = malloc(MAX_LENGTH + 1);
       strcpy(hashtbl[i].subscript, "ZERO\n");

       hashtbl[i].value = malloc(MAX_LENGTH + 1);
       strcpy(hashtbl[i].value, "ZERO\n");
   }
   *size = SIZE; // <-- saving the size for the caller through
                 // pointer
   return hashtbl;

}

int main() {
    Hashtbl* nums;
    size_t table_size
    nums = init_hashtbl(&table_size);
    ...
}

For all functions that manipulate the table but don't resize it, they should all get the size_t parameter. For example your loadfromfile1 function.

Hashtbl* loadfromfile(Hashtbl* hashtbl, size_t size, char *path);

And all functions that resize it should get a pointer and change it, like your insert function2:

Hashtbl *insert(Hashtbl *hashtbl, size_t *size, const char *subscript, const char *value)
{
    // check that the params are not NULL

    // your code here

    // need to resize
    size_t newsize = size + 1;
    Hashtbl *tmp = realloc(hashtbl, newsize * sizeof *tmp);
    if(tmp == NULL)
    {
        // error handling
        return NULL;
    }

    hashtbl = tmp;
    *size = newsize;

    // here you need to initialize your subscript, values for the
    // new hashtbl entries

    // ...

    return hashtbl;
}

Note that I changed the signature of the function insert. It now returns a new pointer to the table and updates size if and only if the realloc was successful.3

Calling insert is the like calling realloc

Hashtbl *tmp = insert(hashtbl, &size, "example", "example");
if(tmp == NULL)
{
    // could not resize,
    // the unresized table is still in hashtbl
    // and size still the old value
}

hashtbl = tmp;

About freeing the memory

I advice you to create a destroy function (or use another name, that doesn't matter). When your structs have pointers to dynamically allocated memory, it's best to initialize them to NULL. Using calloc is a good way to do that.

If you encounter an error or want to free the whole stack, then you only have to call your destroy function. The destroy function will then free all the memory. For example:

hashtbl_destroy(Hashtbl *hashtbl, size_t size)
{
    for(int i = 0; i < size; ++i)
    {
        free(hashtbl[i].subscript);
        free(hashtbl[i].value);
        free(hashtbl[i]);
    }
}

Doing free(NULL) is allowed and that's it's better to initialize your pointers will NULL before you allocate memory, because in case that you encounter a failure while initializing, you just call the destroy function.


One last thing: When you are designing a structure like Hashtbl and you have a number of function that manipulate the table, I recommend that you name your function using a prefix, for example hashtbl_.

hashtbl_create(...);
hashtbl_init(...);
hashtbl_destroy();
hashtbl_insert();
hashtbl_remove();

It's easy to avoid naming conflicts when you have multiple insert functions.


Annotations

1 The function loadfromfile came up in this question: Why is reading from file function crashing?

2 When a function expects a pointer, it's always a good thing to tell whether your are going to modify the contents of the data pointed to by the pointer. In case of strings, whether you are going to manipulate the string.

void some_func(char *txt);

Shows that some_func potentially manipulates the string, for example lowering the case of the first letter. In that case it's better not to pass a string literal:

some_func("This might cause a crash");
char txt[] = "This will not";
some_func(txt);

If however your function is not going to manipulate the string, then it's better to declare you function like this:

void some_other_function(const char *txt);

As a caller of some_function I know that the content is not going to change, so

some_other_function("Not a problem");

That's why I use const char * for subscript and value in the insert function.

3 You could also use another technique and instead of returning the new table, you can pass a double pointer

int insert(Hashtbl **hashtbl, size_t *size, const char *subscript, const char *value)
{
    // check that the params are not NULL

    // your code here

    // need to resize
    size_t newsize = size + 1;
    Hashtbl *tmp = realloc(*hashtbl, newsize * sizeof *tmp);
    if(tmp == NULL)
    {
        // error handling
        return NULL;
    }

    *hashtbl = tmp;
    *size = newsize;

    // here you need to initialize your subscript, values for the
    // new hashtbl entries

    // ...

    return 1; // 1 on success, 0 on failure
}

if(!insert(&hashtbl, &size, "example", "example"))
{
    // could not resize,
    // the unresized table is still in hashtbl
    // and size still the old value
}
Pablo
  • 13,271
  • 4
  • 39
  • 59