Skip to content

repr(C) on MSVC targets does not always match MSVC type layout when ZST are involved #81996

Description

@mahkoh

View all comments

Also see this summary below.

Consider

#![allow(dead_code)]

use std::mem;

#[no_mangle]
pub fn sizeof_empty_struct_1() -> usize {
    #[repr(C)]
    struct EmptyS1 {
        f: [i64; 0],
    }

    // Expected: 4
    // Actual: 0
    mem::size_of::<EmptyS1>()
}

#[no_mangle]
pub fn sizeof_empty_struct_2() -> usize {
    #[repr(C, align(8))]
    struct X {
        i: i32,
    }

    #[repr(C)]
    struct EmptyS2 {
        x: [X; 0],
    }

    // Expected: 8
    // Actual: 0
    mem::size_of::<EmptyS2>()
}

#[no_mangle]
pub fn sizeof_enum() -> usize {
    #[repr(C)]
    enum E {
        A = 1111111111111111111
    }

    // Expected: 4
    // Actual: 8
    mem::size_of::<E>()
}

#[no_mangle]
pub fn sizeof_empty_union_1() -> usize {
    #[repr(C)]
    union EmptyU1 {
        f: [i8; 0],
    }

    // Expected: 1
    // Actual: 0
    mem::size_of::<EmptyU1>()
}

#[no_mangle]
pub fn sizeof_empty_union_2() -> usize {
    #[repr(C)]
    union EmptyU2 {
        f: [i64; 0],
    }

    // Expected: 8
    // Actual: 0
    mem::size_of::<EmptyU2>()
}

and the corresponding MSVC output: https://godbolt.org/z/csv4qc

The behavior of MSVC is described here as far as it is known to me: https://github.com/mahkoh/repr-c/blob/a04e931b67eed500aea672587492bd7335ea549d/repc/impl/src/builder/msvc.rs#L215-L236

Activity

  1. jyn514 commented on Feb 11, 2021

    @jyn514
    Member

    @mahkoh can you reproduce this without no_mangle? no_mangle should really require unsafe, it can cause unsoundness if it overlaps with another linker symbol.

  2. mahkoh commented on Feb 11, 2021

    @mahkoh
    ContributorAuthor
  3. mahkoh commented on Feb 11, 2021

    @mahkoh
    ContributorAuthor

    repr(C) has served two purposes:

    • A representation with a know layout algorithm which is the same across all targets
    • A representation that is compatible with C types

    The first purpose is advertised both in the reference and in stdlib code e.g. in Layout. It is probably used in many other places.

    The second purpose is also advertised in the reference.

    However, these purposes are not compatible as shown above.

  4. jonas-schievink commented on Feb 11, 2021

    @jonas-schievink
    Contributor

    The layout algorithm is not the same across all targets, it is always supposed to be whatever the C ABI mandates on that particular target

  5. jonas-schievink commented on Feb 11, 2021

    @jonas-schievink
    Contributor

    Does this reproduce with clang?

  6. added
    A-FFIArea: Foreign function interface (FFI)
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    O-windows-msvcToolchain: MSVC, Operating system: Windows
    on Feb 11, 2021
  7. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 11, 2021
  8. mahkoh commented on Feb 11, 2021

    @mahkoh
    ContributorAuthor

    The layout algorithm is not the same across all targets, it is always supposed to be whatever the C ABI mandates on that particular target

    The layout algorithms used by the C compilers are not the same. But repr(C) is advertised with a specific layout algorithm that is the same across all targets. Namely in these places

    Does this reproduce with clang?

    Clang contains many bugs in their MSVC-compatible layout algorithm. It should not be used as a reference:

    The output of clang is incompatible with both MSVC and rustc: https://github.com/llvm/llvm-project/blob/661f9e2a92302b1c7140528423fdbfc133a68b41/clang/lib/AST/RecordLayoutBuilder.cpp#L3076-L3087

  9. rylev commented on Feb 12, 2021

    @rylev
    Contributor

    Let's make sure the windows notification group is aware of this:

    @rustbot ping windows

  10. rustbot commented on Feb 12, 2021

    @rustbot
    Collaborator

    Hey Windows Group! This bug has been identified as a good "Windows candidate".
    In case it's useful, here are some instructions for tackling these sorts of
    bugs. Maybe take a look?
    Thanks! <3

    cc @arlosi @danielframpton @gdr-at-ms @kennykerr @luqmana @lzybkr @nico-abram @retep998 @rylev @sivadeilra

  11. rylev commented on Feb 12, 2021

    @rylev
    Contributor

    Assigning P-high as discussed as part of the Prioritization Working Group procedure and removing I-prioritize.

  12. removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 12, 2021
  13. 126 remaining items

  14. briansmith commented on Mar 31, 2025

    @briansmith
    Contributor

    repr(C) actually means "linear layout using the following specific layout algorithm".

    No, I would instead expect repr(c) to product a result that matches the ABI spec, which usually is defined in reference to C.

    To clarify further my positoin, I think that

    • The default assumption should be that #[repr(C)] cannot be used on field-less array structs or an empty array types, since ISO C doesn't support them.
    • If the ABI does specify a representation for a field-less struct or zero-sized array (Windows ABIs don't, and most ABIs I have seen don't) then go ahead and support it in a way that matches the ABI. But in most cases a field-less C/C++-compatible struct will not be a ZST, so you might warn if something looks like a ZST but repr(C) makes it not zero-sized ("warning, this field-less struct isn't zero-sized").
    • If we already support something and that doesn't match the ABI, or that support makes assumptions beyond what the ABI says, and/or where C compilers disagree for a target, then we probably then we should warn the programmer about the mismatch, at least ("warning: this is not compatible with the ABI specification" or "warning: this is not compatible with clang" or whatever.

    Note that the ABI might define a representation for zero-sized arrays but not field-less structs, or vice-versa. The idea that these are somehow similar constructs is a Rust-ism that doesn't naturally apply to C.

    We have a lint to detect where that does not match the native C layout on the system (or cannot even be expressed natively).

    I don't distinguish between "lints" and other kinds of type checks, and I don't know enough to suggest how they should be implemented. I am just suggesting the checks that should exist, independently of how they are implemented

  15. RalfJung commented on Apr 1, 2025

    @RalfJung
    Member

    Note that the examples above don't even involve any field-less structs, they involve 0-length arrays. And apparently those are allowed by MSVC, there's a link in the issue description. So I don't understand why you are focusing on fieldless structs.

    Other than that, warnings have been suggested above, so -- not sure what new point you are intending to bring to this conversation.

  16. briansmith commented on Apr 1, 2025

    @briansmith
  17. RalfJung commented on Apr 1, 2025

    @RalfJung
    Member

    I am genuinely puzzled by your comments, I am sorry if that comes across as negative. This is a huge thread, and lints were discussed multiple times before. I was trying to understand if you are suggesting something new or supporting some previously voiced position.

    You might try searching this page for "array" in my message so you can see that I am not "focusing" on fieldless structs.

    I was referring specifically to the 2nd bullet about windows -- sorry, I should have made that more clear. I'm sometimes replying in haste when wading through my backlog. Maybe I should stop engaging in some threads entirely so that I can spend proper time on the remaining ones and be more likely to be productive there.

    In that bullet, you mostly spoke about "fieldless structs"; there was one mention of "fieldless arrays" which looked like a typo since the immediately next sentence just spoke about "structs" again and "fieldless array" does not make sense (I think? arrays don't usually have "fields").

    Now you edited this adding more mentions of "arrays", without leaving a note that there was an edit, which is disingenuous given that I already replied.

    In the edited version you are claiming the windows ABI does not have zero-sized arrays, but in the OP we have C code using zero-sized arrays with MSVC. I am still puzzled by your comments.

    I don't distinguish between "lints" and other kinds of type checks

    I mean, the Rust compiler does, so when proposing changes to the Rust compiler it'd be good to use terms that make sense in this context. Otherwise how should anyone understand what you are saying?
    Also, my key question for you was in the next line which you did not reply to at all:
    "What is not clear to me is whether you think a lint with false negatives is sufficient to resolve this issue or not."
    That's entirely unrelated to whether it's called a "lint" or not, the key is you did not make it clear whether the check should in your opinion be best-effort or must be fully reliable to realize your vision for how this should be handled (where the latter requires a post-monomorphization check which has a long list of downsides).

    But, I think I am getting the clue, you don't like the way I am probing your comments to try and understand them. I am not sure what to do about that. Asking a question does not mean I am saying you are wrong, or being negative. Don't try to read things between the lines, there's nothing there, what I want to express is just literally the question that I wrote.

  18. CAD97 commented on Apr 1, 2025

    @CAD97
    Contributor

    There are two ways of defining "the Win64 ABI:" what the Microsoft Learn documentation says, and what MSVC does.

    I believe @briansmith's position is that the prose documentation (which in the MSVC case, is never referred to as a specification) should be considered authoritative, and anything not defined in said documentation should not be considered defined for FFI.

    The more typically expected understanding is that MSVC provides a stable ABI for any C signature that it allows you to write, and that what the compiler does is the source of truth, not the documentation.

  19. Jules-Bertholet commented on May 5, 2026

    @Jules-Bertholet
    Contributor

    Godbolt says that this C program:

    #include <stdio.h>
    
    struct empty {
        long long i[0];
    };
    
    int main() {
        printf("size: %zu; align: %zu", sizeof(struct empty), _Alignof(struct empty));
    }

    Prints the following on x86-64 MSVC:

    size: 4; align: 8
    

    Even if we split repr(ordered_fields) from repr(C), that just seems too cursed to replicate?

    (Replacing long long with double has the same effect. With __m64, however, the size is 8.)

    However, we could interpret this as a specific instance of the general issue that _Alignof on MSVC is a lie, and define this as having alignment of 4 in Rust. But that doesn't work for 64-bit…

  20. Jules-Bertholet commented on May 6, 2026

    @Jules-Bertholet
    Contributor

    Reported to Microsoft: https://developercommunity.visualstudio.com/t/C-structs-containing-only-a-flexible-arr/11086085

    (An upvote on the issue might help it get prioritized)

  21. RalfJung commented on May 6, 2026

    @RalfJung
    Member

    _Alignof on MSVC is "preferred alignment", right? I don't think we have any way of querying the required alignment on MSVC, do we? So, there's not that much we can actually read into that value sadly.

  22. Jules-Bertholet commented on May 6, 2026

    @Jules-Bertholet
    Contributor

    Yes, that's what I explain in the issue. But if struct empty { long long i[]; }; is to have alignment 4 even on x64, as the sizeof would imply, that's going to be a problem (because it would imply that we have to make u64 4-aligned on x64, or that the Rust equivalent of that struct has to effectively be repr(packed))

  23. jendrikw commented on May 7, 2026

    @jendrikw
    Contributor
  24. deleted a comment from givebk-bot on Jun 8, 2026
  25. deleted a comment from antl3x on Jun 8, 2026
  26. deleted a comment from givebk-bot on Jun 8, 2026
  27. Jules-Bertholet commented on Aug 10, 2026

    @Jules-Bertholet
    Contributor
  28. Jules-Bertholet commented on Aug 10, 2026

    @Jules-Bertholet
    Contributor

    https://rust-lang.zulipchat.com/#narrow/channel/122651-general/topic/Please.20upvote.20my.20issue.20on.20MSVC's.20issue.20tracker/near/615558327

    The fix: Follow the behavior of C++, which makes the size of the FAM-only struct to match the alignment of the type in the array. We added logic to set minimum size for structs containing only zero-sized arrays and added assertion to verify C structs have non-zero size.
    FAM = flexible array members

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-FFIArea: Foreign function interface (FFI)A-reprArea: the `#[repr(stuff)]` attributeC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessO-windowsOperating system: WindowsO-windows-msvcToolchain: MSVC, Operating system: WindowsP-mediumMedium priorityT-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions