Сначала давайте рассмотрим назначение без приведения.C 2018 6.5.16.1 1 перечисляет ограничения для простого присваивания, говоря, что одно из них должно выполняться.Первые два относятся к арифметике, структуре и типам объединения.Последние две сделки включают в себя константы нулевого указателя или _Bool
.Средние два имеют дело с назначением указателей указателям:
левый операнд имеет атомный, квалифицированный или неквалифицированный тип указателя, и… оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов,и тип, на который указывает левый, имеет все квалификаторы типа, на который указывает правый
левый операнд имеет атомный, квалифицированный или неквалифицированный тип указателя, и ... один операнд являетсяуказатель на тип объекта, а другой - указатель на квалифицированную или неквалифицированную версию void, а тип, на который указывает левый, имеет все квалификаторы типа, на который указывает правый
Последний говорит, что мы можем присвоить void *
любому указателю объекта и наоборот, если не удаляются квалификаторы (const
, volatile
, restrict
или __Atomic
).
Бывший говорит, что мы можем назначать указатели совместимым типам, если не удаляются квалификаторы.Что такое совместимые типы?
6.2.7 1 говорит:
- Два типа совместимы, если они одинаковы.
- Дополнительные правила приведены в 6.7.2,6.7.3 и 6.7.6.
- Два типа структуры, объединения или перечисления, объявленные в отдельных единицах перевода, совместимы, если они, по сути, объявлены одинаково.(Интересно, что два таких типа, объявленные в единице перевода , одинаковые , несовместимы.)
6.7.2 4 говорит, что каждый перечислимый тип (тип enum
) совместим сопределяемый реализацией выбор char
или целочисленный тип со знаком или без знака.Таким образом, указатель на некоторый enum
может быть назначен указателю на один char
или целочисленный тип (и наоборот), но вы не можете знать какой, не зная что-то о конкретной реализации языка Си.
6.7.3 11 говорит, что квалифицированные типы должны иметь одинаковые квалификаторы для совместимости.Таким образом, int
несовместим с const int
, и это препятствует назначению int *
для const int *
.
6.7.6.1 2 говорит о совместимости двух типов указателей, онидолжны быть идентично квалифицированные указатели на совместимые типы.Это говорит нам, например, что int *
несовместим с char *
, и, следовательно, в соответствии с вышеизложенными ограничениями, char **
не может быть назначен для int **
.
6.7.6.2 6 говорит о совместимости двух типов массивов, они должны иметь совместимые типы элементов и, если они оба имеют целочисленные постоянные размеры, они должны быть одинаковыми.(Это позволяет совместить массив с неизвестным размером с массивом известного размера. Однако дополнительный текст говорит, что если массивы в конечном итоге имеют разные размеры, их использование в контексте, требующем их совместимости, имеет неопределенное поведение.присвоение указателей таким массивам может удовлетворять его ограничениям и компилироваться без ошибок, но результирующая программа может вести себя некорректно.)
6.7.6.3 15 представлены несколько сложные правила совместимости типов функций.Эти правила сложны, потому что функции могут быть объявлены с или без списков параметров, с эллипсами и так далее.Я опущу их полное обсуждение.
Это правила, которые сообщают вам, какие назначения указателей могут быть сделаны без приведений.
6.5.4 обсуждает приведение типов.Его ограничения не ограничивают, какие типы указателей могут быть преобразованы в какие другие типы указателей.(Они запрещают другие вещи, включающие указатели, такие как преобразование типа указателя в тип с плавающей запятой.) Таким образом, вы можете указать любое преобразование указателя, которое вы хотите в приведении, и, пока результирующий тип совместим с типом длякоторому оно присваивается, никакое присваивание или ограничение приведения не нарушаются.Однако все еще остается вопрос о том, является ли преобразование правильным.
6.3.2.3 определяет правила для преобразования указателей.Те, кто занимается преобразованием указателей в указатели (исключая целые числа и константы нулевого указателя), говорят:
Любой указатель на тип объекта (не на типы функций) может быть преобразован в указатель на voidи наоборот.Результат преобразования указателя объекта в пустой указатель и обратно сравнивается равным оригиналу.
Указатель может быть преобразован в тот же тип с большим количеством классификаторов, а результат сравнения равеноригинал.
Указатель на тип объекта может быть преобразован в указатель на другой тип объекта, если результирующий указатель правильно выровнен для его типа (в противном случае поведение не определено),При обратном преобразовании результат сравнивается равным исходному указателю.(Обратите внимание, что, хотя вам разрешено выполнять это преобразование, в этом правиле не говорится, что результирующий указатель может использоваться для доступа к объекту нового типа. В C существуют другие правила об этом.)
Указатель на тип функции может быть преобразован в указатель на другой тип функции.При обратном преобразовании результат сравнивается равным исходному указателю.(Как и в случае с объектами, вам разрешено выполнять это преобразование, но использование результирующего указателя для вызова несовместимой функции имеет неопределенное поведение.)
Таким образом, при использовании приведений вы можете преобразоватьлюбой тип указателя на объект к любому типу указателя на объект и назначать его, пока выполняется требование выравнивания, и вы можете преобразовать любой тип указателя функции в любой тип указателя на функцию и назначить его.