I wrote a function (get_last_word_of
) that takes a C string and a destination buffer, and then copies the last word into the buffer. The code was originally C++, and was changed into C later.
#include <assert.h>
#include <stdio.h>
#include <string.h>
// Check whether the two values are equal, and print an error if they are not.
void AssertEq(int lhs, int rhs, int line) {
if (lhs != rhs)
printf("Fail: %d != %d (line %d)", lhs, rhs, line);
}
#define ASSERT_EQ(lhs, rhs) do { AssertEq(lhs, rhs, __LINE__); } while (0);
// Given a valid C string pointer, find the index of the last character that
// is not whitespace. If str points to an empty string, return -1.
int find_index_of_last_nonwhitespace(char const* str) {
assert(str && "str must point to a valid C string");
int const length = strlen(str);
// We subtract 1 to skip the null terminator. Seeing as we check p >= str
// before we do anything else, this should be okay even for a str that is empty.
char const* p = str + length - 1;
while (p >= str && *p == ' ')
--p;
return p - str;
}
// Return the index of the beginning last word in the given C string. If the string
// is empty, return 0.
int find_index_of_beginning_of_last_word(char const* str) {
assert(str && "str must point to a valid C string");
int end_of_last_word = find_index_of_last_nonwhitespace(str);
// Subtract 1 so that we have the index of the first letter
char const* p = str + end_of_last_word;
while (p >= str && *p != ' ')
--p;
return p - str + 1; // To compensate for this being the index prior to the word.
}
// Given a destination buffer and a source C string pointer, copy the source
// into the destination until a space or the end of the string is hit.
// The buffer must be large enough to store the word and a \0 character after it.
// If dest == src, simply truncate after the first word.
void wordcpy(char* dest, char const* src) {
assert(src && "src must point to a valid C string");
assert(dest && "dest must point to a valid buffer");
char* d = dest;
char const* s = src;
for ( ; *s != '\0' && *s != ' '; ++s, ++d)
*d = *s;
*d = '\0';
}
// Given a pointer to a C string, and a pointer to an output buffer that is at least
// as large as the last word in the input plus one, copy the last word of the input
// into the output buffer.
void get_last_word_of(char const* input, char* output) {
assert(input && "input must be a valid C string");
assert(output && "output must be a valid buffer");
int index_of_last_word = find_index_of_beginning_of_last_word(input);
wordcpy(output, input + index_of_last_word);
}
int main() {
ASSERT_EQ(find_index_of_last_nonwhitespace("Test "), 3);
ASSERT_EQ(find_index_of_last_nonwhitespace("Test"), 3);
ASSERT_EQ(find_index_of_last_nonwhitespace("Te st "), 4);
ASSERT_EQ(find_index_of_last_nonwhitespace("Te st"), 4);
ASSERT_EQ(find_index_of_last_nonwhitespace(""), -1);
ASSERT_EQ(find_index_of_last_nonwhitespace(" "), -1);
ASSERT_EQ(find_index_of_beginning_of_last_word("Test"), 0);
ASSERT_EQ(find_index_of_beginning_of_last_word("Test "), 0);
ASSERT_EQ(find_index_of_beginning_of_last_word("Test test"), 5);
ASSERT_EQ(find_index_of_beginning_of_last_word("Test test "), 5);
ASSERT_EQ(find_index_of_beginning_of_last_word(""), 0);
ASSERT_EQ(find_index_of_beginning_of_last_word(" "), 0);
char buf[100];
wordcpy(buf, "Hello");
ASSERT_EQ(strcmp(buf, "Hello"), 0);
wordcpy(buf, "Hello ");
ASSERT_EQ(strcmp(buf, "Hello"), 0);
wordcpy(buf, " ");
ASSERT_EQ(strcmp(buf, ""), 0);
return 0;
}
I'm primarily interested in:
- What inputs (if any) could cause these functions to perform undefined behaviour?
- Are there enough comments?
- Is the
ASSERT_EQ
macro safe to use, and is there any way to let it be used with types other thanint
? (I used templates in C++, but am at a loss in C.) - Would there be a significant advantage to using
size_t
instead ofint
here? - Are the tests sufficient? Are there any cases I missed? Are some unnecessary?
Any further nitpicking is of course welcome.
assert(input && "input must be a valid C string");
will never fail. If input is false, the string will evaluate to true, no? \$\endgroup\$&&
, not||
. Ifinput
is false, the string will never evaluate at all (and even if it does,false && true
is stillfalse
). \$\endgroup\$