Below is an example of direct variable declaration.
double multiplyByTwo (double input) {
double twice = input * 2.0;
return twice;
}
Below is an example of dynamic memory allocation.
double *multiplyByTwo (double *input) {
double *twice = malloc(sizeof(double));
*twice = *input * 2.0;
return twice;
}
If I had a choice, I will use direct variable declaration all the time because the code looks more readable. When are circumstances when dynamic memory allocation is more suitable?
"If I had a choice, I will use direct variable declaration all the time"
As well you should. You don't use heap memory unless you need to. Which obviously begs the question: When do I need dynamic memory?
struct huge_struct array[10000]
). To get an idea of how big the stack is see this page. Note that the actual stack size may differ.realloc
), or you don't know how much memory you'll need to store something. An array that you've declared on the stack is fixed in size, a pointer to a block of memory can be re-allocated (malloc
new block >= current block size + memcpy
+ free
original pointer is basically what realloc
does)So, some examples to clarify:
//perfectly fine
double sum(double a, double b)
{
return a + b;
}
//call:
double result = sum(double_a, double_b);
//or to reassign:
double_a = (double_a, double_b);
//valid, but silly
double *sum_into(double *target, double b)
{
if (target == NULL)
target = calloc(1, sizeof *target);
*target = b;
return target;
}
//call
sum_into(&double_a, double_b);//pass pointer to stack var
//or allocate new pointer, set to value double_b
double *double_a = sum_into(NULL, double_b);
//or pass double pointer (heap)
sum_into(ptr_a, double_b);
Returning "arrays"
//Illegal
double[] get_double_values(double *vals, double factor, size_t count)
{
double return_val[count];//VLA if C99
for (int i=0;i<count;++i)
return_val[i] = vals[i] * factor;
return return_val;
}
//valid
double *get_double_values(const double *vals, double factor, size_t count)
{
double *return_val = malloc(count * sizeof *return_val);
if (return_val == NULL)
exit( EXIT_FAILURE );
for (int i=0;i<count;++i)
return_val[i] = vals[i] * factor;
return return_val;
}
Having to resize the object:
double * double_vals = get_double_values(
my_array,
2,
sizeof my_array/ sizeof *my_array
);
//store the current size of double_vals here
size_t current_size = sizeof my_array/ sizeof *my_array;
//some code here
//then:
double_vals = realloc(
double_vals,
current_size + 1
);
if (double_vals == NULL)
exit( EXIT_FAILURE );
double_vals[current_size] = 0.0;
++current_size;
Variables that need to stay in scope for longer:
struct callback_params * some_func( void )
{
struct callback_params *foo = malloc(sizeof *foo);//allocate memory
foo->lib_sum = 0;
call_some_lib_func(foo, callback_func);
}
void callback_func(int lib_param, void *opaque)
{
struct callback_params * foo = (struct callback_params *) opaque;
foo->lib_sum += lib_param;
}
In this scenario, our code is calling some library function that processes something asynchronously. We can pass a callback function that handles the results of the library-stuff. The lib also provides us with a means of passing some data to that callback through a void *opaque
.
call_some_lib_func
will have a signature along the lines of:
void call_some_lib_func(void *, void (*)(int, void *))
Or in a more readable format:
void call_some_lib_func(void *opaque, void (*callback)(int, void *))
So it's a function, called call_some_lib_func
, that takes 2 arguments: a void *
called opaque
, and a function pointer to a function that returns void, and takes an int and a void *
as arguments.
All we need to do is cast the void *
to the correct type, and we can manipulate it. Also note that the some_func
returns a pointer to the opaque pointer, so we can use it wherever we need to:
int main ( void )
{
struct callback_params *params = some_func();
while (params->lib_sum < 100)
printf("Waiting for something: %d%%\r", params->lib_sum);
puts("Done!");
free(params);//free the memory, we're done with it
//do other stuff
return 0;
}