16

Strict aliasing prevents us from accessing the same memory location using an incompatible type.

int* i = malloc( sizeof( int ) ) ;  //assuming sizeof( int ) >= sizeof( float )
*i = 123 ;
float* f = ( float* )i ;
*f = 3.14f ;

this would be illegal according to C standard, because the compiler "knows" that int cannot accessed by a float lvalue.

What if I use that pointer to point to correct memory, like this:

int* i = malloc( sizeof( int ) + sizeof( float ) + MAX_PAD ) ;
*i = 456 ;

First I allocate memory for int, float and the last part is memory which will allow float to be stored on aligned address. float requires to be aligned on multiples of 4. MAX_PAD is usually 8 of 16 bytes depending on the system. In any case, MAX_PAD is large enough so float can be aligned properly.

Then I write an int into i, so far so good.

float* f = ( float* )( ( char* )i + sizeof( int ) + PaddingBytesFloat( (char*)i ) ) ;
*f= 2.71f ;

I use the pointer i, increment it with the size of int and align it correctly with the function PaddingBytesFloat(), which returns the number of bytes required to align a float, given an address. Then I write a float into it.

In this case, f points to a different memory location that doesn't overlap; it has a different type.


Here are some parts from the standard (ISO/IEC 9899:201x) 6.5 , I was relying on when writing this example.

Aliasing is when more than one lvalue points to the same memory location. Standard requires that those lvalues have a compatible type with the effective type of the object.

What is effective type, quote from standard:

The effective type of an object for an access to its stored value is the declared type of the object, if any.87)If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

87) Allocated objects have no declared type.

I'm trying to connect the pieces and figure out if this is allowed. In my interpretation the effective type of an allocated object can be changed depending on the type of the lvalue used on that memory, because of this part: For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

Is this legal? If not, what if I used a void pointer as lvalue instead of an int pointer i in my second example? If even that wouldn't work, what if I got the address, which is assigned to the float pointer in the second example, as a memcopied value, and that address was never used as an lvalue before.

Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
this
  • 5,229
  • 1
  • 22
  • 51
  • 2
    Why are you trying to complicate what should be simple? Use a struct and be happy! – pmg May 10 '14 at 15:00
  • @pmg This way has certain broader aplications. I'm not looking for a better/different solution. – this May 10 '14 at 15:01
  • 1
    Why are you writing code that is going to be difficult to read and maintain? – Ed Heal May 10 '14 at 15:04
  • 1
    Are you specifically asking if this is legal, or just looking for opinions on it? – Andrew Barber May 10 '14 at 22:30
  • 1
    *"In this case f points to a different memory location that doesn't overlap; it has a different type."* It overlaps (parts of) `i[1]` and/or `i[2]`. – pmg May 12 '14 at 15:34
  • That crap is there just to help the compiler with optimization. IIRC it was perfectly legal until C99. Regardless, you can turn of strict aliasing with compiler flags, or use an inline pragma to ID it or replace it with a union as in Carmack's fast inverse square root trick @ http://en.wikipedia.org/wiki/Fast_inverse_square_root – technosaurus May 12 '14 at 15:34
  • @pmg Hmm yes, but then it also overlaps all memory after `i` with: `i[any_positive_number]`. – this May 12 '14 at 15:35
  • 1
    Did you mean the title to be **String Aliasing...** or **Strict Aliasing...**? – R Sahu May 12 '14 at 15:40
  • @RSahu Thank you, I don't know how I missed that. Strict of course. – this May 12 '14 at 15:42
  • Are there any standard gurus here that can explain why the standard cares whether I assume a memory location holds an `int` or `float` and treat it as `int*` or `float*`? – R Sahu May 12 '14 at 16:00
  • 1
    @RSahu: The compiler cares basically so that it can optimize code. For example `void foo(int *x, float *y) { ... }` the compiler will assume that the memory pointed by `x` and `y` do not overlap. The details are a bit long for a SO comment but surely you'll find a lot of details if you look for _strict aliasing_ and the `restrict` qualifier. – rodrigo May 12 '14 at 16:38
  • @rodrigo Thanks for the pointer. I did indeed find more information on the subject. – R Sahu May 12 '14 at 16:43
  • @rodrigo If the sole concern of the compiler w.r.t strict aliasing is optimization, should I be able to alias the `i` to a `float*` in a function call, such as `foo((float*)i)` without affecting anything? – R Sahu May 12 '14 at 16:48
  • @RSahu: breaking the SA rules is technically UB, no matter where or why. That said, if `foo()` is an external function (say a syscall or a third party library) then most likely nothing strange will happen. If the function is in your program, then full-program optimization might bite you. – rodrigo May 12 '14 at 16:59
  • Here's my take: `malloc()` returns an object which consists of several member objects. After `*i = 456`, the effective type **of the first member object** turns into `int` for all subsequent non-modification accesses. The next contiguous member object doesn't have an effective type yet. – ninjalj May 12 '14 at 19:16
  • 1
    " the compiler "knows" that int cannot accessed by an lvalue float." - are you referring to something other than just the strict aliasing rule? – M.M May 13 '14 at 08:45
  • @MattMcNabb No; that behavior is the consequence of the type based aliasing, and compiler can freely assume that float cannot point to the same address as an int and reorder accesses when optimizing. – this May 13 '14 at 10:20
  • 1
    Whilst using a union is the proper way to fix this, I still don't understand WHY you want to do this? To stop the compiler optimising something? To break some existing code? To find a 0-day vulnerability in your local nuclear power station? Why do you want to bypass the trouble everyone (compiler/standards writers) has gone to, to prevent you doing this? – Neil May 13 '14 at 11:35
  • 4
    @Neil: Come on! There are a lot of reasons for doing things like this, after all C is a low-level language: implementing a memory allocator or a memory arean, a garbage collector, a variadic type, a type system for an interpreter, a virtual CPU... In some cases you might use an union, but not in all of them. And knowing where the language sets its limits is always useful. – rodrigo May 13 '14 at 15:39
  • 1
    @rodrigo I disagree, there are very few reasons to do this. all of those things do not require you to butcher the language like this. In an ancient compiler (pre C89) perhaps you did, but if you are worried about 'strict-aliasing' that's clearly not the case. – Neil May 15 '14 at 10:46
  • @Neil: It would be possible, and not even difficult, for compilers to apply type-based aliasing optimizations in ways that would be compatible with most code that relies upon type punning while achieving the majority of the total optimizations available. For some kinds of code, increased aggressiveness might represent the difference between a 60% speedup and a 90% speedup versus no aliasing optimizations, but for many others being able to receive a 60% speedup and have code work may be a lot more useful than any level of "speedup" that breaks code. – supercat Sep 09 '16 at 14:33
  • @Neil: What would be most helpful would be to recognize a split between the language in which memory-management code is written, and between code which never needs to recycle memory except via `free` or other designated means. Each could then have rules which were far better designed for its purpose than the present rules which are insufficient to allow memory-management code to be written efficiently, but impose some significant obstacles to optimization in cases where such semantics would not need to be supported. – supercat Sep 09 '16 at 14:40

4 Answers4

10

I think that yes, it is legal.

To illustrate my point, let's see this code:

struct S
{
    int i;
    float f;
};
char *p = malloc(sizeof(struct S));

int *i = p + offsetof(struct S, i);  //this offset is 0 by definition
*i = 456;
float *f = p + offsetof(struct S, f);
*f= 2.71f;

This code is, IMO, clearly legal, and it is equivalent to yours from a compiler point of view, for appropriate values of PaddingBytesFloat() and MAX_PAD.

Note that my code does not use any l-value of type struct S, it is only used to ease the calculation of the paddings.

As I read the standard, in malloc'ed memory has no declared type until something is written there. Then the declared type is whatever is written. Thus the declared type of such memory can be changed any time, overwriting the memory with a value of different type, much like an union.

TL; DR: My conclusion is that with dynamic memory you are safe, with regard to strict-aliasing as long as you read the memory using the same type (or a compatible one) you use to last write to that memory.

Daniel Kamil Kozar
  • 18,476
  • 5
  • 50
  • 64
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • Wont the float pointer arithmetic fail? You are trying to use the offsetof which returns the offset in bytes, and incrementing a float which is more than one byte per member, wont that end up actually incrementing by offsetof(struct S, f) * sizeof(float)? – DAhrens May 13 '14 at 15:21
  • 2
    @DAhrens: No. The code is `p + offset(...)` and `p` is a pointer-to-char. The cast to pointer-to-float is done implicitly later in the assignment. – rodrigo May 13 '14 at 15:33
  • Ah, I missed that part. Thanks for the clarification. – DAhrens May 13 '14 at 17:51
  • This doesn't need to be dynamic memory `char p[sizeof(struct S)];` should also work. – dave May 15 '14 at 04:43
  • @dave: It should work, but for different reasons: with `malloc` it is because the memory has no declared type; with `char[]` it is because `char` can alias any other type. Any difference in practice? Well, IIUIC, with the array you can do `*(int*)p = 456; `float f = *(float*)p` and that _is not_ an aliasing violation, because the effective type of that memory is `char[]` (it may yet be undefined for other reasons). But the same code with dynamic memory is an aliasing violation, because the first assignment changes the effective type of the dynamic memory. – rodrigo May 15 '14 at 07:47
  • 1
    @dave: On second thought I'm not so sure. It is true that `char` can alias any other type, but can `char` be aliased by any other type? I'm not sure... actually, I think that you cannot. Moreover, with the `char[]` thing you have the alignment issue... – rodrigo May 15 '14 at 07:53
  • @rodrigo: alignment could be taken care of in order to make it work, either using a pragma or with a similar method to that used in the question. That `char` can access any other type is enough for the compiler to generate correct code. It must reload any memory after a write to a char because the underlying value might have changed, and cannot reorder writes without breaking the standard. – dave May 15 '14 at 08:43
  • 2
    @dave: One thing is to use a `char` lvalue to access an object of `int` effective type. Another is to use `int` to access an object of `char` effective type. The former is allowed per 6.5.7 of the cited standard. The latter is not. – rodrigo May 15 '14 at 09:21
  • @rodrigo, perhaps I'm missing the use case but I thought "or copied from an array of char" would cover the case that I'm talking about. It is hard to say without a specific code example. And this is getting a bit off topic. Though I don't think the question is very clear as to what the top is! – dave May 15 '14 at 14:42
  • @rodrigo About the first comment that dave made; Memory in his example has automatic storage duration, while malloc returns memory with allocated storage duration( 6.2.4 ). The rule: *Allocated objects have no declared type.* specifies only allocated memory. So you cannot cast a part of a char local array to a different type, even if properly aligned for that type. – this May 16 '14 at 14:31
  • @this: Allocated objects are given as an *example* of objects with no declared type, but I don't think the Committee has ever had any consensus understanding as to what the phrase "with no declared type" is supposed to mean. No way of defining the term will result in consistently sensible corner case behavior, but different ways of defining the term would botch different corner cases. – supercat Jan 21 '21 at 22:09
9

Yes, this is legal. To see why, you don't even need to think about strict aliasing rule, because it doesn't apply in this case.

According to the C99 standard, when you do this:

int* i = malloc( sizeof( int ) + sizeof( float ) + MAX_PAD ) ;
*i = 456 ;

malloc will return a pointer to a memory block large enough to hold an object of size sizeof(int)+sizeof(float)+MAX_PAD. However, notice that you are only using a small piece of this size; in particular, you are only using the first sizeof(int) bytes. Consequently, you are leaving some free space that can be used to store other objects, as long as you store them into a disjoint offset (that is, after the first sizeof(int) bytes). This is tightly related with the definition of what exactly is an object. From C99 section 3.14:

Object: region of data storage in the execution environment, the contents of which can represent values

The precise meaning of the contents of the object pointed to by i is the value 456; this implies that the integer object itself only takes a small portion of the memory block you allocated. There is nothing in the standard stopping you from storing a new, different object of any type a few bytes ahead.

This code:

float* f = ( float* )( ( char* )i + sizeof( int ) + PaddingBytesFloat( (char*)i ) ) ;
*f= 2.71f ;

Is effectively attaching another object to a sub-block of the allocated memory. As long as the resulting memory location for f doesn't overlap with that of i, and there is enough room left to store a float, you will always be safe. The strict aliasing rule doesn't even apply here, because the pointers point to objects that do not overlap - the memory locations are different.

I think the key point here is to understand that you are effectively manipulating two distinct objects, with two distinct pointers. It just so happens that both pointers point to the same malloc()'d block, but they are far enough from one another, so this is not a problem.

You can have a look at this related question: What alignment issues limit the use of a block of memory created by malloc? and read Eric Postpischil's great answer: https://stackoverflow.com/a/21141161/2793118 - after all, if you can store arrays of different types in the same malloc() block, why wouldn't you store an int and a float? You can even look at your code as the special case in which these arrays are one-element arrays.

As long as you take care of alignment issues, the code is perfectly fine and 100% portable.

UPDATE (follow-up, read comments below):

I believe your reasoning about the standard not enforcing strict aliasing on malloc()'d objects is wrong. It is true that the effective type of a dynamically allocated object can be changed, as conveyed by the standard (it is a matter of using an lvalue expression with a different type to store a new value in there), but note that once you do that, it is your job to ensure that no other lvalue expression with a different type will access the object value. This is enforced by rule 7 on section 6.5, and you quoted it in your question:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types: - a type compatible with the effective type of the object;

Thus, by the time you change the effective type of an object, you are implicitly promising to the compiler that you won't access this object using an old pointer with an incompatible type (compared to the new effective type). This should be enough for the purposes of the strict aliasing rule.

Community
  • 1
  • 1
Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
  • Looking back at my question makes it pretty clear now. I guess my next question will involve if the standard really enforces strict aliasing on memory with allocated storage duration. If you read the excerpt from the standard that I posted in the question, you will notice it hints that you can change the effective type of the object. As if you could point to the same allocated memory with different types. – this May 17 '14 at 15:42
  • Since this: *For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access* and this: *Allocated objects have no declared type.* strict aliasing doesn't apply to memory that was (m)allocated. – this May 17 '14 at 15:54
  • @self. Hum. That's a good point, but I don't fully agree with your reasoning. See my updated answer. – Filipe Gonçalves May 17 '14 at 16:21
  • I have just came to a similar conclusion. Your explanation makes it clearer. I think I have all the information I need, I'm gonna let it sink for a while. – this May 17 '14 at 16:23
  • does it holds if next pointer assigned without intermediate cast to `char *`, for example like this `float* f = (float *)(i + 1)` ? – user3489275 May 25 '14 at 10:48
  • @user3489275 It might be problematic because of alignment. `i+1` is not guaranteed to give you an address that is correctly aligned to store a float. – Filipe Gonçalves May 25 '14 at 11:18
  • @FilipeGonçalves Thanks! But if alignment is correct - am I safe regarding to strict aliasing rule? – user3489275 May 25 '14 at 11:23
  • 1
    @user3489275 If alignment is correct, and under the circumstances in the orignal question, yes, it is safe, because you are not really creating an alias - `i` and `f` will effectively point to different objects - there is no aliasing. Aliasing would occur if you did `float *f = (float *) i;`. That would be a problem. – Filipe Gonçalves May 25 '14 at 13:18
  • The only definition of "object" I can think of which would be consistent with the requirement that memcpy cannot access outside the bounds of the object to which it receives a pointer would imply that a malloc'ed region is a single object, and writing to any part of it sets the Effective Type of the whole thing to be an array of type in question. Such an interpretation would be needed to justify some aliasing-based assumptions which are generally useful (e.g. if `p1` and `p2` are pointers to the same structure type, `p1->x` and `p2->y` cannot alias). – supercat Sep 08 '16 at 23:37
  • Of course, such an interpretation would obviously totally gut the language, but since "modern" interpretations of the rules already gut the language one may as well be honest about it. – supercat Sep 08 '16 at 23:38
2

I found a nice analogy. You may also find it useful. Quoting from ISO/IEC 9899:TC2 Committee Draft — May 6, 2005 WG14/N1124

6.7.2.1 Structure and union specifiers

[16] As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply. However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

[17] EXAMPLE After the declaration:

 struct s { int n; double d[]; };

the structure struct s has a flexible array member d. A typical way to use this is:

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m])); 

and assuming that the call to malloc succeeds, the object pointed to by p behaves, for most purposes, as if p had been declared as:

 struct { int n; double d[m]; } > *p;

(there are circumstances in which this equivalence is broken; in particular, the offsets of member d might not be the same).

It would be more fair to use an example like:

struct ss {
  double da;
  int ia[];
}; // sizeof(double) >= sizeof(int)

In example of above quote, size of struct s is same as int (+ padding) and it is then followed by double. (or some other type, float in your case)

Accessing memory sizeof(int) + PADDING bytes after the struct start as double (using syntactic sugar) looks fine as per this example, so I believe your example is legal C.

Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
  • But with flexible array members you access them trough the struct member and proper notation(`.`). I don't quite see you point. – this May 14 '14 at 19:11
  • You access parts of raw memory through one type only and thus a compliant compiler won't introduce nasty surprises for you even when the optimization level is maximum. Your examples do a similar work as `struct ss` with flexible array, but instead of accessing `int member` through `obj.ia[0]` everytime, you access it with `*((char *)&obj + offset_of_first_integer)` without even actually declaring the `struct ss`. Comparing 2 made me believe your piece of code has well defined behaviour. (Hoping I am not missing anything) – Mohit Jain May 15 '14 at 05:09
  • Accumulating my point, if you (m)alloc more bytes then required for 1 data type, you can use remaining bytes as different type provided memories don't overlap and you access one piece of memory as 1 type of data only. – Mohit Jain May 19 '14 at 12:34
  • It should be perfectly "fair" to cast a pointer to any type and use the associated memory as that type provided that all accesses that will ever done with the newly-cast pointer precede any accesses that will be done via any other means. A pointer from one specific type will rarely be cast to a pointer to another except in cases where it would be necessary to reinterpret the contents of the storage. The purpose of the rule was to avoid mandating "overly pessimistic" assumptions about what might alias, and assuming that the target of a cast will be type-punned is hardly pessimistic. – supercat Jul 18 '16 at 15:15
1

The strict aliasing rules are there to allow for more aggressive compiler optimizations, specifically by being able to reorder accesses to different types without having to worry about whether they point to the same location. So for instance, in your first example it is perfectly legal for a compiler to reorder the writes to i and f, and thus your code is an example of undefined behaviour (UB).

There is an exception to this rule and you have the relevant quote from the standards

having a type that is not a character type

Your second bit of code is entirely safe. The memory regions do no overlap so it does not matter if memory accesses are reordered across that boundary. Indeed the behaviour of the two pieces of code is completely different. The first one places an int in a memory region and then a float in to the same memory region, whereas the second one places an int in to a memory region and a float in to a bit of memory next to it. Even if these accesses are reordered then your code will have the same effect. Perfectly, legal.

I get the feeling I have thus missed the real question here.

The safest way to fiddle with low level memory if you really did want the behaviour in your first program is either (a) a union or (b) a char *. Using char * and then casting to the proper type is used in a lot of C code, e.g: in this pcap tutorial (scroll down to "for all those new C programmers who insist that pointers are useless, I smite you."

dave
  • 4,812
  • 4
  • 25
  • 38
  • @self: this is not true, it is assigning a struct pointer to a char pointer. This is safe to do so, even if you continue to access the memory via BOTH pointers, and accesses will not be re-ordered. (e.g. by changing a field via the struct pointer and then calling printf to print the packet). – dave May 14 '14 at 09:29
  • Right, but every pointer( except char ) points to different memory. With that being the case, I don't see how that related to my first example. (Maybe you should be more specific with external links and rather post the text here, so there isn't any confusion ). – this May 14 '14 at 09:45
  • The entire point, which I was saying in my post, is that if you do want overlapping types then you have to use a char *. So saying "except for char *" which was what that example was showing is missing the point of the example and of the sentence that it is connected to. You cannot do what you want in your first example. I thought that was clear. It is undefined behaviour. And that the second example is different and safe precisely because it doesn't have overlapping pointers. – dave May 14 '14 at 10:40
  • @self: and also, your first example is UB as I state in my answer. So I'm not going to be able to find code that does relate to your first example. – dave May 15 '14 at 08:41