Выравнивание памяти класса C++

Недавно я видел, как один из моих коллег делал что-то подобное:

#include <iostream>

class myClass
{
public:
    float X, Y;
    myClass(int x, int y) : X(x), Y(y){}
};

int main()
{
    char buffer[1024] = { 0 };
    myClass example(12, 24);
    memcpy(buffer, &example.X, sizeof(float) * 2); // Is this safe? Will X always be allocated next o Y?
}

По сути, он пытается скопировать как X, так и Y в char[] за один шаг, сообщая копии памяти, чтобы она читала вдвое больше размера float.

Это определенно работает и нормально, я бы подумал, что это круто, и пошел бы дальше. Но из-за всего неопределенного поведения в C++. Я хотел бы знать, гарантированно ли это всегда работает. Будет ли Y всегда располагаться сразу после X?


person Caesar    schedule 07.11.2012    source источник
comment
Не могли бы вы использовать alignof для предоставления статического утверждения, что Y идет сразу после X без заполнения?   -  person Lily Ballard    schedule 08.11.2012
comment
Если вы хотите, чтобы это гарантированно сработало, почему бы просто не сделать это так, чтобы вы знали, что оно гарантированно сработает? Если вы хотите, чтобы он был неопределенным(?), но довольно крутым, оставьте его как есть.   -  person Eric B    schedule 08.11.2012
comment
Чтобы повторить вопрос из ответа: с какой стати вы вообще написали sizeof(float)*2 вместо sizeof(myClass)? А почему 1024 для буфера?   -  person Nemo    schedule 08.11.2012
comment
@Nemo Это базовый пример, который я только что написал. На самом деле класс имеет еще несколько членов.   -  person Caesar    schedule 08.11.2012
comment
@Немо спасибо; Вероятно, мне следовало сделать это более прямо, а не прятать его в скобках в середине абзаца, потому что это действительно важно. Либо у коллеги есть веская причина для этого, которую мы не видим, либо ОП предстоит несколько ужасных месяцев тщательной проверки кода, пока его коллегу не уволят…   -  person abarnert    schedule 08.11.2012
comment
Если реальный класс имеет больше членов, то еще более нелепо пытаться сложить размеры членов вручную вместо того, чтобы просто вызывать sizeof для класса. Даже если бы он был легальным и переносимым, он был бы многословным, утомительным, и в нем было бы очень легко ошибиться и вызвать приятные ошибки, связанные с переполнением буфера.   -  person abarnert    schedule 08.11.2012


Ответы (1)


Это определенно работает и нормально, я бы подумал, что это круто, и пошел бы дальше. Но из-за всего неопределенного поведения в C++. Я хотел бы знать, гарантированно ли это всегда работает. Будет ли Y всегда располагаться сразу после X?

Точно нет. Любая структура может иметь отступы между элементами. У большинства компиляторов будет достаточно документации, чтобы сказать вам, безопасно ли это на вашей конкретной платформе с этим компилятором, но он никогда не будет безопасным при переносе. Вы должны использовать sizeof(myClass) вместо sizeof(float)*2. (И мне любопытно, почему вы вообще не хотите…)

Если вы работаете на C++03, здесь есть более серьезная проблема: myClass имеет определяемый пользователем конструктор, так что это не POD, поэтому вы вообще не можете переносимо memcpy его. (Сначала я думал, что это проблема и в C++11, но, поскольку все члены имеют одинаковый контроль доступа, это это класс стандартной компоновки, что означает, что вы можете memcpy его использовать в C ++11.)

Наконец, это может показаться немного глупым, но нет никакой гарантии, что 1024 байта достаточно места для структуры из двух float. Вы действительно должны сделать что-то вроде char buffer[sizeof(myClass)].

person abarnert    schedule 07.11.2012
comment
Это может быть не POD, но это стандартный класс макета. - person CB Bailey; 08.11.2012
comment
В C++11 вы можете memcpy использовать его, потому что это тип стандартного макета. (По сути, POD с несколькими конструкторами, которые действительно должны были обрабатываться как последовательность байтов; теперь это исправлено.) - person GManNickG; 08.11.2012
comment
И на самом деле, теперь, когда я перечитал вещи, если все элементы данных являются закрытыми, а все методы общедоступными, это так же хорошо для стандартного макета, как и все, что является общедоступным, верно? - person abarnert; 08.11.2012
comment
@abarnert Вы говорите о структуре, но то же самое для класса? - person Caesar; 08.11.2012
comment
Да, struct и class абсолютно одинаковы во всех отношениях, за исключением контроля доступа по умолчанию. Я использую здесь struct, чтобы подчеркнуть, что даже если бы это был просто традиционный struct без методов, и если на то пошло, даже если бы вы использовали C, а не C++, это все равно было бы небезопасно. - person abarnert; 08.11.2012
comment
@abarnert: Когда у типа класса есть частные члены, он больше не имеет стандартного макета. - person GManNickG; 08.11.2012
comment
Спасибо за ответ, очень признателен. Немедленно уведомлю моего коллегу. - person Caesar; 08.11.2012
comment
@GManNickG: Нет. Вот тоже вспомнил, но n3242 9.0.7 говорит, что мы не правы. Правило состоит в том, чтобы иметь одинаковый контроль доступа (пункт 11) для всех нестатических элементов данных. Итак, если все элементы данных закрыты, это нормально; это происходит только тогда, когда класс имеет и частные и общедоступные (или частные и защищенные и т. д.) элементы данных. (И обратите внимание, что это относится только к элементам данных — члены открытых функций с закрытыми элементами данных не нарушают стандартный макет.) - person abarnert; 08.11.2012