Algorithm
There are more efficient string searching algorithms than this; if performance is important, you should research them.
Also, consider whether tolower()
is really what you want for a case-insensitive comparison - it's fine for the C locale, but in some locales, both e
and é
are equivalent to E
, in which case, comparing the results of tolower()
will miss matches and comparing toupper()
will produce false positives.
Includes
The code is missing definitions for size_t
, NULL
and tolower()
. It needs
#include <ctype.h>
#include <stddef.h>
Interface
All names beginning with is
are reserved for future use by the standard library; so declaring istrstr()
is Undefined Behaviour. Similarly, we can't call it stristr()
or similar; we could safely call it i_strstr
if you want.
The standard library strstr()
accepts str
as a const char*
. Even though it returns a pointer to non-const
and thus creates a hole in the type system, it's probably better to be consistent with that implementation, to avoid surprising users.
Logic
There's a redundant test here:
if (!substr[n]) {
return str;
}
else if (!str[n]) {
return substr[n] ? str : NULL;
}
We only reach the else
branch if substr[n]
is true, so we don't need to re-test it:
if (!substr[n]) {
return str;
}
else if (!str[n]) {
return str;
}
However, this still looks incorrect - if we ran out of str
while there's still some substr
remaining to check, then we didn't find a match; this fails the abcdefg
and efg
tests in my test program (below). We want to return NULL
in that case:
if (!substr[n]) {
return str;
}
else if (!str[n]) {
return NULL;
}
Alternatively, just omit the else
- we'll fall through to returning NULL before long anyway.
We can stop the loop when str
is too short to contain substr
:
const size_t substr_len = strlen(substr);
if (strlen(str) < substr_len)
return NULL;
while (str[substr_len]) {
/* ... */
}
To further reduce unnecessary work when str
is very long, we don't need to measure its full length - we can define a shorter_than()
function which iterates until either string ends. I wanted to call it is_shorter()
, but as we've seen above, that's reserved for future Standard Library extension.
Improved version
#include <ctype.h>
#include <stddef.h>
#include <string.h>
static int shorter_than(const char *a, const char *b)
{
while (*a++ && *b)
++b;
return *b;
}
char *i_strstr(const char *str, const char *substr)
{
const size_t substr_len = strlen(substr);
if (shorter_than(str, substr))
return NULL;
do {
for (const char *p = str, *q = substr; ; ++p, ++q) {
/* Have we matched all of substr */
if (!*q)
return (char*)str;
/* Test the next char */
if (tolower(*p) != tolower(*q))
break;
}
} while (str++[substr_len]);
return NULL;
}
Test program
Note carefully that many of these tests are designed to exercise the edge cases, such as matching (or not) at the extreme ends of the string, or using empty strings as arguments.
I've purposefully not tested passing null pointers to the functions; I consider that a programming error and should document that it would be undefined behaviour. Alternatively, we could add some appropriate tests, and adjust the code to pass them.
#include <stdarg.h>
#include <stdio.h>
int expect(int val, const char *file, int line, const char *message, ...)
{
if (!val) {
va_list args;
va_start(args, message);
fprintf(stderr, "%s:%d:fail: ", file, line);
vfprintf(stderr, message, args);
fprintf(stderr, "\n");
va_end(args);
}
return !val;
}
#define expect_true(a) expect((a), __FILE__, __LINE__, #a " should be true")
#define expect_false(a) expect(!(a), __FILE__, __LINE__, #a " should be false")
#define expect_equal(actual, expected) \
expect((actual)==(expected), __FILE__, __LINE__, #actual " should be " #expected)
int main()
{
static char p[] = "ABcdEF";
static char z[] = "";
/* Return the number of test failures */
return 0
+ expect_false(shorter_than("a", "a"))
+ expect_false(shorter_than("ab", "a"))
+ expect_true(shorter_than("a", "ab"))
+ expect_false(shorter_than("", ""))
+ expect_false(shorter_than("a", ""))
+ expect_true(shorter_than("", "a"))
+ expect_equal(i_strstr(p, ""), p)
+ expect_equal(i_strstr(p, "ABC"), p)
+ expect_equal(i_strstr(p, "abcdef"), p)
+ expect_equal(i_strstr(p, "bc"), p+1)
+ expect_equal(i_strstr(p, "cd"), p+2)
+ expect_equal(i_strstr(p, "ef"), p+4)
+ expect_equal(i_strstr(p, "BA"), NULL)
+ expect_equal(i_strstr(p, "abcdefg"), NULL)
+ expect_equal(i_strstr(p, "efg"), NULL)
+ expect_equal(i_strstr("", p), NULL)
+ expect_equal(i_strstr(z, z), z)
;
}
istrstr
mean? \$\endgroup\$strstr
. \$\endgroup\$strstr
, so… byte strings (no encoding specified). \$\endgroup\$