Std :: atomic in conjunction with another character

I recently read code that had an atom and a symbol in the same union. Something like that

union U {
    std::atomic<char> atomic;
    char character;
};

I'm not quite sure about the rules here, but the code comments say that since the character can use something, we can safely work with the atomic variable if we promise not to change the last few bits of the byte. And the character uses only those last few bytes.

Is this allowed? Can we put an atomic integer on a symbol, and both of them will be active? If so, what happens when one thread tries to load a value from an atomic integer and the other goes and writes a character (only the last few bytes), will the char record represent the atomic record? What is happening there? Will the cache have to be flushed for a thread that is trying to load an atomic integer?

(This code looks smelly to me, and I'm not a fan of using it. I just want to understand what parts of the above schema can be determined and under what circumstances)


As requested, the code does something like this

// thread using the atomic
while (!atomic.compare_exchange_weak(old_value, old_value | mask, ...) { ... }

// thread using the character
character |= 0b1; // set the 1st bit or something
+6
source share
1 answer

, , - , , .

. char -can-alias - , , , , (, gcc, clang MSVC) (, x86).

, . ISO ++ 11 " " , char[] ( , c[0] c[1] ). , |= char , atomic<char>, , , , |=.

, UB , , , () , . (, , , .. atomic<int> -punning atomic<char> . , "" , type-punning atomic , atomic<int/char> , , .

, -punning ISO ++. , char*, char. union-punning ISO C99 GNU GNU C89 GNU ++, ++.


, , ? , ..

character |= 1 ( ) asm, char, , . x86 destination-destination or, ( , ). - RMW, .

--, , , - -- . asm, , . ( , . std::atomic, , ...)

:

 thread A           |     thread B
 -------------------|--------------
 read tmp=c=0000    |
                    |
                    |     c|=0b1100     # atomically, leaving c = 1100
 tmp |= 1 # tmp=1   |
 store c = tmp

c= 1, 1101, . / , B.


asm, , ( Godbolt):

void t1(U &v, unsigned mask) {
    // thread using the atomic
    char old_value = v.atomic.load(std::memory_order_relaxed);
    // with memory_order_seq_cst as the default for CAS
    while (!v.atomic.compare_exchange_weak(old_value, old_value | mask)) {}

    // v.atomic |= mask; // would have been easier and more efficient than CAS
}

t1(U&, unsigned int):
    movzx   eax, BYTE PTR [rdi]            # atomic load of the old value
.L2:
    mov     edx, eax
    or      edx, esi                       # esi = mask (register arg)
    lock cmpxchg    BYTE PTR [rdi], dl     # atomic CAS, uses AL implicitly as the expected value, same semantics as C++11 comp_exg seq_cst
    jne     .L2
    ret


void t2(U &v) {
  // thread using the character
  v.character |= 0b1;   // set the 1st bit or something
}

t2(U&):
    or      BYTE PTR [rdi], 1    # NON-ATOMIC RMW of the whole byte.
    ret

, v.character |= 1 , v.atomic ^= 0b1100000 ( CAS) .

, , XOR, , . , or XOR. , , 0x10 - , 50% , 1 16 , 4 .

, , .


, ?

, , . , , , CPU - , . .

, , , (MESI), . L1D, . . num ++ 'int num'? .

+9

All Articles