Код в вашем вопросе правильный. Приведение указателя определяется тогда и только тогда, когда оно правильно выровнено для типа приведения.
(Сноска. Я видел, как люди утверждают, что 6.3.2.3/7 не указывает, что результат преобразования указывает на один и тот же байт в памяти - однако, если этот аргумент принят, то malloc
непригодный для использования, поскольку он не гарантирует преобразование void *
в любой тип для будущих точек использования в выделенный блок. Поэтому я не считаю этот аргумент действительным).
Из-за необходимости, чтобы массивы не имели дополняя элементы, мы можем заключить, что sizeof(T)
должно быть кратным _Alignof(T)
, и поэтому все ваши целочисленные выражения кратны _Alignof(T)
, и, следовательно, все рассматриваемые указатели правильно выровнены.
Что касается терминологии, объект означает «область хранения» в C. Таким образом, все пространство, выделенное mallo c, является объектом, как и любое смежное его подмножество.
Использование оператора присваивания изменяет объект, а не создает его. Когда вы используете оператор присваивания на малло c d, он устанавливает эффективный тип записанных байтов. C11 6.5 / 6 (aka. Строгое правило алиасинга) определяет значение «эффективного типа».
Есть еще одна деталь. Если ваша функция init
выглядит следующим образом:
void init(s* v) {
s a = { .a = 1, .b = 2 };
*v = a;
}
, тогда это будет конец истории, значение типа s
записывается в местоположение. Но в стандарте неясно, какой эффективный тип установлен на v->a = 1;
. Наиболее распространенная интерпретация стандарта состоит в том, что v->a = 1
означает (*v).a = 1;
, и это назначение также имеет «побочный эффект» установки эффективного типа для всего объекта *v
.
Эта интерпретация позволяет TBAA для функции, подобной void f(s *ps, t* pt)
(где s
- структура без членов типа t
), предполагать, что *ps
и *pt
не пересекаются.
Я уверен, что в этом последнем пункте уже есть несколько вопросов о SO