为什么结构对准取决于字段类型是原始或用户定义的? [英] Why does struct alignment depend on whether a field type is primitive or user-defined?

查看:141
本文介绍了为什么结构对准取决于字段类型是原始或用户定义的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

野田佳彦时间 v2中,我们正在纳秒的分辨率。这意味着我们可以不再使用一个8字节的整数,表示时间我们感兴趣的整个范围。这促使我去研究野田时间的(多)结构的内存使用,这又使我揭示了CLR的对齐方式决定略有古怪。

In Noda Time v2, we're moving to nanosecond resolution. That means we can no longer use an 8-byte integer to represent the whole range of time we're interested in. That has prompted me to investigate the memory usage of the (many) structs of Noda Time, which has in turn led me to uncover a slight oddity in the CLR's alignment decision.

首先,我知道这的的实施决定,并默认行为可能在任何时候改变。我知道我的可以的修改,使用 [StructLayout] [FieldOffset] ,但我宁愿拿出一个解决方案,它并不要求如果可能的话。

Firstly, I realize that this is an implementation decision, and that the default behaviour could change at any time. I realize that I can modify it using [StructLayout] and [FieldOffset], but I'd rather come up with a solution which didn't require that if possible.

我核心的情况是,我有一个结构,其中包含引用类型字段和另外两个数值类型的字段,在这些字段是简单包装为 INT 。我的希望的,这将被表示为在64位CLR(8参考和4对每个人)的16个字节,但由于某种原因它使用24个字节。我测量使用数组的空间,顺便说一句 - 我明白的布局可能会在不同的场合不同,但这种感觉就像一个合理的起点

My core scenario is that I have a struct which contains a reference-type field and two other value-type fields, where those fields are simple wrappers for int. I had hoped that that would be represented as 16 bytes on the 64-bit CLR (8 for the reference and 4 for each of the others), but for some reason it's using 24 bytes. I'm measuring the space using arrays, by the way - I understand that the layout may be different in different situations, but this felt like a reasonable starting point.

下面是一个示例程序演示问题:

Here's a sample program demonstrating the issue:

using System;
using System.Runtime.InteropServices;

#pragma warning disable 0169

struct Int32Wrapper
{
    int x;
}

struct TwoInt32s
{
    int x, y;
}

struct TwoInt32Wrappers
{
    Int32Wrapper x, y;
}

struct RefAndTwoInt32s
{
    string text;
    int x, y;
}

struct RefAndTwoInt32Wrappers
{
    string text;
    Int32Wrapper x, y;
}    

class Test
{
    static void Main()
    {
        Console.WriteLine("Environment: CLR {0} on {1} ({2})",
            Environment.Version,
            Environment.OSVersion,
            Environment.Is64BitProcess ? "64 bit" : "32 bit");
        ShowSize<Int32Wrapper>();
        ShowSize<TwoInt32s>();
        ShowSize<TwoInt32Wrappers>();
        ShowSize<RefAndTwoInt32s>();
        ShowSize<RefAndTwoInt32Wrappers>();
    }

    static void ShowSize<T>()
    {
        long before = GC.GetTotalMemory(true);
        T[] array = new T[100000];
        long after  = GC.GetTotalMemory(true);        
        Console.WriteLine("{0}: {1}", typeof(T),
                          (after - before) / array.Length);
    }
}

和我的笔记本电脑的编辑和输出:

And the compilation and output on my laptop:

c:\Users\Jon\Test>csc /debug- /o+ ShowMemory.cs
Microsoft (R) Visual C# Compiler version 12.0.30501.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.


c:\Users\Jon\Test>ShowMemory.exe
Environment: CLR 4.0.30319.34014 on Microsoft Windows NT 6.2.9200.0 (64 bit)
Int32Wrapper: 4
TwoInt32s: 8
TwoInt32Wrappers: 8
RefAndTwoInt32s: 16
RefAndTwoInt32Wrappers: 24

所以:


  • 如果您还没有引用类型领域,CLR是幸福的收拾 Int32Wrapper 字段合在一起( TwoInt32Wrappers 的大小8)

  • 即使是引用类型领域,CLR仍然乐意收拾 INT 字段合在一起( RefAndTwoInt32s 的大小16 )

  • 结合两个,每个 Int32Wrapper 字段似乎填充/对齐到8个字节。 ( RefAndTwoInt32Wrappers 的大小为24。)

  • 运行在调试器中相同的代码(但仍然是一个发布版本)显示尺寸12

  • If you don't have a reference type field, the CLR is happy to pack Int32Wrapper fields together (TwoInt32Wrappers has a size of 8)
  • Even with a reference type field, the CLR is still happy to pack int fields together (RefAndTwoInt32s has a size of 16)
  • Combining the two, each Int32Wrapper field appears to be padded/aligned to 8 bytes. (RefAndTwoInt32Wrappers has a size of 24.)
  • Running the same code in the debugger (but still a release build) shows a size of 12.

其他一些实验已经得出了类似的结果:

A few other experiments have yielded similar results:


  • 把引用类型字段中的值类型字段后,并没有帮助

  • 使用对象而不是字符串没有帮助(我希望它是任何引用类型)

  • 使用另一种结构作为一个包装围绕基准没有按'T帮助

  • 使用一个通用的结构作为围绕基准的包装不利于

  • 如果我继续添加字段(成对为简单起见) , INT 领域仍算为4个字节, Int32Wrapper 字段数8个字节

  • 添加 [StructLayout(LayoutKind.Sequential,包= 4)] 来在望每个结构不会改变结果

  • Putting the reference type field after the value type fields doesn't help
  • Using object instead of string doesn't help (I expect it's "any reference type")
  • Using another struct as a "wrapper" around the reference doesn't help
  • Using a generic struct as a wrapper around the reference doesn't help
  • If I keep adding fields (in pairs for simplicity), int fields still count for 4 bytes, and Int32Wrapper fields count for 8 bytes
  • Adding [StructLayout(LayoutKind.Sequential, Pack = 4)] to every struct in sight doesn't change the results

有没有人有任何解释(最好有参考文档),或怎样可以得到提示,我想等领域的CLR的建议被包装的没有的指定一个常数域偏移?

Does anyone have any explanation for this (ideally with reference documentation) or a suggestion of how I can get hint to the CLR that I'd like the fields to be packed without specifying a constant field offset?

推荐答案

我觉得这是一个错误。你看到自动布局的副作用,它喜欢对齐非平凡字段这8个字节在64位模式的倍数的地址。当你明确地应用 [StructLayout(LayoutKind.Sequential)] 属性,它发生均匀。 ,这是不应该发生的。

I think this is a bug. You are seeing the side-effect of automatic layout, it likes to align non-trivial fields to an address that's a multiple of 8 bytes in 64-bit mode. It occurs even when you explicitly apply the [StructLayout(LayoutKind.Sequential)] attribute. That is not supposed to happen.

您可以通过使结构成员的公共和附加这样的测试代码中看到它:

You can see it by making the struct members public and appending test code like this:

    var test = new RefAndTwoInt32Wrappers();
    test.text = "adsf";
    test.x.x = 0x11111111;
    test.y.x = 0x22222222;
    Console.ReadLine();      // <=== Breakpoint here

在断点命中,使用调试+的Windows +内存+内存1.切换到4字节的整数,并把&放大器;测试在地址栏:

When the breakpoint hits, use Debug + Windows + Memory + Memory 1. Switch to 4-byte integers and put &test in the Address field:

 0x000000E928B5DE98  0ed750e0 000000e9 11111111 00000000 22222222 00000000 

0xe90ed750e0 是我的机器上的字符串指针(不是你的)。你可以很容易地看到 Int32Wrappers ,用额外的4个填充,它把大小为24字节。回去的结构和最后把字符串。重复,你会看到字符串指针的还是的第一位。违反 LayoutKind.Sequential ,你得到了 LayoutKind.Auto

0xe90ed750e0 is the string pointer on my machine (not yours). You can easily see the Int32Wrappers, with the extra 4 bytes of padding that turned the size into 24 bytes. Go back to the struct and put the string last. Repeat and you'll see the string pointer is still first. Violating LayoutKind.Sequential, you got LayoutKind.Auto.

这将是很难说服微软来解决这个问题,它已经太长时间工作这种方式,所以任何变化将被打破的的东西的。在CLR只会使企图兑现 [StructLayout] 为一个结构的托管版本,并使其blittable,它一般很快放弃。众所周知,对于包含一个DateTime任何结构。封送一个结构时,你只得到真实的LayoutKind保证。该封版本肯定是16个字节,为 Marshal.SizeOf()会告诉你。

It is going to be difficult to convince Microsoft to fix this, it has worked this way for too long so any change is going to be breaking something. The CLR only makes an attempt to honor [StructLayout] for the managed version of a struct and make it blittable, it in general quickly gives up. Notoriously for any struct that contains a DateTime. You only get the true LayoutKind guarantee when marshaling a struct. The marshaled version certainly is 16 bytes, as Marshal.SizeOf() will tell you.

使用 LayoutKind.Explicit 修复它,不是你想听到的。

Using LayoutKind.Explicit fixes it, not what you wanted to hear.

这篇关于为什么结构对准取决于字段类型是原始或用户定义的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆