Live data from Hacker News

Modern C (2019)

gustedt.gitlabpages.inria.fr

51–57 of 57 posts

Re: Modern C (2019)

#51
post #27

Earlier quoted context omitted.

"I'm okay with undefined behavior, as long as its behavior is well-defined." You keep using that word. I don't think it means what you think it means. :)

A behavior like "corrupts whatever is in memory at that location" when your code screws up a pointer is not exactly "well-defined", but it does disallow "pretends it could have corrupted RAM and does whatever it feels like instead". To put that another way, you can treat certain behaviors as if they're opaque functions until after you're done optimizing. Similar to how you might handle volatile. Some of these changes…

Your platform almost certainly does do something sane and reasonable for UB-exhibiting code.

Where your problem lies is what happens when you optimize that code. And guaranteeing optimizations do something superficially sane on unsound code while still performing reasonable and obvious optimizations for well-formed code is insanely hard.

The fundamental problem is that the design of C guarantees that lots and lots and lots of syntactically valid and intuitively written code is semantically total nonsense. Part of this is that C is low-level and part is just that it was designed 50 years ago before anyone had the faintest idea what kinds of terrible mistakes were being made.

If there were simple fixes here it wouldn’t still be a problem.

Re: Modern C (2019)

#52
post #51

Earlier quoted context omitted.

A behavior like "corrupts whatever is in memory at that location" when your code screws up a pointer is not exactly "well-defined", but it does disallow "pretends it could have corrupted RAM and does whatever it feels like instead". To put that another way, you can treat certain behaviors as if they're opaque functions until after you're done optimizing. Similar to how you might handle volatile. Some of these changes…

Your platform almost certainly does do something sane and reasonable for UB-exhibiting code. Where your problem lies is what happens when you optimize that code. And guaranteeing optimizations do something superficially sane on unsound code while still performing reasonable and obvious optimizations for well-formed code is insanely hard. The fundamental problem is that the design of C guarantees that lots and lots an…

> Where your problem lies is what happens when you optimize that code. And guaranteeing optimizations do something superficially sane on unsound code while still performing reasonable and obvious optimizations for well-formed code is insanely hard.

It really depends on the type of optimization.

A lot of the scariest results come when the compiler treats code as dead, and in very many cases you could go ahead and emit the code anyway and performance would be fine.

> If there were simple fixes here it wouldn’t still be a problem.

Nobody is trying to reduce the scope of UB, though. Even things that are very easy to define or implementation-define like syntax errors or left shifting with certain parameters are still sitting on the list.

Re: Modern C (2019)

#53
post #4

Earlier quoted context omitted.

C biggest "mistake" (there are historical reasons for this, though, but it doesn't make it acceptable nowadays) is the concept of undefined behavior, period.

I'm okay with undefined behavior, but the language should revert back to the original (an exhaustive list of permissible behavior by the host when the C abstract machine's behavior is undefined) rather than the current mere list of examples of possible behavior by the host.

So, which behaviours should be allowed for this code?

    void (*f)() = (void (*)())rand();
    f();

Re: Modern C (2019)

#54

Earlier quoted context omitted.

I don't entirely agree with this article ... it's quite possible to "pass" an array as a pointer to an array type, as opposed to a decayed pointer, e.g. void f( int (*a) [5] ); int a[5] = {1, 2, 3, 4, 5}; f( &a ); The larger problem here is more that this mechanism doesn't support arbitrary sizes. Which probably makes sense from how c handles memory, but it's very restrictive. Incidentally, I recently had an argument…

You can’t pass an array by value and you can’t pass a pointer with a corresponding length of non-fixed size (you can actually with VLAs, but frankly the syntax sucks and not all compilers support it). This missing language feature has led to horrible things like nil-terminated strings , a million bespoke ways to encode the length of the array, or just functions that pray you have enough space like `strcpy` and `gets`…

> You can't pass an array by value

Sure, but, this is the case in all languages, no? Otherwise, what should be the convention? A deep copy? A first-degree shallow copy? You simply can't avoid the referential nature of arrays for use as arguments to functions. So the issue here shouldn't be the referential nature of this, but the fact that they chose to implement it as a "decaying" pointer as opposed to a "pointer to array type of a certain size".

And hence why I'm pointing out there is, in fact, a (relatively simple) way to do this, but only for fixed sizes. But the reason it's not THAT useful in practice is because, in general, you probably wouldn't want the function's parameter to expect a fixed size array, but you'd probably want the flexibility of a variable-sized array, whose length is defined at runtime. And this is what is not supported by c.

So in some sense, in the absence of support for variable-sized array parameters, then not expecting fixed array sizes at the point of defining function parameters, and using a decaying pointer instead (which simply doesn't care about the size), is probably the lesser evil of the two. So for me the 'bigger' evil isn't decaying pointers, but the lack of support for VLAs as function parameters. This would have allowed you to use the above syntax to pass a pointer to an array type, retaining full information about the pointed array, including its size.

(in other words I think we're saying a very similar thing but with different words)

Re: Modern C (2019)

#55

Earlier quoted context omitted.

I'm okay with undefined behavior, but the language should revert back to the original (an exhaustive list of permissible behavior by the host when the C abstract machine's behavior is undefined) rather than the current mere list of examples of possible behavior by the host.

So, which behaviours should be allowed for this code? void (*f)() = (void (*)())rand(); f();

The three enumerated options in the ANSI C standard and no others, especially not invisibly modifying non-dead code during translation.

The compiler could behave in a documented manner with or without a warning; it could error out and stop translation; or it could do what you fracking told it to, consequences be damned.

Re: Modern C (2019)

#56

Earlier quoted context omitted.

So, which behaviours should be allowed for this code? void (*f)() = (void (*)())rand(); f();

The three enumerated options in the ANSI C standard and no others, especially not invisibly modifying non-dead code during translation. The compiler could behave in a documented manner with or without a warning; it could error out and stop translation; or it could do what you fracking told it to, consequences be damned.

So it should never do function inlining, or replace multiplication by two with a bit shift? Because both of them quite fit "invisibly modifying non-dead code during translation".

> or it could do what you fracking told it to, consequences be damned

Yes, that's precisely what happens. The point is agreeing on what you "told it". The agreement stipulated in the standard is that in some cases (when you hit UB) you're effectively not agreeing on anything, i.e., the compiler can do what it want.

If that's not what you like, please let me know what should be mandated in the case of the fragment I wrote above, and how a compiler could offer that guarantee in an efficient and effective manner.

Re: Modern C (2019)

#57

Earlier quoted context omitted.

The three enumerated options in the ANSI C standard and no others, especially not invisibly modifying non-dead code during translation. The compiler could behave in a documented manner with or without a warning; it could error out and stop translation; or it could do what you fracking told it to, consequences be damned.

So it should never do function inlining, or replace multiplication by two with a bit shift? Because both of them quite fit "invisibly modifying non-dead code during translation". > or it could do what you fracking told it to, consequences be damned Yes, that's precisely what happens. The point is agreeing on what you "told it". The agreement stipulated in the standard is that in some cases (when you hit UB) you're ef…

As to your first point: fair. It shouldn't modify it in a semantically material way.

> that's precisely what happens

No, it really isn't. Numerous compilers do static analysis to identify code that could be UB and then mangle it to mean something entirely different or eliminate it entirely. And if the compiler really did already do this, you wouldn't need to resort to...

> the compiler can do what it want[s]

Under current standards, yes, but that's a circular and nonsensical argument. You can't base your argument on what the standard permits when the very content of what the standard should be is the topic of discussion.

> please let me know what should be mandated

I already responded. The ANSI C standard is clear, and the modification to that language in subsequent ISO standards is, in my opinion, ill-advised.

Post reply on HN