Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to analyze the virtual function of C++

2025-03-26 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

How to carry out C++ virtual function analysis, in view of this problem, this article introduces the corresponding analysis and answers in detail, hoping to help more partners who want to solve this problem to find a more simple and feasible method.

Virtual function calls belong to run-time polymorphism. In the inheritance relationship of classes, the methods of the same name of different subclass objects are called through the parent class pointer, resulting in different effects.

Polymorphism in C++ is achieved through late binding (when objects are constructed).

Usage

Declare the keyword virtual before the function to indicate that it is a virtual function, and add a = 0 after the function to indicate that it is a pure virtual function. The class of a pure virtual function cannot create a concrete instance.

This example is used for later analysis, a parent class that contains pure virtual functions, a subclass that overrides the parent method, and a class without inheritance.

Struct Base {Base (): val (7777) {} virtual int fuck (int a) = 0; int val;}; struct Der: public Base {Der () = default; int fuck (int a) override {return val + 4396;}}; struct A {A () = default; void funny (int a) {}}; int main () {Der der; Base * pbase = & der; pbase- > fuck (sizeof (Der); / / call Der::fuck (int a); An a A.funny (sizeof (A)); / / A::funny (int a); return 3;}

Realize

Originally, it is understood that the virtual function is realized through the offset of the virtual table to obtain the actual calling function address, but when to determine this offset and the specific offset details are not specified. Let's find out today.

Take the above code to disassemble and extract some functions. Main,Base::Base (), Base::fuck (), Der::Der (), Der::fuck, A::funny () are as follows:

_ ZN4BaseC2Ev:.LFB1: .cfi _ startproc pushq% rbp .cfi _ def_cfa_offset 16 .cfi _ offset 6,-16 movq% rsp,% rbp .cfi _ def_cfa_register 6 movq% rdi,-8 (% rbp) / / or the stack frame of the main function-32 (% rpb) address leaq 16+_ZTV4Base (% rip),% rdx / / the key point comes, and the address of virtual table offset 16 is also _ _ cxa_pure_virtual Here is the meaningless movq-8 (% rbp),% rax movq% rdx, (% rax) / / stores the address of _ _ cxa_pure_virtual in the memory of the address rax (in this case, where the stack frame of the main function is-32 (% rpb)), movq-8 (% rbp),% rax / / and then offsets 8 bytes back, that is, skipping the virtual table pointer and initializing the member variable val. Movl $7777, 8 (% rax) nop / / Note: the above is brought in with the actual address in this example. In fact, the processing of a class is a general logic. The first parameter rdi passed in the constructor is the this pointer. Due to the existence of the virtual table, the content of the address where the this pointer is located will be modified. That is, the offset address of the virtual table (non-starting address) popq% rbp .cfi _ def_cfa 7, 8 ret .cfi _ endproc.LFE1: .size _ ZN4BaseC2Ev,.-_ ZN4BaseC2Ev .offset _ ZN4BaseC1Ev .set _ ZN4BaseC1Ev,_ZN4BaseC2Ev .section .text. _ ZN3Der4fuckEi, "axG", @ progbits,_ZN3Der4fuckEi,comdat .align 2 .offset _ ZN3Der4fuckEi .type _ ZN3Der4fuckEi, @ function_ZN3Der4fuckEi:.LFB3: .cfi _ startproc 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) movq-8 (% rbp),% rax movl 8 (% rax),% eax / / member variable val Val is the value addl $4396,% eax / / val + 4396 popq% rbp .cfi _ def_cfa 7, 8 ret .cfi _ endproc.LFE3: .size _ ZN3Der4fuckEi,.-_ ZN3Der4fuckEi .section .text. _ ZN1A5funnyEi, "axG", @ progbits,_ZN1A5funnyEi,comdat .align 2. Offset _ ZN1A5funnyEi. Type _ ZN1A5funnyEi, @ function_ZN1A5funnyEi:.LFB4: .cfi _ startproc 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) nop popq% rbp .cfi _ def_cfa 7,8 ret .cfi _ endproc.LFE4: .size _ ZN1A5funnyEi,.-_ ZN1A5funnyEi .section .text. _ ZN3DerC2Ev, "axG", @ progbits,_ZN3DerC5Ev,comdat .align 2 .align _ ZN3DerC2Ev .type _ ZN3DerC2Ev, @ function_ZN3DerC2Ev:.LFB7: .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% rdi,-8 (% rbp) / / rdi is the constructor of the main stack frame-32 (% rbp) address movq-8 (% rbp),% rax movq% rax,% rdi call _ ZN4BaseC2Ev / / Base And the incoming parameters are passed in as arguments, where leaq 16+_ZTV3Der (% rip) is tracked,% rdx / / offset the 16-byte _ ZN3Der4fuckEi address movq-8 (% rbp) from the virtual table,% rax movq% rdx, (% rax) / / rax has been modified in the previous Base constructor, here will continue to modify the content, the previous modification is invalid. Nop leave .cfi _ def_cfa 7, 8 ret .cfi _ endproc.LFE7: .size _ ZN3DerC2Ev,. _ ZN3DerC2Ev .requests _ ZN3DerC1Ev .set _ ZN3DerC1Ev,_ZN3DerC2Ev .text .globl main .type main, @ functionmain:.LFB5: .cfi _ startproc pushq% rbp .cfi _ def_cfa_offset 16.cfi _ offset 6,-16 movq% rsp,% rbp .cfi _ def_cfa_register 6 subq $48,% rsp leaq-32 (% rbp),% rax / / take-32 (% rbp) address Corresponding to Base * pbase Movq% rax,% rdi call _ ZN3DerC1Ev / / called the constructor and took the address of-32 (% rbp) as an argument, where leaq-32 (% rbp) is tracked in,% rax / /-32 (% rbp) is modified, and the contents of this memory are the offset addresses movq% rax,-8 (% rbp) movq-8 (% rbp),% rax movq (% rax),% rax / / rax = M [rax] of the Der virtual table. Take the address movq (% rax) in the virtual table offset,% rdx / / rdx = M [rax], and take out the contents of the virtual table offset (that is, the function address). Taking into account the above, this is twice dereferencing movq-8 (% rbp),% rax movl $16,% esi / / sizeof (Der) = 16, including a virtual table pointer and int val Movq% rax,% rdi / / address in virtual table offset call *% rdx / / call function leaq-33 (% rbp),% rax movl $1,% esi movq% rax,% rdi call _ ZN1A5funnyEi / / ordinary member function Implement simple movl $3,% eax leave .cfi _ def_cfa 7, 8 ret .cfi _ endproc.LFE5: .size main,.-main .alignment _ ZTV3Der .section .data.rel.ro.local. _ ZTV3Der, "awG", @ progbits,_ZTV3Der,comdat .align 8.type _ ZTV3Der, @ object .size _ ZTV3Der, 24_ZTV3Der: .quad 0.quad _ ZTI3Der .quad _ ZN3Der4fuckEi / / Der::fuck (int a) .virtual _ ZTV4Base .section .data.rel.ro. _ ZTV4Base, "awG", @ progbits,_ZTV4Base,comdat .align 8.type _ ZTV4Base, @ object .size _ ZTV4Base, 24_ZTV4Base: .quad 0.quad _ ZTI4Base .quad _ cxa_pure_virtual / / Pure virtual function, there is no corresponding symbol table .virtual _ ZTI3Der .section .data.rel.ro. _ ZTI3Der, "awG", @ progbits,_ZTI3Der,comdat .align 8 .type _ ZTI3Der, @ object .size _ ZTI3Der, 24

Now it is a pure virtual function, and there are no virtual destructors in the class. Look at some of this implementation through disassembly.

_ ZTV3Der and _ ZTV4Base are two virtual tables of 24, 8-byte alignment, corresponding to the Der subclass and the Base parent class, respectively. The 16-byte offset in the virtual table (the offset size may be related to the implementation) is the virtual function address. Each time the constructor is called, the offset address is stored in the memory where the parent pointer is located, so you can see in the above code that the offset address is set in the constructor of the Base and Der classes, but the subclass constructor overrides the modification of the parent class. In this way, the running address of the actual function depends on the constructor, the subclass object is constructed to call the method of the subclass, and the parent class construction calls the method of the parent class (not pure virtual function), realizing runtime polymorphism.

After adding a virtual function, the following virtual function address is added to the virtual table, as follows

Virtual void Base::shit () {} void Der::shit () override {} _ ZTV3Der: .quad 0.quad _ ZTI3Der .quad _ ZN3Der4fuckEi .quad _ ZN3Der4shitEv .quad _ ZTV4Base .section .data.rel.ro. _ ZTV4Base, "awG", @ progbits,_ZTV4Base,comdat .align 8 .type _ ZTV4Base, @ object .size _ ZTV4Base, 32_ZTV4Base: .quad 0.quad _ ZTI4Base .quad _ cxa_pure_virtual _ cxa_pure_virtual _ ZN4Base4shitEv .quad _ ZTI3Der .section .data.rel.ro. _ ZTI3Der, "awG", @ progbits _ ZTI3Der,comdat .align 8 .type _ ZTI3Der, @ object .size _ ZTI3Der, 24

It is much easier to call another virtual function, and the address is offset directly (here shit is after fuck, so + 8)

Movq-8 (% rbp),% rax movq (% rax),% rax addq $8,% rax movq (% rax),% rdx movq-8 (% rbp),% rax movq% rax,% rdi call *% rdx

Briefly draw the memory structure diagram of the virtual function.

This is the end of the answer to the question on how to carry out C++ virtual function analysis. I hope the above content can be of some help to you. If you still have a lot of doubts to be solved, you can follow the industry information channel for more related knowledge.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report