Any C gurus want to make sure I'm doing this elegantly and wisely?
Your solution works correctly as long as the argument is a valid null terminated string. This is the most important and in this regard, you are doing this wisely. More complicated solutions posted as answers do not meet this goal.
The compiler will inline strlen(".foo") and should be able to determine that both instances of strlen(str) return the same value, hence generate a single call as (clang and gcc do).
Yet it would be more elegant IMHO to compute the lengths once and use memcmp() instead of strcmp() which needs more work and is not inlined. You should also define str as a const char * to achieve const correctness and prevent warnings when calling your function with constant strings or string literals.
Testing for a specific ".foo" suffix is a special case of a more general problem: testing that a string is a suffix of another string.
Here is a simple and efficient solution:
#include <string.h>
int strEndsWith(const char *s, const char *suff) {
size_t slen = strlen(s);
size_t sufflen = strlen(suff);
return slen >= sufflen && !memcmp(s + slen - sufflen, suff, sufflen);
}
int strEndsWithFoo(const char *s) {
return strEndsWith(s, ".foo");
}
The code is very simple and generic, yet modern compilers will inline strEndsWithFoo very efficiently. As can be verified on GodBolt's compiler explorer, clang 12.0.0 computes the length of ".foo" at compile time and inlines memcmp() as a single cmp instruction, generating just 12 x86_64 instructions:
strEndsWithFoo: # @strEndsWithFoo
pushq %rbx
movq %rdi, %rbx
callq strlen
movq %rax, %rcx
xorl %eax, %eax
cmpq $4, %rcx
jb .LBB1_2
xorl %eax, %eax
cmpl $1869571630, -4(%rbx,%rcx) # imm = 0x6F6F662E
sete %al
.LBB1_2:
popq %rbx
retq
gcc 11.2 generates very similar code, also 12 instructions:
strEndsWithFoo:
pushq %rbx
movq %rdi, %rbx
call strlen
xorl %r8d, %r8d
cmpq $3, %rax
jbe .L7
xorl %r8d, %r8d
cmpl $1869571630, -4(%rbx,%rax)
sete %r8b
.L7:
movl %r8d, %eax
popq %rbx
ret
Intel's ICC compiler generates a long and complex set of SIMD instructions, much more difficult to follow and possibly less efficient even on Intel processors. The performance depends heavily on the efficiency of the strlen() library function, so benchmarks should include various distributions of string lengths.
There is no absolute answer to what if the most efficient solution? but simplicity does not preclude efficiency, and simple straightforward code is easier to validate. When it combines simplicity, correctness and efficiency, elegance is achieved.
Quoting Brian Kernighan:
Controlling complexity is the essence of computer programming.
Software Tools (1976), p. 319 (with P. J. Plauger).
Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?
"The Elements of Programming Style", 2nd edition, chapter 2.