Live data from Hacker News

Everything in C is undefined behavior

blog.habets.se

541–550 of 748 posts

Re: Everything in C is undefined behavior

#541

Earlier quoted context omitted.

I think the disconnect here is that you're operating on the assumptions built by using common architectures that have solved these problems in implementation specific forms, and you're used to those solutions. But just because those forms are common, doesn't mean the behavior is actually defined. Ex - I might be using a vendor specific compiler for custom embedded devices where dynamic linking isn't available at all,…

I’m not sure there’s a disconnect at all (note that I’m not saagarjah, they and lelanthran seem to be pushing back on each other’s opinions; I’m just asking a clarifying question).

Yes, and I'm saying your clarifying question hints at a misunderstanding.

You're already deep into the bowels of implementation specific behavior by the time we talk about dynamic linking. The C standard doesn't have anything to say about it at all.

My read on the above conversation is basically a discussion about asking/requiring vendors to properly document their implementation, as opposed to leaving it undocumented (the default - given my experience with hardware manufacturers...).

I don't think the real takeaway is that "instructions should be eliminated in case [blah blah blah]" it's that "Something is going to happen, please tell me what that is on your system, instead of leaving it as UB" (Basically - make UB in the standard implementation defined behavior from the vendor).

My read is that this won't happen because it's genuinely incredibly difficult to do, and this isn't a space overflowing with capital to allocate to the problem. But I do think there's merit to the idea of pushing vendors to provide coverage in this space AT SOME POINT.

Re: Everything in C is undefined behavior

#542
post #113

Yes there is tons of surprising and weird UB in C, but this article doesn't do a great job of showcasing it. It barely scratches the surface. Here's a way weirder example: volatile int x = 5; printf("%d in hex is 0x%x.\n", x, x); This is totally fine if x is just an int, but the volatile makes it UB. Why? 5.1.2.4.1 says any volatile access - including just reading it - is a side effect. 6.5.1.2 says that unsequenced…

I think C standard doesn't do itself any favors by using "undefined behavior" to signify both "anything can happen, including erasing all your data and setting your data center on fire" and "one of the very small and well defined set of things would happen, but we can not commit to which one". The latter is not exactly great, but significantly less dangerous than the former.

Re: Everything in C is undefined behavior

#543

Earlier quoted context omitted.

Are you talking about creating a pointer (more than one item) past an array, or dereferencing that pointer? Both are currently UB. For the former, I kinda get it. It may need to be there for cases like with segmented address space where p+10 could actually be a value less than p, for the eventually generated assembly. Maybe it should be fine to create such a pointer, but have it be "indeterminate value" or whatever,…

I'm not saying that the result of the dereference be known, I'm saying that the instructions to do the dereference be always emitted. Right now, if a dereference results in UB, the compiler may omit it entirely.

Wouldn't that make a compiler that emitted bounds checks violate the standard, since it would not be emitting the actual memory operations if you deref out of bounds?

Re: Everything in C is undefined behavior

#544
> the OpenBSD project has not been very receptive in the past for bug reports, my sense of “this is probably fine, in practice”, and that if OpenBSD wants to weed out UB from their code base, then that’s a major project that should be done in a better way than me just being the middle man between the LLM and them for a patch here and there.

Part of the reason for all the UB in OpenBSD is that UBSan doesn't run on that platform. When I ported OpenBSD's httpd to Linux, I found that UBSan tripped before the server even came up because the config flag parsing shifts into the MSB of a signed integer.

I tried to contribute back a patch (just make the flag bitfield unsigned), but it was ignored. I think if UBSan ran natively on OpenBSD, then there would be a lot more of these patches, and the maintainers would have to take an official stance on whether they think these bugs matter.

Re: Everything in C is undefined behavior

#545

Earlier quoted context omitted.

But it's genuinely useful. In all seriousness, are you sure you aren't perhaps just using the wrong language? At this point UB and leveraging it for optimization are core parts of the most performant C implementations. That said, I think there are many cases where compilers could make a better effort to link UB they're optimizing against to UB that appears in the code as originally authored and emit a diagnostic or e…

> At this point UB and leveraging it for optimization are core parts of the most performant C implementations. I am skeptical that NULL-pointer checks being removed contribute anything more than a rounding error in performance gains in any non-trivial program.

I got a measurable improvement from eliminating a null pointer check within the last week. Billions of devices have arm little cores, and the extra branch predictor pressure and frontend bandwidth from those instructions can be significant.

A standard way to eliminate those is to invoke undefined behavior if some condition is not met;

    if (a == NULL) {
      __builtin_unreachable();
    }
Which then allows elimination of the null check in later code, possibly after inlining some function.

Re: Everything in C is undefined behavior

#546

Earlier quoted context omitted.

What should the behavior above be defined to do?

Couldn’t you just define that function arguments are evaluated left to right? Or just throw an error.

Why? Just for this edge case? It could be faster and/or allow smaller code size to allow this to be undefined.

Undefined is also different from "depends on the compiler", because which behavior is chosen can even depend on the circumstances, whatever code appears before and/or after it.

That said, UB in code, such as this example of ordering of reads of volatile parameters being undefined, does not automatically mean that code that uses it is bad. It may very well be that the function being called doesn't misbehave either way.

Re: Everything in C is undefined behavior

#547

The UB in unaligned pointers is even worse: an unaligned pointer in itself is UB, not only an access to it. So even implicit casting a void*v to an int*i (like 'i=v' in C or 'f(v)' when f() accepts an int*) is UB if the cast pointer is not aligned to int. It is important to understand that this is a C level problem: if you have UB in your C program, then your C program is broken, i.e., it is formally invalid and wron…

Which is totally fine and expected for any decent programmer. Casting pointers is clearly here be dragons territory.

Yes but casting pointers is virtually required in any non-trivial C program, and frankly even a lot of the trivial ones, because there's no other way to do type erasure or generics. Well, there kind of are now, and there's always been macros, but void * has historically been the predominant way this is done at runtime.

Re: Everything in C is undefined behavior

#548
> probably meaning on an address that’s a multiple of sizeof(int), but who knows

Sigh. s/sizeof(int)/_Alignof(int)/.

There are good reasons for an implementation to have sizeof(int) = _Alignof(int) and not a mere multiple of it, but if you are going to discuss subtle points and UB, just stick to the language guarantees.

> But let’s say you have a modern machine, where NULL is a pointer to address zero, and you actually have an object there.

You don't program in C on such a machine. Or maybe memory is virtualized, and it does not matter that your object lives at physical address zero, as long as you can map a non-zero virtual address to it.

> So how do you print an uid_t?

    if ((uid_t)-1 
> It’s not rare for the denominator to come from untrusted input.

It's not rare for the array index to come from untrusted input.

It's not rare for the supposedly valid UTF-8 string to come from untrusted input.

...

Why single out division? This problem affects every partially defined operation. In the case of division at least, everyone learned in school that thou shalt not divide by zero. Adding two untrusted integers and forgetting that signed overflow is UB, not defined as a modulo? Your average programmer is much less likely to see that coming.

    > unsigned char a = 0xff;
    > unsigned char b = 1;
    > unsigned char zero = 0;
    > bool overflowed = (a + b) == zero;
    >
    > unsigned char a = 0x80;
    > uint64_t b = a 
Please. Convert your operands to wide enough types before the operation. Convert your results back to narrow enough types to compensate for integer promotion to wider types than you would have liked. Do that consistently, and you're good.

Here:

    unsigned char a = 0xff;
    unsigned char b = 1;
    unsigned char zero = 0;
    bool overflowed = (unsigned char)(a + b) == zero;

    unsigned char a = 0x80;
    uint64_t b = (uint32_t)a 

Re: Everything in C is undefined behavior

#549
post #299

Earlier quoted context omitted.

Author here. > It barely scratches the surface. I agree. The point of the post is not to enumerate and explain the implications of all 283 uses of the word "undefined" in the standard. Nor enumerate all the things that are undefined by omission. The point of the post is to say it's not possible to avoid them. Or at least, no human since the invention of C in 1972 has. And if it's not succeeded for 54 years, "try hard…

Fair enough! > And if it's not succeeded for 54 years, "try harder", or "just never make a mistake", is at least not the solution. And I 100% agree. UB is way overused by these standards for how dangerous it is, and as a consequence using C (and C++) for anything nontrivial amounts to navigating a minefield.

I think as compilers got smarter, UB changed somewhat in meaning. Originally the compilers didn't perform such complex analysis, and while invoking UB could break your program, it would still do something reasonable.

Re: Everything in C is undefined behavior

#550

> The compiler, and really the underlying hardware too, is playing a game of telephone with your UB intentions. The part about hardware is wrong BTW. In all the cases about null pointers and out-of-bounds access and integer overflow and whatnot, the hardware semantics are clearly defined, and the assembler code does exactly what is written. The way modern compilers act on your code makes C less safe than assembler in…

Author here > The part about hardware is wrong BTW Could you be more specific? I think by "wrong" you may mean "not actually relevant to UB", and you're right about that. If that's what you mean then that part is not for you. It's for the "but it's demonstrably fine" crowd. > the hardware semantics are clearly defined Yup. The article means to dive from the C abstract machine to illustrate how your defined intentions…

It seems like I simply misunderstood the point of the "game of telephone" metaphor. To be honest, even with your added explanation, I don't fully get why you express it that way. But I think we're in agreement on the substance, and I shouldn't have worded my response so harshly.
Post reply on HN