The question:
I have some code executing which is very unsafe. My question is, does this code trigger undefined behavior or is it 'correct' (= behavior fully defined).
Some context:
I have a program which has shown strange bugs/crashes in the past, which repeatedly also vanish again without reason. I have identified some pieces of (pointer) logic as a potential source of these bugs. Still, the bugs can also be caused by other part of that code. The bugs typically happen in Release builds, and not in Debug builds in case that matters.
I have taken these lines of (pointer) logic code and created a clear minimum-working example. This minimum-working example runs without problems which I know is no conclusive proof that no undefined behavior is happening. Therefore, I ask if someone sees if some 'C++ rules' are broken (= results in undefined behavior).
I build and develop this in Visual Studio 2019 and/or 2022.
I know this code contains very bad patterns. However, this is a minimal working example and in the actual code, many things are out of my control so I cannot simply change much of it. For now, I just want to learn if certain pointer operations result in defined or undefined behavior.
In case one or more operation do trigger undefined behavior, I would be grateful if someone can point out which operations they are and why they trigger undefined behavior.
The code:
Main program, minimum_working_example.cpp:
#include <iostream>
#include <sstream>
#include "CppObject.h"
int main()
{
/* Create a shared pointer on the heap.
* The lifetime of this shared_ptr is managed by some managed C# code which wraps this C++ code.
* That is why the shared_ptr is created on the heap, so C# can manage the lifetime of the pointer and therefore the C++ object.
*/
std::shared_ptr<void>* regularPointerToSmartPointer = new std::shared_ptr<void>(); // Create a shared pointer on the heap, and store a regular pointer pointing to it.
// Create an CppObject and store it in the shared_ptr<void> (note that this should add a reference to the correct type deleter into the shared_ptr).
int someValue = 5; // Some value stored in the CppObject, just for illustration.
*regularPointerToSmartPointer = std::make_shared<CppObjectStuff::CppObject>(someValue); // Create an object on the heap, create a smart pointer pointing to it, and store this smart pointer on the heap at the memory reserved in the previous step.
// Just get the raw pointer to object.
void* regularPointerToObject = (*regularPointerToSmartPointer).get();
// Convert pointer to string.
std::stringstream ss1;
ss1 << std::hex << regularPointerToObject; // Pointer to hex string.
std::string regularPointerToObjectString = ss1.str();
/* Many other operations can happen here
* e.g. like passing this string to all sorts of objects which could
desire to use the instance of CppObject.
*/
// Convert string to back pointer.
std::stringstream ss2;
ss2 << regularPointerToObjectString;
intptr_t raw_ptr = 0;
ss2 >> std::hex >> raw_ptr; //Hex string to int.
void* regularPointerToObjectFromString = reinterpret_cast<void*>(raw_ptr);
// Convert the void pointer to a CppObject pointer.
CppObjectStuff::CppObject* pointerToCppObject = static_cast<CppObjectStuff::CppObject*>(regularPointerToObjectFromString);
// Do something with the CppObject behind the pointer.
int result = pointerToCppObject->GiveIntValue();
// Print result.
std::cout << "This code runs fine:" << "\n";
std::cout << result;
// Delete the shared_ptr which in turn deletes the CppObject (something the managed C# code would normally do).
delete regularPointerToSmartPointer;
}
For reference, CppObject.h:
namespace CppObjectStuff
{
class CppObject
{
public:
CppObject(int value) { _intValue = value; };
int GiveIntValue() { return _intValue; };
private:
int _intValue;
};
}
One item that could cause discussion:
I already researched specifically the following tricky case which I concluded should be allowed, see e.g. this bog post. Note, this is similar but not fully identical to the usage in my code above so there could still be stuff I misunderstand:
// Start scope
{
// Create object in shared_ptr
std::shared_ptr<void> regularPointerToSmartPointer = std::make_shared<CppObjectStuff::CppObject>(someValue);
}
// Scope ended, shared_ptr went out of scope and should delete the `ppObjectStuff::CppObject` created before.