From what I understand this is not true. Pointers cease to be valid the moment you try to leave a single allocation. You get to play around within a single continuous allocation and one past the end, everything further out is playing with fire.
Even comparing the "addresses" of two separate allocations is undefined if done with "<" . The comparison function std::less is basically magic to get well defined behavior out of a language that doesn't guarantee it.
> C++ is similar to assembly code
Only if you use a compiler that does not optimize anything.
For the other readers who might not know what this is referring to, it's pointer provenance. For an introduction to the topic, I always recommend Ralf Jung's blog series, "Pointers Are Complicated":
https://www.ralfj.de/blog/2018/07/24/pointers-and-bytes.html
There is no operator++ equivalent in Java to apply to object references (unless you go unsafe); you can't immediately shoot yourself in the foot without the compiler noticing by asking for "the next object after this one" when no such thing exists.
(handwave a bit: of course, you can ask for an object past the last object in any container. That's (a) not the same thing and (b) results in an immediate runtime error in Java, instead of undefined behavior)
That's the point. C and C++ don't prevent you from playing with that for. Memory-safe language do.
[0] https://github.com/dotnet/aspnetcore/tree/1a56bdb671700ae698...
[1] https://github.com/dotnet/aspnetcore/blob/1a56bdb671700ae698...
int x = 0;
int y = 0;
if(&x < &y) {
printf("???");
}
Now of course the implementation of C and C++ actually assumes without checking that you only access objects and not raw memory, and thus will happily read raw memory directly.If it's undefined but it compiles to something, is it really undefined, or is the definition merely not standardized?
Read this and don’t come back on this topic until you clearly understand it: https://en.cppreference.com/w/cpp/language/ub
This is why I actually hate using this programming language, because when you hit undefined behavior (which the language makes trivial to do; incrementing a pointer past the allocated memory is a one-line operation that throws no errors) the end-result is usually subtle, wrong, and hard to find later if it isn't actually "close enough to right" because the compiler desperately tries to make a useful program because that's what compilers are for. Hell, if it formatted my hard drive and blew up my monitor, it'd be much easier to figure out where the problem was! Hand-waving this flaw in the design of the programming tool with "oh, it's undefined behavior; you should never have relied on that in the first place" when so many valid statements in the language compile to undefined behavior, as if that is good enough, is building a house on sand.
... and quite frankly, our industry is full of sand houses and we could stand to respond to the amount of undefined behavior in C++ by ceasing to build on that shaky foundation.
You are arguing against topics I never brought up. (I am an EE fwiw). I have no misconceptions where undefined behavior comes from and have demonstrated none thus far.
> This is why I actually hate using this programming language, because when you hit undefined behavior (which the language makes trivial to do)
Ok so you want to editorialize on something else entirely.
No idea how that dismisses anything I said or referred to.
Regardless of how trivial it is in practice to invoke undefined behavior it doesn’t change the real differences between undefined and implementation defined behavior.
(I know the c++ spec defines these terms differently; I'm not talking about the spec definitions and I never was. I'm saying the spec definitions are a dodge around what actually happens when code is compiled and executed).
It seems to me that you’re claiming that the existence of undefined behavior is bad. Which isn’t actually that controversial outside of certain people weirdly infatuated with C/C++.
But it seems moreover you merely have a problem with it being called undefined behavior or something, as if the word itself isn’t harsh enough.
I don’t see it. I don’t see the problem with the definitions as stated. It doesn’t weaken any commentary about undefined behavior to me at least.
And again regardless of your hatred for C++ weenies it doesn’t change the fact that there are meaningful practical differences between undefined and implementation defined behavior, the distinction has to exist regardless of what you call them.
> Every undefined behavior is de facto implementation-defined because something happens resulting from the state of the machine and the code being executed.
Regardless of spec this makes no sense to me. Implementation defined implies something is still “defined”, like not in the spec but somewhere. Undefined means what it says - it’s undefined.
I don’t even disagree with your other points but I don’t get how complaining about the practical difficulties of avoiding undefined behavior have to do with a “definitions dodge”
This is incorrect. In one very popular web server, some behavior depends on the values set for some response headers, and the value is checked in part by calling a function like strstr which takes two pointers and two lengths and searches one string for the other string. If you pass {null, 0} as the haystack and the implementation of the strstr-like function starts by computing the upper bound of the haystack (null + 0) then the compiler can legally produce *any value* for that expression. Then your program will quite predictably segfault. When it does your recourse is to fix your program, not to fix the compiler, because the compiler is working correctly.
Why does the distinction matter? Because I don't believe any program beyond minimal complexity written in C++ is actually free of undefined behavior. Therefore, being able to ask questions like " What configuration of compiler built this?" is meaningful for debugging code.
Because undefined behavior is so easily reached in the language's specification, the abstraction is broken from the start and one must know implementation details of the compilers used to understand how the code behaves.
I don't know about C.
A lot of bugs have been caused by programmers assuming any access to the 'linear address space' is fine, but that has never been reliable as it's not allowed by the standard. The worse thing is when it looks like it works for a while, but you're relying on stuff not allowed by the standard so may change at any time (like a compiler version or option change, or even a change to a different part of the code that happens to tickle the compiler's analysis stages a slightly different way). See the "Time traveling NULL-check removal" - as the compiler "knows" that no pointer can ever have the value of NULL during deference, any path that does that can be completely removed - even if there's something like a NULL check and a logging output before said deference, if compiler decides that deference will eventually happen in that path unconditionally, that path and logging before the deference Can Never Happen so can be removed.
Or type punning and pointer aliasing - objects are created with a type, and so the compiler Knows if you convert a pointer type to another type that isn't compatible with the first type, they somehow magically point to different memory, and all the assumptions that implies for the following code.
A lot of these restrictions are pretty similar to things like Java have - the difference is that the JVM checks and flags violations and/or straight up disallows them when compiling - not just allowing the compiler to (silently) optimize based on those assumptions, and throwing the result at hardware to see what happens.
There may be a few platform/compiler-specific behavior used to implement super low-level stuff like OSs, but that's platform-specific stuff outside the C++ (or C) spec itself.
The bit that has me confused is that it's inventing a new term, "borrowing affine style", to describe a longstanding paradigm that has traditionally been called "RAII". Now, neither term is very clear, but surely it's better to use the existing confusing jargon instead of inventing new terms.
std::unique_ptr<foo> var;
// init and use var
var = SomeFunction(std::move(var));
// use var again.
Note that while in SomeFunction you lose access to var, but since SomeFunction returns it again you don't really lose anything. Of course Somefunction can also return some other unique_ptr<foo> that isn't var and you can't control that.It is an interesting idea, though I'm not sure if I like it for real world code or not.
(Also, I used the term "borrowless affine style" mostly because people might hear the term "affine style" and assume I'm talking about Rust, since that's what most people know.)
C++ does not restrict you from using things after they have been moved and therefore does not have affine typing!