Yes, your check is correct, but it is certainly not the most efficient (if by "efficiency" you mean the computational efficiency). The obvious intuitive inefficiency in your implementation is based on the fact that when the strings actually overlap, the strlen
calls will iterate over their common portion twice.
For the sake of formal efficiency, one might use a slightly different approach
int are_overlapping(const char *a, const char *b)
{
if (a > b) /* or `(uintptr_t) a > (uintptr_t) b`, see note below! */
{
const char *t = a;
a = b;
b = t;
}
while (a != b && *a != '\0')
++a;
return a == b;
}
An important note about this version is that it performs relational comparison of two pointers that are not guaranteed to point to the same array, which formally leads to undefined behavior. It will work in practice on a system with flat memory model, but might draw criticism from a pedantic code reviewer. To formally work around this issue one might convert the pointers to uintptr_t
before performing relational comparisons. That way the undefined behavior gets converted to implementation-defined behavior with proper semantics for our purposes in most (if not all) traditional implementations with flat memory model.
This approach is free from the "double counting" problem: it only analyzes the non-overlapping portion of the string that is located "earlier" in memory. Of course, in practice the benefits of this approach might prove to be non-existent. It will depend on both the quality of your strlen
implementation and one the properties of the actual input.
For example, in this situation
const char *str = "Very very very long string, say 64K characters long......";
are_overlapped(str, str + 1);
my version will detect the overlap much faster than yours. My version will do it in 1 iteration of the cycle, while your version will spend 2 * 64K iterations (assuming a naive implementation of strlen
).
If you decide to dive into the realm of questionable pointer comparisons, the above idea can also be reimplemented as
int are_overlapping(const char *a, const char *b)
{
if (a > b)
{
const char *t = a;
a = b;
b = t;
}
return b <= a + strlen(a);
}
This implementation does not perform an extra pointer comparison on each iteration. The price we pay for that is that it always iterates to the end of one of the strings instead of terminating early. Yet it is still more efficient than your implementation, since it calls strlen
only once.