Hello all! In this session, we'll delve into the concept of Bare Function Invocations in C++. You might wonder why these are referred to as "naked" in the realm of C++. But before addressing that, let's first understand the essence of a function call.
Function Call in C++
Initiating the function and enabling its execution at a specific point in a C++ program is referred to as invoking the function in C++.
There are two categories of function invocations in C++:
- Function Call with Argument
In this function invocation, we neither assign nor pass any arguments to the C++ Program. These inputs are essential for the program's execution.
- Function Call without Parameters
In this function invocation, we either assign or pass parameters to the C++ program. These arguments are essential for the execution of the program.
Example
File name: withParameters1. cpp
/* This is a program written to find the factorial of a number using function call which is parameterized or we can say that parameters are passed into the program */
#include <iostream>
#include <cstdio>
using namespace std;
int factorial (int number)
{
int i;
int f=1;
for(i = 1; i <= number; i++)
{
f = f * i;
}
return f;
}
int main()
{
int n;
cout <<"Enter any number: " << endl;
cin >>n;
int fact;
fact = factorial(n);
printf("%d is the factorial of %d",fact,n);
return 0;
}
Input:
Enter any number:
10
Output:
3628800 is the factorial of 10
Example
File name: withoutParameters1. cpp
/* This is a program written to find the factorial of a number using function call which is not parameterized or no parameter passing */
#include <iostream>
#include <cstdio>
using namespace std;
void factorial ()
{
int number;
cout <<"Enter any number: " << endl;
cin >>number;
int i;
int f=1;
for(i = 1; i <= number; i++)
{
f = f * i;
}
printf("%d is the factorial of %d",f,number);
}
int main()
{
int fact;
factorial();
return 0;
}
Input:
Enter any number:
15
Output:
2004310016 is the factorial of 15
Naked Function Calls
When a function is defined with the naked attribute, it omits the generation of standard prolog or epilog code, enabling you to employ inline assembler to craft custom prolog/epilog sequences as needed.
An advanced capability is the inclusion of bare functions. These functions offer the ability to define a function that is invoked from a context other than C or C++, enabling you to make assumptions about the parameter location or register preservation in a unique manner.
Routines such as interrupt handlers serve as illustrations. Those developing virtual device drivers (VxDs) will particularly appreciate this feature for its convenience and ease of implementation.
The naked attribute is employed to define functions, where the resulting code does not contain prologue and epilogue sections.
Applying inline assembly code allows you to craft custom prolog/epilog code sequences. Naked functions are particularly beneficial when developing virtual device drivers. It's important to note that while the naked attribute is supported on x86 and ARM architectures, it is not compatible with the x64 platform.
Syntax
_delspec (naked)
Naked functions are required to make use of the extended attribute syntax and the __declspec keyword since the naked attribute specifically impacts the declaration of a function and does not act as a modifier for types.
Even when a function is marked with the __force inline keyword and the naked attribute, the compiler is unable to generate an inline function for it.
If the naked attribute is applied to any context beyond defining a non-member method, the compiler will generate an error.
Examples
_declspec (naked) float fun ( initial parameters ) { }
Or
#define Naked _declspec (naked)
Naked float fun ( initial parameters ) { }
The naked attribute only affects the structure of the prolog and epilog sequences generated by the compiler.
It does not affect the code when calling these functions. Therefore, function pointers are unable to include the naked attribute as it is not considered part of the function's type. Furthermore, the naked attribute cannot be utilized in a data definition.
Rules and Limitations
Following are the rules and limitations of Naked Function Call in C++:
- The return keyword cannot be used here in this type of function call
- _alloca function cannot be used in this type of function calls
- Initialized local variables are not allowed at function scope to guarantee that no initialization code for local variables enters before the prolog sequence. Particularly, function scope does not allow the definition of C++ objects. A nested scope could, however, include initialized data.
- Naked Function Calls in C++ must unwind over the stack frame. So, C++ exception handling constructs and structured exception handling are not allowed.
- Naked Function Calls in C++ must unwind over the stack frame. So, setjmp cannot be used for Naked Function Calls in C++.
- Every time one of the register parameters for __fastcall bare functions is referenced in C/C++ code, the prolog code should save the value of that register onto the stack location for that variable.
- If the Naked function call is in lexical scope, C++ class objects cannot be declared. But you are still allowed to define objects in nested blocks.
- Although it is not advised, the frame pointer optimization (the / Oy compiler option) is automatically suppressed for a naked function.
- When Naked Function Call building is done with / clr, the naked keyword can be ignored.
Example
// nkdfastcl.cpp
// compile with: /c
// processor: x86
__declspec(naked) int __fastcall power(int i, int j) {
// This code is written to calculate the value of x EXOR y, assumes that j >= 0
// prolog
__asm {
push ebp
mov ebp, esp
sub esp, __LOCAL_SIZE
// Place ECX and EDX in the x and y stack places.
mov x, ecx
mov y, edx
}
{
int s = 1; // return value
while (y-- > 0)
s * = x;
__asm {
mov eax, k
};
}
// epilog
__asm {
mov esp, ebp
pop ebp
ret
}
}
Ideas to Keep in Mind While Writing Prolog/Epilog Code
It is essential to grasp the organization of the stack frame prior to developing your custom prologue and epilogue code sequences. Familiarity with the implementation of the __LOCAL SIZE token can also be advantageous.
Stack Frame Layout
This diagram showcases standard Prolog code commonly employed in a 32-bit operation:
Example:
push ebp ; Save ebp
sub esp, localbytes ; Allocate space for locals
mov ebp, esp ; Set stack frame pointer
push <registers> ; Save registers
The variable "registers" serves as a placeholder representing the register list to be preserved on the stack, while the "localbytes" variable specifies the amount of stack space needed for local variables. Additional relevant data can be stored on the stack subsequent to pushing the registers. Below is the corresponding epilogue code snippet:
Epilog Code
pop <registers> ; Restore registers
pop ebp ; Restore ebp
mov esp, ebp ; Restore stack pointer
ret ; Return from function
The stack decreases in size as it moves from higher to lower memory addresses. The value pushed onto the stack at ebp represents the location to which the base pointer (ebp) is pointing. The area where local variables are stored begins at ebp-4. To access local variables, determine the offset from ebp by subtracting the appropriate value from ebp.
LOCAL SIZE
For implementation within the inline assembler section of the function prolog code, the compiler provides a symbol known as __LOCAL SIZE. When crafting custom prolog code, this symbol serves the purpose of reserving memory for local variables within the stack frame.
The compiler determines the LOCAL SIZE value, which consists of the total of user-defined local variables and temporary variables generated by the compiler. LOCAL SIZE can only be used as an immediate operand and cannot be part of an expression. It is crucial to retain the original meaning of this symbol without any modifications or reinterpretations. For example:
mov eax, [ebp - __LOCAL_SIZE] ;Error
mov eax, __LOCAL_SIZE ;Immediate operand--Okay
The __LOCAL SIZE symbol is utilized within the prolog sequence of the naked function, which implements distinct prolog and epilog sequences in the following manner:
Example
// the__local_size_symbol.cpp
// processor: x86
__declspec ( naked ) int main() {
int x;
int y;
__asm { /* prolog */
push ebp
mov ebp, esp
sub esp, __LOCAL_SIZE
}
/* Function body */
__asm { /* epilog */
mov esp, ebp
pop ebp
ret
}
}
This tutorial focuses on Direct Function Invocations in the C++ Programming Language.