gcc 4.5.1 c89
I have written this source code for my better understanding of malloc and calloc.
I understand, but just have a few questions.
dev = malloc(number * sizeof *devices);
is equal to this calloc. I am not concerned about clearing the memory.
dev = calloc(number, sizeof *devices);
What is that exactly, compared to doing this 5 times in a while loop:
dev = malloc(sizeof *devices);
I guess the first one and the second is creating a pointer to 5 struct device. And the third is creating a single pointer to a struct device?
My program illustrates this 3 different methods compiled and ran with valgrind --leak-check=full.
Many thanks for any advice.
#include <stdio.h>
#include <stdlib.h>
struct Devices {
#define MAX_NAME_SIZE 80
size_t id;
char name[MAX_NAME_SIZE];
};
struct Devices* create_device(struct Devices *dev);
void destroy_device(struct Devices *dev);
int main(void)
{
size_t num_devices = 5;
size_t i = 0;
struct Devices *device = NULL;
struct Devices *dev_malloc = NULL;
struct Devices *dev_calloc = NULL;
for(i = 0; i < num_devices; i++) {
device = create_device(device);
/* Assign values */
device->id = i + 1;
sprintf(device->name, "Device%zu", device->id);
/* Print values */
printf("ID ----- [ %zu ]\n", device->id);
printf("Name --- [ %s ]\n", device->name);
/* Test free */
destroy_device(device);
}
printf("\n");
dev_malloc = malloc(num_devices * sizeof *dev_malloc);
for(i = 0; i < num_devices; i++) {
/* Assign values */
dev_malloc->id = i + 1;
sprintf(dev_malloc->name, "dev_malloc%zu", dev_malloc->id);
/* Print values */
printf("ID ----- [ %zu ]\n", dev_malloc->id);
printf("Name --- [ %s ]\n", dev_malloc->name);
}
/* Test free */
destroy_device(dev_malloc);
printf("\n");
dev_calloc = calloc(num_devices, sizeof *dev_calloc);
for(i = 0; i < num_devices; i++) {
/* Assign values */
dev_calloc->id = i + 1;
sprintf(dev_calloc->name, "dev_calloc%zu", dev_calloc->id);
/* Print values */
printf("ID ----- [ %zu ]\n", dev_calloc->id);
printf("Name --- [ %s ]\n", dev_calloc->name);
}
/* Test free */
destroy_device(dev_calloc);
return 0;
}
struct Devices* create_device(struct Devices *dev)
{
/* Not checking for memory error - just simple test */
return dev = malloc(sizeof *dev);
}
void destroy_device(struct Devices *dev)
{
if(dev != NULL) {
free(dev);
}
}
calloc(a,b) and malloc(a*b) are equivalent except for the possibility of arithmetic overflow or type issues, and the fact that calloc ensures the memory is zero-byte-filled. Either allocated memory that can be used for an array of a elements each of size b (or vice versa). On the other hand, calling malloc(b) a times will results in a individual objects of size b which can be freed independently and which are not in an array (though you could store their addresses in an array of pointers).
Hope this helps.
malloc(n) allocates n bytes plus padding and overhead.
calloc(m, n) allocates m*n bytes plus padding and overhead, and then zero's the memory.
That's it.
edited for clarity
I guess the first one and the second is creating a pointer to 5 struct device. And the third is creating a single pointer to a struct device?
The first one malloc(number * sizeof(*devices)) would allocate enough memory to store number of Devices. As others have mentioned, you can treat this block like an array of Device. The pointer you get back will point to the beginning of the block.
int number = 5;
Device *ptr = malloc(number * sizeof(*ptr));
/* stuff */
free(ptr);
The second one that uses calloc does the same thing, while also initializing the memory to 0. Again, you can use treat the block like an array of Device.
int number = 5;
Device *ptr = calloc(number, sizeof(*ptr));
/* stuff */
free(ptr);
The third one, looping 5 times, would result in 5 different pointers to 5 different blocks large enough to store one Device each. This also means each of the 5 pointers has to be free'ed individually.
Device *ptrs[5];
for(int i = 0; i < 5; ++i)
{
ptrs[i] = malloc(sizeof(*ptrs[i]));
}
/* stuff */
for(int i = 0; i < 5; ++i)
{
free(ptrs[i]);
}
The first two create an array of 5 devices in contiguous memory. The last malloc, done five times, will create 5 individual devices which are not guaranteed to be in contiguous memory.
All three loops in your program use only one struct Devices object at a time. The later ones allocate extra memory as though they are going to use multiple objects, but then keep overwriting the beginning of that memory. If you tried to use the object with ID 1 after setting up the object with ID 2, you would find there is no longer any object with ID 1.
Instead, you could do something like this to treat the allocated memory as an array of structs:
dev_malloc = malloc(num_devices * sizeof *dev_malloc);
for (i=0; i<num_devices; i++) {
/* Assign values */
dev_malloc[i].id = i + 1;
sprintf(dev_malloc[i].name, "dev_malloc%zu", dev_malloc[i].id);
/* Print values */
printf("ID ----- [ %zu ]\n", dev_malloc[i].id);
printf("Name --- [ %s ]\n", dev_malloc[i].name);
}
free(dev_malloc);
Look at an implementation of calloc to see the differences. It's probably something like this:
// SIZE_MAX is defined in stdint.h from C99
void *calloc( size_t N, size_t S)
{
void *ret;
size_t NBYTES;
// check for overflow of size_t type
if (N > SIZE_MAX / S) return NULL;
NBYTES = N * S;
ret = malloc( NBYTES);
if (ret != NULL)
{
memset( ret, 0, NBYTES);
}
return ret;
}
As you point out, calloc zeroes out the memory is allocates, while malloc doesn't.
Your examples 1 & 2 each allocate a single contiguous block of five structs (and returns a pointer to that block), whereas example 3 allocates five separate blocks of one struct each (and gives you five pointers unrelated to one another.)
Related
This is my very first post on stackoverflow. I am a CS student learning C, and I am having some issues with the problem I'm working on. Additionally, I should mention that I know very little, so if anything I put here comes off as foolish or ignorant, it is absolutely not my intention
I am aware that there are other posts similar to this one, however so far I feel that I have tried making a lot of amendments that all end with the same result.
I am given a text file in which each line contains studentName(tab)gpa. The total size of the file is unknown, this I must use dynamic memory allocation.
Example of text file format
Jordan 4.0
Bhupesh 2.51
General steps for program
Many details will be left out to save myself from embarrassment, however I will give a high-level overview of the process I am struggling with:
1.) Create dynamic memory array to hold struct for each line
2.) Start looping through file
3.) check the current size of the array to see if reallocation is necessary
4.) Create dynamic array to hold name
5.) Place name and gpa into struct
6.) rinse & repeat
Finally, one last thing. The error occurs when my initial allocated memory limit is reached and the program attempts to reallocate more memory from the heap.
Screenshot of error being thrown in clion debugger
My code is shown below:
#define EXIT_CODE_FAIL 1
#define ROW_COUNT 10
#define BUFFER_SIZE 255
#define VALID_ARG_COUNT 2
struct Student {
float gpa;
char * name;
};
// read the file, pack contents into struct array
struct Student * readFileContents(char *filename, int *rowCounter) {
// setup for loop
int maxDataSize = ROW_COUNT;
float currentStudentGpa = 0;
char studentNameBuffer[BUFFER_SIZE];
// initial structArray pre-loop
struct Student * structArray = calloc(maxDataSize, sizeof(*structArray));
FILE *pFile = fopen(filename, "r");
validateOpenFile(pFile);
// loop through, get contents, of eaach line, place them in struct
while (fscanf(pFile, "%s\t%f", studentNameBuffer, ¤tStudentGpa) > 0) {
structArray = checkArraySizeIncrease(*rowCounter, &maxDataSize, &structArray);
structArray->name = trimStringFromBuffer(studentNameBuffer);
structArray->gpa = currentStudentGpa;
(*rowCounter)++, structArray++;
}
fclose(pFile);
return structArray;
}
// resize array if needed
struct Student * checkArraySizeIncrease(int rowCount, int * maxDataSize, struct Student ** structArray) {
if (rowCount == *maxDataSize) {
*maxDataSize += ROW_COUNT;
**// line below is where the error occurs**
struct Student * newStructArray = realloc(*structArray, *maxDataSize * sizeof(*newStructArray));
validateMalloc(newStructArray);
return newStructArray;
}
return *structArray;
}
// resize string from initial data buffer
char *trimStringFromBuffer(char *dataBuffer) {
char *string = (char *) calloc(strlen(dataBuffer), sizeof(char));
validateMalloc(string);
strcpy(string, dataBuffer);
return string;
}
Once again, I apologize if similar questions have been asked, but please know I have tried most of the recommendations that I have found on stack overflow with no success (of which I'm well aware is the result of my poor programming skill level in C).
I will now promptly prepare myself for my obligatory "first post on stackoverflow" roasting. Cheers!
You are reusing structArray as both the base of the array and a pointer to the current element. This won't work. We need two variables.
There are a number of "loose" variables related to the dynamic array. It's cleaner to define a struct (e.g. dynarr_t below) to contain them and pass just the struct pointer around.
When you're duplicating the string, you must allocate strlen + 1 [not just strlen]. But, the entire function does what strdup already does.
I tried to save as much as possible, but I've had to refactor the code a fair bit to incorporate all the necessary changes.
By passing sizeof(*structArray) to the arrnew function, this allows the struct to be used for arbitrary size array elements.
Anyway, here's the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define sysfault(_fmt...) \
do { \
printf(_fmt); \
exit(1); \
} while (0)
#define EXIT_CODE_FAIL 1
#define ROW_COUNT 10
#define BUFFER_SIZE 255
#define VALID_ARG_COUNT 2
struct Student {
float gpa;
char *name;
};
// general dynamic array control
typedef struct {
void *base; // base address
size_t size; // bytes in array element
size_t count; // current number of used entries
size_t max; // maximum number of entries
size_t grow; // number of entries to grow
} dynarr_t;
// arrfind -- return pointer to array element
void *
arrfind(dynarr_t *arr,size_t idx)
{
void *ptr;
ptr = arr->base;
idx *= arr->size;
ptr += idx;
return ptr;
}
// arrnew -- create new array control
dynarr_t *
arrnew(size_t siz,size_t grow)
// siz -- sizeof of array element
// grow -- number of elements to grow
{
dynarr_t *arr;
arr = calloc(1,sizeof(*arr));
if (arr == NULL)
sysfault("arrnew: calloc fail -- %s\n",strerror(errno));
arr->size = siz;
arr->grow = grow;
return arr;
}
// arrgrow -- grow array [if necessary]
// RETURNS: pointer to element to fill
void *
arrgrow(dynarr_t *arr)
{
void *ptr;
// grow array if necessary
// NOTE: use of a separate "max" from "count" reduces the number of realloc
// calls
if (arr->count >= arr->max) {
arr->max += arr->grow;
arr->base = realloc(arr->base,arr->size * arr->max);
if (arr->base == NULL)
sysfault("arrgrow: realloc failure -- %s\n",strerror(errno));
}
// point to current element
ptr = arrfind(arr,arr->count);
// advance count of elements
++arr->count;
return ptr;
}
// arrtrim -- trim array to actual number of elements used
void
arrtrim(dynarr_t *arr)
{
arr->base = realloc(arr->base,arr->size * arr->count);
if (arr->base == NULL)
sysfault("arrtrim: realloc failure -- %s\n",strerror(errno));
arr->max = arr->count;
}
void
validateMalloc(void *ptr)
{
if (ptr == NULL) {
perror("validateMalloc");
exit(1);
}
}
void
validateOpenFile(FILE *ptr)
{
if (ptr == NULL) {
perror("validateOpenFile");
exit(1);
}
}
// resize string from initial data buffer
char *
trimStringFromBuffer(char *dataBuffer)
{
#if 0
#if 0
char *string = calloc(1,strlen(dataBuffer));
#else
char *string = calloc(1,strlen(dataBuffer) + 1);
#endif
validateMalloc(string);
strcpy(string, dataBuffer);
#else
char *string = strdup(dataBuffer);
validateMalloc(string);
#endif
return string;
}
// read the file, pack contents into struct array
dynarr_t *
readFileContents(char *filename)
{
dynarr_t *arr;
// setup for loop
float currentStudentGpa = 0;
char studentNameBuffer[BUFFER_SIZE];
struct Student *structArray;
arr = arrnew(sizeof(*structArray),10);
FILE *pFile = fopen(filename, "r");
validateOpenFile(pFile);
// loop through, get contents, of eaach line, place them in struct
while (fscanf(pFile, "%s\t%f", studentNameBuffer, ¤tStudentGpa) > 0) {
structArray = arrgrow(arr);
structArray->name = trimStringFromBuffer(studentNameBuffer);
structArray->gpa = currentStudentGpa;
}
fclose(pFile);
arrtrim(arr);
return arr;
}
I think your issue is with the calculation of the size of the realloc. Rather than using sizeof(*newStructArray), shouldn't you really be using the size of your pointer type? I would have written this as realloc(*structArray, *maxDataSize * sizeof(struct Student *))
There's a lot of other stuff in here I would never do - passing all those variables in to checkArraySizeIncrease as pointers is generally a bad idea because it can mask the fact that things are getting changed, for instance.
There is an issue in allocation of the buffer for string
char *string = (char *) calloc(strlen(dataBuffer), sizeof(char));
it should be:
char *string = (char *) calloc(1 + strlen(dataBuffer), sizeof(char));
as C-strings require extra 0-byte at the end.
Without it, the following operation:
strcpy(string, dataBuffer);
may damage data after the buffer, likely messing malloc() metadata.
I have a struct let say
struct packets {
int tcp_source;
int tcp_dest;
char *ip_source;
char *ip_dest;
int seq;
...
} *pkts;
I am allocating space using malloc in while loop and inside while loop when I done using specific pkts (in pointed to by (ptks+index)) I need to free it like this:
while (i++ < n - 1) {
(pkts+i)=(struct packets *) malloc(sizeof (struct packets))
//shared `i` and signal sending thread to send packet
...
}
// Now I need to free `(pkts+i)` like
while (i < 10)
free((pkts + i)); //Not working
//OR
free(pkts[i]); / Not working either -- in both cases error zsh: abort
Question is if I choose to free specific element at time then what is the right way plus what I need if I choose to free all elements at once
You need to use pkts as an array of pointers to struct packets.
struct packets **pkts = malloc(N * sizeof *pkts);
...
for (int i = 0; i < N; ++i) {
// setting i-th element
pkts[i]=malloc(sizeof pkts[i]);
// accessing i-th element
pkts[i]-> ...
}
for (int i = 0; i < N; ++i) {
// freeing individual element
free(pkts[i]);
pkts[i] = NULL; // avoid accidental accessing or re-freeing
}
// freeing whole array of pointers
free(pkts);
As mentioned in the comments, you probably do not need to allocate each packets individually. Just allocate an array of packets.
struct packets *pkts = malloc(N * sizeof *pkts);
...
free(pkts);
If you use C99-compliant compiler and you have a reasonable bound on the number of packets use VLA.
struct packets pkts[N]; // that's all
I have a function that initializes a struct that contains nested structs and arrays.
While initializing the struct I have multiple calls to calloc.
Refer to code bellow:
typedef struct
{
int length;
uint8_t *buffer;
} buffer_a;
typedef struct
{
int length;
uint8_t *buffer;
int *second_buffer_size;
uint8_t **second_buffer;
} buffer_b;
typedef struct
{
int max_length;
buffer_a *buffer_in;
buffer_b *buffer_out;
} state_struct;
state_struct *init(int size, int elements) {
size_t struct_size = sizeof(state_struct);
state_struct *s = (state_struct*) calloc(struct_size, struct_size);
log("Building state with length %d", size);
s->max_length = size;
size_t buffer_in_size = s->max_length * sizeof(buffer_a);
s->buffer_in = (buffer_a*) calloc(buffer_in_size, buffer_in_size);
size_t buffer_out_size = s->max_length * sizeof(buffer_b);
s->buffer_out = (buffer_b*) calloc(buffer_out_size, buffer_out_size);
log("Allocated memory for both buffers structs");
for (int i = 0; i < s->max_length; ++i) {
size_t buf_size = elements * sizeof(uint8_t);
s->buffer_in[i].buffer = (uint8_t*) calloc(buf_size, buf_size);
s->buffer_in[i].length = -1;
log(s, "Allocated memory for in buffer");
s->buffer_out[i].buffer = (uint8_t*) calloc(buf_size, buf_size);
s->buffer_out[i].length = -1;
log(s, "Allocated memory for out buffer");
size_t inner_size = elements * elements * sizeof(uint8_t);
size_t inner_second_buffer_size = elements * sizeof(int);
s->buffer_out[i].second_buffer = (uint8_t**) calloc(inner_size, inner_size);
s->buffer_out[i].second_buffer_size = (int*) calloc(inner_second_buffer_size, inner_second_buffer_size);
log(s, "Allocated memory for inner buffer");
}
return s;
}
Logs just before the for loop are printed but the program crashes and the first log statement inside the loop does not get printed out.
Why is this happening?
So this may not be an answer to your question, but here goes:
When I ran this code (on Ubuntu, gcc 7.4), and replaced all the log functions with printf, it finished succesfuly. I suspect the problem might be in the way you use the log function. You specify that it works up until the first log call inside the loop. You didn't specify what the log function does, or whether it is a function or just a macro wrapper for printf, but you call it in a different manner inside the loop - the first parameter is *state_struct rather than a format string.
Also, the way you call calloc seems to be semantically incorrect. The first parameter should be the number of blocks of second parameter size you want to allocate (presumably 1 in this case)
Question
Implement XXX new_malloc(unsigned int size), a function that allocates memory on the heap and returns a pointer to the allocated struct. The limitation is that you must use simple_malloc. Keep in mind that the allocated memory is not continuous. You can choose how to implement the XXX struct as you wish.
#define BUFF_SIZE 256
void* simple_malloc() {
return malloc(BUFF_SIZE);
}
My idea
Define a struct (Buffer) that will hold the number of chunks and a pointer to all the pointers that will hold the address of the allocated chunk(ChunkBuffer). Since we deal with dynamic memory allocation, the pointer in the struct, will be stored along with the size of the array.
Example
The user asks to allocate 1362 bytes. We calculate the total number of chunks by dividing the required size by the BUFF_SIZE --> 1362/256 = 5 and we add 1 if size % BUFF_SIZE ---> 82 > 0 is greater than zero. We get total of 6 ChunkBuffer.
Allocation.h
#define BUFF_SIZE 256
void* simple_malloc() {
return malloc(BUFF_SIZE);
}
// Stores chunk of allocated space
typedef struct ChunkBuffer {
unsigned int chunk_size; // Total number of bytes allocated
void* allocated_chunk_ptr; // Allocated buffer for the chunk
}ChunkBuffer, *ChunkBufferPtr, **ChunkBufferTable;
// Stores the collection of the different chunks
typedef struct Buffer {
unsigned int chunks_size; // Total chunks number
ChunkBufferTable chunk_buffer_tbl; // Array of all the allocated chunks
}Buffer, *BufferPtr;
void* simple_malloc(); // Given function
BufferPtr new_malloc(unsigned int); // Allocation by chunks
int calc_chunks(unsigned int, int); // Help function to calculate the number of needed chunks
Allocation.c
#include <stdlib.h>
#include "Allocation.h"
/* Calculate the amount of needed chunks */
int calc_chunks(unsigned int size, int buff_size) {
int num_chunks = size / BUFF_SIZE;
int mod = size % BUFF_SIZE;
return 0 < mod ? ++num_chunks : num_chunks;
}
BufferPtr new_malloc(unsigned int size)
{
BufferPtr buffer_ptr = NULL;
int num_chunks = 0;
unsigned int i = 0;
/* Sanity check */
if (0 >= size) {
return NULL;
}
num_chunks = calc_chunks(size, BUFF_SIZE);
/* Allocating the data structure that will hold the required allocated space */
if (!(buffer_ptr = (BufferPtr)malloc(sizeof(Buffer)))) {
return NULL;
}
/* Allocating the array that will hold the chunks of data */
if (!(buffer_ptr->chunk_buffer_tbl = (ChunkBufferTable)malloc(sizeof(ChunkBufferPtr) * num_chunks))) {
goto buffer_cleanup;
}
buffer_ptr->chunks_size = num_chunks;
/* Allocating each chunk */
for (; i < buffer_ptr->chunks_size; i++) {
buffer_ptr->chunk_buffer_tbl[i] = (ChunkBufferPtr)malloc(sizeof(ChunkBuffer));
if (!(buffer_ptr->chunk_buffer_tbl[i]->allocated_chunk_ptr = simple_malloc())) {
goto chunk_cleanup;
}
}
/* Allocation success */
return buffer_ptr;
chunk_cleanup:
for (unsigned int j = 0; j < i; j++) {
free(buffer_ptr->chunk_buffer_tbl[j]->allocated_chunk_ptr);
}
table_cleanup:
free(buffer_ptr->chunk_buffer_tbl);
buffer_cleanup:
free(buffer_ptr);
return NULL;
}
The problem is that I get C6386, C6011, C6385 error for this section of the above code(Thank you rici):
Warning C6386 Buffer overrun while writing to 'buffer_ptr->chunk_buffer_tbl': the writable size is 'sizeof(ChunkBuffer *)*num_chunks' bytes, but '8' bytes might be written
Warning C6011 Dereferencing NULL pointer 'buffer_ptr->chunk_buffer_tbl[i]'
Warning C6385 Reading invalid data from 'buffer_ptr->chunk_buffer_tbl': the readable size is 'sizeof(ChunkBuffer *)*num_chunks' bytes, but '8' bytes may be read
/* Allocating each chunk */
for (; i < buffer_ptr->chunks_size; i++) {
buffer_ptr->chunk_buffer_tbl[i] = (ChunkBufferPtr)malloc(sizeof(ChunkBuffer));
if (!(buffer_ptr->chunk_buffer_tbl[i]->allocated_chunk_ptr = simple_malloc())) {
goto chunk_cleanup;
}
}
I don’t understand where I’m not allocating the memory properly and why.
Also, why there is a possible buffer overflow if I’m always allocating space for the struct and then allocate space for the inside pointers?
I'm facing a problem that seems complicated to me so i'll be very grateful to who can help me.
I'm sort of trying to replicate the array of string behavior only with structures
so instead of having for example
char **mys={"hello","this is a long message"};
I'm trying to have an array with structures (each structure containing an array of variable length). I'm not quite sure if I exposed my problem very well so i hope the code is going to indicate what i'm trying to do:
my two structures :
typedef struct
{
int *iTab;
int iTail;
}strDynArr;
typedef struct
{
strDynArr **iTab;
int iTailStruct;
int iTail;
}strDynDynArr;
All the functions related to those two structures :
void initArray(strDynArr *ar)
{
ar->iTab=malloc(sizeof(int));
ar->iTail=1;
}
void pushArray(int iElement,strDynArr *ar)
{
ar->iTab[ar->iTail-1]=iElement;
realloc(ar->iTab,(ar->iTail++)*sizeof(int));
}
void freeDynArray(strDynArr *ar)
{
free(*ar->iTab);
}
void initDynDynArray(strDynDynArr *ar)
{
ar->iTab=malloc(sizeof(int));
ar->iTail=1;
ar->iTailStruct=1;
}
//the problem
void pushDynDynArray(strDynArr *daElement,strDynDynArr *ar)
{
//ar->iTab[ar->iTail-1]=daElement;
ar->iTailStruct+=(daElement->iTail+1);
realloc(ar->iTab,(ar->iTailStruct)*sizeof(int));
ar->iTab[ar->iTailStruct-(daElement->iTail+1)]=daElement;
//realloc(ar->iTab,(ar->iTail++)*sizeof(int));
}
void freeDynDynDynArray(strDynDynArr *ar)
{
free(*ar->iTab);
}
And the one function where i'm stuck is pushDynDynArray so far i've attempted two things either just using the pointer that points on à structure strDynArr but i don't know how space is managed at all so i tryed to allocate the size of the all the structures contained in the array of the structure strDynDynArr.
So what i would like to know is how to allocate space for my array (iTab) for a structure of type strDynDynArr.
In other words what is the way for me to store several structures containing for example :
//content of structure 1
int *iTab={2,3,4};
int iTail=3;
//content of structure 2
int *iTab={5,8,9,10,54,8,2};
int iTail=7;
All help is welcome and thanks à lot for it !
For simplicity, lets call each array of "strings" a table (of arrays), and each "string" an array of items. In your case, the item type seems to be int:
typedef int item;
typedef struct {
size_t size; /* Number of items allocated for */
size_t used; /* Number of items in item[] */
item *item;
} array;
typedef struct {
size_t size; /* Number of array (pointers) allocated for */
size_t used; /* Number of arrays in array[] */
array *array;
} table;
It is common to define initializer macros and functions for such types:
#define ARRAY_INIT { 0, 0, NULL }
#define TABLE_INIT { 0, 0, NULL }
static inline void array_init(array *ref)
{
if (!ref) {
fprintf(stderr, "array_init(): NULL array!\n");
exit(EXIT_FAILURE);
}
ref->size = 0;
ref->used = 0;
ref->item = NULL;
}
static inline void table_init(table *ref)
{
if (!ref) {
fprintf(stderr, "table_init(): NULL table!\n");
exit(EXIT_FAILURE);
}
ref->size = 0;
ref->used = 0;
ref->array = NULL;
}
Also, free functions are obviously useful:
static inline void array_free(array *ref)
{
if (!ref) {
fprintf(stderr, "array_free(): NULL array!\n");
exit(EXIT_FAILURE);
}
free(ref->item); /* Note: free(NULL) is safe; it does nothing. */
ref->size = 0;
ref->used = 0;
ref->item = NULL;
}
static inline void table_free(table *ref)
{
size_t i;
if (!ref) {
fprintf(stderr, "table_free(): NULL table!\n");
exit(EXIT_FAILURE);
}
i = ref->size;
while (i-->0)
array_free(ref->array + i); /* array_free(&(ref->array[i])); */
free(ref->array);
ref->size = 0;
ref->used = 0;
ref->array = NULL;
}
When freeing a table, we want to free all (possible) arrays individually, then the memory used by the pointers. Note that one could assume that only used arrays are in use; however, I wanted the above to be thorough, and free all size arrays allocated for.
Both the init and free functions above take a pointer to an array or table. They should never be NULL. (That is, the item or array member in the structure may well be NULL; it's just that you should never call e.g. array_init(NULL); or table_free(NULL).)
Let us implement a function to push and pop individual ints from a solitary array (that may or may not be part of a table):
void array_push(array *ref, const item value)
{
if (!ref) {
fprintf(stderr, "array_push(): NULL array!\n");
exit(EXIT_FAILURE);
}
/* Need to grow the array? */
if (ref->used >= ref->size) {
size_t size; /* Minimum is ref->used + 1 */
void *temp;
/* Dynamic growth policy. Pulled from a hat. */
if (ref->used < 64)
size = 64; /* Minimum 64 elements */
else
if (ref->used < 1048576)
size = (3*ref->used) / 2; /* Grow by 50% up to 1048576 */
else
size = (ref->used | 1048575) + 1048577; /* Grow to next multiple of 1048576 */
temp = realloc(ref->item, size * sizeof ref->item[0]);
if (!temp)
return -1; /* Out of memory */
ref->item = temp;
ref->size = size;
}
/* Append value to array. */
ref->item[ref->used++] = value;
}
and a corresponding pop operation:
item array_pop(array *ref)
{
if (!ref) {
fprintf(stderr, "array_pop(): NULL array!\n");
exit(EXIT_FAILURE);
}
if (ref->used < 1) {
fprintf(stderr, "array_pop(): Array is already empty; nothing to pop.\n");
exit(EXIT_FAILURE);
}
/* Since ref->used is the *number* of items in the array,
we want to decrement it first, to get the last item in array. */
return ref->item[--ref->used];
}
In your program, you can use an array for example thus:
array scores = ARRAY_INIT;
array_push(&scores, 5);
array_push(&scores, 2);
printf("%d\n", array_pop(&scores)); /* Will print 2 */
printf("%d\n", array_pop(&scores)); /* Will print 5 */
array_free(&scores);
The line array scores = ARRAY_INIT; both declares and initializes the array (to an empty array). You could also equivalently use array scores; array_init(&scores);.
The resize or growth policy in array_push() is roughly along the lines I'd personally recommend, although the actual numerical values are pulled from a hat, and you may wish to adjust them. The idea is that there is a minimum number of items allocated for (say, 64). For bigger arrays, we increase the size fractionally, so that when the array is large, the size increase is also large. Many people use 100% increase in size (doubling the size, i.e. size = 2 * ref->used;), but I like 50% increase better (multiplying the size by one and one half, size = (3 * ref->used) / 2;). For huge arrays, we don't want to waste potentially lots of memory, so we allocate in fixed-size (but huge) chunks instead. (There is no need or real benefit to align the huge size to some multiple like I did; I just like it that way. And the more complicated code ensures you need to understand it and edit it, rather than submitting it raw as yours; otherwise, you'll instructor will catch you cheating.)
Pushing a single value to the last array in the table is now simple to implement:
void table_push_value(table *ref, const item value)
{
size_t i;
if (!ref) {
fprintf(stderr, "table_push_value(): NULL table!\n");
exit(EXIT_FAILURE);
}
/* Ensure there is at least one array. */
if (!ref->size < 1) {
/* Empty table: ref->size = 0, and ref->used must be 0 too. */
const size_t size = 1; /* Allocate for exactly one array. */
void *temp;
temp = realloc(ref->array, size * sizeof ref->array[0]);
if (!temp) {
fprintf(stderr, "table_push_value(): Out of memory.\n");
exit(EXIT_FAILURE);
}
ref->size = size;
ref->array = temp;
for (i = 0; i < size; i++)
array_init(ref->array + i); /* array_init(&(ref->array[i])); */
}
if (ref->used > 0)
i = ref->used - 1; /* Last array in table */
else
i = 0; /* Table is empty, so use first array */
array_push(ref->array + i, value); /* array_push(&(ref->array[i])); */
}
This time, you need special logic for an empty table, for both allocating the description for an array, as well as where to push.
Pop is simpler:
item table_pop_value(table *ref)
{
size_t i;
if (!ref) {
fprintf(stderr, "table_pop_value(): NULL table!\n");
exit(EXIT_FAILURE);
}
i = ref->used;
/* Find the last array with items in it, and pop from it. */
while (i-- > 0)
if (ref->array[i].used > 0) {
return array_pop(ref->array + i); /* array_pop(&(ref->array[i])); */
fprintf(stderr, "table_pop_value(): Empty table, no items to pop!\n");
exit(EXIT_FAILURE);
}
To push an entire array to a table (pushing the array of items in it, not making a copy of the items) is pretty simple, but we do need to implement a reallocation/growth policy again:
void table_push_array(table *ref, array *one)
{
if (!ref) {
fprintf(stderr, "table_push_array(): NULL table!\n");
exit(EXIT_FAILURE);
}
if (!one) {
fprintf(stderr, "table_push_array(): NULL array!\n");
exit(EXIT_FAILURE);
}
if (ref->used >= ref->size) {
size_t size, i;
void *temp;
if (ref->used < 1)
size = 1; /* Minimum size is 1 */
else
size = (ref->used | 7) + 9; /* Next multiple of 8 */
temp = realloc(ref->array, size * sizeof ref->array[0]);
if (!temp) {
fprintf(stderr, "table_push_array(): Out of memory.\n");
exit(EXIT_FAILURE);
}
ref->array = temp;
for (i = ref->size; i < size; i++)
array_init(ref->array + i); /* array_init(&(ref->array[i])); */
ref->size = size;
}
ref->array[ref->used] = *one; /* "shallow copy" */
ref->used++;
}
The corresponding pop operation should be pretty obvious by now:
array *table_pop_array(table *ref)
{
array retval = ARRAY_INIT;
if (!ref) {
fprintf(stderr, "table_pop_array(): NULL table!\n");
exit(EXIT_FAILURE);
}
if (ref->used < 1) {
fprintf(stderr, "table_pop_array(): Table is empty, no arrays to pop!\n");
exit(EXIT_FAILURE);
}
/* Decrement the used count, so it refers to the last array in the table. */
ref->used--;
/* Shallow copy the array. */
retval = ref->array[ref->used];
/* Init, but do not free, the array in the table. */
array_init(ref->array + ref->used); /* array_init(&(ref->array[ref->used)); */
/* Return the array. */
return retval;
}
There is a "trick" in table_pop_array() above, that you should understand. While we cannot return pointers to local variables, we can return structures. In the above case, the structure describes the array, and the pointer in it does not refer to a local variable, but to a dynamically allocated array of items. Structure types can be assigned just as normal scalar types (like int or double); it is basically the same as if you assigned each member separately.
Overall, you should notice I have not used a single malloc() call. This is because realloc(NULL, size) is equivalent to malloc(size), and simply initializing unused pointers to NULL makes everything simpler.
When a table is grown (reallocated), we do need to initialize all the new arrays, because of the above realloc() use pattern.
The above approach does not preclude direct access to specific arrays in the table, or specific items in an array. If you intend to implement such functions, two helper functions similar to
void table_need_arrays(table *ref, const size_t size);
void array_need_items(array *ref, const size_t size);
that ensure that the table has room for at least size arrays, and an array has room for at least size items. They are also useful when pushing multiple items or arrays consecutively, as then one can do e.g. table_need_arrays(&mytable, mytable.used + 10); to ensure there is room for additional 10 arrays in the table.
All throughout the functions above, you can see notation name_of_array + index, and a comment with corresponding &(name_of_array[index]). This is because the two notations are equivalent: pointer to the index'th element in name_of_array.
I didn't bother to compile-check the above code, so there might be typos hidden in there. (This too is intentional, because I want you to understand the code, and not just copy it and use it as your own without understanding any of the details.) However, the logic is sound. So, if you find a typo or issue, let me know in a comment, and I shall fix.