Безопасно ли 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 должен помочь объяснить, почему это оборудование, а не язык вопрос: цель выравнивания памяти