Live data from Hacker News

Undefined behavior in C is a reading error

yodaiken.com

171–180 of 503 posts

Re: Undefined behavior in C is a reading error

#171

Compilers have become more powerful (opening up new ways to exploit undefined behavior) and the primary C compilers are free software with corporate sponsors, not programmer customers (or else perhaps Andrew Pinski would not have been so blithe about ignoring his customer Felix-gcc in the GCC bug report cited above). This is the real problem. We have reached a situation where a small number of compilers dominate the…

> The C standard is a product of an era where you would pay for your tools and so would demand a refund from any compiler vendor that would treat undefined behavior in an absurd manner. Since current compilers aren’t out to do anything malicious with UB, but instead simply treat it as “assume this can’t happen and proceed accordingly”, it’s not clear at all to me what you think paid compilers would do here instead: r…

I see this argument a lot, but it's silly. I don't have to care whether gcc changed the settled semantics without notice or documentation out of sincere belief that they were helping or out of a desire to beat a benchmark that doesn't matter to me, or out of habit. It's not the intent of the compiler authors that matters, but their disregard of the needs of application programmers.

Re: Undefined behavior in C is a reading error

#172
post #119

Earlier quoted context omitted.

"Undefined behavior" is a term of art relevant to C, meaning that the standard no longer has any comment about what happens. Thus the comments about launching nukes, or destroying your machine, etc., being standards complaint, even though obvious real compilers won't actually emit code that does that. Dereferencing a nil pointer in Java is not undefined behavior. It is defined; it throws a NullPointerException, and "…

Point taken about NPEs being defined behavior, but from a practical point of view, a bug is a bug (and bugs is what the parent comment was referring to). Whether the bug was due to a well-defined or undefined behavior seems like an exercise in assigning blame to another entity (the language, the committee, the compiler, etc).

Correct assignment of (technical) blame is an important engineering task, though. You sound to me like you are thinking that's some sort of wrong thing to do, but I would disagree. Identifying whether the bug is a defined (in C) behavior or the result of the compiler making a certain choice as the result of your code invoking undefined behavior is an important element of fixing the bug; if you don't know which is which you're operating under a critical handicap.

Re: Undefined behavior in C is a reading error

#173

Earlier quoted context omitted.

You are mistaken, the C standard is quite clear that it does not make any guarantees regarding the behavior of programs that exhibit undefined behavior, and that signed integer overflow is undefined behavior.

"for (int i=param; i < param + 16; i++) does not have a guaranteed loop count in the presence of undefined behavior" is true, but it's equally true that the C standard is quite clear that undefined behavior can be ignored, so we can validly treat "for (int i=param; i < param + 16; i++)" as if it were guaranteed to loop 16 times in all cases.

No, the C standard doesn't say that "undefined behavior can be ignored" (which would mean what, making it defined?).

It says, "NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, ...".

It doesn't say that the behavior can be ignored. It says that the undefinedness can be ignored. The implementation doesn't have to take notice of the fact that the behavior is undefined.

Let's take a simpler example:

    printf("%d\n", INT_MAX + 1);
The behavior is undefined. The standard does not guarantee anything about it. A conforming implementation can reject it at compile time, or it can generate code that crashes, or it can generate code that emits an ADD instruction and print whatever the hardware returns, or it can play roge at compile time. (The traditional joke is that can make demons fly out of your nose. Of course it can't, but an implementation that did so would be physically impossible, not non-conforming.)

An implementation might define the behavior, but it's still "undefined behavior" as that term is defined by the ISO C standard.

Re: Undefined behavior in C is a reading error

#174

Earlier quoted context omitted.

> But I also think a lot of discussions of this topic caricaturize compiler writers to a ridiculous degree. The inflamed backlash should tell you just how damaging it is to impose silent failure on meticulously written, previously fine programs.

> meticulously written, previously fine programs With relatively few exceptions, if your program hits undefined behavior, then your program was already doing something pretty wrong to begin with. Signed overflow is a poignant example: in how many contexts is INT_MAX + 1 overflowing to INT_MIN actually sane semantics? Unless you're immediately attempting to check the result to see if it overflowed (which is extremely…

I recall a bug-report discussion that I sadly have never been able to find. It contains a pretty bad side-effect of this.

It had code like:

    int *p;
    // lots of code
    
    if (p != NULL)
        return 1;
    // use p
Then a later refactor wrongly added a single line before the if statement:

    int *p;
    // lots of code
    
    int a = *p;
    if (p != NULL)
        return 1;
    // use p
This meant the null check was optimized away, since de referencing a null pointer is undefined behavior, so the if-statement can be assumed to be always false. This then lead to actual errors (perhaps even an exploit, I do not recall) arising from the removed null-check.

I think in general the "sanity check" cases are the worst. It is hard to determine whether an expression causes undefined behavior if you cannot try and evaluate it. Perhaps a compiler intrinsic that checks (at runtime) whether an expression causes undefined behavior could be useful here. Though I can imagine such an intrinsic being essentially impossible to implement.

Re: Undefined behavior in C is a reading error

#176

Earlier quoted context omitted.

> all of the tricks which make "malloc" work, What are those, exactly? AFAIK, you can safely track memory addresses by storing them as intptr_t/uintptr_t.

The C standard says very little about how those types work. In particular, you can cast a pointer to one of them and then cast back to a pointer -- but only if you cast the exact same value back, and the intptr values are not guaranteed to be in any way meaningful. In particular, casting a pointer to intptr_t, doing arithmetic on it, and casting back is not guaranteed to do anything useful. It almost certainly will,…

Do you have an example of a situation in which you'd want to cast the result of arithmetic intptr_t values to a pointer? The situations I can think of off the top of my head would be better done as arithmetic between pointers.

Re: Undefined behavior in C is a reading error

#177
post #111

Earlier quoted context omitted.

> For example, if signed integer overflow yielded an unspecified result rather than causing undefined behavior, I wonder if any implementations would be adversely affected. I suspect so - makes it harder to reason about loop counts because the compiler can't necessarily guarantee that an incremented loop counter won't become negative and thus the loop needs to iterate more. E.g. something like for (int i=param; i Tha…

Assuming you defined signed integer overflow to follow two’s complement rules (the only reasonable interpretation other than UB), it would still be a guaranteed loop count of 16. (EDIT: i’m a dumbass, this is obvs not true. disregard this paragraph) There’s an interesting thing to note with that example though: even if you did make signed integer overflow defined, that code is still obviously incorrect if param + 16…

The real problem is in a better world 'int' would be replaced by types that actually exhibit the correct behavior.

for a loop counter you want an index type that will seg fault on overflow. If you think not having that check is worth it the programmer would need to tag it with unsafe.

It's also problematic because it's size is defined as at least 16 bits. But programmers which means you should never use it to store a constant larger than 16 bits. But people do that all the time.

Re: Undefined behavior in C is a reading error

#178

First: I do dislike how hard it is to avoid some UB / how impractical some of the rules are. But I also think a lot of discussions of this topic caricaturize compiler writers to a ridiculous degree. Almost describing them to write optimization passes looking for UB so they can over-optimize something, while cackling loudly in glee about all the programs they can break. The set of people doing so overlaps with the set…

The current situation is not good for compiler writers either. But nobody has ever shown that either C programmers want to sacrifice safety for "optimizations", or that these UB optimizations actually improve performance of anything.

To do UB "optimizations", the compiler first needs to figure out that there is an UB it can "optimize" anyway. At this point instead of "optimizing" it could, and in my humble opinion absolutely should, blow up the compilation by generating an UB error, so people can fix their stuff.

What about backwards compatibility in regards to a new compiler version deciding to issue errors on UB now? You don't have any guarantees about what happens with UB right now, so if you upgrade to a new version compiler that generates errors instead of "optimizations" everything would be still as before: no guarantees. And it's frankly a lot better to blow up the compilation with errors than to have the compiler accept the UB code and roll a dice on how the final binary will behave later. You can either fix the code to make it compile again, or use an older "known good" version of the compiler that you previously used as a stopgap measure.

I fail to see any reason whatsoever why compilers are still doing all kinds of stupid stuff with UB instead of doing the right thing and issuing errors when they encounter UB.

I also fail to see why the C language designers still insist on keeping so much of the legacy shit around.

Re: Undefined behavior in C is a reading error

#179

It truly boggles my mind that this discussion (linked in TFA) played out the way it did. In my mind it's 100% not okay to (by default) optimize away a check for integer overflow. I've never really written in C (or any unsafe language) before so I had little context for the types of traps C sets for developers. Based on the responses of the person who presumably implemented the optimization, it comes as no surprise th…

> In my mind it's 100% not okay to (by default) optimize away a check for integer overflow. But a + 100 > a is not a check for overflow. If a >= INT_MAX - 100, it is an overflow. The "will this operation overflow" check would be a >= INT_MAX - 100, and GCC would not optimize that away.

What you've written doesn't demonstrate the issue outlined in the big report. The issue is that real-world code defends against certain hostile inputs like so, and that the optimization breaks those defenses:

    int a,b,c;
    a=INT_MAX;     /\* statement ONE */
    b=a+2;         /* statement TWO */
    c=(b>a);       /* statement THREE \*/
Whether or not you think this is technically permissible by a sufficiently self-serving (from the perspective of a person whose ONLY goal is speed optimization) reading of spec is irrelevant. Any reading of the spec should err on the side of protection against real-world consequences.

I don't want lawyering and technically correct-ing, I want pragmatism.

Re: Undefined behavior in C is a reading error

#180
post #94

Earlier quoted context omitted.

for (int i=param; i does not have a guaranteed loop count with the current rules. The loop body will execute 16 times if param <= INT_MAX-16, but if the expression "param + 16" can overflow, the behavior is undefined. (I'm assuming param is of type int.)

That's precisely my point? Because the overflow case is undefined, the compiler can assume it doesn't happen and optimize based on the fixed loop count.

The overflow case is not UB. param can be unsigned, of fwrapv may be declared. Or the compiler chooses to declare fwrapv by default. In no case is the compiler allowed to declare the overflow away, unless it knows from before that param can not overflow. The optimization on loop count 16 can still happen with a runtime guard.
Post reply on HN