Live data from Hacker News

Undefined behavior in C is a reading error

yodaiken.com

491–500 of 503 posts

Re: Undefined behavior in C is a reading error

#491

Earlier quoted context omitted.

The thing is that it's necessary, and (x86, not sure about arm) asm tells you via the d register if not the overflow flag. So why doesn't the C standard arguably provide a misuse-resistant way of doing so? Instead every project having to reinvent it and get it wrong if they do the "obvious" (but wrong) thing.

What would the misuse-resistant way be? Would you want the multiplication operator (*) to return 2 values: the number and a bool telling you whether it overflowed? Yeah, a standard library function that does this would be good. But many people would just use * instead of this function, and so the problem would partially remain.

mul_would_overflow(), mul_wrap(), mul_saturate(), mul_do_whatever_the_hardware_does_just_please_dont_break_my_code_by_assuming_ub. Can't help people who just use * and don't care about overflow without breaking compatibility, but at least the people who do care won't have to reinvent safe arithmetic.

Re: Undefined behavior in C is a reading error

#492

Earlier quoted context omitted.

> 1. write your own compiler that does it right (ETOOHARD now that clang didn't) The problem here is that "right" is not quite that black-and-white. To a lot of users, "right" is "my code appears to work," optionally with "at a given performance level." A new compiler that's slower, or changes semantics compared to some baseline, won't necessarily be seen as "better" unless there's some clear benefit, and "doesn't pe…

This is only a matter of opinion because this mistake was made at all in the spec. If it hadn't been you'd not be saying any of the above. Most UB in C has reasonable implementation. E.g., `sizeof(enum_type)` should be 4 if its value range permits it, else 8. E.g., 1-bit int bitfields should behave like 1-bit unsigned int bitfields. I already covered bit shifting, signed integer overflows, and NULL dereferences, whic…

> If it hadn't been you'd not be saying any of the above.

Well, yes, that's kind of the point. If GCC adopts that interpretation and gets a speed boost out of it, then there's pressure on Clang to do the same, unless they can convince developers that not making such aggressive optimizations is worth it. "Your code may be slower so this (arguably) edge case behaves better, but you get better error messages/compile times" is not going to be as easy to sell as "Your code will behave the same and perform as well as when compiled with GCC, and you get better error messages/compile times to boot".

> "Clang produces slow code" might be due to it not deleting "unpossible" code, but screw that, you can always go delete it yourself if it was dead.

The entire point of the dead code elimination pass is to do that for you. If anything, that's why the optimizer exists; so you don't have to perform optimizations manually.

Re: Undefined behavior in C is a reading error

#493

Earlier quoted context omitted.

Your examples are far too simplistic. Real world code is going to be far more complex and will tend to resist trivial analysis. For example, please explain how to prevent this without also (inadvertently) preventing the removal of unnecessary null checks when functions are inlined. What about an unnecessary null check that's hidden inside a macro? What about whole program LTO? A macro could be used in a variety of si…

> For example, please explain how to prevent this without also (inadvertently) preventing the removal of unnecessary null checks when functions are inlined. What about an unnecessary null check that's hidden inside a macro? If a null check is unnecessary, that's because it is reachable only from the not-null side of some previous null check. The compiler can track that information just fine, using the same tools it u…

This is still too simplistic. Think along the lines of:

    void foo(T *p) {
      if (!p) abort();
      bar(p);
    }

    void bar(T *p) {
      use(*p);
      MACRO(p);
    }
In other words, the first null-check and the latter check are in different functions that may not even be in the same compilation unit, or where the call sequence is hard to reason about due to function pointers etc.

Re: Undefined behavior in C is a reading error

#494
There is such flags as -fwrapv which I use in programs that I think may need it.

Maybe, there should be another flag which defines or partially defines many more things. For example, shift amounts out of range might produce any numerical answer (without other side effects), or it might trap once that operation is reached and not give any answer at all, but not demons in your nose or anything like that; either way, the compiler might or might not display a warning message. This is similar to what is mentioned in the linked document.

Another alternative flag might be used to guarantee terminating with an error message in such a case, making the program execute more slowly (because the compiler will insert such a check), for testing purposes perhaps. The document linked mentions such a case, but I think that it should not be the default case.

If you have:

  int x[3];
  int*y=x+1;
  int*z=x-1;
Then, what I think is how it should work, should be:

- The program itself is valid.

- The pointers y and z are unequal; if you write y!=z then it is guaranteed to be true.

- Reading or writing through z should be considered as undefined behaviour and not allowed. The compiler can optimize based on the assumption that it doesn't happen, unless that optimization is disabled, or if the program is changed to specify it as volatile (in which case it remains undefined behaviour, but the compiler can no longer assume this when optimizing).

- Since z is out of range, comparing if it is less or greater than y is not predictable.

- Since z is out of range, there is no guarantee that it is unequal to any other pointer that isn't based on x, including null; it might or might not be null.

- If you write z+=2 then z is now in range and z==y is true and you can read/write through z now.

If you write:

  static int f(void) {
    int a;
    return a;
  }

  int main(int argc,char**argv) {
    int x=42;
    x=f();
    printf("%d\n",x);
    printf("%d\n",x);
    printf("%d\n",x);
    return 0;
  }
Then it should be: The return value of f() will be some value for each call (not necessarily the same every time), and can optimize it. Since the function does nothing, and the return value is not defined, it is allowed to optimize out the reassignment to x and always print 42, or it can set x to some other value, which is unpredictable and not necessarily the same every time, but not print three different numbers, trap, or do other stuff. If standard library optimizations are enabled (which they might be by default, depending on the compiler), it may optimize out the entire program and replace it with a constant string and printing it, as long as it is a number and line feed, repeated three times with the same number, which must be in range of the int type. It may also compile the program like it is and when the program is executed multiple times, print different numbers different times, but always three same numbers. Printing nothing is not allowed, since the %d format specifier can never result in nothing; it always results in at least one digit.

Re: Undefined behavior in C is a reading error

#495

Earlier quoted context omitted.

It just isn't that simple in practice. Modern compilers automatically apply a plethora of different rules in sequence when transforming code. This results in a long chain of transformations where each operation is perfectly reasonable when considered on its own. Unfortunately, for certain inputs, the sum total occasionally appears demonic. The current state of the art is such that there is no way to reliably prevent…

> where each operation is perfectly reasonable when considered on its own. No, it is not. For example, the transformation: int read_and_discard = *p; // vvvv int read_and_discard = *p; __unsafe_assume_always(p != 0); is not reasonable, since p is not, in fact, always nonnull.

I disagree. The transformation should be valid; if p is null then the result is undefined and may crash, including the rest of the program after that occurs. However, I do think that it should not make such a transformation for a volatile read/write; in that case it should not make such an assumption (who knows if it is meaningful on the target computer or some sort of emulator or operating system or debugger or whatever).

Re: Undefined behavior in C is a reading error

#496

I think the problem is cultural in the C community. C programmers have Stockholm Syndrome around UB optimizations. As TFA notes, "There is No Reliable Way to Determine if a Large Codebase Contains Undefined Behavior" https://blog.llvm.org/2011/05/what-every-c-programmer-should... That's because UB is a bug that occurs as your program runs (e.g. dereferencing a null pointer). You'd have to prove that your program is f…

Good point. C programmers have come to assume that C is a shitty language with terrible semantics and it's their fault for not using Rust or something. They blame the language for the mess made by standards/compilers.

I don't really like Rust; I like C. But, it could be improved, making one with less undefined behaviour and better macros and less confusing syntax for types etc. Many of the new programming languages that try to avoid the bad thing from C I think are avoiding most of the good thing from C, too.

Re: Undefined behavior in C is a reading error

#497

Earlier quoted context omitted.

For reference, [0] appears to be the referenced paper. The relevant passage: > To understand how disabling these optimizations may impact performance, we ran SPECint 2006 with GCC and Clang, respectively, and measured the slowdown when compiling the programs with all the three -fno-* [-fno-strict-overflow, -fno-delete-null-pointer-checks, and -fno-strict-aliasing] options shown in Figure 9. The experiments were condu…

> If we change the type of k to size_t, then we no longer observe any slowdown with the workaround options Basically in that case the benefits of the optimization disappears once you fix the code.

I mean, that's kind of a tautological statement; if you change the code to either eliminate the need for the optimization or manually implement it, of course the benefits of the optimization disappear. That would apply to most, if not all, optimizations.

Re: Undefined behavior in C is a reading error

#498

Earlier quoted context omitted.

> Perhaps I should have phrased it as "all implementation-defined behaviour is whatever the hardware happens to do when executing whatever code the compiler happens to generate". Even with this definition, the important part is that compilers would no longer be able to ignore control flow paths that invoke undefined behavior. Signed integer overflow/null pointer dereference/etc. may be documented to produce arbitrary…

Err, that's not a definition, that's a example of pathologically useless 'documentation' that a perverse implementation might provide if it were allowed to 'define' implementation-defined behaviour by deferring to the hardware. Deferring to the hardware is what undefined behaviour is, the point of implementation-defined behaviour is to be less vague than that. > may be documented to produce arbitrary results , and th…

> Deferring to the hardware is what undefined behaviour is

If that were the case, the Standard would say so. The entire reason people argue over this in the first place is because the Standard's definition of undefined behavior allows for multiple interpretations.

In any case, you're still missing the point. It doesn't matter how good or bad the documentation of implementation-defined behavior may or may not be; the important part is that compilers cannot optimize under the assumption that control flow paths containing implementation-defined behavior are never reached. Null-pointer checks, overflow checks, etc. would remain in place.

> Yes, exactly; that is what undefined behaviour is. That is what "the standard imposes no requirements" means.

I think you're mixing standardese-undefined-behavior with colloquial-undefined-behavior here. For example, if reading an uninitialized variable were implementation-defined behavior, and an implementation said the result of reading an uninitialized variable was "whatever the hardware returns", you're going to get some arbitrary value/number, but your program is still going to be well-defined in the eyes of the Standard.

Re: Undefined behavior in C is a reading error

#499
post #257

Earlier quoted context omitted.

It’s not useless. The assumption is not false if the program doesn’t have undefined behavior. The assumption allows the code to be a few times faster. To disallow this assumption would inhibit these optimizations.

a) the assumption is not false if it is not false! b) the speedup is not shown anywhere

The speedup is more or less the difference between O1 and O3 optimization levels.

Re: Undefined behavior in C is a reading error

#500

Earlier quoted context omitted.

Err, that's not a definition, that's a example of pathologically useless 'documentation' that a perverse implementation might provide if it were allowed to 'define' implementation-defined behaviour by deferring to the hardware. Deferring to the hardware is what undefined behaviour is, the point of implementation-defined behaviour is to be less vague than that. > may be documented to produce arbitrary results , and th…

> Deferring to the hardware is what undefined behaviour is If that were the case, the Standard would say so. The entire reason people argue over this in the first place is because the Standard's definition of undefined behavior allows for multiple interpretations. In any case, you're still missing the point. It doesn't matter how good or bad the documentation of implementation-defined behavior may or may not be; the…

> the important part is that compilers cannot optimize under the assumption that control flow paths containing [undefined] behavior are never reached.

Yes. That. Exactly that. Compilers cannot assume that, because (in the general case) it is not true.

Post reply on HN