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 loadfromfile
1
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
}