Что делает memcpy Calls
Вопрос плохо поставлен. Давайте сначала посмотрим на код:
char repr[offsetof(struct first_member_padded_t, b)] = //some value
memcpy(repr, &a, sizeof(a));
memcpy(&(s.a), repr, sizeof(repr));
Первое замечание: repr
инициализирован, поэтому все элементы в нем имеют заданные значения.
Первый memcpy
в порядке - он копирует байты a
в repr
.
Если бы вторым memcpy
было memcpy(&s, repr, sizeof repr);
, он скопировал бы байты из repr
в s
. Это будет записывать байты в s.a
и из-за размера repr
в любой отступ между s.a
и s.b
. В соответствии с C 2018 6.5 7 и другими частями стандарта разрешается доступ к байтам объекта (а «доступ» означает чтение и запись, согласно 3.1 1). Так что эта копия в s
в порядке, и в результате s.a
принимает то же значение, что и a
.
Однако memcpy
использует &(s.a)
вместо &s
. Он использует адрес s.a
, а не адрес s
. Мы знаем, что преобразование s.a
в указатель на символьный тип позволит нам получить доступ к байтам s.a
(6,5 7 и более) (и передача его в memcpy
имеет тот же эффект, что и такое преобразование, как * Указано, что 1035 * влияет на копирование байтов), но неясно, что позволяет нам получить доступ к другим байтам в s
. Другими словами, у нас есть вопрос, можем ли мы использовать &s.a
для доступа к байтам, отличным от байтов в s.a
.
6.7.2.1 15 говорит нам, что, если указатель на первый член структуры «соответствующим образом преобразован», результат указывает на структуру. Итак, если мы преобразуем &s.a
в указатель на struct first_member_padding_t
, он будет указывать на s
, и мы, безусловно, можем использовать указатель на s
для доступа ко всем байтам в s
. Таким образом, это также будет хорошо определено:
memcpy((struct first_member_padding t *) &s.a, repr, sizeof repr);
Однако memcpy(&s.a, repr, sizeof repr);
преобразует только &s.a
в void *
(поскольку объявляется, что memcpy
принимает void *
, поэтому &s.a
автоматически преобразуется во время вызова функции), а не в указатель на тип конструкции. Это подходящее преобразование? Обратите внимание, что если бы мы сделали memcpy(&s, repr, sizeof repr);
, он конвертировал бы &s
в void *
. 6.2.5 28 говорит нам, что указатель на void
имеет то же представление, что и указатель на символьный тип. Итак, рассмотрим эти два утверждения:
memcpy(&s.a, repr, sizeof repr);
memcpy(&s, repr, sizeof repr);
Оба эти оператора передают void *
в memcpy
, и эти два void *
имеют одинаковое представление и указывают на один и тот же байт. Теперь мы можем интерпретировать стандарт педантично и строго так, чтобы он отличался тем, что последний может использоваться для доступа ко всем байтам s
, а первый - нет. Тогда странно, что у нас есть два обязательно идентичных указателя, которые ведут себя по-разному.
Такая строгая интерпретация стандарта C представляется теоретически возможной - разница между указателями может возникать во время оптимизации, а не в реальной реализации memcpy
, но я не знаю ни одного компилятора, который бы это делал. Обратите внимание, что такая интерпретация противоречит разделу 6.2 стандарта, в котором говорится о типах и представлениях. Интерпретация стандарта таким образом, что (void *) &s.a
и (void *) &s
ведут себя по-разному, означает, что две вещи с одним и тем же значением и типом могут вести себя по-разному, что означает, что значение состоит из чего-то большего, чем его значение и тип, что, по-видимому, не является целью 6,2 или стандарт вообще.
Тип-каламбурная
Вопрос гласит:
Я пытаюсь понять, как работает наказание типов, когда речь идет о сохранении значения в элементе структуры или объединения.
это неТип-наказание, как термин, который обычно используется.Технически, код получает доступ к s.a
, используя lvalues другого типа, чем его определение (потому что он использует memcpy
, который определен для копирования как с типом символа, в то время как определенный тип int
), но байтыисходят из int
и копируются без изменений, и этот вид копирования байтов объекта обычно рассматривается как механическая процедура;это делается для создания копии, а не для повторной интерпретации байтов в новом типе.«Наказание по типу» обычно относится к использованию различных значений l с целью реинтерпретации значения, например, к записи unsigned int
и чтению float
.
В любом случае наложение типа на самом деле не являетсяТема вопроса.
Значения в элементах
Заголовок спрашивает:
Какие значения мы можем хранить в элементах структуры или объединения?
Этот заголовок кажется несоответствующим содержанию вопроса.На вопрос заголовка легко ответить: значения, которые мы можем хранить в элементе, являются теми значениями, которые может представлять тип элемента.Но вопрос продолжает исследовать отступы между участниками.Заполнение не влияет на значения в элементах.
Заполнение принимает неопределенные значения
Вопрос цитирует стандарт:
Когда значение сохраняется в объектеструктуры или типа объединения, в том числе в объекте-члене, байты представления объекта, которые соответствуют любым байтам заполнения, принимают неопределенные значения.
и говорят:
ИтакЯ интерпретировал это так, как будто у нас есть объект для хранения в элементе, так что размер объекта равен s izeof(declared_type_of_the_member) + padding
, байты, относящиеся к заполнению, будут иметь неопределенное значение…
Текст в кавычках встандарт означает, что, если байты заполнения в s
были установлены в некоторые значения, как в случае memcpy
, и мы затем делаем s.a = something;
, то байты заполнения больше не должны содержать свои предыдущие значения.
Код в вопросе исследует другую ситуацию.Код memcpy(&(s.a), repr, sizeof(repr));
не хранит значения в элементе структуры в смысле, указанном в 6.2.6.1 6. Он не сохраняется ни в одном из элементов s.a
или s.b
.Это копирование байтов в, что отличается от того, что обсуждается в 6.2.6.1.
6.2.6.1 6 означает, что, например, если мы выполним этот код:
char repr[sizeof s] = { 0 };
memcpy(&s, repr, sizeof s); // Set all the bytes of s to known values.
s.a = 0; // Store a value in a member.
memcpy(repr, &s, sizeof s); // Get all the bytes of s to examine them.
for (size_t i = sizeof s.a; i < offsetof(struct first_member_padding_t, b); ++i)
printf("Byte %zu = %d.\n", i, repr[i]);
тогда не обязательно верно, что все нули будут напечатаны - байты в заполнении могли измениться.