20 ms·
I would be (mostly) fine with C for the purposes it's designed for if each C compiler were required to have an option that makes the compiler use implementation
by rbehrends 9y ago
I would be (mostly) fine with C for the purposes it's designed for if each C compiler were required to have an option that makes the compiler use implementation-defined behavior everywhere. This would make C more like a portable assembler and less like the software engineering version of "do you feel lucky, punk?"
As it is, lots of major C applications already do this on a piecemeal basis and use one or more of:
-fno-strict-aliasing
-fno-strict-overflow
-fno-delete-null-pointer-checks
with gcc to keep a lid on the effects of undefined behavior (and often more or even stricter options, such as -fwrapv).
The problem is that even experienced C programmers can get tripped up by undefined behavior all the time. "Yes, it does exactly what you tell it do" doesn't really cut it when the vast majority of actual humans cannot safely predict the effects because they're so unintuitive. Or, in the words of Douglas Adams:
"But the plans were on display . . ."
"On display? I eventually had to go down to the cellar to
find them."
"That's the display department."
"With a torch."
"Ah, well the lights had probably gone."
"So had the stairs."
"But look, you found the notice, didn't you?"
"Yes," said Arthur, "yes I did. It was on display in the
bottom of a locked filing cabinet stuck in a disused
lavatory with a sign on the door saying 'Beware of the
Leopard'."
- colechristensen 9y agoHow possible do you think it is to create a new language which is almost, but not quite, exactly the same as C? Edge cases that have evolved into it do have plenty of problems, but it's very difficult to create a new backwards-incompatible standard which is just a little bit different. Then again, the embedded (microcontrollers) world seems to be filled with slightly esoteric implementations of C and everybody gets along just fine.
- rbehrends 9y agoAs I said, I think that basically doing a global search and replace of "undefined" with "implementation-defined" in the standard would be a good first attempt (this should be possible, because that's how C compilers worked for a long time and – with optimizations turned off – generally still do). This wouldn't solve everything, but it makes the use case for C as a "portable assembler" better, which could then be used as a foundation to build upon.
- InternetOfStuff 9y ago> Then again, the embedded (microcontrollers) world seems to be filled with slightly esoteric implementations of C and everybody gets along just fine. But embedded teams often severely limit the language constructs you may use, in order to avoid the foot howitzers and only have to worry about the foot handguns. There are also many other techniques (such as pre-allocating all variables during initialisation) which can be considered a mitigation techniques for the many traps C offers. I'm also skeptical of the quality of a lot of embedded software (and I've seen quite a bit of it). This is not to say that C is bad, or that I disagree with you. But embedded is a bad example.
- CJefferson 9y agoAlmost impossible to do efficiently. Let's consider a tiny example: void copy(int* in, int* out) { for(int i = 0; i < 10; ++i) { *out = *in; ++out; ++in; } } Now, in C it is undefined behaviour if either o in or out point into the stack where the variables i, in and out live. Maybe their values will change, maybe they won't. In practice, this code will be optimised to some code that just stores in and out in registers. Now let's imagine we want to make it defined. Then the statement '* out = * in' would have to become something like: 1) Write the current values of i, in and out to memory (in case * in overlaps with any of their locations. 2) Read * in into a register 3) Write *out from that register 4) Re-read i, in and out from memory, in case they just changed. Now, you could make that more efficient by adding to the start of the function a check which looked to see if either 'in' or 'out' pointed anywhere near the stack, but there is still going to be a significant cost. If you plan for this from the start with your language, you can drive the costs much lower. The main thing that separates C from assembler is that you do not have to worry about what values are in registers, and which are in memory. However, that means any code which writes to memory provides a place where this can make a difference.
- staticassertion 9y agoYou can express what you're talking about (non aliasing mutable pointers) for free, at compile time. In fact, you can't express this in C (or, restricted, whatever), so I do wonder if your assumption that because it's UB it'll compile to the optimal code is true.
- CJefferson 9y agoI think you are thinking about the memory that 'in' and 'out' point to aliasing each other. That's handled with the "restricted" keyword, as you say. I'm talking about something else, the issue of 'in' and 'out' pointing into the stack, so writing to them changing the value of the local variables. That is basically treated as UB by every C compiler, and no compiler I am aware of provides any switches which would make it easier to handle, it's just horrible and your code behaves in different ways depending on the compiler and optimisation level.
- 9y ago
- naasking 9y ago> How possible do you think it is to create a new language which is almost, but not quite, exactly the same as C? See Friendly C: https://blog.regehr.org/archives/1180 https://blog.regehr.org/archives/1180 https://blog.regehr.org/archives/1287 https://blog.regehr.org/archives/1287
- josephg 9y ago> with gcc to keep a lid on the effects of undefined behavior (and often more or even stricter options, such as -fwrapv). Speaking of which, a killer feature I'd like to see in a C compiler is a flag for debug mode which SIGABRTs whenever it enters an undefined behaviour codepath. For example, sometimes the compiler knows that if pointers alias, or an integer overflows or something, it can do whatever it wants. I want a flag which will automatically add assertions to the generated code, so at least in debug mode it'll crash if thats ever actually the case.
- rbehrends 9y agoThis cannot be done for all undefined behavior (because that would be too expensive, if at all possible), but you can use the `-fsanitize` family of options in both gcc and clang to at least get runtime errors for quite a few important cases of undefined behavior.
- deleted 9y ago[deleted]
- jchb 9y agoIt exists. Clang has UBSan which can detect many undefined behaviour (https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#available-checks https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#...). It's integrated into Apple's Xcode 9, which can pause into the debugger when encountering an issue. I was actually using it to debug an issue just as I read your comment.
- iainmerrick 9y agoIt exists, but it doesn't do anything in the example that was discussed recently, "undefined behavior may call a never-called function": https://news.ycombinator.com/item?id=15324414 https://news.ycombinator.com/item?id=15324414 As far as I can tell, -O0 is the only way to avoid that particular mis-optimization in Clang.