# Nested C structs, alignment, and "do not copy fields over"

**URL:** <https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191>\
**Category:** General Usage\
**Created:** [December 26, 2024, 5:47pm UTC](https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191 "2024-12-26T17:47:03Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![luke-kiernan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-kiernan/32/214309_2.png) [@luke-kiernan](https://discourse.julialang.org/u/luke-kiernan)\
**Post date:** [December 26, 2024, 5:47pm UTC](https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191/1 "2024-12-26T17:47:04Z")

</div>

I’m writing a Julia interface for a C library. I have two nested C-style structs, where the inner is used by-value. Thes [docs](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#Struct-Type-Correspondences) say “declare an `isbits` struct,” but the inner C struct stores pointers, so that isn’t an option. They also say

> it is imperative that you do not attempt to manually copy the fields over, as this will not preserve the correct field alignment.

Luckily, I find myself in a situation where the boundaries between the two structs happen to be 8-byte aligned. For example,

```julia
mutable struct Inner
   p::Ptr{Cint}
   b::Cchar
end
mutable struct Outer
   a::Inner
   q::Ptr{Cint}
end
mutable struct Combined
   P::Ptr{Cint} # 8 bytes: offset 0
   B::Cchar # 1 byte: offset 8
   Q::Ptr{Cint} # 8 bytes: offset 16
end

```

The first field of the inner struct (`P`) and the first field after the inner struct (`Q`) in `Combined` happen to be 8-byte aligned. So (apparently) the memory layout matches the corresponding nested C struct. To be safe, I can add guardrails: `@assert fieldoffset(Combined, P) % 8 == 0 && field offset(Combined, Q) % 8 == 0`. Is there still a good reason why I shouldn’t use `Combined`?

On the other hand, are there better ways to achieve my aims? I’m making these structs to pass them into `@ccall`, so I don’t have a lot of flexibility. I must use pointers, and the memory layout must match the C, even if it’s inconvenient.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [December 26, 2024, 6:00pm UTC](https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191/2 "2024-12-26T18:00:15Z")

</div>

Do you intend to use `Inner`/`Outer` in your code on the julia side, or are they purely for FFI? If the latter, you can make `Inner` immutable and the alignments should match:

```julia-repl
julia> struct Inner
           p::Ptr{Cint}
           b::Cchar
       end

julia> isbitstype(Inner)
true

julia> mutable struct Outer # mutable to get a stable pointer for FFI
           a::Inner
           q::Ptr{Cint}
       end

julia> mutable struct Combined
          P::Ptr{Cint} # 8 bytes: offset 0
          B::Cchar # 1 byte: offset 8
          Q::Ptr{Cint} # 8 bytes: offset 16
       end

julia> sizeof(Outer)
24

julia> sizeof(Combined)
24

julia> Base.padding(Outer)
svec(Base.Padding(16, 7))

julia> Base.padding(Combined)
svec(Base.Padding(16, 7))

 # q in Outer
julia> fieldoffset(Outer, 2)
0x0000000000000010

 # q in Combined
julia> fieldoffset(Combined, 3)
0x0000000000000010

# b in Inner
julia> fieldoffset(Inner, 2)
0x0000000000000008

 # B in Combined
julia> fieldoffset(Combined, 2)
0x0000000000000008

```

---

<div class="post-metadata">

**Author:** ![luke-kiernan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-kiernan/32/214309_2.png) [@luke-kiernan](https://discourse.julialang.org/u/luke-kiernan)\
**Post date:** [December 26, 2024, 6:17pm UTC](https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191/3 "2024-12-26T18:17:35Z")

</div>

> Do you intend to use `Inner` /`Outer` in your code on the julia side, or are they purely for FFI?

Regular usage will involve reading and writing the buffer stored at `p`. I could maybe have it such that once an `Inner` object is created, the pointed-to contents of `p` aren’t modified. But that would be inconvenient and probably bad performance-wise (more allocations).

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [December 26, 2024, 6:22pm UTC](https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191/4 "2024-12-26T18:22:22Z")

</div>

Modifying the contents of the object `p` points to shouldn’t be a problem at all though, as long as it stays alive for the duration of the FFI call (or as long as C has access to that pointer, if it retains the pointer internally). If `Inner` is only used for the FFI part and the actual data lives in some julia-controlled allocation, it should work as intended 🤔

---

<div class="post-metadata">

**Author:** ![luke-kiernan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-kiernan/32/214309_2.png) [@luke-kiernan](https://discourse.julialang.org/u/luke-kiernan)\
**Post date:** [December 26, 2024, 10:06pm UTC](https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191/5 "2024-12-26T22:06:12Z")

</div>

I see now: structs containing pointers can be immutable. The pointer must remain unchanged, but the pointed-to data may be overwritten just fine. The confusion stemmed from running `isbits(Inner)` (`false`), when what I really wanted was `isbitstype(Inner)` (`true`).

Still, it’s inconvenient to be unable to change the pointer. For example, if I want to use `cconvert` and `unsafe_convert` to turn something else into an `Outer`, I ought to allocate `x::Outer` inside `cconvert` then set the pointer (`x.a.p`) inside `unsafe_convert`. (See the docs [here](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#automatic-type-conversion): more detail about my specific use in [this post](https://discourse.julialang.org/t/ccall-with-a-c-struct-containing-a-pointer/124036/7).) But I can’t do that if `Inner` is immutable.

EDIT: There is a workaround, at the cost of performance: I could allocate a large enough buffer inside `cconvert`, then copy the data over inside `unsafe_convert`.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [December 26, 2024, 11:37pm UTC](https://discourse.julialang.org/t/nested-c-structs-alignment-and-do-not-copy-fields-over/124191/6 "2024-12-26T23:37:46Z")

</div>

> [@luke-kiernan](#):
>
> I see now: structs containing pointers can be immutable.

There’s a subtlety with the wording in the docs here - `Ptr{T}` objects themselves are `isbits` just fine, since they are purely defined by their bitpattern (of course, `Ptr` objects refer to some other memory, but for Julia, the `Ptr` and the memory it refers to are not the same thing). The case that doesn’t work is when you have some object that isn’t `isbits`, which ends up being stored as a (transparent) reference in the outer struct. That’s the case for mutable structs or things like arrays or `Ref`s.

Further, the immutable part of a struct only refers to setting the fields of the struct themselves; you can still use the objects stored in those fields as normal (e.g. setting array indices in an array that’s stored in an immutable struct works).

> [@luke-kiernan](#):
>
> I ought to allocate `x::Outer` inside `cconvert` then set the pointer (`x.a.p`) inside `unsafe_convert`. (See the docs [here](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#automatic-type-conversion): more detail about my specific use in [this post](https://discourse.julialang.org/t/ccall-with-a-c-struct-containing-a-pointer/124036/7).) But I can’t do that if `Inner` is immutable.

You can still do that, though you have to “replace” the whole instance of `Inner` in `unsafe_convert` to “set” the pointer. In `cconvert`, you can just create the initial `Inner` with `C_NULL` as the pointer in order to create `Outer`. In practice, this is likely going to end up as just storing the pointer instead of all of `Inner`.
