views:

101

answers:

6

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;
}
+1  A: 

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.

Brent Arias
Just I said I *can't* change type. But I can (and it's common way) write portable `MyPortableInterlockedExchangeWrapper` (of course, there is a safe way to pass int to `InterlockedExchange` on Windows).
Shcheklein
+2  A: 

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.

Hans Passant
@Hans: Why it was a problem to provide `InterlockedExchange32`? There are functions for 64-bit integers and they work with __int64 type.
Shcheklein
@Hans: LONG is not portable at all. It works only on MS, but I need 32-bit portable type.
Shcheklein
Shcheklein
If you don't want to take a dependency on Win32 then don't use its API. Boost can help, interlocked.hpp header. Exact same problem, it's a long. For the exact same reasons.
Hans Passant
No, C casts are not safe.
Hans Passant
I suspect this isn't really going to go anywhere until you explain why it *has* to be a 32-bit integer.
Hans Passant
A: 

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).

Mark Ransom
Sizes are equal on Windows now (and I believe always will be). Assuming that is this cast safe and why?
Shcheklein
@Shcheklein, I'm assuming that pointers to integer types of the same size can be safely cast to each other, because the machine representation is the same; even if they aren't the same, such as a signed/unsigned mismatch, the difference should be harmless. I suppose it's possible for `LONG` to include some Microsoft-specific alignment keyword, so I can't make any absolute guarantees. This is all implementation-specific voodoo so you're going to have to rely on some assumptions and accept some compromises.
Mark Ransom
@Mark: I believe, you are right. At least seems no one bothers about casting. See also question updates.
Shcheklein
@Shcheklein, they *are* doing a cast in some cases, but it's an old style C cast rather than a C++ style cast.
Mark Ransom
+1  A: 

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;
caf
We already use `stdint.h` (there is implementation for Windows also). I used `__int32` just for simplicity in my question.
Shcheklein
I don't like to introduce new types just because of one function but probably it's the only way.
Shcheklein
A: 

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.

Chris Becke
A: 

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.

andras