We have a number of C++ PRs open around problems with code like this:

  struct S {
    void f();
    virtual void g();
  };

  typedef __attribute__((...)) struct S T;

If the attribute makes any substantive change to S (e.g., changes its size, alignment, etc.) then bad things happen. For example, the member functions of "S" have expectations about the layout of "S" that are not satisfied if they are called with a "T". Depending on the attribute and circumstances, we do all manner of bad things, including ICE, generate wrong code, etc.

For a while now, I've been promising to propose semantics for these constructs. Here is a sketch of the semantics that I think we should have. (I say a sketch because I have not attempted to write standardese.)

All attributes must be classified as either "semantic" or "non-semantic" attributes. A "semantic" attribute is one which might affect code-generation in any way. A "non-semantic" attribute cannot affect code-generation. For example, "used" and "deprecated" are non-semantic attributes; there is no way to observe, by looking at an object file, whether or not a class has been marked with one of these attributes. In contrast, "packed" is a semantic attribute; the size of a class is different depending on whether or not it is "packed".

Any attribute may be applied at the point of definition of a class. These attributes (whether semantic or non-semantic) apply to the class. For example, if the class is packed, then the member functions expect the "this" pointer to point to the packed class.

A typedef declaration which adds only non-semantic attributes is always valid. As with other typedefs, the typedef declaration creates a new name for an existing type. The type referred to by that name is the same type as the original type. However, the *name* has additional properties, implied by the (non-semantic) attributes. For example, using a "deprecated" name for a type results in a deprecation warning. But, a function declared to take a parameter with the non-deprecated name may be passed a parameter with the "deprecated" name.

A typedef declaration which adds semantic attributes to a class type, other than POD classes with no explicitly declared members other than data members, to arrays of such classes, to arrays of such arrays, etc., is invalid. (POD-ness alone is not satisfactory, as PODs may contain function members, and I think dealing with static data members and typedef members is not worth the trouble.)

A typedef declaration which adds semantic attributes to a POD class type with no function members is valid, but creates an entirely new type, different from all other types except others formed by adding the same combination of semantic attributes to the same original class type. In the example above, if the typedef adds a semantic attribute, you may not pass an "S" to a function expecting a "T" or vice versa. Neither may you pass an "S*" to a function expecting a "T*", without an explicit reinterpret_cast. The name of "T", for linkage purposes, is "T", and there is no implicit "T::S" type; instead, however, there is a "T::T" type. (Various consequences follow; for example, typeid(T) gives you a type_info object that indicates that the name of the type is "T".) References to the original type from within the types of the members of the class still refer to the original class. For example, in:

  struct S {
    char c;
    S* next;
  };
  typedef __attribute__((packed)) S T;

the data member T::next has type "S*", not "T*".

A typedef declaration which adds semantic attributes to a non-class type is valid, but again creates an entirely new type. (We might want a special exception to the "entirely new type" rule for the "mode" attribute, declaring that "typedef __attribute__((mode(DI))) int LL" is equivalent to "typedef long long LL;" on platforms where "long long" has DImode.) So,

  typedef S* P;
  typedef __attribute__((...)) P Q;

creates a type "Q" that is incompatible with "S*" if the attribute is semantic. However, the type of "*Q" is still "S". It is invalid to do anything that would require either type_info or a mangled name for "Q", including using it as an argument to typeid, thowing an exception of a type involving "Q", or declaring a template to take a parameter of a type involving "Q". (We could relax some of these restrictions in future, if we add mangling support for attributes.)

A variable declaration involving attributes, like:

  __attribute__((...)) S v;

is treated as syntactic sugar for:

  typedef __attribute__((...)) S T;
  T v;

where T is some invented type name different from all others in the program.

For example given:

  __attribute__((packed)) S v;

the type of "&v" is "__attribute__((packed)) S *", and cannot be passed to a function expecting an "S*", but can of course be passed to a function expecting an "__attribute__((packed)) S *", or a typedef for such a type.

Thoughts?

--
Mark Mitchell
CodeSourcery
[EMAIL PROTECTED]
(650) 331-3385 x713

Reply via email to