Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Swift guarantee the storage order of fields in classes and structs?

Tags:

swift

In C, the order in which you define fields in a struct is the order in which they will be instantiated in memory. Taking into account memory alignment, the following struct will have a size of 8 bytes in memory as shown, but only 6 bytes if the fields are reversed as there doesn't need to be any alignment padding.

struct s {
    int32_t a;
    /* 2 bytes of padding to align a 64 bit integer */
    int64_t b;
}

This ordering guarantee is present in C structs, C++ classes (and structs), and Objective-C classes.

Is the order of storage similarly guaranteed for fields in Swift classes and structs? Or (given that the language doesn't support pointers in the same way as the others listed), does the compiler optimally re-arrange them for you at compile-time?

like image 517
Ephemera Avatar asked Sep 03 '16 02:09

Ephemera


People also ask

What are the main differences between classes and structs in Swift?

In Swift, structs are value types whereas classes are reference types. When you copy a struct, you end up with two unique copies of the data. When you copy a class, you end up with two references to one instance of the data. It's a crucial difference, and it affects your choice between classes or structs.

Why structure is faster than class in Swift?

Since struct instances are allocated on stack, and class instances are allocated on heap, structs can sometimes be drastically faster.

What situation you will use structs and classes in Swift?

Use structs when working with a few, relatively simple data values. Use classes to access Objective-C runtime. Use classes to control an object's identity. Use structs when there is no need to control an object's identity.

How are structs stored in memory?

Struct members are stored in the order they are declared. (This is required by the C99 standard, as mentioned here earlier.) If necessary, padding is added between struct members, to ensure that the latter one uses the correct alignment. Each primitive type T requires an alignment of sizeof(T) bytes.


2 Answers

Yes, the order of the struct elements in memory is the order of their declaration. The details can be found in Type Layout (emphasis added). Note however the use of "currently", so this may change in a future version of Swift:

Fragile Struct and Tuple Layout

Structs and tuples currently share the same layout algorithm, noted as the "Universal" layout algorithm in the compiler implementation. The algorithm is as follows:

  • Start with a size of 0 and an alignment of 1.
  • Iterate through the fields, in element order for tuples, or in var declaration order for structs. For each field:
    • Update size by rounding up to the alignment of the field, that is, increasing it to the least value greater or equal to size and evenly divisible by the alignment of the field.
    • Assign the offset of the field to the current value of size.
    • Update size by adding the size of the field.
    • Update alignment to the max of alignment and the alignment of the field.
  • The final size and alignment are the size and alignment of the aggregate. The stride of the type is the final size rounded up to alignment.

The padding/alignment is different from C:

Note that this differs from C or LLVM's normal layout rules in that size and stride are distinct; whereas C layout requires that an embedded struct's size be padded out to its alignment and that nothing be laid out there, Swift layout allows an outer struct to lay out fields in the inner struct's tail padding, alignment permitting.

Only if a struct is imported from C then it is guaranteed to have the same memory layout. Joe Groff from Apple writes at [swift-users] Mapping C semantics to Swift

If you depend on a specific layout, you should define the struct in C and import it into Swift for now.

and later in that discussion:

You can leave the struct defined in C and import it into Swift. Swift will respect C's layout.

Example:

struct A {
    var a: UInt8 = 0
    var b: UInt32 = 0
    var c: UInt8 = 0
}

struct B {
    var sa: A
    var d: UInt8 = 0
}

// Swift 2:
print(sizeof(A), strideof(A)) // 9, 12
print(sizeof(B), strideof(B)) // 10, 12

// Swift 3:
print(MemoryLayout<A>.size, MemoryLayout<A>.stride) // 9, 12
print(MemoryLayout<B>.size, MemoryLayout<B>.stride) // 10, 12

Here var d: UInt8 is layed out in the tail padding of var sa: A. If you define the same structures in C

struct  CA {
    uint8_t a;
    uint32_t b;
    uint8_t c;
};

struct CB {
    struct CA ca;
    uint8_t d;
};

and import it to Swift then

// Swift 2:
print(sizeof(CA), strideof(CA)) // 9, 12
print(sizeof(CB), strideof(CB)) // 13, 16

// Swift 3:
print(MemoryLayout<CA>.size, MemoryLayout<CA>.stride) // 12, 12
print(MemoryLayout<CB>.size, MemoryLayout<CB>.stride) // 16, 16

because uint8_t d is layed out after the tail padding of struct CA sa.

As of Swift 3, both size and stride return the same value (including the struct padding) for structures imported from C, i.e. the same value as sizeof in C would return.

Here is a simple function which helps to demonstrate the above (Swift 3):

func showMemory<T>(_ ptr: UnsafePointer<T>) {
    let data = Data(bytes: UnsafeRawPointer(ptr), count: MemoryLayout<T>.size)
    print(data as NSData)
}

The structures defined in Swift:

var a = A(a: 0xaa, b: 0xbbbbbbbb, c: 0xcc)
showMemory(&a)    // <aa000000 bbbbbbbb cc>

var b = B(sa: a, d: 0xdd)
showMemory(&b)    // <aa000000 bbbbbbbb ccdd>

The structures imported from C:

var ca = CA(a: 0xaa, b: 0xbbbbbbbb, c: 0xcc)
showMemory(&ca)   // <aa000000 bbbbbbbb cc000000>

var cb = CB(ca: ca, d: 0xdd)
showMemory(&cb)   // <aa000000 bbbbbbbb cc000000 dd000000>
like image 68
Martin R Avatar answered Oct 06 '22 17:10

Martin R


It seems that the order is guaranteed.

Given the following struct:

struct s {
    var a: UInt32
    var c: UInt8
    var b: UInt64
}

sizeof(s) // 16

Alignment is still happening. There's a lost 8 bits there between c and b.

Of course, it's not clear whether these bits are actually between c & b or just tacked at the end... until we rearrange:

struct s {
    var a: UInt32
    var b: UInt64
    var c: UInt8
}

sizeof(s) // 17

I believe this behavior matches the other languages you mentioned in your question.

like image 28
nhgrif Avatar answered Oct 06 '22 17:10

nhgrif