6

For reference: The offsetof macro(!) takes a struct data type and a member of the specified struct as arguments and returns an integer offset of the given member relative to the beginning of the struct. See the detailed overview and references.

The logic behind the generic offsetof is quite simple and can be reproduced in Delphi with ease (more or less) (literally with ease, elimination of predeclared pointer type requirement makes it a basic inline expression, see David Heffernan's answer and comment about swapping reference and dereference operators) as in-place code. However, I see absolutely no way to convert the in-place code solution to the reusable function. Can we actually do that?

Community
  • 1
  • 1
Free Consulting
  • 4,300
  • 1
  • 29
  • 50
  • Possible duplicate, [`Delphi: Offset of record field`](http://stackoverflow.com/q/14462103/576719). – LU RD Sep 13 '13 at 13:26
  • 2
    This seems to me to be a subtly different question. The asker knows how to write code to achieve the desired effect, but wants to be able to write it in the same neat and tidy way that `offsetof` allows. – David Heffernan Sep 13 '13 at 13:32
  • @DavidHeffernan, you are correct. Voting to reopen. – LU RD Sep 13 '13 at 13:48
  • 1
    That's a long standing FPC feature request http://bugs.freepascal.org/view.php?id=15416 – Marco van de Voort Sep 13 '13 at 19:10

2 Answers2

16

Without a pre-processor or a built-in function, there's no way to do it quite as cleanly as the offsetof macro. The way that offsetof is able to do it so cleanly is that the pre-processor does the work. In fact some compilers implement it as a built-in, but that's beside the point. Delphi has no pre-processor, and no built-in offsetof.

The cleanest solution I know is like this:

NativeUInt(@TMyRecord(nil^).MyField)

But that is nothing like as clean as

offsetof(struct MyStruct, MyField)
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Native[U]Int to preserve portability – Free Consulting Sep 13 '13 at 13:18
  • @FreeConsulting Well maybe, but I think that you are unlikely to encounter a struct with size > 2GB. And I hope that your code that uses this is going to assign the result to a Native[U]Int! ;-) Thanks for the edit, and you corrected my other mistake too. – David Heffernan Sep 13 '13 at 13:20
  • 1
    @FreeConsulting I swapped around the pointer de-reference so that the code doesn't require the user to define a bespoke PMyRecord just to use this trick. I think this is as clean as it can be done. But it's rather yucky compared to C. – David Heffernan Sep 13 '13 at 13:28
  • 3
    FWIW: If you have a need for a lot of these constructs you can declare `const p = PMyRecord(nil)` and use it like `Whatever(p^.MyField)`. – Uli Gerhardt Sep 13 '13 at 15:37
  • @UliGerhardt that's a tidy as it gets, but should be `NativeUInt(@p.MyField)` shouldn't it? – Marc Durdin Jun 22 '16 at 22:21
  • @MarcDurdin, no - `p` is a pointer and needs to be dereferenced with `^` to access its fields. `@p` would give its address, a pointer to a pointer to a record. – Uli Gerhardt Jun 23 '16 at 07:11
  • @UliGerhardt, `@p.MyField` gets the address of `p.MyField`, not the address of `p`. That's what we want; we don't want to access the values of the fields (which is what the dereference operator `^` does), because we are not pointing to real data, we are pointing to `nil`. Dereferencing `nil` will of course cause an access violation. – Marc Durdin Jun 23 '16 at 07:19
  • @Marc Not quite. `@p.MyField` is the same as `@p^.MyField` – David Heffernan Jun 23 '16 at 07:24
  • @Uli in Delphi the `^` can be omitted when dereferencing a pointer to record – David Heffernan Jun 23 '16 at 07:25
  • @David, yes, that's true, but as you don't need the `^` in this instance, why use it? Both versions return the address of `MyField`, not `p`. As @Uli's first example did not include the `@`, it resulted in a dereference of `p` and an access violation. See also my answer below for a full example. – Marc Durdin Jun 23 '16 at 07:29
  • @David: Yes, unfortunately. :-) – Uli Gerhardt Jun 23 '16 at 07:30
  • 1
    @Marc, I use it exactly like that without AV. My `Whatever` routine takes an untyped `var` parameter and does some pointer arithmetic inside. – Uli Gerhardt Jun 23 '16 at 07:31
  • @Uli, Yes that works, as a `var` parameter is basically just a pointer, so the dereference is then elided by the compiler. But it wouldn't work if you weren't passing it to a function with a `var` parameter. :) – Marc Durdin Jun 23 '16 at 07:34
  • 1
    @Marc De-referencing nil is absolutely fine. See the code in my answer for example. What is not fine is accessing that memory. You can de-reference nil and then take the address which is exactly what you do in your answer. – David Heffernan Jun 23 '16 at 07:44
1

To expand on @DavidHeffernan's answer and @UliGerhardt's comment, here's what we now use, tested and in use:

procedure foo;
type
  TPerson = record
    DOB: TDate;
    FirstName: string;
    LastName: string;
  end;
const
  Offset_TPerson: ^TPerson = nil;
begin
  writeln('OffsetOf FirstName = '+IntToStr(NativeUInt(@Offset_TPerson.FirstName)));
end;
Community
  • 1
  • 1
Marc Durdin
  • 1,675
  • 2
  • 20
  • 27
  • The downside is the extra const declaration which is not needed and I personally prefer to avoid such declarations on grounds of clutter. – David Heffernan Jun 23 '16 at 07:46
  • @David, IMHO it makes the inline expression more readable (especially if one has multiple occurrences). – Uli Gerhardt Jun 23 '16 at 07:56
  • @UliGerhardt Is `@TMyRecord(nil^).MyField` really harder to read than `@Offset_TMyRecord.MyField`? The former has the benefit of having all the information right there and not requiring you to consult something declared elsewhere. I understand that this comes down to taste to a degree, but I suspect that most of the dislike of the former comes down to a subconscious distaste for `nil^`. For instance, Marc objected to the use of `^` on the grounds that nil was being de-referenced. But in fact de-referencing `nil` is perfectly valid, it's what happens after that matters. tbc – David Heffernan Jun 23 '16 at 08:01
  • 1
    contd. So Marc prefers a version that does not include `^` out of fear of de-referencing `nil`. But in fact the code in this answer does de-reference `nil`. The expressions `Offset_TPerson.FirstName` and `Offset_TPerson^.FirstName` have identical meaning. The compiler accepts the former as a matter of convenience. Convenience that was added (perhaps accidentally) when the compiler was extended to support Delphi 1 class instance references. In my view, I actually prefer to see `nil^` because it makes it crystal clear what is really going on. – David Heffernan Jun 23 '16 at 08:03
  • 1
    But fundamentally it all comes down to personal preference, I do agree. Just don't kid yourself that you aren't writing `nil^` because you obfuscated it! ;-) – David Heffernan Jun 23 '16 at 08:04
  • 1
    It all comes down to preference. :-) – Marc Durdin Jun 23 '16 at 08:06
  • @David is correct here, Marc. Delphi implicitly dereferences pointers to structured types (to support classes), so basically you are doing `Offset_TPerson^.FirstName` – Free Consulting Aug 25 '16 at 13:39