Выбрасывает ли указатель T указатель T и возвращает исходный указатель, если T 'является неполным типом?

Рассмотрим следующий фрагмент, который адаптирован из исходного кода Tor:

/* This can be a malloc wrapper with minimal initialization. */
other_t *make_other(void);

/* This struct is never defined. */
struct undef;
typedef struct undef undef_t;

undef_t *make_undef(void)
{
    other_t *other = make_other();
    return (undef_t*)other;
}

Предположим, что все указатели undef_t в программе отличены указателями other_t и предполагают, что все процедуры, которые принимают undef_t*, передают их в other_t* перед использованием.

В соответствии с разделом 6.3.2.3 стандарта C99 приведение в оператор return вызывает поведение undefined, если other неправильно выровнено как указатель на undef_t, но если это так, результат make_undef назад на other_t* возвращает исходный указатель, возвращенный make_other. Однако undef_t - это тип undefined, и я не могу найти никаких правил выравнивания относительно них. Эти преобразования все еще работают так, как если бы они были undef_t и имели правильное выравнивание?

Ответы

Ответ 1

Я не думаю, что имеет значение, является ли тип неполным.

В C11 6.2.5.22 существует только три типа неполного типа, которые могут быть созданы:

  • Тип массива неизвестного размера
  • Тип структуры неизвестного контента
  • Тип объединения неизвестного содержимого

Далее, за C11 6.2.5.28:

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

Таким образом, требования представления и выравнивания для указателя на структуру или объединение известны, является ли тип завершенным или нет, поскольку вы все еще знаете его как указатель на структуру или объединение какого-то рода.

Для массивов, поскольку из C11 6.3.2.1.3 известно, что:

выражение, которое имеет тип "массив типа", преобразуется в выражение с типом "указатель на тип", указывающий на начальный элемент объекта массива

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

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

В вашем случае как undef_t, так и other_t находятся typedef d где-то в вашей единицы перевода, поэтому вы знаете, какой у вас неполный тип, и поэтому вы знаете требования к выравниванию для него, поэтому есть никакой двусмысленности, созданной тем фактом, что этот тип является неполным. Конечно, если вы, как программист, используете их, не пытаясь найти это сами, тогда вы все равно можете столкнуться с проблемами, если, например, other_t является указателем на объединение, а undef_t является указателем на структуры. Но это обычная проблема преобразования между указателями, которые могут иметь разные требования к выравниванию - ничто из этой проблемы не усложняется, если один из более скрытых типов является неполным.

EDIT - некоторое дальнейшее разъяснение, основанное на комментариях:

"Указатель на неполный тип" может только указывать на объект полного типа, поскольку неполные типы, очевидно, по определению не могут быть созданы (для краткости я откладываю возможность malloc() ing произвольный объем памяти и указав на него указатель неполного типа).

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

В терминах выравнивания представление указателя может изменяться в зависимости от требований к выравниванию объекта, на который он указывает. Например, как указано в комментариях, если все int должны храниться на 8-байтных границах, то представление указателя потенциально может не хранить 3 младших значащих бита, потому что они всегда будут равны нулю. Но если вы затем попытаетесь преобразовать указатель char в указатель int и обратно, вы можете потерять информацию, потому что указатель char должен указывать на отдельные байты, и некоторая часть этой информации будет потеряна в это гипотетическое преобразование в int *.

Аналогичным образом, может быть, что некоторые небольшие структуры могут быть выровнены, скажем, на 4 байтовых границах и больше на 32 байтовых границах. Но требования к выравниванию для указателей на структуры должны быть одинаковыми, именно из-за этих указателей на неполные типы. В том месте, где вы используете указатель на неполный тип структуры, у вас нет определения типа, доступного (если бы вы это сделали, это был бы полный тип), и поэтому вы не знаете, можете ли вы игнорировать наименее значимые 5 бит или только наименее значимые 3 бита, например. Таким образом, требования к выравниванию для указателя на неполный тип структуры должны быть достаточно разрешительными, чтобы они могли правильно удерживать местоположение любого мыслимого типа структуры. Чтобы упростить прохождение этих операций, мы можем видеть из приведенной выше цитаты, что C требует, чтобы все указатели на типы структуры имели одинаковые требования к выравниванию. Например, некоторые структуры могут быть фактически выровнены на 32 байтовых границах, но указатель на эту структуру не может быть разрешен в полной мере использовать это, и он должен быть способен удерживать местоположение любого типа структуры.

Но, если бы все типы структуры были выровнены не менее, чем на 4 байтовых границах, то это было бы прекрасно для указателей на структуры, включая указатели на неполные типы структуры, чтобы игнорировать наименее значимые 3 бита, поскольку вы можете сделайте это, все еще будучи уверенным, что вы можете безопасно представлять местоположение любой структуры. Действительно, тот факт, что C требует, чтобы все указатели на типы структуры имели одинаковые требования к выравниванию и требовали, чтобы все указатели на типы объединения имели одинаковые требования к выравниванию, но не требуют, чтобы требования выравнивания для указателей к типам структуры были такими же, как выравнивание требования к указателям на типы объединения указывают на то, что можно, например, указывать структурам на минимум 4 байта, но для указателей на объединения полагаться на минимум 8-байтовую границу. В этом случае вы не можете безопасно преобразовать указатель на неполный тип структуры в указатель на неполный тип объединения и обратно.

Вот почему неверно говорить, как в настоящее время делает другой ответ, что "нет требований к выравниванию для неполного типа". Но это означает, что неполные типы не являются проблематичными для исходного вопроса, потому что, как уже объяснялось, когда у вас есть неполный тип, вы, по крайней мере, знаете, является ли его структура или объединение или массив, и что все, что вам нужно имеют полное знание требований к выравниванию.