Подтвердить что ты не робот

Как определить размер объекта в С++?

Например, скажем, у меня есть класс Temp:

class Temp
{
    public:
        int function1(int foo) { return 1; }
        void function2(int bar) { foobar = bar; }

    private:
        int foobar;
};

Когда я создаю объект класса Temp, как бы я вычислил, сколько места ему нужно и как он представлен в памяти (например, 4 байта для foobar | 8 байтов для функции1 и т.д.)

4b9b3361

Ответ 1

В первом приближении размер объекта является суммой размеров его составных элементов данных. Вы можете быть уверены, что он никогда не будет меньше этого.

Точнее, компилятор имеет право вставлять пробел между элементами данных, чтобы гарантировать, что каждый элемент данных отвечает требованиям выравнивания платформы. Некоторые платформы очень строги относительно выравнивания, в то время как другие (x86) являются более прощающими, но будут выполнять значительно лучше при правильном выравнивании. Таким образом, даже параметр оптимизации компилятора может влиять на размер объекта.

Наследование и виртуальные функции добавляют дополнительное усложнение. Как говорили другие, функции-члены вашего класса сами не занимают пространство "на объект", но существование виртуальных функций в этом интерфейсе класса обычно подразумевает существование виртуальной таблицы, по существу, таблицы поиска указателей функций, используемых для динамически разрешать правильную реализацию функции для вызова во время выполнения. Виртуальная таблица (vtbl) доступна в основном через указатель, хранящийся в каждом объекте.

Производные объекты класса также включают все члены данных их базовых классов.

Наконец, спецификаторы доступа (public, private, protected) предоставляют компилятору определенную свободу действий с упаковкой данных.

Короткий ответ: sizeof (myObj) или sizeof (MyClass) всегда укажет вам правильный размер объекта, но его результат не всегда легко предсказать.

Ответ 2

sizeof(Temp)

предоставит вам размер. Скорее всего, это 4 байта (учитывая множество предположений), и это только для int. Функции не занимают ни одной комнаты на основе каждого объекта, они скомпилированы один раз и связаны с компилятором каждый раз, когда они используются.

Невозможно точно сказать, что такое макет объекта, однако стандарт не определяет двоичное представление для объектов.

Есть несколько вещей, которые нужно знать с двоичными представлениями, например, они не обязательно являются суммой байтов элементов данных из-за таких вещей, как добавление структуры

Ответ 3

Если вам нужна подробная информация о том, как объекты представлены в памяти во время выполнения, спецификацией ABI (Application Binary Interface) является место смотреть. Вам нужно будет определить, какой ABI ваш компилятор реализует; например, версии GCC версии 3.2 и выше реализуют Itanium С++ ABI.

Ответ 4

Методы принадлежат классу, а не конкретному экземпляру объекта.

Если нет виртуальных методов, размер объекта представляет собой сумму размера его нестатических элементов, плюс необязательное дополнение между членами для выравнивания. Члены, вероятно, будут последовательно размещены в памяти, но спецификация не гарантирует упорядочение между разделами с разными спецификациями доступа и не упорядочивает относительно макета суперклассов.

При наличии виртуальных методов может быть добавлено дополнительное пространство для vtable и другая информация RTTI.

На большинстве платформ исполняемый код переходит в раздел только для чтения .text (или аналогично названный) исполняемого файла или библиотеки и никогда не копируется нигде. Если class Temp имеет метод public: int function1(int), метаданные Temp могут иметь указатель на функцию _ZN4Temp9function1Ei (искаженное имя может отличаться в зависимости от компилятора) для фактической реализации, но, конечно, он никогда не будет содержать исполняемый код встроенные.

Ответ 5

Я всегда задумывался над этим, поэтому решил придумать полный ответ. Это о том, чего вы можете ожидать, и это предсказуемо (yay)! Таким образом, с приведенной ниже информацией, вы должны быть в состоянии предсказать размер класса.

Использование Visual Studio Community 2017 (версия 15.2) в режиме деблокирования со всеми отключенными оптимизациями и RTTI (Информация о времени выполнения) Я определил следующее:


Короткий ответ:

Прежде всего:

  • В бит 32 (x86), <size of pointer> == 4 байты
  • В 64 (x64) бит, <size of pointer> == 8 bytes
  • Когда я говорю "наследование виртуального класса", я имею в виду, например: class ChildClass: virtual public ParentClass

Теперь мои выводы заключаются в следующем:

  • пустые классы - 1 байт
  • Наследование пустого класса еще 1 байт
  • пустые классы с функциями по-прежнему 1 байт (см. Примечание ниже для объяснения)
  • Наследование пустого класса с помощью функции остается 1 байт
  • добавление переменной в пустой класс <size of variable> bytes
  • Наследование класса переменной и добавление другой переменной <size of variables> bytes
  • Наследование класса и переопределение его функции добавляет vtable (дополнительное объяснение в разделе Выводы) и <size of pointer> bytes
  • просто объявление функции virtual также добавляет vtable, делая ее <size of pointer> bytes
  • Наследование виртуального класса пустого класса (с или без функции-члена) также добавляет vtable и делает класс <size of pointer> bytes
  • Наследование виртуального класса непустого класса также добавляет vtable, но оно несколько усложняется: он добавляет <size of pointer> байты в общую, упаковку всех переменные-члены в количестве шагов <size of pointer> байтов, которые необходимы для покрытия <total size of member variables> - да, вы читаете это правильно... (см. мое предположение о том, что происходит в Выводы... )

Примечание, что я даже попытался использовать функцию() cout для некоторого текста, создания экземпляра класса и вызова функции; он не меняет размер класса функций (это не оптимизация)! Я был несколько удивлен, но на самом деле имеет смысл: функции-члены не меняются, поэтому они могут храниться вне самого класса.

Выводы:

  • Пустые классы - 1 байт, так как это минимум, необходимый для наличия в памяти. После добавления данных или данных vtable начните подсчет с 0 байтов.
  • Добавление (не виртуальной) функции-члена ничего не делает для размера, поскольку функция-член хранится извне.
  • Объявление функции-члена виртуальным (даже если класс не переопределяется!) или переопределение функции-члена в дочернем классе добавляет то, что называется "vtable" или "таблица виртуальных функций" , что позволяет Dynamic Dispatch (что действительно супер круто использовать, хотя я настоятельно рекомендую используй это). Этот vtable потребляет <size of pointer> байт, добавляя <size of pointer> bytes к указанному классу. Разумеется, этот vtable может существовать только один раз за класс (или он делает это или нет).
  • Добавление переменной-члена увеличивает размер класса по этой переменной-члену независимо от того, находится ли указанная переменная-член в родительском или дочернем классе (конечно, родительский класс остается своим собственным размером).
  • Наследование виртуального класса является единственной частью, которая усложняется... Итак... Я думаю, что происходит после небольшого эксперимента: размер класса фактически увеличивается в <size of pointer> байтах за раз, даже если он не нужно потреблять столько памяти, я предполагаю, потому что он добавляет vtable "вспомогательный блок" для каждого байта памяти <size of pointer> или что-то в этом роде...

Длинный ответ:

Я определил все это с помощью этого кода:

#include <iostream>

using namespace std;

class TestA
{

};

class TestB: public TestA
{

};

class TestC: virtual public TestA
{

};

class TestD
{
    public:
        int i;
};

class TestE: public TestD
{
    public:
        int j;
};

class TestF: virtual public TestD
{
    public:
        int j;
};

class TestG
{
    public:
        void function()
        {

        }
};

class TestH: public TestG
{
    public:
        void function()
        {

        }
};

class TestI: virtual public TestG
{
    public:
        void function()
        {

        }
};

class TestJ
{
    public:
        virtual void function()
        {

        }
};

class TestK: public TestJ
{
    public:
        void function() override
        {

        }
};

class TestL: virtual public TestJ
{
    public:
        void function() override
        {

        }
};

void main()
{
    cout << "int:\t\t" << sizeof(int) << "\n";
    cout << "TestA:\t\t" << sizeof(TestA) << "\t(empty class)\n";
    cout << "TestB:\t\t" << sizeof(TestB) << "\t(inheriting empty class)\n";
    cout << "TestC:\t\t" << sizeof(TestC) << "\t(virtual inheriting empty class)\n";
    cout << "TestD:\t\t" << sizeof(TestD) << "\t(int class)\n";
    cout << "TestE:\t\t" << sizeof(TestE) << "\t(inheriting int + int class)\n";
    cout << "TestF:\t\t" << sizeof(TestF) << "\t(virtual inheriting int + int class)\n";
    cout << "TestG:\t\t" << sizeof(TestG) << "\t(function class)\n";
    cout << "TestH:\t\t" << sizeof(TestH) << "\t(inheriting function class)\n";
    cout << "TestI:\t\t" << sizeof(TestI) << "\t(virtual inheriting function class)\n";
    cout << "TestJ:\t\t" << sizeof(TestJ) << "\t(virtual function class)\n";
    cout << "TestK:\t\t" << sizeof(TestK) << "\t(inheriting overriding function class)\n";
    cout << "TestL:\t\t" << sizeof(TestL) << "\t(virtual inheriting overriding function class)\n";

    cout << "\n";
    system("pause");
}

Вывод:

32 (x86) бит:

int:            4
TestA:          1       (empty class)
TestB:          1       (inheriting empty class)
TestC:          4       (virtual inheriting empty class)
TestD:          4       (int class)
TestE:          8       (inheriting int + int class)
TestF:          12      (virtual inheriting int + int class)
TestG:          1       (function class)
TestH:          1       (inheriting function class)
TestI:          4       (virtual inheriting function class)
TestJ:          4       (virtual function class)
TestK:          4       (inheriting overriding function class)
TestL:          8       (virtual inheriting overriding function class)

64 (x64) бит:

int:            4
TestA:          1       (empty class)
TestB:          1       (inheriting empty class)
TestC:          8       (virtual inheriting empty class)
TestD:          4       (int class)
TestE:          8       (inheriting int + int class)
TestF:          24      (virtual inheriting int + int class)
TestG:          1       (function class)
TestH:          1       (inheriting function class)
TestI:          8       (virtual inheriting function class)
TestJ:          8       (virtual function class)
TestK:          8       (inheriting overriding function class)
TestL:          16      (virtual inheriting overriding function class)

Если вам нужна информация о множественном наследовании, пойдите, сравните его с самим собой! -.-

Ответ 6

Функции-члены не учитывают размер объектов определенного класса. Размер объекта зависит только от переменных-членов. В случае классов, содержащих виртуальные функции, VPTR добавляется к макету объекта. Таким образом, размер объектов - это в основном размер переменных-членов + размер VPTR. Иногда это может быть неверным, поскольку компиляторы пытаются найти переменные-члены на границе DWORD.

Ответ 8

Если вы хотите изучить макет конкретной структуры, макрос offsetof(s,member) также может быть полезен. Он сообщает вам, как далеко от базового адреса структуры, в которой живет определенный член:

struct foo {
  char *a;
  int b;
};

// Print placement of foo members
void printFoo() {
  printf("foo->a is %zu bytes into a foo\n", offsetof(struct foo, a));
  printf("foo->b is %zu bytes into a foo\n", offsetof(struct foo, b));
}

int main() {
  printFoo();
  return 0;
}

Будет напечатан на обычной 32-разрядной машине:

foo->a is 0 bytes into a foo
foo->b is 4 bytes into a foo

В то время как на типичной 64-битной машине он печатает

foo->a is 0 bytes into a foo
foo->b is 8 bytes into a foo

Ответ 9

Это может помочь.

Кроме того, функции класса представлены так же, как и любая другая функция. Единственное волшебство, которое С++ делает для этой функции, - это исказить имена функций, чтобы однозначно идентифицировать определенную функцию с определенным набором параметров внутри определенного класса.

Ответ 10

Есть вызов утилиты pahole (для 'Poke-A-HOLE'), который номинально предназначен для изучения того, как макеты объектов дополняются, но отлично подходит для визуализации размера и компоновки объекта в целом.

Ответ 11

Размер объекта класса равен сумме размеров всех элементов данных этого класса. Например, если у меня есть класс

class student
{
private:   
   char name[20];
   int rollno, admno;
   float marks;

public:
  float tmarks, percentage;

  void getdata();

  void putdata();
};

Теперь, если я создаю объект этого класса, скажем s1, тогда размер этого объекта будет 36 байтов:

[20(name)+2(rollno)+2(admno)+4(marks)+4(tmarks)+4(percentage)]