assert is a low-level mechanism that could be used (among other things) to implement your unit tests. It can also be useful for asserting preconditions and/or postconditions on your code.
Unit tests typically come in two varieties: those that should log an error, then continue testing, and those that immediately abort if a test fails. Clearly an
assert can only be used for the latter variety.
The main strength (and weakness) of
assert is that it immediately aborts the program, with no attempt at remediation, clean shutdown, or anything else. It's the "do not pass Go, do not collect $200" option.
This is useful when you've detected a condition so problematic that attempting to shut down cleanly is more likely to lead to more problems, or possibly destroy the user's data. Just for one obvious case, you've detected that the stack has been corrupted, so even attempting to return from the current function may start executing invalid code. Likewise, if you detect that the heap has been corrupted (especially in a program that uses the heap heavily) your safest course of action may be to shut down immediately, to at least prevent further damage to the system. As @Andrew said, this isn't a desirable outcome--but if the system has sufficient problems, it may be the least undesirable option available.
The other major characteristic of
assert is what you've noticed: in a release build it'll normally be compiled out so the expression in the
assert isn't evaluated1.
This indicates that if you use
assert directly in your code (as opposed to implementing a unit test, or similar) it should be reserved for cases where the logic remains complete even when it's absent. This can be things like testing that the stack and heap haven't bee corrupted. It can also be simply assuring the consistency of the programs internal logic. It definitely means that an
assert should not normally depend on input to the program, or anything along that line.
In the case you cited, you clearly can't put initialization into the assert itself. It might make sense to use an
assert on the return value (but more likely not). The question at that point is whether you can meaningfully continue execution at all. If failed initialization means you need to shut down immediately, no matter what, then an assert can make sense. If you can continue to the point of logging the result and shutting down more cleanly, then an assert isn't appropriate. If you might even be able to continue execution, and just have some features disabled, then an assert would clearly be entirely the wrong tool for the job.
Even in a case where you can't continue execution, using assert to test the return value is probably somewhat questionable. The initialization is (apparently) designed so it may return
false in case of failure. If it does so, that's still correct operation. The more fitting use of
assert would be to test its post-conditions in case of success. For example, let's assume that the initialization is required to set up a global pointer to some program parameters (and we'll leave discussing the wisdom of that design aside for the moment), in case of success it might make sense to assert that the pointer in question is non-null (because if the initialization succeeded and worked correctly, it must always be non-null).
In his comment, David Hammen pointed to some languages that omit
assert-like mechanisms on the grounds that: "the authors of those languages think
assert is evil, perhaps more so than is
goto." While I don't particularly doubt that at least one such ignorant buffoon has designed (and perhaps even implemented) a language, any such language should be avoided. That's not to say that
assert is particularly necessary--only that anybody who so clearly misunderstands something so simple shouldn't be trusted to tie his own shoes, not to mention designing a programming language.
If you wanted to question something (and you had a brain) what you'd question would be the inclusion of a fairly specialized construct in the standard library, when others that are likely to be of much wider use are omitted--especially in a library like C's that's missing so much in the way of fundamental functionality that most programs need far more often than they need
assert. This position would be reinforced by the fact that assert is quite trivial to implement on your own (a half dozen lines or so of simple code) while common tasks that involved a great deal more boiler plate are left to users to implement and re-implement in nearly every program they write.
That's a valid question, but (I think) it does have an answer (and a pretty simple one). When the C89/90 standard was written, a fair number of people were still doing development on small machines with extremely limited RAM and mass storage (e.g., a single floppy disc).
assert was something they could add that was fairly widely usable, and still small enough that it wouldn't cause a major problem for such limited systems.
It would also be possible (entirely reasonable, IMO) for somebody to point out that macros, as defined and implemented in the C preprocessor are seriously problematic--and as it happens,
assert is required to be a C preprocessor macro. If you eliminate preprocessor macros, you eliminate
assert (as it's defined in C today). I would assert (pardon the semi-pun) that this is a reasonable approach--in fact, it's probably fair to say that there's only one good reason to design a new language that includes the C preprocessor--to maintain compatibility with existing C source code (e.g., new versions of C or C++). For just about anybody else, using the C preprocessor (or its macros) would be highly questionable at best.
1. Interestingly, however, it still has to compile to a syntactically valid expression (typically something like
(void)0). Although almost nobody really does, it's officially valid to use
assert in places that expanding to an empty string would produce invalid syntax (e.g.,
something ? assert(x) : assert(y);).