Question

If we have:

__int32 some_var = 0;

What is the best (if any) way to call InterlockedExchange, InterlockedIncrement and other interlocked functions which require LONG* for some_var ?

Since, there is guarantee that LONG is 32 bit on any Windows, it's probably safe just to pass (long*) some_var. However, it seems to me quite ugly and I can't find confirmation that it's safe.

Note, I can't change type to long because it's not portable. I need exactly 32 bit type.

Update: some research of libraries which provide portable atomic operations has shown that no one bothers about casting. Some examples:

Apache Portable Runtime (APR):

typedef WINBASEAPI apr_uint32_t (WINAPI * apr_atomic_win32_ptr_val_fn)
    (apr_uint32_t volatile *, 
     apr_uint32_t);

APR_DECLARE(apr_uint32_t) apr_atomic_add32(volatile apr_uint32_t *mem, apr_uint32_t val)
{
#if (defined(_M_IA64) || defined(_M_AMD64))
    return InterlockedExchangeAdd(mem, val);
#elif defined(__MINGW32__)
    return InterlockedExchangeAdd((long *)mem, val);
#else
    return ((apr_atomic_win32_ptr_val_fn)InterlockedExchangeAdd)(mem, val);
#endif
}

atomic_ops:

AO_INLINE AO_t
AO_fetch_and_sub1_full (volatile AO_t *p)
{
  return _InterlockedDecrement64((LONGLONG volatile *)p) + 1;
}
Was it helpful?

Solution

Just do assert(sizeof(LONG) == sizeof(some_var)) and only worry about the problem when the assertion fails. YAGNI. As long as the assertion holds, you can use reinterpret_cast<LONG*>(&some_var).

OTHER TIPS

Well, it's a rock and a hard place. An atomic increment is a heavy duty platform implementation detail. That's why the LONG typedef exists in the first place. Some future operating system 20 or 50 years from now might redefine that type. When, say, 256 bit cores are common and atomic increments work differently. Who knows.

If you want to write truly portable code then you should use truly portable types. Like LONG. And it will be Microsoft's burden to make it work, instead of yours.

It's going to be a 32-bit integer for quite a while to come, I'd recommend you don't worry about it.

You might as well change the type to a long, leaving behind portability, because the entire "interlocked" family of atomic operations are also not portable.

Incidentally, as a side note, I thought interlocked supported an integer overload. Perhaps thats only in .net though.

Well, __int32 isn't a portable type either. So my suggestion to make the problem go away is to use a typedef. On Windows, you can do:

typedef LONG my_int32;

...and safely pass a pointer to such a type to InterlockedExchange(). On other systems, use whatever is a 32 bit type there - for example, if they have stdint.h, you can do:

typedef int32_t my_int32;

Amusingly enough, there is InterlockedExchange - a windows API that takes a LONG* and _InterlockedExchange a msvc compiler intrinsic, that takes a long*.

Because portability has been invoked, Ill also link a page on GCC atomic intrinsics.

The point is well taken however: MSVC uses the ILP32LLP64 data model for 32bit builds and LLP64 for 64bit builds. GCC based toolchains (such as MinGW) do exist for windows and may very well implement the LP64 model - leading to amusing! occurrences such as 'long' being 64bits, but LONG being 32.

If you are sticking to Microsoft compilers its not something you need to worry about.

So, in conclusion: 1. The value being passed MUST be qualified with 'volatile'. 2. Because you are (a) using a 32bit quantity (and that is youre requirement) and (b) using the explicitly 32bit form of the InterlockedXXX API - its 100% safe to just do the bloody cast and be done with it: InterlockedIncrement is going to operate on a 32bit value on all bit sizes, your variable is going to be explicitly 32bits on all bit sizes - even with different data models in use.

the cast is safe, don't over complicate things for no reason.

Hans Passant has expressed it very well:

"An atomic increment is a heavy duty platform implementation detail."

That is why implementations provide type specific overloads.

atomic_ops is one such project.

Theoretically, every Interlocked function can be implemented by using full-blown locks - which in-turn, rely on platform specifics :-) - but this is a real performance overkill for types and functions that are supported on the target hardware platform.

There is some standardization going on in this regard, see e.g. similar questions answered here and here as well.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top