5

While trying to debug a problem I'm having using Speex, I noticed that it (well, not just Speex, but some example code as well) does the following:

  • Return a pointer to EncState from an initialization function
  • Cast that pointer to a void pointer
  • Store the void pointer
  • (elsewhere)
  • Cast the void pointer to a pointer to pointer to SpeexMode
  • Dereference the pointer

It so happens that the definition of EncState starts with a field of type SpeexMode *, and so the integer values of a pointer to the first field and a pointer to the struct happen to be the same. The dereference happens to work at runtime.

But... does the language actually allow this? Is the compiler free to do whatever it wants if it compiles this? Is casting a struct T* to a struct C* undefined behavior, if T''s first field is a C`?

Roberto Caboni
  • 7,252
  • 10
  • 25
  • 39
Craig Gidney
  • 17,763
  • 5
  • 68
  • 136
  • Also a duplicate of [Struct pointer compatibility](http://stackoverflow.com/questions/8702713/struct-pointer-compatibility) – netcoder Jan 24 '13 at 21:25
  • It is technically UB, yes, because of the strict aliasing rule. The preferred way is to usually to use a union with members of both types. – netcoder Jan 25 '13 at 15:11
  • @netcoder: The behavior with a union is no more nor less defined than without, since there isn't actually any rule that allows an object of struct or union type to be accessed via lvalue of member type. A compiler that can see that a pointer or lvalue has a fresh association with a struct or union should allow for the possibility of it being used to access the struct or union, but the question of when a compiler sees that is a *quality-of-implementation* issue. Clang and gcc are obtusely blind to anything beyond the bare minimum necessary to make the language usable, but... – supercat Feb 20 '21 at 18:39
  • ...under a sufficiently pedantic reading of the rules as written, almost anything that code does with structs or union members of non-character types invokes UB, but any usable compiler will extend the language by processing constructs like `someAggregate.array[i]` usefully even though the Standard doesn't require it. – supercat Feb 20 '21 at 18:40

2 Answers2

8

From the C11 standard:

(C11 §6.7.2.1.15: "A pointer to a structure object, suitably converted, points to its initial member ... and vice versa. There may be unnamed padding within as structure object, but not at its beginning.")

Which means that the behavior you see is allowed and guaranteed.

pmr
  • 58,701
  • 10
  • 113
  • 156
  • 1
    It does breaks the strict aliasing rule though. – netcoder Jan 24 '13 at 21:46
  • @netcoder True, if it leads to problems in this case is unclear though. – pmr Jan 25 '13 at 10:35
  • +1 Concise and clear answer ... I might add as a comment that you can find many many examples of this kind of typecasting in the GObject framework – Rerito Jan 26 '13 at 10:06
  • The pointer may have the same address as the object, but that doesn't imply permission to use the pointer to actually access the object. If the Standard would fully specify the behavior of a program in the absence of the N1570 6.5p7 "strict aliasing rule", but fails to meet the requirements of that section, then as far as the Standard is concerned the program invokes Undefined Behavior. Compilers that are written for paying customers will process cases those customers would find useful without regard for whether the Standard would require it, but ... – supercat Feb 20 '21 at 18:47
  • 1
    ...compiler vendors who aren't beholden to paying customers have successfully spread the myth that programmers should be required to jump through hoops to appease them. – supercat Feb 20 '21 at 18:48
  • @netcoder: It does not violate the strict aliasing rule. The so-called strict aliasing rule in C 2018 6.5 7 governs only the type of an lvalue expression used to access an object, not how a pointer used to form the expression was obtained. If `p` is pointer to type `C`, and there is a `C` object at `p`, then `*p` accesses the `C` object using an lvalue expression that is correct for the `C` type according to 6.5 7. The fact that `p` was obtained by converting a pointer to a `T` object to a pointer to a `C` object is irrelevant. – Eric Postpischil Feb 26 '21 at 15:07
-2

Every version of the Standard has treated support for many aliasing constructs as a Quality of Implementation issue, since it would have been essentially impossible to write rules which supported all useful constructs, didn't block any useful optimizations, and could be supported by all compilers without significant rework. Consider the following function:

struct foo {int length; int *dat; };

int test1(struct foo *p)
{
  int *ip = &p->length;
  *ip = 2;
  return p->length;      
}

I think it's rather clear that any quality compiler should be expected to handle the possibility that an object of type struct foo might be affected by the assignment to *ip. On the other hand, consider the function:

void test2(struct foo *p)
{
    int i;
    for (i=0; i < p->length; i++)
        p->dat[i] = 0;
}

Should a compiler be required to make allowances for the possibility that writing to p->dat[i] might affect the value of p->length, e.g. by reloading the value of p->length after at least the first iteration of the loop?

I think some members of the Committee may have intended to require that compilers make such allowance, but I don't think they all did, and the rules as written wouldn't require it since they list the types of lvalue that may be used to access an object of type struct foo, and int is not among them. Some people may think the omission was accidental, but I think it was based on an expectation that compilers would interpret the rule as requiring that objects which are accessed as some particular type in some context be accessed by lvalues which have a visible association with an object of one of the listed types, within that context. The question of what constitutes a "visible association" left as a QoI issue outside the Standard's jurisdiction, but compiler writers were expected to make reasonable efforts to recognize associations when practical.

Within a function like test1, an lvalue of type p is used to derive ip, and p is not used in any other fashion to access p->length between the formation of ip and its last usage. Thus, compilers should have no difficulty recognizing that a store to *ip cannot be reordered across the later read to p->length, even without a general rule giving blanket permission to use pointers of type int* to access int members of unrelated structures. Within test2, however, there is no visible means by which the address of p->length could have been used in the computation of pointer p->dat, and thus it would be reasonable for optimizing compilers intended for most common purposes to hoist the read of p->length before the loop in the expectation that its value won't change.

Rather than making any effort to recognize the types of object from which a pointer is derived, clang and gcc instead opt to behave as though the Standard gives general permission to access struct (but not union!) members using pointers of their types. This is allowable but not required by the Standard (a conforming but garbage quality implementation could process test1 in arbitrary meaningless fashion), but the blindness to pointer derivation needlessly restricts the range of constructs available to programmers, and makes it necessary to forego what should be useful optimizations such as those exemplified by test2().

Overall, the correct answer to almost any question related to aliasing in C is "that's a quality-of-implementation issue". Observations about what clang and gcc do may be useful for people who need to appease the -fstrict-aliasing mode of those compilers, but have little to do with what the Standard actually says.

supercat
  • 77,689
  • 9
  • 166
  • 211