Recently I have been trying out a simple but really interesting interview question – how do you reverse a string in C?
Hopefully most people should be aware that string functions in C are limited, there are ones like strlen, strcopy etc. but there isn’t a string.reverse such as those present in high level languages.
One of the first pitfalls people fall into is how strings are formed in C. They are a memory mapping of characters followed by the null character signalling the end of the string.
For example ‘Turnip’ would look like the following in memory:
T u r n i p /0
So how do we go about reversing a string? Well there are lots of different ways, some are better than others, some easier, and some programmatically more efficient in space or processing.
So the first way would be to use a new array to hold the temporary version of the reversed string.
void rev_string(char* str) { int i, j; char* tmp; tmp = malloc((strlen(str) + 1) * sizeof(char)); for(i = 0, j = strlen(str) - 1; i < strlen(str); i++, j--) { tmp[j] = str[i]; } tmp[strlen(str)] = '/0'; strcpy(str, tmp); free(tmp); }
Now for maintainability and readability the above example is fine but its memory requirements are quite high, as it needs a temporary store the same size as the input string.
Of course everyone should know that on page 62 of Kernighan & Ritchie’s ‘The C Programming Language’ shows how to perform in-place string reversal with a single temporary character and not an allocated array.
char* rev_string(char* const str) { int i, j; char tmp; for(i = 0, j = strlen(str)-1; i < j; i++; j--) { tmp = str[i]; str[i] = str[j]; str[j] = tmp; } return str; }
This is one of the best examples, however from here we can optimise space requirements even further. What if we don’t want to even declare the temporary variable, well there are two versions.
First we can do a clever bit of bitwise operators using XOR.
char* rev_string(char* str) { int end= strlen(str)-1; int start = 0; while( start<end ) { str[start] ^= str[end]; str[end] ^= str[start]; str[start]^= str[end]; ++start; --end; } return str; }
The other way is one that I don’t really like as it can break some runtime optimisations and security/memory checkers. As mentioned earlier the end character in a string is the null character, we can use this memory location as the temporary hold location.
char* rev_string(char* const str) { int i, j, l; l = strlen(str); for(i = 0, j = l - 1; i < j; i++; j--) { str[l] = str[i]; str[i] = str[j]; str[j] = tmp; } str[l] = '/0'; return str; }
Its sneaky and breaks many rules but sometimes memory is scarce.