www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Address of a class object

reply Paul <phshaffer gmail.com> writes:
Hello.  Thanks for any assistance.

Can I acquire the address of a class object, not a class variable 
(i.e. the instantiations of the class) but the object definition 
itself?

```d
class MyClass {char c}
...
MyClass MyClassVar;

writeln(&MyClassVar); // this compiles
writeln(&MyClass);    // this does not
```
Dec 31 2022
next sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Sun, Jan 01, 2023 at 12:35:40AM +0000, Paul via Digitalmars-d-learn wrote:
 Hello.  Thanks for any assistance.
 
 Can I acquire the address of a class object, not a class variable
 (i.e. the instantiations of the class) but the object definition
 itself?
 
 ```d
 class MyClass {char c}
 ...
 MyClass MyClassVar;
 
 writeln(&MyClassVar); // this compiles
 writeln(&MyClass);    // this does not
 ```
What's your purpose in doing this? Maybe if you explain what you're trying to accomplish, we can better understand how to help you. The class definition does not have an address, because it's an abstract definition that only exists during compilation. At runtime the class definition isn't stored anywhere. So it isn't possible to take its address. There is, however, typeid(MyClass), which is a runtime structure that gives you some amount of information about the class. But the use of typeid is discouraged due to some inherent issues with its design. T -- Designer clothes: how to cover less by paying more.
Dec 31 2022
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 12/31/22 16:35, Paul wrote:

 Can I acquire the address of a class object,
Answering that question literally, yes, you can by casting the class variable to void*. But note: 'class object' means the instance of class in D.
 not a class variable (i.e.
 the instantiations of the class)
D terminology is different there: A class variable is a reference to the class object (that carries the member variables, etc. of a class instance.) auto c = new C(); 'c' is the *variable*, providing access to the *object* in dynamic memory.
 but the object definition itself?
As H. S. Teoh answered, Python etc. can do that but not D's compilation model. The following program tries to demonstrate that the members are offset two void* sizes further from the address of the object: class C { int i; } void main() { auto c = new C(); const likelyOffsetOfMembers = 2 * (void*).sizeof; // HERE: const objectAddrInDynamicMemory = cast(void*)c; assert(objectAddrInDynamicMemory + likelyOffsetOfMembers == &c.i); } If the class is defined as extern(C++), then you must replace 2 above with 1: extern(C++) class C { int i; } Ali
Dec 31 2022
parent reply Paul <phshaffer gmail.com> writes:
Thanks all. Yes it seems my understanding and "D" vocabulary are 
still a bit confused.

So I'm taking a D course online and was trying to verify what I 
was learning. The course example printed out the size and 
alignment of types...something close to this:
```d
import std.stdio;
import std.traits;

class MyClass {char c;}

void main() {
     writeln(" Size  Alignment  Type\n",
             "=========================");

         size_t size = __traits(classInstanceSize, MyClass);
         size_t alignment = classInstanceAlignment!MyClass;

     writefln("%4s%8s      %s",size, alignment, MyClass.stringof);

	// my test code added
	MyClass MyClassO1;
	MyClass	MyClassO2;
	writeln("\n",&MyClassO1,"\t",&MyClassO2);
}
```
...on my laptop it prints...
```
  Size  Alignment  Type
=========================
    9       4      MyClass

4FFB20  4FFB24
```

If the size of MyClass is 9 bytes why do MyClassO1 & O2 addresses 
only differ by 4 bytes?

Because those addresses(4FFB20  4FFB24) are the addresses of the 
class **variables**, not the addresses of the **objects** 
themselves?

So, I guess my question is actually how do I print out the 
addresses of the MyClassO1 & O2 objects themselves?
```
Jan 01 2023
next sibling parent reply matheus <matheus gmail.com> writes:
On Sunday, 1 January 2023 at 09:01:24 UTC, Paul wrote:
 ...
 If the size of MyClass is 9 bytes why do MyClassO1 & O2 
 addresses only differ by 4 bytes?

 Because those addresses(4FFB20  4FFB24) are the addresses of 
 the class **variables**, not the addresses of the **objects** 
 themselves?
Because MyClass01 and MyClass02 are pointers and in your case they differ 4 bytes each other. Now you could do this: import std.stdio, std.traits; class MyClass {char[16] c;} void main() { writeln(" Size Alignment Type\n", "========================="); size_t size = __traits(classInstanceSize, MyClass); size_t alignment = classInstanceAlignment!MyClass; writefln("%4s%8s %s",size, alignment, MyClass.stringof); // my test code added MyClass MyClassO1; MyClass MyClassO2; writeln("\n",&MyClassO1,"\t",&MyClassO2); writeln("\n",&(MyClassO1.c),"\t",&(MyClassO2.c)); MyClassO1 = new MyClass(); MyClassO2 = new MyClass(); writeln("\n",&MyClassO1,"\t",&MyClassO2); writeln("\n",&(MyClassO1.c),"\t",&(MyClassO2.c)); } In this machine it will print: Size Alignment Type ========================= 32 8 MyClass 7FFD890C6410 7FFD890C6418 <- &MyClassO1 &MyClassO2 10 10 <- Note here [&(MyClassO1.c), &(MyClassO2.c)] 7FFD890C6410 7FFD890C6418 <- &MyClassO1 &MyClassO2 (Same address) 7FD0435D8010 7FD0435D8030 <- Now after instantiation! [&(MyClassO1.c), &(MyClassO2.c)] Finally as you can see I changed your: class MyClass {char c;} to: class MyClass {char[16] c;} Because from char[1] to char[16] it will keep the address difference for [&(MyClassO1.c), &(MyClassO2.c)] by 0x20 (32): 7FD0435D8010 7FD0435D8030 If I change to char[17] The difference goes up from 0x20 (32) to 0x30 (48), and keeps that way until char[32]: 7FD0435D8010 7FD0435D8040 char[33] will increase again by 16 bytes and so on. Matheus.
Jan 01 2023
next sibling parent Paul <phshaffer gmail.com> writes:
Thank you, Teoh, Ali, & Matheus
Jan 02 2023
prev sibling parent reply Paul <phshaffer gmail.com> writes:
matheus, using dmd64 on my laptop to compile and run this:
```d
import std.stdio, std.traits;

class MyClass {char[16] c;}

void main() {
     writeln(" Size  Alignment  Type\n",
             "=========================");

     size_t size = __traits(classInstanceSize, MyClass);
     size_t alignment = classInstanceAlignment!MyClass;
     writefln("%4s%8s      %s",size, alignment, MyClass.stringof);

     // my test code added
     auto MyClassO1 = new MyClass();
     auto MyClassO2 = new MyClass();
     writeln("\nMyClassObj1     MyClassObj2");
     writeln(cast(void*)MyClassO1,"\t",cast(void*)MyClassO2);
}
```
I get this:
```
  Size  Alignment  Type
=========================
   32       8      MyClass

MyClassObj1     MyClassObj2
21EF8E22000     21EF8E22020
```
If I change this line to:
```
class MyClass {char[1] c;}
```
I get this:
```
  Size  Alignment  Type
=========================
   17       8      MyClass

MyClassObj1     MyClassObj2
27727202000     27727202020
```
If my size is 17 bytes and my alignment is 8 bytes, shouldn't my 
MyClassObj2 in this example be   277272020**18** ?
Jan 03 2023
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/3/23 20:01, Paul wrote:

   Size  Alignment  Type
 =========================
    17       8      MyClass

 MyClassObj1     MyClassObj2
 27727202000     27727202020
 ```
 If my size is 17 bytes and my alignment is 8 bytes, shouldn't my
 MyClassObj2 in this example be   277272020**18** ?
Good question. I made some guesses about object layouts, GC allocation schemes, etc. but did not like any of them. Ali
Jan 04 2023
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Jan 04, 2023 at 09:51:05AM -0800, Ali Çehreli via Digitalmars-d-learn
wrote:
 On 1/3/23 20:01, Paul wrote:
 
   Size  Alignment  Type
 =========================
    17       8      MyClass

 MyClassObj1     MyClassObj2
 27727202000     27727202020
 ```
 If my size is 17 bytes and my alignment is 8 bytes, shouldn't my
 MyClassObj2 in this example be   277272020**18** ?
Good question. I made some guesses about object layouts, GC allocation schemes, etc. but did not like any of them.
[...] Allocations are not necessarily consecutive; the GC may have its own strategy of allocation that doesn't follow a linear sequence. Furthermore, GC-allocated blocks may be larger than the request size because there may be some extra management information stored in the block (but outside the pointer range returned). Basically, addresses returned by an allocator (GC or otherwise) should be treated as opaque handles, whose exact values are implementation- dependent and not really relevant to application code other than identifying the allocated object. T -- We've all heard that a million monkeys banging on a million typewriters will eventually reproduce the entire works of Shakespeare. Now, thanks to the Internet, we know this is not true. -- Robert Wilensk
Jan 04 2023
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/4/23 10:48, H. S. Teoh wrote:

 Allocations are not necessarily consecutive; the GC may have its own
 strategy of allocation that doesn't follow a linear sequence.
That was one of my guesses. So, I put the objects into a 2-length static array but the difference was still 0x20. (?)
 Furthermore, GC-allocated blocks may be larger than the request size
 because there may be some extra management information stored in the
 block (but outside the pointer range returned).
Good point. I think the minimum size of a dynamically allocated memory of the current GC implementation is 32 bytes. I've just realized that I was confusing myself by thinking the object pointer that cast(void*) produces was the first member's address. I was wrong: That is the address of the first of the two hidden members (the vtbl pointer and the monitor). My current guess is, although it could be a void*, the alignment of the first of those hidden members is 16. Now that I have a more correct understanding, the following program prints those hidden members. One of the examples shows the monitor of an object used with 'synchronized' is non-null. import std.stdio, std.traits; class MyClass { char[1] c; } void main() { writeln(" Size Alignment Type\n", "========================="); size_t size = __traits(classInstanceSize, MyClass); size_t alignment = classInstanceAlignment!MyClass; writefln("%4s%8s %s",size, alignment, MyClass.stringof); // Apologies for using lower-cased variable names. :) auto a = new MyClass(); auto b = new MyClass(); printObject!a; printObject!b; auto withMonitor = new MyClass(); synchronized (withMonitor) { // This object's "hidden 1" (the monitor) will not be 'null' printObject!withMonitor; } // This object's "hidden 0" (vtbl pointer) will be different: class SubClass : MyClass { } auto sub = new SubClass(); printObject!sub; } void printObject(alias obj)() { writeln("\n"); writeln("name : ", obj.stringof); const addr = cast(void*)obj; writeln("address : ", addr); writeln("hidden 0 : ", hiddenValue(addr, 0)); writeln("hidden 1 : ", hiddenValue(addr, 1)); } void* hiddenValue(const(void)* obj, size_t index) { alias HiddenType = void*; auto ptrToHiddens = cast(HiddenType[2]*)obj; return (*ptrToHiddens)[index]; } Here is the output: Size Alignment Type ========================= 17 8 MyClass name : a address : 7F4009D48000 hidden 0 : 558A1E172520 hidden 1 : null name : b address : 7F4009D48020 hidden 0 : 558A1E172520 hidden 1 : null name : withMonitor address : 7F4009D48040 hidden 0 : 558A1E172520 hidden 1 : 558A20164B80 <-- non-null monitor name : sub address : 7F4009D48060 hidden 0 : 558A1E172630 <-- Different vtbl for sub-class hidden 1 : null Ali
Jan 04 2023
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/4/23 11:27, Ali Çehreli wrote:

      writeln("hidden 0    : ", hiddenValue(addr, 0));
      writeln("hidden 1    : ", hiddenValue(addr, 1));
Silly me! :) Those members have names: writeln("__vptr : ", obj.__vptr); writeln("__monitor : ", obj.__monitor); https://dlang.org/spec/abi.html#classes Ali
Jan 04 2023
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/4/23 2:27 PM, Ali Çehreli wrote:
 On 1/4/23 10:48, H. S. Teoh wrote:
 
  > Allocations are not necessarily consecutive; the GC may have its own
  > strategy of allocation that doesn't follow a linear sequence.
 
 That was one of my guesses. So, I put the objects into a 2-length static 
 array but the difference was still 0x20. (?)
Are you putting the class *references* in a 2-length static array? That's just going to be 2 pointers in an array, and won't affect where they are allocated on the heap.
  > Furthermore, GC-allocated blocks may be larger than the request size
  > because there may be some extra management information stored in the
  > block (but outside the pointer range returned).
 
 Good point. I think the minimum size of a dynamically allocated memory 
 of the current GC implementation is 32 bytes.
It is 16 bytes, but there are no 24-byte bins. Here are the bin sizes: https://github.com/dlang/dmd/blob/4dc7259a89950b0a0feda05b8c35e52cadd00c95/druntime/src/core/internal/gc/impl/conservative/gc.d#L1402-L1424 And of course, this could change again. Basically, follow H. S. Teoh's advice, stop worrying about the addresses given out by the GC, it's not important. -Steve
Jan 04 2023
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/4/23 12:02, Steven Schveighoffer wrote:
 On 1/4/23 2:27 PM, Ali Çehreli wrote:
 I put the objects into a 2-length
 static array but the difference was still 0x20. (?)
Are you putting the class *references* in a 2-length static array?
I lied. As I could not put the objects in a static array, I put them on the stack with the 'scope' keyword and the difference was still 0x20. (Of course, I could emplace the objects myself in my static array but that wouldn't prove anything about why the current alignment appears to be 0x20.)
 stop worrying about the addresses
 given out by the GC
No worries; just trying to explain... Ali
Jan 04 2023
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Jan 04, 2023 at 01:20:12PM -0800, Ali Çehreli via Digitalmars-d-learn
wrote:
 On 1/4/23 12:02, Steven Schveighoffer wrote:
 On 1/4/23 2:27 PM, Ali Çehreli wrote:
 I put the objects into a 2-length
 static array but the difference was still 0x20. (?)
Are you putting the class *references* in a 2-length static array?
I lied. As I could not put the objects in a static array, I put them on the stack with the 'scope' keyword and the difference was still 0x20. (Of course, I could emplace the objects myself in my static array but that wouldn't prove anything about why the current alignment appears to be 0x20.)
You do realize that the compiler is free to reorder local variables on the stack, right? ;-) Generally it doesn't, but nothing in the spec precludes it from doing so. Highly-optimizing compilers like ldc also tend to move code around, and with it, any associated local variables, so exactly where something is put on the stack isn't really something you can rely on. And of course, stuff on the stack may also be subject to alignment requirements depending on your CPU/architecture. Though generally speaking this shouldn't be any stricter than alignment requirements on general heap/GC allocations.
 stop worrying about the addresses given out by the GC
No worries; just trying to explain...
[...] I think at this point any explanation is likely to involve many more implementation-specific details than is warranted for understanding D code. :-P It can of course be extremely interesting (and instructive) to know about these details, but one has to keep in mind that the deeper one digs, the more non-portable these implementation details become, and the more divergences you'll see across different CPUs and OSes. T -- Insanity is doing the same thing over and over again and expecting different results.
Jan 04 2023
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/4/23 13:43, H. S. Teoh wrote:

 You do realize that the compiler is free to reorder local variables on
 the stack, right? ;-)
Of course. :) I was trying different strategies to catch the compiler (dmd here) in a single act of 8-byte object alignment as reported by .alignof. Another thing the compiler is free for class members is to reorder them. I also theorized perhaps the compiler was also considering the alignment of a member. But as expected, char[1] does have 1 alignment and it can't be the reason in this case. (Again, there is no problem here; we are just learning.)
 implementation-specific details
RazvanN or Dennis may chime in on that. Ali
Jan 04 2023
parent reply Paul <phshaffer gmail.com> writes:
 (Again, there is no problem here; we are just learning.)
 Ali
Do I have this much right? ```d import std.stdio, std.traits; class MyClass {char c;} void main() { auto MyInt = 1; writeln("The address of MyInt is : ",&MyInt," (stack)"); auto MyClassVar1 = new MyClass(); writeln("The address of MyClassVar1 is: ",&MyClassVar1," (stack)"); auto MyClassVar2 = new MyClass(); writeln("The address of MyClassVar2 is: ",&MyClassVar2," (stack)"); writeln; auto MyClassObj1 = cast(void*)MyClassVar1; writeln("The address of MyClassObj1 is: ",MyClassObj1," (heap)"); auto MyClassObj2 = cast(void*)MyClassVar2; writeln("The address of MyClassObj2 is: ",MyClassObj2," (heap)"); } ``` ..with this output? ``` The address of MyInt is : 1CA1CFFB1C (stack) The address of MyClassVar1 is: 1CA1CFFB10 (stack) The address of MyClassVar2 is: 1CA1CFFB08 (stack) The address of MyClassObj1 is: 1EB93212000 (heap) The address of MyClassObj2 is: 1EB93212020 (heap) ```
Jan 04 2023
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/4/23 20:04, Paul wrote:
 (Again, there is no problem here; we are just learning.)
 Ali
Do I have this much right?
 ..with this output?
Looks good to me. While we're here, you can force the class objects to be on the stack as well: scope MyClassVar1 = new MyClass(); I replaced 'auto' with 'scope'. Ali
Jan 04 2023
next sibling parent Paul <phshaffer gmail.com> writes:
On Thursday, 5 January 2023 at 05:59:26 UTC, Ali Çehreli wrote:

 While we're here, you can force the class objects to be on the 
 stack as well:

     scope MyClassVar1 = new MyClass();

 I replaced 'auto' with 'scope'.

 Ali
Very interesting. Thanks Ali.
Jan 05 2023
prev sibling parent Paul <phshaffer gmail.com> writes:
On Thursday, 5 January 2023 at 05:59:26 UTC, Ali Çehreli wrote:
 On 1/4/23 20:04, Paul wrote:
 (Again, there is no problem here; we are just learning.)
 Ali
Do I have this much right?
 ..with this output?
Looks good to me. While we're here, you can force the class objects to be on the stack as well: scope MyClassVar1 = new MyClass(); I replaced 'auto' with 'scope'. Ali
I was tinkering with this use of 'scope' and the math for pointer location, object size, and alignment started working out.
Jan 31 2023
prev sibling parent reply areYouSureAboutThat <areYouSureAboutThat gmail.com> writes:
On Thursday, 5 January 2023 at 04:04:39 UTC, Paul wrote:
 ..
 Do I have this much right?
 ...
First, i would say, add safe to your main. safe void main() ... Then you will see you are treading on dangerous waters ;-) Second, to be sure your getting the correct results, it would be nice if there was a 'category of type' in std.traits for: isAllocatedOnStack isAllocatedOnHeap As it is, your just guessing (although the addresses printed will clear this up for you anyway I guess). Also, I cannot read hex, so if it were me, I'd be casting the hex into an size_t: cast(size_t)&MyInt
Jan 04 2023
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Jan 05, 2023 at 06:32:47AM +0000, areYouSureAboutThat via
Digitalmars-d-learn wrote:
[...]
 Second, to be sure your getting the correct results, it would be nice
 if there was a 'category of type' in std.traits for:
 
 isAllocatedOnStack
 isAllocatedOnHeap
 
 As it is, your just guessing (although the addresses printed will
 clear this up for you anyway I guess).
In general, it's not possible to determine whether an arbitrary reference is pointing to the stack or the heap, because it could have come from anywhere. You can only determine this if you are privy to the internal implementation details of the allocator or code that created the reference (GC, other allocator, or whatever took an address of a stack-allocated object), or the platform-specific details of your runtime environment (range of stack addresses).
 Also, I cannot read hex,
[...] IMNSHO, anyone who claims to be a programmer should at least know that much. As Knuth once said, People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird. -- D. Knuth T -- "Real programmers can write assembly code in any language. :-)" -- Larry Wall
Jan 05 2023
parent areYouSureAboutThat <areYouSureAboutThat gmail.com> writes:
On Thursday, 5 January 2023 at 17:23:39 UTC, H. S. Teoh wrote:
 On Thu, Jan 05, 2023 at 06:32:47AM +0000, areYouSureAboutThat
 Also, I cannot read hex,
[...] IMNSHO, anyone who claims to be a programmer should at least know that much.
?? Well, like all, I learnt this at uni. .. as well as lot of other useless stuff. But in all my years as a well paid and productive 'programmer' ;-) .. I cannot recall ever, a consistent need to read hex (using by brain, that is). As such, those neuron connections needed to read hex do not exist anymore. Even the neurons in my brain are smarted than Knuth.
Jan 05 2023
prev sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/1/23 01:01, Paul wrote:

 ...on my laptop it prints...
 ```
   Size  Alignment  Type
 =========================
     9       4      MyClass

 4FFB20  4FFB24
 ```
 If the size of MyClass is 9 bytes why do MyClassO1 & O2 addresses only
 differ by 4 bytes?
As matheus said, classes are reference types, meaning, class variables are implemented as pointers. The values above are the addresses of those pointers. The fact that those values are different by 4 tells us that the code was compiled for 32 bits. On the other hand, matheus's values were 8 apart because that compilation was 64 bits.
 So, I guess my question is actually how do I print out the addresses of
 the MyClassO1 & O2 objects themselves?
You must have missed my earlier answer: You need to cast the variable to 'void*'. That special operation will give you the address of the object. I am adding the following last line to matheus's example: // ... writeln("\n",&(MyClassO1.c),"\t",&(MyClassO2.c)); writeln("\n",cast(void*)MyClassO1,"\t",cast(void*)MyClassO2); The output: [...] 7F125FE77010 7F125FE77030 <-- The addresses of the 'c' members 7F125FE77000 7F125FE77020 <-- The addresses of the objects The reason why objects are 0x10 bytes before the 'c' members is because a class object has two hidden initial members: the vtbl pointer and the monitor, both of which are size of a pointer (4 on your system, and 8 on matheus's system and mine). Additionally, if you define the class as extern(C++), there will not be the monitor member. The reason for that is C++ does not have that concept and it would break interoperability. Ali
Jan 01 2023