Pointer Conventions with: Array of pointers to certain elements - c

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? =)

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;
}

Using an array of strings to implement a symbol table in 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 );
}

C pthread accessing an array from multiple threads

I have a problem for accessing an array from several threads. I have written a struct which gathers all informations needed for the job I want to do.
The structure is defined like this:
struct thread_blk
{
size_t th_blk_count;
size_t th_blk_size;
size_t th_blk_current;
size_t* th_blk_i_start;
void* data;
pthread_t* th_id;
ThreadBlockAdaptive th_blk_adapt;
};
The idea is to fill an array from multiple threads, each one working on a delimited field of memory of an array.
The th_blk_count field represents the amount of block that has to
be treated,
The th_blk_size field represents the size of a block,
The th_blk_current field represents the processed block (they are
listed from 0 to n),
The th_blk_i_start is an array which contains indexes of the array
that has to be filled.
Just a single function applied to the thread_blk struct is not working properly:
int startAllThreadBlock(struct thread_blk* th_blk, worker_func f)
{
int res = 0;
for(size_t i = 0; i < th_blk->th_blk_count; ++i)
{
res |= pthread_create(th_blk->th_id + i, NULL, f, th_blk);
th_blk->th_blk_current++;
}
return res;
}
In fact, the th_blk_current field is not incremented properly. I used it to retrieve the th_blk_i_start indexes which serve as intervals. As a result, my worker (shown bellow) is processing the same indexes of the double array.
Here is the function I use in the startAllThreadBlock function:
void* parallel_for(void* th_blk_void)
{
ThreadBlock th_blk = (ThreadBlock)th_blk_void;
size_t i = getThreadBlockStartIndex(th_blk, getThreadBlockCurrentIndex(th_blk));
printf(
"Running thread %p\n"
" -Start index %zu\n\n",
pthread_self(),
i
);
if(getThreadBlockCurrentIndex(th_blk) == (getThreadBlockCount(th_blk) - 1))
{
for(; i < MAX; ++i)
{
result[i] = tan(atan((double)i));
}
}
else
{
size_t threshold = getThreadBlockStartIndex(th_blk, getThreadBlockCurrentIndex(th_blk) + 1);
for(; i < threshold; ++i)
{
result[i] = tan(atan((double)i));
}
}
return NULL;
}
ThreadBlock is just a typedef over a thread_blk*; result is the array of double.
I am pretty sure that the problem lies around the startAllThreadBlock (if I use a 1 second sleep everything run as expected). But I don't know how to fix it.
Does someone have an idea?
Thanks for your answers.
Update
Placing the incrementation in the worker solved the problem. But I think it is not safe though, for the reason Some programmer dude mentioned.
void* parallel_for(void* th_blk_void)
{
ThreadBlock th_blk = (ThreadBlock)th_blk_void;
size_t i = getThreadBlockStartIndex(th_blk, getThreadBlockCurrentIndex(th_blk));
size_t n;
if(getThreadBlockCurrentIndex(th_blk) == (getThreadBlockCount(th_blk) - 1))
{
n = MAX;
}
else
{
n = getThreadBlockStartIndex(th_blk, getThreadBlockCurrentIndex(th_blk) + 1);
}
incThreadBlockCurrent(th_blk);
printf(
"Running thread %p\n"
" -Start index %zu\n\n",
pthread_self(),
i
);
for(; i < n; ++i)
{
result[i] = tan(atan((double)i));
}
return NULL;
}
It would do it with a mutex on th_blk_current no?
I think the problem here is that you think the thread gets passed a copy of the structure. It doesn't, it gets a pointer. All the threads get a pointer to the same structure. So any changes to the structure will affect all threads.
You need to come up with a way to pass individual data to the individual threads. For example a thread-specific structure containing only the thread-specific data, and you dynamically allocate an instance of that structure to pass to the thread.

C malloc use case - realloc versus pre-computation

I want to create an array of structs on the heap from another data structure. Say there are N total elements to traverse, and (N-x) pointers (computed_elements) will be added to the array.
My naive strategy for this is to create an array (temp_array) size N on the stack and traverse the data structure, keeping track of how many elements need to be added to the array, and adding them to temp_array when I encounter them. Once I've finished, I malloc(computed_elements) and populate this array with the temp_array.
This is suboptimal because the second loop is unnecessary. However, I am weighing this against the tradeoff of constantly reallocating memory every iteration. Some rough code to clarify:
void *temp_array[N];
int count = 0;
for (int i = 0; i < N; i++) {
if (check(arr[i])) {
temp_array[count] = arr[i];
count++;
}
}
void *results = malloc(count * sizeof(MyStruct));
for (int i = 0; i < count; i++) {
results[i] = temp_array[i];
}
return results;
Thoughts would be appreciated.
One common strategy is to try to estimate the number of elements you're going to need (not a close estimate, more of a "On the order of..." type estimate). Malloc that amount of memory, and when you get "close" to that limit ("close" also being up for interpretation), realloc some more. Personally, I typically double the array when I get close to filling it.
-EDIT-
Here is the "ten minute version". (I've ensured that it builds and doesn't segfault)
Obviously I've omitted things like checking for the success of malloc/realloc, zeroing memory, etc...
#include <stdlib.h>
#include <stdbool.h>
#include <string.h> /* for the "malloc only version" (see below) */
/* Assume 500 elements estimated*/
#define ESTIMATED_NUMBER_OF_RECORDS 500
/* "MAX" number of elements that the original question seems to be bound by */
#define N 10000
/* Included only to allow for test compilation */
typedef struct
{
int foo;
int bar;
} MyStruct;
/* Included only to allow for test compilation */
MyStruct arr[N] = { 0 };
/* Included only to allow for test compilation */
bool check(MyStruct valueToCheck)
{
bool baz = true;
/* ... */
return baz;
}
int main (int argc, char** argv)
{
int idx = 0;
int actualRecordCount = 0;
int allocatedSize = 0;
MyStruct *tempPointer = NULL;
MyStruct *results = malloc(ESTIMATED_NUMBER_OF_RECORDS * sizeof(MyStruct));
allocatedSize = ESTIMATED_NUMBER_OF_RECORDS;
for (idx = 0; idx < N; idx++)
{
/* Ensure that we're not about to walk off the current array */
if (actualRecordCount == (allocatedSize))
{
allocatedSize *= 2;
/* "malloc only version"
* If you want to avoid realloc and just malloc everything...
*/
/*
tempPointer = malloc(allocatedSize);
memcpy(tempPointer, results, allocatedSize);
free(results);
results = tempPointer;
*/
/* Using realloc... */
tempPointer = realloc(results, allocatedSize);
results = tempPointer;
}
/* Check validity or original array element */
if (check(arr[idx]))
{
results[actualRecordCount] = arr[idx];
actualRecordCount++;
}
}
if (results != NULL)
{
free(results);
}
return 0;
}
One possibility is malloc for the size N, then run your loop, then realloc for size N-x. Memory fragmentation may result for small x.
The best re-usable code very often is scalable. Unless obligated to use small sizes, assume code will grow in subsequent applications and need to be reasonable efficient for large N. You do want to re-use good code.
realloc() an array by a factor of 4 or so as the need arises. Arrays may also shrink - no need to have a bloated array laying around. I've used grow by factor of 4 at intervals 1,4,16,64... and shrink intervals at 2,8,32,128... By having grow/shrink intervals apart from each other, it avoid lots of actively should N waver around an interval.
Even in small ways, like using size_t vs. int. Sure, with sizes like 1000, it makes no difference, but with code re-use, an application may push the limit: size_t is better for array indexes.
void *temp_array[N];
for (int i = 0; i < N; i++) {
void *temp_array[N];
for (size_t i = 0; i < N; i++) {

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);
}

Resources