Подтвердить что ты не робот

C - Несовместимый тип указателя

Почему следующий код дает предупреждения?

int main(void)
{
    struct {int x; int y;} test = {42, 1337};
    struct {int x; int y;} *test_ptr = &test;
}

Результаты:

warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
         struct {int x; int y;} *test_ptr = &test;
                                            ^
4b9b3361

Ответ 1

Это два анонимных типа структуры (они не имеют тега). Все такие типы структуры (в одной единице перевода) различны - они никогда не бывают одного типа. Добавьте тег!

Соответствующее предложение в стандарте приведено в §6.7.2.1 Спецификаторы структуры и объединения:

¶8 Наличие списка struct-declaration-list в struct-or-union-specifier объявляет новый тип, в пределах единицы перевода.

Список struct-declaration-list относится к материалу между { и } в типе.

Это означает, что в вашем коде есть два отдельных типа: по одному для каждого struct { … }. Эти два типа являются отдельными; вы не можете официально присваивать значение одного типа другому, а также создавать указатели и т.д. Фактически вы не можете ссылаться на эти типы снова после точки с запятой.

Это означает, что вы могли бы:

int main(void)
{
    struct {int x; int y;} test = {42, 1337}, *tp = &test;
    struct {int x; int y;} result, *result_ptr;
    result_ptr = &result;
    …
}

Теперь test и tp относятся к одному типу (одна - структура, одна - указатель на структуру) и аналогично result и result_ptr относятся к одному типу, а инициализации и назначения отлично, но два типа различны. Не ясно, что вы создаете составной литерал любого типа - вам нужно написать (struct {int x; int y;}){.y = 9, .x = 8}, но наличие списка struct-declaration-list означает другой новый тип.

Как отмечено в комментариях, существует также раздел §6.2.7 Совместимый тип и составной тип, который гласит:

¶1... Кроме того, две структуры, объединение или перечисляемые типы, объявленные в отдельных единицах перевода, совместимы, если их теги и элементы удовлетворяют следующим требованиям: если объявлено тегом, другие должны быть объявлены одним и тем же тегом. Если они завершены в любом месте их соответствующие единицы перевода, то применяются следующие дополнительные требования: быть взаимно однозначным соответствием между их членами, так что каждая пара соответствующие члены объявляются с совместимыми типами; если один член пары объявленный с помощью спецификатора выравнивания, другой объявляется с эквивалентным выравниванием спецификатор; и если один член пары объявлен с именем, другой объявлен с тем же именем. Для двух структур соответствующие члены должны быть объявлены в такой же заказ. Для двух структур или объединений соответствующие битовые поля должны иметь одинаковые ширина.

Грубо говоря, это говорит о том, что если определения типов в двух единицах перевода (думаю, "исходные файлы" плюс включенные заголовки) одинаковы, то они относятся к одному типу. Слава богу за это! В противном случае вы не могли бы использовать стандартную библиотеку ввода-вывода, среди других мелких деталей.

Ответ 2

Переменные &test и test_ptr, которые являются анонимными структурами, имеют разные типы.

Анонимные структуры, определенные в одной и той же единицы перевода, никогда не совместимы с типами 1 так как Стандарт не определяет совместимость для двух определений типа структуры в одной и той же единицы перевода.

Чтобы скомпилировать ваш код, вы можете сделать:

struct {int x; int y;} test = {42, 1337} , *test_ptr;
test_ptr = &test;

1 (Цитируется по: ISO: IEC 9899: 201X 6.2.7 Совместимый тип и составной тип 1)
Два типа имеют совместимый тип, если их типы одинаковы. Дополнительные правила для определения совместимости двух типов описаны в 6.7.2 для спецификаторов типов, в 6.7.3 для классификаторов типов и в 6.7.6 для деклараторов. Кроме того, две структуры, объединения или перечисленные типы, объявленные в отдельных единицах перевода, совместимы, если их теги и члены удовлетворяют следующим требованиям: если объявлено тегом, другое объявляется с тем же тег. Если оба они заполнены в любом месте своих соответствующих единиц перевода, применяются следующие дополнительные требования: между их членами должно быть взаимно однозначное соответствие, чтобы каждая пара соответствующих членов была объявлена ​​с совместимыми типами; если один член пары объявлен с помощью спецификатора выравнивания, другой объявлен с эквивалентным спецификатором выравнивания; и если один член пары объявлен с именем, другой объявляется с тем же именем. Для двух структур соответствующие члены должны быть объявлены в том же порядке. Для двух структур или объединений соответствующие битовые поля должны иметь одинаковую ширину. Для двух перечислений соответствующие члены должны иметь одинаковые значения.

Ответ 3

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

struct { int size; int foo[2]; } my_two_foos = {2, {1,2} };
struct { int size; int foo[3]; } my_three_foos = {3, {4,5,6} };

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

К сожалению, авторы компиляторов потеряли из виду тот факт, что стандарт определяет только то, что необходимо для реализации, как "совместимый" и не определяет, какие функции делают что-то хорошим компилятором для данной платформы, и как вследствие этого они стали все более агрессивными при поиске оправданий, чтобы игнорировать прецеденты на платформах, где поведение на протяжении десятилетий поддерживалось с минимальными затратами. Как следствие, код, который зависит от поведения, который обычно был обычным явлением, может работать только правильно, если использовать параметры компилятора, такие как -fno-strict-aliasing, которые отключают гораздо больше оптимизаций, чем это было бы необходимо при использовании менее агрессивного компилятора.