鱼C论坛

 找回密码
 立即注册
查看: 3628|回复: 15

类的成员函数地址存放问题

[复制链接]
发表于 2021-10-25 22:12:11 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
class c
{
public:
        int a;
        char b;
        int cc1;
        c()
        {

        }
        int add()
        {
                return this->a;
        }

        int ss()
        {
                int i = this->add();
                return i;
        }

};

突然想到的问题,如c类字节是12,this指针的地址存放的是对象首地址,那么this是怎么调用函数的?12个字节已经被变量占完了,函数是存放在哪里的(虚函数除外)?又不像虚函数一样,前面有4个字节可以存放一张函数表
下断点看汇编也下不了,断点自动跳到mian函数里了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-10-25 22:31:31 | 显示全部楼层
编译器最终会把this->???转换成地址,然后call 地址
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-26 12:10:49 | 显示全部楼层
jhq999 发表于 2021-10-25 22:31
编译器最终会把this->???转换成地址,然后call 地址

存放位置喃?这个我知道,this会指向一张表,表里就是各种JMP,JMP的地址就是真正要call的地址
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-26 12:55:37 | 显示全部楼层
2736946915 发表于 2021-10-26 12:10
存放位置喃?这个我知道,this会指向一张表,表里就是各种JMP,JMP的地址就是真正要call的地址

一般在代码段或者导入表指向的映射在内存里的动态库等等。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-27 20:34:32 | 显示全部楼层
jhq999 发表于 2021-10-26 12:55
一般在代码段或者导入表指向的映射在内存里的动态库等等。

我也认为函数是在代码段,但是网上说this是在对象的首地址(我不清楚),所以好奇this这四个字节他是跟着函数还是跟着对象走的,因为个人理解指针移动是用偏移的(类似于jmp内存偏移),既能调用函数又能调用变量,不可能偏移那么多吧,不然资源损耗太大
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-27 21:40:47 | 显示全部楼层
2736946915 发表于 2021-10-27 20:34
我也认为函数是在代码段,但是网上说this是在对象的首地址(我不清楚),所以好奇this这四个字节他是跟着函 ...

this 就是当前对象的地址,在调用成员函数的时候把这个地址传给成员函数
#include <iostream>

class point_t {
public:
    point_t(ssize_t x = 0, ssize_t y = 0): x(x), y(y) {}
    void print() {
        std::cout << "this: " << this << std::endl;
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
private:
    ssize_t x, y;
};

int main() {
    point_t p1(1, 2), p2(3, 4);
    std::cout << "&p1: " << &p1 << std::endl;
    std::cout << "&p2: " << &p2 << std::endl;
    p1.print();
    p2.print();
    return 0;
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-28 12:12:37 | 显示全部楼层
人造人 发表于 2021-10-27 21:40
this 就是当前对象的地址,在调用成员函数的时候把这个地址传给成员函数

就是回调函数呗,对象的内存空间已经被变量占完了,那么他的成员函数放在那?
目前网上看到最靠谱的答案是,this存在于CPU的寄存器中,通过ECX去访问代码段的地址,也就是说一个类编译时会把函数地址大小确定在代码区,生成的对象调用时会直接重系统表里查找地址并传参,
但是问题就是this访问两段内存块(对象内存和函数内存),听起来有点不可思议,
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-28 12:42:03 | 显示全部楼层
2736946915 发表于 2021-10-28 12:12
就是回调函数呗,对象的内存空间已经被变量占完了,那么他的成员函数放在那?
目前网上看到最靠谱的答案 ...

1. 不是回调函数
2. 函数放在代码段
3. this 不是存在于 CPU 的寄存器中,this 就是当前对象的地址,你在当前对象前面加上取地址符号(&)就可以得到当前对象的 this 值
4. 生成的对象调用时会直接重系统表里查找地址并传参,不是这样的,是直接取到当前对象的地址(汇编语言的 lea 指令),然后传给成员函数(通过堆栈或者寄存器)
5. 但是问题就是this访问两段内存块(对象内存和函数内存),没有对象内存和函数内存这么一说,是代码段,数据段,堆栈段
6. 如果你真的想要弄明白这些东西,你必须去学汇编语言,8086 汇编语言
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-28 12:47:11 | 显示全部楼层
这是上面那个代码的汇编语言版本,但是你现在还看不懂对吧?
你的这个问题的答案全在下面这个汇编语言代码中
等你什么时候看得懂了这个代码,你的这个问题才算得上是真的得到了解决
很抱歉,我没办法给你解释清楚这一切,因为你现在还有好多东西没有学,你至少也需要先把汇编语言学完
    .file   "main.cpp"
    .text
    .section .rdata,"dr"
_ZStL19piecewise_construct:
    .space 1
.lcomm _ZStL8__ioinit,1,1
    .section    .text$_ZN7point_tC1Ell,"x"
    .linkonce discard
    .align 2
    .globl  _ZN7point_tC1Ell
    .def    _ZN7point_tC1Ell;   .scl    2;  .type   32; .endef
    .seh_proc   _ZN7point_tC1Ell
_ZN7point_tC1Ell:
.LFB1496:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    .seh_endprologue
    movq    %rcx, 16(%rbp)
    movq    %rdx, 24(%rbp)
    movq    %r8, 32(%rbp)
    movq    16(%rbp), %rax
    movq    24(%rbp), %rdx
    movq    %rdx, (%rax)
    movq    16(%rbp), %rax
    movq    32(%rbp), %rdx
    movq    %rdx, 8(%rax)
    nop
    popq    %rbp
    ret
    .seh_endproc
    .section .rdata,"dr"
.LC0:
    .ascii "this: \0"
.LC1:
    .ascii "(\0"
.LC2:
    .ascii ", \0"
.LC3:
    .ascii ")\0"
    .section    .text$_ZN7point_t5printEv,"x"
    .linkonce discard
    .align 2
    .globl  _ZN7point_t5printEv
    .def    _ZN7point_t5printEv;    .scl    2;  .type   32; .endef
    .seh_proc   _ZN7point_t5printEv
_ZN7point_t5printEv:
.LFB1497:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $32, %rsp
    .seh_stackalloc 32
    .seh_endprologue
    movq    %rcx, 16(%rbp)
    leaq    .LC0(%rip), %rdx
    movq    .refptr._ZSt4cout(%rip), %rcx
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movq    16(%rbp), %rdx
    movq    %rax, %rcx
    call    _ZNSolsEPKv
    movq    .refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
    movq    %rax, %rcx
    call    _ZNSolsEPFRSoS_E
    leaq    .LC1(%rip), %rdx
    movq    .refptr._ZSt4cout(%rip), %rcx
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movq    %rax, %rcx
    movq    16(%rbp), %rax
    movq    (%rax), %rax
    movq    %rax, %rdx
    call    _ZNSolsEl
    leaq    .LC2(%rip), %rdx
    movq    %rax, %rcx
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movq    %rax, %rcx
    movq    16(%rbp), %rax
    movq    8(%rax), %rax
    movq    %rax, %rdx
    call    _ZNSolsEl
    leaq    .LC3(%rip), %rdx
    movq    %rax, %rcx
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movq    .refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
    movq    %rax, %rcx
    call    _ZNSolsEPFRSoS_E
    nop
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .def    __main; .scl    2;  .type   32; .endef
    .section .rdata,"dr"
.LC4:
    .ascii "&p1: \0"
.LC5:
    .ascii "&p2: \0"
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
.LFB1498:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $64, %rsp
    .seh_stackalloc 64
    .seh_endprologue
    call    __main
    leaq    -16(%rbp), %rax
    movl    $2, %r8d
    movl    $1, %edx
    movq    %rax, %rcx
    call    _ZN7point_tC1Ell
    leaq    -32(%rbp), %rax
    movl    $4, %r8d
    movl    $3, %edx
    movq    %rax, %rcx
    call    _ZN7point_tC1Ell
    leaq    .LC4(%rip), %rdx
    movq    .refptr._ZSt4cout(%rip), %rcx
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movq    %rax, %rcx
    leaq    -16(%rbp), %rax
    movq    %rax, %rdx
    call    _ZNSolsEPKv
    movq    .refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
    movq    %rax, %rcx
    call    _ZNSolsEPFRSoS_E
    leaq    .LC5(%rip), %rdx
    movq    .refptr._ZSt4cout(%rip), %rcx
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movq    %rax, %rcx
    leaq    -32(%rbp), %rax
    movq    %rax, %rdx
    call    _ZNSolsEPKv
    movq    .refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rdx
    movq    %rax, %rcx
    call    _ZNSolsEPFRSoS_E
    leaq    -16(%rbp), %rax
    movq    %rax, %rcx
    call    _ZN7point_t5printEv
    leaq    -32(%rbp), %rax
    movq    %rax, %rcx
    call    _ZN7point_t5printEv
    movl    $0, %eax
    addq    $64, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .def    _Z41__static_initialization_and_destruction_0ii;    .scl    3;  .type   32; .endef
    .seh_proc   _Z41__static_initialization_and_destruction_0ii
_Z41__static_initialization_and_destruction_0ii:
.LFB1919:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $32, %rsp
    .seh_stackalloc 32
    .seh_endprologue
    movl    %ecx, 16(%rbp)
    movl    %edx, 24(%rbp)
    cmpl    $1, 16(%rbp)
    jne .L7
    cmpl    $65535, 24(%rbp)
    jne .L7
    leaq    _ZStL8__ioinit(%rip), %rcx
    call    _ZNSt8ios_base4InitC1Ev
    movq    .refptr.__dso_handle(%rip), %r8
    leaq    _ZStL8__ioinit(%rip), %rdx
    movq    .refptr._ZNSt8ios_base4InitD1Ev(%rip), %rcx
    call    __cxa_atexit
.L7:
    nop
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .def    _GLOBAL__sub_I_main;    .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_main
_GLOBAL__sub_I_main:
.LFB1920:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe   %rbp, 0
    subq    $32, %rsp
    .seh_stackalloc 32
    .seh_endprologue
    movl    $65535, %edx
    movl    $1, %ecx
    call    _Z41__static_initialization_and_destruction_0ii
    nop
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .section    .ctors,"w"
    .align 8
    .quad   _GLOBAL__sub_I_main
    .ident  "GCC: (GNU) 7.4.0"
    .def    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc;    .scl    2;  .type   32; .endef
    .def    _ZNSolsEPKv;    .scl    2;  .type   32; .endef
    .def    _ZNSolsEPFRSoS_E;   .scl    2;  .type   32; .endef
    .def    _ZNSolsEl;  .scl    2;  .type   32; .endef
    .def    _ZNSt8ios_base4InitC1Ev;    .scl    2;  .type   32; .endef
    .def    __cxa_atexit;   .scl    2;  .type   32; .endef
    .section    .rdata$.refptr._ZNSt8ios_base4InitD1Ev, "dr"
    .globl  .refptr._ZNSt8ios_base4InitD1Ev
    .linkonce   discard
.refptr._ZNSt8ios_base4InitD1Ev:
    .quad   _ZNSt8ios_base4InitD1Ev
    .section    .rdata$.refptr.__dso_handle, "dr"
    .globl  .refptr.__dso_handle
    .linkonce   discard
.refptr.__dso_handle:
    .quad   __dso_handle
    .section    .rdata$.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, "dr"
    .globl  .refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    .linkonce   discard
.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_:
    .quad   _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    .section    .rdata$.refptr._ZSt4cout, "dr"
    .globl  .refptr._ZSt4cout
    .linkonce   discard
.refptr._ZSt4cout:
    .quad   _ZSt4cout
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-28 21:36:21 | 显示全部楼层
人造人 发表于 2021-10-28 12:42
1. 不是回调函数
2. 函数放在代码段
3. this 不是存在于 CPU 的寄存器中,this 就是当前对象的地址,你 ...

3,我指的是this的地址(&this),不是this指向的地址
4,我是指使用的时候这个this会一直存在ECX中,
5.知道是代码段数据段,但是说习惯了。。。。
6.没必要太了解汇编,毕竟不是什么大牛,会用就行,能看懂就OK,比如说之前的LEA指令,意思类似于&,
call的时候也是call到一堆jmp中,并不是想象中的直接push了,不确定一截jmp是不是类似于虚函数的函数表,只是猜测
00EA109B  jmp         __execute_onexit_table (0EA50CDh)  
00EA10A0  jmp         __scrt_stub_for_acrt_uninitialize (0EA51C0h)  
00EA10A5  jmp         __scrt_main_policy::set_app_type (0EA2180h)  
00EA10AA  jmp         __wsplitpath_s (0EA50F1h)  
00EA10AF  jmp         __scrt_dllmain_before_initialize_c (0EA2D00h)  
00EA10B4  jmp         __scrt_get_show_window_mode (0EA3740h)  
00EA10B9  jmp         ___current_exception (0EA4FF5h)  
00EA10BE  jmp         _CRT_RTC_INITW (0EA1CC0h)  
00EA10C3  jmp         _GetProcessHeap@0 (0EA5163h)  
00EA10C8  jmp         _GetCurrentThreadId@0 (0EA50FDh)  
00EA10CD  jmp         printf (0EA19E0h)  
00EA10D2  jmp         _get_startup_file_mode (0EA3430h)  
00EA10D7  jmp         __CrtDbgReportW (0EA5031h)  
00EA10DC  jmp         _RTC_Failure (0EA23C0h)  
00EA10E1  jmp         _GetCurrentProcessId@0 (0EA5121h)  
00EA10E6  jmp         _FreeLibrary@4 (0EA516Fh)  
00EA10EB  jmp         __set_app_type (0EA503Dh)  
00EA10F0  jmp         __scrt_dllmain_exception_filter (0EA2D90h)  
00EA10F5  jmp         _matherr (0EA3400h)  
00EA10FA  jmp         ___stdio_common_vfprintf (0EA5025h)  
00EA10FF  jmp         __register_thread_local_exe_atexit_callback (0EA5091h)  
00EA1104  jmp         __scrt_dllmain_uninitialize_c (0EA2DF0h)  
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-28 21:51:21 | 显示全部楼层
2736946915 发表于 2021-10-28 21:36
3,我指的是this的地址(&this),不是this指向的地址
4,我是指使用的时候这个this会一直存在ECX中,
5.知 ...


3. this 没有地址,&this 是错误的表达式
#include <iostream>

class point_t {
public:
    point_t(ssize_t x = 0, ssize_t y = 0): x(x), y(y) {}
    void print() {
        std::cout << "this: " << this << std::endl;
        std::cout << "&this: " << &this << std::endl;
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
private:
    ssize_t x, y;
};

int main() {
    point_t p1(1, 2), p2(3, 4);
    std::cout << "&p1: " << &p1 << std::endl;
    std::cout << "&p2: " << &p2 << std::endl;
    p1.print();
    p2.print();
    return 0;
}

4. 这个不一定,和编译器有关,编译器想用哪个寄存器就用哪个,编译器也完全可以使用 push 指令
6. 不是虚函数的函数表,在我这边就没有这个
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-28 21:58:15 | 显示全部楼层
这里就没有使用 ecx 寄存器
__ZN7point_tC1Eii:
        pushl        %ebp
        movl        %esp, %ebp
        movl        8(%ebp), %eax
        movl        12(%ebp), %edx
        movl        %edx, (%eax)
        movl        8(%ebp), %eax
        movl        16(%ebp), %edx
        movl        %edx, 4(%eax)
        nop
        popl        %ebp
        ret
        movl        $2, 8(%esp)
        movl        $1, 4(%esp)
        leal        24(%esp), %eax
        movl        %eax, (%esp)
        call        __ZN7point_tC1Eii
        movl        $4, 8(%esp)
        movl        $3, 4(%esp)
        leal        16(%esp), %eax
        movl        %eax, (%esp)
        call        __ZN7point_tC1Eii
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-28 22:02:14 | 显示全部楼层
6. 不同的编译器在调用成员函数的时候,生成的指令一般是不一样的
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-29 13:47:54 | 显示全部楼层
人造人 发表于 2021-10-28 21:58
这里就没有使用 ecx 寄存器

如果ECX被占用,就会用其他的寄存器,大部分情况下都是用ECX来传this,做外挂的也是找ECX,为什么,省事啊,
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-10-29 13:52:30 | 显示全部楼层
人造人 发表于 2021-10-28 21:51
3. this 没有地址,&this 是错误的表达式

一个类有虚函数为什么比普通类多4个字节?这4个字节就是虚函数的一个函数指针,指向的就是一张表,那个子类调用移动函数指针就行了,省事又方便,,ECX传this,EAX,EBX一般用于传参数,return的数据一般用EAX传,还有好多,大部分情况下是这样,除非当时被占用,这个资料百度上也是大把的,寄存器是有优先用那个,后用那个的,不是随意
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-10-29 14:09:46 | 显示全部楼层
2736946915 发表于 2021-10-29 13:52
一个类有虚函数为什么比普通类多4个字节?这4个字节就是虚函数的一个函数指针,指向的就是一张表,那个子 ...

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-9-22 19:34

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表