Можно ли использовать memcpy для штамповки типов?

Это цитата из стандарта C11:

6.5 Выражения
...

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

7 Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов:

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

Означает ли это, чтоmemcpy не может использоваться для типа наказания таким образом:

double d = 1234.5678;
uint64_t bits;
memcpy(&bits, &d, sizeof bits);
printf("the representation of %g is %08"PRIX64"\n", d, bits);

Почему бы не дать такой же вывод, как:

union { double d; uint64_t i; } u;
u.d = 1234.5678;
printf("the representation of %g is %08"PRIX64"\n", d, u.i);

Что делать, если я использую свою версиюmemcpy используя типы символов:

void *my_memcpy(void *dst, const void *src, size_t n) {
    unsigned char *d = dst;
    const unsigned char *s = src;
    for (size_t i = 0; i < n; i++) { d[i] = s[i]; }
    return dst;
}

РЕДАКТИРОВАТЬ: EOF отметил, чтоЧасть оmemcpy() в пункте 6 не применяется в этой ситуации, так какuint64_t bits имеет объявленный тип. Я согласен, но, к сожалению, это не помогает ответить на вопросmemcpy может использоваться для типа наказания, просто делает пункт 6 неуместным для оценки достоверности приведенных выше примеров.

Вот еще одна попытка напечататьmemcpy Я полагаю, что это будет охватываться пунктом 6:

double d = 1234.5678;
void *p = malloc(sizeof(double));
if (p != NULL) {
    uint64_t *pbits = memcpy(p, &d, sizeof(double));
    uint64_t bits = *pbits;
    printf("the representation of %g is %08"PRIX64"\n", d, bits);
}

Если предположить,sizeof(double) == sizeof(uint64_t)Имеет ли вышеуказанный код определенное поведение в соответствии с пунктами 6 и 7?

РЕДАКТИРОВАТЬ: Некоторые ответы указывают на возможность неопределенного поведения, возникающего при чтении представления ловушки. Это не имеет значения, поскольку Стандарт C явно исключает эту возможность:

7.20.1.1 Целочисленные типы с точной шириной

1 имя типа определенияintN_t обозначает целочисленный тип со знаком с ширинойN, без дополнительных битов и представления дополнения до двух. Таким образом,int8_t обозначает такой целочисленный тип со знаком шириной ровно 8 бит.

2 Имя типа определенияuintN_t обозначает целочисленный тип без знака с ширинойN и нет битов заполнения. Таким образом,uint24_t обозначает такой целочисленный тип без знака с шириной ровно 24 бита.

Эти типы не являются обязательными. Однако, если реализация предоставляет целочисленные типы с шириной 8, 16, 32 или 64 бита, без битов заполнения и (для подписанных типов), которые имеют представление дополнения до двух, она должна определять соответствующие имена typedef.

Типuint64_t имеет ровно 64 значения битов и не имеет битов заполнения, поэтому не может быть никаких представлений прерываний.

 Richard Chambers27 июл. 2016 г., 04:21
Я собрал ответ. вероятно, слишком многословно, так как это проблема, которая у меня есть. <Смех>.
 chqrlie27 июл. 2016 г., 11:09
@EOF: хорошая мысль. Я редактировал вопрос.
 chqrlie27 июл. 2016 г., 03:50
@RichardChambers: ты можешь написать ответ?
 chux27 июл. 2016 г., 16:43
Предложитьuint64_t bits = *pi; ->uint64_t bits = *pbits;
 Chris Beck27 июл. 2016 г., 12:42
Соответствующий:blog.regehr.org/archives/959  Джон Регер говорит, что в некоторых случаяхmemcpy это его предпочтительный способ сделать это и позволяет большинству компиляторов генерировать оптимальный объектный код.
 Richard Chambers27 июл. 2016 г., 03:09
Я могу понять, что озадачен формулировкой параграфа 6. У этого есть тот адвокатский ритм и конструкция, которая действительно не доступна простым смертным. Когда я читаю это, у меня складывается впечатление, что это то же самое и должно быть в безопасности. Какunion, с помощьюmemcpy() копирует значение из ячейки памяти, значение которой относится к определенному типу, и помещает его в другое место, а значение остается того же типа. Однако при доступе через lvalue другого типа значение извлекается как тип lvalue, а не как исходный тип.
 chqrlie27 июл. 2016 г., 02:51
@RichardChambers: я знаю, что никакого фактического преобразования типов не происходит, просто переосмысление побитового представленияdouble какuint64_t, Я озадачен формулировкой параграфа 6, особенно относительно копии, сделанной черезmemcpy, Я жестко, это должно быть безопасно, но другие опытные эксперты C отличаются.
 chqrlie27 июл. 2016 г., 03:21
@RichardChambers:Однако при доступе через lvalue другого типа значение извлекается как тип lvalue, а не как исходный тип. что является точным определением типа наказания и целью приведенного выше кода. Таков твой ответ:да,memcpy может быть использован для типа штамповки.
 supercat27 июл. 2016 г., 16:50
... использовать такие вещи, какrestrict, Я действительно сомневаюсь, что большинство людей, голосующих за языки в стандарте, ожидают, что качественные компиляторы не признают что-то вроде((uint16_t*)someUint32Ptr)[IS_BIG_ENDIAN]; может изменить значение типаuint32_t [и, возможно, это самый эффективный способ очистить нижнюю его половину], или он подумал бы, что оптимизации способствует то, что программистам приходится писать код, который заставит компилятор предполагать, что указатель может иметь псевдоним практически для чего угодно, где угодно, любого типа.
 Gene27 июл. 2016 г., 02:43
Я не эксперт по стандарту C, но между ними есть важное различие.union версия обеспечивает соответствие выравнивания обоих полей начальным адресомu,memcpy версия не гарантирует, чтоbits расположен наdouble граница. Это может не сработать, еслиdouble выравнивание было более ограничительным, чемuint64_tнапример, с ошибкой шины, когдаprintf пытается вычислитьdouble строковое представлениеbits.
 chqrlie27 июл. 2016 г., 02:54
@Gene: я не вижу проблем с выравниванием:memcpy явно безопасно копировать между невыровненными блоками и значениями, переданными вprintf читаются из их эффективных типов.
 EOF27 июл. 2016 г., 10:07
Часть оmemcpy() в пункте 6 не применяется в этой ситуации, так какuint64_t bits имеет объявленный тип.
 Richard Chambers27 июл. 2016 г., 05:02
@chux, вmemcpy() указанное количество байтовsizeof пункт назначения, поэтому максимальное количество копируемых байтов зависит от размера пункта назначения. Если тип источника отличается вunion у вас все еще будет та же проблема интерпретации байтов при доступе к байтам с использованием типа, отличного от типа, используемого для хранения этих байтов.
 Richard Chambers27 июл. 2016 г., 03:48
Я бы посчиталmemcpy() Сейф для типа наказания. И на самом деле я видел много встроенного кода, который зависит от него сmemcpy() раньше делитьstruct объекты с вычислением адреса смещения и действительно странные вещи.struct были определены сpragma для выравнивания байтов, так что все работает. Это был не C11, а более старый C98 и, в некоторой степени, старый K & R C. Это могло бы стать улучшением спецификации, чтобы фактически определить поведение, которое было канонизировано как поведение де-факто в течение многих лет.
 Gene27 июл. 2016 г., 06:14
@chqrlie Конечно,memcpy безопасно. Но для компилятора, выдвигающегоuint64_t в стеке дляprintf знать, что это должно быть выровнено наdouble границу, он должен был бы проверить строку формата, выдвинуть каждый аргумент varargs на lcm всех границ выравнивания или динамически проверить каждыйprintf Аргумент для выравнивания и копирования по мере необходимости на лету. Все это кажется очень не в стиле Си. Но, как я уже сказал, я не эксперт.
 chux27 июл. 2016 г., 04:47
Примечание: разница вunion v memcpy() долженsizeof double != sizeof uint64_t - но я беру их равные размеры, принимаю как данность.
 supercat27 июл. 2016 г., 16:44
@ChrisBeck: что могло бы способствовать оптимальному коду, если бы в стандарте требовалось, чтобы при приведении указателя из одного конкретного типа к другому все доступы, выполненные через указатель приведения, предшествующие любым другим доступам к тому же хранилищу, были распознаны как потенциальные доступы старого или нового типа. Самым простым способом достижения на традиционных компиляторах было бы сказать, что если, например,int* приведен кshort* тогда любой кешируетсяint значения, которые могут быть идентифицированы этим указателем, должны быть сброшены. Переупорядочение кода усложняет ситуацию, но правильным решением для этого было бы ...
 Richard Chambers27 июл. 2016 г., 02:35
То, как я читаю раздел 6 - это то, эквивалентен ли ваш пример. Сmemcpy() значение совпадает, так как вы назначаетеdouble тогда сделайmemcpy() кuint64_t поэтому скопированное значение имеет типdouble, Аналогично сunion Вы назначаете значение дляdouble часть объединения, поэтому значение в области памяти имеет типdouble и вы получаете к нему доступ черезuint64_t, В любом случае нет преобразования типа фактического значения. Так что нет преобразования изdouble вuint64_t. memcpy() копирует указанное количество байтов. Как вы думаете, почему два примера будут разными?

Ответы на вопрос(5)

Я читаю параграф 6, говоря, что используяmemcpy() Функция для копирования серии байтов из одной ячейки памяти в другую ячейку памяти может использоваться для обозначения типа, точно так же, какunion с двумя различными типами могут быть использованы для типа штамповки.

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

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

Если затем вы получите доступ к скопированным байтам через переменную назначения (lvalue),uint64_t bits; в этом примере тип этих данных рассматривается как тип l-значения, используемого для извлечения байтов данных из этой целевой переменной. Таким образом, байты интерпретируются (не преобразуются, а интерпретируются) как тип целевой переменной, а не как тип исходной переменной.

Доступ к байту через другой тип означает, что байты теперь интерпретируются как новый типхотя байты на самом деле не изменились.

Это также способunion работает.union не делает никаких преобразований. Вы храните байты вunion член, который имеет один тип, а затем вы вытаскиваете те же байты обратно через другойunion член. Байты одинаковы, однако интерпретация байтов зависит от типаunion элемент, который используется для доступа к области памяти.

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

Поскольку тип исходного местоположения, используемого вmemcpy() тип байтов, хранящихся там, те же самые проблемы, с которыми вы можете столкнуться при использованииunion для наказания также применяются к использованиюmemcpy() таким образом, такой какПорядок байтов типа данных.

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

 chqrlie27 июл. 2016 г., 10:32
Хотелось бы, чтобы ваше толкование было правильным, но если в пункте 6 указан эффективный тип представительства в пункте назначенияmemcpy это источник, пункт 7 говорит, что доступ к нему с типом назначения не является одним из перечисленных случаев, потому что тип выражения lvalue,uint64_t, не совместим с эффективным типом объекта, значение которого хранится, а именноdoubleи не является символьным типом.

Есть два случая для рассмотрения:memcpy()входя в объект, которыйимеет объявленный тип иmemcpy()в объект, который не делает.

Во втором случае

double d = 1234.5678;
void *p = malloc(sizeof(double));
assert(p);
uint64_t *pbits = memcpy(p, &d, sizeof(double));
uint64_t bits = *pbits;
printf("the representation of %g is %08"PRIX64"\n", d, bits);

Поведение действительно не определено, поскольку эффективный тип объекта, на который указываетp станетdoubleи доступ к объекту эффективного типаdouble хотя lvalue типаuint64_t не определено

С другой стороны,

double d = 1234.5678;
uint64_t bits;
memcpy(&bits, &d, sizeof bits);
printf("the representation of %g is %08"PRIX64"\n", d, bits);

являетсяне не определено. Проект стандарта C11 n1570:

7.24.1 Соглашения о строковых функциях
3 Для всех функций в этом подпункте каждый символ должен интерпретироваться так, как если бы он имел тип unsigned char (и, следовательно, каждое возможное представление объекта является допустимым и имеет другое значение).

А также

6.5 Выражения
7 У объекта должно быть сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов: 88)

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

Сноска 88) Целью этого списка является определение тех обстоятельств, при которых объект может или не может быть псевдонимом.

Итакmemcpy() Сам по себе четко определен.

посколькуuint64_t bits имеет объявленный тип, он сохраняет свой тип, даже если его объектное представление было скопировано изdouble.

Как указывает chqrlie,uint64_t не может иметь представления ловушек, поэтому доступbits послеmemcpy() являетсяне не определено, при условииsizeof(uint64_t) == sizeof(double), Тем не менеезначение изbits будет зависеть от реализации (например, из-за порядкового номера).

Заключение: memcpy() Можно использоваться для наказания, при условии, что пункт назначенияmemcpy() имеет объявленный тип, т.е. не выделяется[m/c/re]alloc() или эквивалент.

 EOF12 мар. 2019 г., 10:47
@SomeName Учитывая, что актхранения к объекту заставляет его иметь эффективный тип для дальнейших (чтение) доступов, даже если у него не было эффективного типа раньше, единственный способ скопироватьот объект без эффективного типа будет иметь доступ к объекту, который не инициализирован и не записан, что делает его значение неопределенным и считывает его неопределенное поведение.
 Some Name10 мар. 2019 г., 14:34
Являетсяmemcpy используется для определения типа, касающегося типа источника? Может ли источник не иметь объявленного типа (m/c/re/allocред) при условии, что мы знаем егоэффективный тип?

Вы предлагаете 3 способа, которые имеют разные проблемы со стандартом Си.

стандартная библиотекаmemcpy

double d = 1234.5678;
uint64_t bits;
memcpy(&bits, &d, sizeof bits);
printf("the representation of %g is %08"PRIX64"\n", d, bits);

memcpy часть легальна (предоставляется в вашей реализацииsizeof(double) == sizeof(uint64_t) которыйне гарантировано по стандарту): вы получаете доступ к двум объектам через указатели на символы.

Ноprintf линии нет. Представление вbits сейчас двойник. это может быть представление ловушки дляuint64_t, как определено в 6.2.6.1 Общие §5

Определенные представления объекта не должны представлять значение типа объекта. Если сохраненное значение объекта имеет такое представление и читается выражением lvalue, которое не имеет символьного типа, поведение не определено. Если такое представление создается побочным эффектом, который изменяет весь или любую часть объекта выражением lvalue, которое не имеет символьного типа, поведение не определено. Такое представление называется представлением ловушек.

И 6.2.6.2 Целочисленные типы говорят явно

Для целых типов без знака, отличных от знака без знака, биты представления объекта должны быть разделены на две группы: биты значения и биты заполнения ... Значения любых битов заполнения не определены.53

С примечанием 53 говорится:

Некоторые комбинации битов заполнения могут генерировать представления ловушек,

Если вы знаете, что в вашей реализации нет битов заполнения (все еще никогда не видели один ...), каждое представление является допустимым значением, иprint линия снова становится действительной. Но это толькозависит от реализации и может быть неопределенным поведением в общем случае

союз

union { double d; uint64_t i; } u;
u.d = 1234.5678;
printf("the representation of %g is %08"PRIX64"\n", d, u.i);

Члены объединения не имеют общей подпоследовательности, и вы получаете доступ к члену, который не является последним записанным значением. Хорошо, общая реализация даст ожидаемые результаты, нопо стандарту точно не определено, что должно произойти. Сноска в 6.5.2.3 Структура и члены объединения § 3 гласит, что if приводит к тем же проблемам, что и в предыдущем случае:

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

обычайmemcpy

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

Единственный способ, который будет четко определен для каждого стандарта, - это сохранить представлениеdouble в массиве char правильного размера, а затем отобразите байтовые значения массива char:

double d = 1234.5678;
unsigned char bits[sizeof(d)];
memcpy(&bits, &d, sizeof(bits));
printf("the representation of %g is ", d);
for(int i=0; i<sizeof(bits); i++) {
    printf("%02x", (unsigned int) bits[i]);
}
printf("\n");

И результат будет применим, только если реализация использует ровно 8 бит дляchar, Но это будет видно, потому что он будет отображать более 8 шестнадцатеричных цифр, если один избайтов имел значение более 255.

Все вышеперечисленное действует только потому, чтоbits имеет объявленный тип. Посмотри пожалуйста@ EOF ответ чтобы понять, почему это будет иначе для выделенного объекта

 Serge Ballesta27 июл. 2016 г., 13:47
@EOF: хорошая точность. Я отредактировал свой ответ с этим.
 EOF27 июл. 2016 г., 14:19
На самом деле, соответствующая часть для вашего конкретного примера - это предложение ранее:Если значение сохраняется в объекте, у которого нет объявленного типа, через lvalue, имеющий тип, который не является символьным типом, то тип lvalue становится эффективным типом объекта для этого доступа и для последующие обращения, которые не изменяют сохраненное значение. [...] , но дело в том же.
 EOF27 июл. 2016 г., 14:08
Помните, что проект стандарта C11 n1570:6.5 Выражения 6 [...] Если значение копируется в объект, не имеющий объявленного типа, с использованием memcpy или memmove, или копируется как массив символьного типа, тогда эффективный тип модифицированного объекта для этого доступа и для последующих обращений, которые не изменяют значение является эффективным типом объекта, из которого копируется значение, если оно есть. memcpy() делает изменить значение, чтобы эффективный тип объектаснова меняется.
 chqrlie27 июл. 2016 г., 18:28
Проблема не в ловушках представлений. По сути дела,uint64_t не может иметь представления ловушек, поскольку представление должно быть 64 битами значения и не должно содержать битов заполнения. Проблема в том, достаточно ли умен / компилятор компилятор, чтобы предположить, чтоbits не изменялся с момента последнего чтения или записи, предположение, которое может быть поддержано отсутствием доступа к записи через соответствующий тип.memcpy очевиднодолжен предполагается изменитьbits поскольку неконстантный указатель на него передаетсяmemcpy, конвертировано вvoid*.
 Serge Ballesta27 июл. 2016 г., 14:04
@EOF: Я согласен с вами, но это другая проблема, которую я считаю слабо связанной с xalloc. Если я изменю ваш пример наvoid *p = malloc(sizeof(double)); uint64_t *pbits = p; *pbits = 0 // effective type is now uint64_t; memcpy(p, &d, sizeof(double));Я остался только с определенным реализацией поведением из-за возможного представления ловушек. Как только выделенная переменная получает тип, она сохраняет его, пока не будет освобождена.
 EOF27 июл. 2016 г., 13:51
Одна часть, которая здесь отсутствует, это то, что поведение явноне определено вmemcpy() вариант, если копируемый объект выделен[m/c/re]alloc(), Посмотрите на мой ответ, почему это так.
 EOF27 июл. 2016 г., 14:25
В обоснование стандарта C993. Термины и определения В определении объекта не используется понятие типа. Таким образом, объект не имеет типа сам по себе. Однако, поскольку объект может быть обозначен только lvalue (см. §6.3.2.1), фраза «тип объекта» понимается здесь и в стандарте как «тип lvalue, обозначающий этот объект, »И« значение объекта »означает« содержимое объекта, интерпретируемое как значение типа lvalue, обозначающего объект ». Таким образом, время жизни объекта не привязано к его типу.
 Serge Ballesta27 июл. 2016 г., 14:26
@EOF: это интересный вопрос, потому что стандарт ничего не говорит о том, как можно построить выделенный объект. Я недавно спросилвопрос На это и через 6 дней предложили ответ. Пожалуйста, прочтите его и прокомментируйте либо вопрос, либо ответ, если вы можете пролить свет на это. Но теперь я понял вашу точку зрения и связался с вашим ответом.
 EOF27 июл. 2016 г., 12:40
union случай явно (но, к сожалению, ненормативно) задокументирован для определения реализацией в проекте стандарта C11 n15706.5.2.3 Структура и члены объединения [Сноска] 95 Если элемент, используемый для чтения содержимого объекта объединения, не совпадает с элементом, который последний раз использовался для хранения значения в объекте, соответствующая часть представления объекта значения переинтерпретируется как представление объекта в новом типе, как описано в 6.2.6 (процесс, иногда называемый '' наказанием типа ''). Это может быть представление ловушки.

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

 chqrlie27 июл. 2016 г., 02:22
Отсюда вопрос:Почему бы и нет?  Пункт 6 неясен для меня. Может кто-нибудь объяснить? Что, еслиmemcpy() реализован как чтение и запись байтов. Сохраняет ли последний пункт в параграфе 7 день?
 supercat27 июл. 2016 г., 04:37
@chqrlie: Исторически сложилось так, что такое поведение было на 100% надежным, но основные авторы компиляторов настолько отчаянно пытаются получить дополнительный процентный показатель или два из производительности теста, что они готовы выбросить десятилетия прецедента в окно (и заставить программистов, которые не т писать тесты для написания менее эффективного кода), чтобы сделать это.

ИЗМЕНЕНО - СМОТРИ НИЖЕ

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

Что касается того, означает ли это, что лучше использовать memcpy или ручной цикл байтового копирования, цель которого достаточно хорошо замаскирована, чтобы его нельзя было распознать как «копирование массива символьного типа», я понятия не имею. Я бы сказал, что разумно было бы избегать кого-либо настолько тупого, чтобы предполагать, что хороший компилятор должен генерировать фиктивный код без такой маскировки, но поскольку поведение, которое считалось бы тупым в прошлые годы, в настоящее время модно, я понятия не имею,memcpy станет следующей жертвой в гонке за нарушение кода, который компиляторы десятилетиями считали «четко определенным».

ОБНОВИТЬ

GCC с 6.2 иногда пропускает операции memmove в тех случаях, когда он видит, что пункт назначения и источник идентифицируют один и тот же адрес, даже если они являются указателями разных типов. Если хранилище, которое было записано как тип источника, позже считывается как тип назначения, gcc будет считать, что последнее чтение не может идентифицировать то же хранилище, что и предыдущая запись. Такое поведение со стороны gcc оправдано только из-за языка в Стандарте, который позволяет компилятору копировать Эффективный Тип черезmemmove, Неясно, было ли это намеренным толкованием правил, касающихсяmemcpyОднако, учитывая, что gcc также проведет аналогичную оптимизацию в некоторых случаях, когда это явноне разрешено стандартом, например, когда член объединения одного типа (например, 64-битныйlong) копируется во временное и оттуда в элемент другого типа с таким же представлением (например, 64-битныйlong long). Если gcc видит, что назначение будет бит-бит-идентичным временному, оно пропустит запись и, следовательно, не заметит, что эффективный тип хранилища был изменен.

 Richard Chambers27 июл. 2016 г., 05:09
не уверен, что это имеет отношение к вопросу. доступны две функции копирования памяти,memcpy() для неперекрывающихся областей памяти иmemmov() для возможно перекрывающихся областей памяти. Я предполагаю, что это позволяет оптимизировать библиотеку для неперекрывающихся областей памяти, которые опасны для перекрывающихся областей памяти.stackoverflow.com/questions/4415910/memcpy-vs-memmove
 supercat27 июл. 2016 г., 16:32
@RichardChambers: если место назначения memcpy или memmove не имеет объявленного типа, компилятору разрешается считать недоступным любой код, который будет пытаться прочитать место назначения, используя любой тип, отличный от источника; компилятор также может сделать это, если он считает, что данные копируются как массив символов (что бы это ни значило). Однако если данные копируются в виде последовательности совершенно не связанных между собой символьных операций, компилятору не разрешается делать такой вывод, но Стандарт не ясно, насколько несвязанными должны быть операции.

Ваш ответ на вопрос