#include<stdio.h>
#define SIZE 3
int main()
{
char intArrayOne[SIZE] = {'A', 'B', 'C'};
char (*ptrToAnOneDimArray)[SIZE] = &intArrayOne;
int i = 0;
for(i=0 ; i<SIZE ; i++)
{
printf("%c ", (*ptrToAnOneDimArray)[i]);
}
}
Output
A B C
When should we use "ptrToAnOneDimArray" - kinds of usages in C/C++? Please give me a real-world example.
Can we avoid these kinds of complex and fuzzy usages of pointers?
For example, when you want to implement a dynamic multidimensional array:
int (*ptrArr)[WIDTH] = malloc(sizeof ptrArr[0] * HEIGHT);
is much better than
int **ptrPtr = malloc(sizeof ptrPtr[0] * HEIGHT);
for (size_t i = 0; i < HEIGHT; i++) {
ptrPtr[i] = malloc(sizeof ptrPtr[0][i] * WIDTH);
}
for various reasons (it does actually point to a 2D array that is contiguous in memory, it requires less allocations and frees, so it's less likely that you get it wrong, etc.)
Suppose that you are embedded programmer. Now you have some piece of hardware with exchangable modules. With each module you must communicate differently ie. you init/read/write to/from it differently.
Now you have your software that must handle all of these module types. You have 3 routines (simplified here) to init, read, write each type module (HW0 is module A, HW1 is module B).
void HW0_init() { printf("HW0_init\n"); }
void HW0_read() { printf("HW0_read\n"); }
void HW0_write(){ printf("HW0_write\n"); }
void HW1_init() { printf("HW1_init\n"); }
void HW1_read() { printf("HW1_read\n"); }
void HW1_write(){ printf("HW1_write\n"); }
Now imagine you want to init your module and read sth from it so you do:
int hw_id = 1;
// want to init hardware
switch(hw_id)
{
case 0: HW0_init(); break;
case 1: HW1_init(); break;
// ...
}
// now I want to read
switch(hw_id)
{
case 0: HW0_read(); break;
case 1: HW1_read(); break;
// ...
}
This may be accomplished differently using pointers to arrays. If you declare arrays of pointers to your functions like so:
// as many arrays as you have modules
void (*hw0[3])() = { HW0_init, HW0_read, HW0_write };
void (*hw1[3])() = { HW1_init, HW1_read, HW1_write };
your code may be simplified to this:
enum HW_ACTION
{
HW_INIT = 0,
HW_READ = 1,
HW_WRITE = 2
};
// pointer to array of pointers to funcs taking nothing
// and returning nothing
void (*(*f)[3])(void);
// detect hardware and set 'f'
f = &hw1;
(*f)[HW_INIT](); // same as HW1_init(); <=> hw1[HW_INIT]();
(*f)[HW_READ](); // same as HW1_read(); <=> hw1[HW_READ]();
Same effect - 'easier code'.
You may treat it as poor's man virtual methods for C users having no C++ compiler where you would normally create base abstract class with init, read, write methods and implement them for every kind of module.
Real life here http://en.wikipedia.org/wiki/Virtual_method_table.
Pointers-to-pointers (hence pointers-to-arrays by proxy) are really useful. If you have a function/method and it takes a pointer-to-value argument you can change the value inside your function and that value stays in scope after you leave the function - pass-by-reference of course. However you can't change the address your pointer points at - e.g. make the pointer you passed into a NULL pointer, or point it at a different value somewhere else in memory. If you use a pointer-to-pointer-to-value then you can change the value of the 'middle' pointer inside your function. I think the MySQL C-connector library is an example of where this is used.
In your example you could pass ptrToAnOneDimArray into a function and make *ptrToAnOneDimArray be a NULL pointer or a pointer to some other data rather than intArrayOne - as intArrayOne is a fixed size by the compiler (on the stack) then you could dynamically update *ptrToAnOneDimArray from the stack to be an array malloc()'d on the heap.
#include <stdio.h>
#include <stdlib.h>
#define SIZE 3
void display(char* data) {
int i = 0;
for(i=0 ; i<SIZE ; i++) {
printf("%c ", data[i]);
}
}
void changeMyArgument(char** pointerToPointer) {
*pointerToPointer = (char*) malloc(SIZE * sizeof(char));
/* now we use array notation for a change */
(*pointerToPointer)[0] = 'X';
(*pointerToPointer)[1] = 'Y';
(*pointerToPointer)[2] = 'Z';
}
int main() {
/* intArrayOne is implicitly char* */
char intArrayOne[SIZE] = {'A', 'B', 'C'};
char* arraysArePointers = intArrayOne;
/* ptrToAnOneDimArray is implicitly char** */
char** ptrToAnOneDimArray;
ptrToAnOneDimArray = &arraysArePointers;
display(*ptrToAnOneDimArray);
changeMyArgument(ptrToAnOneDimArray);
display(*ptrToAnOneDimArray);
}
Related
I'm working on a project and I have a list of functions to use.
I use the provided DLL.
I have the function's prototypes in the file "list.h".
This file is also provided to me
list.h
typedef unsigned short (__stdcall * ReadConfig)
(void * * pDataOut,
size_t * pSizeDataOut);
I wrote this main.c
int main (int nbArg, char** listeArg)
{
// Initialization
unsigned short outputFunction;
void * pointerMemoryZone = NULL;
size_t sizeMemoryZone = NULL;
// Load DLL
HMODULE dllLoadOuput= LoadLibrary("dllFile");
// Alias Creation
typedef unsigned short(*A_ReadConfig) (void * *, size_t *);
// Get Pointer on Function
A_ReadConfig ptrReadConfiguration = (A_ReadConfig)GetProcAddress(dllLoadOuput, "ReadConfig");
// Launch of the function
outputFunction = ptrReadConfiguration(&pointerMemoryZone, &sizeMemoryZone);
// Display
printf("\n\nSize Read Config : %ld\n\n", sizeMemoryZone);
// Unload DLL
FreeLibrary(dllLoadOuput);
return 0;
}
This program works and I get the size of the memory area fine.
But my program, my variables are they correctly declared and used...?
And how can I read the data contained in the memory area...?
Below is a diagram provided in the documentation :
Presumming outputFunction indicates success, the pointer pointerMemoryZone should contain sizeMemoryZone bytes of data.
How you access the data depends on the format (e.g. text/json/xml string).
A sample loop to display the data in ascii and hex:
for(int i=0; i<sizeMemoryZone; i++) {
char c = ((char*) pointerMemoryZone)[i];
printf("%c{%x} ", c, c);
}
I answer to my question. I find a solution.
I have to read datas byte by byte.
So I create a caracter pointer on "pointerMemoryZone" (wich contains adress of datas) by forcing the pointer type to character (because 1 byte)
And I make a loop in which I loop through the addresses one by one (along the length of the data)
Below the code for the loop
//...
//...
//...
// Launch of the function
outputFunction = ptrReadConfiguration(&pointerMemoryZone, &sizeMemoryZone);
// Creation of Pointer
char *pointerOnPointerMemoryZone = NULL;
// Begin Loop
for (int i = 0; i < sizeMemoryZone; i++)
{
// Association Pointer to Adress (+0, +1, +2, etc..)
pointerOnPointerMemoryZone = (char*)pointerMemoryZone + i;
printf("\n%d\t\t%d\t\t%c",i, *pointerOnPointerMemoryZone, *pointerOnPointerMemoryZone);
}
// End Loop
//...
//...
//...
I am working on a series of C functions to allow a user to dynamically build an array. The core of the library resides in the Array struct which contains a pointer variable array that contains the array data, len which contains the length of the array, size, which is the total memory allocation for the array, elem, which contains the memory allocation per indices, and pointer variables name and dtype which contains strings describing the name of the array and the type of the array. For the moment I have constrained the scope so that only int, float, double, and char arrays can be considered.
Thus far I have defined, and individually tested the following functions;
array_mem_alloc which contains code that allocates memory for an array.
init_array which is a wrapper around array_mem_alloc that instantiates an Array struct, determines the data type and returns an Array data type to a user.
append_array which allows a user to dynamically grow an array one index at a time, or add an already defined array.
free_array which frees all memory and resets struct variables
int_array_val which typecasts the data at an index and returns to user. I have versions of this function for all relevant data types, but for this problem I will only use this version.
find_int_array_indices which looks for where a specific integer exists in the array and records the index number into another array which is returned to the user.
For the purposes of testing find_int_array_indices I am calling init_array for a variable titled arr_test and appending it with 7 integers int a[7] = {6, 1, 3, 6, 6, 4, 5}. I pass the Array container arr_test to the find_int_array_indices function and everything works fine, which also returns another Array container titled p. However, when I try to retrieve the integer variables with the int_array_val function it fails, because it does not recognize the variable array->dtype as containing the string "int". However, when I test the container inside of find_int_array_indices and in the main function, the variable does contain the string "int". This tells me that I probably have a pointer error, but I do not see it. Any advice would be very useful. I am wondering if I need to go back to the beginning and define name and dtype as fixed length arrays in the Array struct instead of as pointer variables.
array.h
typedef struct
{
void *array; // Pointer to array
size_t len; // Active length of array
size_t size; // Number of allocated indizes
int elem; // Memory consumption per indice
char *name; // The array name
char *dtype; // A string representing the datatype
} Array;
void array_mem_alloc(Array *array, size_t num_indices);
Array init_array(char *dtype, size_t num_indices, char *name);
int append_array(Array *array, void *elements, size_t count);
void free_array(Array *array);
int int_array_val(Array *array, int indice);
Array find_int_array_indices(Array *array, int integer);
array.c
void array_mem_alloc(Array *array, size_t num_indices) {
// Determine the total memory allocation and assign to pointer
void *pointer;
pointer = malloc(num_indices * array->elem);
// If memory is full fail gracefully
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
free(pointer);
exit(0);
}
// Allocate resources and instantiate Array
else {
array->array = pointer;
array->len = 0;
array->size = num_indices;
}
}
// --------------------------------------------------------------------------------
Array init_array(char *dtype, size_t num_indices, char *name) {
// Determine memory blocks based on data type
int size;
if (strcmp(dtype, "float") == 0) size = sizeof(float);
else if (strcmp(dtype, "int") == 0) size = sizeof(int);
else if (strcmp(dtype, "double") == 0) size = sizeof(double);
else if (strcmp(dtype, "char") == 0) size = sizeof(char);
else {
printf("Data type not correctly entered into init_array, exiting program!\n");
exit(0);
}
// Allocate indice size and call array_mem_alloc
Array array;
array.dtype = dtype;
array.elem = size;
array_mem_alloc(&array, num_indices);
array.name = name;
return array;
}
// --------------------------------------------------------------------------------
int append_array(Array *array, void *elements, size_t count) {
// Allocae more memory if necessary
if (array->len + count > array->size) {
size_t size = (array->len + count) * 2;
void *pointer = realloc(array->array, size * array->elem);
// If memory is full return operations
if (pointer == NULL) {
printf("Unable to allocate memory, exiting.\n");
return 0;
}
// Allocate memory to variables and increment array size
array->array = pointer;
array->size = size;
}
// Append variables and increment the array length
memcpy((char *)array->array + array->len * array->elem, elements, count * array->elem);
array->len += count;
return 1;
}
// --------------------------------------------------------------------------------
void free_array(Array *array) {
// Free all memory in the array
free(array->array);
// Reset all variables in the struct
array->array = NULL;
array->size = 0;
array->len = 0;
array->elem = 0;
}
// --------------------------------------------------------------------------------
int int_array_val(Array *array, int indice) {
// Ensure array contains integers
printf("%s\n", array->dtype);
if (strcmp(array->dtype, "int") != 0) {
printf("Function can only return integer values, exiting function!\n");
exit(0);
}
// Cast value to an integer and return
int a = ((int *)array->array)[indice];
return a;
}
Array find_int_array_indices(Array *array, int integer) {
int number = 0;
int input;
for (int i = 0; i < array->len; i++) {
if (integer == int_array_val(array, i)) {
number++;
}
}
char dtype[7] = "int";
char name[9] = "indices";
Array indice_arr = init_array(dtype, number, name);
for (int i = 0; i < array->len; i++) {
input = i;
if (integer == int_array_val(array, i)) {
append_array(&indice_arr, &input, 1);
}
}
return indice_arr;
}
main.c
size_t indices = 10;
char name[6] = "array";
char dtype[7] = "int";
Array arr_test = init_array(dtype, indices, name);
int a[7] = {6, 1, 3, 6, 6, 4, 5};
append_array(&arr_test, a, 7);
Array p = find_int_array_indices(&arr_test, 6);
printf("%s\n", p.dtype); // This shows that p does contain dtype "int"
int d = int_array_val(&p, 0); // This fails in function, because it does not see dtype = "int"???
printf("%d\n", d);
In find_int_array_indices
char dtype[7] = "int";
char name[9] = "indices";
are both local variables, which are invalidated when the function returns. See: Dangling pointer and Lifetime.
init_array uses these values as if they had a lifetime to match its return value
Array array;
array.dtype = dtype;
array.elem = size;
array_mem_alloc(&array, num_indices);
array.name = name;
return array;
which, as a structure type, is a lifetime determined by the context of its caller (return is copy, after all).
find_int_array_indices completes the error when it returns indice_arr to main.
Some options:
Strictly use pointers to strings with static storage duration.
Change your structure definition to include space for these strings (or allocate it), and perform string copies.
Use an enumerated type instead.
Ditch this string-based, type limited paradigm all together by supporting all memory sizes generically (the naming feature remains an issue, though).
A rather long-winded continuation, to elaborate on using enumerated types:
The idea is to define a smaller set of acceptable values that your library works with, and making the user more aware of these values. As we can see, you have partially done that using strings but the implementation has some issues, as strings are generally clunky. Some problems with strings:
you have no control over the strings that users of your library use (this leads you to have to exit1 the program in the event the users enters something unexpected, which is easy to do),
you must account for their potentially large or excess memory consumption,
string comparison is O(N),
strings are generally unsafe in C, requiring more care than other basic constructs when handling them (assignment, comparison, storage).
So instead of using strings ("foo", "bar", "qux" in these examples), we use an enumerated type
enum OBJECT_TYPE {
OBJECT_FOO,
OBJECT_BAR,
OBJECT_QUX
};
which establishes the following:
it is more clear what the acceptable values are
some2 control over what users enter, via type hinting
comparison is O(1)
handling is the same as any integral type
The structure definition then looks like
typedef struct {
/* ... whatever members are needed for the structure */
size_t something_based_on_type;
enum OBJECT_TYPE type;
char debug_name[MAX_DEBUG_NAME];
} Object;
Nothing can really be done about the name member of your structure. If you want user defined nametags for things, then yes, as stated previously, you need to allocate space for them.
Our initialization function works similarly, but we can2 take advantage of some properties of integral types.
void object_init(Object *object, enum OBJECT_TYPE type, const char *debug_name) {
/* ... accept other arguments, whatever is needed to initialize */
size_t value_translations[] = { 42, 51, 99 };
object->type = type;
/* while neat, this is somewhat naive, see footnotes */
object->something_based_on_type = value_translations[type];
if (debug_name && strlen(debug_name) < MAX_DEBUG_NAME)
strcpy(object->debug_name, debug_name);
else
*object->debug_name = '\0';
}
Now we want to provide a function that works with our generic data of only type OBJECT_FOO (like your int_array_val). Again, the comparison is much easier to understand.
void object_print_foo(Object *o) {
if (OBJECT_FOO != o->type)
/* handle type mismatch */;
}
Although it would be better to provide a generic object_print function that again branches based on o->type.
A main function for completeness:
int main(void) {
Object a;
object_init(&a, OBJECT_QUX, "object_a");
object_print_foo(&a);
}
This is the general idea of using enumerated types.
With all that said, I think this is not really any better than just handling arbitrary data sizes, risks included. Something like
const void *array_get(Array *array, size_t index) {
if (index >= array->length)
return NULL;
return (char *) array->array + index * array->elem;
}
works, if the user respects the const contract, and uses the correct types (they would need to remember their typing with specifically typed getters too).
Generic data structures in C are a bit of a leap of faith no matter what.
1. So a note on exiting from library code: don't. As a library author, you have no reasonable right to cause user programs to terminate (unless requested, or the user invokes UB outside your control). Delegate upwards, return errors, and let the user exit the program on their own terms, as they may need to perform their own cleanups (or might carry on if the failure is non-critical).
2. C's enumeration type is rather weak. enum are actually just int, and users can enter plain integer values outside the specified ranges. This is akin to invoking undefined behavior from a library's point of view, but we may wish to protect the user anyway.
This probably has been asked already, but I'm unable to find anything on it.
I have a string array, where the numbers of strings in it is determined at runtime (the max string length is known, if that helps). Since I need global access to that array, I used a pointer and malloc'ed enough space to it when I actually know how much has to fit in there:
char *global_strings;
void some_func(int strings_nr, int strings_size)
{
global_strings = (char*) malloc(strings_nr* strings_size* sizeof(char));
}
What would be the correct way in C to use this pointer like a two-dimensional char array equivalent to
global_strings[strings_nr][strings_size] ?
As a global pointer to 2D data, whose N*M characteristics defined at run-time, I'd recommend a helper function to access the strings rather than directly use it. Make it inline or as a macro if desired.
char *global_strings = NULL;
size_t global_strings_nr = 0;
size_t global_strings_size = 0;
// Allocation -
// OK to call again, but prior data may not be organized well with a new string_size
// More code needed to handle that.
void some_func(int strings_nr, int strings_size) {
global_strings_nr = strings_nr; // save for later use
global_strings_size = strings_size; // save for later use
global_strings = realloc(global_strings,
sizeof *global_strings * strings_nr * strings_size);
if (global_strings == NULL) {
global_strings_nr = global_strings_size = 0;
}
}
// Access function
char *global_strings_get(size_t index) {
if (index >= global_strings_nr) {
return NULL;
}
return global_strings + index*global_strings_size;
}
#define GLOBAL_STRINGS_GET_WO_CHECK(index) \
(global_strings + (index)*global_strings_size)
Better to use size_t for array indexing and sizing than int.
Casts not needed.
Memory calculations should begin with a size_t rather than int * int * size_t.
I am having a bunch of problems with pointers and dynamic arrays here.
I have a function that I call, that does a bunch a stuff, like removing an ellement from the dynamic array , which leads me to reallocating memory to one of those dynamic arrays. The problem is I call functions within functions, and I can't return all my values properly to the Main.
Since I can't return 2 values, how can I do this?
structure1* register(structure1 *registerArray,structure2 *waitingList, int counter){
//Bunch of code in here
registerArray = realloc(inspecao, (counter)+1);
waitingList = eliminate(waitingList, 5, counter); //Doesn't matter what it does really
return registerArray;
}
structure1* eliminate(structure1 *arrayToEliminateFrom, int positionToEliminate, int *counter){
//The code for this doesn't matter
//All I do is eliminate an ellement and reallocate it
arrayToEliminateFrom = realloc(arrayToEliminateFrom, (*counter-1)*sizeof(structure1))
return arrayToEliminateFrom;
}
As you can see , I don't know how to return the pointer to the waitingList dynamic array to the Main. How can I do this?
I have searched everywhere.
Help
Okay, here are two ways to do it.
The first is, based upon your comment, what you think your instructor would want:
void
xregister(structure1 **registerArray, int *arrayCount,
structure1 **waitingList, int *waitCount)
{
// Bunch of code in here
*arrayCount += 1;
*registerArray = realloc(inspecao, *arrayCount * sizeof(structure1));
// Doesn't matter what it does really
eliminate(waitingList, 5, waitCount)
}
void
eliminate(structure1 **arrayToEliminateFrom, int positionToEliminate,
int *count)
{
// The code for this doesn't matter
*count -= 1;
// All I do is eliminate an ellement and reallocate it
*arrayToEliminateFrom = realloc(*arrayToEliminateFrom,
*count * sizeof(structure1))
}
Here is what Roberto and I were suggesting. Actually, mine's a general variable length array approach that can be fully generalized with some slight field changes. In a way, since you're already using a struct, I can't see why your instructor would object to this as it's a standard way to do it. Less cumbersome and cleaner.
struct vector {
int vec_count;
structure1 *vec_base;
};
void
xregister(vector *registerArray,vector *waitingList)
{
// Bunch of code in here
registerArray->vec_count += 1;
registerArray->vec_base = realloc(registerArray->vec_base,
registerArray->vec_count * sizeof(structure1));
// Doesn't matter what it does really
eliminate(waitingList, 5)
}
void
eliminate(vector *arrayToEliminateFrom, int positionToEliminate)
{
// The code for this doesn't matter
arrayToEliminateFrom->vec_count -= 1;
// All I do is eliminate an ellement and reallocate it
arrayToEliminateFrom->vec_base = realloc(arrayToEliminateFrom->vec_base,
arrayToEliminateFrom->vec_count * sizeof(structure1))
}
Here's an even more compact way:
struct vector {
int vec_count;
structure1 *vec_base;
};
void
vecgrow(vector *vec,int inc)
{
vec->vec_count += inc;
vec->vec_base = realloc(vec->vec_base,vec->vec_count * sizeof(structure1));
}
void
xregister(vector *registerArray,vector *waitingList)
{
// Bunch of code in here
vecgrow(registerArray,1);
// Doesn't matter what it does really
eliminate(waitingList, 5)
}
void
eliminate(vector *arrayToEliminateFrom, int positionToEliminate)
{
// The code for this doesn't matter
vecgrow(arrayToEliminateFrom,-1);
}
you should try to do an higher structure that contains both pointers and pass and return that structure beetween your functions, because function can return only one object/structure, but your structure/object can contain more objects/structures
I have a C struct:
typedef struct {
Dataset *datasets;
int nDatasets;
char *group_name;
enum groupType type;
} DatasetGroup;
It has a constructor function like this:
DatasetGroup * new_DatasetGroup(char *group_name, enum groupType type, enum returnCode *ret)
{
DatasetGroup *dg;
dg = (DatasetGroup *) malloc(sizeof(DatasetGroup));
if (dg == NULL)
{
*ret = EMEMORY_ERROR;
}
// Allocate space for a few datasets
dg->datasets = malloc(sizeof(Dataset) * INCREMENT);
if (dg->datasets == NULL)
{
*ret = EMEMORY_ERROR;
}
dg->group_name= malloc(sizeof(char) * strlen(group_name));
strcpy(dg->group_name, group_name);
dg->type = type;
groupCount++;
return dg;
}
I want to dynamically create an array of these structs. Whats the best way to do this?
So far I have something like:
DatasetGroup * make_array(){
DatasetGroup *dg_array;
// Allocate space for a few groups
dg_array = (DatasetGroup *) malloc(sizeof(DatasetGroup) * INCREMENT);
return dg_array;
}
void add_group_to_array(DatasetGroup *dg_array, ...){
// Add a datasetgroup
DatasetGroup *dg = new_DatasetGroup(...);
// groupCount - 1 as the count is incremented when the group is created, so will always be one ahead of the array index we want to assign to
dg_array[groupCount - 1] = dg;
if (groupCount % INCREMENT == 0)
{
//Grow the array
dg_array = realloc(dg_array, sizeof(DatasetGroup) * (groupCount + INCREMENT));
}
}
But this doesnt seem right....
any ideas?
A few suggestions:
You have groupCount being incremented by the constructor function of the struct. This means you can only have one array of the struct that uses your array function. I would recommend having the array be responsible for managing the count.
To that affect if you want to have a managed array I would create a struct for that and have it keep both the pointer to the array,the number of objects and the size of the array (e.g. the maximum number of structs it can currently hold)
If you keep proper track of how many elements you have and the size of the array you can replace groupCount % INCREMENT == 0 with something like groupCount == arraySize which is a lot more intuitive in my opinion.
You can avoid the second malloc in the constructor all together by having the array be an array of the elements instead of an array of pointers. The constructor than then just initialize the struct members instead of allocating memory. If you are doing this a lot you will be avoiding a lot of memory fragmentation.
Finally, while this depends on your application, I usually recommend when you realloc do not increase by a constant but instead of by a multiple of the current array size. If say you double the array size you only have to do log_2 n number of reallocs with n being the final array size and you waste at most half of memory (memory is generally cheap, like I said it depends on the application). If that is wasting to much memory you can do say 1.5. If you want a more detailed explanation of this I recommend this Joel on Software article, the part about realloc is about 2/3 down.
Update:
A few others things:
dg = (DatasetGroup *) malloc(sizeof(DatasetGroup));
if (dg == NULL)
{
ret = EMEMORY_ERROR;
}
// Allocate space for a few datasets
dg->datasets = malloc(sizeof(Dataset) * INCREMENT);
As previously pointed out is very bad as you will us dg even if it is NULL. You probably want to exit right after detecting the error.
Furthermore you are setting ret but ret is passed by value so it will not be changed for the caller if the callee changes it. Instead you probably want to pass a pointer and dereference it.
Update 2: Can I give an example, sure, quick not so much ;-D.
Consider the following code (I apologize if there are any mistakes, still half asleep):
#include <stdio.h>
#include <stdlib.h>
#define LESS_MALLOCS
#define MAX_COUNT 100000000
typedef struct _foo_t
{
int bar1;
int bar2;
} foo_t;
void foo_init(foo_t *foo, int bar1, int bar2)
{
foo->bar1 = bar1;
foo->bar2 = bar2;
}
foo_t* new_foo(int bar1, int bar2)
{
foo_t *foo = malloc(sizeof(foo_t));
if(foo == NULL) {
return NULL;
}
foo->bar1 = bar1;
foo->bar2 = bar2;
return foo;
}
typedef struct _foo_array_t
{
#ifdef LESS_MALLOCS
foo_t *array;
#else
foo_t **array;
#endif
int count;
int length;
} foo_array_t;
void foo_array_init(foo_array_t* foo_array, int size) {
foo_array->count = 0;
#ifdef LESS_MALLOCS
foo_array->array = malloc(sizeof(foo_t) * size);
#else
foo_array->array = malloc(sizeof(foo_t*) * size);
#endif
foo_array->length = size;
}
int foo_array_add(foo_array_t* foo_array, int bar1, int bar2)
{
if(foo_array->count == foo_array->length) {
#ifdef LESS_MALLOCS
size_t new_size = sizeof(foo_t) * foo_array->length * 2;
#else
size_t new_size = sizeof(foo_t*) * foo_array->length * 2;
#endif
void* tmp = realloc(foo_array->array, new_size);
if(tmp == NULL) {
return -1;
}
foo_array->array = tmp;
foo_array->length *= 2;
}
#ifdef LESS_MALLOCS
foo_init(&(foo_array->array[foo_array->count++]), bar1, bar2);
#else
foo_array->array[foo_array->count] = new_foo(bar1, bar2);
if(foo_array->array[foo_array->count] == NULL) {
return -1;
}
foo_array->count++;
#endif
return foo_array->count;
}
int main()
{
int i;
foo_array_t foo_array;
foo_array_init(&foo_array, 20);
for(i = 0; i < MAX_COUNT; i++) {
if(foo_array_add(&foo_array, i, i+1) != (i+1)) {
fprintf(stderr, "Failed to add element %d\n", i);
return EXIT_FAILURE;
}
}
printf("Added all elements\n");
return EXIT_SUCCESS;
}
There is a struct (foo_t) with two members (bar1 and bar2) and another struct that is an array wrapper (foo_array_t). foo_array_t keeps track of the current size of the array and the number of elements in the array. It has an add element function (foo_array_add). Note that there is a foo_init and a new_foo, foo_init takes a pointer to a foo_t and new_foo does not and instead returns a pointer. So foo_init assumes the memory has been allocated in some way, heap, stack or whatever doesn't matter, while new_foo will allocate memory from the heap. There is also a preprocess macro called LESS_MALLOCS. This changes the definition of the array member of foo_array_t, the size of the initial array allocation, the size during reallocation and whether foo_init or new_foo is used. The array and its size have to change to reflect whether a pointer or the actually element is in the array. With LESS_MACRO defined the code is following my suggestion for number 4, when not, it is more similar to your code. Finally, main contains a simple micro-benchmark. The results are the following:
[missimer#asus-laptop tmp]$ gcc temp.c # Compile with LESS_MACROS defined
[missimer#asus-laptop tmp]$ time ./a.out
Added all elements
real 0m1.747s
user 0m1.384s
sys 0m0.357s
[missimer#asus-laptop tmp]$ gcc temp.c #Compile with LESS_MACROS not defined
[missimer#asus-laptop tmp]$ time ./a.out
Added all elements
real 0m9.360s
user 0m4.804s
sys 0m1.968s
Not that time is the best way to measure a benchmark but in this case I think the results speak for themselves. Also, when you allocate an array of elements instead of an array of pointers and then allocate the elements separately you reduce the number of places you have to check for errors. Of course everything has trade-offs, if for example the struct was very large and you wanted to move elements around in the array you would be doing a lot of memcpy-ing as opposed to just moving a pointer around in your approach.
Also, I would recommend against this:
dg_array = realloc(dg_array, sizeof(DatasetGroup) * (groupCount + INCREMENT));
As you lose the value of the original pointer if realloc fails and returns NULL. Also like your previous ret, you should pass a pointer instead of the value as you are not changing the value to the caller, just the callee which then exits so it has no real affect. Finally, I noticed you changed your function definition to have a pointer to ret but you need to dereference that pointer when you use it, you should be getting compiler warnings (perhaps even errors) when you do try what you currently have.
You could do two things, either you dynamically create an array of struct pointers, then call your new function to create N datagroups, or you could dynamically request memory for N structures at once, this would mean your N structures would be contiguously allocated.
Datagroup **parry = malloc(sizeof(datagroup *) * N)
for (int i = 0; i < N; i++){
parry[i] = //yourconstructor
}
Or
//allocate N empty structures
Datagroup *contarr = calloc(N, sizeof(Datagroup))
The second method might need a different initialization routine than your constructor, as the memory is already allocated