Skip to content

Instantly share code, notes, and snippets.

@TotallyNotChase
Last active April 26, 2020 16:29
Show Gist options
  • Save TotallyNotChase/d5789b5a12e794326e92f30cfe51b78e to your computer and use it in GitHub Desktop.
Save TotallyNotChase/d5789b5a12e794326e92f30cfe51b78e to your computer and use it in GitHub Desktop.
Some string functions to review
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "strfuncs.h"
#include "strfuncsdef.h"
// Append given char to given string
// Returns NULL on failure
static char* str_append(char element, char* str, size_t end_index, size_t* size)
{
char* temp;
if (end_index == *size)
{
/*
This reallocates str to target_size
If realloc fails, it frees str and returns NULL
*/
STR_INCREASE_ALLOCATION(str, temp, *size, str_append);
}
// Assign the char
str[end_index] = element;
// Assign the new size
return str;
}
// Inserts a string to given array at given index
// Returns NULL on failure
static char** strarr_append(char* elementstr, char** strarr, size_t end_index, size_t* size)
{
char** temp;
if (end_index == *size)
{
/*
This reallocates strarr to target_size
If realloc fails, it passes strarr to destroy_strarr
The current number of strings in strarr is *size (which is why we need to realloc)
So *size is passed as length param to destroy_strarr
*/
STRARR_INCREASE_ALLOCATION(strarr, temp, *size, strarr_append);
}
// Assign the string
strarr[end_index] = elementstr;
// Assign the new size
return strarr;
}
// Minimize given string for exactly the amount of memory it needs
// Returns NULL on failure
static char* trunc_string(char* str, size_t curr_len)
{
char* temp;
// 1 more slot for NULL terminator
/*
This reallocates str to len
If realloc fails, it frees str and returns NULL
*/
STR_MINIMIZE_ALLOCATION(str, temp, curr_len, trunc_string);
// Null terminate the string
str[curr_len] = '\0';
return str;
}
// Minimize given string array for exactly the amount of memory it needs
// Returns NULL on failure
static char** trunc_strarray(char** strarr, size_t curr_len)
{
char** temp;
/*
This reallocates strarr to curr_len (which is equal to the number of strings in strarr)
If realloc fails, it passes strarr to destroy_strarr
The current number of strings in strarr is curr_len
So curr_len is passed as length param to destroy_strarr
*/
STRARR_MINIMIZE_ALLOCATION(strarr, temp, curr_len, trunc_strarray);
return strarr;
}
// A function to get string user input
// Returns NULL on failure
char* get_string(const char* prompt)
{
size_t index, capacity = 1;
int element;
char* string = malloc(capacity * sizeof(*string));
if (string == NULL)
{
return NULL;
}
// Print the given prompt
fputs(prompt, stdout);
for (index = 0; (element = fgetc(stdin)) != EOF && element != '\n'; index++)
{
// Record every character input until user presses enter (and or we encounter EOF)
string = str_append((char) element, string, index, &capacity);
// Check if string is NULL, indicating error
STRINGFUNC_FAIL_CHECK(string, get_string);
}
if (ferror(stdin) != 0)
{
// Free the buffer and return NULL
free(string);
// If an error occured while reading input let the user know
fprintf(stderr, "An error occured while reading input from stdin in get_string\n");
// Clear the error as it has already been checked
clearerr(stdin);
return NULL;
}
if (index == 0 && element == EOF)
{
// Input was just EOF, invalid
free(string);
fprintf(stderr, "Encountered EOF with no input\n");
return NULL;
}
// Truncate and null terminate the string
string = trunc_string(string, index);
// Check if string is NULL, indicating error
STRINGFUNC_FAIL_CHECK(string, get_string);
return string;
}
// Splits a string by delimiter into an array of strings
// Returns NULL on failure
char** split_string(char delimiter, const char* string, size_t* length)
{
// Variables to keep track of splitarr
size_t arrsize = 2, arrindex = 0;
// Variables to keep track of elementstr
size_t strsize = 2, strindex = 0;
// Set up splitarr and elementstr with an initial size;
char** splitarr = malloc(arrsize * sizeof(*splitarr));
if (splitarr == NULL)
{
fprintf(stderr, "malloc failed in split_string\n");
return NULL;
}
char* elementstr = malloc(strsize * sizeof(*elementstr));
if (elementstr == NULL)
{
destroy_strarr(splitarr, arrindex);
fprintf(stderr, "malloc failed in split_string\n");
return NULL;
}
char* temp;
// Defining a custom scope
{
// Custom scope
int ignore_strindex_increment = 0;
for (int index = 0; string[index] != '\0'; strindex++, index++)
{
if (ignore_strindex_increment)
{
// Ignore latest increment
strindex--;
ignore_strindex_increment = 0;
}
if (string[index] == delimiter)
{
// elementstr ends here
// Truncate and null terminate the string
elementstr = trunc_string(elementstr, strindex);
/*
Check if string is NULL, indicating error
If it is, make sure splitarr is freed before returning
*/
STRINGFUNC_FAIL_CHECK_FREE_STRARR(elementstr, splitarr, arrindex, split_string);
// Add string to string array
splitarr = strarr_append(elementstr, splitarr, arrindex, &arrsize);
/*
Check if stringarr is NULL, indicating error
If it is, make sure elementstr is freed before returning
*/
STRINGFUNC_FAIL_CHECK_FREE_STR(splitarr, elementstr, split_string);
arrindex++;
// Cleanup - make elementstr empty and have a size of 1
// This is invoked right after encountering the delimiter
// This prepares elementstr for the next string
strsize = 1;
strindex = 0;
temp = realloc(NULL, strsize * sizeof(*temp));
/*
Check if string is NULL, indicating error
If it is, make sure splitarr is freed before returning
*/
STRINGFUNC_FAIL_CHECK_FREE_STRARR(temp, splitarr, arrindex, split_string);
elementstr = temp;
// The strindex will be incremented to 1 right afterwards, which we don't want
// elementstr should start from the beginning after cleanup
ignore_strindex_increment = 1;
}
else
{
// non-delimiter character, append to elementstr
elementstr = str_append(string[index], elementstr, strindex, &strsize);
/*
Check if string is NULL, indicating error
If it is, make sure splitarr is freed before returning
*/
STRINGFUNC_FAIL_CHECK_FREE_STRARR(elementstr, splitarr, arrindex, split_string);
}
}
if (ignore_strindex_increment)
{
// Ignore latest increment
strindex--;
}
}
// Truncate and null terminate the final string
elementstr = trunc_string(elementstr, strindex);
/*
Check if string is NULL, indicating error
If it is, make sure splitarr is freed before returning
*/
STRINGFUNC_FAIL_CHECK_FREE_STRARR(elementstr, splitarr, arrindex, split_string);
// Add final string to string array
splitarr = strarr_append(elementstr, splitarr, arrindex, &arrsize);
/*
Check if stringarr is NULL, indicating error
If it is, make sure elementstr is freed before returning
*/
STRINGFUNC_FAIL_CHECK_FREE_STR(splitarr, elementstr, split_string);
// Truncate the string array
splitarr = trunc_strarray(splitarr, arrindex);
/*
Check if splitarr is NULL, indicating error
This is the only time when STRINGFUNC_FAIL_CHECK can be used
in a function with more than one pointer, at this point all the char pointers
are already in splitarr, so freeing just splitarr in case of failure is enough
*/
STRINGFUNC_FAIL_CHECK(splitarr, split_string);
// Assign the length of the array
*length = arrindex + 1;
return splitarr;
}
// Free all strings inside an array of strings and the array itself
// Returns NULL on success
char** destroy_strarr(char** strarr, size_t length)
{
size_t index = 0;
while (index < length)
{
// Free the elements and assign the pointer to NULL
free(strarr[index]);
strarr[index++] = NULL;
}
// Free the array itself and assign to NULL
free(strarr);
strarr = NULL;
return strarr;
}
#ifndef STRFUNCS_H
#define STRFUNCS_H
/*
Take string input from user
Pass in a string prompt to display to the user prior to input
Returns a pointer to the input string
Returns NULL upon failure
Returned pointer should be freed by the user using free()
*/
char* get_string(const char* prompt);
/*
Split given string by delimiter into an array of strings
Pass in the address of a variable to store the length of the array
Returns a pointer to the array of strings
Returns NULL upon failure
Returned pointer should be freed by the user using destroy_strarr()
*/
char** split_string(char delimiter, const char* string, size_t* length);
/*
Free all the memory used by an array of strings
Assigns all the string elements as NULL
Returns NULL on success
*/
char** destroy_strarr(char** strarr, size_t length);
#endif // !STRFUNCS_H
#ifndef STRFUNCSDEF_H
#define STRFUNCSDEF_H
// DO NOT INCLUDE THIS HEADER IN YOUR CODE
// INTENDED FOR INTERNAL LIBRARY USE ONLY
// INTENDED FOR USE ONLY BY THIS HEADER
/*
Macro for sanity checking current length - should be used by char* str
Check if given length is suitable for increasing exponentially (multiply by 2)
If it is 0 (i.e exponential increase has no effect), change it to 1
If it exceeds SIZE_MAX when increased, free the buffer and return NULL
*/
#define STR_SIZE_CHECK(buffer, len, location) \
do { \
if (len == 0) \
{ \
len = 1; \
} \
else if (len > SIZE_MAX / 2) \
{ \
fprintf(stderr, "target allocation exceeds SIZE_MAX in " #location "\n"); \
free(buffer); \
buffer = NULL; \
return NULL; \
} \
} while (0)
// INTENDED FOR USE ONLY BY THIS HEADER
/*
Macro for checking realloc return value - should be used by char* str
Check if the temporary pointer returned by realloc is NULL
If it is NULL, free the actual buffer and return NULL
If it is not NULL, assign the actual buffer with the temporary pointer
*/
#define STR_ALLOC_FAIL_CHECK(buffer, temp, location) \
if (temp == NULL) \
{ \
free(buffer); \
buffer = NULL; \
fprintf(stderr, "realloc failed in " #location "\n"); \
return NULL; \
} \
buffer = temp
// INTENDED FOR USE ONLY BY THE LIB SOURCE FILE
/*
Macro for increasing memory for a string when needed - should be used by char* str
Includes guards for error checking
*/
#define STR_INCREASE_ALLOCATION(buffer, temp, curr_len, location) \
do { \
STR_SIZE_CHECK(buffer, curr_len, location); \
size_t target_capacity = curr_len * 2; \
temp = realloc(buffer, target_capacity * sizeof(*buffer)); \
STR_ALLOC_FAIL_CHECK(buffer, temp, location); \
curr_len = target_capacity; \
} while (0)
// INTENDED FOR USE ONLY BY THE LIB SOURCE FILE
/*
Macro for minimizing memory for a string - should be used by char* str
Includes guards for error checking
*/
#define STR_MINIMIZE_ALLOCATION(buffer, temp, curr_len, location) \
do { \
temp = realloc(buffer, (curr_len + 1) * sizeof(*buffer)); \
STR_ALLOC_FAIL_CHECK(buffer, temp, location); \
} while (0)
// INTENDED FOR USE ONLY BY THIS HEADER
/*
Macro for sanity checking size - should be used by char** strarr
Check if given size is suitable for increasing exponentially (multiply by 2)
If it is 0 (i.e exponential increase has no effect), change it to 1
If it exceeds SIZE_MAX when increased, free the buffer, all its contents and return NULL
*/
#define STRARR_SIZE_CHECK(buffer, len, location) \
do { \
if (len == 0) \
{ \
len = 1; \
} \
else if (len > SIZE_MAX / 2) \
{ \
buffer = destroy_strarr(buffer, len); \
fprintf(stderr, "target allocation exceeds SIZE_MAX in " #location "\n"); \
return NULL; \
} \
} while (0)
// INTENDED FOR USE ONLY BY THIS HEADER
/*
Macro for checking realloc return value - should be used by char** strarr
Check if the temporary pointer returned by realloc is NULL
If it is NULL, free the actual buffer, all its contents and return NULL
If it is not NULL, assign the actual buffer with the temporary pointer
*/
#define STRARR_ALLOC_FAIL_CHECK(buffer, temp, len, location) \
if (temp == NULL) \
{ \
buffer = destroy_strarr(buffer, len); \
fprintf(stderr, "realloc failed in " #location "\n"); \
return NULL; \
} \
buffer = temp \
// INTENDED FOR USE ONLY BY THE LIB SOURCE FILE
/*
Macro for increasing memory for an array of strings when needed - should be used by char** strarr
Includes guards for error checking
*/
#define STRARR_INCREASE_ALLOCATION(buffer, temp, curr_len, location) \
do { \
STRARR_SIZE_CHECK(buffer, curr_len, location); \
size_t target_capacity = curr_len * 2; \
temp = realloc(buffer, target_capacity * sizeof(*buffer)); \
STRARR_ALLOC_FAIL_CHECK(buffer, temp, curr_len, location); \
curr_len = target_capacity; \
} while (0)
// INTENDED FOR USE ONLY BY THE LIB SOURCE FILE
/*
Macro for minimizing memory for an array of strings string - should be used by char** strarr
Includes guards for error checking
*/
#define STRARR_MINIMIZE_ALLOCATION(buffer, temp, curr_len, location) \
do { \
temp = realloc(buffer, (curr_len + 1) * sizeof(*buffer)); \
STRARR_ALLOC_FAIL_CHECK(buffer, temp, curr_len, location); \
} while (0)
// INTENDED FOR USE ONLY BY THE LIB SOURCE FILE
/*
Macro for checking internal function calls for errors
All internal (private) strfuncs library functions return NULL on failure
Check if the return value was NULL from any of these functions
If it was, return NULL from current function
*/
#define STRINGFUNC_FAIL_CHECK(buffer, location) \
do { \
if (buffer == NULL) \
{ \
fprintf(stderr, "private strfunc function failed in " #location "\n"); \
return NULL; \
} \
} while (0)
/*
NOTE: This only makes sure the given buffer is free, if there are more than one
buffer in current scope, make sure they are all freed before returning
*/
// INTENDED FOR USE ONLY BY THE LIB SOURCE FILE
/*
Macro for memory cleanup before returning
Check if returned buffer from private stringfunc is NULL
If it is, free the other pointer in the current scope
which is the array of strings splitarr in case of split_strings
*/
#define STRINGFUNC_FAIL_CHECK_FREE_STRARR(buffer, bufferarr, bufferarr_len, location) \
do { \
if (buffer == NULL) \
{ \
destroy_strarr(bufferarr, bufferarr_len); \
fprintf(stderr, "private strfunc function failed in " #location "\n"); \
return NULL; \
} \
} while (0)
/*
NOTE: This is to be used when there are more than one pointer in current scope
Private stringfunc function will free the pointer that failed and assign it to NULL
But if there is another variable (which is splitarr in case of split_strings), it needs
to be freed
*/
// INTENDED FOR USE ONLY BY THE LIB SOURCE FILE
/*
Macro for memory cleanup before returning
Check if returned bufferarr from private stringfunc is NULL
If it is, free the other pointer in the current scope
which is the string elementstr in case of split_strings
*/
#define STRINGFUNC_FAIL_CHECK_FREE_STR(bufferarr, buffer, location) \
do { \
if (bufferarr == NULL) \
{ \
free(buffer); \
fprintf(stderr, "private strfunc function failed in " #location "\n"); \
return NULL; \
} \
} while (0)
/*
NOTE: This should be used in the same situation as the previous macro
This one frees the char* str instead of the char** strarray
*/
#endif // !STRFUNCSDEF_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment