rhynodegreat
rhynodegreat

Reputation: 195

What's the reason for this struct's alignment?

In the Vulkan header vulkan.h there is a struct defined as

typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType                  sType;
    const void*                      pNext;
    VkSwapchainCreateFlagsKHR        flags;
    VkSurfaceKHR                     surface;
    uint32_t                         minImageCount;
    VkFormat                         imageFormat;
    VkColorSpaceKHR                  imageColorSpace;
    VkExtent2D                       imageExtent;
    uint32_t                         imageArrayLayers;
    VkImageUsageFlags                imageUsage;
    VkSharingMode                    imageSharingMode;
    uint32_t                         queueFamilyIndexCount;
    const uint32_t*                  pQueueFamilyIndices;
    VkSurfaceTransformFlagBitsKHR    preTransform;
    VkCompositeAlphaFlagBitsKHR      compositeAlpha;
    VkPresentModeKHR                 presentMode;
    VkBool32                         clipped;
    VkSwapchainKHR                   oldSwapchain;
} VkSwapchainCreateInfoKHR;

I've used the following code to see each field's alignment (Visual Studio 2015)

    std::cout <<
        "sType: " << offsetof(VkSwapchainCreateInfoKHR, sType) << std::endl <<
        "pNext: " << offsetof(VkSwapchainCreateInfoKHR, pNext) << std::endl <<
        "flags: " << offsetof(VkSwapchainCreateInfoKHR, flags) << std::endl <<
        "surface: " << offsetof(VkSwapchainCreateInfoKHR, surface) << std::endl <<
        "minImageCount: " << offsetof(VkSwapchainCreateInfoKHR, minImageCount) << std::endl <<
        "imageFormat: " << offsetof(VkSwapchainCreateInfoKHR, imageFormat) << std::endl <<
        "imageColorSpace: " << offsetof(VkSwapchainCreateInfoKHR, imageColorSpace) << std::endl <<
        "imageExtent: " << offsetof(VkSwapchainCreateInfoKHR, imageExtent) << std::endl <<
        "imageArrayLayers: " << offsetof(VkSwapchainCreateInfoKHR, imageArrayLayers) << std::endl <<
        "imageUsage: " << offsetof(VkSwapchainCreateInfoKHR, imageUsage) << std::endl <<
        "imageSharingMode: " << offsetof(VkSwapchainCreateInfoKHR, imageSharingMode) << std::endl <<
        "queueFamilyIndexCount: " << offsetof(VkSwapchainCreateInfoKHR, queueFamilyIndexCount) << std::endl <<
        "pQueueFamilyIndices: " << offsetof(VkSwapchainCreateInfoKHR, pQueueFamilyIndices) << std::endl <<
        "preTransform: " << offsetof(VkSwapchainCreateInfoKHR, preTransform) << std::endl <<
        "compositeAlpha: " << offsetof(VkSwapchainCreateInfoKHR, compositeAlpha) << std::endl <<
        "presentMode: " << offsetof(VkSwapchainCreateInfoKHR, presentMode) << std::endl <<
        "clipped: " << offsetof(VkSwapchainCreateInfoKHR, clipped) << std::endl <<
        "oldSwapchain: " << offsetof(VkSwapchainCreateInfoKHR, oldSwapchain) << std::endl <<
        std::endl;

And got these results

sType: 0
pNext: 8
flags: 16
surface: 24
minImageCount: 32
imageFormat: 36
imageColorSpace: 40
imageExtent: 44
imageArrayLayers: 52
imageUsageFlags: 56
imageSharingMode: 60
queueFamilyIndexCount: 64
pQueueFamilyIndices: 72
preTransform: 80
compositeAlpha: 84
presentMode: 88
clipped: 92
oldSwapchain: 96

Between the fields flags and surface there is an 8 byte gap, even though flags has an underlying type of uint32_t. The same is true with the fields queueFamilyIndexCount and pQueueFamilyIndices. Why do flags and queueFamilyIndexCount take up 8 bytes when they are only 4 bytes wide and every other field of type uint32_t takes up only 4 bytes? Is there something special about the memory alignment requirements at those offsets?

Upvotes: 2

Views: 622

Answers (2)

tmlen
tmlen

Reputation: 9100

All type have a size sizeof(T), and an alignment requirement alignof(T). Instances of it always need to be laid out in memory at addresses that are multiple of alignof(T).

In a struct, the compiler automatically inserts padding between the elements such that this requirement is fulfilled for all its elements, relative to the start address of the struct (i.e. for the offsetof values). This is empty space between subsequent struct elements.

It also sets the alignof of the whole struct, such that all its elements will be correctly aligned in memory. For example on a typical 64 bit platform:

struct A {
    char c;    // sizeof(char) == 1; alignof(char) == 1
    int* ptr;  // sizeof(char) == 8; alignof(char) == 8
}

Pointers have a size of 8 byte (64 bit), and also an alignment requirement of 8 byte. In this struct there will be 7 bytes of padding between c and ptr, such that offsetof(A, c) == 0 and offsetof(A, ptr) == 8. And alignof(A) == 8. Whenever an instance A a is created (on stack or on heap), &a % 8 == 0, and so &(a.ptr) % 8 == 0.


This is done because some CPU instructions (for example SIMD vector instructions) require their operands to be aligned at word boundaries, i.e. multiples of 8 (or 4 for 32 bit) in memory. Without the proper alignment these instructions could not be directly used and the program would become slower.


In this example VkSurfaceKHR and pQueueFamilyIndices are pointers with alignof == 8.

Upvotes: 1

Nicol Bolas
Nicol Bolas

Reputation: 474456

VkSurfaceKHR is a non-dispatchable handle. Which, by Vulkan's definitions, is a 64-bit integer. And therefore it must have 8-byte alignment.

When structures are being laid out, the compiler will ensure that each member gets the alignment that the type requires. If surface had come immediately after flags, it would not have 8-byte alignment. Thus, the compiler inserts 4 bytes of padding between the two.

Upvotes: 4

Related Questions