Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to use the Structure dereference(->) operator on the result of std::atomic::load

Whilst trying to work with std atomic pointer, I ran into the following. Say I do this:

std::atomic<std::string*> myString;
// <do fancy stuff with the string... also on other threads>

//A can I do this?
myString.load()->size()

//B can I do this?
char myFifthChar = *(myString.load()->c_str() + 5);

//C can I do this?
char myCharArray[255];
strcpy(myCharArray, myString.load()->c_str());

I'm pretty sure C is illegal because myString might be deleted in the meantime.

However I'm unsure about A and B. I suppose they are illegal since the pointer might be deferenced whilst performing the read operation.

However if this is the case, how can you ever read from an atomic pointer that might be deleted. Since the load is 1 step, and the reading of the data is 1 step.

like image 669
laurisvr Avatar asked May 08 '15 10:05

laurisvr


People also ask

What happens when you dereference a pointer C++?

Dereferencing is used to access or manipulate data contained in memory location pointed to by a pointer. *(asterisk) is used with pointer variable when dereferencing the pointer variable, it refers to variable being pointed, so this is called dereferencing of pointers.

What is the use of dereferencing operator?

In computer programming, a dereference operator, also known as an indirection operator, operates on a pointer variable. It returns the location value, or l-value in memory pointed to by the variable's value. In the C programming language, the deference operator is denoted with an asterisk (*).


1 Answers

// A can I do this?
myString.load()->size()

Yes you can, but you do have a race condition if something else might be mutating or destructing/deallocating the string to which the snapshot of myString you received points. In other words, the situation after atomically retrieving the pointer is the same as for any std::string object to which multiple threads might have pointers, except that...

There is the question of whether the atomic load guarantees some particular construction/change to the string - perhaps performed by whichever thread updated myString to point to the particular string instance you've loaded a pointer to - will be visible to you. The default is to ensure this, but you might want to read over this explanation of the memory_order parameter to load(). Note that not explicitly asking for memory synchronisation does not keep you safe from mutating/destruction by other threads.

So, say myString() is pointed successively at string's a, b then c, and your code retrieves &b... as long as the string b isn't mutated or destructed/deallocated while you're calling size(), you're ok. It doesn't matter that myString() might be updated to point to c before/during/after your call to b's .size().

Taking a step back, it can be tricky for the program to know how long after you call load() you might try to dereference the pointer, and if the b object is to later be mutated or destructed/deallocated, the kind of call you propose doesn't cooperate in any synchronisation around that later mutation/destruction. You can obviously add such coordination in myriad ways (e.g. some other atomic counter/flag, notifying the would-be modifier/destructor/deleter using a condition variable...), or you might decide to accept such a race condition sometimes (e.g. perhaps if b is known to be one of the newest entries in a generously sized LRU cache).

If you're doing something like cycling myString around a number of static const string instances, you don't have to worry about all the mutation/destruction stuff above (well, not unless you're accessing them before/after main()).

// B can I do this?
char myFifthChar = *(myString.load()->c_str() + 5);

Yes, with all the caveats above.

// C can I do this?
char myCharArray[255];
strcpy(myCharArray, myString.load()->c_str());

Yes, as above (and subject to the buffer provided being large enough).

I'm pretty sure C is illegal because myString might be deleted in the meantime.

As above - that concern's equally valid for all the 3 uses you've mentioned, just somewhat more likely for C because copying takes more CPU cycles to complete, and rather than get garbage values back losing a race could cause a buffer overrun.

like image 194
Tony Delroy Avatar answered Nov 15 '22 04:11

Tony Delroy