extern "C" int bar(int a,int b,int c);We can actually write this "bar" function in assembly, like this:
int foo(void) {
return bar(0xA0B1C2D3, 0xE0E1E2E3, 0xF0F1F2F3);
}
global barWhen we first get called, sitting on the top of the stack (at DWORD[esp]) should be our caller's return address. Deeper into the stack is our first argument (at DWORD[esp+4]), then our second argument (at DWORD[esp+8]), and so on. Of course, if you change the stack pointer, the location of your arguments relative to the stack pointer changes too!
bar:
mov eax,[esp+4]
ret
global barSay we need to make some space on the stack for an array. Now our code becomes:
bar:
mov eax,[esp+4]
ret
global barNote that because esp moved down, we have to adjust our accesses to get to the same locations.
bar:
sub esp,100
mov eax,[esp+104]
add esp,100
ret
global barIt's traditional to use register "ebp" (Extended Base Pointer) to store the old value of the stack pointer. The compiler always sets up register ebp in every function (unless you ask it to omit the frame pointer with "-fomit-frame-pointer"). Unfortunately, ebp is a "callee saved" register--you can't just start using the value like you can with eax through edx, you have to make sure you set it back to the old value (just like the stack pointer!). So it's traditional to push and pop ebp at the start and end of your function, like this:
bar:
mov ecx,esp ;<- backup copy of old stack pointer
sub esp,100
mov eax,[ecx+4] ;<- always our first argument, regardless of the current value of esp
add esp,100 ;<- "mov esp,ecx" would work here too!
ret
global barThe push-and-move at the start of the function is often called the "function prologue"; and the restore-and-pop at the end is often called the "function epilogue". Your function arguments are always at +8, +12, +16, ... bytes from ebp, and your local variables are always at negative offsets from ebp. Because ebp is callee saved, you don't have to worry that print_int is going to change your ebp value--it's every function's responsibility to preserve esp and ebp from their caller.
bar:
push ebp ;<- save the old ebp onto the stack (warning: this does change esp!)
mov ebp,esp ;<- backup copy of old stack pointer
sub esp,100
mov eax,[ebp+8] ;<- always our first argument, regardless of the current value of esp
add esp,100 ;<- or "mov esp,ebp"
pop ebp ; <- restore the old ebp, so we don't crash after we return
ret
C++ "mangles" the linker names of its functions to include the data types of the function arguments. This is good, because it lets you overload function names; but it's bad, because plain C and assembly don't do anything special to the linker names of functions.
In C or assembly, a function "foo" shows up as just plain "foo" in the linker. In C++, a function foo shows up as "foo()" or "foo(int,double,void *)". (Check out the disassembly to be sure how your linker names are coming out.)
So if you call C or assembly code from C++, you have to turn off C++'s name mangling by declaring the C or assembly routines 'extern "C"', like this:
extern "C" void some_assembly_routine(int param1,char *param2);or wrapped in curly braces like this:
extern "C" {
void one_assembly_routine(int x);
void another_assembly_routine(char c);
}
In fact, it's common to provide a "magic" header file for C code that automatically provides 'extern "C"' prototypes for C++, but just works normally in plain C:
#ifdef __cplusplus /* only defined in C++ code */Definitely try these things out yourself:
extern "C" {
#endif
void one_assembly_routine(int x);
void another_assembly_routine(char c);
#ifdef __cplusplus /* only defined in C++ code */
}
#endif
int bar(int i,int j) {(executable NetRun link)
printf("bar(%d,%d)\n",i,j);
return i;
}
extern "C" int bar(int i,int j);(executable NetRun link)
int foo(void)
{
return bar(2,3);
}
Code written in |
With name |
Has linker name |
C++ |
int bar(int a,int b) |
bar(int,int) <- But "mangled" to be alphanumeric... |
C++ |
extern "C" int bar(int a,int b) | bar |
C |
int bar(int a,int b) |
bar |
Assembly |
global bar bar: |
bar |
Fortran |
SUBROUTINE bar() |
bar_, BAR, BAR_, bar__, or some such. Disassemble to be sure... |
When passing... |
In C/C++, you... |
In Fortran, you... |
an int |
push the int |
push a pointer to the integer |
an array |
push a pointer to the first element of the array |
push a pointer to the first element of the array |
a char |
push an int containing the character's value |
push a pointer to the character |
function foo()Here's a "do loop" (the Fortran equivalent of C/C++ "for"):
INTEGER foo
i = 7;
foo = i + 3;
end function
function foo()
INTEGER foo
do i=1,10
CALL print_int(i)
end do
foo = i + 3;
end function
Note that "print_int" is defined in NetRun's "inc.c" as:
CDECL void print_int__(int *i) {print_int(*i);}
Here,