Using an array of strings to implement a symbol table in C - c

I am trying to use an array of structs to create a symbol table. This is what I have so far, but I am having trouble allocating memory in the create function, is what I have so far correct?
I want something like this as my final result for arr
{ {"sym1"; 1}, {"sym2"; 2}, {"sym3"; 3} }
struct str_id {
char* s;
int id;
}
struct symbol_table {
int count;
struct str_id** arr;
}
struct symbol_table *symbol_table_create(void) {
struct symbol_table *stt = malloc(sizeof(struct symbol_table));
stt->count = 1;
stt->arr = malloc(sizeof(struct str_id*) * stt->count);
return stt;
}

Use descriptive names for identifiers, not cryptic short names (like s and str_id).
Avoid Systems Hungarian Notation (i.e. naming or prefixing identifiers after their type or what-they-are as opposed to what-they-mean).
In your case, I assume str_id is an abbreviation for struct_id (or string_id) - which is a bad name because it's already immediately obvious that it's a struct (or contains a string).
It was popular right until the 1990s when programmers started using more powerful editors and IDEs that kept track of variable types - it just isn't needed today.
*
Always check if a heap allocation succeeded or failed by comparing calloc and malloc's return values to NULL. This can be done with if( some_pointer ) abort().
Don't use assert( some_pointer ) because assertions are only enabled in debug builds, use abort instead as it signifies abnormal program termination compared to exit.
Pass a size_t parameter so consumers can specify the size of the symbol table.
Quantities of objects held in memory should be expressed as size_t (e.g. array indexers). Never use int for this!
You need to put a semi-colon at the end of each struct definition.
Are you sure you want an array-of-pointers-to-structs and not just an array-of-structs? In this case you can use inline structs and use a single allocation for the array, instead of allocating each member separately.
Because you're performing custom allocation, you must also define a destructor function.
struct symbol_table_entry {
char* symbolText;
int id;
};
struct symbol_table {
size_t count;
struct symbol_table_entry** entries;
};
struct symbol_table* create_symbol_table( size_t count ) {
struct symbol_table* stt = malloc( sizeof(struct symbol_table) );
if( !stt )
{
abort();
}
stt->count = count;
stt->entries = calloc( count, sizeof(struct symbol_table_entry) );
if( !stt->entries ) {
free( stt );
abort();
}
// Note that calloc will zero-initialize all entries of the array (which prevents debuggers showing garbage string contents) so we don't need to do it ourselves.
return stt;
}
void destroy_symbol_table( struct symbol_table* stt, bool free_strings ) {
if( stt->entries ) {
if( free_strings ) {
for( size_t i = 0; i < stt->count; i++ ) {
free( stt->entries[i]->symbolText );
}
}
free( stt->entries );
}
free( stt );
}

Related

Cannot free memory in a function

I'm writing a code in which I read some graphs from text file to process it later. I have one function to write graph to memory and second one which uses first one and then operates on this graph. The problem is that I allocate some memory in first function but I don't know where should I free it, because freeing it in first function crashed program, while in second function compiler says there is no such a struct.
struct Graph* createGraph(struct edge edges[], int wxk, int l)
{
// allocate memory for the graph data structure
//struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
struct Graph* graph = malloc(sizeof *graph);
graph->head = malloc( l * sizeof *(graph->head) );
// initialize head pointer for all vertices
for ( int i = 0; i < wxk; i++ ) {
graph->head[i] = NULL;
}
// add edges to the directed graph one by one
for ( int i = 0; i < l; i++ )
{
// get the source and destination vertex
int src = edges[i].src;
int dest = edges[i].dest;
double weight = edges[i].weight;
// allocate new node of adjacency list from `src` to `dest`
struct node* newNode = malloc(sizeof *(newNode) );
struct node* newNode2 = malloc( sizeof *(newNode2));
newNode->dest = dest;
newNode->weight = weight;
newNode->next = NULL;
if( graph->head[src] == NULL ) {
graph->head[src] = newNode;
} else {
for( newNode2 = graph->head[src]; newNode2->next != NULL; newNode2 = newNode2->next )
;
newNode2->next = newNode;
}
struct node* newNode3 = malloc( sizeof *(newNode3) );
struct node* newNode4 = malloc( sizeof *(newNode4) );
newNode3->dest = src;
newNode3->weight = weight;
newNode3->next = NULL;
if( graph->head[dest] == NULL ) {
graph->head[dest] = newNode3;
} else {
for( newNode4 = graph->head[dest]; newNode4->next != NULL; newNode4 = newNode4->next )
;
newNode4->next = newNode3;
}
}
return graph;
}
Here is first function code, in which I allocate memory to newNode, newNode2, newNode3 and newNode4. When I free this memory at end of this function, program crashes later.
void check_graph( char *plik)
{
FILE *in = fopen( plik, "r");
struct edge *edges = readfromfile(in);
int l = getl();
int wxk = getwxk();
struct Graph *graph = createGraph( edges, wxk, l);
struct FIFO queue;
short int *visited = malloc ( wxk * sizeof (int));
for( int i = 0; i < wxk; i++)
{
visited[i] = 0;
}
queue.vertices = (int *) malloc( wxk * sizeof(int) );
queue.front = 0;
queue.end = 0;
add_to_queue( &queue, 0);
visited[0] = 1;
while( queue.front != queue.end)
{
int current_vertex = del_from_queue( &queue);
struct node *tmp = graph->head[current_vertex];
while( tmp != NULL)
{
int adjVertex = tmp->dest;
if( visited[adjVertex] == 0)
{
visited[adjVertex] = 1;
add_to_queue( &queue, adjVertex);
}
tmp = tmp->next;
}
}
free(queue.vertices); // czyszczenie pamięci
free(visited);
free(edges);
for( int i = 0; i < wxk; i++ )
free( graph->head[i] );
free(graph->head);
free(graph);
}
If I try to free the previous memory here, compiler says that names of variables are undeclared
Short answer
Freeing memory should be handled in separate functions that destroy a specific object, one for (adjacency) lists and one for graphs (which calls the adjacency list destroying function). The adjacency list destructor should iterate over a list, freeing nodes as it visits them (note the nodes are freed using the destructor's own local variables, not the newNodeI variables in the graph constructor). The graph destructor would be called from check_graph. Note that this parallels how creation is handled in the code.
Longer answer
The program would greatly benefit from following some fundamental design principles. In particular, break up the functions into more basic operations, each of which performs a single task (akin to the Single Responsibility Principle from OOP). When considering the sub-tasks of a task, they should be at the same level and in the same domain (more on this later). Additionally, functions shouldn't be overlong. Repeated code is a candidate for abstraction into a separate function (Don't Repeat Yourself), as long as it is conceptually a single task. Though the program may not be explicitly object-oriented, some OO conventions can be usefully applied. Variable names should be descriptive.
Start thinking about function names. The sample has createGraph and check_graph, a mix of naming conventions. This isn't inherently wrong, but naming conventions should only be mixed when each convention is doing something different, and are in different parts of a program. One C convention for naming methods in an OO manner is to use DromedaryCase for class names and camelCase for method names (as is done in C++), and connect the two with an underscore (basically, snake case) (e.g. ClassName_methodName). Extending this, the underscore indicates going down in scope, so nested class methods would be named as: Outer_Inner_methodName. Alternatives include using camelCase for class names, or snake case for everything, or snake case but with a double underscore for scope (e.g. outer_class__inner_class__method_name). "Private" methods can be indicated with a leading underscore.
The check_graph function performs the following sub-tasks:
opens a file
causes edges to be read from file
causes a Graph object to be created
allocates space for a member field of a queue (queue.vertices)
traverses the graph breadth-first
examines queue members to determine when it's empty
destroys the queue member, edges, and Graph
This mixes different levels of tasks (e.g. causing a Graph object to be created (which happens in a different function) but destroying the object itself; creating a part of the queue) and domains (e.g. file I/O, memory management, and graph algorithms), resulting in multiple responsibilities. Reading objects from files should be handled by a component whose responsibility it is to bridge I/O and object creation. Destroying the graph object should be handled by a separate function, a counterpart to createGraph (or Graph_create, if you use the convention above). This in particular should resolve the issue in question. Queue manipulation should be farmed out to queue functions, encapsulating the operations and data.
The majority of the lines in check_graph are concerned with the breadth-first traversal of the graph. This could be the basis for a function that implements the BFS algorithm, taking a callback that's called for each vertex as it's visited. check_graph would then call the BFS function.
A sketch of a refactored version:
typedef void (*Graph_visitor_t)(Graph_t *graph, int iVertex, void *additional);
/**
* Breadth-first traversal of a graph
*
* visit: a callback, invoked for each vertex when visited
* pAdditional: additional data passed along to the `visit` function
*/
void Graph_bfs(Graph_t *graph, Graph_visitor_t visit, void *pAdditional) {
// TODO: detect & handle memory errors
bool *visited = calloc(sizeof(*visited), graph->nVertices);
IntQueue_t *queue = IntQueue_create(graph->nVertices);
visit(graph, 0, pAdditional);
visited[0] = 1;
IntQueue_push(queue, 0);
while (! IntQueue_empty(queue)) {
int current_vertex = IntQueue_pop(queue);
/* much the same as the original `check_graph` (only
* add a call to `visit`)
*/
// ...
}
IntQueue_destroy(queue);
free(visited);
}
void _Graph_countVisited(Graph_t* graph, int iVertex, int *pnVisited) {
++(*pnVisited);
}
// Demonstrates how to use Graph_bfs (check_graph woudl be similar).
void Graph_isConnected(Graph_t *graph) {
int nVisited = 0;
Graph_bfs(graph, &_Graph_countVisited, &nVisited);
return nVisited == graph->nVertices;
}
createGraph performs the following sub-tasks:
allocates the graph object & members
allocates the adjacency list nodes
traverses adjacency lists
adds nodes to adjacency lists
Again, some of these tasks are at different levels and should be farmed out (e.g. adjacency list manipulation). The code that manipulates the adjacency list within the loop is also repetitive, and is a great candidate for being moved to another function.
Many of the variable names (e.g. l, wxk, newNode3) aren't very descriptive, leading to some bugs. For example, in createGraph, graph->head is allocated to hold l entries, but wxk entries are accessed when initializing it (in this case, the better fix is to use calloc instead of manually initializing all entries to NULL). If these variables were name more descriptively, e.g. nVertices and nEdges (I'm guessing as to purpose), the bug would be more obvious and likely wouldn't have occurred in the first place.
void _Graph_addAdjacency(Graph_t *graph, int from, int to, double weight) {
Node_t *newNode = List_Node_create(to, weight);
if (graph->head[from] == NULL ) {
graph->head[from] = newSrcNode;
} else {
List_append(graph->head[from], newSrcNode);
}
}
void _Graph_addEdge(Graph_t *graph, Edge_t *edge) {
_Graph_addAdjacency(graph, edges[i].src, edges[i].dest, edges[i].weight);
_Graph_addAdjacency(graph, edges[i].dest, edges[i].src, edges[i].weight);
}
Graph_t* Graph_create(Edge_t edges[], int nEdges, int nVertices) {
//// allocation
// TODO: detect & handle memory errors
Graph_t *graph = malloc(sizeof *graph);
graph->head = calloc(sizeof *(graph->head), nVertices);
graph->nVertices = nVertices;
//// initialization
// add edges to the directed graph one by one
for (int i = 0; i < nEdges; i++) {
// TODO: add error detection
_Graph_addEdge(graph, edges[i]);
}
return graph;
}
Rounding out the example are functions to read the graph from the file (Graph_readFromPath) and to tie it all together (main in this example, though in a larger program it wouldn't be the main function).
Graph_t* Graph_readFromPath(const char *fName) {
FILE *in = fopen(fName, "r");
int nVertices = Count_readFromFile(in);
int nEdges = Count_readFromFile(in);
Edge_t *edges = Edges_readFromFile(in, nEdges);
fclose(in);
Graph_t* graph = Graph_create(Edge_t edges[], nEdges, nVertices);
free(edges);
return graph;
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "No input file given.");
return 1;
}
const char *fName = argv[1];
if (access(fname, R_OK)) {
fprintf(stderr, "Error reading input file '%s': %s", fName, strerror(errno));
return 1;
}
Graph_t *graph = Graph_readFromFile(fName);
if (! Graph_isConnected(graph)) {
// ...
}
Graph_destroy(graph);
return 0;
}

How to correctly and safely free() all memory used a nested struct in C?

I have four different layers of struct nested. The code is as follows:
typedef struct System system;
typedef struct College college;
typedef struct Student student;
typedef struct Family family;
#define MAX_COLLEGES 10
#define MAX_NAME_LEN 32
#define MAX_STUDENTS 10
struct System {
college *Colleges[MAX_COLLEGES];
};
struct College {
char name[MAX_NAME_LEN];
student *Students[MAX_STUDENTS];
};
struct Student {
char name[MAX_NAME_LEN];
int id;
family *fam; //was typo familiy
};
struct Family {
char fatherName[MAX_NAME_LEN];
char motherName[MAX_NAME_LEN];
};
And I allocated memory to all of them (I'm not sure if I allocated all of them correctly), as follows:
system *collegeSys = malloc(sizeof(system));
college *colleges = malloc(sizeof(college));
student *students = malloc(sizeof(student));
family *fam = malloc(sizeof(family));
// then the following is initialization
...
...
...
Now, I need to delete the collegeSys structure and anything associated with it. So, I don't know if I can just free the first collegeSys struct without freeing any other structs, like this:
free(collegeSys);
Or in order to "delete anything associated with it", I have to free everything bottom-up, like this:
free(fam);
free(students);
free(colleges);
free(collegeSys);
Or to that end, I even have to free anything included inside each struct and free them bottom-up, like this:
free (fam -> fatherName);
free (fam -> motherName);
free (fam);
free (students -> name);
free (students -> id);
free (students -> fam);
free (students)
.
. till
.
free (collegeSys -> colleges);
free (collegeSys);
Which one is the correct and safe way to free the memory? Or none of them is?
I don't really understand point of having array of pointers, it could be done with pointer.
Definition:
struct System {
college *Colleges;
};
struct College {
char name[MAX_NAME_LEN];
student *Students;
};
struct Student {
char name[MAX_NAME_LEN];
int id;
familiy *fam;
};
struct Family {
char fatherName[MAX_NAME_LEN];
char motherName[MAX_NAME_LEN];
};
Allocation and initialization :
system *collegeSys = malloc(sizeof(*collegeSys));
collegeSys->colleges = malloc(MAX_COLLEGES * sizeof(*(collegeSys->colleges)));
collegeSys->colleges->students = malloc(MAX_STUDENTS * sizeof(*(collegeSys->colleges->students)));
collegeSys->colleges->students->fam = malloc(sizeof(*(collegeSys->colleges->students->fam)));
Freeing:
free(collegeSys->colleges->students->fam);
free(collegeSys->colleges->students);
free(collegeSys->colleges);
free(collegeSys);
Update:
Like I want to have struct student A, B, C, D under a struct college
collegeSys->colleges->students[0] = A;
collegeSys->colleges->students[1] = B;
collegeSys->colleges->students[2] = C;
collegeSys->colleges->students[3] = D;
Should do it.
If yo have array of students you can use memcpy or copy in loop.
struct student stud[MAX_STUDENTS] = {...};
memcpy(collegeSys->colleges->students[2], stud, MAX_STUDENTS);
or
for (int i = 0; i< MAX_STUDENTS; i++)
collegeSys->colleges->students[i] = stud[i];
Note:
You can assign the array to collegeSys->colleges->students in that case you don't need dynamic memory allocation or freeing.
// collegeSys->colleges->students = malloc(MAX_STUDENTS * sizeof(*(collegeSys->colleges->students))); //Leaks memory
collegeSys->colleges->students = stud;
//free(collegeSys->colleges->students); //wrong
When you allocate a structure then set all pointers in it to NULL.
For example to allocate your College structure you need to set all the students to NULL:
struct College* CollegeAlloc( char name[MAX_NAME_LEN] ) {
struct College* college = malloc( sizeof(struct College) );
if ( college ) {
for ( int i = 0; i < MAX_STUDENTS; ++i )
college->Students[i] = NULL;
memcpy( college->name, name, MAX_NAME_LEN );
}
return college;
}
Alternatively you could add a count field in the structures for each array, in order to count the number of array elements that are actually used.
If you set the array element to NULL when not used then you can free from the bottom up first.
void FamilyFree( struct Family *fam ) {
free( fam );
}
void StudentFree( struct Student *student ) {
if ( student ) {
FamilyFree( student->fam );
free( student );
}
}
void CollegeFree( struct College *college ) {
if ( college ) {
for ( int i = 0; i < MAX_STUDENTS; ++i )
StudentFree( college->Students[i] );
free( college );
}
}
void SystemFree( struct System *sys ) {
if ( sys ) {
for ( int i = 0; i < MAX_COLLEGES; ++i )
CollegeFree( sys->Colleges[i] );
free( sys );
}
}
Note this assumes that there is no sharing of pointers, e.g. the same student being at more than one college (when the implementation has allocated just one structure for each student), or when there are two siblings who share the same family structure. (the family structure does not model families very well, e.g. single parents, divorced, remarried, gay parents, legal guardians).
When structures can be shared then you can put a reference count in the structure and free only when it is reduced to zero.

How to design a function which return a array of oid

As already written at issue#2217, I want to design a function which return a list of oid in the first out param.
Should I:
Return the list of oids as a pointer to pointer?
int git_commit_tree_last_commit_id(git_oid **out, git_repository *repo, const git_commit *commit, char *path)
Or return the list of oids as a pointer to a custom struct?
int git_commit_tree_last_commit_id(git_oid_xx_struct *out, git_repository *repo, const git_commit *commit, char *path)
What is your advice?
The question is, how do you know how many OIDs are in the returned array, and who allocates the underlying memory.
For the first part there are several possibilities,
Return the number in a separate return parameter,
Use a sentinel value to terminate the list.
Return a new struct type, like git_strarray that contains the count and the
raw data.
For the second part, either
the caller can allocate the underlying memory
The function can allocate the memory
the new struct type can manage the memory.
Which path you go down depends upon what you want the code to look like, how much you expect it to be reused, how critical performance is etc.
To start with I'd go with the simplest, which IMO is function returns count and allocates memory.
That means my function would have to look like this:
int get_some_oids_in_an_array(OID** array, int * count, ... ) {
...
*count = number_of_oids;
*array = (OID*)malloc( sizeof(OID)*number_of_oids);
for(i=0; i<number_of_oids; ++i) {
*array[i]=...;
}
...
return 0;
}
/* Example of usage */
void use_get_oids() {
OID* oids;
int n_oids;
int ok = get_some_oids_in_an_array(&oids, &n_oids, ...);
for(i=0; i<n_oids; ++i ) {
... use oids[i] ...
}
free(oids);
}
Note: I'm returning an array of OID, rather than an array of OID*, either is a valid option, and which will work best for you will vary.
If it turned out I was using this kind of pattern often, then would consider switching to the struct route.
int get_some_oids( oidarray * oids, ... ) {
int i;
oidarray_ensure_size(number_of_oids);
for(i=0; i<number_of_oids; ++i) {
oidarray_set_value(i, ...);
}
return 0;
}
typedef struct oidarray {
size_t count;
OID* oids;
};
/* Example of usage */
void use_get_oids() {
oid_array oids = {0};
get_some_oids(&oids);
for(i=0; i<oids.count; ++i) {
... use oids.oids[i] ...
}
oidarray_release(&oids);
}

Alternative to heap allocated strings in C (with long lifetimes)

Is it possible to use "temporary string objects" in a C program?
For example, I have a reasonably large array of char * objects (part of a struct), which are currently using heap allocated memory. I have an opportunity to reduce my program's memory usage, since most of these names can be determined without using an explicit character array (although not all of them can).
In C++, I'd simply create an API that returns a std::string object (by value) and be done with it. In C, I can come up with no solutions that I'm thrilled about. Advice?
Here's some code, as requeted:
struct FOO {
char *name;
...
};
extern FOO* global_foo_array; /* in .h file */
void setup_foo(void) {
int i;
global_foo_array = (FOO*) malloc ( get_num_foo() * sizeof(FOO) );
for (i = 0; i < get_num_foo_with_complex_name(); ++i) {
global_foo_array.name[i] = malloc ( ... );
}
for (i = get_num_foo_with_complex_name(); i < get_num_foo(); ++i) {
char buf[100];
sprintf( buf, "foo #%d", i );
global_foo_array[i].name = strdup( buf );
}
}
get_num_foo() is approximately 100-1000x larger than get_num_foo_with_complex_name(). The program, for the most part, treats 'global_foo_array[].name' as read-only.
I see a non-negligible memory savings here and am wondering what's the best way to achieve that savings, balancing the human investment.
If you don't actually need all the strings, the obvious choice would be to create them on demand:
struct FOO {
char *name;
...
};
static FOO* global_foo_array; /* NOT in .h file */
void setup_foo(void) {
int i;
global_foo_array = (FOO*) malloc ( get_num_foo() * sizeof(FOO) );
memset(global_foo_array, 0, get_num_foo() * sizeof(FOO) );
}
FOO *get_foo(int i) {
if (i < 0 || i > get_num_foo())
return 0;
if (!global_foo_array[i].name) {
if (i < get_num_foo_with_complex_name()) {
global_foo_array.name[i] = malloc ( ... );
} else {
char buf[32];
sprintf( buf, "foo #%d", i );
global_foo_array[i].name = strdup( buf );
}
}
return &global_foo_array[i];
}
This still wastes space for all the FOO objects you don't need. If those are large, it might be better to have static FOO **global_foo_array (an extra level of indirection), and allocate those on demand too.

Pointer Conventions with: Array of pointers to certain elements

This question is about the best practices to handle this pointer problem I've dug myself into.
I have an array of structures that is dynamically generated in a function that reads a csv.
int init_from_csv(instance **instances,char *path) {
... open file, get line count
*instances = (instance*) malloc( (size_t) sizeof(instance) * line_count );
... parse and set values of all instances
return count_of_valid_instances_read;
}
// in main()
instance *instances;
int ins_len = init_from_csv(&instances, "some/path/file.csv");
Now, I have to perform functions on this raw data, split it, and perform the same functions again on the splits. This data set can be fairly large so I do not want to duplicate the instances, I just want an array of pointers to structs that are in the split.
instance **split = (instance**) malloc (sizeof(instance*) * split_len_max);
int split_function(instance *instances, ins_len, instances **split){
int i, c;
c = 0;
for (i = 0; i < ins_len; i++) {
if (some_criteria_is_true) {
split[c++] = &instances[i];
}
return c;
}
Now my question what would be the best practice or most readable way to perform a function on both the array of structs and the array of pointers? For a simple example count_data().
int count_data (intances **ins, ins_len, float crit) {
int i,c;
c = 0;
for (i = 0; i < ins_len; i++) {
if ins[i]->data > crit) {
++c;
}
}
return c;
}
// code smell-o-vision going off by now
int c1 = count_data (split, ins_len, 0.05); // works
int c2 = count_data (&instances, ins_len, 0.05); // obviously seg faults
I could make my init_from_csv malloc an array of pointers to instances, and then malloc my array of instances. I want to learn how a seasoned c programmer would handle this sort of thing though before I start changing a bunch of code.
This might seem a bit grungey, but if you really want to pass that instances** pointer around and want it to work for both the main data set and the splits, you really need to make an array of pointers for the main data set too. Here's one way you could do it...
size_t i, mem_reqd;
instance **list_seg, *data_seg;
/* Allocate list and data segments in one large block */
mem_reqd = (sizeof(instance*) + sizeof(instance)) * line_count;
list_seg = (instance**) malloc( mem_reqd );
data_seg = (instance*) &list_seg[line_count];
/* Index into the data segment */
for( i = 0; i < line_count; i++ ) {
list_seg[i] = &data_seg[i];
}
*instances = list_seg;
Now you can always operate on an array of instance* pointers, whether it's your main list or a split. I know you didn't want to use extra memory, but if your instance struct is not trivially small, then allocating an extra pointer for each instance to prevent confusing code duplication is a good idea.
When you're done with your main instance list, you can do this:
void free_instances( instance** instances )
{
free( instances );
}
I would be tempted to implement this as a struct:
struct instance_list {
instance ** data;
size_t length;
int owner;
};
That way, you can return this from your functions in a nicer way:
instance_list* alloc_list( size_t length, int owner )
{
size_t i, mem_reqd;
instance_list *list;
instance *data_seg;
/* Allocate list and data segments in one large block */
mem_reqd = sizeof(instance_list) + sizeof(instance*) * length;
if( owner ) mem_reqd += sizeof(instance) * length;
list = (instance_list*) malloc( mem_reqd );
list->data = (instance**) &list[1];
list->length = length;
list->owner = owner;
/* Index the list */
if( owner ) {
data_seg = (instance*) &list->data[line_count];
for( i = 0; i < line_count; i++ ) {
list->data[i] = &data_seg[i];
}
}
return list;
}
void free_list( instance_list * list )
{
free(list);
}
void erase_list( instance_list * list )
{
if( list->owner ) return;
memset((void*)list->data, 0, sizeof(instance*) * list->length);
}
Now, your function that loads from CSV doesn't have to focus on the details of creating this monster, so it can simply do the task it's supposed to do. You can now return lists from other functions, whether they contain the data or simply point into other lists.
instance_list* load_from_csv( char *path )
{
/* get line count... */
instance_list *list = alloc_list( line_count, 1 );
/* parse csv ... */
return list;
}
etc... Well, you get the idea. No guarantees this code will compile or work, but it should be close. I think it's important, whenever you're doing something with arrays that's even slightly more complicated than just a simple array, it's useful to make that tiny extra effort to encapsulate it. This is the major data structure you'll be working with for your analysis or whatever, so it makes sense to give it a little bit of stature in that it has its own data type.
I dunno, was that overkill? =)

Resources