Влияет ли размер объекта на тип спецификатора доступа и тип наследования?

При ответе на один из вопросов возникла ветка обсуждения под моим ответом. Это говорит о том, что в зависимости от спецификатора доступа (или может быть типа наследования) private/protected/public объект sizeof и class может различаться!

Я до сих пор не понимаю из их краткого обсуждения, как это возможно?


person iammilind    schedule 05.07.2011    source источник


Ответы (1)


Обратите внимание на новый язык для C++11 ниже

В C++03 есть язык, который делает это возможным, 9.2 [class.mem]/12 (выделено мной):

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

Итак, учитывая это определение:

class Foo
{
    char a; //8 bits
    // a must come before b, so 3 bytes of padding have to go here to satisfy alignment
    int b; //32 bits
    char c; //8 bits
    // 24 bits of padding required to make Foo a multiple of sizeof(int)
};

в системе с 32-битным (int) выравниванием компилятору не разрешено переупорядочивать c так, чтобы оно располагалось перед b, заставляя вставлять дополнительные отступы между a и b и после c до конца объекта (создавая sizeof(Foo) == 12) . Однако для этого:

class Foo
{
    char a;
public:
    int b;
public:
    char c;
};

a и (b и c) разделены спецификатором доступа, поэтому компилятор может выполнять такое переупорядочение, что делает

memory-layout Foo
{
    char a; // 8 bits
    char c; // 8 bits
    // 16 bits of padding
    int b; // 32 bits
};

sizeof(Foo) == 8.

В C++11 язык немного изменился. N3485 9.2 [class.mem]/13 говорит (выделено мной):

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

Это означает, что в C++11 в приведенном выше примере (разделенном тремя общедоступными) компилятору по-прежнему не разрешено выполнять переупорядочение. Это должно быть что-то вроде

class Foo
{
    char a;
public:
    int b;
protected:
    char c;
};

, который размещает a, b и c с разным контролем доступа.

Обратите внимание, что в соответствии с правилами С++ 11 при таком определении, как:

class Foo
{
    char a;
public:
    int b;
protected:
    char c;
public:
    int d;
};

компилятор должен поставить d после b, даже если они разделены спецификаторами доступа.


(Тем не менее, я не знаю ни одной реализации, которая действительно использует широту, предлагаемую любым стандартом)

person Billy ONeal    schedule 05.07.2011
comment
Вышесказанное верно, и оно хорошо отвечает на вопрос, но как вопрос действительно влияет на пользователя? Один ДОЛЖЕН всегда использовать оператор sizeof для получения размера любого типа, и никакая реализация не должна делать предположения о размере класса/структуры, потому что компиляторы в любом случае могут дополнять их. - person Alok Save; 05.07.2011
comment
@Als: Это может иметь значение, если вы делаете что-то вроде memcpying структуры в буфер с разными компиляторами на обоих концах. Этот контент в стандарте означает, что он должен работать до тех пор, пока требования выравнивания/порядка байтов остаются одинаковыми, он просто будет работать таким образом. (Что касается полезности для пользователя, да, это бесполезно, опять же, потому что никто не пользуется этой свободой. Но я ответил на заданный вопрос... не пытаясь судить об ОП) - person Billy ONeal; 05.07.2011
comment
На самом деле sizeof(Foo) тоже будет 8, потому что он всегда вычисляется для массивов, так что вы можете использовать malloc для выделения пространства массива. Добавление второго char c после b поможет вам проиллюстрировать вашу точку зрения. - person Matthieu M.; 05.07.2011
comment
Я думаю, что предложенный вами вариант размещения запрещен, потому что b должно стоять перед c. - person Bruno Martinez; 10.07.2013
comment
@Bruno: я также обновил ответ, чтобы отразить изменения в формулировках в C++11. - person Billy ONeal; 10.07.2013