Function Overloading and Default Parameters

One post I responded to on a forum was regarding overloaded functions and default parameters–was there any performance difference? I was pretty sure there wasn’t, but I put some time into figuring it out and illustrating the concepts, so I figured I might as well post it here. Most of THIS post is THAT post verbatim. So if you’ve read that post, then there’s nothing new here, really.

“Overloading functions” is a little bit misleading. Having two identically named functions with different parameters doesn’t produce one magical function that sorts itself out with no runtime cost (but overloaded functions don’t have any runtime cost). Overloading a function just puts the work of writing separate function names on the compiler–two independent functions are still generated. For instance, in this program:

void default_param(int a, int b=777) { }
void overloaded(int a) { }
void overloaded(int a, int b) { }

int main(int argc, char *argv[]) {
	default_param(500);

	overloaded(500);
	overloaded(500, 333);
}

Visual Studio, the compiler that I use, will mangle each of these function names. The mangled names for the “overloaded” functions are overloaded@@YAXH@Z and overloaded@@YAXHH@Z (and yes, Visual Studio mangles by a set of rules, but the actual rules are compiler dependent). My first call to ‘overloaded’ will just jump to the “YAXH” version, and the second will jump to the “YAXHH” version. There is no ‘if’ branch or anything… the appropriate calls are just stuck in the right places when the code is generated.

Our overloaded function, when supplied with only one parameter, pushes only one value on the stack, because the corresponding function only deals with one:

; 10   : 	overloaded(500);

	push	500					; 000001f4H
	call	?overloaded@@YAXH@Z			; overloaded

But our default_param function call requires two pushes, even though the parameter defaults to 777:

; 8    : 	default_param(500);

	push	777					; 00000309H
	push	500					; 000001f4H
	call	?default_param@@YAXHH@Z			; default_param

..and just for completeness, calling “overloaded” with two parameters requires two pushes and will call the YAXHH function, as mentioned:

; 11   : 	overloaded(500, 333);

	push	333					; 0000014dH
	push	500					; 000001f4H
	call	?overloaded@@YAXHH@Z			; overloaded

There are usually pops to mirror the pushes as well (so pushing two variables would later require two pops), but Visual Studio does some crazy indexing into the stack and never actually pops the variables. I don’t understand why or how it works… so I can’t really help you with that part of it.

Here’s the complete C++/ASM output, if anyone is interested:

; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.21022.08 

	TITLE	c:\\C++ Projects\\quicktests2\\quicktests2\\main.cpp
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC	?default_param@@YAXHH@Z				; default_param
; Function compile flags: /Odtp
; File c:\\c++ projects\\quicktests2\\quicktests2\\main.cpp
_TEXT	SEGMENT
_a$ = 8							; size = 4
_b$ = 12						; size = 4
?default_param@@YAXHH@Z PROC				; default_param

; 1    : void default_param(int a, int b=777) { }

	push	ebp
	mov	ebp, esp
	pop	ebp
	ret	0
?default_param@@YAXHH@Z ENDP				; default_param
_TEXT	ENDS
PUBLIC	?overloaded@@YAXH@Z				; overloaded
; Function compile flags: /Odtp
_TEXT	SEGMENT
_a$ = 8							; size = 4
?overloaded@@YAXH@Z PROC				; overloaded

; 2    : void overloaded(int a) { }

	push	ebp
	mov	ebp, esp
	pop	ebp
	ret	0
?overloaded@@YAXH@Z ENDP				; overloaded
_TEXT	ENDS
PUBLIC	?overloaded@@YAXHH@Z				; overloaded
; Function compile flags: /Odtp
_TEXT	SEGMENT
_a$ = 8							; size = 4
_b$ = 12						; size = 4
?overloaded@@YAXHH@Z PROC				; overloaded

; 3    : void overloaded(int a, int b) { }

	push	ebp
	mov	ebp, esp
	pop	ebp
	ret	0
?overloaded@@YAXHH@Z ENDP				; overloaded
_TEXT	ENDS
PUBLIC	_main
; Function compile flags: /Odtp
_TEXT	SEGMENT
_argc$ = 8						; size = 4
_argv$ = 12						; size = 4
_main	PROC

; 5    : int main(int argc, char *argv[]) {

	push	ebp
	mov	ebp, esp

; 6    : 	default_param(500);

	push	777					; 00000309H
	push	500					; 000001f4H
	call	?default_param@@YAXHH@Z			; default_param
	add	esp, 8

; 7    :
; 8    : 	overloaded(500);

	push	500					; 000001f4H
	call	?overloaded@@YAXH@Z			; overloaded
	add	esp, 4

; 9    : 	overloaded(500, 333);

	push	333					; 0000014dH
	push	500					; 000001f4H
	call	?overloaded@@YAXHH@Z			; overloaded
	add	esp, 8

; 10   : }

	xor	eax, eax
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS
END

I know you’re anxious, so I’ll just mention that the overloaded function example actually does save one push operation, and possibly a pop operation. These days, however, the cost of this push operation is insanely small. To figure out how much time this might take, you can cruise on over to wikipedia’s article on instructions per second, and check out their timeline. I’m not gonna tell you how to live your life, but maintaining two otherwise identical functions in order to save one push/pop operation isn’t something I’d recommend to my worst enemy (who, by the way, is Batman).

But there ya go!