Live data from Hacker News

“gcc will quietly break nearly half of all the packages that it compiles”

metzdowd.com

101–110 of 110 posts

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#101

Earlier quoted context omitted.

I think you misunderstood the example -- the memory is cleared after use to ensure that if it's reallocated by someone else, or someone hooks up a debugger, the content can't be examined (except when the compiler removes this clearing attempt because of an optimization). Lets say that chunk of memory held a password -- you'd definitely want to clear it after use, even if you immediately free it and never plan to read…

That's actually a very good example, but I'd argue that this is actually a violation of the standard: memset is defined as setting the value in memory. Most optimizations on undefined behavior don't really fall into this category. I guess you could group this kind of thing into the category of "dead code elimination" which is useful, but results in parts of the code written not producing the specified executable. I h…

It is allowed under the "as if" rule. If no visible aspects of the program are changed by an optimization, then it is allowed. The value stored in memory is not considered to be a visible aspect, and so the compiler is allowed to modify which memory is changed.

It's the same as inlining a function. The standard says that a function call is a function call. Compilers are still allowed to inline the call, even if it has not been specifically marked as "inline".

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#102
post #99
post #89

Earlier quoted context omitted.

> I doubt rust could be ported to a 8bit PIC microcontroller, or to a 6502 keeping reasonable performance characteristics I don't believe this is correct. Most of why Rust avoids UB is that it uses static types much more effectively than C does. Static types are an abstraction between the programmer and the compiler for conveying intent, that cease to exist at runtime. So the runtime processor architecture should be…

> I don't believe this is correct. Did rust find a way to defeat the halting problem and push all the array bound checks to compile time? How well does rust deal with memory bank switching where an instruction here makes that pointer there refer to a different area of memory?

You don't need to solve the halting problem to push bounds checks to compile time, you need dependent types.

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#103
post #30

Earlier quoted context omitted.

If bar() returned the mutated state, he could just do: foo(baz(bar()))

I think a better design would be to separate mutators from pure functions. If a procedure mutates state, it should have a void return type, and if a procedure returns a value it should be a pure function that doesn't mutate state. This is, of course, a rule of thumb, not a hard law. Some exceptions: 1. I think it's okay (and in fact, idiomatic in C) to mutate state and return some sort of information about what occur…

I'm a fan of fluent design myself, and dislike flags.

    return foo().baz().bar();
With flags you start with success/fail, and end up with HRESULT.

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#104

Earlier quoted context omitted.

I believe the complaint is actually that the compiler makes secure code insecure by removing checks that rely on undefined behavior (which presumably can't be made any other way).

That complaint isn't a valid complaint. If the checks relied on undefined behavior, the code wasn't secure. If you want to rely on the behavior of a specific version of a specific compiler, then you need to define that in your dependencies instead of pretending that you've written general-purpose C code. This isn't even just a GCC problem; compiling the code on a different compiler breaks this too.

It's not GCC's job to break my program just in case I might one day run it on a broken compiler. That's like the fire marshal burning down my house to demonstrate how it violates fire codes.

There's also nothing wrong with writing a C program that rests on a base of POSIX, or the GNU system, and requires stronger guarantees than C alone provides.

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#105
post #100

Earlier quoted context omitted.

> I'm not arguing that GCC should violate the ANSI standard; rather it should provide additional guarantees above the what ANSI requires ... The problem with that approach is that it introduces dependency on the compiler. The original code was ANSI C and thus should compile fine on all compilers compatible with ANSI C, the new code is not as each compiler will decide to handle undefined behavior differently. Either y…

> The original code was ANSI C and thus should compile fine on all compilers compatible with ANSI C, the new code is not as each compiler will decide to handle undefined behavior differently. Except 40% of the original code already wasn't ANSI C. > Either you'll make the exact compiler a hard dependency (i.e. it always has to be compiled with gcc and fails to build with everything else), or it will produce "correct"…

> Except 40% of the original code already wasn't ANSI C.

Then why complain that ANSI C compiler gets confused by it?

> Having code that was broken under GCC not be broken under GCC absolutely is an improvement, particularly since in fact this kind of code often works on every other extant compiler.

No, the code does not work on every other compiler. And if it is, there's no guarantee it will stay like that.

> Standards tend to codify existing practice. There's no reason the standard couldn't be made stricter - but the way we get to there from here is if the major compilers implement stricter restrictions and can show that they can be implemented consistently and users find them useful. GCC has been willing to do that kind of innovation for other parts of the standard.

AFAIK some of the limitations are there because of non-traditional platforms - some of them may be a thing of the past so removing them would be OK, but some are not (and thus won't be removed from the standard). And one of the points of ANSI C (and POSIX) is to define global guarantees, not per-platform ones.

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#106
post #99
post #89

Earlier quoted context omitted.

> I doubt rust could be ported to a 8bit PIC microcontroller, or to a 6502 keeping reasonable performance characteristics I don't believe this is correct. Most of why Rust avoids UB is that it uses static types much more effectively than C does. Static types are an abstraction between the programmer and the compiler for conveying intent, that cease to exist at runtime. So the runtime processor architecture should be…

> I don't believe this is correct. Did rust find a way to defeat the halting problem and push all the array bound checks to compile time? How well does rust deal with memory bank switching where an instruction here makes that pointer there refer to a different area of memory?

> Did rust find a way to defeat the halting problem

I don't understand why the halting problem is relevant to this conversation. Just about all practical programs don't care about the halting problem in a final sense, anyway; see e.g. the calculus of inductive constructions for a Turing-incomplete language that lets you implement just about everything you actually care about implementing.

The halting problem merely prevents a program from evaluating a nontrivial property of another program with perfect accuracy. It does not prevent a program from evaluating a nontrivial property (bounds checks, type safety, whatever) with possible outputs "Yes" or "Either no, or you're trying to trick me, so cut that out and express what you mean more straightforwardly kthx."

This realization is at the heart of all modern language design.

> How well does rust deal with memory bank switching where an instruction here makes that pointer there refer to a different area of memory?

This problem boils down to shared mutable state, so the conceptual model of Rust deals with it very well. The current Rust language spec does not actually have a useful model of this sort of memory, but it would be a straightforward fork. As I said in my comment above, if there was interest and a use case for a safe language for these processors, it could be done easily.

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#107
post #103

Earlier quoted context omitted.

I think a better design would be to separate mutators from pure functions. If a procedure mutates state, it should have a void return type, and if a procedure returns a value it should be a pure function that doesn't mutate state. This is, of course, a rule of thumb, not a hard law. Some exceptions: 1. I think it's okay (and in fact, idiomatic in C) to mutate state and return some sort of information about what occur…

I'm a fan of fluent design myself, and dislike flags. return foo().baz().bar(); With flags you start with success/fail, and end up with HRESULT.

I'd agree with you in some languages, but in C this would be prohibitively difficult. In general, good fluent design uses immutable objects, which makes it basically just a syntactic sugar for functional programming. While fluent syntax is nice, the functional semantics are the real value, and are much easier to do in C (although, as soon as you add in memory management, functional programming often becomes prohibitively difficult too).

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#108
post #100

Earlier quoted context omitted.

> The original code was ANSI C and thus should compile fine on all compilers compatible with ANSI C, the new code is not as each compiler will decide to handle undefined behavior differently. Except 40% of the original code already wasn't ANSI C. > Either you'll make the exact compiler a hard dependency (i.e. it always has to be compiled with gcc and fails to build with everything else), or it will produce "correct"…

> Except 40% of the original code already wasn't ANSI C. Then why complain that ANSI C compiler gets confused by it? > Having code that was broken under GCC not be broken under GCC absolutely is an improvement, particularly since in fact this kind of code often works on every other extant compiler. No, the code does not work on every other compiler. And if it is, there's no guarantee it will stay like that. > Standar…

> Then why complain that ANSI C compiler gets confused by it?

Because I didn't ask for an ANSI C-and-not-a-penny-more compiler. Nobody wants that. Back in the day the GNU project made a point of going against standards when the standardized behaviour was user-unfriendly (POSIX_ME_HARDER etc.)

> No, the code does not work on every other compiler.

In many of these cases it does work on all other major compilers, or all other relevant platforms for that particular codebase.

> And if it is, there's no guarantee it will stay like that.

So what? That doesn't make it better to break it now.

> one of the points of ANSI C (and POSIX) is to define global guarantees, not per-platform ones.

Which is why it's GCC's (or any other compiler's) responsibility to define the per-platform guarantees.

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#109
post #67

Earlier quoted context omitted.

You don't need 4 decades of programming language research to specify that e.g. signed integer overflow either returns an implementation-defined value or the program aborting. There are languages older than C that allowed the useful forms of bit-twiddling but offered much stronger safety guarantees.

But you pay a performance cost for either of those decisions. Consider code like this: for (int i=0; i Most processors have special support for looping a fixed number of times, e.g. "decrement then branch if zero." If overflow is UB, the compiler can use this support. But if overflow returns an implementation defined value, then it is possible that N is INT_MAX and the loop will not terminate. In this case the compil…

A correctly predicted branch is almost free, the compiler could check for that case.

The real problem of course is that C requires the programmer to obscure their intent by messing with a counter variable. Is there really no "loop exactly N times" construct in the language?

Re: “gcc will quietly break nearly half of all the packages that it compiles”

#110
post #23

Earlier quoted context omitted.

IMO, it's a little subtler than that. It's not the compiler's fault, it's the language's . Plenty of languages have no undefined behavior that can be written by accident. Go and safe Rust, for instance, have just about no undefined behavior at all, and are both performance-competitive with C. (Go has UB if you cause race conditions, and Rust has UB within `unsafe` blocks analogous to C's UB.) A C compiler, meanwhile,…

This makes it sound like Go and Rust have some secret sauce that enables C's performance without UB. But it is not so. For example, consider an expression like (x*2)/2. clang and gcc will both optimize this to just x, but Go and Rust will actually perform the multiplication and division, as required by their overflow semantics. So the performance vs safety tradeoff is real.

I finally understood your point and where I was unclear, mostly after reading the other comment about the for loop.

You're right that C requires UB to optimize things like (x2)/2. My argument is that Go and Rust's secret sauce is you wouldn't have to write things spiritually similar to that in the first place. (x2)/2 is a bad example, since nobody would write that on purpose, but loops are a great one. C's only interface to elements of an array is a pointer. So C has to say that accessing out-of-bounds pointers are UB, as is even having a pointer that's neither in-bounds or right at the end, because it simply has no way to distinguish "pointer" from "pointer that is in-bounds for this array". The type of an array iterator in Rust (and I think also Go) carries knowledge of what array it's iterating over, and how far it can iterate; since it's part of the type system, the compiler has access to that knowledge. So you can just say `for element in array`, and the compiler knows that you're only accessing in-bounds pointers, and generate the same code C would, without needing to define a concept of UB.

Of course if you do generate pointers in unsafe code, you are subject to UB, same as in C. Rust and Go simply give you a language where most of the time, you don't need to reach for constructs that require UB to be performant.

(There is, however, an actual bit of secret sauce, at least in Rust but I suspect Go has an analogue: Rust's ownership system allows it to do far stronger alias analysis than even C's -fstrict-aliasing, without the risk of false positives -- you cannot construct overlapping mutable pointers in safe code.)

Post reply on HN