Is it always the case that sizeof(T) >= alignof(T) for all object types T?
Asked Answered
A

4

51

For any object type T is it always the case that sizeof(T) is at least as large as alignof(T)?

Intuitively it seems so, since even when you adjust the alignment of objects like:

struct small {
  char c;
};

above what it would normally be, their "size" is also adjusted upwards so that the relationship between objects in an array makes sense while maintaining alignment (at least in my testing). For example:

struct alignas(16) small16 {
  char c;
};

Has both a size and alignment of 16.

Arbogast answered 27/9, 2017 at 21:23 Comment(26)
For what architecture? What about for char x[12] or int y[200] or a struct { int a; char b; float c; }?Wiredraw
@Wiredraw - for any architecture really. I'm asking what the standard guarantees or implies. Clearly in those two examples sizeof (12 and I*200, respectively) is larger than alignof (1 and I respectively), where I is sizeof(I).Arbogast
There's no guarantee these will be the same. Older x86 processors didn't care where you put values. Newer ones are much more fussy. There's a lot of history here.Wiredraw
@Wiredraw - this isn't a question about x86 processors. When you say "these will be the same", what are "these"?Arbogast
That sizeof(T) == alignof(T) for any given combination of T and architecture. The alignment value might be larger or smaller, it's impossible to say with any certainty. If you have a smaller list of types and one architecture you can run tests to find out.Wiredraw
Well, if you start with an assumption such as "float is 4 bytes, sizeof( float ) returns 4, but the system architecture requires that a float be on an 8-byte boundary", where does that lead? Offhand, I think that means an array of float would be broken.Xyloid
@Wiredraw Of course is it not the case that sizeof(T) == alignof(T) in general. It is trivial to show that for example struct S { char a,b; }; usually has size 2 and alignof 1. My question is about >= not == though...Arbogast
In the LP64 data model sizeof(long double)==8 while alignof(long double)==16 depending on your compiler and OS combination. Likewise, sizeof(long long)==8 while alignof(long long)==4.Wiredraw
@Wiredraw - do you have a concrete example? It seems like it would be impossible to properly even allocate an array of long double since in particular something like malloc(N * sizeof(long double)) would allocate insufficient memory, and array indexing with sizeof() would also be broken.Arbogast
You'll have to do some testing here and find out, as you're getting into some gritty details. I'm not sure if GCC aligns that way out of preference or as a hard requirement.Wiredraw
@Wiredraw In the LP64 data model sizeof(long double)==8 Not on my Centos 6 machine. sizeof( long double == alignof( long double ) == 16.Xyloid
@AndrewHenle gcc or clang? To do a deeper investigation here a test script would be useful.Wiredraw
@Wiredraw gcc version 4.4.7 20120313 (Red Hat 4.4.7-18) (GCC)Xyloid
You of course can't get a warranty. But any processor designer that designed one where an array of T needed to intentionally leave extra space between the array elements did not hold on to his job for very long unless his first name was Seymour.Yeseniayeshiva
@HansPassant But suppose that such a designer existed, or for some other reason the alignment requirement was greater than the amount of data (eg, you want cache-aligned or page-aligned variables). Would the compiler increase sizeof(T) to match alignof(T)?Roccoroch
Find the guy first. Call him Bob.Yeseniayeshiva
@HansPassant Suppose I go back in time and prevent anybody from hiring any of these Bobs. That still leaves non-processor reasons for over-aligned types.Roccoroch
@hans - even in the world of really weird designs, I don't see how padding could be allowed by the standard. Isn't it required, for example that sizeof(T[10]) == 10 * sizeof(T)? That would seem to preclude padding. If it's not guaranteed, then we have a problem since almost any non-trivial C++ program would seem to assume this somewhere.Arbogast
@Arbogast As far as I’ve been able to determine, arrays can be larger than (number of elements) * sizeof(T), but no compiler does that and all the extra padding has to go at the end or subscripting won’t work. That still means arrays of types with alignment greater than size would be problematic.Roccoroch
... so while at the hardware level you might have weird stuff like 10 byte values that need to be 16-byte aligned, from the C++ point of view I think this must simply have sizeof 16. That's why I was asking @Wiredraw for an example of a platform where sizeof(long double) is less than alignof(long double) - since it seems impossible for this to be a compliant implementation!Arbogast
@daniel that would break all sorts of common idioms like sizeof(a)/sizeof(a[0]) to calculate array sizes, as well as any use of malloc() or operator new to allocate arrays. It probably deserves a separate question though.Arbogast
It’s already the case that operator new[] can allocate more memory than that, to allow for some bookkeeping. Since all the unread padding bytes would be at the end, mallocing something smaller would be fine. I agree that the first would break, which is probably part of why nobody has built a compiler that way (the rest of the reason being that there would be little if any point).Roccoroch
@daniel, sure but I'm not talking about invisible implementation details such as how many extra bytes memory allocation routines allocate under the cover. It's obvious there is going to be overhead, but it's invisible to the programmer. What I'm asking is whether sizeof(T[10]) can ever be unequal to 10 * sizeof(T). That's the programmer visible size of an array and it's the crux of the argument in the top answer below.Arbogast
I asked about the array size question over here..Arbogast
Please consider incorporating some of the clarifications and responses provided in the comments into the question proper.Satanic
@cody - I think the question is clear as it stands. The comments are mostly just discussions about possible answers, or in the case of of the first comment, something I consider obvious from the question and tags.Arbogast
J
38

At least in standard C++, for anything you can make an array of (with length > 1), this will have to be true. If you have

Foo arr[2];

and alignof(Foo) > sizeof(Foo), then arr[0] and arr[1] can't both be aligned.

As Zalman Stern's example shows, though, at least some compilers will allow you to declare a type with alignment greater than its size, with the result that the compiler simply won't let you declare an array of that type. This is not standards-compliant C++ (it uses type attributes, which are a GCC extension), but it means that you can have alignof(T) > sizeof(T) in practice.

The array argument assumes sizeof(Foo) > 0, which is true for any type supported by the standard, but o11c shows an example where compiler extensions break that guarantee: some compilers allow 0-length arrays, with 0 sizeof and positive alignof.

Jamieson answered 27/9, 2017 at 21:39 Comment(6)
I'm not sure there are any cases where this argument doesn't apply. Maybe an object type you can't make instances of at all, or a type so huge you can't fit two of them in the same address space?Jamieson
Well Hans in the comments on the question seems to imply that it is allowed that sizeof(Foo[2]) > 2*sizeof(Foo) which would break your argument. I don't know if I believe it though.Arbogast
@Jamieson You can't declare an array of an abstract class, or even a single instance. So its sizeof is not very useful.Ablate
@curiousguy: Well, you can have an instance of an abstract class as a base class subobject of an instance of a concrete subclass. I don't think there's any way to get an array of them, though, so that seems to be an example where the array argument fails even without compiler extensions.Jamieson
"I don't know whether that's strictly conformant behavior" - it's not. Zalman's example uses a type attribute, __attribute__ ((aligned (64))). Type attributes are a GCC extension, and not part of the C or C++ standards. I edited to clarify - hope that's ok.Touchdown
@sleske: I should have phrased that differently: there are restrictions on what kinds of extensions a conforming implementation is allowed to have, and I'm not sure this extension's behavior is within the restrictions. I think it's allowed, but I'm not sure.Jamieson
C
17
#include <iostream>

typedef double foo __attribute__ ((aligned (64)));
alignas(64) double bar;
double baz __attribute__ ((aligned (64)));

int main(int argc, char *argv[]) {
    std::cout << "foo sizeof: " << sizeof(foo) << " alignof: " << alignof(foo) << "\n";
    std::cout << "bar sizeof: " << sizeof(bar) << " alignof: " << alignof(decltype(bar)) << "\n";
    std::cout << "baz sizeof: " << sizeof(baz) << " alignof: " << alignof(decltype(baz)) << "\n";
}

Compile with:

clang++ -std=c++11 alignof_test.cpp -o alignof_test && ./alignof_test

Output:

foo sizeof: 8 alignof: 64
bar sizeof: 8 alignof: 8
baz sizeof: 8 alignof: 8

So strictly speaking, no, but the above argument re: arrays has to be preserved.

Coxa answered 27/9, 2017 at 22:7 Comment(8)
Try foo arr[2]; std::cout << sizeof( arr ) << "\n";Xyloid
error: alignment of array elements is greater than element size, so you just can't make an array of these. I wonder if that's strictly conformant behavior.Jamieson
Updated to include use of the attribute version on an actual decl, which indeed does collapse. Points stands however that you can make types that violate the constraint, which might be important to know if one were depending on this in e.g. metaprogramming.Coxa
If you declare an object of type foo, it keeps the 64-byte alignof. It's interesting how the aligned attribute interacts differently with alignof if you attach it to a typedef and use the typedef compared to if you attach it to a variable declaration directly.Jamieson
Is __attribute__ ((aligned)) standard C++? Can you do the same trick with alignof?Arbogast
no, wait, it looks like baz also has 64-byte alignof, but decltype interacts weirdly and removes the attribute, changing the alignment.Jamieson
__attribute__ is a GCC extension. I haven't found a way to get the same results with alignof; I can create an object with alignof(obj) > sizeof(obj), but not a type.Jamieson
Yeah @Jamieson - it seems the use of __attribute__ ((aligned)) allows you to use typedef to create a weird kind of half-type, like foo. A typedef should normally be a pure alias with the same characteristics as the aliased type, but here it's the aliased type + additional alignment information, so it's something like a new type. However, it isn't a new type in the C++ type system (e.g,. you can't overload a function taking a double with one taking a foo), and name mangling will result in double being used and there are various other ways to "lose" the alignment...Arbogast
F
10

According to the c++ 11 standard that introduced the alignof operator, sizeof is defined as following (see 5.3.3 expr.sizeof):

The sizeof operator yields the number of bytes in the object representation of its operand

Whereas alignof definition is (see 5.3.6 expr.alignof):

An alignof expression yields the alignment requirement of its operand type.

Since the defintion of alignof specifies a requirement, possibly made by the user, rather than a specification of the language, we can manipulate the compiler:

typedef uint32_t __attribute__ ((aligned (64))) aligned_uint32_t;
std::cout << sizeof(aligned_uint32_t) << " -> " << alignof(aligned_uint32_t);
// Output: 4 -> 64

Edited

As others have pointed out, such types cannot be used in arrays, e.g trying to compile the following:

aligned_uint32_t arr[2];

Results in error: alignment of array elements is greater than element size

Since arrays require the specified type to conform with the condition: sizeof(T) >= alignof(T)

Finegrain answered 27/9, 2017 at 22:22 Comment(2)
Yes, but note that aligned_uinit32_t is not a type, despite the way you've named it. It's declaring a stack variable with different alignment. I know that in such a case the alignment is what you asked for and sizeof doesn't change. The question is whether you can make a type with this behavior wherever it is used.Arbogast
@BeeOnRope, you are right, I accidentally posted intermediate code. I have adjusted my answer to show that a type can be declared in such a way.Finegrain
L
7

Many compilers allow arrays of size 0. The alignment remains the same as the alignment of the sole element.

(Among other things, this is useful for forcing a particular alignment in cases when you can't use a bitfield)

Lumisterol answered 28/9, 2017 at 4:26 Comment(2)
Oh yeah, 0-length arrays are a thing on some compilers. Having types of size 0 screws with a lot of things.Jamieson
@user2357112: Generalizing language concepts to size zero adds a few tricky corner cases, but allows programs to eliminate many others. For example, if padding may or may not be needed at a certain place in a structure, allowing an array whose size might be zero would allow the padding to be included or not based upon compiler computations.Maxiemaxilla

© 2022 - 2024 — McMap. All rights reserved.