Cookie Consent by Free Privacy Policy Generator 📌 Virtual functions and binary sizes

🏠 Team IT Security News

TSecurity.de ist eine Online-Plattform, die sich auf die Bereitstellung von Informationen,alle 15 Minuten neuste Nachrichten, Bildungsressourcen und Dienstleistungen rund um das Thema IT-Sicherheit spezialisiert hat.
Ob es sich um aktuelle Nachrichten, Fachartikel, Blogbeiträge, Webinare, Tutorials, oder Tipps & Tricks handelt, TSecurity.de bietet seinen Nutzern einen umfassenden Überblick über die wichtigsten Aspekte der IT-Sicherheit in einer sich ständig verändernden digitalen Welt.

16.12.2023 - TIP: Wer den Cookie Consent Banner akzeptiert, kann z.B. von Englisch nach Deutsch übersetzen, erst Englisch auswählen dann wieder Deutsch!

Google Android Playstore Download Button für Team IT Security



📚 Virtual functions and binary sizes


💡 Newskategorie: Programmierung
🔗 Quelle: dev.to

In the previous article of this series on binary sizes, we discussed how special functions' implementations - or their lack of - influence the size of the generated binary.

Our conclusion was that if we could, we should follow the rule of 0. Not only because of simplicity for the reader but also for the compiler. If that's not possible, we should not declare destructors virtual in vain. And if it's possible, we should =default non-virtual destructors in the header both for readability and for binary sizes.

On the other hand, if we must have a non-virtual destructor, we should clearly think about implementing (preferably with =default) the destructor in the .cpp file, in other words, out-of-line. While you might find it strange in terms of readability, it produced a smaller binary.

It's still worth noting that in most circumstances, a class with a virtual destructor contributes to a larger binary file.

But does it matter how many functions are virtual? Given the same amount of methods in a class, is there a difference in terms of binary size whether one or all of them are virtual?

That's the question we are going to discuss in this post.

What does the virtual keyword do?

When any of a class-member function is declared with the virtual keyword, it means that the compiler cannot know during compile-time which implementation of the virtual function will be called.

How could it resolve during runtime which function is to be called?

As it's explained on Johnny's Software Lab, the way to do it is not standardized. Yet, most compilers do it in a similar way.

For each class that has at least one virtual method, the compiler creates a virtual table. It's usually just referred to as a vtable. The vtable of each class contains a pointer to each of their virtual functions. A virtual table might not contain pointers only to the same class. If a derived class doesn't override a method of a base class, then it will point to that base class implementation.

When an object is created at runtime, there is also a virtual pointer (vpointer) created pointing to the right virtual table.

So there is one vpointer for each object created at run-time, but only one vtable per type which is created per compile-time.

I think we can rightly expect that therefore a virtual method - due to the vtable - results in an increased binary size and we could also expect that any additional virtual method will add to the size of the binary as the vtable grows, but only a little bit.

Validate our hypothesis

Now let's create two main.cpp files. In the first one, we are going to have two classes with a virtual destructor and with 3 members each having a non-virtual accessor. In the second example, the same class will have those accessors turned into virtual ones. I know it's not a particularly elaborate example, but it's a start.

// main-single-virtual.cpp
#include <array>

class SingleVirtual {
public:
    SingleVirtual() = default;
    SingleVirtual(int a, int b, int c) : m_a(a), m_b(b), m_c(c) {}
    virtual ~SingleVirtual() = default;

    int getA() const { return m_a; }
    int getB() const { return m_b; }
    int getC() const { return m_c; }

private:
    int m_a = 0;
    int m_b = 0;
    int m_c = 0;
};


std::array<SingleVirtual, 10'000> a;

int main() { }

// main-many-virtuals.cpp
#include <array>

class ManyVirtuals {
public:
    ManyVirtuals() = default;
    ManyVirtuals(int a, int b, int c) : m_a(a), m_b(b), m_c(c) {}
    virtual ~ManyVirtuals() = default;

    virtual int getA() const { return m_a; }
    virtual int getB() const { return m_b; }
    virtual int getC() const { return m_c; }

private:
    int m_a = 0;
    int m_b = 0;
    int m_c = 0;
};

std::array<ManyVirtuals, 10'000> a;

int main() { }

Depending on the optimization level, the version where only the destructor is virtual was compiled into a binary with the size of 281,488/281,520. The fully virtual version had slightly bigger binaries, 281,615/281,647.

We can see that the difference is small. In the previous articles, I used an array of 10,000 objects so that we don't have to look into tiny differences. But in this case, it's worth having a look at the exact size of the difference.

It's less than 200 bytes per 10,000 objects. To be more precise, the difference is 127 bytes, way less than one byte per object. It cannot have anything to do with the number of objects. It is only about the size of the vtable. It's almost negligible.

First, I ran both examples with a much smaller array of only 10 objects. The difference between the two versions was still exactly 127 bytes.

As we start to remove the virtual keywords from the accessors one by one, the difference also shrinks. First by 48 byes to 79 bytes, then by another 32/48 bytes (depending on the optimization level) to 31-47 bytes. And finally, by devirtualizing the third accessor, the difference shrinks by another 48 bytes depending.

Another observation that we can make, is that at the end, ManyVirtuals ended up with a smaller binary. That's because its name is shorter. But that only mattered when the optimization level was -O0

What can we learn from this?

It seems that the size of a vtable entry is about 48 bytes, at least on Apple Clang 15. It might not seem a big deal at first, but if we have 10 classes with 3 virtual methods each that's already more than 1KB. But these numbers can be much higher, especially if we consider a virtual method in a class template. That can quickly explode if we don't pay attention.

At the same time, we can only rest assured that the length of class/function names is not a problem anymore as it was in the past - given that we turn on compiler optimizations.

If we have a look at the assembly code after an unoptimized build (so that it remains somewhat readable), we can see things that seem like a list, probably the virtual table:

// SingleVirtual.s

    .section    __DATA,__const
    .globl  __ZTV13SingleVirtual            ; @_ZTV13SingleVirtual
    .weak_def_can_be_hidden __ZTV13SingleVirtual
    .p2align    3
__ZTV13SingleVirtual:
    .quad   0
    .quad   __ZTI13SingleVirtual
    .quad   __ZN13SingleVirtualD1Ev
    .quad   __ZN13SingleVirtualD0Ev


// ManyVirtuals.s

    .section    __DATA,__const
    .globl  __ZTV12ManyVirtuals             ; @_ZTV12ManyVirtuals
    .weak_def_can_be_hidden __ZTV12ManyVirtuals
    .p2align    3
__ZTV12ManyVirtuals:
    .quad   0
    .quad   __ZTI12ManyVirtuals
    .quad   __ZN12ManyVirtualsD1Ev
    .quad   __ZN12ManyVirtualsD0Ev
    .quad   __ZNK12ManyVirtuals4getAEv
    .quad   __ZNK12ManyVirtuals4getBEv
    .quad   __ZNK12ManyVirtuals4getCEv

For SingleVirtual, we can see an entry for the constructors and the destructor. We can also see that it's in the const DATA section. But there is nothing for the getter methods. On the other hand, those are there in ManyVirtuals. I couldn't figure out why there are entries for the constructors which are not virtual in C++. If you happen to know, please share in the comments or by e-mail.

Conclusion

In this post, we saw how little it matters whether we add a new virtual method to a class that has already a virtual destructor. Having a virtual destructor ensures that everything necessary is instrumented for polymorphic behaviour which can make a huge difference in your binary size - and runtime performance. At the same time, adding another virtual function barely adds to the size of the binary. It'll only mean an extra entry in your vtable.

In the next episode, we'll have a look into two classic design patterns. We'll take a classical reference semantic and a modern value semantic implementation and see how much that matters in terms of binary size.

...



📌 Virtual functions and binary sizes


📈 52.3 Punkte

📌 Image sizes vulnerable to command injection typecast sizes to int


📈 38.76 Punkte

📌 Functions of Commercial Bank: Primary Functions and Secondary Functions


📈 36.82 Punkte

📌 Binary Rewriting Tutorial – learn to disassemble, transform, and relink binary executables


📈 25.78 Punkte

📌 B2R2 - Collection Of Useful Algorithms, Functions, And Tools For Binary Analysis


📈 25.46 Punkte

📌 Durable Functions 2.0 - Serverless Actors, Orchestrations, and Stateful Functions


📈 25.14 Punkte

📌 The difference between Arrow functions and Normal functions in JavaScript


📈 25.14 Punkte

📌 Virtual Pets, Virtual People and Virtual Immortality


📈 24.16 Punkte

📌 Count ways of creating Binary Array ending with 1 using Binary operators


📈 23.99 Punkte

📌 Hooking Linux Kernel Functions, Part 2: How to Hook Functions with Ftrace


📈 23.35 Punkte

📌 Hooking Linux Kernel Functions, Part 2: How to Hook Functions with Ftrace


📈 23.35 Punkte

📌 Polypyus - Learns To Locate Functions In Raw Binaries By Extracting Known Functions From Similar Binaries


📈 23.35 Punkte

📌 Durable functions in Python for Azure Functions | Azure Friday


📈 23.35 Punkte

📌 You Need to Know About Pure Functions & Impure Functions in JavaScript


📈 23.35 Punkte

📌 C User-Defined Functions vs Library Functions


📈 23.35 Punkte

📌 JavaScript Arrow Functions vs Regular Functions


📈 23.35 Punkte

📌 Arrow Functions vs. Regular Functions in JavaScript: A Comprehensive Guide


📈 23.35 Punkte

📌 Instagram dimensions and sizes for photos, videos, and stories


📈 22.97 Punkte

📌 RAM and Battery Sizes Unveiled for the iPhone XS, XS Max, and XR


📈 22.97 Punkte

📌 Merging PDFs used pdfunite and ghostscript and obtained two very different file sizes.


📈 22.97 Punkte

📌 Web inventor Sir Tim sizes up handcuffs for his creation – and world has 2 weeks to appeal


📈 21.18 Punkte

📌 Snack-happy parrot shows insider threats come in all shapes and sizes


📈 21.18 Punkte

📌 Snack-happy parrot shows insider threats come in all shapes and sizes


📈 21.18 Punkte

📌 General availability of new Azure disk sizes and bursting


📈 21.18 Punkte

📌 Best of Galaxy Store Awards 2019 Winner: David Brittain of TopHatch on Adapting to Different Input Methods and Screen Sizes


📈 21.18 Punkte

📌 Understanding thread stack sizes and how Alpine Linux is different


📈 21.18 Punkte

📌 Apple Store Leak Confirms New Apple Watch Series 4 Sizes as 40mm and 44mm


📈 21.18 Punkte

📌 Despite their small sizes, the ASUS ZenBook 13 and 14 pack plenty of ports


📈 21.18 Punkte

📌 The Surface Laptop 3 is on sale for $300 off on several colors and sizes


📈 21.18 Punkte

📌 Xbox controllers of all shapes and sizes in these Super Spring deals


📈 21.18 Punkte

📌 EmulationStation Desktop Edition (ES-DE) gets new default look and different font sizes


📈 21.18 Punkte











matomo