在C ++中调用非虚拟成员函数的机制是什么?

时间:2018-02-02 19:04:50

标签: c++ function member-functions function-call

C ++对象模型是这样的,它不包含任何非虚拟成员函数的表。当有这样一个功能的召唤时

a.my_function();

名称变形就像是

my_function__5AclassKd(&a)

该对象仅包含数据成员。没有非虚函数表。那么在这种情况下,调用机制如何找出要调用的函数? 引擎盖下发生了什么?

3 个答案:

答案 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

如果编译程序然后查看反汇编,它通常也会显示实际的虚拟地址偏移量。