diff --git a/src/lib/allocator-handle.hpp b/src/lib/allocator-handle.hpp index b93adb1c0..5526baa1c 100644 --- a/src/lib/allocator-handle.hpp +++ b/src/lib/allocator-handle.hpp @@ -57,6 +57,7 @@ #include "lib/error.hpp" +#include #include #include @@ -81,7 +82,8 @@ namespace lib { { struct Allocation { - char buf_[sizeof(TY)]; + alignas(TY) + std::byte buf_[sizeof(TY)]; template TY& @@ -93,7 +95,7 @@ namespace lib { TY& access() { - return reinterpret_cast (buf_); + return * std::launder (reinterpret_cast (&buf_)); } void discard() /// @warning strong assumption made here: Payload was created diff --git a/src/lib/diff/tree-diff-application.hpp b/src/lib/diff/tree-diff-application.hpp index 3dd0124e2..7c8f84e68 100644 --- a/src/lib/diff/tree-diff-application.hpp +++ b/src/lib/diff/tree-diff-application.hpp @@ -209,9 +209,6 @@ namespace diff{ { scopes_.emplace(); TreeMutator::Handle placementHandle (scopes_.top()); - - static_assert (buffSiz >= sizeof(typename MutatorStack::value_type) - ,"insufficient working buffer for TreeMutator"); return placementHandle; } diff --git a/src/lib/format-string.cpp b/src/lib/format-string.cpp index cf86e2595..25ec9abed 100644 --- a/src/lib/format-string.cpp +++ b/src/lib/format-string.cpp @@ -54,6 +54,8 @@ #include #include #include +#include +#include @@ -67,14 +69,14 @@ namespace util { namespace { // implementation details... inline boost::format& - accessImpl (char* buffer) + accessImpl (std::byte* buffer) { - return reinterpret_cast (*buffer); + return * std::launder (reinterpret_cast (buffer)); } inline void - destroyImpl (char* buffer) + destroyImpl (std::byte* buffer) { accessImpl(buffer).~format(); } @@ -83,7 +85,7 @@ namespace util { /** in case the formatting of a (primitive) value fails, * we try to supply an error indicator instead */ void - pushFailsafeReplacement (char* formatter, const char* errorMsg =NULL) + pushFailsafeReplacement (std::byte* formatter, const char* errorMsg =NULL) try { string placeholder("; using Iter = typename Group::iterator; struct Buffer + : lib::UninitialisedStorage { - char storage[sizeof(Group)]; - - Group& group() { return reinterpret_cast (storage); } - - Iter begin() { return group().begin();} - Iter end() { return group().end(); } + Group& group(){ return *this; } + Iter begin() { return group().begin();} + Iter end() { return group().end(); } }; Buffer buff_; uint pos_{0}; diff --git a/src/lib/meta/typelist-util.hpp b/src/lib/meta/typelist-util.hpp index 01f710569..490a1126a 100644 --- a/src/lib/meta/typelist-util.hpp +++ b/src/lib/meta/typelist-util.hpp @@ -90,6 +90,25 @@ namespace meta { }; + /** + * Metafunction " max( alignof(T) ) for T in TYPES " + */ + template + struct maxAlign; + template<> + struct maxAlign + { + static constexpr int value = 0; + }; + template + struct maxAlign> + { + static constexpr size_t thisval = alignof(TY); + static constexpr size_t nextval = maxAlign::value; + static constexpr size_t value = nextval > thisval? nextval:thisval; + }; + + /** * Metafunction to check if a specific type is contained * in a given typelist. Only exact match is detected. diff --git a/src/lib/opaque-holder.hpp b/src/lib/opaque-holder.hpp index 6fea33594..a04eea942 100644 --- a/src/lib/opaque-holder.hpp +++ b/src/lib/opaque-holder.hpp @@ -86,8 +86,9 @@ #include -#include +#include #include +#include namespace lib { @@ -213,7 +214,8 @@ namespace lib { /** Inner capsule managing the contained object (interface) */ struct Buffer { - char content_[siz]; + alignas(size_t) std::byte content_[siz]; + void* ptr() { return &content_; } virtual ~Buffer() {} ///< this is an ABC with VTable @@ -255,7 +257,7 @@ namespace lib { SUB& get() const ///< core operation: target is contained within the inline buffer { - return *reinterpret_cast (unConst(this)->ptr()); + return * std::launder (reinterpret_cast (unConst(this)->ptr())); } ~Buff() @@ -328,12 +330,12 @@ namespace lib { Buffer& buff() { - return *reinterpret_cast (&storage_); + return * std::launder (reinterpret_cast (&storage_)); } const Buffer& buff() const { - return *reinterpret_cast (&storage_); + return * std::launder (reinterpret_cast (&storage_)); } @@ -614,13 +616,14 @@ namespace lib { : util::NonCopyable { - mutable char buf_[siz]; + alignas(BA) mutable + std::byte buf_[siz]; BA& getObj() const { - return reinterpret_cast (buf_); + return * std::launder (reinterpret_cast (&buf_)); } void diff --git a/src/lib/polymorphic-value.hpp b/src/lib/polymorphic-value.hpp index b4626cf65..370b8f036 100644 --- a/src/lib/polymorphic-value.hpp +++ b/src/lib/polymorphic-value.hpp @@ -166,6 +166,9 @@ #include "lib/meta/duck-detector.hpp" #include "lib/util.hpp" +#include +#include + namespace lib { @@ -383,12 +386,13 @@ namespace lib { /* === embedded object in buffer === */ /** Storage for embedded objects */ - mutable char buf_[siz]; + alignas(IFA) mutable + std::byte buf_[siz]; IFA& accessEmbedded() const { - return reinterpret_cast (buf_); + return * std::launder (reinterpret_cast (&buf_)); } void diff --git a/src/lib/replaceable-item.hpp b/src/lib/replaceable-item.hpp index 3ec157212..4729cc285 100644 --- a/src/lib/replaceable-item.hpp +++ b/src/lib/replaceable-item.hpp @@ -65,6 +65,7 @@ #include #include +#include @@ -86,7 +87,8 @@ namespace wrapper { template class ReplaceableItem { - char content_[sizeof(X)]; + alignas(X) + std::byte content_[sizeof(X)]; public: ReplaceableItem() @@ -131,7 +133,7 @@ namespace wrapper { X& access() { - return reinterpret_cast(content_); + return * std::launder (reinterpret_cast (&content_)); } void diff --git a/src/lib/scoped-collection.hpp b/src/lib/scoped-collection.hpp index a6e04858a..007e3007f 100644 --- a/src/lib/scoped-collection.hpp +++ b/src/lib/scoped-collection.hpp @@ -75,6 +75,7 @@ #include "lib/meta/trait.hpp" #include "lib/iter-adapter.hpp" +#include #include @@ -113,15 +114,15 @@ namespace lib { class ElementHolder : util::NonCopyable { - - mutable char buf_[siz]; + alignas(I) mutable + std::byte buf_[siz]; public: I& accessObj() const { - return reinterpret_cast (buf_); + return * std::launder (reinterpret_cast (&buf_)); } void diff --git a/src/lib/uninitialised-storage.hpp b/src/lib/uninitialised-storage.hpp new file mode 100644 index 000000000..c4a3fbb51 --- /dev/null +++ b/src/lib/uninitialised-storage.hpp @@ -0,0 +1,130 @@ +/* + UNINITIALISED-STORAGE.hpp - array-like container with raw storage + + Copyright (C) Lumiera.org + 2023, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file uninitialised-storage.hpp + ** A raw memory block with proper alignment and array access. + ** This is a building block for custom containers and memory management schemes. + ** Regular containers in C++ always ensure invocation of any object's constructors + ** and destructors -- which is a boon and prevents a lot of consistency problems. + ** When constructing custom management schemes however, this automatic initialisation + ** can be problematic; some objects require constructor arguments, preventing mass + ** initialisation; and initialising a large memory block has considerable cost, + ** which may be wasted it the intended usage is to plant objects into that space + ** later, on demand. The std::vector::reserve(size) function can often be used + ** to circumvent those problems — yet unfortunately std::vector has the hard + ** requirement on the content objects to be at least _movable_ to support + ** automatic storage growth. + ** + ** A traditional workaround is to exploit a programming trick relying on a + ** _forced cast:_ Storage is declared as `char[]` without initialiser; since + ** `char` is a primitive type, C++ will not default-initialise the storage then + ** for sake of plain-old-C compatibility. A special accessor function will then + ** force-cast into the desired target type. However well established, this + ** practice is riddled with various problems + ** - it requires very precise setup and any mistake leads to memory corruption + ** - according to the C++ standard, such an access involves _undefined behaviour_ + ** - compilers are getting better and increasingly aggressive with performing global + ** optimisation and may miss the secondary hidden access under some circumstances. + ** - when objects with `const` data fields are placed into such a buffer, the compiler + ** may perform constant folding based on the current/initial object values and thus + ** fail to propagate changes due to other object instances being placed into storage. + ** + ** Attempts were made to codify this kind of usage properly into the standard; these + ** turned out to be problematic however and were [deprecated again]. Starting with + ** C++17, fortunately there is now a way to express this kind of raw unprotected + ** access through standard conformant ways and marked clearly to prevent overly + ** zealous optimisation. Since the typical use is for allocating a series of + ** storage slots with a well defined target type, this implementation + ** relies on std::array as »front-end« for access. + ** - target type `T` and size are given as template parameters + ** - the storage is defined as `std::byte` to express its very purpose + ** - the secondary access is marked by `std::launder` to prevent optimisation + ** - an implicit conversion path to the corresponding array type is provided + ** - subscript operators enable direct access to the raw storage + ** - helper functions allow for placement new and delete. + ** [deprecated again]: https://stackoverflow.com/a/71828512 + ** @see extent-family.hpp + ** @see vault::gear::TestChainLoad::Rule where this setup matters + */ + + +#ifndef LIB_UNINITIALISED_STORAGE_H +#define LIB_UNINITIALISED_STORAGE_H + + +#include +#include +#include +#include + + +namespace lib { + + /** + * Block of raw uninitialised storage with array like access. + * @tparam T the nominal type assumed to sit in each »slot« + * @tparam cnt number of »slots« in the array + */ + template + class UninitialisedStorage + { + using _Arr = std::array; + alignas(T) std::byte buffer_[sizeof(_Arr)]; + + public: + _Arr& + array() + { + return * std::launder (reinterpret_cast<_Arr* > (&buffer_)); + } + + _Arr const& + array() const + { + return * std::launder (reinterpret_cast<_Arr const*> (&buffer_)); + } + + + operator _Arr&() { return array(); } + operator _Arr const&() const { return array(); } + + T & operator[] (size_t idx) { return array()[idx]; } + T const& operator[] (size_t idx) const { return array()[idx]; } + + + template + T& + createAt (size_t idx, Args&& ...args) + { + return *new(&operator[](idx)) T{std::forward(args)...}; + } + + void + destroyAt (size_t idx) + { + operator[](idx).~T(); + } + }; + + +} // namespace lib +#endif /*LIB_UNINITIALISED_STORAGE_H*/ diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp index 9b01219cf..70f3fcba9 100644 --- a/src/lib/variant.hpp +++ b/src/lib/variant.hpp @@ -88,6 +88,7 @@ #include "lib/util.hpp" #include +#include #include #include @@ -222,10 +223,11 @@ namespace lib { template class Variant { - // WARNING: never add any member field before the storage_ array /////////////////////////TICKET #1204 public: - enum { SIZ = meta::maxSize::value }; + enum { SIZ = meta::maxSize::value + , ALIGN = meta::maxAlign::value + }; template using VisitorFunc = typename variant::VFunc::template VisitorInterface; @@ -264,7 +266,8 @@ namespace lib { struct Buffer : meta::VirtualCopySupportInterface { - char content_[SIZ]; + alignas(ALIGN) + std::byte content_[SIZ]; void* ptr() { return &content_; } @@ -287,7 +290,7 @@ namespace lib { TY& access() const ///< core operation: target is contained within the inline buffer { - return *reinterpret_cast (unConst(this)->ptr()); + return * std::launder (reinterpret_cast (unConst(this)->ptr())); } ~Buff() @@ -396,12 +399,12 @@ namespace lib { Buffer& buffer() { - return *reinterpret_cast (&storage_); + return * std::launder (reinterpret_cast (&storage_)); } Buffer const& buffer() const { - return *reinterpret_cast (&storage_); + return * std::launder (reinterpret_cast (&storage_)); } template diff --git a/src/lib/wrapper.hpp b/src/lib/wrapper.hpp index 37cea1cee..f036c31c9 100644 --- a/src/lib/wrapper.hpp +++ b/src/lib/wrapper.hpp @@ -49,6 +49,7 @@ #include "lib/util.hpp" #include +#include namespace lib { @@ -113,8 +114,9 @@ namespace wrapper { using TY_unconst = std::remove_const_t; - mutable - char content_[sizeof(TY)]; + alignas(TY) mutable + std::byte content_[sizeof(TY)]; + bool created_; template @@ -135,7 +137,7 @@ namespace wrapper { TY& access () const { - return reinterpret_cast (content_); + return * std::launder (reinterpret_cast (&content_)); } TY_unconst& diff --git a/src/steam/engine/tracking-heap-block-provider.hpp b/src/steam/engine/tracking-heap-block-provider.hpp index a1a7869aa..9d40fa822 100644 --- a/src/steam/engine/tracking-heap-block-provider.hpp +++ b/src/steam/engine/tracking-heap-block-provider.hpp @@ -175,7 +175,7 @@ namespace engine { throw error::Invalid ("Buffer with the given ID not yet emitted"); diagn::Block& memoryBlock = access_emitted (bufferID); - TY* converted = reinterpret_cast (memoryBlock.accessMemory()); + TY* converted = std::launder (reinterpret_cast (memoryBlock.accessMemory())); REQUIRE (converted); return *converted; diff --git a/src/vault/mem/extent-family.hpp b/src/vault/mem/extent-family.hpp index d47aa26ad..a445fd995 100644 --- a/src/vault/mem/extent-family.hpp +++ b/src/vault/mem/extent-family.hpp @@ -42,6 +42,7 @@ #include "vault/common.hpp" +#include "lib/uninitialised-storage.hpp" #include "lib/iter-adapter.hpp" #include "lib/nocopy.hpp" #include "lib/util.hpp" @@ -90,16 +91,18 @@ namespace mem { private: + using _UniqueStoragePtr = std::unique_ptr>; + /** Entry in the Extents management datastructure */ struct Storage - : std::unique_ptr + : _UniqueStoragePtr { /** * @note default ctor immediately allocates the full storage, - * but without initialisation since payload is `char` + * but without any initialisation of memory content */ Storage() - : unique_ptr{new char[sizeof(Extent)]} + : _UniqueStoragePtr{new lib::UninitialisedStorage} { } /** access projected Extent storage type @@ -108,8 +111,9 @@ namespace mem { Extent& access() { - ENSURE (get() != nullptr); - return reinterpret_cast (*get()); + auto* rawStorage = this->get(); + ENSURE (rawStorage != nullptr); + return static_cast (rawStorage->array()); } }; using Extents = std::vector; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index be1b2e027..b1b0fac2d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -110692,7 +110692,7 @@ class Something - + @@ -110750,6 +110750,37 @@ class Something + + + + + + + + +

+ Da alignof(char) ≡ 1, ist es gradezu eine »Steilvorlage« für Probleme, wenn man einen Allocation-Buffer per char[] deklariert. GCC macht das nicht (er fängt Allokationen immer an 64bit-Grenzen an), aber grundsätzlich dürfte ein Compiler ein char[] anfangen, wo er grad' lustig ist. Besonders gefährlich, wenn das Array in ein anderes Objekt eingebettet wird. Nur den zuletzt genannten Fall habe ich 2019 abgeklärt; gegen alle sonstigen Schnaps-Ideen gibt es keinen Schutz — es sei denn, man hält sich an die Sprachmittel +

+ + +
+
+ + + + + + + + + + + + + + + +