I am struggling with implementing a shared memory buffer without breaking C99's strict aliasing rules.
Suppose I have some code that processes some data and needs to have some 'scratch' memory to operate. I could write it as something like:
void foo(... some arguments here ...) {
int* scratchMem = new int[1000]; // Allocate.
// Do stuff...
delete[] scratchMem; // Free.
}
Then I have another function that does some other stuff that also needs a scratch buffer:
void bar(...arguments...) {
float* scratchMem = new float[1000]; // Allocate.
// Do other stuff...
delete[] scratchMem; // Free.
}
The problem is that foo() and bar() may be called many times during operation and having heap allocations all over the place may be quite bad in terms of performance and memory fragmentation. An obvious solution would be to allocate a common, shared memory buffer of proper size once and then pass it into foo() and bar() as an argument, BYOB-style:
void foo(void* scratchMem);
void bar(void* scratchMem);
int main() {
const int iAmBigEnough = 5000;
int* scratchMem = new int[iAmBigEnough];
foo(scratchMem);
bar(scratchMem);
delete[] scratchMem;
return 0;
}
void foo(void* scratchMem) {
int* smem = (int*)scratchMem;
// Dereferencing smem will break strict-aliasing rules!
// ...
}
void bar(void* scratchMem) {
float* smem = (float*)scratchMem;
// Dereferencing smem will break strict-aliasing rules!
// ...
}
I guess I have two questions now:
- How can I implement a shared common scratch memory buffer that is not in violation of aliasing rules?
- Even though the above code does violate strict aliasing rules, there is no 'harm' being done with the alias. Therefore could any sane compiler generate (optimized) code that still gets me into trouble?
Thanks
Actually, what you have written is not a strict aliasing violation.
C++11 spec 3.10.10 says:
So the thing that causes the undefined behavior is accessing the stored value, not just creating a pointer to it. Your example does not violate anything. It would need to do the next step: float badValue = smem[0]. smem[0] gets the stored value from the shared buffer, creating an aliasing violation.
Of course, you aren't about to just grab smem[0] before setting it. You are going to write to it first. Assigning to the same memory does not access the stored value, so no ailiasing However, it IS illegal to write over the top of an object while it is still alive. To prove that we are safe, we need object lifespans from 3.8.4:
You have a POD type, so trivial destructor, so you can simply declare verbally "the int objects are all at the end of their lifespan, I'm using the space for floats." You then reuse the space for floats, and no aliasing violation occurs.
It is always valid to interpret an object as a sequence of bytes (i.e. it is not an aliasing violation to treat any object pointer as the pointer to the first element of an array of chars), and you can construct an object in any piece of memory that's large enough and suitably aligned.
So, you can allocate a large array of
char
s (any signedness), and locate an offset that's aliged atalignof(maxalign_t)
; now you can interpret that pointer as an object pointer once you've constructed the appropriate object there (e.g. using placement-new in C++).You do of course have to make sure not to write into an existing object's memory; in fact, object lifetime is intimately tied to what happens to the memory which represents the object.
Example:
Note that memory obtained by
malloc
ornew char[N]
is always aligned for maximal alignment (but not more, and you may wish to use over-aligned addresses).If a union is used to hold the int and float variables, then you can by pass the strict aliasing. More about this is given in http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
Also see the following article.
http://blog.regehr.org/archives/959
He gives a way to use unions to do this.