/* to reproduce: cl /Zi -O2 /Bv atomicptr.cpp Microsoft (R) C/C++ Optimizing Compiler Version 19.31.31105 for ARM64 Copyright (C) Microsoft Corporation. All rights reserved. Compiler Passes: c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\cl.exe: Version 19.31.31105.0 c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\c1.dll: Version 19.31.31105.0 c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\c1xx.dll: Version 19.31.31105.0 c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\c2.dll: Version 19.31.31105.0 c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\c1xx.dll: Version 19.31.31105.0 c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\link.exe: Version 14.31.31105.0 c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\mspdb140.dll: Version 14.31.31105.0 c:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX64\ARM64\1033\clui.dll: Version 19.31.31105.0 atomicptr.cpp Microsoft (R) Incremental Linker Version 14.31.31105.0 Copyright (C) Microsoft Corporation. All rights reserved. /out:atomicptr.exe /debug atomicptr.obj if compiling against Qt (for their QAtomicPointer impl) you will need something like: cl /Zi -O2 -FS /DNDEBUG /MD /W3 /EHsc -Zc:__cplusplus -permissive- -utf-8 -std:c++20 /Ie:\qsc\6.2.4_dolphin.arm64.build\qtbase\include /Ie:\qsc\6.2.4_dolphin.arm64.build\qtbase\include\QtCore\6.2.4 /Ie:\qsc\6.2.4_dolphin.arm64.build\qtbase\include\QtCore /nologo atomicptr.cpp */ #include #include #include #include // for the HeapAlloc test //#include // replacement for QAtomicPointer template struct AtomicPointerTest { using PtrType = T*; using AtomicType = std::atomic; AtomicType _q_value; // default value has no effect on the bug constexpr AtomicPointerTest(T* value /*= nullptr*/) noexcept : _q_value(value) { // storing to the std::atomic after the init list eliminates the bug //_q_value = value; } // i think the question is if this operator= implementation is allowed, or // triggering some bad behavior somehow inline AtomicPointerTest& operator=( const AtomicPointerTest& other) noexcept { this->storeRelease(other.loadAcquire()); return *this; } inline PtrType loadAcquire() const noexcept { return _q_value.load(std::memory_order_acquire); } inline void storeRelease(const PtrType newValue) noexcept { _q_value.store(newValue, std::memory_order_release); } }; /* #include "QtCore/qpointer.h" template using AtomicPtr = QAtomicPointer; //*/ //* template using AtomicPtr = AtomicPointerTest; //*/ class QObjectPrivate { public: struct Connection {}; struct ConnectionList { // Something extremely weird here: no values assigned to first/last will // appear in the output, if the bug occurs ConnectionList() : first(nullptr), last((Connection*)0xdeadbeefull) { //__debugbreak(); first = (Connection*)0xdeadbeefull; last = (Connection*)0xdeadbeefull; } AtomicPtr first; AtomicPtr last; }; struct SignalVector { ConnectionList signals_[1]; }; struct ConnectionData { SignalVector* alloc(int size) { auto buf_len = size * sizeof(ConnectionList); //* directly casting result of malloc() results in the buggy code return reinterpret_cast(malloc(buf_len)); //*/ /* bug does NOT occur if result is put in variable before cast auto ptr = malloc(buf_len); return reinterpret_cast(ptr); //*/ /* bug does NOT occur with HeapAlloc (doesn't matter if // HEAP_ZERO_MEMORY is passed or not) return reinterpret_cast(HeapAlloc(GetProcessHeap(), 0, buf_len)); //*/ /* bug does NOT occur with operator new return reinterpret_cast(operator new(buf_len)); //*/ } SignalVector* resizeSignalVector(int size) { SignalVector* newVector = alloc(size); // check for alloc failure isn't required for the bug, but shows that it // doesn't fix it, either if (!newVector) { return nullptr; } for (int i = 0; i < size; ++i) { newVector->signals_[i] = ConnectionList(); } // returning the allocated memory isn't required, but just shows the // bug is still present whether the result is used or not return newVector; } }; }; int main(int argc, char* argv[]) { QObjectPrivate::ConnectionData cd{}; auto v = cd.resizeSignalVector(0x10); printf("%p %p\n", v, v->signals_[0].first.loadAcquire()); return 0; }