"Dereferencing Type-Punned Pointer Will Break Strict-Aliasing Rules" Warning

Fix for dereferencing type-punned pointer will break strict-aliasing

First off, let's examine why you get the aliasing violation warnings.

Aliasing rules simply say that you can only access an object through its own type, its signed / unsigned variant type, or through a character type (char, signed char, unsigned char).

C says violating aliasing rules invokes undefined behavior (so don't!).

In this line of your program:

unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));

although the elements of the incoming_buf array are of type char, you are accessing them as unsigned int. Indeed the result of the dereference operator in the expression *((unsigned int*)dcc->incoming_buf) is of unsigned int type.

This is a violation of the aliasing rules, because you only have the right to access elements of incoming_buf array through (see rules summary above!) char, signed char or unsigned char.

Notice you have exactly the same aliasing issue in your second culprit:

*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);

You access the char elements of outgoing_buf through unsigned int, so it's an aliasing violation.

Proposed solution

To fix your issue, you could try to have the elements of your arrays directly defined in the type you want to access:

unsigned int incoming_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];
unsigned int outgoing_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];

(By the way the width of unsigned int is implementation defined, so you should consider using uint32_t if your program assumes unsigned int is 32-bit).

This way you could store unsigned int objects in your array without violating the aliasing rules by accessing the element through the type char, like this:

*((char *) outgoing_buf) =  expr_of_type_char;

or

char_lvalue = *((char *) incoming_buf);

EDIT:

I've entirely reworked my answer, in particular I explain why the program gets the aliasing warnings from the compiler.

dereferencing type-punned pointer will break strict-aliasing rules warning

In order:

  • Yes. GCC will assume that the pointers cannot alias. For instance, if you assign through one then read from the other, GCC may, as an optimisation, reorder the read and write - I have seen this happen in production code, and it is not pleasant to debug.

  • Several. You could use a union to represent the memory you need to reinterpret. You could use a reinterpret_cast. You could cast via char * at the point where you reinterpret the memory - char * are defined as being able to alias anything. You could use a type which has __attribute__((__may_alias__)). You could turn off the aliasing assumptions globally using -fno-strict-aliasing.

  • __attribute__((__may_alias__)) on the types used is probably the closest you can get to disabling the assumption for a particular section of code.

For your particular example, note that the size of an enum is ill defined; GCC generally uses the smallest integer size that can be used to represent it, so reinterpreting a pointer to an enum as an integer could leave you with uninitialised data bytes in the resulting integer. Don't do that. Why not just cast to a suitably large integer type?

Need help to resolve warning: dereferencing type-punned pointer will break strict-aliasing rules

The strict aliasing rule is paragraph 6.5/7 of the Standard. It says basically that you may access an object only through an lvalue of compatible type, possibly with additional qualifiers; the corresponding signed / unsigned type; an array, structure, or union type with one of those among its members, or a character type. The diagnostic you received is saying that your code violates that rule, and it does, multiple times.

You get yourself in trouble very early with:

    T1 = (struct text *)data;

That conversion is allowed, though the resulting pointer is not guaranteed to be correctly aligned, but there's not much you can then do with T1 without violating the strict aliasing rule. In particular, if you dereference it with * or -> -- which is in fact the very next thing you do -- then you access a char array as if it were a struct text. That is not allowed, though the reverse would be a different story.

Converting T1 to a char * and accessing the pointed to array through that pointer, as you do later, are some of the few things you may do with it.

gettextexpr() is the same (both versions). It performs the same kind of conversion described above, and dereferences the converted pointer when it accesses p->count. The resulting behavior violates the strict aliasing rule, and is therefore undefined. What GCC is actually complaining about in the second case, however, is probably accessing *T1 as if it were a char *, when it is really a struct text * -- another, separate, strict aliasing violation.

So, in response to your specific questions:

  1. Was the code calling by value in first case?

C has only pass by value, so yes. In the first case, you pass two char pointers by value, which you could then use to modify the caller's char data. In the second case, you pass two char * pointers by value, which you can and do use to modify the caller's char * variables.


  1. Isn't (char **) permitted for casting while keeping strict aliasing rules?

No, absolutely not. Casting to char * (not char **) can allow you to access an object's representation through the resulting pointer, because dereferencing a char * produces an lvalue of character type, but there is no type that can generically be converted from without strict-aliasing implications.


  1. What am I missing to resolve this warning?

You are missing that what you are trying to do is fundamentally disallowed. C does not permit access a char array as if it were a struct text, period. Compilers may nevertheless accept code that does so, but its behavior is undefined.

Resolve the warning by abandoning the cast-to-structure approach, which is providing only a dusting of syntactic sugar, anyway. It's actually simpler and clearer to get rid of all the casting and write:

    count = ((*T1)[0] - '0') * 10 + ((*T1)[1] - '0');

It's perhaps clearer still to get rid of all the casting use sscanf:

    sscanf(*T1, "%2d", &count);

Note also that even if it were allowed, your specific access pattern seems to make assumptions about the layout of the structure members that are not justified by the language. Implementations may use arbitrary padding between members and after the last member, and your code cannot accommodate that.

Why is this claimed dereferencing type-punned pointer warning compiler-specific?

A value of type void** is a pointer to an object of type void*. An object of type Foo* is not an object of type void*.

There is an implicit conversion between values of type Foo* and void*. This conversion may change the representation of the value. Similarly, you can write int n = 3; double x = n; and this has the well-defined behavior of setting x to the value 3.0, but double *p = (double*)&n; has undefined behavior (and in practice will not set p to a “pointer to 3.0” on any common architecture).

Architectures where different types of pointers to objects have different representations are rare nowadays, but they are permitted by the C standard. There are (rare) old machines with word pointers which are addresses of a word in memory and byte pointers which are addresses of a word together with a byte offset in this word; Foo* would be a word pointer and void* would be a byte pointer on such architectures. There are (rare) machines with fat pointers which contain information not only about the address of the object, but also about its type, its size and its access control lists; a pointer to a definite type might have a different representation from a void* which needs additional type information at runtime.

Such machines are rare, but permitted by the C standard. And some C compilers take advantage of the permission to treat type-punned pointers as distinct to optimize code. The risk of pointers aliasing is a major limitation to a compiler's ability to optimize code, so compilers tend to take advantage of such permissions.

A compiler is free to tell you that you're doing something wrong, or to quietly do what you didn't want, or to quietly do what you wanted. Undefined behavior allows any of these.

You can make freefunc a macro:

#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)

This comes with the usual limitations of macros: lack of type safety, p is evaluated twice. Note that this only gives you the safety of not leaving dangling pointers around if p was the single pointer to the freed object.

Type dereferencing type-punned pointer will break strict-aliasing rule

Reinterpreting objects that don't have allocated storage duration as objects of some other (incompatible) type is undefined behavior.

In your example Buffwhich has type uint8_t which has either static or automatic storage duration, is being reinterpreted as type DWORD. Those type are not compatible1, the behavior is not defined.

You should simply define Buff as type you intend to use, which appears to be DWORD:

volatile DWORD Buff[READ_BUFF_SIZE];

and then you don't need macros for access, simply using the built-in operator will do:

ChunkID = Buff[0];

1 Even if we assume2 uint8_t is defined as unsigned char, which may alias any type, the type DWORD may not alias unsigned char.

2 Standard permits that the type uint8_t is not defined as unsigned char even if CHAR_BIT is 8. See extended integer types.

Dereferencing type-punned pointer will break strict-aliasing rules

It looks a lot as if you really want to use fread:

int data;
fread(&data, sizeof(data), 1, stream);

That said, if you do want to go the route of reading chars, then reinterpreting them as an int, the safe way to do it in C (but not in C++) is to use a union:

union
{
char theChars[4];
int theInt;
} myunion;

for(int i=0; i<4; i++)
myunion.theChars[i] = fgetc(stream);
return myunion.theInt;

I'm not sure why the length of data in your original code is 3. I assume you wanted 4 bytes; at least I don't know of any systems where an int is 3 bytes.

Note that both your code and mine are highly non-portable.

Edit: If you want to read ints of various lengths from a file, portably, try something like this:

unsigned result=0;
for(int i=0; i<4; i++)
result = (result << 8) | fgetc(stream);

(Note: In a real program, you would additionally want to test the return value of fgetc() against EOF.)

This reads a 4-byte unsigned from the file in little-endian format, regardless of what the endianness of the system is. It should work on just about any system where an unsigned is at least 4 bytes.

If you want to be endian-neutral, don't use pointers or unions; use bit-shifts instead.

warning! dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

You should use something like the following to don't break strict aliasing rule:

const uint16_t nseq = htons(packet->seq);
const uint32_t nts = htonl(packet->ts + timestamp);
memcpy(packet->buf + 2, &nseq, 2);
memcpy(packet->buf + 4, &nts, 4);

dereferencing type-punned pointer will break strict-aliasing rules

According to the C and C++ standards, it is undefined behaviour to access a variable of a given type through a pointer to another type. Example:

int a;
float * p = (float*)&a; // #1
float b = *p; // #2

Here #2 causes undefined behaviour. The assignment at #1 is called "type punning". The term "aliasing" refers to the idea that several different pointer variables may be pointing at the same data -- in this case, p aliases the data a. Legal aliasing is a problem for optimization (which is one of the main reasons for Fortran's superior performance in certain situations), but what we have here is flat-out illegal aliasing.

Your situation is no different; you're accessing data at buffer through a pointer to a different type (i.e. a pointer that isn't char *). This is simply not allowed.

The upshot is: You should never have had data at buffer in the first place.

But how to solve it? Make sure you have a valid pointer! There is one exception to type punning, namely accessing data through a pointer to char, which is allowed. So we can write this:

record_t data;
record_t * p = &data; // good pointer
char * buffer = (char*)&data; // this is allowed!

return p->len; // access through correct pointer!

The crucial difference is that we store the real data in a variable of the correct type, and only after having allocated that variable do we treat the variable as an array of chars (which is allowed). The moral here is that the character array always comes second, and the real data type comes first.

Consequenes of warning “dereferencing type-punned pointer will break strict-aliasing rules”

Language standards try to strike a balance between the sometimes competing interests of programmers that will use the language and compiler writers that want to use a broad set of optimizations to generate reasonably fast code. Keeping variables in registers is one such optimization. For variables that are "live" in a section of a program the compiler tries to allocate them in registers. Storing at the address in a pointer could store anywhere in the program's address space - which would invalidate every single variable in a register. Sometimes the compiler could analyze a program and figure out where a pointer could or could not be pointing, but the C (and C++) language standards consider this an undue burden, and for "system" type of programs often an impossible task. So the language standards relax the constraints by specifying that certain constructs lead to "undefined behavior" so the compiler writer can assume they don't happen and generate better code under that assumption. In the case of strict aliasing the compromise reached is that if you store to memory using one pointer type, then variables of a different type are assumed to be unchanged, and thus can be kept in registers, or stores and loads to these other types can be reordered with respect to the pointer store.

There are many examples of these kind of optimizations in this paper "Undefined Behavior: What Happened to My Code?"

http://pdos.csail.mit.edu/papers/ub:apsys12.pdf

There is an example there of a violation of the strict-aliasing rule in the Linux kernel, apparently the kernel avoids the problem by telling the compiler not to make use of the strict-aliasing rule for optimizations "The Linux kernel uses -fno-strict-aliasing to
disable optimizations based on strict aliasing."

struct iw_event {
uint16_t len; /* Real length of this stuff */
...
};
static inline char * iwe_stream_add_event(
char * stream, /* Stream of events */
char * ends, /* End of stream */
struct iw_event *iwe, /* Payload */
int event_len ) /* Size of payload */
{
/* Check if it's possible */
if (likely((stream + event_len) < ends)) {
iwe->len = event_len;
memcpy(stream, (char *) iwe, event_len);
stream += event_len;
}
return stream;
}

Figure 7: A strict aliasing violation, in include/net/iw_handler.h of the
Linux kernel, which uses GCC’s -fno-strict-aliasing to prevent possible
reordering.

2.6 Type-Punned Pointer Dereference

C gives programmers the freedom to cast pointers of one type
to another. Pointer casts are often abused to reinterpret a given
object with a different type, a trick known as type-punning. By
doing so, the programmer expects that two pointers of different
types point to the same memory location (i.e., aliasing).
However, the C standard has strict rules for aliasing. In
particular, with only a few exceptions, two pointers of different
types do not alias [19, 6.5]. Violating strict aliasing leads to
undefined behavior.
Figure 7 shows an example from the Linux kernel. The
function first updates iwe->len, and then copies the content of
iwe, which contains the updated iwe->len, to a buffer stream
using memcpy. Note that the Linux kernel provides its own optimized memcpy implementation. In this case, when event_len
is a constant 8 on 32-bit systems, the code expands as follows.

iwe->len = 8;
*(int *)stream = *(int *)((char *)iwe);
*((int *)stream + 1) = *((int *)((char *)iwe) + 1);

The expanded code first writes 8 to iwe->len, which is of
type uint16_t, and then reads iwe, which points to the same
memory location of iwe->len, using a different type int. According to the strict aliasing rule, GCC concludes that the read
and the write do not happen at the same memory location,
because they use different pointer types, and reorders the two
operations. The generated code thus copies a stale iwe->len
value. The Linux kernel uses -fno-strict-aliasing to disable optimizations based on strict aliasing.

Answers

1) What optimizations could the compiler perform in this aliasing case ?

The language standard is very specific about the semantics (behavior) of a strictly conforming program - the burden is on the compiler writer or language implementor to get it right. Once the programmer crosses the line and invokes undefined behavior then the standard is clear that the burden of proof that this will work as intended falls on the programmer, not on the compiler writer - the compiler in this case has been nice enough to warn that undefined behavior has been invoked although it is under no obligation to even do that. Sometimes annoyingly people will tell you that at this point "anything can happen" usually followed by some joke/exaggeration. In the case of your program the compiler could generate code that is "typical for the platform" and store to localval the value of something and then load from localval and store at DataPtr, like you intended, but understand that it is under no obligation to do so. It sees the store to localval as a store to something of uint32 type and it sees the dereference of the load from (*(const float32*)((const void*)(&localval))) as a load from a float32 type and concludes these aren't to the same location so localval can be in a register containing something while it loads from an uninitialized location on the stack reserved for localval should it decide it needs to "spill" that register back to its reserved "automatic" storage (stack). It may or may not store localval to memory before dereferencing the pointer and loading from memory. Depending on what follows in your code it may decide that localval isn't used and the assignment of something has no side-effect, so it may decide that assignment is "dead code" and not even do the assignment to a register.

2) As both would occupy the same size (correct me if not) what could be the side affects of such a compiler optimization ?

The effect could be that an undefined value is stored at the address pointed to by DataPtr.

3) Can I safely ignore the warning or turn off aliasing ?

That is specific to the compiler you are using - if the compiler documents a way to turn off the strict aliasing optimizations then yes, with whatever caveats the compiler makes.

4) If the compiler hasn't performed an optimization and my program is not broken after my first compilation ? Can i safely assume that every time the compiler would behave the same way (does not do optimizations) ?

Maybe, sometimes very small changes in another part of your program could change what the compiler does to this code, think for a moment if the function is "inlined" it could be thrown in the mix of some other part of your code, see this SO question.

5) Does the aliasing apply to a void * typecast too ? or is it applicable only for the standard typecasts (int,float etc...) ?

You cannot dereference a void * so the compiler just cares about the type of your final cast (and in C++ it would gripe if you convert a const to non-const and vice-versa).

6) what are the affects if I disable the aliasing rules ?

See your compiler's documentation - in general you will get slower code, if you do this (like the Linux kernel chose to do in the example from the paper above) then limit this to a small compilation unit, with only the functions where this is necessary.

Conclusion

I understand your questions are for curiosity and trying to better understand how this works (or might not work). You mentioned it is a requirement that the code be portable, by implication then it is a requirement that the program be compliant and not invoke undefined behavior (remember, the burden is on you if you do). In this case, as you pointed out in the question, one solution is to use memcpy, as it turns out not only does that make your code compliant and therefore portable, it also does what you intend in the most efficient way possible on current gcc with optimization level -O3 the compiler converts the memcpy into a single instruction storing the value of localval at the address pointed to by DataPtr, see it live in coliru here - look for the movl %esi, (%rdi) instruction.



Related Topics



Leave a reply



Submit