Live data from Hacker News

The Problem with Friendly C

blog.regehr.org

51–60 of 174 posts

Re: The Problem with Friendly C

#51
post #12

I think the most important point of Friendly C is not to define the behaviour for what would otherwise be undefined, but to define a behaviour; from this point of view, it would be unnecessary to argue over the examples he mentions like memcpy() vs memmove() and integer shifting --- it only suffices that every implementation define the behaviour, and what that behaviour precisely is can differ between them. A lot dif…

You have to look deeper for the OOB array access question. For example, imagine I have code like this: if (a > 1) b++; array[b] = 0; c = a + 10; Under your suggested semantics, can the compiler use the value loaded from `a` at the point of the if() statement to calculate `a + 10`? Or does it have to emit a reload of `a` after the array access, in case `array[b]` was an OOB access that overwrote `a`?

I'm not seeing the deeper question, whereas what 'userbinator' said still makes sense to me.

The compiler's implementation defined behavior would simply be "if you tell me to write a value to an address, I'll create assembly that tries to write that value to that address". That's all. If this address is outside the allocated range of the array, there is no guarantee that it's safe that write this value, or that it won't break something else.

So in your example, no, the compiler is not required to reload 'a'. The compiler is allowed to presume that the array[x] syntax will have no affect on the value of a local variable, whether that variable is stored in a register or on the stack. The compiler is not guaranteeing safety, just best effort.

Re: The Problem with Friendly C

#52
post #48
post #45

Earlier quoted context omitted.

You'd think that'd make things clear, then: if you want a compiler to error out on trying to compile a literal 0/0, then you'd probably also want the same behavior of trying to compile a literal dereference of 0, or a literal (INT_MAX * 2), or whatever else.

You missed the point of the analogy: It's not about how compilers treat the expression 0/0, but how mathematicians do. If that expression turns up in your calculation, you did something you were not supposed to do and have no one else to blame.

No, rebutting that was exactly my point: programming is, in some ways, a friendlier version of working on mathematical proofs, in that the compiler will tell you when you "did something you were not supposed to do." Programmers expect to be able to hack away like monkeys on typewriters, and just run into a virtual wall whenever they misstep. Compilers will error out when a programmer explicitly types "0/0", and that's a good thing. It'd be a good thing in the other cases, too.

Re: The Problem with Friendly C

#53
I think this is a fundamental problem with C. Both of the language itself, but also of the C programming culture of insisting on very low levels of abstractions.

Bugs and undefined behaviour is too easy for programmers to write when the level of abstraction is low. Too much boilerplate to get wrong. But too low a level is also bad for an optimizer. It cant assume it understand the programmers intention with the code.

C++ tries to fix some of this by creating a higher level language and library on top of C. Low level code is considered unsafe, when higher level abstractions can be used as replacements.

Some examples of replacing low level with high level. Raw (owning) pointers and manual memory management are replaced with RAII value semantics and occasionally smart pointers. Raw loops are replaced by container iteration and better yet by STL algorithms. Casts are replaced by templates. Threads and mutexes are replaced by tasks and async, etc.

Eventually the idea is to subset C++ so that we can rip the C out of C++.

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...

Re: The Problem with Friendly C

#54
post #33
post #6

Earlier quoted context omitted.

The, to me, obvious thing to do with code with undefined behaviour is to emit an error and refuse to compile it. Programmers should never rely on undefined behaviour. Then again, I think languages shouldn't have undefined behaviour and that programmers who use languages with undefined behaviour deserve what they get.

The problem, as Regehr (the author of this submission) has pointed out many times before, is more subtle than that. Because certain behavior is considered undefined, compilers are allowed to assume the code it is compiling is well defined, and optimize accordingly. That can cause simple bugs to have mysterious effects. For example: a->thing = 42; if (a == NULL) { return; } Obviously that code is wrong; I should check…

But the compiler could just as well emit a warning "useless NULL comparison" instead of blithely assuming that the programmer intentionally wrote a book expression. That wouldn't handle every case of UB, but would handle many.

Re: The Problem with Friendly C

#55
post #53

I think this is a fundamental problem with C. Both of the language itself, but also of the C programming culture of insisting on very low levels of abstractions. Bugs and undefined behaviour is too easy for programmers to write when the level of abstraction is low. Too much boilerplate to get wrong. But too low a level is also bad for an optimizer. It cant assume it understand the programmers intention with the code.…

I don't think C is bad for optimizers. What language consistently generates faster machine code than C?

Re: The Problem with Friendly C

#56

Earlier quoted context omitted.

I think the problem is that some of these optimizations aren't just compiler makers being greedy, they're actually a huge benefit. IMO what's missing is the ability to mark areas "unsafe" -- IE, tell the compiler "it's ok to take advantage of certain optimizations here" while marking other areas "please don't goof with this" (ie, security critical code). You can kind of do this with pragmas, but not really. Here's a…

For example, knowing that INT_MAX+1 is undefined allows optimizing "X+1 > X" to "true". If a programmer writes "X+1 > X", chances are this is an overflow check. Doing this is perfectly defined by the standard if X is an unsigned integer, but not if it's signed. That's what doesn't make sense, since they could've made the unsigned case undefined as well. which allows a broad range of loop optimizations to kick in What…

> I don't believe C should be a language where the compiler does all sorts of high-level optimisation; it should be a straightforward "do what I say" type of language where you get almost exactly what you write, and the only optimisations should be at the level of things like instruction selection --- the optimisations that a programmer would not be able to do at the source level.

In that case, you're asking for easily 3x-5x less performance in the software you use every day, if not more. You can get essentially this by turning on -O0 and compiling, say, Firefox. This is done in debugging, and it's a terrible browsing experience.

Compiler optimizations are really, really important. CSE, GVN, LICM, SROA, etc. aren't done for fun. They're done because modern software depends on them to be fast.

Writing low-level "post-optimized" code is at odds with good engineering practice. If you can't factor things out into functions and count on an inliner to inline them and then propagate away constants, SROA away intermediate structures, etc. then you're essentially telling programmers they can't factor code out into functions. If your compiler refuses to do the Hacker's Delight integer divide optimization (which, by the way, you need good constprop to make good use of a lot of the time), then you're telling programmers they have to get out their copy from their bookshelf and compute the magic number every time they want to integer divide fast (especially on ARM). This sort of thing puts a huge drain on developer productivity and maintainability.

Re: The Problem with Friendly C

#57
post #4

It's easier to expand than contract. When the same decision is faced multiple times, it will be made multiple ways, and it's very difficult to remove one of those options once it's in use. On the other hand, if you remove the decision by standardizing on one of the options, you can always allow the other option later. Working in Perl, I run into this a lot. When 'there is more than one way to do it' is a driving prin…

Working in say Java doesn't really force consistency, it just pushes the inconsistency up a level. Programmers still get painfully "clever" and idiosyncratic.

Re: The Problem with Friendly C

#58
post #29

Earlier quoted context omitted.

How so? I don't see the connection to undeciability.

If you have undefined behaviour after an infinite loop that breaks on some condition, then that undefined behaviour will not necessarily have been triggered. Thus, no warning.

Regehr himself "disproved" Fermat's Last Theorem by abusing a non-terminating loop and over-aggressive compilers.

http://blog.regehr.org/archives/140

Re: The Problem with Friendly C

#59
post #45
post #25

Earlier quoted context omitted.

the Standard imposes no requirements on undefined behaviour. The list you quoted is informative, to give us a basic idea what to possibly expect, not a list of things we may rely upon. Rejecting translation outright aside, undefined behaviour is the strongest language the standard uses for illegal constructs. I do not see it as undefined in the sense of lacking a common definition (eg is 0 a natural number, or not) b…

You'd think that'd make things clear, then: if you want a compiler to error out on trying to compile a literal 0/0, then you'd probably also want the same behavior of trying to compile a literal dereference of 0, or a literal (INT_MAX * 2), or whatever else.

clang does warn about both of those:

    $ cat foo.c

    #include 
    int main() {
      int x = INT_MAX+1;
      *(int *)0;
    }

    $ clang foo.c
    foo.c:3:17: warning: overflow in expression; result is -2147483648 with type 'int' [-Winteger-overflow]
            int x = INT_MAX+1;
                           ^
    foo.c:4:2: warning: indirection of non-volatile null pointer will be deleted, not trap [-Wnull-dereference]
            *(int *)0;
            ^~~~~~~~~
    foo.c:4:2: note: consider using __builtin_trap() or qualifying pointer with 'volatile'
    3 warnings generated.

Re: The Problem with Friendly C

#60
post #3

If ((uint32_t)x But why can't it just produce something different on each system? Allow me to call it as I see it: the modern interpretation of undefined behaviour is bullshit. What compilers do today should be the recourse of absolute last resort, and the sort of thing that makes its authors feel bad. But it seems to be treated as a matter of course. I don't know what to say. Mandatory reading: http://robertoconcert…

> But why can't it just produce something different on each system? It could. That is indeed how Rust, for example, defines it. But in C "unspecified values" have a way of turning into undefined behavior really quickly. Here's an actual bug we have in Rust [1]: let index = 1.04E+17 as u8; let array = vec![1, 2, 3, 4, 5]; println!("{}", array[index as usize]); // segfault Why does that segfault instead of emitting a s…

For anyone wondering: double -> uintx_t is indeed undefined in C and C++ for values outside of (0, UINTx_MAX), which is kind of a gotcha since, double -> long -> uint8_t is fine (assuming all integer parts of double fit in a long).
Post reply on HN