3 ms·
Except that because unsigned integers wrap the compiler needs to emit different instructions that can handle wrapping appropriately. This is a problem for vecto
by UncleMeat 3y ago
Except that because unsigned integers wrap the compiler needs to emit different instructions that can handle wrapping appropriately. This is a problem for vectorizing code and a problem for compiling to architectures where the hardware wrap happens at a different bit width than the data type size in software. So your code is often slower.
And a wrapped unsigned integer is probably still a bug. Yes, your compiler can't blow up a branch that you thought would be there, but "hey I added two numbers and got back a smaller number" is almost always not actually the function that you were trying to implement. The wrap can even happily lead to a security vulnerability if you are computing some sort of buffer offset and think that the guarantees of arithmetic will keep you within the buffer bounds and didn't stop to think about modular arithmetic. So your program is slower and still buggy.
- gavinhoward 3y agoBut it's buggy in a way that doesn't destroy the meaning of the whole program. The Sledgehammer Principle is not enough; UB is a nuclear holocaust for a program. So I'll take the buggy behavior that slows my code and only uses a sledgehammer over the buggy behavior that treats my program as a suggestion.
- UncleMeat 3y agoIt is true that the damage is more unbounded when you trigger undefined behavior. I mentioned this in my comment. It is real, but I believe that people largely overstate the degree with which these two things are different (nasal demons).
- gavinhoward 3y agoNot really. https://web.archive.org/web/20210508125532/https://twitter.com/m13253/status/1371615680068526081 https://web.archive.org/web/20210508125532/https://twitter.c... https://godbolt.org/z/1q1bjn https://godbolt.org/z/1q1bjn https://news.ycombinator.com/item?id=26498233 https://news.ycombinator.com/item?id=26498233 Nasal demons was an analogy, not something to take literally.
- UncleMeat 3y agoOh come on, this is exactly what I'm talking about. We've seen these before. The "format your hard drive" one is instead "completely fuck up your control flow and jump between functions" and it is purposefully constructed. People could have just focused on that - it was already intense enough. And you can happily get fucked up control flow with good old fashioned modular arithmetic errors. C and C++ are both nightmare languages and the connection between UB and the as-if rule are a mess. But incorrect programs can still blast off into incredible harm without the optimizer's help. And the emphasis on UB because it is so fun to construct weird scenarios has led to a world where people make strange and ill defined demands for how the compiler should react in these situations.
- gavinhoward 3y agoIt may be purposefully constructed for the example, but it is also easily constructed. This is only possible because the compiler deletes code. Yes, you can get messed up control flow from modular arithmetic code, but the compiler is not going to delete that code if it happens to overflow. And just because it's purposefully constructed doesn't mean it won't happen in real code, which is much more complicated and liable to triggering such problems.
- UncleMeat 3y agoThe compiler deletes code all the time in well formed programs. You typically want this. I do agree that the "but I put the check there" argument is real and that it absolutely breaks the principle of least surprise that you could look at your source and see a check that is important yet doesn't actually end up in the binary. But basically nobody has provided a rigorous conception of what a compiler shouldn't do w.r.t. undefined behavior. "Just don't delete my code" doesn't work since that's a big portion of what makes compiler optimization valuable. "Don't ever optimize based on UB assumptions" doesn't work, since that means that literally any function without a body available to the current translation unit can havoc all memory (or worse, even another function running concurrently).
- arp242 3y ago> "hey I added two numbers and got back a smaller number" is almost always not actually the function that you were trying to implement. As a bit of an aside, when is this actually useful? The only example I know of is bytebeats[1], and it's unlikely this is what Ritchie had in mind when he created C. Wikipedia says "for some applications, such as timers and clocks, wrapping on overflow can be desirable", but even there wrapping an int usually seems wrong (e.g. you want to wrap minutes on 60, hours on 24, etc – not 255 or 65k or whatever the size is). [1]: e.g. stuff like: main(t){for(;;t++) putchar(t*((t>>12|t>>8)&63&t>>4));} – pipe to aplay or /dev/dsp or whatever.
- gizmo686 3y agoIts useful for implementing arbitrary precision integers.
- m-ee 3y agoIn the embedded world I almost never care about real time in hours or minutes. I just want a timer to increment for use with input capture or determining the period of some function.
- nullc 3y agoArithmetic mod the word size is quite useful, but it's useful to specialized code-- hashes, checksums, periodic counters, fast division by constants, implementations of fast algebraic codes for things like combinatorial number (en/de)coding. For the counters, since you questioned it-- say you want your program to perform a calibration cycle every once in a while, the exact interval isn't critical. Just free run an unsigned iteration counter and then test that some masked version of it equals a constant. All that said, when you want wrapping behavior you know it. It's critical that it's available, first class supported, and fast-- but it wouldn't hurt if you had to ask for it. But the alternatives to wrapping aren't themselves magic bullets: Say you use saturating arithmetic, now lots of code that might have worked more or less fine with wrapping (or only been a little glitchy) goes into a hard lockup, esp since saturation breaks the property that var+1 != var, or fails in some more serious way, when e.g. because var+pos-pos != var. And the wrapping behavior at least has the property that its fast on extant hardware, while implementing saturating or trapping operations is inherently slower. Wrapping also manages to produce correct results by accident pretty often, like in my above point about var+pos-pos=var. Sometimes machine generated code does some nonsense that takes a variable out of range then pulls it back. Switching to saturating would just switch the cases that are magically correct, switching to trapping would turn any otherwise magically correct cases into failures. In some cases you might prefer a clean crash to potentially glitchy behavior, but in other cases there is no such thing as a "clean crash" and crashing on overflow is the worst failure possible (so whatever else might happen if it kept running could be no worse). There is, unfortunately, no replacement for thoroughly understanding the behavior of a piece of code nor any replacement for formal analysis. We shouldn't cry too much. Software is absurdly cheap to make compared to e.g. engineering and machining a part out of metal. But part of that ease is an illusion because it's only that easy if you don't care if the software is correct, and only need it to be somewhat correct or only correct some of the time. Few of the drivers for improved software quality are actually backed by research either. Would doing X or Y instead be better? We don't know. Alternatives start with at least a minor headwind that there is a lot of experience out there dealing with the way things have historically been done. Unfortunately researching the interaction between programming languages and programmers is itself really hard.