Understanding object pointers
TL;DR
In the virtual machine, objects are just pointers to data. It is possible to use these pointers to walk through memory and visit all objects. It is also possible to
swap
all pointers to an object to another object by sending become:
. This can be useful for updating a running system, or for temporarily replacing an object by a wrapper. It is also possible to change th
class
of an object at run time, but this is tricky and should be used sparingly.
Objects are pointers
As we have seen in Querying objects, an object is a reference (a pointer), it contains values, and is an instance of a class.
Until recently, these pointers used to be 32 bits long, with one bit reserved to distinguish between
immediate
objects, such as SmallInteger
integers, which are fully represented in the bits of the pointer, and regular objects, but with modern memory architectures, they are 64 bits.
For most purposes, we cannot directly access or manipulate object pointers from a running Smalltalk image, but there are a few exceptions to this rules. We can (1) iterate through memory using nextObject:
, and (2) we can swap object pointers using become:
and becomeForward:
.
Walking through memory
You can obtain the first (non-immediate) object in memory by sending someObject
to any class. You can then iterate through all objects by sending nextObject
to each object until the value 0 is returned.
For example, this code will walk through memory and return a count of all the regular objects.
someBlElement := Object someObject. currentElement := someBlElement nextObject. count := 1. [ currentElement = 0 ] whileFalse: [ currentElement := currentElement nextObject. count := count + 1 ]. count.
Note that SystemNavigation
provides the utility methods SystemNavigation>>#allObjects
and SystemNavigation>>#allObjectsDo:
, which are probably more convenient to use.
SystemNavigation default allObjects size.
In this case however the result includes immediate objects.
There also exist someInstance
and nextInstance
, which are specific to a given class. These are used to implement Context>>#allInstances
.
Dictionary allInstances.
Swapping and forwarding object pointers
In Smalltalk it is possible to swap the identities of two objects.
If you send foo become: bar
, then every references to foo
now points to bar
, and vice versa. The main applications are (1) temporarily replace an object by a “wrapper” around that object (such as a logging proxy — see Overriding doesNotUnderstand:, or (2) to migrate objects in a running system from an old implementation to a new one.
Consider the following example:
becomePoints <gtExample> | pt1 pt2 pt3 | pt1 := 0 @ 0. pt2 := pt1. pt3 := 100 @ 100. pt1 become: pt3. self assert: pt1 equals: 100 @ 100. self assert: pt1 == pt2. self assert: pt3 equals: 0 @ 0. ^ {pt1. pt2. pt3}
In this example, pt2
points to (is identical to) pt1
. After swapping pt1
and pt3
, pt2
still
points to the same object as pt1
—
i.e.,
pt1 == pt2
—, but it is now a
different
object, namely 100@100
. We can see this clearly by inspecting the result.
There is also becomeForward:
, which does almost the same thing, but instead of swapping pointers, everything that pointed to the receiver now points to the argument, and pointers to the old object are lost. In this variant, pt1
and pt2
end up pointing to pt3
, and the old 0@0
point is lost.
becomeForwardPoints <gtExample> | pt1 pt2 pt3 | pt1 := 0 @ 0. pt2 := pt1. pt3 := 100 @ 100. pt1 becomeForward: pt3. self assert: pt1 equals: 100 @ 100. self assert: pt1 == pt2. self assert: pt2 == pt3. ^ {pt1. pt2. pt3}
Here's the result:
Changing the class of an object
It is also possible to change the class of an object, without swapping any pointers, using Object>>#primitiveChangeClassTo:
.
Caveat:
while become:
can be used to swap the pointers of
any
non-immediate objects (you can't swap an instance of Object
with an instance SmallInteger
), primitiveChangeClassTo:
can
only
be used to change the class of an object to another class with
the same format
. For details, see the method comment of .
Here's an example where this works. We create anon
as a new, anonymous subclass of Object
. We create anObject
as an instance of Object
, and change its class to anon
. We then compile a new method in anon
and verify that the object understands this new method.
anonymousClassInstance <gtExample> | anon anObject | anon := Object newAnonymousSubclass. anObject := Object new. anObject primitiveChangeClassTo: anon new. anon compile: 'thisIsATest ^ 2'. self assert: anObject thisIsATest equals: 2. self should: [ Object new thisIsATest ] raise: MessageNotUnderstood. ^ anObject
NB: the argument to primitiveChangeClassTo:
is an
object
, not a class. The reciever's class is changed to be the same as that of the argument.
This technique should be used sparingly. In fact, there are very few usages in the system.
#primitiveChangeClassTo: gtSenders.
There is also Behavior>>#adoptInstance:
, which returns the class instead of the instance, but it is also rarely used in practice.
#adoptInstance: gtSenders.