Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer sizeof over Marshal.SizeOf #33802

Open
terrajobst opened this issue Mar 19, 2020 · 11 comments
Open

Prefer sizeof over Marshal.SizeOf #33802

terrajobst opened this issue Mar 19, 2020 · 11 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime.InteropServices code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer
Milestone

Comments

@terrajobst
Copy link
Member

sizeof is much more efficient than Marshal.SizeOf. In cases where they'll produce the same answer, the former should be preferred (it'll generally require using unsafe, though, so this should only fire if unsafe is allowed in the project).

Category: Performance

@terrajobst terrajobst added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime.InteropServices code-analyzer Marks an issue that suggests a Roslyn analyzer labels Mar 19, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Mar 19, 2020
@tannergooding
Copy link
Member

How do we determine they will produce the same answer?

@terrajobst
Copy link
Member Author

terrajobst commented Mar 19, 2020

AFAIK @stephentoub wrote this one. I think the idea is that we can know this statically either by having a list of hardcoded types or when the type has the right characteristic based on the data in the reference assembly.

@Gnbrkm41
Copy link
Contributor

so this should only fire if unsafe is allowed in the project.

What about the S.R.CS.Unsafe alternative? That class, while equally "unsafe", does not require the language keyword unsafe and relevant settings in the project file.

@jeffhandley jeffhandley added this to the Future milestone Mar 20, 2020
@jeffhandley jeffhandley added the code-fixer Marks an issue that suggests a Roslyn code fixer label Mar 21, 2020
@jeffhandley
Copy link
Member

Estimates:

  • Analyzer: Medium
  • Fixer: Small

@terrajobst terrajobst removed the untriaged New issue has not been triaged by the area owner label Mar 21, 2020
@Joe4evr
Copy link
Contributor

Joe4evr commented Mar 21, 2020

@Gnbrkm41 There's a proposal to mark methods that do "unsafe" things but lack doing things with actual pointers to force that they are called from an unsafe context: #31354. Practically all methods in S.R.CS.Unsafe also qualify, but as noted in that thread, you've already written Unsafe.<something> so it may look a bit redundant. (For the record, I think the redundancy is not enough reason to not mark them, mainly because of using static.)

@Mrnikbobjeff
Copy link

So we would warn for all primitives used in Marshal.Sizeof() except for char and bool as they differ from sizeof. This could probably also be extended to value types, but then the analyzed would become quite a lot more complicated. I assume the baseline request covers simply known primitives?

@Mrnikbobjeff
Copy link

Mrnikbobjeff commented Sep 2, 2020

Also, which Marshal.SizeOf options to we aim to find with the analyzer? I assume only the invocations with one type argument, as the overloads accepting either a type or an object are already flagged as obsolete?

@carlossanlop
Copy link
Member

@Mrnikbobjeff can you please explain how you determined that only char and bool have a different return value with sizeof vs Marshal.SizeOf()? Why don't other primitives have this difference?

@Gnbrkm41 would you mind sharing examples of S.R.CS.Unsafe being flagged by this proposed analyzer?

What is the condition to require unsafe code in the project?

For example, these don't require to enable unsafe code in my project:

int x = sizeof(char);
int y = sizeof(bool);
int z = sizeof(int);

But these cases do:

int w = sizeof(z);
int r = sizeof(5);
int v = 4;
int s = sizeof(v);

@tannergooding
Copy link
Member

For example, these don't require to enable unsafe code in my project:

sizeof and Unsafe.SizeOf<T> are equivalent; they both compile down to the sizeof IL instruction and return the size of the managed layout.
C# special cases sizeof for primitive types to be a constant (most of the types that have a keyword equivalent). Anything that isn't constant sized (including say nint, nuint, or Guid) requires unsafe.

Marshal.SizeOf<T> returns the unmanaged layout of the type.

It's only safe to replace Marshal.SizeOf<T> with sizeof when the following conditions are met:

  • The type is considered unmanaged (contains no reference types, directly or indirectly)
  • The type is Sequential or Explicit layout
  • The type is only comprised of blittable types
    • bool is not blittable, it is always "normalized" and defaults to being 4-bytes for the unmanaged layout, while its 1-byte in the managed layout
    • char is sometimes blittable. By default it is 1-byte for the unmanaged layout and 2-bytes for the managed layout. If you force the unmanaged layout to be 2-bytes (via interop attributes), it will be treated as blittable

CC. @jkoritzinsky, @AaronRobinsonMSFT

@Joe4evr
Copy link
Contributor

Joe4evr commented Oct 7, 2021

Anything that isn't constant sized (including say nint, nuint, or Guid)

I get nint and nuint, but why is Guid not a constant size of 16, tho? 🤔

@tannergooding
Copy link
Member

I get nint and nuint, but why is Guid not a constant size of 16, tho

Because it isn't a "primitive', its a user-defined struct and the C# language makes no substantial inferences about user-defined structs.

  • decimal is the one exception here, but its a bit special and has limitations the other primitives don't have. The language could special-case any number of other user-defined types, but that's not sustainable overall.

As far as the language is concerned, Guid is simply some value type that isn't known by the language and who's fields could change between when the C# compilation happens and when the IL compilation happens.

Likewise, in cases of other structs the field contents might stay the same but the actual physical layout could change. For example, nint/nuint change in size between 32-bit and 64-bit. Some other struct such as struct S { float x; double y; } could also change (on some platforms, double or long/ulong, have a natural alignment of 4, in which case the size is 12, not 16).

So its easiest in those cases to not allow it to be a C# constant and instead make it a JIT time constant instead (static readonly of a primitive type effectively works like this today; as does things like sizeof in IL, it becomes a JIT time constant).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime.InteropServices code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer
Projects
Status: No status
Development

No branches or pull requests

9 participants