Безопасно ли memcpy для динамической структуры хранения?
контекст:
я просматривал некоторый код, который получает данные из дескриптора ввода-вывода в символьный буфер, выполняет некоторый контроль над ним, а затем использует часть полученного буфера для заполнения структуры, и внезапно задался вопросом, Может ли быть задействовано строгое нарушение правила псевдонима.
вот упрощенная версия
#define BFSZ 1024
struct Elt {
int id;
...
};
unsigned char buffer[BFSZ];
int sz = read(fd, buffer, sizeof(buffer)); // correctness control omitted for brievety
// search the beginning of struct data in the buffer, and process crc control
unsigned char *addr = locate_and_valid(buffer, sz);
struct Elt elt;
memcpy(&elt, addr, sizeof(elt)); // populates the struct
// and use it
int id = elt.id;
...
пока все хорошо. Предоставьте буфер, содержащий действительное представление структуры-скажем, он был создан на том же платформа, поэтому без endianness или проблемы заполнения -memcpy
вызов заполнил структуру, и ее можно безопасно использовать.
если структура динамически выделяется, она не имеет объявленного типа. Давайте заменим последние строки на:
struct Elt *elt = malloc(sizeof(struct Element)); // no declared type here
memcpy(elt, addr, sizeof(*elt)); // populates the newly allocated memory and copies the effective type
// and use it
int id = elt->id; // strict aliasing rule violation?
...
проект n1570 для языка C говорит в 6.5 выражениях §6
эффективным типом объекта для доступа к его сохраненному значению является объявленный тип объекта возражаю, если есть.87) если значение сохраняется в объект, не имеющий объявленного типа через lvalue, имеющий тип, который не является типом символа, тогда тип lvalue становится эффективный тип объекта для этого доступа и для последующих обращений, которые не изменяются сохраненное значение. если значение копируется в объект, не имеющий объявленного типа, используя memcpy или memmove, или копируется как массив символьного типа, а затем эффективный тип измененного объекта для этого доступа и для последующего доступы, которые не изменяют value-эффективный тип объекта, из которого копируется значение, если оно имеет значение.
buffer
имеет эффективный тип и даже объявленный тип: это массив unsigned char
. Именно по этой причине код использует memcpy
вместо простого псевдонима вроде:
struct Elt *elt = (struct Elt *) addr;
что действительно было бы строгим нарушением правила сглаживания (и может дополнительно возникнуть с проблемами выравнивания). Но если ... --6--> дал эффективный тип беззнаковый char массив в зону указал elt
, все потеряно.
вопрос:
тут memcpy
от массива символьного типа к объекту без объявленного типа дают эффективный тип массива символов?
отказ от ответственности:
Я знаю, что он работает без предупреждения со всеми распространенными компиляторами. Я просто хочу знать, правильно ли мое понимание стандарта
для того, чтобы лучше покажите мою проблему, давайте рассмотрим другую структуру Elt2 с sizeof(struct Elt2)
sizeof(struct Elt) и
struct Elt2 actual_elt2 = {...};
для статического или автоматического хранения я не могу повторно использовать память объекта:
struct Elt elt;
struct Elt2 *elt2 = &elt;
memcpy(elt2, &actual_elt2, sizeof(*elt2));
elt2->member = ... // strict aliasing violation!
пока это нормально для динамического (вопрос об этом здесь):
struct Elt *elt = malloc(sizeof(*elt));
// use elt
...
struct Elt2 *elt2 = elt;
memcpy(elt2, &actual_elt2, sizeof(*elt2));
// ok, memory now have struct Elt2 effective type, and using elt would violate strict aliasing rule
elt2->member = ...; // fine
elt->id = ...; // strict aliasing rule violation!
что может сделать копирование из массива разных?
2 ответов
код в порядке, нет строгого нарушения сглаживания. Указываемые данные имеют эффективный тип, поэтому жирный цитируемый текст не применяется. Здесь применима часть, которую вы пропустили, последнее предложение 6.5 / 6:
для всех других обращений к объекту, не имеющему объявленного типа, эффективным типом объекта является просто тип lvalue, используемый для доступа.
таким образом, эффективный тип заостренного объекта становится struct Elt
. Возвращаемый указатель malloc действительно указывает на объект без типа delcared, но как только вы указываете на него, эффективный тип становится указателем структуры. В противном случае программы C вообще не смогут использовать malloc.
что делает код безопасным, так это то, что вы копирование данные в эту структуру. Если бы вы вместо этого просто назначили struct Elt*
чтобы указать на то же место памяти, что и addr
, тогда у вас будет строгое нарушение сглаживания и UB.
ответ Лундина правильный; то, что вы делаете, прекрасно (до тех пор, пока данные выровнены и имеют одинаковую эндианность).
Я хочу отметить, что это не столько результат спецификации языка C, сколько результат того, как оборудование строительство. Таким образом, нет ни одного авторитетного ответа. Спецификация языка C определяет, как язык работает, а не как язык компилируется или реализованы на разных системный.
вот интересная статья о выравнивании памяти и строгом сглаживании на процессоре SPARC против Intel (обратите внимание, что один и тот же код C работает по-разному и дает ошибки на одной платформе при работе на другой): https://askldjd.com/2009/12/07/memory-alignment-problems/
по сути, два одинаковых структур, в той же системе с тем же прямым и выравнивание памяти, должны работа через memcpy. Если нет, то ... компьютер ничего не сможет сделать.
наконец, следующий вопрос объясняет больше о выравнивании памяти в системах, и ответ joshperry должен помочь объяснить, почему это оборудование, а не язык вопрос: цель выравнивания памяти