Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Strict aliasing

104 views
Skip to first unread message

Joshua Maurice

unread,
May 7, 2013, 2:45:18 AM5/7/13
to

Sorry, I have not been following this issue for a while. Has the
standard committee gotten a consensus on this issue?

I was writing a prototype, and I realized that I stumbled across this
issue again, and I was wondering what the standard guarantees, what
the people on the standards committee actually intend and want for old
versions an upcoming versions (and what common compilers actually
do).

Here's a trimmed example of the code that I'm working on. It's a
runtime interpreter. A previous bit of code takes a string like "out_1
= in_1 + in_2" and compiles that into a list (std::vector) of
"Operations". Each "Operation" is something simple, like copy from an
input column (ex: "in_1") to the scratch space at a certain byte
index, or copy from the scratch space from a certain byte index to an
output column (ex: "out_1"). There will be a full expression parser
that will compile down to a list of operations, and the size of the
scratch space needed.

So, the code to run the list of "Operations" is a for loop containing
a giant switch statement. The switch statement switches on an enum
type, where the enum values are like "int32Add", "int32InputToData",
"int32DataToOutput", and so on. The case label for each enum simply
does the operation. Here's some of the code:


//Allocated from ::operator new, so it's guaranteed to be
//properly aligned.
//This is the scratch space talked about above.
char * const calculationData = data->calculationData;

for (size_t row = 0; row < numRowsToTransfer; ++row)
{ size_t const inputRow = row + data->inputStartRow;
size_t const outputRow = row + data->numRowsSetInOutputBlock;
for (size_t operationIndex=0;
operationIndex<calculation.operations.size(); ++operationIndex)
{ Operation const& op = calculation.operations[operationIndex];
switch (op.opCode)
{
case Operation::int32Add:
*reinterpret_cast<std::int32_t*>(calculationData +
op.arg2) =
*reinterpret_cast<std::int32_t
const*>(calculationData + op.arg0)
+ *reinterpret_cast<std::int32_t
const*>(calculationData + op.arg1);
break;
case Operation::int32InputToData:
*reinterpret_cast<std::int32_t*>(calculationData +
op.arg1) =
data->inputBlock-
>primitiveData<std::int32_t>(op.arg0)[inputRow];
break;
case Operation::int32DataToOutput:
data->outputBlock->primitiveData<std::int32_t>(op.arg1)
[outputRow] =
*reinterpret_cast<std::int32_t
const*>(calculationData + op.arg0);
break;


Of course, I plan to add other types in the mix, like int64, string
types, and so on, and so the scratch space will contain objects of
different types. However, I will have validation that each particular
memory region will only be accessed by a single lvalue type. (I will
also identify the effective end of life of an object, and allow that
memory to be reused for another object of a different type. Still, I
will not have a read on a memory region that immediately follows a
write of a different type.)

I have this all tested and working on Windows msvc, and I realized
that this might be running afoul of the strict aliasing rules (aka the
effective lvalue access type rules). I also remembered that I've
posted on this topic previously, and the conclusion was that the state
of affairs in both C and C++ simply are not well defined.

I think that the intent of both the C and C++ standards committees is
that code like the above is well defined as long as I guarantee that
every read of calculationData through a std::int32_t& lvalue happens
after a write to that exact index of calculationData through a
std::int32_t& lvalue, and there are no intervening writes to that
memory region via lvalues of different types (except maybe intervening
char& and unsigned char& writes, and maybe cv-qualified differences).

I'm pretty sure that the above code is functionally equivalent to
writing a general purpose pooling memory allocator in userspace. So,
if my code doesn't work, then I don't think you can write a userspace
memory allocator either, and I think or I hope that it is intended to
be possible to write a userspace memory allocator.

From my understanding of the effective C type rules, the above code is
"valid" according to the intent behind the effective C type rules,
though maybe not the explicit wording. For C++, it's my understanding
that they intended to keep code like this working, although it's
unclear if the actual wording says it is well-defined or not.

Finally, does anyone know if a real C or C++ compiler will choke on
code like this and produce output code contrary to my intended
behavior?

PS: Of course, there are alignment issues to worry about, and the
rules concerning those are much better defined. Let's ignore that for
now, and assume the accesses are properly aligned.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

0 new messages