14

For example, is this code valid, or does it invoke undefined behavior by violating the aliasing rules?

int x;
struct s { int i; } y;
x = 1;
y = *(struct s *)&x;
printf("%d\n", y.i);

My interest is in using a technique based on this to develop a portable method for performing aliased reads.

Update: here is the intended usage case, a little bit different, but it should be valid if and only if the above is valid:

static inline uint32_t read32(const unsigned char *p)
{
    struct a { char r[4]; };
    union b { struct a r; uint32_t x; } tmp;
    tmp.r = *(struct a *)p;
    return tmp.x;
}

GCC, as desired, compiles this to a single 32-bit load, and it seems to avoid the aliasing issues that could happen if p actually points to a type other than char. In other words, it seems to act as a portable replacement for the GNU C __attribute__((__may_alias__)) attribute. But I'm uncertain whether it's really well-defined...

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 7
    That looks very dangerous to me. – OldProgrammer Jun 29 '13 at 21:38
  • 3
    **This is perfectly valid.** I don't remember the exact part/quote of the standard, but you can do this. Just like one can alias a struct through a pointer which points to a struct that shares its initial members of the first struct. –  Jun 29 '13 at 21:38
  • @H2CO3 so padding can happen only *between* fields? – Elazar Jun 29 '13 at 21:45
  • 6
    @Elazar: Indeed, padding can only happen between fields, not at the beginning. My concern is not about padding but about aliasing. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 21:46
  • @Elazar Yup, not before the first member. –  Jun 29 '13 at 21:49
  • Actually I think the first code snippet might be well-define and the latter undefined, if `p` points to an object with effective type different from `unsigned char`. The reasoning has to do with the ordering of bullet points in C11 6.5p7... – R.. GitHub STOP HELPING ICE Jun 29 '13 at 21:55
  • 1
    my gut feeling is it'll violate effective typing rules, but I'll have to re-read and meditate on the relevant parts of the standard; may I ask what's wrong with `memcpy()`? – Christoph Jun 29 '13 at 21:55
  • @R.. I've got a nice standard quote for you. –  Jun 29 '13 at 21:55
  • @Christoph: `memcpy` would work wonders, except that I'm stuck with gcc `-ffreestanding`, which disables all builtins, so `memcpy` is an actual function call. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 22:02
  • Why can't you just say `static inline uint32_t read32(const uint8_t *p) { return *(const uint32_t *)p; }` ? PS: your usage case seems to be missing some `const` in the body... probably inconsequential for the present discussion, but thought I would point it out – Nicu Stiurca Jun 29 '13 at 22:03
  • @SchighSchagh: C11 6.5 §6/7 – Christoph Jun 29 '13 at 22:11
  • 1
    @SchighSchagh: That's definitely UB. Classic aliasing violation. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 22:18
  • I believe your `read32()` function would crash on RISC machines such as SPARC or PPC with a SIGBUS error if `p` is improperly aligned for the 32-bit read. You'll probably be OK on Intel machines which support misaligned access; they aren't RISC. (That is, given `char data[20];`, suitably initialized, on a SPARC or PPC machine, one of `uint32_t r1 = read32(&data[0]);` and `uint32_t r2 = read32(&data[1]);` is pretty much guaranteed to give a SIGBUS.) I'm not clear whether your planned usage could encounter this problem. – Jonathan Leffler Jun 29 '13 at 22:22
  • 1
    @JonathanLeffler: what leads you to believe that? my assumption would be `_Alignof (struct { char r[4]; }) == 1`; I'm not sure that's required, though... – Christoph Jun 29 '13 at 22:30
  • 1
    @JonathanLeffler: Unless it's UB, it can't crash. The compiler would be responsible for generating a read that's safe. In my usage case, I've already achieved alignment at this point, so it wouldn't matter (and hopefully the compiler could prove that `p` is aligned and thus assume alignment), but that matter is rather separate from the question of whether it's an aliasing violation. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 22:30
  • I no longer have access to RISC systems, so I cannot prove it, but on SPARC or PPC, I'm tolerably certain that if you try misaligned memory access as in my example, you will get a SIGBUS. The main issue to me is 'is my example usage isomorphic with the intended usage'. But be wary of assuming 'all the world is running on an Intel chip'. Until you've either stated that my interpretation of your usage is not relevant, or you've proven that I'm wrong by running the code on SPARC or PPC, be wary of claiming 'portable'. – Jonathan Leffler Jun 29 '13 at 22:37
  • 1
    @JonathanLeffler: Oh, I agree misaligned accesses will crash. My claim is that if the compiler can't prove alignment, and if the struct does not have an alignment requirement, then the compiler must generate safe byte-by-byte accesses for copying the structure. A crash here would be a *symptom* of UB, and the UB, not the crash, would be the argument that the code is invalid. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 23:07
  • 1
    I've already seen this same code before, not remember where. But it was defined an UB... – The Mask Jun 29 '13 at 23:51
  • @R.. I just re-read the quote I've provided you with yesterday, **and there indeed is a "vice versa" in there.** "A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa" - Is that what you are looking for? –  Jun 30 '13 at 13:30

5 Answers5

5

I believe this will still violate effective typing rules. You want to access a memory location that wasn't declared explicitly (or implicitly via storage in case of dynamic allocation) as containing a struct a through an expression of that type.

None of the sections that have been quoted in other answers can be used to escape this basic restriction.

However, I believe there's a solution to your problem: Use __builtin_memcpy(), which is available even in freestanding environments (see the manual entry on -fno-builtin).


Note that the issue is a bit less clear-cut than I make it sound. C11 section 6.5 §7 tells us that it's fine to access an object through an lvalue expression that has an aggregate or union type that includes one of the aforementioned types among its members.

The C99 rationale makes it clear that this restriction is there so a pointer to an aggregate and a pointer to one of its members may alias.

I believe the ability to use this loophole in the way of the first example (but not the second one, assuming p doesn't happen to point to an actual char [4]) is an unintended consequence, which the standard only fails to disallow because of imprecise wording.

Also note that if the first example were valid, we'd basically be able to sneak in structural typing into an otherwise nominally typed language. Structures in a union with common initial subsequence aside (and even then, member names do matter), an identical memory layout is not enough to make types compatible. I believe the same reasoning applies here.

Christoph
  • 164,997
  • 36
  • 182
  • 240
  • Using memcpy is interesting, `uint32_t i;memcpy(&i,p,4); return i; ` gcc generates exactly the same code as the read32() function. Just a single load on x86. On ARM, a call to memcpy (if the buffer is unaligned) is generated even for the read32() function given in the question. – nos Jul 04 '13 at 10:30
3

My reading of aliasing rules (C99, 6.5p7) with the presence of this sentence:

"an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or"

leads to me think it does not violate the C aliasing rules.

But the fact it does not violate aliasing rules is not enough for this code snippet to be valid. It may invoked undefined behavior for other reasons.

(struct s *) &x

is not guaranteed to point to a valid struct s object. Even if we assume the alignment of x is suitable for an object of type struct, the resulting pointer after the cast may not point to a space large enough to hold the structure object (as struct s may have padding after its last member).

EDIT: the answer has been completely reworked from its initial version

ouah
  • 142,963
  • 15
  • 272
  • 331
  • that's my take on C11 6.5 §7 as well, but the wording ([An object may have its stored value accessed by an lvalue expression that has] *an aggregate or union type that includes one of the aforementioned types among its members*) is less clear – Christoph Jun 30 '13 at 00:19
  • @Christoph: Indeed, the text you cited is what makes me think my code snippet #1 may be well-defined, but snippet #2 undefined (assuming the effective type is not actually `unsigned char [4]` but is instead attempting to acces the representation of another type. It seems deliberate that the text about `char` types being able to alias anything was put *after* the text you cited about "one of the aforementioned types" rather than before... – R.. GitHub STOP HELPING ICE Jun 30 '13 at 00:36
  • @R.. for code snippet #1 this interpretation would make also `struct bla {long a; float b;} x; float y = 0; x = *(struct bla *) &y;` valid (assuming alignment is OK) which seems strange, no? – ouah Jun 30 '13 at 00:54
  • @ouah: I'm not sure. An object in C has a well-defined size, so technically you are not just accessing "the object" but a potential object, which may or may not actually exist, partly overlapping with the object... I think that's reason enough for your version to be undefined and mine to *possibly* be defined (I'm still not convinced it is, though.) – R.. GitHub STOP HELPING ICE Jun 30 '13 at 01:31
  • @R..: as I said in my answer, I don't believe this use is intended; very specific circumstances aside (structures in a union with common initial subsequence), C's type system is nominal, and using an exception added for a very specific case (aliasing of pointer-to-struct and pointer-to-member) to introduce structural typing through the backdoor is not 'in the spirit' of the language – Christoph Jun 30 '13 at 08:14
  • @R.. I actually changed my answer. When I wrote the initial version, I didn't have in mind the sentence *an aggregate or union type that includes one*. – ouah Jul 04 '13 at 10:18
0

Not sure it's a proper answer, but what could happen (in your second example) is this:

  1. The compiler defines struct a as an 8-byte object, with padding after the 4 bytes in the array (why? because it can).
  2. You then use tmp.r = *(struct a *)p; which treats p as an address of a struct a (namely, an 8 byte object). It tries to copy the contents of this object into tmp.r, that is, 8 bytes from the address that p is holding. But you're only allowed to read 4 bytes from there.

Implementations do not have to copy padding bytes, but they're allowed to do so.

Omri Barel
  • 9,182
  • 3
  • 29
  • 22
  • This is a good objection, but I'm content to ignore this possibility, since the padding of structure types is well-defined by the ABI used on the implementation and the situation you're considering never happens in real-world ABIs. BTW, whether this issue applies is easily *testable* with `sizeof`. I'm much more concerned about the aliasing issue, which can definitely matter in the real world, and which is not, in principle, testable. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 22:21
0

In your second example

struct a { char r[4]; };

this structure type might have some alignment restrictions. The compiler might decide that struct a is always 4 byte aligned, e.g, such that it always can use a 4 byte aligned read instruction, without looking at the actual address. The pointer p that you receive as an argument to read32 has no such restriction, so

*(struct a*)p;

might cause a bus error.

I notice that this type of argument is a "practical" one.

In point of view of the standard this is UB as soon as (struct a*)p is a conversion to a type with more restrictive alignment requirements.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 1
    While I agree this is a possible issue, it's separate from the aliasing issue I was asking about. If it's possible for the struct to have an alignment requirement, that's both implementation-defined and testable, and the UB from alignment failure would only arise on implementations where the struct has an alignment requirement. On the other hand, if there's an aliasing violation, it's equally undefined on all implementations. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 23:11
  • @R.. well, it just makes your second approach invalid for any type of portable code. And it also invalids the claim from your question, that the first and second are equivalent. – Jens Gustedt Jun 30 '13 at 06:49
-1

From the C standard:

A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned(57) for the pointed-to type, the behavior is undefined.

The resulting pointer in this case is guaranteed to be correctly aligned (because the first member of a struct must be coincident with the struct), so this limitation doesn't apply here. What does apply is additional restrictions on pointer use requiring that access to an object is only via pointers compatible with the "effective type" of the object ... in this case, the effective type of x is int and so it cannot be accessed via a struct pointer.

Note that, contrary to some claims, the conversion between pointer types is not limited to round trip use. The standard says that the pointer can be converted, with a proviso as to when such conversions result in undefined behavior. Elsewhere it gives the semantics of the use of pointers of the resulting type. The round-trip guarantees in the standard are additional specifications ... things that you can count on that you could not if not explicitly stated:

Otherwise, when converted back again, the result shall compare equal to the original pointer.

This specifies a guarantee about the round trip, it is not a limitation to a round trip.

However, as noted, the "effective type" language is a limitation on the use of the pointer resulting from a conversion.

Jim Balter
  • 16,163
  • 3
  • 43
  • 66
  • This citation does not deal with accessing a value through the pointer, just successful conversion, e.g. for round-trip use. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 21:59
  • 1
    @R.. No, it's not just for round-trip use. The standard says it may be converted; it puts no further restrictions on use of the resulting pointer. Compare "A pointer to void may be converted to or from a pointer to any incomplete or object type" -- no restriction on the result, and we use this freely. – Jim Balter Jun 29 '13 at 22:02
  • @R.. Note also "A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined." -- The standard does not explicitly say that you *can* call with the resulting pointer, it only says when you can't ... uses not explicitly labeled as undefined are defined by standard semantics of pointers of that type. – Jim Balter Jun 29 '13 at 22:07
  • 1
    how does this address violation of effective typing rules? Yes, the pointer will be valid, but dereferencing it will still be UB – Christoph Jun 29 '13 at 22:18
  • @JimBalter: The restrictions on usage are in C11 6.5, which deals with the "aliasing rules" (rules about the type of an lvalue that may be used to access an object). Just because you can legally convert the pointer does not mean you can use the lvalue obtained by dereferencing it to access an object. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 22:19
  • @R.. You're changing the subject from round trip, which I addressed and on which you are clearly wrong, to something else. You can dereference the lvalue if you haven't violated any of the limitations, such as "not correctly aligned for the pointed-to type". If there is such a limitation in C11 6.5 that applies here, you have yet to state it. – Jim Balter Jun 29 '13 at 22:24
  • @JimBalter: You can only access an object via an lvalue whose type is compatible with the effective type of the object, or which meets one of several other conditions. These conditions are laid out in C11 6.5p7 or the essentially identical text in C99 which is at a slightly different location. – R.. GitHub STOP HELPING ICE Jun 29 '13 at 22:27
  • @JohannesSchaub-litb True but not relevant to anything I wrote ... we do dereference pointers that have been converted *from* void pointers. What *is* relevant is what R.. and Christoph have pointed out, which are the sorts of restrictions I wrote about. I will update accordingly. – Jim Balter Jun 29 '13 at 22:39