With all the storage available in a modern computer, it’s easy — and often perfectly okay — to be overly generous when allocating memory. Still, the old coder in me has a lingering desire to save every byte possible. So when it comes to crafting a solution for this month’s Exercise, my desire is to be byte stingy.
The issue for this week’s Lesson isn’t to convert between camelCase and snake_case, but rather to perfectly allocate storage for the converted strings.
For the snake_case to camelCase conversion, fewer characters are required. My approach is to count the number of underscores in the original name, then allocate storage based on the original name’s string size, minus the number of underscores.
For the camelCase to snake_case conversion, more characters are required. The quantity depends on the number of uppercase characters found in the name. For each uppercase character, an underscore is added. To allocate storage, I use the original string’s size, plus the number of uppercase characters found.
Here is my new solution:
2023_08_19-Lesson.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> int main() { const int count = 7; char *variable[] = { "readInputMeter", "cyclical_redundancy_check", "bumpyRide", "search_for_node", "string_convert", "divideByZeroError", "giveUpAndExplode" }; char *v[count]; int x,c,us,caps; char *n; for(x=0; x<count; x++ ) { n = variable[x]; /* initialize pointer n */ c = 0; /* initialize offset */ /* test for the underscore */ if( strchr(variable[x],'_') ) { /* name is in snake_case */ /* count the underscores */ us = 0; while( *n ) { if( *n=='_' ) us++; n++; } /* re-initialize n */ n = variable[x]; /* allocate proper storage */ v[x] = malloc( strlen(variable[x]) - us + 1 ); if( v[x]==NULL ) { fprintf(stderr,"Memory allocate error\n"); exit(1); } /* build the new string */ while( *n ) { if( *n=='_' ) { n++; *(v[x]+c) = toupper(*n); } else { *(v[x]+c) = *n; } n++; c++; } } else { /* name is in camelCase */ /* count the capital letters */ caps = 0; while( *n ) { if( isupper(*n) ) caps++; n++; } /* re-initialize n */ n = variable[x]; /* allocate proper storage */ v[x] = malloc( strlen(variable[x]) + caps + 1 ); if( v[x]==NULL ) { fprintf(stderr,"Memory allocate error\n"); exit(1); } /* build the new string */ while( *n ) { if( isupper(*n) ) { *(v[x]+c) = '_'; c++; *(v[x]+c) = tolower(*n); } else { *(v[x]+c) = *n; } n++; c++; } } /* cap the string */ *(v[x]+c) = '\0'; } /* output the result */ for(x=0; x<count; x++ ) printf("%25s -> %s\n", variable[x], v[x] ); return(0); }
For the string_case to camelCase conversion, the number of underscores are saved in variable us
. This variable is used to calculate needed storage for the converted string:
v[x] = malloc( strlen(variable[x]) - us + 1 );
The underscore character count (us
) is subtracted from the original string’s length: strlen(variable[x])
Then one is added to create storage for the new string.
When converting from camelCase to string_case, the number of capital letters found is stored in variable caps
. This value is added to the original string’s length to allocate storage for the new string:
v[x] = malloc( strlen(variable[x]) + caps + 1 );
Honestly, in the era of multi-gigabyte computers, quibbling over a handful of bytes here and there is silly. Yet it’s always good practice to be accurate, especially when dealing with larger memory chunks.
The other aspect of this month’s Exercise is to properly detect whether a name is true camelCase or snake_case. I decided not to pursue this angle, though I may cover such detection methods in a future Lesson.
The typical use of C these days is for something that runs 24/7/365 on a server, possibly with a squillion threads. I therefore think it’s good practice to use only the exact amount of memory you need and to free it as soon as you’ve finished with it.
A few bytes here and there might not do any harm by themselves but over time they can build up. Someone might steal your code and turn it into a microservice callable by large numbers of users so it might cause them unhappiness if it ran out of memory.
I wonder whether valgrind checks for strings (char arrays) being null terminated before the end of the allocated memory? I’d better check.
From LinkedIn, the feedback also praised the desire to count every byte.
I thought about writing a string_check() utility that checks validity. For example, it would scan for multiple null bytes
\0
or non-ASCII values and return a grade. I’m unsure whether valgrind does that.