Memory management
@Edward pointed out that your malloc()
was missing a corresponding free()
— a memory leak. But I'd like to ask, why was malloc()
used in the first place? In C, returning new strings to the caller is a memory-management headache, so functions are often designed to avoid having to do that altogether. A common convention is have the caller supply the buffer in which to put the results. This feels unnatural to programmers accustomed to higher-level languages, but memory management in C is such a headache that avoiding heap allocation is considered the lesser evil.
In this case, the result is guaranteed to be no longer than the input. That is a special case in which you can use an in-out parameter: just overwrite the input buffer with the results.
An additional design flaw is that NAME_LEN
is baked into the parameter type. A good string-handling function should be prepared to accept a NUL-terminated string of any length. (I also consider words
to be an unfortunate parameter name, since words[i]
looks like it refers to the ith word, when it actually refers to the ith character of the name.)
Behaviour
Your function only works correctly with lowercase input. Even though the problem description lacks specificity, I'd consider this to be wrong behaviour. The way in which it fails is particularly interesting: "Barack Obama"
produces "BAOB"
.
Another ambiguity in the spec is how to interpret characters that are neither letters nor spaces. Personally, I would expect "barack o'bama"
to produce "BO"
, rather than "BOB"
as your function does.
It would be nice if the function were robust to weird input. Ideally, "barackspacespaceobama"
should produce "BO"
instead of "B O"
.
Implementation details
Initializing pos = 0
would be more natural than pos = -1
. Just replace all ++pos
with pos++
.
It's a good habit to make helper functions static
(even though it makes no difference for a trivially small program like this). Personally, I wouldn't bother with defining an is_terminating_char()
helper function.
It is unnecessary to test for is_letter()
before calling toupper()
. The documentation for toupper()
guarantees that "All other arguments in the domain are returned unchanged."
The i++
of the for-loop header, while technically correct, is a bit misleading, since i
can get incremented many times by the inner while
loop.
Suggested solution
In another answer, I suggested that strpbrk()
would be helpful. It turns out that strcspn()
is more appropriate. One advantage of the solution below is that it is easy to redefine what constitutes a word. If you want to consider hyphens to be word delimiters, you can just add it to WORD_DELIMS
.
Note that no special treatment of '\n'
is necessary; it gets skipped over just like any other whitespace. In this solution, it turns out that you don't even need to explicitly NUL-terminate the output either – it gets copied automatically.
I've chosen to have the function return the length of the output string. We get that information for free, and there might be scenarios in which it could spare the caller from having to call strlen()
on the result.
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Extracts the first character of each word of the name, converted to
* uppercase, overwriting the name. (Words are delimited by any amount
* of whitespace.) Returns the number of characters in the result
* (i.e. the number of words in the original name).
*/
size_t initials(char *name) {
static const char *WORD_DELIMS = "\t\n\v\f\r ";
char *in = name, *out = name;
// Skip any whitespace, then copy a character (which may be NUL)
while ((*out = toupper(*(in += strspn(in, WORD_DELIMS))))) {
in++; out++;
// Skip over the rest of the word
in += strcspn(in, WORD_DELIMS);
}
return out - name;
}
int main(int argc, char *argv[]) {
char name[161];
if (!fgets(name, sizeof(name), stdin)) {
return EXIT_FAILURE;
}
initials(name);
puts(name);
}
In case you find the loop to be too slick, here's an expanded version:
size_t initials(char *name) {
static const char *WORD_DELIMS = "\t\n\v\f\r ";
char *in = name, *out = name;
do {
in += strspn(in, WORD_DELIMS); // Skip any whitespace
*out = toupper(*in); // Copy a character (maybe NUL)
if (*in == '\0') {
return out - name;
}
in++; out++;
in += strcspn(in, WORD_DELIMS); // Skip over the rest of the word
} while (1);
}