日韩欧美人妻无码精品白浆,夜夜嗨AV免费入口,国产欧美官网在线看,高校回应聋哑女生因长相完美被质疑

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

理解 .NET 結(jié)構(gòu)體字段的內(nèi)存布局

freeflydom
2025年6月9日 9:19 本文熱度 50

前言

大部分情況下我們并不需要關(guān)心結(jié)構(gòu)體字段的內(nèi)存布局,但是在一些特殊情況下,比如性能優(yōu)化、和非托管代碼交互、對(duì)結(jié)構(gòu)體進(jìn)行序列化等場(chǎng)景下,了解字段的內(nèi)存布局是非常重要的。

本文寫作時(shí) 最新的 .NET 正式版是 .NET 9,以后的版本不保證本文內(nèi)容的準(zhǔn)確性,僅供參考。

本文將介紹 .NET 中結(jié)構(gòu)體字段的內(nèi)存布局,包括字段的對(duì)齊(Alignment)、填充(Padding)以及如何使用 StructLayoutAttribute 來(lái)控制字段的內(nèi)存布局。

對(duì)齊的目的是為了 CPU 訪問(wèn)內(nèi)存的效率,64 位系統(tǒng)和 32 位系統(tǒng)中對(duì)齊要求存在差異,下文如果沒(méi)有特別說(shuō)明,均指 64 位系統(tǒng)。

填充則是為了滿足對(duì)齊要求而在字段之間或結(jié)構(gòu)體末尾添加的額外字節(jié)。

結(jié)構(gòu)體的對(duì)其規(guī)則同時(shí)適用于棧上和堆上的結(jié)構(gòu)體結(jié)構(gòu)體實(shí)例,方便起見(jiàn),大部分例子將使用棧上結(jié)構(gòu)體實(shí)例來(lái)演示。

一些資料是從 字段的偏移量(offset)為出發(fā)點(diǎn)來(lái)介紹字段的內(nèi)存布局的,但筆者認(rèn)為從字段的 內(nèi)存地址 出發(fā)更容易理解。

由于一些資料并沒(méi)有找到明確的官方的解釋,筆者是在實(shí)驗(yàn)和推導(dǎo)的基礎(chǔ)上總結(jié)出這些規(guī)則的,可能會(huì)有不準(zhǔn)確的地方,歡迎讀者在評(píng)論區(qū)指出。

本文雖然沒(méi)有直接介紹引用類型的字段布局,但引用類型實(shí)例的字段的內(nèi)存布局概念與結(jié)構(gòu)體實(shí)例的內(nèi)存布局是相同的。不同之處在于引用類型的默認(rèn)布局是 LayoutKind.Auto,而結(jié)構(gòu)體的默認(rèn)布局是 LayoutKind.Sequential。讀者可以自己嘗試觀察引用類型實(shí)例字段的內(nèi)存布局。

本文將使用下面的方法來(lái)觀察字段的內(nèi)存地址:

// 打印日志頭
void PrintPointerHeader()
{
    Console.WriteLine(
        $"| {"Expr",-15} | {"Address",-15} | {"Size",-4} | {"AlignedBySize",-13} | {"Addr/Size",-12} |");
}

// 打印指針的詳細(xì)信息
unsafe void PrintPointerDetails<T>(
    T* ptr,
    [CallerArgumentExpression("ptr")] string? pointerExpr = null)
    where T : unmanaged
{
    ulong addressValue = (ulong)ptr;
    ulong typeSize = (ulong)sizeof(T);

    decimal addressDivBySize = addressValue / (decimal)typeSize;
    bool isAlignedBySize = addressValue % typeSize == 0;

    Console.WriteLine(
        $"| {pointerExpr,-15} | {addressValue,-15} | {typeSize,-4} | {isAlignedBySize,-13} | {addressDivBySize,-12:0.##} |"
    );
}

并使用 ObjectLayoutInspector 這個(gè)開源庫(kù)來(lái)觀察字段的內(nèi)存布局。

項(xiàng)目地址:https://github.com/SergeyTeplyakov/ObjectLayoutInspector

nuget 包地址:https://www.nuget.org/packages/ObjectLayoutInspector

dotnet add package ObjectLayoutInspector --version 0.1.4

基本概念

以下是理解結(jié)構(gòu)體字段布局的幾個(gè)關(guān)鍵點(diǎn):

  • 字段順序:字段在結(jié)構(gòu)體實(shí)例中的排列順序,默認(rèn)按聲明順序排列,但可以通過(guò) StructLayoutAttribute 來(lái)控制。

  • 對(duì)齊(Alignment):對(duì)齊需要分成三部分理解:

    • 字段的對(duì)齊要求(alignment requirement):指字段在內(nèi)存中的地址必須是其對(duì)齊要求的倍數(shù)。對(duì)于基元類型(primitive types),對(duì)齊要求默認(rèn)等于其大小,非基元類型的對(duì)齊要求取決于結(jié)構(gòu)體中最大字段的對(duì)齊要求。
    • 結(jié)構(gòu)體實(shí)例的大?。罕仨毷墙Y(jié)構(gòu)體對(duì)齊要求的整數(shù)倍。
    • 結(jié)構(gòu)體實(shí)例的起始地址:在 64 位系統(tǒng)中,數(shù)據(jù)的地址按 8 字節(jié) 對(duì)齊有利于提升 CPU 的訪問(wèn)效率,32 位系統(tǒng)中則為 4 字節(jié)對(duì)齊。
  • 填充(Padding):為了滿足對(duì)齊要求,runtime 可能會(huì)在結(jié)構(gòu)體實(shí)例字段之間及末尾插入填充字節(jié)。這些填充字節(jié)不會(huì)被顯式聲明,但會(huì)影響字段在內(nèi)存中的實(shí)際布局。

結(jié)構(gòu)體的默認(rèn)字段布局

對(duì)齊#

字段默認(rèn)的對(duì)齊要求是類型的大小。例如,int 類型的字段需要在 4 字節(jié)對(duì)齊邊界(alignment boundary)上,而 double 類型的字段需要在 8 字節(jié)對(duì)齊邊界上。如果字段類型并非基元類型(primitive types),則對(duì)齊要求取決于結(jié)構(gòu)體中最大字段的對(duì)齊要求。對(duì)齊要求為 2 的整數(shù)次冪,例如 1、2、4、8 等。最大對(duì)齊要求為 8 字節(jié)。

注意:decimal 不屬于基元類型,目前版本中由三個(gè)字段組成,實(shí)例大小為 16 字節(jié),按 8 字節(jié)對(duì)齊。

Type layout for 'Decimal'
Size: 16 bytes. Paddings: 0 bytes (%0 of empty space)
|===============================|
|   0-3: Int32 _flags (4 bytes) |
|-------------------------------|
|   4-7: UInt32 _hi32 (4 bytes) |
|-------------------------------|
|  8-15: UInt64 _lo64 (8 bytes) |
|===============================|

下面是一個(gè)簡(jiǎn)單的示例,展示了結(jié)構(gòu)體字段的默認(rèn)布局:

using System.Runtime.CompilerServices;

var foo = new Foo();
var bar = new Bar();
var baz = new Baz();

unsafe
{
    PrintPointerHeader();

    PrintPointerDetails(&foo);
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);

    PrintPointerDetails(&bar);
    PrintPointerDetails(&bar.foo);
    PrintPointerDetails(&bar.foo.a);
    PrintPointerDetails(&bar.foo.b);

    fixed (Foo* bazFooPtr = &baz.foo)
    {
        PrintPointerDetails(bazFooPtr);
        PrintPointerDetails(&bazFooPtr->a);
        PrintPointerDetails(&bazFooPtr->b);
    }
}

struct Foo
{
    public int a;
    public long b;
}

struct Bar
{
    public Foo foo;
}

class Baz
{
    public Foo foo;
}

輸出結(jié)果如下:

| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo            | 6095528264      | 16   | False         | 380970516.5  |
| &foo.a          | 6095528264      | 4    | True          | 1523882066   |
| &foo.b          | 6095528272      | 8    | True          | 761941034    |
| &bar            | 6095528248      | 16   | False         | 380970515.5  |
| &bar.foo        | 6095528248      | 16   | False         | 380970515.5  |
| &bar.foo.a      | 6095528248      | 4    | True          | 1523882062   |
| &bar.foo.b      | 6095528256      | 8    | True          | 761941032    |
| bazFooPtr       | 12885617264     | 16   | True          | 805351079    |
| &bazFooPtr->a   | 12885617264     | 4    | True          | 3221404316   |
| &bazFooPtr->b   | 12885617272     | 8    | True          | 1610702159   |

首先看 Foo 結(jié)構(gòu)體,它有兩個(gè)字段 a 和 b,分別是 int 和 long 類型,對(duì)齊要求分別是 4 字節(jié)和 8 字節(jié)。

所以 Foo 實(shí)例在棧上的地址按照 8 字節(jié) 對(duì)齊(6095528264 / 8 = 761941033)。

a 字段是 foo 的第一個(gè)字段,它的地址也就是 foo 的起始地址,自然也滿足 int 的對(duì)齊要求(6095528264 / 4 = 1523882066)。

b 字段是 foo 的第二個(gè)字段,它的地址為 6095528272,滿足 long 的對(duì)齊要求(6095528272 / 8 = 761941032)。

Bar 結(jié)構(gòu)體包含一個(gè) Foo 類型的字段 foo,它的對(duì)齊要求也是 8 字節(jié)(取最大字段 long 的對(duì)齊要求),所以 bar 的地址也是按照 8 字節(jié)對(duì)齊(6095528248 / 8 = 761941031)。bar.foo.a 和 bar.foo.b 的地址也滿足各自的對(duì)齊要求。

Baz 類包含一個(gè) Foo 類型的字段 foo,由于 Baz 是引用類型,所以它的實(shí)例在堆上分配內(nèi)存。Baz 的 Foo 類型字段也依舊需要滿足 8 字節(jié)對(duì)齊要求(12885617264 / 8 = 1610702158)。

64 位系統(tǒng)與 32 位系統(tǒng)的對(duì)齊要求差異#

在 64 位系統(tǒng)中,結(jié)構(gòu)體實(shí)例的起始地址默認(rèn)按 8 字節(jié)對(duì)齊。

而在 32 位系統(tǒng)中,結(jié)構(gòu)體實(shí)例的起始地址默認(rèn)按 4 字節(jié)對(duì)齊。經(jīng)筆者測(cè)試,CPU 為 intel 時(shí) 只按 4 字節(jié)對(duì)齊,CPU 為 AMD 時(shí) 如果結(jié)構(gòu)體包含了 8 字節(jié)對(duì)齊的字段,則按 8 字節(jié)對(duì)齊,否則按 4 字節(jié)對(duì)齊。

首先在 64 位系統(tǒng)上運(yùn)行下面的代碼:

using System.Runtime.CompilerServices;
using ObjectLayoutInspector;

unsafe
{
    var foo = new Foo();
    var bar = new Bar();

    // 方法 PrintPointerHeader 和 PrintPointerDetails 在前言部分已經(jīng)定義
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
    PrintPointerDetails(&bar.d);
    PrintPointerDetails(&bar.e);
    PrintPointerDetails(&bar.f);
}

TypeLayout.PrintLayout<Foo>();
TypeLayout.PrintLayout<Bar>();

struct Foo
{
    public int a;
    public long b;
    public byte c;
}

struct Bar
{
    public int d;
    public int e;
    public byte f;
}

輸出結(jié)果如下:

| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 985964996520    | 4    | True          | 246491249130 |
| &foo.b          | 985964996528    | 8    | True          | 123245624566 |
| &foo.c          | 985964996536    | 1    | True          | 985964996536 |
| &bar.d          | 985964996504    | 4    | True          | 246491249126 |
| &bar.e          | 985964996508    | 4    | True          | 246491249127 |
| &bar.f          | 985964996512    | 1    | True          | 985964996512 |
Type layout for 'Foo'
Size: 24 bytes. Paddings: 11 bytes (%45 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|   4-7: padding (4 bytes) |
|--------------------------|
|  8-15: Int64 b (8 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-23: padding (7 bytes) |
|==========================|


Type layout for 'Bar'
Size: 12 bytes. Paddings: 3 bytes (%25 of empty space)
|==========================|
|   0-3: Int32 d (4 bytes) |
|--------------------------|
|   4-7: Int32 e (4 bytes) |
|--------------------------|
|     8: Byte f (1 byte)   |
|--------------------------|
|  9-11: padding (3 bytes) |
|==========================|

可以看到,Foo 和 Bar 結(jié)構(gòu)體的實(shí)例大小分別為 24 字節(jié)和 12 字節(jié),且它們的起始地址都滿足 8 字節(jié)對(duì)齊要求。

在 Windows 環(huán)境中,如果安裝了 x86 版本的 .NET SDK,可以在 csproj 文件中添加以下屬性來(lái)讓項(xiàng)目運(yùn)行在 32 位的環(huán)境中:

<PropertyGroup>
    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>

下面是 intel CPU 的輸出結(jié)果:

| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 43511772        | 4    | True          | 10877943     |
| &foo.b          | 43511780        | 8    | False         | 5438972.5    |
| &foo.c          | 43511788        | 1    | True          | 43511788     |
| &bar.d          | 43511760        | 4    | True          | 10877940     |
| &bar.e          | 43511764        | 4    | True          | 10877941     |
| &bar.f          | 43511768        | 1    | True          | 43511768     |
Type layout for 'Foo'
Size: 24 bytes. Paddings: 11 bytes (%45 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|   4-7: padding (4 bytes) |
|--------------------------|
|  8-15: Int64 b (8 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-23: padding (7 bytes) |
|==========================|


Type layout for 'Bar'
Size: 12 bytes. Paddings: 3 bytes (%25 of empty space)
|==========================|
|   0-3: Int32 d (4 bytes) |
|--------------------------|
|   4-7: Int32 e (4 bytes) |
|--------------------------|
|     8: Byte f (1 byte)   |
|--------------------------|
|  9-11: padding (3 bytes) |
|==========================|

Foo 和 Bar 結(jié)構(gòu)體的起始地址和字段地址都只滿足 4 字節(jié)對(duì)齊要求(43511772 / 4 = 10877943),而不是 8 字節(jié)對(duì)齊要求。

下面是 AMD CPU 的輸出結(jié)果:


運(yùn)行上述代碼,輸出結(jié)果如下:

```bash
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 47706560        | 4    | True          | 11926640     |
| &foo.b          | 47706568        | 8    | True          | 5963321      |
| &foo.c          | 47706576        | 1    | True          | 47706576     |
| &bar.d          | 47706548        | 4    | True          | 11926637     |
| &bar.e          | 47706552        | 4    | True          | 11926638     |
| &bar.f          | 47706556        | 1    | True          | 47706556     |
Type layout for 'Foo'
Size: 24 bytes. Paddings: 11 bytes (%45 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|   4-7: padding (4 bytes) |
|--------------------------|
|  8-15: Int64 b (8 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-23: padding (7 bytes) |
|==========================|


Type layout for 'Bar'
Size: 12 bytes. Paddings: 3 bytes (%25 of empty space)
|==========================|
|   0-3: Int32 d (4 bytes) |
|--------------------------|
|   4-7: Int32 e (4 bytes) |
|--------------------------|
|     8: Byte f (1 byte)   |
|--------------------------|
|  9-11: padding (3 bytes) |
|==========================|

Foo 的起始地址仍然滿足 8 字節(jié)對(duì)齊要求,但 Bar 的起始地址不再滿足 8 字節(jié)對(duì)齊要求(47706548 / 8 = 5963318.5),而是滿足 4 字節(jié)對(duì)齊要求(47706548 / 4 = 11926637)。

默認(rèn)字段布局中 對(duì)齊要求 與 偏移量 的關(guān)系#

偏移量(offset)是指字段相對(duì)于結(jié)構(gòu)體實(shí)例起始地址的距離,決定了字段在內(nèi)存中的位置。

偏移量的值取決于對(duì)齊要求和字段的順序,會(huì)在未被順序在前的字段占用的內(nèi)存空間中取對(duì)齊要求的最小整數(shù)倍。

下面幾個(gè)設(shè)計(jì)確保了不管結(jié)構(gòu)體實(shí)例的起始地址如何,任意一個(gè)字段只要給定一個(gè)滿足對(duì)齊要求的偏移量,就可以滿足該字段的對(duì)齊要求:

  • 對(duì)齊要求總是 2 的整數(shù)次冪。
  • 實(shí)例的起始地址(按 8 字節(jié) 對(duì)齊)總是滿足最大字段的對(duì)齊要求。
  • 偏移量的值是對(duì)齊要求的整數(shù)倍

下面做一個(gè)簡(jiǎn)單的推導(dǎo)來(lái)幫助讀者理解:

假設(shè)結(jié)構(gòu)體中最大字段的對(duì)齊要求為 2^m(m 為 <= 8 的非負(fù)整數(shù)),則 runtime 會(huì)保證結(jié)構(gòu)體實(shí)例的起始地址也是 2^m 的整數(shù)倍,可記作 2^m * k(k為非負(fù)整數(shù))。

若某字段的對(duì)齊要求為 2^n(n≤m),其偏移量必為 2^n 的整數(shù)倍,記為 2^n * f(f為非負(fù)整數(shù))。

則該字段實(shí)際地址為:

結(jié)構(gòu)體起始地址 + 字段偏移量 = (2^m * k) + (2^n * f)

由于 2^m 必定可以被 2^n 整除(因?yàn)?n≤m),所以無(wú)論 k 和 f 取何值,上述字段地址總能被 2^n 整除。這就保證了該字段的地址總是滿足其對(duì)齊要求。

因此,只要給每個(gè)字段的 偏移量 選擇其 對(duì)齊要求 的整數(shù)倍,就能保證結(jié)構(gòu)體任何實(shí)例、任意字段的地址都天然對(duì)齊,而無(wú)需依賴結(jié)構(gòu)體起始地址的額外信息。

unsafe
{
    var foo = new Foo();

    var addr = (ulong)&foo;

    Console.WriteLine($"a offset: {(ulong)&foo.a - addr}");
    Console.WriteLine($"b offset: {(ulong)&foo.b - addr}");
    Console.WriteLine($"c offset: {(ulong)&foo.c - addr}");
}

struct Foo
{
    public int a;
    public long b;
    public byte c;
}

輸出結(jié)果如下:

a offset: 0
b offset: 8
c offset: 16

填充#

填充(Padding)分為兩部分:

  1. 字段之間的填充:為了滿足對(duì)齊要求,.NET 可能會(huì)在字段之間插入填充字節(jié)。字段之間的填充由字段的偏移量決定。

  2. 結(jié)構(gòu)體末尾的填充:為了確保結(jié)構(gòu)體的大小是最大字段對(duì)齊要求的倍數(shù),.NET 可能會(huì)在結(jié)構(gòu)體末尾添加填充字節(jié)。末尾填充保證了數(shù)組中連續(xù)的結(jié)構(gòu)體實(shí)例在內(nèi)存中也滿足對(duì)齊要求。

借助 ObjectLayoutInspector 庫(kù),我們可以觀察到結(jié)構(gòu)體的內(nèi)存布局,包括字段之間的填充和結(jié)構(gòu)體末尾的填充。

using ObjectLayoutInspector;

TypeLayout.PrintLayout<Foo>();
TypeLayout.PrintLayout<Bar>();

struct Foo
{
    public int a;
    public long b;
    public byte c;
}

struct Bar
{
    public byte c;
    public int a;
    public long b;
}

輸出結(jié)果如下:

Type layout for 'Foo'
Size: 24 bytes. Paddings: 11 bytes (%45 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|   4-7: padding (4 bytes) |
|--------------------------|
|  8-15: Int64 b (8 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-23: padding (7 bytes) |
|==========================|


Type layout for 'Bar'
Size: 16 bytes. Paddings: 3 bytes (%18 of empty space)
|==========================|
|     0: Byte c (1 byte)   |
|--------------------------|
|   1-3: padding (3 bytes) |
|--------------------------|
|   4-7: Int32 a (4 bytes) |
|--------------------------|
|  8-15: Int64 b (8 bytes) |
|==========================|

Foo 和 Bar 雖然包含了相同類型的字段,但由于字段的順序不同,導(dǎo)致它們的內(nèi)存布局和填充字節(jié)數(shù)量也不同。Foo 需要在在末尾添加 7 字節(jié)的填充才能滿足其大小是最大字段對(duì)齊要求的倍數(shù)。

包含引用類型字段的結(jié)構(gòu)體的默認(rèn)字段布局

如果結(jié)構(gòu)體包含引用類型字段,則該結(jié)構(gòu)體的默認(rèn)布局為 LayoutKind.Auto

using ObjectLayoutInspector;

TypeLayout.PrintLayout<Foo>();

struct Foo
{
    public int a;
    public string b;
    public byte c;
}
Type layout for 'Foo'
Size: 16 bytes. Paddings: 3 bytes (%18 of empty space)
|===========================|
|   0-7: String b (8 bytes) |
|---------------------------|
|  8-11: Int32 a (4 bytes)  |
|---------------------------|
|    12: Byte c (1 byte)    |
|---------------------------|
| 13-15: padding (3 bytes)  |
|===========================|

用 StructLayoutAttribute 控制字段布局

在某些情況下,我們可能需要控制結(jié)構(gòu)體字段的內(nèi)存布局,以滿足特定的性能要求或與非托管代碼交互。可以使用 StructLayoutAttribute 特性來(lái)控制結(jié)構(gòu)體的內(nèi)存布局。

StructLayoutAttribute 有兩個(gè)重要的屬性:

  • LayoutKind:指定結(jié)構(gòu)體的布局方式,可以是 Sequential(按聲明順序排列)、Explicit(顯式指定字段偏移量)或 Auto(自動(dòng)布局)。

  • Pack:指定結(jié)構(gòu)體及其字段的對(duì)齊要求,其值必須為 0、1、2、4、8、16、32、64 或 128,否則無(wú)法編譯成功,默認(rèn)值為 0。** 指定 Pack > 8 時(shí), 等效于 Pack = 8,因?yàn)槟壳鞍姹緵](méi)有任何類型的對(duì)齊要求超過(guò) 8 字節(jié)。**

Pack 屬性在 LayoutKind.Auto 布局中無(wú)效。在 LayoutKind.Sequential 布局中,Pack 屬性用于指定字段的對(duì)齊要求及結(jié)構(gòu)體實(shí)例的對(duì)齊要求;在 LayoutKind.Explicit 布局中,Pack 屬性用于結(jié)構(gòu)體的對(duì)齊要求,會(huì)影響結(jié)構(gòu)體實(shí)例的末尾填充。

LayoutKind.Sequential#

Pack 為 0 時(shí)等于默認(rèn)布局#

using System.Runtime.InteropServices;
using ObjectLayoutInspector;

TypeLayout.PrintLayout<Foo>();

[StructLayout(LayoutKind.Sequential, Pack = 0)]
struct Foo
{
    public int a;
    public long b;
    public byte c;
}
Type layout for 'Foo'
Size: 24 bytes. Paddings: 11 bytes (%45 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|   4-7: padding (4 bytes) |
|--------------------------|
|  8-15: Int64 b (8 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-23: padding (7 bytes) |
|==========================|

Pack 不為 0 時(shí),取 Pack 和 字段類型大小 的較小值#

Pack 設(shè)置為 4 時(shí),int 和 long 字段的對(duì)齊要求都將被設(shè)置為 4 字節(jié),而 byte 字段的對(duì)齊要求仍然是 1 字節(jié)。結(jié)構(gòu)體的對(duì)齊要求是 4 字節(jié)。

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

{
    var foo = new Foo();

    // 方法 PrintPointerHeader 和 PrintPointerDetails 在前言部分已經(jīng)定義
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Foo
{
    public int a;
    public long b;
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 782597876240    | 4    | True          | 195649469060 |
| &foo.b          | 782597876244    | 8    | False         | 97824734530.5 |
| &foo.c          | 782597876252    | 1    | True          | 782597876252 |
Type layout for 'Foo'
Size: 16 bytes. Paddings: 3 bytes (%18 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|  4-11: Int64 b (8 bytes) |
|--------------------------|
|    12: Byte c (1 byte)   |
|--------------------------|
| 13-15: padding (3 bytes) |
|==========================|

結(jié)構(gòu)體實(shí)例的起始地址按 8 字節(jié) 對(duì)齊(782597876240 / 8 = 97824734530)。但其大小取滿足 4 字節(jié)對(duì)齊要求的最小整數(shù)倍 16 字節(jié)( 末尾字段 c 的偏移量為 12,最小只能取到 16),并在末尾添加 3 字節(jié)的填充。

Pack 設(shè)置為 1 時(shí),會(huì)形成密集的字段布局#

當(dāng) Pack 設(shè)置為 1 時(shí),所有字段的對(duì)齊要求都將被設(shè)置為 1 字節(jié),這意味著結(jié)構(gòu)體實(shí)例將按照 1 字節(jié)對(duì)齊。此時(shí),結(jié)構(gòu)體實(shí)例的字段將緊密排列,不會(huì)有額外的填充字節(jié)。

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

var foo = new Foo
{
    a = 1,
    b = 2,
    c = 3
};

unsafe
{
    // 方法 PrintPointerHeader 和 PrintPointerDetails 在前言部分已經(jīng)定義
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Foo
{
    public int a;
    public long b;
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 302463314288    | 4    | True          | 75615828572  |
| &foo.b          | 302463314292    | 8    | False         | 37807914286.5 |
| &foo.c          | 302463314300    | 1    | True          | 302463314300 |
Type layout for 'Foo'
Size: 13 bytes. Paddings: 0 bytes (%0 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|  4-11: Int64 b (8 bytes) |
|--------------------------|
|    12: Byte c (1 byte)   |
|==========================|

起始地址為 8 的倍數(shù)(302463314288 / 8 = 37807914286),但結(jié)構(gòu)體實(shí)例的大小變?yōu)?13 字節(jié),沒(méi)有末尾填充。

Pack 不為 0 的結(jié)構(gòu)體作為其他結(jié)構(gòu)體字段時(shí)#

如果外層的結(jié)構(gòu)體采用默認(rèn)字段布局則,則其實(shí)例的起始地址取決嵌套結(jié)構(gòu)體的最大字段默認(rèn)對(duì)齊要求,其實(shí)例大小取決該結(jié)構(gòu)體的最大字段對(duì)齊要求。

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

unsafe
{
    PrintPointerHeader();
    PrintPointerDetails(&bar.foo.a);
    PrintPointerDetails(&bar.foo.b);
    PrintPointerDetails(&bar.foo.c);
    PrintPointerDetails(&bar.d);
}

TypeLayout.PrintLayout<Bar>();

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Foo
{
    public int a;
    public long b;
    public byte c;
}

struct Bar
{
    public Foo foo;
    public int d;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &bar.foo.a      | 724703897336    | 4    | True          | 181175974334 |
| &bar.foo.b      | 724703897340    | 8    | False         | 90587987167.5 |
| &bar.foo.c      | 724703897348    | 1    | True          | 724703897348 |
| &bar.d          | 724703897352    | 4    | True          | 181175974338 |
Type layout for 'Bar'
Size: 20 bytes. Paddings: 3 bytes (%15 of empty space)
|==============================|
|  0-12: Foo foo (13 bytes)    |
| |==========================| |
| |   0-3: Int32 a (4 bytes) | |
| |--------------------------| |
| |  4-11: Int64 b (8 bytes) | |
| |--------------------------| |
| |    12: Byte c (1 byte)   | |
| |==========================| |
|------------------------------|
| 13-15: padding (3 bytes)     |
|------------------------------|
| 16-19: Int32 d (4 bytes)     |
|==============================|

在上面的例子中,Bar 結(jié)構(gòu)體包含一個(gè) Foo 類型的字段 foo。

Bar 的實(shí)例起始地址也滿足 8 字節(jié)對(duì)齊要求(724703897336 / 8 = 90587987167)。

foo 的對(duì)齊要求為 1 字節(jié), d 的對(duì)齊要求為 4 字節(jié),所以 Bar 的實(shí)例大小為 20 字節(jié)(4 的整數(shù)倍),并在foo 和 d 之間添加了 3 字節(jié)的填充。

LayoutKind.Explicit#

Pack 為 0 時(shí),結(jié)構(gòu)體按照最大字段默認(rèn)對(duì)齊要求對(duì)齊#

在 Explicit 布局中,我們需要顯式指定每個(gè)字段的偏移量。使用 FieldOffsetAttribute 來(lái)指定字段的偏移量。此時(shí)偏移量可以是任意值,甚至允許重疊字段。

此時(shí)雖然字段地址可能由于是任意值而不滿足對(duì)齊要求,但結(jié)構(gòu)體實(shí)例的起始地址依舊按 8 字節(jié) 對(duì)齊,且結(jié)構(gòu)體實(shí)例的大小是最大字段對(duì)齊要求的整數(shù)倍。

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

var foo = new Foo
{
    a = 1,
    b = 2,
    c = 3
};

unsafe
{
    // 方法 PrintPointerHeader 和 PrintPointerDetails 在前言部分已經(jīng)定義
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();

[StructLayout(LayoutKind.Explicit, Pack = 0)]

struct Foo
{
    [FieldOffset(0)]
    public int a;
    [FieldOffset(3)]
    public long b;
    [FieldOffset(11)]
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 6095151432      | 4    | True          | 1523787858   |
| &foo.b          | 6095151435      | 8    | False         | 761893929.38 |
| &foo.c          | 6095151443      | 1    | True          | 6095151443   |
Type layout for 'Foo'
Size: 16 bytes. Paddings: 4 bytes (%25 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|  3-10: Int64 b (8 bytes) |
|--------------------------|
|    11: Byte c (1 byte)   |
|--------------------------|
| 12-15: padding (4 bytes) |
|==========================|

上面例子中,Foo 結(jié)構(gòu)體的字段 a、b 和 c 的偏移量分別為 0、3 和 11??梢钥吹?,雖然字段的地址不再滿足對(duì)齊要求,但結(jié)構(gòu)體實(shí)例的起始地址仍然按 8 字節(jié) 對(duì)齊(6095151432 / 8 = 761893929),且結(jié)構(gòu)體實(shí)例的大小為 16 字節(jié)(最大字段對(duì)齊要求的整數(shù)倍),末尾添加了 4 字節(jié)的填充。

如果將 c 字段的偏移量改為 16,則結(jié)構(gòu)體實(shí)例的大小將變?yōu)?24 字節(jié),并且會(huì)在末尾添加 7 字節(jié)的填充。

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

var foo = new Foo
{
    a = 1,
    b = 2,
    c = 3
};

unsafe
{
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 6166536512      | 4    | True          | 1541634128   |
| &foo.b          | 6166536515      | 8    | False         | 770817064.38 |
| &foo.c          | 6166536528      | 1    | True          | 6166536528   |
Type layout for 'Foo'
Size: 24 bytes. Paddings: 12 bytes (%50 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|  3-10: Int64 b (8 bytes) |
|--------------------------|
| 11-15: padding (5 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-23: padding (7 bytes) |
|==========================|

Pack 不為 0 時(shí),結(jié)構(gòu)體實(shí)例按照 Pack 與 最大字段對(duì)齊要求 的較小值對(duì)齊#

在 Explicit 布局中,如果設(shè)置了 Pack 屬性且不為 0,則結(jié)構(gòu)體實(shí)例將按照 Pack 的值對(duì)齊。字段的偏移量仍然可以是任意值,但結(jié)構(gòu)體實(shí)例的大小將受到 Pack 屬性的影響。

var foo = new Foo
{
    a = 1,
    b = 2,
    c = 3
};

unsafe
{
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();

[StructLayout(LayoutKind.Explicit, Pack = 4)]
struct Foo
{
    [FieldOffset(0)]
    public int a;
    [FieldOffset(5)]
    public long b;
    [FieldOffset(16)]
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 6122676544      | 4    | True          | 1530669136   |
| &foo.b          | 6122676549      | 8    | False         | 765334568.63 |
| &foo.c          | 6122676560      | 1    | True          | 6122676560   |
Type layout for 'Foo'
Size: 20 bytes. Paddings: 7 bytes (%35 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|     4: padding (1 byte)  |
|--------------------------|
|  5-12: Int64 b (8 bytes) |
|--------------------------|
| 13-15: padding (3 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-19: padding (3 bytes) |
|==========================|

在上面的例子中,由于 Pack 屬性設(shè)置為 4,與 long 類型的 8 字節(jié) 比較則結(jié)構(gòu)體實(shí)例應(yīng)按照 4 字節(jié) 對(duì)齊。因?yàn)?nbsp;c 的偏移量為 16,所以 Foo 的大小在取值此時(shí)符合條件的 4 的最小整數(shù)倍后變?yōu)?20 字節(jié),并在末尾添加了 3 字節(jié) 的填充。

改成 Pack = 128 后,結(jié)構(gòu)體實(shí)例的大小按照最大字段默認(rèn)對(duì)齊要求 8 字節(jié) 對(duì)齊。

var foo = new Foo
{
    a = 1,
    b = 2,
    c = 3
};

unsafe
{
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();

[StructLayout(LayoutKind.Explicit, Pack = 128)]
struct Foo
{
    [FieldOffset(0)]
    public int a;
    [FieldOffset(5)]
    public long b;
    [FieldOffset(16)]
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 6104211776      | 4    | True          | 1526052944   |
| &foo.b          | 6104211781      | 8    | False         | 763026472.63 |
| &foo.c          | 6104211792      | 1    | True          | 6104211792   |
Type layout for 'Foo'
Size: 24 bytes. Paddings: 11 bytes (%45 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|     4: padding (1 byte)  |
|--------------------------|
|  5-12: Int64 b (8 bytes) |
|--------------------------|
| 13-15: padding (3 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|--------------------------|
| 17-23: padding (7 bytes) |
|==========================|

將 Pack 屬性設(shè)置為 1 可以消除結(jié)構(gòu)體實(shí)例的末尾填充#

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

var foo = new Foo
{
    a = 1,
    b = 2,
    c = 3
};
unsafe
{
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();

[StructLayout(LayoutKind.Explicit, Pack = 1)]
struct Foo
{
    [FieldOffset(0)]
    public int a;
    [FieldOffset(5)]
    public long b;
    [FieldOffset(16)]
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 468685679112    | 4    | True          | 117171419778 |
| &foo.b          | 468685679117    | 8    | False         | 58585709889.63 |
| &foo.c          | 468685679128    | 1    | True          | 468685679128 |
Type layout for 'Foo'
Size: 17 bytes. Paddings: 4 bytes (%23 of empty space)
|==========================|
|   0-3: Int32 a (4 bytes) |
|--------------------------|
|     4: padding (1 byte)  |
|--------------------------|
|  5-12: Int64 b (8 bytes) |
|--------------------------|
| 13-15: padding (3 bytes) |
|--------------------------|
|    16: Byte c (1 byte)   |
|==========================|

此時(shí)實(shí)例的起始地址仍然按 8 字節(jié) 對(duì)齊(468685679112 / 8 = 58585709889),但實(shí)例的大小則是 17 字節(jié),末尾填充為 0 字節(jié)。

Pack 屬性不為 0 的結(jié)構(gòu)體作為其他結(jié)構(gòu)體字段時(shí)#

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

var bar = new Bar
{
    foo = new Foo(),
    d = 4
};

unsafe
{
    PrintPointerHeader();
    PrintPointerDetails(&bar.foo.a);
    PrintPointerDetails(&bar.foo.b);
    PrintPointerDetails(&bar.foo.c);
    PrintPointerDetails(&bar.d);
}

TypeLayout.PrintLayout<Bar>();

[StructLayout(LayoutKind.Explicit, Pack = 1)]
struct Foo
{
    [FieldOffset(0)]
    public int a;
    [FieldOffset(5)]
    public long b;
    [FieldOffset(16)]
    public byte c;
}

struct Bar
{
    public Foo foo;
    public int d;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &bar.foo.a      | 967090628200    | 4    | True          | 241772657050 |
| &bar.foo.b      | 967090628205    | 8    | False         | 120886328525.63 |
| &bar.foo.c      | 967090628216    | 1    | True          | 967090628216 |
| &bar.d          | 967090628220    | 4    | True          | 241772657055 |
Type layout for 'Bar'
Size: 24 bytes. Paddings: 7 bytes (%29 of empty space)
|==============================|
|  0-16: Foo foo (17 bytes)    |
| |==========================| |
| |   0-3: Int32 a (4 bytes) | |
| |--------------------------| |
| |     4: padding (1 byte)  | |
| |--------------------------| |
| |  5-12: Int64 b (8 bytes) | |
| |--------------------------| |
| | 13-15: padding (3 bytes) | |
| |--------------------------| |
| |    16: Byte c (1 byte)   | |
| |==========================| |
|------------------------------|
| 17-19: padding (3 bytes)     |
|------------------------------|
| 20-23: Int32 d (4 bytes)     |
|==============================|

在上面的例子中,Bar 結(jié)構(gòu)體包含一個(gè) Foo 類型的字段 foo,由于 Foo 的最大字段對(duì)齊要求為 8 字節(jié),所以 Bar 的實(shí)例起始地址也滿足 8 字節(jié)對(duì)齊要求(967090628200 / 8 = 120886328525)。

foo 的對(duì)齊要求為 1 字節(jié), d 的對(duì)齊要求為 4 字節(jié),所以 Bar 的實(shí)例大小為 24 字節(jié)(4 的整數(shù)倍),并在foo 和 d 之間添加了 3 字節(jié)的填充。

LayoutKind.Auto#

使用 LayoutKind.Auto 時(shí),runtime 將根據(jù)字段的類型和聲明順序自動(dòng)確定字段的布局,會(huì)調(diào)整實(shí)例字段的排列順序和對(duì)齊要求,以優(yōu)化內(nèi)存布局和性能。

LayoutKind.Auto 也是引用類型實(shí)例字段的默認(rèn)布局方式。

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ObjectLayoutInspector;

var foo = new Foo
{
    a = 1,
    b = 2,
    c = 3
};

unsafe
{
    PrintPointerHeader();
    PrintPointerDetails(&foo.a);
    PrintPointerDetails(&foo.b);
    PrintPointerDetails(&foo.c);
}

TypeLayout.PrintLayout<Foo>();


[StructLayout(LayoutKind.Auto)]
struct Foo
{
    public int a;
    public long b;
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &foo.a          | 6166815056      | 4    | True          | 1541703764   |
| &foo.b          | 6166815048      | 8    | True          | 770851881    |
| &foo.c          | 6166815060      | 1    | True          | 6166815060   |
Type layout for 'Foo'
Size: 16 bytes. Paddings: 3 bytes (%18 of empty space)
|==========================|
|   0-7: Int64 b (8 bytes) |
|--------------------------|
|  8-11: Int32 a (4 bytes) |
|--------------------------|
|    12: Byte c (1 byte)   |
|--------------------------|
| 13-15: padding (3 bytes) |
|==========================|

上面例子中,Foo 結(jié)構(gòu)體的字段 b 被放在了前面,各字段都按照其類型大小進(jìn)行了對(duì)齊,相較于默認(rèn)布局,Foo 結(jié)構(gòu)體的內(nèi)存布局更加緊湊,減少了填充字節(jié)的數(shù)量。

等效于于下面的結(jié)構(gòu)體定義

[StructLayout(LayoutKind.Sequential, Pack = 0)]
struct Foo
{
    public long b;
    public int a;
    public byte c;
}

作為數(shù)組元素時(shí)的結(jié)構(gòu)體實(shí)例

默認(rèn)字段布局#

默認(rèn)布局的結(jié)構(gòu)體實(shí)例在數(shù)組中也會(huì)按照最大字段對(duì)齊要求進(jìn)行對(duì)齊。每個(gè)結(jié)構(gòu)體實(shí)例的起始地址都是該結(jié)構(gòu)體最大字段對(duì)齊要求的整數(shù)倍。

因此默認(rèn)布局下,數(shù)組中的每個(gè)結(jié)構(gòu)體的字段都是滿足對(duì)齊要求的。

using System.Runtime.CompilerServices;

unsafe
{
    // 讀者也可以替換成堆上分配的數(shù)組來(lái)查看運(yùn)行結(jié)果
    var arr = stackalloc Foo[] { new Foo(), new Foo() };

    PrintPointerHeader();
    
    PrintPointerDetails(&arr[0].a);
    PrintPointerDetails(&arr[0].b);
    PrintPointerDetails(&arr[0].c);
    PrintPointerDetails(&arr[1].a);
    PrintPointerDetails(&arr[1].b);
    PrintPointerDetails(&arr[1].c);
}

struct Foo
{
    public int a;
    public long b;
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &arr[0].a       | 1029625933216   | 4    | True          | 257406483304 |
| &arr[0].b       | 1029625933224   | 8    | True          | 128703241653 |
| &arr[0].c       | 1029625933232   | 1    | True          | 1029625933232 |
| &arr[1].a       | 1029625933240   | 4    | True          | 257406483310 |
| &arr[1].b       | 1029625933248   | 8    | True          | 128703241656 |
| &arr[1].c       | 1029625933256   | 1    | True          | 1029625933256 |

非默認(rèn)字段布局#

因?yàn)閿?shù)組中結(jié)構(gòu)體實(shí)例是連續(xù)存儲(chǔ)的,如果結(jié)構(gòu)體實(shí)例的字段布局進(jìn)行了非默認(rèn)的調(diào)整,則可能導(dǎo)致第二個(gè)開始的構(gòu)體實(shí)例完全不滿足對(duì)齊要求(包括實(shí)例的起始地址和字段地址)。

using System.Runtime.CompilerServices;

unsafe
{
    // 讀者也可以替換成堆上分配的數(shù)組來(lái)查看運(yùn)行結(jié)果
    var arr = stackalloc Foo[] { new Foo(), new Foo() };

    PrintPointerHeader();
    
    PrintPointerDetails(&arr[0].a);
    PrintPointerDetails(&arr[0].b);
    PrintPointerDetails(&arr[0].c);
    PrintPointerDetails(&arr[1].a);
    PrintPointerDetails(&arr[1].b);
    PrintPointerDetails(&arr[1].c);
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Foo
{
    public int a;
    public long b;
    public byte c;
}
| Expr            | Address         | Size | AlignedBySize | Addr/Size    |
| &arr[0].a       | 654696769936    | 4    | True          | 163674192484 |
| &arr[0].b       | 654696769940    | 8    | False         | 81837096242.5 |
| &arr[0].c       | 654696769948    | 1    | True          | 654696769948 |
| &arr[1].a       | 654696769949    | 4    | False         | 163674192487.25 |
| &arr[1].b       | 654696769953    | 8    | False         | 81837096244.13 |
| &arr[1].c       | 654696769961    | 1    | True          | 654696769961 |

轉(zhuǎn)自https://www.cnblogs.com/eventhorizon/p/18913041


該文章在 2025/6/9 9:19:33 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved