2

I was using C's assert.h assert function in a method similar to this:

int x = 3;

if (x == 3)
    printf("x is 3 ✅");

assert(x != 3);

When running it, I found out that the if statement is skipped entirely, and the program terminates when it reaches the assert statement. Needless to say, this caused a couple of extremely nasty bugs in my program before I found the culprit. What could be the cause of this? Why is the conditional being skipped entirely? If it isn't being skipped, then why is the code inside not being executed? I set up an online example here.

human bean
  • 847
  • 3
  • 15
  • it does not, you have a different bug in there, post a whole program that shows the problem – pm100 Dec 02 '22 at 23:49
  • 7
    `printf("x is 3 ✅");` lacks a _flush_, so data is in `stdout`, ready to go, but the assertion fails and that pending output is lost. Try adding `fflush(stdout);` before assert or add `"\n"` to the `printf()`. – chux - Reinstate Monica Dec 02 '22 at 23:54

1 Answers1

6

The if statement is not skipped.

When assert is executed, it is not considered a "clean" program termination. Therefore, I/O buffers are not flushed. Try adding a fflush() call to explicitly flush stdout's buffer:

int x = 3;

if (x == 3)
{
    printf("x is 3 ✅");
    fflush(stdout);
}

assert(x != 3);

Here are the relevant paragraphs from the C17 standard (italic emphasis mine):

7.2.1.1

The assert macro puts diagnostic tests into programs; it expands to a void expression. When it is executed, if expression (which shall have a scalar type) is false (that is, compares equal to 0), the assert macro writes information about the particular call that failed (including the text of the argument, the name of the source file, the source line number, and the name of the enclosing function — the latter are respectively the values of the preprocessing macros __FILE__ and __LINE__ and of the identifier __func__) on the standard error stream in an implementation-defined format. It then calls the abort function.

7.22.4.1

The abort function causes abnormal program termination to occur, unless the signal SIGABRT is being caught and the signal handler does not return. Whether open streams with unwritten buffered data are flushed, open streams are closed, or temporary files are removed is implementation-defined.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
DarkAtom
  • 2,589
  • 1
  • 11
  • 27
  • I see. Thanks for the explanation; I never considered stream flushing to be the problem! Quick question: when *are* buffers flushed, then? In my head, I would assume `stdout` is flushed when scope end is reached; but clearly that's not the case. How and when is `stdout` flushed? – human bean Dec 03 '22 at 00:15
  • It probably wouldn't even be necessary in OP's case (unless redirecting stdout to a file) if the format string weren't missing a newline. – Tom Karzes Dec 03 '22 at 00:17
  • @humanbean It depends on the buffering mode. Standard output is usually flushed when a newline is written (i.e. "line buffered"), but since your format string is missing the normal trailing newline, it isn't being flushed when you expect it. You should add the missing newline in any case. – Tom Karzes Dec 03 '22 at 00:18
  • @TomKarzes I believe that whether `stdout` is line-buffered by default is implementation-defined. – DarkAtom Dec 03 '22 at 00:21
  • I believe that counts as a separate question; thanks to both of you gentlemen for helping me! :) – human bean Dec 03 '22 at 00:23
  • @DarkAtom That may be true (note that I said "usually", not "always"), but on any normal system it shouldn't be more aggressively buffered than that (though it may be less aggressively buffered). If it is, then it will have portability problems. – Tom Karzes Dec 03 '22 at 00:23
  • 1
    @humanbean In general (and this is not an exhaustive list), a stream is flushed when calling `fclose()`, `fflush()`, `freopen()`, `exit()` and returning from `main()`. It is also flushed "instantly" if the stream is unbuffered and when outputting a `\n` if the stream is line-buffered (check out `setbuf()` and `setvbuf()`). – DarkAtom Dec 03 '22 at 00:24
  • @DarkAtom Much obliged. I will definitely be reading more on C streams; not really my strong suit! – human bean Dec 03 '22 at 00:26
  • @DarkAtom Standard error (stderr) is usually unbuffered. – Tom Karzes Dec 03 '22 at 00:27
  • @TomKarzes And that's for a very good reason: usually when calling `abort()` or alike, you output logs and errors on `stderr` so that they show up after the program dies. – DarkAtom Dec 03 '22 at 00:30
  • @humanbean "How and when is stdout flushed? " --> [What are the rules of automatic stdout buffer flushing in C?](https://stackoverflow.com/q/39536212/2410359). – chux - Reinstate Monica Dec 03 '22 at 01:21