Почему структуры копируются через memcpy во встроенный системный код? - PullRequest
11 голосов
/ 27 июля 2011

Во встроенном программном обеспечении для копирования структуры одного типа люди не используют прямое назначение и делают это с помощью функции 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;

почему ????

Ответы [ 9 ]

15 голосов
/ 27 июля 2011

Так или иначе, в встроенных системах нет ничего особенного, что делает это опасным, семантика языка одинакова для всех платформ.

C использовался во встроенных системах для многихгоды и ранние компиляторы Си до стандартизации ANSI / ISO не поддерживали непосредственное назначение структуры.Многие практикующие либо из той эпохи, либо обучались теми, кто был, или используют устаревший код, написанный такими практикующими.Вероятно, это корень сомнения, но это не проблема реализации, соответствующей ISO.Для некоторых целей с очень ограниченными ресурсами доступный компилятор может быть не полностью совместимым с ISO по ряду причин, но я сомневаюсь, что эта функция будет затронута.

Одна проблема (которая относится как к встроенным, так и к встроенным), заключается в том, что при назначении структуры реализации не нужно дублировать значение любых неопределенных битов заполнения, поэтому, если вы выполнили присвоение структуры, а затем выполнили memcmp(), а не сравнение по элементам для проверки на равенство,нет гарантии, что они будут равны.Однако, если вы выполните memcpy(), все биты заполнения будут скопированы, так что memcmp() и сравнение по элементам приведут к равенству.

Так что, возможно, безопаснее использовать memcpy() во всех случаях (не только встраиваемых), но улучшение является незначительным и не способствует удобочитаемости.Это была бы странная реализация, в которой не использовался самый простой метод присвоения структуры, и это простой memcpy(), поэтому маловероятно, что теоретическое несоответствие произойдет.

4 голосов
/ 05 июля 2014

Копирование C-структур с помощью memcpy() часто используется программистами, которые изучили C десятилетия назад и с тех пор не следовали процессу стандартизации.Они просто не знают, что C поддерживает назначение структур (прямое назначение структуры не было доступно во всех компиляторах до ANSI-C89).

Когда они узнают об этой функции, некоторые все еще придерживаются пути memcpy()потому что это их обычай.Есть также мотивы, которые возникают в программировании культа груза , например, утверждается, что memcpy просто быстрее - конечно - без возможности подкрепить это контрольным тестом.

Структуры также memcpy() используются некоторыми начинающими программистами, потому что они либо путают присвоение структуры с назначением указателя структуры - либо они просто чрезмерно используют memcpy() (они часто также используют memcpy(), где strcpy() будет большецелесообразно).

Существует также шаблон сравнения структур memcmp(), который иногда цитируется некоторыми программистами для использования memcpy() вместо назначения структуры.Причина этого заключается в следующем: поскольку C не генерирует автоматически оператор == для структур, а написание пользовательской функции сравнения структур утомительно, memcmp() используется для сравнения структур.На следующем шаге - чтобы избежать различий в заполнении битов сравниваемых структур - memset(...,0,...) используется для инициализации всех структур (вместо использования синтаксиса инициализатора C99 или инициализации всех полей отдельно), а memcpy() -используется для копирования структур!Потому что memcpy() также копирует содержимое битов заполнения ...

Но учтите, что эта аргументация ошибочна по нескольким причинам:

  • использование memcpy() / memcmp() / memset() вводит новые возможности ошибок - например, предоставляет неправильный размер
  • , когда структура содержит целочисленные поля, порядок в memcmp() изменяется между архитектурами с большим и младшим порядком байтов
  • charПоле массива размером n, определяемое 0 в позиции x, также должно иметь все элементы после позиции x, обнуляемой в любое время, иначе 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

Ошибка последнего утверждения может зависеть от переупорядочения кода, изменения компиляцииr, изменение параметров компилятора и тому подобное.

Заключение

Как правило: для повышения корректности и переносимости кода используйте прямое присвоение структуры (вместо memcpy()), инициализация структуры C99синтаксис (вместо memset) и пользовательская функция сравнения (вместо memcmp()).

4 голосов
/ 27 июля 2011

В заданном вами коде нет проблем, даже если вы напишите:

example2 = example1;

Но просто предположите, что в будущем определение struct изменится на:

struct tag
{
  int a[1000];
  int b;
};

Сейчасесли вы выполняете оператор присваивания, как указано выше, то (некоторые из) компилятор может встроить код для побайтового (или int, int) копирования.то есть

example1.a[0] = example.a[0];
example1.a[1] = example.a[1];
example1.a[2] = example.a[2];
...

, что приведет к раздуванию кода в вашем сегменте кода.Такого рода ошибки памяти не являются тривиальными для поиска.Вот почему люди используют memcpy.

[Тем не менее, я слышал, что современные компиляторы достаточно способны использовать memcpy внутри, когда такая инструкция встречается, особенно для POD.]

2 голосов
/ 05 июля 2014

Помимо того, что другие написали некоторые дополнительные пункты:

  • Использование memcpy вместо простого присваивания дает подсказку тому, кто поддерживает код, что операция может быть дорогой.Использование memcpy в этих случаях улучшит понимание кода.

  • Встраиваемые системы часто пишутся с учетом переносимости и производительности.Переносимость важна, потому что вы можете захотеть повторно использовать ваш код, даже если ЦП в оригинальном дизайне недоступен или если более дешевый микроконтроллер может выполнить ту же работу.

    В наши дни бюджетные микроконтроллеры приходят и уходят быстрее, чем могут догнать разработчики компиляторов, поэтому нередко работать с компиляторами, которые используют простой цикл копирования байтов вместо чего-то, оптимизированного для структурных назначений,С переходом на 32-битные ядра ARM это не так для большой части разработчиков встраиваемых систем.Однако есть много людей, которые создают продукты, предназначенные для непонятных 8- и 16-битных микроконтроллеров.

  • Memcpy, настроенный для конкретной платформы, может быть более оптимальным, чем компиляторможет генерировать.Например, на встроенных платформах наличие структур во флэш-памяти является обычным явлением.Чтение из флэш-памяти не такое медленное, как запись в него, но оно все же намного медленнее, чем обычное копирование из ОЗУ в ОЗУ.Оптимизированная функция memcpy может использовать DMA или специальные функции контроллера флэш-памяти для ускорения процесса копирования.

2 голосов
/ 27 июля 2011

В C люди, вероятно, делают это, потому что думают, что memcpy будет быстрее.Но я не думаю, что это правда.Об этом позаботится оптимизация компилятора.

В C ++ он также может иметь различную семантику из-за определенного пользователем оператора присваивания и конструкторов копирования.

1 голос
/ 08 июля 2014

В некоторых реализациях способ выполнения memcpy () может отличаться от способа выполнения «нормального» назначения структуры, что может быть важно в некоторых узких контекстах. Например, один или другой операнд структуры может быть не выровненным, и компилятор может не знать об этом (например, одна область памяти может иметь внешнюю связь и быть определена в модуле, написанном на другом языке, который не имеет средств для принудительного выравнивания). Использовать объявление __packed было бы лучше, если бы компилятор поддерживал такое, но не все компиляторы поддерживают.

Другая причина использования чего-то другого, кроме присвоения структуры, может заключаться в том, что memcpy конкретной реализации может обращаться к своим операндам в последовательности, которая будет корректно работать с определенными типами volatile источника или назначения, в то время как структурное назначение этой реализации может использовать другая последовательность, которая не будет работать. Однако, как правило, это не является хорошей причиной для использования memcpy, поскольку, за исключением проблемы выравнивания (которую memcpy требуется для правильной обработки в любом случае), спецификации для memcpy не обещают много о том, как операция будет выполнено. Было бы лучше использовать специально написанную подпрограмму, которая выполняла операции именно так, как требуется (например, если целью является аппаратное обеспечение, которое должно иметь 4 байта данных структуры, записанных с использованием четырех 8-разрядных операций записи, а не 32). -бит пишет, нужно написать подпрограмму, которая делает это, вместо того, чтобы надеяться, что ни одна будущая версия memcpy не решит «оптимизировать» операцию).

Третьей причиной использования memcpy в некоторых случаях может быть тот факт, что компиляторы часто выполняют небольшие структурные назначения, используя прямую последовательность загрузок и сохранений, а не библиотечную подпрограмму. На некоторых контроллерах количество кода, которое для этого требуется, может варьироваться в зависимости от того, где расположены структуры в памяти, до такой степени, что последовательность загрузки / сохранения может в итоге оказаться больше, чем вызов memcpy. Например, на контроллере PICmicro с 1Kwords кодового пространства и 192 байтами оперативной памяти для копирования 4-байтовой структуры из банка 1 в банк 0 потребуется 16 инструкций. Для вызова memcpy потребуется восемь или девять (в зависимости от того, является ли count unsigned char или int [всего с 192 байтами ОЗУ, unsigned char должно быть более чем достаточно!] Обратите внимание, однако, что вызов Подпрограмма memcpy-ish, которая приняла жестко запрограммированный размер и требовала, чтобы оба операнда находились в оперативной памяти, а не в кодовом пространстве, потребовала бы только пяти инструкций для вызова, и это можно было бы уменьшить до четырех с использованием глобальной переменной.

1 голос
/ 27 июля 2011

Что бы вы ни делали, не делайте этого:

exmple2.a=exmple1.a; 
exmple2.b=exmple1.b;

Это создает проблему сопровождения, потому что каждый раз, когда кто-либо добавляет элемент в структуру, он должен добавить строку кода, чтобы сделать копию этого члена. Кто-то забудет это сделать, и это приведет к трудному поиску ошибки.

1 голос
/ 27 июля 2011

Это полная чушь. Используйте тот способ, который вы предпочитаете. Самое простое это:

exmple2=exmple1;
0 голосов
/ 27 июля 2011

первая версия идеальна.
вторая может использоваться для скорости (нет причин для вашего размера).
3-я версия используется только в том случае, если отступы различаются для цели и источника.

...