This C code:
#include <stdlib.h>
typedef struct
{
int *a;
int sz, cap;
} Vector;
void vector_init(Vector *v)
{
v->a = (int *)malloc(sizeof(int));
v->sz = 0;
v->cap = 1;
}
int main()
{
Vector *v;
vector_init(v);
}
throws a bus error.
but replacing Vector *v
with Vector v
and vector_init(v)
with vector_init(&v)
solves it. why?
CodePudding user response:
When you write Vector *v
, you're creating an uninitialized pointer to Vector
. There's no object of type Vector
that exists in this program. v
is uninitialized, dereferencing it is undefined behaviour, and in practice will often lead to a crash because of an illegal memory access.
Defining a pointer does not automatically create memory for an object of the pointed-to type, you need to do that.
Doing Vector v
with vector_init(&v)
works because you're creating an actual object of type Vector
, then passing in a pointer to that.
To have an object of type Vector *
, you have to give it an object to point to. That could be by explicitly creating an object:
Vector v_storage;
Vector *v = &v_storage;
vector_init(v);
Though it'd be silly to do the above in actual code, you should just stick with the Vector v
and vector_init(&v)
.
You could also create storage for a Vector
using a dynamic memory allocation using malloc
, if that was appropriate for what you want to write, and then clean it up with free
later.
CodePudding user response:
Design your API to parallel malloc() and free()
The way to handle objects in C is to take a pointer on create and pass a pointer on destroy. That is, you should write functions that parallel malloc()
and free()
(or, in my personal preference, free-and-null):
Vector * Vector_create( size_t size );
Vector * Vector_destroy( Vector * v );
They are used thus:
Vector * v0 = Vector_create( 0 ); // create an empty vector
Vector * v100 = Vector_create( 100 ); // create a vector with 100 elements
v0 = Vector_destroy( v0 ); // free and null
Vector_destroy( v100 ); // zeroing your pointer is optional
Useful operations on your vector should likewise have proper signatures:
Vector * Vector_copy( Vector * v ); // deep copy a vector
int * Vector_at( Vector * v, size_t index ); // etc
void Vector_push_back( Vector * v, int x ); // may resize the vector!
Vector * v2 = Vector_copy( v ); // create new vector v2 == v
*Vector_at( v, 7 ) = 123456; // assign value to v[7] = 123456
Vector_push_back( v, 42 ); // increase size of v and assign 42 to new element (possible capacity change)
FAM
Depending on the object, I sometimes like to do as 0___________ did in his answer (use a flexible array member), but this does complicate your calling code a little because it removes a level of indirection — you must now pass references to your object as argument to every function (for consistency, even for functions that don’t need it, because some do!):
Vector * v = Vector_create( 0 );
Vector_push_back( &v, 42 ); // because v may be reallocated!
printf( "%d\n", *Vector_at( &v, 0 ) );
free( v ); // this might still be worth hiding behind a Vector_free() function
CodePudding user response:
- I would use flexible array members instead of pointers.
- In your code you need to pass double pointer In this case it is not the best idea, it is much better to use function return value instead side effects
- You need to allocate the memory for the structure itself, bot only for the data.
- Use correct types for sizes and indexes.
- Check for allocation errors!!
- Do not cast the result of malloc
typedef struct
{
size_t sz, cap;
int a[];
} Vector;
Vector *vector_init(void)
{
Vector *vi = malloc(sizeof(*vi) sizeof(vi->a[0]));
if(vi)
{
vi->sz = 0;
vi->cap = 1;
}
return vi;
}
If you do not want to return the value
void vector_init(Vector **vi)
{
*vi = malloc(sizeof(**vi) sizeof((*vi)->a[0]));
if(*vi)
{
(*vi)->sz = 0;
(*vi)->cap = 1;
}
}
and usage
int main()
{
Vector *v;
vector_init(&v);
}