You are right: TObject.InstanceSize returns the instance size in memory. Not the nested fields.
If you want to compute the "size" of the class instance in memory, then the value will never match what any streaming process would do.
I mean, if you serialize your class instance into a TStream, the size will depend on the actual serialization. Not the total memory used size. Most serialization engines will only serialize the published properties, not the public and not the nested protected fields. And if you serialize as JSON, or as DFM binary, or as DFM text, or as ProtoBuf or as our mORMot binary layout, you will have diverse values, which won't match the actual RAM consumption.
The situation is even worth for nested reference types, like string or dynamic arrays. They use reference counting, so a single string instance, if reused in several properties/variables, will just consume its length once for all the occurrences... And what about nested objects, which may be reused between instances?
So there is no simple way to compute how much "memory" a class instance consumes. What you can do is write your own function, with an "estimate" of the consumed memory. Something like
function TMyObject.ConsumedBytes: integer;
begin
result := InstanceSize +
length(fName) * SizeOf(char) +
length(fFirstName) * SizeOf(char) +
length(fAddress) * SizeOf(char);
end;
And even this would be considered as an estimation, not how much RAM was consumed. For instance, some types like string do have an header before the text (including length, refcount and codepage), and the memory manager itself would always reserve a bit more than what is exactly needed, for low-level reasons of optimization.
I guess you need to refine what is your goal. If it is to check for memory usage, then you need to learn a bit more about how Delphi uses its memory for its low level types, and how the heap memory manager reserves memory areas from the Operating System.