【问题标题】:Matching the memory size of C# struct with C++ struct将 C# struct 的内存大小与 C++ struct 匹配
【发布时间】:2017-06-25 20:28:01
【问题描述】:

我正在尝试创建从 C# 结构到 C++ 和 CUDA 中的非托管代码的自动转换。不幸的是,我似乎无法创建匹配的结构。结构:

// C#
[StructLayout(LayoutKind.Sequential)]
struct DebugComponent
{
    public float4 Float4;
    public float Float;
}

// C++
struct CPP_DebugComponent
{
    float4 Float4;
    float Float;
};

结构体的大小使用 C# 调用计算为 20 字节:

var size = System.Runtime.InteropServices.Marshal.SizeOf<DebugComponent>();

但是在使用 sizeof 的 C++ 中,相同的结构是 32 字节:

auto size = sizeof(CPP_DebugComponent);

我相信差异源于 float4 结构的定义方式。 CUDA 对 float4 的定义将其对齐为 16 个字节:

// C++
struct __device_builtin__ __builtin_align__(16) float4
{
    float x, y, z, w;
};
// For similar results without the CUDA definition, you can use:
// struct __align__(16) float4

因此向单个浮点数添加 12 个字节的填充。在 C# 中,没有这种对齐方式,导致编译器选择 4 字节的打包。为了完整起见,C# 中的 float4 定义(来自 ManagedCuda):

// C#
public struct float4
{
    public float x;
    public float y;
    public float z;
    public float w;
}

我知道我可以通过指定以下属性在 C# 中人为地重新创建正确大小的结构:

 [StructLayout(LayoutKind.Sequential, Size = 32)]
 struct DebugComponent { ... }

但是对于需要知道结构中所有类型的总和以及有关打包和填充的一些假设的自动代码生成。看来我无法使用StructLayoutAttribute.Pack 扩展结构。

我也知道我可以改变 C++ 代码中的包装,例如:

#pragma pack(1) 

但我宁愿避免这种解决方案,因为填充会导致性能显着提高。

我的问题:我能否模仿非托管填充/打包的行为,使 C# 结构在内存大小方面与 C++ 结构保持一致?

有没有很好的替代方案来调整结构,同时牢记性能?

以上所有代码示例均在 Windows、Visual Studio 2017 上运行并在 x64 上编译。

【问题讨论】:

  • AFAIK 您拥有的唯一可移植工具是 alignasaligned_storage
  • 如果你将它映射到原始内存,System.Runtime.InteropServices.Marshal.SizeOf 是无关紧要的; sizeof(DebugComponent) 说什么?
  • sizeof(DebugComponent)(在不安全的代码中)与此结构的 Marshal.Sizeof 相同。
  • 难道你不能通过在开头添加总共消耗 12 个字节的未使用成员变量来伪造填充吗?不过,我并不建议这样做,只是说...
  • 当然,C# 编译器知道关于 __builtin_align__(16) 的 bean。 CLR 也没有,那是你的克星。人为地增加结构的大小不是一种解决方法,关键是结构的起始地址是 16 的倍数。不可能,您只能对齐到 32 中的 4-位代码,在 64 位代码中为 8。您可以使用 Marshal.AllocHGlobal() (与 8 对齐)进行技巧,分配超过必要的 8 并在必要时将 8 添加到指针。建议使用 C++/CLI,以便您可以使用 _aligned_malloc()

标签: c# c++


【解决方案1】:

结构/结构成员的填充和对齐是 AFAIK 不是 C++ 标准的一部分,并且依赖于实现。这意味着,一旦您更改了 C++ 编译器,您将获得不同的布局(或可能获得)。这也是 Nvidia 保证 CUDA 仅适用于少数给定编译器的原因之一。

要自动运行此代码转换,您需要使用 StructLayout 选项在 C# 中模拟您的 C++ 编译器:遵循与 C++ 编译器相同的规则。对于一般情况,这并非不可能,但可能很难推导出来......另一方面,对于像给定结构这样的简单结构,应该可以推导出 C++ 编译器对齐规则。例如,一个基本规则是,最大的成员决定了整个结构的对齐方式。

【讨论】:

    【解决方案2】:

    在我看来,最好的方法是在规范中声明一个结构体大小的数组。 C# 或 C++ 对象应将元素从缓冲区复制到其数据成员中。

    这是最便携的,可以处理 C# 和 C++ 成员之间的填充。由于填充、v-tables 和其他东西,C# 和 C++ 类不擅长将数据格式与数据格式进行 1:1 映射。从缓冲区复制也可以处理字符串类和类中的其他非POD类型。

    还研究“序列化”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-27
      • 2012-08-17
      • 2021-05-03
      • 1970-01-01
      • 2011-05-04
      相关资源
      最近更新 更多