Как вы определяете размер объекта в C ++? - PullRequest
38 голосов
/ 02 июня 2009

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

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

    private:
        int foobar;
};

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

Ответы [ 11 ]

61 голосов
/ 02 июня 2009

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

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

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

Объекты производных классов также включают в себя все элементы данных своих базовых классов.

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

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

18 голосов
/ 02 июня 2009
sizeof(Temp)

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

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

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

8 голосов
/ 08 июля 2017

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

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


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

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

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

Итак, мои выводы таковы:

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

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

Выводы:

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

Если вы хотите получить информацию о множественном наследовании, иди разберись с ней! -.-

8 голосов
/ 02 июня 2009

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

6 голосов
/ 02 июня 2009

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

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

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

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

4 голосов
/ 02 июня 2009

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

3 голосов
/ 02 ноября 2011

Если вы используете Microsoft Visual C ++, есть одна опция компилятора, которая сообщает вам, насколько велик ваш объект на самом деле: / d1reportSingleClassLayout

Это недокументированное, кроме этого видео от Lavavej http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-3-of-n

2 голосов
/ 02 июня 2009

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

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

// Print placement of foo's 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
0 голосов
/ 06 июня 2015

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

0 голосов
/ 30 мая 2013

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

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)]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...