cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[dcl.align.example] p1 The example should be based on some assumptions #417

Open xmh0511 opened 11 months ago

xmh0511 commented 11 months ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

The combined effect of all alignment-specifiers in a declaration shall not specify an alignment that is less strict than the alignment that would be required for the entity being declared if all alignment-specifiers appertaining to that entity were omitted.

// [Example 1: 
struct alignas(8) S {};
struct alignas(1) U {
  S s;
};  // error: U specifies an alignment that is less strict than if the alignas(1) were omitted.
// — end example]

According to the rule, the original alignment of S must be less than 8, however, the alignment of object types are implementation-defined

An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated.

Another assumption is that we must know the alignment of U should be no less than that of S, which is not specified in the document, which is the subject of https://github.com/cplusplus/CWG/issues/295

jensmaurer commented 11 months ago

I'm hearing that we should document the assumption in the example that empty objects have no alignment restrictions.

For the second part (alignment of U cannot be no less than that of S), I think that follows from the normative machinery in the standard. Maybe we can add a note.

xmh0511 commented 11 months ago

For the second part (alignment of U cannot be no less than that of S), I think that follows from the normative machinery in the standard. Maybe we can add a note.

It is not always true. Consider the subobject and its complete object are not pointer-convertible, the complete object can be allocated at an address where the subobject's alignment is not satisfied and the implementations can insert padding bits such that the subobject will be allocated at an address where the alignment is satisfied.

When applied to a class, the result is the number of bytes in an object of that class including any padding required for placing objects of that type in an array.

Which is implementation-defined.

jensmaurer commented 11 months ago

No.

sizeof(T) is consistent throughout your program; it doesn't change from one occurrence of T to the next.

Similarly, the subobject layout must be consistent throughout your program, too (otherwise, pointer-to-members wouldn't work).

While the implementation can add padding, this padding needs to be consistent throughout the program, too. (Meaning two objects of the same type have the same internal padding.)

Since the user can placement-new a "U" everywhere where its alignment is satisfied (and the standard guarantees a workable object then), it would mean that the S contained in U would be sometimes incorrectly aligned.

frederick-vs-ja commented 11 months ago

It is not always true. Consider the subobject and its complete object are not pointer-convertible, the complete object can be allocated at an address where the subobject's alignment is not satisfied and the implementations can insert padding bits such that the subobject will be allocated at an address where the alignment is satisfied.

I think such interpretation is not allowed at least in cases where offsetof is required to work. Otherwise, offsetof would give a value that is not always correct, which doesn't seem conforming.

xmh0511 commented 11 months ago

Inserting padding bits just makes the subobject allocate at the address satisfying the alignment, which does not violate anything.

While the implementation can add padding, this padding needs to be consistent throughout the program, too. (Meaning two objects of the same type have the same internal padding.)

I meant:

struct U{
   int a;
   // padding bits
   U u; // the subobject allocates at the address satisfying the alignment requirement
};
jensmaurer commented 11 months ago

There are two "U"s in your example. Let's pretend the inner "U" is spelled "S".

So, let's assume alignof(U) < alignof(S). For example, alignof(U) == 1 and alignof(S) == 4.

Now, I create a "U" object at address x. You're saying the compiler chooses the padding for that U object so that U::u is properly aligned. Good.

Now, I create another "U" object at address x+sizeof(U)+1. Since alignof(U) == 1, this is properly aligned for a "U" object and all should be well. But the nested U::u object won't be properly aligned anymore; by construction it is misaligned by 1. (The implementation can't choose a different padding for that second U object; see above.)

So, there's a contradiction, which only goes away if alignof(U) >= alignof(S).

xmh0511 commented 11 months ago

There are two "U"s in your example. Let's pretend the inner "U" is spelled "S".

Sorry, I meant "S" as the subobject of U, which U subobject was my typo. From your interpretation, IIUC, which means, given determined padding bits thereof and the alignment of the complete objects, these values should provide that the subobjects of the complete object will allocate at an address satisfying the alignment of the subobject type in every address where the complete object can allocate.

Maybe adding formal wording and some notes could make this meaning more explicit.