Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #84201

Re: Function pointer size (was Re: nullptr is not 0)

Subject Re: Function pointer size (was Re: nullptr is not 0)
Newsgroups comp.lang.c++
References (7 earlier) <t65lls$1nug$1@gioia.aioe.org> <IduhK.86$vAW9.18@fx10.iad> <t65sau$jta$1@dont-email.me> <IevhK.7$x7oc.1@fx01.iad> <t6620o$9m3$1@dont-email.me>
From Richard Damon <Richard@Damon-Family.org>
Message-ID <8dChK.6$KWh.2@fx02.iad> (permalink)
Organization Forte - www.forteinc.com
Date 2022-05-19 21:32 -0400

Show all headers | View raw


On 5/19/22 2:24 PM, Andrey Tarasevich wrote:
> On 5/19/2022 10:36 AM, Richard Damon wrote:
>>>>
>>>> Yes that sort of describes the problem. The pointer to member needs 
>>>> to be able to refer to a virtual function, or even a function that 
>>>> is a member of a virtual base class, and handling that sort of stuff 
>>>> is what forces the extra memory in the pointer-to-member.
>>>
>>> That's incorrect in general. Again, using x86 as a counterexample:
>>>
>>> Implementations that use different representations for virtual vs. 
>>> non-virtual member pointers (GCC, Clang) manage to stuff that extra 
>>> information into the "original" memory - they use the lowest bit of 
>>> the pointer as a flag.
>>>
>>> Implementations that use identical representations for virtual vs. 
>>> non-virtual member pointers (MSVC) naturally don't have this issue at 
>>> all.
>>>
>>> So, no, the need to handle virtual functions does not require extra 
>>> memory in pointer-to-member function.
>>
>> But not all processors have a LSB to use for that sort of flag.
>>
>> Some have instructions that can be at any address, because the 
>> instruction set has single byte opcodes (They could just force all 
>> functions to be at addresses with lower order zeros, but that is 
>> wasteful)
>>
>> Others, like the ARM, use the low order bit to indicate which 
>> instruction set the function is written in, so it isn't available for 
>> such a use.
>>
>> Thus, there sometimes IS a need for an additional flag bit to indicate 
>> if the pointer is an index into the v-table or the actual address of 
>> the function.
> 
> Well, that's a completely different claim. "Sometimes", huh...
> 
> Firstly, as MSVC implementation demonstrates that the flag is not 
> necessary at all.

At the cost of an addition layer of calling and more thunks.

> 
> Secondly, quite possibly it is the case "sometimes". However, one might 
> argue that if the implementation had to increase pointer size just for 
> the sake of that single flag, it would probably abandon that approach 
> altogether and switch to MSVC-like approach, which requires no flag.

The question comes can it find a place to hide it. As you have 
mentioned, if the class the function is in isn't at the same base 
address as the class the pointer is based on, you need some offsets, and 
perhaps you can find a place in there.

Especially for the offset cause by virtual bases, which isn't fixed, so 
DOES need to be a lookup table index which can be of limited size, 
leaving space for the direct/virtual function flag.

Note, for a processor with a conditional skip, that flag doesn't even 
cost you a branch predicition slot.

> 
>>>
>>>> I remember seeing documentation in one compiler that gave options 
>>>> for "optimizations" where you could limit what sort of member 
>>>> functions you could take the address of to make the pointers 
>>>> smaller. Omitting the handling of Virtual Base classes, or those and 
>>>> virtual functions (I forget if there was some other level).
>>>
>>> Well, you forget.
>>>
>>> These optimizations have absolutely nothing to do with virtual 
>>> functions. They have everything to do with the hierarchy structure. 
>>> They depend on the answer to one question: is it necessary to correct 
>>> the `this` pointer value when making a call?
>>
>> Not to my knowledge. That adjustment tends to be done by a "Thunk" to 
>> correct the base address of "this" for non-first base classes. 
> 
> No, no, no. That cannot be done by a thunk. The adjustment value is a 
> run-time value, which changes as you convert the pointer up and down the 
> hierarchy.
> 
> Theoretically, it can be probably implemented somehow by a bunch of 
> thunks and a heap of compiler magic: say, by generating as many 
> different thunks as there are different correction values. But I haven't 
> seen any real-life implementations that do that. And it seems unreasonable.
> 
> GCC, Clang and MSVC store the `this` correction offset inside the 
> pointer. That is exactly why in these compilers the pointer takes two 
> words instead of one.

I was thinking of the adjustment for the class of the object that the 
pointer is being applied to, that can't be stored in the pointer 
(because it doesn't know what it is).

I was forgetting the adjustment if the function is in a class with a 
different base than the pointer-to-function type.
> 
>>> Correction to `this` might be necessary in case of multiple 
>>> inheritance, in case of virtual inheritance and in some other niche 
>>> cases. This correction is what the extra memory in pointer-to-member 
>>> function stores. This is why pointer-to-member-function is larger 
>>> than an ordinary pointer.
>>>
>>> But it has nothing to do with calls to virtual functions.
>>>
>>
>> Nope, because the pointer-to-member function pointer has no idea of 
>> the type of the object that it will be used on, so CAN'T store the 
>> full offset. (That is the job of the Thunk).
> 
> This is completely incorrect.
> 
> (Once again, let me reiterate that what I'm stating here is not offered 
> for debate. These are hard facts about how modern implementations work. 
> This is learning material for those willing to learn.)
> 
> The pointer has to store the _delta_ value that corrects the `this` 
> pointer between the static pointer type and the dynamic type of the 
> pointed object.
> 
> Here's a little illustrative sketch
> 
>     struct B {};
>     struct D : /* some other bases */, B { void foo() {} };
>     // Base subobject `B` is offset inside `D`
> 
>     void (B::*pf)() = static_cast<void (B::*)()>(&D::foo);
>     // Here the upper word of `pf` will store the correction
>     // delta, which is needed to down-convert `B *` to `D *`

THAT is dangerous. You have now made a (B::*)() pointer that can only be 
successsfully applied to  D object, but can legally applied to an object 
that is just a B.

It is legal with the cast, because sometimes that is useful.

A Better example would be a (D::*) member-function-pointer being set 
from a (B::*)() member-function pointer. THAT is safe, as it can only be 
applied to a D object (without playing games) and all D's are also B's, 
so the offset is good.

> 
>     D *pd = new D;
>     B *pb = pd;
>     // `pb` is not the same numerically as `pd`
> 
>     (pb->*pf)();
>     // Correctly calls `D::foo`
>     // Before the actual call value of `pb` is adjusted by the
>     // correction delta stored in `pf`, thus providing us with
>     // the proper `D *this` pointer
> 
>     (pd->*pf)();
>     // Correctly calls `D::foo`
>     // Since `pf` is declared as `void (B::*)()`, the `pd` is
>     // first converted to `B *` (offsetting it), and then
>     // adjusted by the correction value stored in `pf`
>     // (un-offsetting it). This results in `pd` again, which
>     // is the proper `D *this` pointer
> 
> As you can see in the example above, even though it "has no idea of the 
> type of the object that it will be used on", everything works out nicely 
> and correctly.
> 
>> But it may be needed for a pointer-to-member function pointer which 
>> doesn't always hold the address of the member function to call but 
>> sometimes an offset/index into the vtable.
>>
>> As an example:
>>
>> class B {
>> public:
>>              void f1();
>>      virtual void f2();
>> }
>>
>> class D : public B {
>> public:
>>      virtual void f2();
>> }
>>
>> we take the member-function-pointers
>>
>> void (B::*ptr1)() = &B::f1;
>> void (B::*ptr2)() = &B::f2;
>>
>>
>> B b;
>> D d;
>>
>> b*->ptr1() vs b *->ptr2() vs d*->ptr1() vs d*->ptr2()
>>
>> There is a need to know that the "value" stored in ptr1 is the actual 
>> address of B:f1(), while ptr2 is storing an offset into the vtable of 
>> B for the member function.
>>
>> ptr2 can NOT just have the address of B::f2() as if derived class D 
>> overrides f2(), then the call via ptr2 needs to go to D::f2().
> 
> Nobody says that `ptr2` has to store address of `B::f2()`. But your 
> claim that there is a need to distinguish between `ptr1 and `ptr2` at 
> run-time is incorrect.
> 
> This is what MSVC will translate your example into:
> 
>    class B {
>    public:
>              void f1();
>      virtual void f2();
>              void f2_thunk() { return f2(); }
>    };
> 
>    class D : public B {
>    public:
>      virtual void f2();
>    }
> 
>    ...
>    void (B::*ptr1)() = &B::f1;
>    void (B::*ptr2)() = &B::f2_thunk;
> 
>    B b;
>    D d;
> 
>    //b*->ptr1() vs b *->ptr2() vs d*->ptr1() vs d*->ptr2()

Actually, if B isn't the first base, then D also needs a f2_thunk_B 
function that takes a B* as its object pointer and adjusts it to a D* 
pointer to call D:f2(), and the vtable that is in D's B sub-object 
points to that thunk.

You could also increase the size of ALL vtables to add an offset, but 
that slows down ALL virtual calls, not just ones that use non-first base 
class object pointers.

IF D derives from B1 and B2, a B1* -> f2() or a  D* -> f2() would lookup 
f2 in the B1/D vtable, while a B2* -> f2() would look up in the B2/D 
vtable using the pointer in the B2 sub-object.

The tricky part is that if you have a virtual base, you don't know the 
offset to that sub-object just based on the type of pointer you have, 
but you need to look it up in the vtable,

> 
> And suddenly everything works correctly, like magic! No need "to know" 
> which pointer is virtual and which pointer is not. Very simple.
> 
> Again, in this case `f2_thunk` takes advantage of the already 
> implemented virtual dispatch mechanism. The pointer simply re-uses it, 
> instead of re-implementing it.
> 
> It is quite possible that GCC's approach is more efficient, despite 
> being more bulky: branch predictions, cache behavior and so on... But 
> there's no denying that MSVC's approach is far more elegant.
> 

Yes, the difference is that the MSVC thunking method needs to figure out 
which thunks are needed. It can make one for EVERY virtual function as 
part of the class definition, and trust the linker to remove unused 
code. The alternative is to generate the thunk in every module when the 
address of a member function is taken, and truct the linker to merge 
duplicate thunks.

I think that once you handle the need to handle the correction of object 
for a virtual base, the cost isn't that high, and you likely have space 
to hide the flag.

This is for the case of a (D::*)() pointer being set to a (VB::*)() 
non-virtual member function (so you can't just try to arrange the 
virtual function thunk to make the correction).

You CAN'T create thunks for that, since when you first set the (VB::*)() 
pointer, you don't know about D, and when you convert the (VB::*)() to a 
(D::*)(() pointer, you would somehow need to translate every possible VB 
member function to is D equivalent.

Back to comp.lang.c++ | Previous | NextPrevious in thread | Next in thread | Find similar | Unroll thread


Thread

nullptr is not 0 Sam <sam@email-scan.com> - 2022-05-08 09:45 -0400
  Re: nullptr is not 0 "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2022-05-08 16:21 +0200
  Re: nullptr is not 0 Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-08 10:00 -0700
  Re: nullptr is not 0 Bonita Montero <Bonita.Montero@gmail.com> - 2022-05-09 06:38 +0200
  Re: nullptr is not 0 Juha Nieminen <nospam@thanks.invalid> - 2022-05-09 05:06 +0000
    Re: nullptr is not 0 Vir Campestris <vir.campestris@invalid.invalid> - 2022-05-09 21:46 +0100
      Re: nullptr is not 0 Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-09 13:57 -0700
        Re: nullptr is not 0 Juha Nieminen <nospam@thanks.invalid> - 2022-05-10 05:04 +0000
          Re: nullptr is not 0 Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-09 22:33 -0700
      Re: nullptr is not 0 scott@slp53.sl.home (Scott Lurndal) - 2022-05-09 21:56 +0000
        Function pointer size (was Re: nullptr is not 0) Vir Campestris <vir.campestris@invalid.invalid> - 2022-05-17 21:30 +0100
          Re: Function pointer size (was Re: nullptr is not 0) scott@slp53.sl.home (Scott Lurndal) - 2022-05-17 21:00 +0000
            Re: Function pointer size (was Re: nullptr is not 0) Vir Campestris <vir.campestris@invalid.invalid> - 2022-05-19 21:52 +0100
            Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 14:08 -0700
          Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-17 17:05 -0700
          Re: Function pointer size (was Re: nullptr is not 0) Juha Nieminen <nospam@thanks.invalid> - 2022-05-18 04:46 +0000
            Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-18 02:14 -0700
              Re: Function pointer size (was Re: nullptr is not 0) Juha Nieminen <nospam@thanks.invalid> - 2022-05-19 14:53 +0000
                Re: Function pointer size (was Re: nullptr is not 0) "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2022-05-19 17:19 +0200
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 10:14 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Richard Damon <Richard@Damon-Family.org> - 2022-05-19 13:45 -0400
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 11:31 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Juha Nieminen <nospam@thanks.invalid> - 2022-05-20 06:58 +0000
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-20 00:11 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Richard Damon <Richard@Damon-Family.org> - 2022-05-19 12:27 -0400
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 09:47 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Richard Damon <Richard@Damon-Family.org> - 2022-05-19 13:36 -0400
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 11:24 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Richard Damon <Richard@Damon-Family.org> - 2022-05-19 21:32 -0400
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 20:28 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Richard Damon <Richard@Damon-Family.org> - 2022-05-20 10:57 -0400
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 09:35 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-19 10:10 -0700
                Re: Function pointer size (was Re: nullptr is not 0) Juha Nieminen <nospam@thanks.invalid> - 2022-05-20 07:15 +0000
          Re: Function pointer size (was Re: nullptr is not 0) "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2022-05-18 11:50 -0700
            Re: Function pointer size (was Re: nullptr is not 0) Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-05-18 15:46 -0700
              Re: Function pointer size (was Re: nullptr is not 0) "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2022-05-19 20:52 -0700

csiph-web