NASM - The Netwide Assembler

NASM Forum => Other Discussion => Topic started by: Bryant Keller on June 21, 2011, 02:32:10 AM

Title: Extra Parameter on Stack (For Frank)
Post by: Bryant Keller on June 21, 2011, 02:32:10 AM
Quote from: Frank Kotler
And, FWIW, I don't "get" C++ at all. To be sure, I haven't really studied it. I have some C++ code - using the "XCB" library code (an alternative to Xlib, for Linux) - which I was trying to help a guy figure out. The disassembled code appears to "push" (it doesn't really "push" - gcc has used "mov [esp + ???], ???" instead) a parameter that doesn't show in the source code. When the library function returns, the secret parameter has apparently been removed with a "ret 4", despite the rest of it being "caller cleans up stack". Some, but not all, of the library routines do this. One of these days, I'll run it by you "C++ gurus" and see if you can help me figure out what's going on. But not in this thread...

This is one of the biggest inconsistencies in the C++ language (and a pain in my butt in the past).

First, lets define what an 'object' really is. An object (from our level) can be simply thought of as a STRUC which contains a pointer to a destructor (dtor), a pointer to an array of procedures (virtual method table), sometimes a pointer to an array of data (virtual data table), and then finally it contains the various entries which make up it's "layout" (variables and static methods). So a good representation of an object could be:

Code: [Select]
.dtor RESD 1 ; used to deconstruct the object
.vmt RESD 1 ; allows you to define methods which can be overloaded
.vdt RESD 1 ; rarely ever used since data is virtual by design, but it's sometimes used as your 'protected' data
; After this point, we are adding non-overloadable methods/data
.m_X RESD 1
.m_Y RESD 1
.Draw RESD 1

When you do inheritance, you basically overwrite any overloaded entries in the vmt/vdt then append your data/methods to your new object. So basically the .dtor,vmt,vdt are always in the same place, everything else gets appended as it's added (parents stuff on top, childrens stuff below).

Easy right! Well, this isn't quite all of it, eventually you end up wanting to create multiple objects of the same type. And unfortunately for us, there is no "standard" method for handling this issue. The issue in question is, How do we identify one Shape object from another? The solution used by pretty much every object is to pass a pointer to the object's location in memory to every single method, and it works great! However, not ever compiler does it the same way.  The GNU C++ Compiler will place the object pointer on the stack and remove it with a 'ret 4' after the call. This means that (assuming our .Draw method takes 1 dword parameter being the address of memory to draw on) we could call our Draw method of an instance of our object in edi like so:

Code: [Select]
; parameters
push dword hScreen
; call a method
push edi
call [edi + Shape.Draw]
; clean up hScreen parameter
add esp, 4

The idea here is that the 'push edi' is part of the call itself and not an 'argument' per-say. Unfortunately, not every compiler will do this the same way! Microsoft takes the view that since a register is in use anyway, it uses it's normal stdcall convention with the object pointer in ECX. Only exception to that is if you use variadic arguments in which case the cdecl convention is used, but the object pointer will still appear in ECX. Microsoft calls it the "ThisCall Convention" and I honestly like this much better than GNU's hybrid calling conventions which hide away manipulations to the stack.


P.S. I wrote this rather quickly, if there is anything else I can clear up, please ask.

Further Reading:
WikiPedia on "ThisCall" (
Title: Re: Extra Parameter on Stack (For Frank)
Post by: JoeCoder on June 21, 2011, 09:10:20 AM
Bryant, that post was amazing. That is the best, simplest, most terse yet clear explanation of how classes are implemented that I have ever seen. It should be stickified.
Title: Re: Extra Parameter on Stack (For Frank)
Post by: Bryant Keller on June 22, 2011, 05:57:14 AM
Thanks man, I was actually just skimming over things cause I ASSume that Frank could probably keep up. Object implementation isn't overly complex, it's just that there isn't a universal method for doing it. In fact, one of the things I didn't cover was object constructors (.ctor) because I find that literally every framework has their own way of implementing them.