这更多的是一个理论问题。我熟悉填充和尾随填充的工作原理。
struct myStruct{
uint32_t x;
char* p;
char c;
};
// myStruct layout will compile to
// x: 4 Bytes
// padding: 4 Bytes
// *p: 8 Bytes
// c: 1 Byte
// padding: 7 Bytes
// Total: 24 Bytes
Run Code Online (Sandbox Code Playgroud)
之后需要有填充x,以便*p对齐,并且之后需要有尾部填充c,以便整个结构体大小可以被 8 整除(为了获得正确的步幅长度)。但考虑这个例子:
struct A{
uint64_t x;
uint8_t y;
};
struct B{
struct A myStruct;
uint32_t c;
};
// Based on all information I read on internet, and based on my tinkering
// with …Run Code Online (Sandbox Code Playgroud) 如果struct包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同?
请考虑以下代码(必须使用"unsafe"启用的编译器应用程序):
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication3
{
static class Program
{
static void Main()
{
Inner test = new Inner();
unsafe
{
Console.WriteLine("Address of struct = " + ((int)&test).ToString("X"));
Console.WriteLine("Address of First = " + ((int)&test.First).ToString("X"));
Console.WriteLine("Address of NotFirst = " + ((int)&test.NotFirst).ToString("X"));
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Inner
{
public byte First;
public double NotFirst;
public DateTime WTF;
}
}
Run Code Online (Sandbox Code Playgroud)
现在如果我运行上面的代码,我得到类似于以下的输出:
struct = 40F2CC的
地址First = 40F2D4的
地址NotFirst的地址= 40F2CC
注意,First的地址与struct的地址不同; 然而,NotFirst的地址是一样的结构的地址.
现在注释掉结构中的"DateTime WTF"字段,然后再次运行它.这一次,我得到的输出类似于:
struct …
结构System.DateTime及其表兄System.DateTimeOffset的结构布局类型设置为"自动".这可以看作:
typeof(DateTime).IsAutoLayout /* true */
Run Code Online (Sandbox Code Playgroud)
要么:
typeof(DateTime).StructLayoutAttribute.Value /* Auto */
Run Code Online (Sandbox Code Playgroud)
或者从IL中可以看出它声明:
.class public auto ansi serializable sealed beforefieldinit System.DateTime
¯¯¯¯
Run Code Online (Sandbox Code Playgroud)
通常,使用C#编写的结构(即不是枚举的.NET值类型)将具有"顺序"布局(除非StructLayoutAttribute已应用指定另一个布局).
我通过一些常见的搜索BCL组件,以及DateTime和DateTimeOffset是唯一公开可见的结构,我发现用这个布局.
有谁知道为什么DateTime这个不寻常的结构布局?
运行此代码时:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace StructLayoutTest
{
class Program
{
unsafe static void Main()
{
Console.WriteLine(IntPtr.Size);
Console.WriteLine();
Sequential s = new Sequential();
s.A = 2;
s.B = 3;
s.Bool = true;
s.Long = 6;
s.C.Int32a = 4;
s.C.Int32b = 5;
int* ptr = (int*)&s;
Console.WriteLine(ptr[0]);
Console.WriteLine(ptr[1]);
Console.WriteLine(ptr[2]);
Console.WriteLine(ptr[3]);
Console.WriteLine(ptr[4]);
Console.WriteLine(ptr[5]);
Console.WriteLine(ptr[6]);
Console.WriteLine(ptr[7]); //NB!
Console.WriteLine("Press any key");
Console.ReadKey();
}
[StructLayout(LayoutKind.Explicit)]
struct Explicit
{
[FieldOffset(0)]
public int Int32a;
[FieldOffset(4)]
public int Int32b;
}
[StructLayout(LayoutKind.Sequential, Pack …Run Code Online (Sandbox Code Playgroud) 我试图理解为什么下面的第二个例子没有问题,但第一个例子给了我下面的例外.在我看来,两个例子都应该基于描述给出例外.任何人都可以开导我吗?
未处理的异常:System.TypeLoadException:无法从程序集'StructTest,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'加载类型'StructTest.OuterType',因为它包含偏移0处的对象字段,该字段未正确对齐或重叠由非对象字段.
在StructTest.Program.Main(String [] args)按任意键继续...
例1
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace StructTest
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct InnerType
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
char[] buffer;
}
[StructLayout(LayoutKind.Explicit)]
struct OuterType
{
[FieldOffset(0)]
int someValue;
[FieldOffset(0)]
InnerType someOtherValue;
}
class Program
{
static void Main(string[] args)
{
OuterType t = new OuterType();
System.Console.WriteLine(t);
}
}
}
Run Code Online (Sandbox Code Playgroud)
例2
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace StructTest …Run Code Online (Sandbox Code Playgroud) 我做需要连续数据的事情。现在使用 C# 10,我们可以执行public readonly record struct.
我喜欢记录所具有的自动 ToString 功能,所以为我完成这个功能真是太好了。
因此,以下等价吗?
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly struct MyVector
{
public readonly float X;
public readonly float Y;
public readonly float Z;
public MyVector(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
}
Run Code Online (Sandbox Code Playgroud)
与简洁的 C# 10 版本相比
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public readonly record struct MyVectorRecord(float X, float Y, float Z)
{
}
Run Code Online (Sandbox Code Playgroud)
或者我这样做会不小心踩到任何地雷吗?我的意思是,是否有任何事情在幕后进行,使得record我上面写的内容不能达到我想要的连续包装效果?我不能让记录插入填充、间距或做任何奇怪的事情。
我没有使用带有记录结构的向量类,而是将其用于说明目的。您可以忽略“浮点相等比较”之类的内容,因为我只关心是否可以将其传递给需要 X/Y/Z 连续序列的库。
我非常喜欢C#语言.我只是玩游戏,绝不会在生产代码中使用下面的代码.显然,编译器被结构的布局所欺骗.但是为什么Super Class上的字符串仍然可以在运行时写入和读取?我本来期望一些内存访问违规.在运行时检查类型,它表示它是Base类型,请参阅NoProblem()函数执行.没有实例化超级类.
它怎么能这样运作?
using System;
using System.Runtime.InteropServices;
namespace Fiddle
{
class Program
{
static void Main(string[] args)
{
var b = new Base
{
IntOnBase = 1
};
var overlay = new Overlay();
overlay.Base = b;
var super = overlay.Super;
var intValue = super.IntOnBase;
super.StringOnSuper = "my test string";
var stringValue = super.StringOnSuper;
super.NoProblem();
Expressions.Fiddle();
}
}
[StructLayout(LayoutKind.Explicit)]
public struct Overlay
{
[FieldOffset(0)]
public Super Super;
[FieldOffset(0)]
public Base Base;
}
public class Super : Base
{
public …Run Code Online (Sandbox Code Playgroud) 我想做以下事情:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SomeStruct
{
public byte SomeByte;
public int SomeInt;
public short SomeShort;
public byte SomeByte2;
}
Run Code Online (Sandbox Code Playgroud)
是否有替代方案,因为紧凑框架不支持Pack?
更新:显式设置结构并为每个提供FieldOffset都不起作用,因为它不会影响结构的打包方式
Update2:如果您尝试以下操作,CF程序甚至不会运行,因为结构的打包方式如下:
[StructLayout(LayoutKind.Explicit, Size=8)]
public struct SomeStruct
{
[FieldOffset(0)]
public byte SomeByte;
[FieldOffset(1)]
public int SomeInt;
[FieldOffset(5)]
public short SomeShort;
[FieldOffset(7)]
public byte SomeByte2;
}
Run Code Online (Sandbox Code Playgroud)
我知道这似乎很难相信,但如果你尝试它,你会看到.将它添加到CF项目并尝试运行它,您将获得TypeLoadException.将偏移分别更改为0,4,8,10并且它将起作用(但是大小最终为12).
我希望也许某人有一个使用反射的解决方案可能单独编组每个字段类型的大小(涉及递归以处理结构或类型数组中的结构).
我声明了这个函数Process32FirstW和结构PROCESSENTRY32W:
[DllImport("KERNEL32.DLL", CallingConvention = CallingConvention.StdCall, EntryPoint = "Process32FirstW")]
private static extern bool Process32FirstW (IntPtr hSnapshot, ref ProcessEntry pProcessEntry);
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Size = 568)]
internal struct ProcessEntry {
[FieldOffset(0)] public int Size;
[FieldOffset(8)] public int ProcessId;
[FieldOffset(32)] public int ParentProcessID;
[FieldOffset(44), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string ExeFile;
}
Run Code Online (Sandbox Code Playgroud)
在调用时Process32FirstW(使用64位进程),我总是TypeLoadException说一句话
ProcessEntry无法加载类型,因为偏移44处的对象字段对齐错误或与另一个字段重叠,而另一个字段不是对象字段.
我也尝试使用char[],而不是string为ProcessEntry.ExeFile使用Pack=4,并Pack=8在该结构的StructLayoutAttribute.我总是设置ProcessEntry.Size为568,我从C++程序(64位版本)复制了偏移数据:
typedef unsigned long long …Run Code Online (Sandbox Code Playgroud) 我刚从VC++ 14.0(2015)编译器中的Bug中学到了什么?那个人不应该假设结构的布局最终会在内存中出现.但是,我不明白我见过很多代码中的常见做法.例如,Vulkan图形API执行以下操作:
定义结构
struct {
glm::mat4 projection;
glm::mat4 model;
glm::vec4 lightPos;
} uboVS;
Run Code Online (Sandbox Code Playgroud)
然后填补其领域:
uboVS.model = ...
uboVS....
Run Code Online (Sandbox Code Playgroud)
然后通过memcpy将结构(在主机内存中)复制到设备内存:
uint8_t *pData;
vkMapMemory(device, memory, 0, sizeof(uboVS), 0, (void **)&pData);
memcpy(pData, &uboVS, sizeof(uboVS));
vkUnmapMemory(device, memory);
Run Code Online (Sandbox Code Playgroud)
然后到GPU,它定义了一个UBO来匹配该结构:
layout (binding = 0) uniform UBO
{
mat4 projection;
mat4 model;
vec4 lightPos;
} ubo;
Run Code Online (Sandbox Code Playgroud)
然后,在GPU方面,ubo将始终匹配uboVS.
这是不一样的未定义行为?那些代码是否依赖于uboVS结构完全按照定义布局,或者双方(编译的C++代码和编译的SPIR-V着色器)基本上生成相同的不同结构布局?(类似于https://www.securecoding.cert.org/confluence/display/c/EXP11-C.+Do+not+make+assumptions+regarding+the+layout+of+structures+with+中的第一个示例位域)
这个问题并不是特定于Vulkan或图形API,我很好奇人们可以假设什么,以及什么时候只使用结构作为一块内存.我理解结构打包和对齐,但还有更多吗?
谢谢
structlayout ×10
c# ×8
struct ×4
.net ×3
c++ ×2
datetime ×2
c ×1
c#-10.0 ×1
clr ×1
inheritance ×1
marshalling ×1
padding ×1
unsafe ×1
vulkan ×1