4 ms·
The reason why safety in C++ is difficult to achieve is due to the memory model used by C and C++. The memory model is a flat space provided by the OS that can
by coliveira 3y ago
The reason why safety in C++ is difficult to achieve is due to the memory model used by C and C++. The memory model is a flat space provided by the OS that can be addressed by pointers. In this sense, C++ is similar to assembly code. A language like Java, on the other hand, assumes a different model where you can only access objects with well defined behavior. To change this, one needs to disallow the use of native pointers in C++ or make them less powerful, like Java did.
- adamnemecek 3y agoYou just need an unsafe keyword.
- ajross 3y agoThat's pretty much what the article says though. "Don't use traditional pointers" is a fairly trivial rule to enforce via static analysis, and constructs like unique_ptr are syntactically identical anyway. 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.
- gpderetta 3y agoThe significant difference is a static guarantee of no reuse after move, hence the 'affine' qualifier (which is not new).
- bluGill 3y agoborrowing affine style is more than RAII. borrowing affine style means that there are no pointers, and always one owner. in borrowing affine style your functions take a unique_ptr for everything, if the lifetime of the data needs to live beyond the function, then the function returns a unique_ptr of that data back. 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.
- verdagon 3y agoI'm not sure this has much to do with RAII. RAII does indeed require affine types, but RAII doesn't provide memory safety. The article describes a method of memory safety. (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.)
- chlorion 3y agoAffine in this case is referring to not being able to use values after they have been moved. When I say not able to use, I mean that it's a compile time error to attempt to use them. C++ does not restrict you from using things after they have been moved and therefore does not have affine typing!
- deleted 3y ago[deleted]
- josefx 3y ago> The memory model is a flat space provided by the OS that can be addressed by pointers 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.
- LoganDark 3y ago> Pointers cease to be valid the moment you try to leave a single allocation. 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 https://www.ralfj.de/blog/2018/07/24/pointers-and-bytes.html
- shadowgovt 3y agoIt is, for all practical purposes, a flat space in the sense that for bare pointers, operator++ is defined (increments to the next whatever, defined based on type of pointer). 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)
- antonvs 3y ago> everything further out is playing with fire. That's the point. C and C++ don't prevent you from playing with that for. Memory-safe language do.
- SubjectToChange 3y ago
- kimixa 3y agoOne issue is that the memory model isn't just a flat space that can be addresses by any pointer value - it may look similar to one if your compiler and OS let you, but doing things like accessing memory allocated as a different type or outside (an array of) objects is invalid, and the compiler is perfectly allowed by the standard to assume that never happens and happily "optimize" everything that may be a result of that away. 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.
- tsimionescu 3y agoIt depends what you mean exactly. The C and C++ official memory model is very much not a flat space, but exactly what you describe for Java - you can only (validly) access objects. For example, the operation x < y is only defined if x and y are both pointers into the same object or array of objects (or one past the end of an array of objects). Otherwise, the operation is entirely undefined in both the C and the C++ memory models. The following program has no defined C or C++ semantics, and neither the C nor the C++ standards can tell you anything about what it could do: 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.
- shadowgovt 3y agoI really feel like it's a hell of a definitions dodge to say "This is what the model is" when no compiler implements constraints to require the user to treat the model like that (i.e. I can always just increment the pointer, or typecast it to numeric type, do math on it, and typecast back to a pointer, without having to pull any big red levers like using "unsafe" methods). If it's undefined but it compiles to something, is it really undefined, or is the definition merely not standardized?
- deleted 3y ago[deleted]
- epcoa 3y agoYes it’s really undefined. There is a distinction from “implementation defined behavior” which you seem to be confusing it with. You are practically wrong in your assumptions. Since undefined behavior is undefined the compiler is free to do anything with compilation, it may compile to something but you have no guarantee what that something is. And in real life this often actually bites you when the optimizer comes into play - modern optimizing compilers can and do optimize undefined behavior into noops or other weird stuff. Read this and don’t come back on this topic until you clearly understand it: https://en.cppreference.com/w/cpp/language/ub https://en.cppreference.com/w/cpp/language/ub