Почему структуры скопированы с помощью memcpy во встроенном системном коде?
В области встроенного программного обеспечения для копирования структуры того же типа люди не используют прямое назначение и делают это с помощью функции memcpy()
или каждого элемента копирования.
позволяет иметь, например,
struct tag
{
int a;
int b;
};
struct tag exmple1 = {10,20};
struct tag exmple2;
для копирования exmple1 в exmple2..
вместо того, чтобы писать прямые
exmple2=exmple1;
люди используют
memcpy(exmple2,exmple1,sizeof(struct tag));
или
exmple2.a=exmple1.a;
exmple2.b=exmple1.b;
почему????
Ответы
Ответ 1
В том или ином случае нет ничего особенного в встроенных системах, которые делают это опасным, семантика языка одинакова для всех платформ.
C использовался во встроенных системах в течение многих лет и для ранних компиляторов C, прежде чем стандартизация ANSI/ISO не поддерживала прямое назначение структуры. Многие практикующие либо с той эпохи, либо учат тех, которые были или используют устаревший код, написанный такими практикующими. Вероятно, это корень сомнения, но это не проблема в реализации, совместимой с ISO. По некоторым целевым ресурсам с ограниченным ресурсом доступный компилятор может быть не полностью совместим с ISO по ряду причин, но я сомневаюсь, что эта функция будет затронута.
Одна проблема (применительно к встроенным и не встроенным) заключается в том, что при назначении структуры реализация не должна дублировать значение каких-либо битов дополнения undefined, поэтому, если вы выполнили назначение структуры, а затем выполнили a memcmp()
, а не по отдельности, для проверки равенства, нет гарантии, что они будут равны. Однако, если вы выполняете memcpy()
, любые биты дополнений будут скопированы так, чтобы сравнение memcmp()
и член-член обеспечивало равенство.
Таким образом, безопаснее использовать memcpy()
во всех случаях (а не только встраивать), но улучшение является незначительным и не способствует удобочитаемости. Было бы странной реализацией, которая не использовала бы самый простой метод назначения структуры, и это простой memcpy()
, поэтому маловероятно, что возникнет теоретическое несоответствие.
Ответ 2
В вашем коде нет проблем, даже если вы пишете:
example2 = example1;
Но предположим, что в будущем определение struct
изменится на:
struct tag
{
int a[1000];
int b;
};
Теперь, если вы выполняете оператор присваивания, как указано выше, то (некоторый из) компилятор может встроить код для байта байтом (или int by int). то есть.
example1.a[0] = example.a[0];
example1.a[1] = example.a[1];
example1.a[2] = example.a[2];
...
что приведет к раздуванию кода в вашем сегменте кода. Подобных ошибок памяти нетрудно найти. Вот почему люди используют memcpy
.
[Тем не менее, я слышал, что современные компиляторы достаточно способны использовать memcpy
внутри, когда такая команда встречается специально для POD.]
Ответ 3
Копирование C-структур через memcpy()
часто используется программистами, которые учились C десятилетиями назад и не следовали процессу стандартизации с тех пор. Они просто не знают, что C поддерживает назначение структур (прямое назначение структуры недоступно во всех компиляторах pre-ANSI-C89).
Когда они узнают об этой функции, некоторые все еще придерживаются пути memcpy()
, потому что это их обычай. Существуют также мотивы, которые возникают в культовом программировании груза, например. утверждается, что memcpy
работает быстрее - конечно, не имея возможности поддержать это с помощью тестового теста.
Структуры также являются memcpy()
некоторыми программистами-новичками, потому что они либо путают назначение структуры с назначением указателя структуры, либо просто используют memcpy()
(они часто также используют memcpy()
, где strcpy()
будет быть более уместным).
Существует также анти-шаблон сравнения структуры memcmp()
, который иногда цитируется некоторыми программистами для использования memcpy()
вместо назначения структуры. Обоснование этого заключается в следующем: поскольку C автоматически не генерирует оператор ==
для структур, и запись функции сравнения пользовательских структур является утомительной, memcmp()
используется для сравнения структур. На следующем шаге - во избежание различий в padding бит сравниваемых структур - memset(...,0,...)
используется для инициализации всех структур (вместо с использованием синтаксиса инициализатора C99 или инициализации всех полей отдельно) и memcpy()
используется для копирования структур! Поскольку memcpy()
также копирует содержимое битов дополнения...
Но учтите, что это рассуждение ошибочно по нескольким причинам:
- использование
memcpy()
/memcmp()
/memset()
представляет новые возможности ошибок - например, поставка неправильного размера
- когда структура содержит целочисленные поля, упорядочивание под
memcmp()
изменяется между большими и мало-endian архитектурами
- a char Поле массива размером
n
, которое 0
-терминировано в позиции x
, должно также иметь все элементы после того, как позиция x
обнулена в любое время - else 2 в противном случае равные структуры сравниваются неравномерно
- из регистров в поле может также устанавливать соседние биты дополнений к значениям неравномерным 0, таким образом, после сравнений с структурами, не имеющими равных равными, дает неравный результат
Последняя точка лучше всего проиллюстрирована небольшим примером (предполагая архитектуру X):
struct S {
int a; // on X: sizeof(int) == 4
char b; // on X: 24 padding bits are inserted after b
int c;
};
typedef struct S S;
S s1;
memset(&s1, 0, sizeof(S));
s1.a = 0;
s1.b = 'a';
s1.c = 0;
S s2;
memcpy(&s2, &s1, sizeof(S));
assert(memcmp(&s1, &s2, sizeof(S)==0); // assertion is always true
s2.b = 'x';
assert(memcmp(&s1, &s2, sizeof(S)!=0); // assertion is always true
// some computation
char x = 'x'; // on X: 'x' is stored in a 32 bit register
// as least significant byte
// the other bytes contain previous data
s1.b = x; // the complete register is copied
// i.e. the higher 3 register bytes are the new
// padding bits in s1
assert(memcmp(&s1, &s2, sizeof(S)==0); // assertion is not always true
Сбой последнего утверждения может зависеть от переупорядочения кода, изменения компилятора, изменения параметров компилятора и т.д.
Заключение
Как правило: для повышения корректности и переносимости кода используйте прямое назначение структуры (вместо memcpy()
), синтаксис инициализации структуры C99 (вместо memset
) и пользовательскую функцию сравнения (вместо memcmp()
).
Ответ 4
В C люди, вероятно, это делают, потому что считают, что memcpy
будет быстрее. Но я не думаю, что это правда. Оптимизация компилятора позаботится об этом.
В С++ он может также иметь различную семантику из-за пользовательского оператора присваивания и конструкторов копирования.
Ответ 5
В дополнение к тому, что другие написали несколько дополнительных точек:
-
Использование memcpy вместо простого назначения дает подсказку тому, кто поддерживает код, который может быть дорогостоящим. Использование memcpy в этих случаях улучшит понимание кода.
-
Встроенные системы часто записываются с учетом переносимости и производительности. Переносимость важна, потому что вы можете повторно использовать свой код, даже если CPU в оригинальном дизайне недоступен или если более дешевый микроконтроллер может выполнять ту же работу.
В наши дни младшие микроконтроллеры приходят и уходят быстрее, чем разработчики компилятора могут наверстать упущенное, поэтому нередко работать с компиляторами, которые используют простой цикл байтовой копии вместо того, что оптимизировано для назначения структуры. С переходом на 32-битные ARM-ядра это не так для значительной части встроенных разработчиков. Тем не менее, есть много людей, которые строят продукты, которые нацелены на неясные 8 и 16-битные микроконтроллеры.
-
Memcpy, настроенная для конкретной платформы, может быть более оптимальной, чем то, что может генерировать компилятор. Например, на встроенных платформах, имеющих структуры во флэш-памяти, является общим. Чтение со вспышки происходит не так медленно, как запись на него, но она все еще намного медленнее обычной копии из ОЗУ в оперативную память. Оптимизированная функция memcpy может использовать DMA или специальные функции контроллера вспышки для ускорения процесса копирования.
Ответ 6
Это полная чушь. Используйте то, что вы предпочитаете. Самый простой способ:
exmple2=exmple1;
Ответ 7
Что бы вы ни делали, не делайте этого:
exmple2.a=exmple1.a;
exmple2.b=exmple1.b;
Он представляет проблему ремонтопригодности, поскольку в любое время, когда кто-либо добавляет член в структуру, они должны добавить строку кода, чтобы сделать копию этого элемента. Кто-то забудет это сделать, и это вызовет трудную ошибку.
Ответ 8
В некоторых реализациях способ, которым выполняется memcpy(), может отличаться от способа, которым будет выполняться "нормальное" назначение структуры, таким образом, что это может быть важно в некоторых узких контекстах. Например, один или другой структурный операнд может быть неровным, и компилятор может не знать об этом (например, одна область памяти может иметь внешнюю связь и быть определена в модуле, написанном на другом языке, который не имеет возможности обеспечить выравнивание). Использование объявления __packed
было бы лучше, если бы компилятор поддерживал такие, но не все компиляторы.
Другая причина использования чего-то другого, кроме назначения структуры, может заключаться в том, что конкретная реализация memcpy
может получить доступ к своим операндам в последовательности, которая будет корректно работать с определенными типами источника или назначения volatile
, тогда как это назначение структуры реализации может использовать другая последовательность, которая не сработает. Это, как правило, не является веской причиной для использования memcpy
, однако, поскольку помимо проблемы выравнивания (для которой memcpy
требуется правильно обрабатывать в любом случае), спецификации для memcpy
не обещают многого о том, как операция будут выполнены. Было бы лучше использовать специально написанную процедуру, которая выполняла бы операции точно по мере необходимости (например, если цель - это часть аппаратного обеспечения, которая должна иметь 4 байта данных структуры, записанных с использованием четырех 8-битных записей, а не 32 -битная запись, нужно написать процедуру, которая делает это, а не надеяться, что никакая будущая версия memcpy
не решит "оптимизировать" операцию).
Третья причина использования memcpy в некоторых случаях будет заключаться в том, что компиляторы часто выполняют небольшие структурные назначения, используя прямую последовательность нагрузок и хранилищ, а не используя библиотечную процедуру. На некоторых контроллерах количество требуемого кода может варьироваться в зависимости от того, где структуры расположены в памяти, до такой степени, что последовательность загрузки/хранения может оказаться больше, чем вызов memcpy. Например, на контроллере PICmicro с 1K словами с кодовым пространством и 192 байтами ОЗУ обработка 4-байтной структуры из банка 1 в банк 0 будет принимать 16 команд. Для вызова memcpy потребуется восемь или девять (в зависимости от того, является ли count
unsigned char
или int
[всего 192 байта общей памяти, unsigned char
должно быть более чем достаточно!] Обратите внимание, однако, что вызов Процедура memcpy-ish, которая предполагала размер жесткого кодирования и требовала, чтобы оба операнда были в ОЗУ, а не в кодовом пространстве, требовали бы только пять команд для вызова, и это могло быть уменьшено до четырех с использованием глобальной переменной.
Ответ 9
первая версия идеальна.
второй может быть использован для скорости (нет причин для вашего размера).
3-й используется только в том случае, если для цели и источника отличается отступ.