C ++对象模型是这样的,它不包含任何非虚拟成员函数的表。当有这样一个功能的召唤时
a.my_function();
名称变形就像是
my_function__5AclassKd(&a)
该对象仅包含数据成员。没有非虚函数表。那么在这种情况下,调用机制如何找出要调用的函数? 引擎盖下发生了什么?
答案 0 :(得分:2)
正式地,标准并不要求它们以任何特定方式工作,但通常它们的工作方式与普通函数完全相同,但具有额外的不可见参数:指向它们被调用的对象实例的指针。
当然,编译器可能能够对其进行优化,例如如果成员函数不使用this
或任何需要this
的成员变量或成员函数,则不传递指针。
答案 1 :(得分:1)
使用非虚函数,无需在运行时确定要调用哪个函数;因此,生成的机器代码通常看起来与普通函数调用相同,只需使用this
的额外参数,如示例所示。 (虽然它并不总是相同 - 例如,我认为MSVC编译32位程序,至少在某些版本中,在this
寄存器中传递ECX
而不是在堆栈中传递 from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
通常的功能参数。)
因此,确定要在编译时由编译器调用哪个函数。那时,它具有通过解析它可以使用的类声明确定的信息,例如进行方法重载解析,并从那里计算或查找损坏的名称以放入汇编代码。
答案 2 :(得分:1)
编译器的工作是将程序所需的数据和代码布局到内存地址中。每个非虚函数 - 无论是成员还是非成员 - 都会获得一个固定的虚拟内存地址,可以在该地址上调用它。然后调用机器代码硬编码要调用的函数的绝对(或使用位置无关代码调用地址相对偏移量)地址。
例如,假设您的编译器正在编译一个非虚拟成员函数,该函数需要20个字节的机器代码,并且它将可执行代码放在偏移量为0x1000的虚拟地址处,并且已经生成了10个字节的可执行代码对于其他函数,它将在虚拟地址0x100A处启动此函数的代码。想要调用该函数的代码然后为&#34生成机器代码;调用0x100A"将任何函数调用参数(包括一个指向要操作的对象的this
指针)推入堆栈后。
您可以轻松地看到所有这一切:
~/dev > cat example.cc
#include <cstdio>
struct X
{
int f(int n) { return n + 3; }
};
int main()
{
X x;
printf("%d\n", x.f(7));
}
~/dev > g++ example.cc -S; c++filt < example.s
.file "example.cc"
.section .text._ZN1X1fEi,"axG",@progbits,X::f(int),comdat
.align 2
.weak X::f(int)
.type X::f(int), @function
X::f(int): // code to execute X::f(int) starts at label .LFB0
.LFB0: // when this assembly is covered to machine code
.cfi_startproc // it's given a virtual address
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -8(%rbp)
movl %esi, -12(%rbp)
movl -12(%rbp), %eax
addl $3, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size X::f(int), .-X::f(int)
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
leaq -9(%rbp), %rax
movl $7, %esi
movq %rax, %rdi
call X::f(int) // call non-member member function
// machine code will hardcoded address
movl %eax, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
movq -8(%rbp), %rdx
xorq %fs:40, %rdx
je .L5
call __stack_chk_fail@PLT
.L5:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 7.2.0-8ubuntu3) 7.2.0"
.section .note.GNU-stack,"",@progbits
如果编译程序然后查看反汇编,它通常也会显示实际的虚拟地址偏移量。