static void
trim (char **ts)
{
unsigned char *exam;
unsigned char *keep;
I would check ts before checking *ts. (It's also not obvious why
you're passing ts in as a "char **".)
errno = EINVAL;
printf ("trim: %s\n", strerror (errno));
It is rare but conceivable that printf will itself alter errno
even if it succeeds; check the FAQ for the famous question of
why errno has changed to ENOTTY after a successful call to printf.
I would usually send errors to stderr, not stdout, but perhaps
more importantly, I would find it pretty upsetting for a "trim
whitespace" function to produce a diagnostic error if I handed
it an empty string!
return;
}
exam = (unsigned char *) *ts;
while (isspace (*exam)) {
++exam;
}
*ts = (char *) exam;
Ah-hah, the explanation becomes clear; you're changing the pointer.
This is almost certainly not a good API. Consider the case where
someone has allocated a string:
char *s = malloc(20);
strcpy(s, " hello ");
trim(&s);
There is no longer any way for them to determine what the address they
need to pass to free() is, because you've tossed the string away.
This produces a possibly-surprising behavior, which is that a series of
spaces with no other content can produce an empty string, and you don't
complain, but an empty string as input produces a complaint.
keep = exam;
while (*++exam) {
if (!isspace (*exam)) {
keep = exam;
}
}
if (*++keep) {
*keep = '\0';
}
}
Here you modify the contents of the string. Which is not necessarily bad,
but if you're willing to do that, I'd say go ahead and do the entire thing
by modification.
Anyone see bugs? It's not a trick question, I use this code. Just
wondering if I overlooked anything.
See above.
Here's how I'd probably do it:
#include <ctype.h>
#include <string.h>
#include <stdio.h>
char *trim(char *s) {
char *save, *next, *last = 0;
if (!s)
return NULL;
if (!*s)
return s;
/* s is a non-empty string */
save = s;
next = s;
last = s;
/* if there are leading spaces, copy everything after
* them to the beginning of the string
*/
if (isspace((unsigned char) *next)) {
while (isspace((unsigned char) *next))
++next;
while (*next) {
*s++ = *next++;
/* save location of last non-space */
if (!isspace((unsigned char) s[-1])) {
last = s;
}
}
} else {
/* just find last non-space */
while (*next) {
if (!isspace((unsigned char) *next)) {
last = next + 1;
}
++next;
}
}
/* if we found a non-space character, we saved a pointer
* to one past it; this is either the first of the
* terminal series of spaces, or a null byte.
*/
if (last)
*last = '\0';
return save;
}
char *tests[] = {
"",
" foo ",
" bar",
"baz ",
" ",
NULL
};
int
main(void) {
char buf[256];
int i;
for (i = 0; tests
; ++i) {
strcpy(buf, tests);
printf("<%s> => <%s>\n", tests, trim(buf));
}
return 0;
}
(For the curious: I originally failed the " " test, because I didn't
set "last" unless I'd seen a non-space character. Arguably, this would
also go away if I simply copied the trailing null byte, and that might
be better.)
-s