Live data from Hacker News

C++26: A User-Friednly assert() macro

sandordargo.com

61–70 of 89 posts

Re: C++26: A User-Friednly assert() macro

#61

Earlier quoted context omitted.

I'm sorry, but what exactly is the problem with the code? I've been staring at it for quite a while now and still don't see what is counterintuitive about it.

There's nothing wrong with it. It does exactly what you think it does when passed null.

A lot of compilers will optimize out a NULL pointer check because dereferencing a NULL pointer is UB.

Because assert will not run the following code in the case of a NULL pointer, AFAIK this exact code is still defined behavior, but if for some reason some code dereferenced the NULL pointer before, it would be optimized out - there are some corner cases that aren't obvious on the surface.

This kind of thing was always theoretically allowed, but really started to become insidious within the past 5-10 years. It's probably one of the more surprising UB things that bites people in the field.

GCC has a flag "-fno-delete-null-pointer-checks" to specifically turn off this behavior.

https://qinsb.blogspot.com/2018/03/ub-will-delete-your-null-...

This is an actual Linux kernel exploit caused by this behavior where the compiler optimized out code that checked for a NULL pointer and returned an error.

https://lwn.net/Articles/342330/

Re: C++26: A User-Friednly assert() macro

#62
post #37

Earlier quoted context omitted.

If your assert compiles down to `if (condition) {}` in production then the compiler will optimize away the condition while keeping any side effects.

Yeah which may not be what you want. E.g. `assert(expensive_to_compute() == 0)`. The correct way to solve this is with debug asserts (as in Rust, or how the parent described).

Compilers are very good these days. If it has no side effects it will likely be compiled out.

Re: C++26: A User-Friednly assert() macro

#63

Earlier quoted context omitted.

Let's not vague post on HN. What's the problem with the above?

The problem is the code unconditionally dereferences the pointer, which would be UB if it was a null pointer. This means it is legal to optimize out any code paths that rely on this, even if they occur earlier in program order.

But if the assertion fails, the program is aborted before the pointer would have been dereferenced, making it not UB. This explanation is bogus.

Re: C++26: A User-Friednly assert() macro

#64

Earlier quoted context omitted.

Let's not vague post on HN. What's the problem with the above?

The problem is the code unconditionally dereferences the pointer, which would be UB if it was a null pointer. This means it is legal to optimize out any code paths that rely on this, even if they occur earlier in program order.

> it is legal to optimize out any code paths that rely on this, even if they occur earlier in program order.

I don't think this is true. The compiler cannot remove or reorder instructions that have a visible effect.

  if (p == 0)
    printf("Ready?\n");
  *p++;
The printf() can't be omitted.

Re: C++26: A User-Friednly assert() macro

#65

Earlier quoted context omitted.

> The problem is the code unconditionally dereferences the pointer, which would be UB if it was a null pointer. Only when NDEBUG is defined, right?

No, the code that does this is always active

Shouldn't control flow diverge if the assert is triggered when NDEBUG is not defined? Pretty sure assert is defined to call abort when triggered and that is tagged [[noreturn]].

Re: C++26: A User-Friednly assert() macro

#66

Earlier quoted context omitted.

No, the code that does this is always active

Right so strictly speaking C++ could do anything here when passed a null pointer, because even though assert terminates the program, the C++ compiler cannot see that, and there is then undefined behaviour in that case

> because even though assert terminates the program, the C++ compiler cannot see that

I think it should be able to. I'm pretty sure assert is defined to call abort when triggered and abort is tagged with [[noreturn]], so the compiler knows control flow isn't coming back.

Re: C++26: A User-Friednly assert() macro

#67

Earlier quoted context omitted.

The problem is the code unconditionally dereferences the pointer, which would be UB if it was a null pointer. This means it is legal to optimize out any code paths that rely on this, even if they occur earlier in program order.

> it is legal to optimize out any code paths that rely on this, even if they occur earlier in program order. I don't think this is true. The compiler cannot remove or reorder instructions that have a visible effect. if (p == 0) printf("Ready?\n"); *p++; The printf() can't be omitted.

> The compiler cannot remove or reorder instructions that have a visible effect.

You might be surprised! When it comes to UB compilers can and do reorder/eliminate instructions with side effects, resulting in "time travel" [0].

IIRC the upcoming version of the C standard bans this behavior, but the C++ standard still allows it (for now, at least).

[0]: https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...

Re: C++26: A User-Friednly assert() macro

#68

Earlier quoted context omitted.

Yeah which may not be what you want. E.g. `assert(expensive_to_compute() == 0)`. The correct way to solve this is with debug asserts (as in Rust, or how the parent described).

Genuine question, does Rust know if `expensive_to_compute()` has side effects? There are no params, so could it be compiled out if the return value is ignored? Ex: `expensive_to_compute()` What about: `(void) expensive_to_compute()`?

No, Rust is the same as C++ in terms of tracking side effects. It doesn't matter that there are no parameters. It could manipulate globals or call other functions that have side effects (e.g. printing).

Re: C++26: A User-Friednly assert() macro

#69

Earlier quoted context omitted.

The problem is the code unconditionally dereferences the pointer, which would be UB if it was a null pointer. This means it is legal to optimize out any code paths that rely on this, even if they occur earlier in program order.

> it is legal to optimize out any code paths that rely on this, even if they occur earlier in program order. I don't think this is true. The compiler cannot remove or reorder instructions that have a visible effect. if (p == 0) printf("Ready?\n"); *p++; The printf() can't be omitted.

No, this is explicitly legal. Most compilers will shy away from it these days since it made a lot of people upset, but it's definitely allowed.

Re: C++26: A User-Friednly assert() macro

#70
post #50

Earlier quoted context omitted.

Assert doesn't work like that in any language.

It does in Rust: assert is always enabled, whereas the debug-only version is called debug_assert. But yes, “assert” in most languages is debug-only.

He said

> some people would expect it to enforce that the pointer is non-null, then proceed

No language magically makes the pointer non-null and then continues. I don't even know what that would mean.

Post reply on HN