10

I've got a structure that contains an atomic field:

#include <stdatomic.h>

struct s {
    ...
    atomic_int a;
};

This structure is allocated with calloc:

struct s *p = calloc(1, sizeof(struct s));

Is it portable to expect p->a to be initialised to 0? There are enough barriers in the code so that weakly consistent initialisation is fine, but is the initial value guaranteed to be 0?

jch
  • 5,382
  • 22
  • 41
  • In case anyone is wondering, this discussion has resulted in https://github.com/jech/threadpool/commit/63edf80d4605e33254173f1d2b82fbc7da1b249c. – jch Jun 03 '15 at 22:36
  • It also depends on **when** you do the `calloc()` and when you do read the atomic struct field. After looking briefly at your code it seems that you do the `calloc()` before the other threads (that read the atomic) are created. And since `pthread_create()` is one of the pthread functions that [acts as memory barrier](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_12) you actually don't have to atomically set it after the `calloc()`, i.e. the `calloc()` alone is sufficient. – maxschlepzig Sep 10 '19 at 17:52

3 Answers3

3

No, this is not portable in general. calloc only guarantees a byte-wise 0 value of the underlying object. For types that (may) have a state this is not equivalent to an initialization. You definitively have to use atomic_init to put your object into a valid state.

The reason for this are platforms that hold a "lock" in addition to the base object because they don't implement the corresponding assembler instruction. So to be portable you really need to use ATOMIC_VAR_INIT or atomic_init for all atomic objects that are not statically allocated.

That said, I don't know of any existing platform that would need such cruft for atomic_int. If your platform has ATOMIC_INT_LOCK_FREE set to 2 and sizeof(atomic_int)==sizeof(int), you can be relatively sure that your strategy works. You could test that in a _Static_assert.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • Interesting. Jens's last paragraph refers to Section 7.17.5 of the spec, with the rationale implicit in Section 7.17.8. – jch May 30 '15 at 15:48
0

My guess is that this is not portable/safe.

It's very likely that calloc() end up doing a simple memset() on the memory area. This simple memset() would not issue the required memory barriers to ensure that other threads reading the structure would see the p->a as 0.

Xaqq
  • 4,308
  • 2
  • 25
  • 38
  • 4
    How might another thread access this object before calloc returns? Only calloc knows its address. – rici May 29 '15 at 17:24
  • Sorry I wasn't clear -- I realise that the initialisation will not be atomic, I'm worried whether the representation of atomic values guarantees that the initial value will be 0. I've edited the question so it's hopefully clearer. – jch May 29 '15 at 17:57
  • @rici It doesn't matter. `calloc()` can return and the pointer can be passed to an other thread. That doesn't guarantee that the second thread will see the memory as initialized. It could still see some previous garbage. – Xaqq May 29 '15 at 19:05
-1
struct s *p = calloc(1, sizeof(struct s));
struct s *q = p;
// some other thread
foo(*q);

The initialization to zero is accounted for before p or any of its assginees can access the memory. It is 0.

Also see deferred zero-ing.

Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256