获取结构体变量的位置,也称记录字段的偏移量

4
我想获取一个结构/记录的“位置”。
比如说,我有这个记录:
type
  MyStruct = record
    MyInteger   : Integer;
    MyInteger2  : Integer;
    MyInteger3  : Integer;
    MyFunc      : function (FirstParam : WideString; SecondParam : String) : Integer;
    MyString    : String;
    MyString2   : WideString;
    MyPchar     : pchar;
end;

如您所见,这个记录的大小为28字节(7个变量x 4字节)。基本上因为所有的变量都是4字节变量(如整数)或指针(也是4字节)。 现在假设我们在一个内存地址(X = 0)中加载了这个结构体(这也意味着MyInteger的地址为0)。 例如,MyInteger3的地址将为8(注意X = 0!) 我如何动态获取结构体的位置(编号/地址)?
希望你们知道我的意思? 提前致谢。
顺便说一句: 在结构体中,任何变量都是4字节吗? 编辑: 如果你修正空格,这是错误的:String[100]

7
不,结构体中的变量并不总是4字节(像Word、Byte、Boolean、枚举类型等小于4字节的数据类型),并且它们不总是在4字节内存边界上对齐(紧凑记录、对齐于Word或Int64的记录等)。而指针在64位系统上占用8字节。 - Remy Lebeau
1
@Remy - 你也没有提到编译器指令,开发者可以通过它们改变对齐方式为1、2、4或8字节边界。 - Arioch 'The
2个回答

10

您可以使用一些指针算术运算来获取任何记录成员的偏移量:

type
  PMyStruct = ^MyStruct;

var
  Offset: Integer;
begin
  Offset := Integer(@(PMyStruct(nil).MyInteger3));
  // or:
  // Offset := Integer(Addr(PMyStruct(nil).MyInteger3));
end;

如果你想获取函数的偏移量,你需要像这样编写代码:

Offset := Integer(@@PMyStruct(nil).MyFunc);
// or:
// Offset := Integer(Addr(@PMyStruct(nil).MyFunc));

1
请注意,这对函数无效。请参考我的示例,了解如何解决这个问题。 - Wouter van Nifterick
编译器会把这样的表达式识别为常量吗? - David Heffernan
1
@WoutervanNifterick,你也可以像Remy一样轻松地实现无实例的方法。请看我的更新。 - David Heffernan
@DavidHeffernan:创意解决方案。我会提名它为Nick每周有趣代码的候选 :) - Wouter van Nifterick
1
如果您的编译器版本是Delphi-2006或更高版本,则可以在MyStruct中添加一个类函数:class function MyInteger3Offs: Integer; static;,并实现如下:class function MyStruct.MyInteger3Offs: Integer; begin Result := Integer(@(PMyStruct(nil).MyInteger3)); end; - LU RD

7
program OffsetTest; {$APPTYPE CONSOLE}

type
  f = function (FirstParam : WideString; SecondParam : String) : Integer;
  TStruct = record
    MyInteger   : Integer;
    MyInteger2  : Integer;
    MyInteger3  : Integer;
    MyFunc      : ^f;
    MyString    : String;
    MyString2   : WideString;
    MyPchar     : pchar;
  end;

var MyStruct :TStruct;

begin
  Writeln( int64(@(MyStruct.MyInteger )) - int64(@(MyStruct)));
  Writeln( int64(@(MyStruct.MyInteger2)) - int64(@(MyStruct)));
  Writeln( int64(@(MyStruct.MyInteger3)) - int64(@(MyStruct)));
  Writeln( int64(@(MyStruct.MyFunc    )) - int64(@(MyStruct)));
  Writeln( int64(@(MyStruct.MyString2 )) - int64(@(MyStruct)));
  Writeln( int64(@(MyStruct.MyPchar   )) - int64(@(MyStruct)));
  Readln;
end.

32位版本的结果:

0 
4 
8
12
20
24

使用64位版本的结果:

0
4
8
16
32
40

64位打包记录的结果:

0
4
8
12
28
36

展示不同结果,以指出在您的代码中依赖这些偏移量可能是一个不好的想法。或者至少要非常注意不同的情况可能会导致不同的偏移量。

1
这个也可以正常工作!非常微小的缺点是你必须分配一个结构体。 - Ben
1
+1,同时也要指出不同对齐方式的陷阱。 - Marjan Venema

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接