Всегда ли гарантируется, что первое поле структуры C будет иметь смещение 0?

Что касается языка программирования C...

Часть вопроса в Смещение структуры C/C++ гласит, что "& не всегда указывает на первый байт первого поля структуры"

Но просматривая «Обоснование ANSI» на http://www.lysator.liu.se/c/rat/c5.html в разделе 3.5.2.1 Спецификаторы структуры и объединения указано, что "в начале не может быть дыры". Так что я не уверен, является ли «Обоснование» окончательным, но, кажется, оно противоречит этой части этого очень заметного вопроса.

Итак, что это? Всегда ли первое поле структуры C имеет смещение 0?

struct A
{
    int x;
};

struct B
{
    struct A myA;
    int y;
};

B myB;

&myB Гарантировано будет таким же, как &(myB.myA) в переносимом виде?

(Более конкретно, трюк пользовательских данных libev в Libev, Как передать аргументы для соответствующих обратных вызовов и во многих других местах предполагают, что первое поле в структуре имеет смещение 0... это действительно переносимо?)


person Community    schedule 10.05.2013    source источник
comment
Я почти уверен, что это верно в C; в C++ это верно для классов стандартной компоновки.   -  person Kerrek SB    schedule 10.05.2013
comment
Я почти уверен, что это правда, просто я не знаю, какой стандарт привести. Портативность — моя проблема. Это работает на всем, что я пробовал, но будет ли это преследовать меня на платформе, которую я еще не видел?   -  person    schedule 10.05.2013
comment
@KerrekSB: где находится виртуальная таблица? в начале? (С++)   -  person Alexander Oh    schedule 10.05.2013
comment
@Alex Это конкретно вопрос C, а не C ++, контекст здесь заключается в написании очень переносимого и стандартного кода на простом C.   -  person    schedule 10.05.2013
comment
@wilsonmichaelpatrick, если ваша цель — портативность. исправление очень переносимого макроса offsetof не повредит вашей переносимости. и вам это понравится, если вы начнете менять компоновку своей структуры, потому что стандартная она или нет, то это предположение вас укусит.   -  person Alexander Oh    schedule 10.05.2013
comment
@Alex: полиморфные классы не являются стандартным макетом.   -  person Kerrek SB    schedule 10.05.2013
comment
Возможный дубликат В C, всегда ли указатель на структуру указывает на ее первый элемент?   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 08.05.2016


Ответы (1)


Из стандартного раздела 6.7.2.1 пункта 13 стандарта C99:

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

Поэтому ответ на ваш вопрос - да.

person JeremyP    schedule 10.05.2013
comment
Спасибо, это ответ, я просто не знал, что искать, и мои поисковые запросы вводили в заблуждение. Я приму это в тот момент, когда это позволит мне. - person ; 10.05.2013
comment
@wilsonmichaelpatrick Одна из проблем заключается в том, что стандарты C недоступны в Интернете в свободном доступе. Обычно за них приходится платить (довольно много). Мне удалось найти копию C99 здесь open-std.org/jtc1 /sc22/wg14/www/standards Технически это рабочий документ, но он достаточно хорош. - person JeremyP; 10.05.2013
comment
@wilsonmichaelpatrick Просто для чрезмерной педантичности: это не гарантирует, что (uintptr_t)&myB == (uintptr_t)&myB.myA. [Но вам понадобится заведомо злая реализация, чтобы не было этого равенства.] - person Daniel Fischer; 10.05.2013
comment
@DanielFischer Какова природа зла, которое отличает (uintptr_t)&myB и (uintptr_t)&myB.myA в контексте, который вы не описываете? :) - person ; 10.05.2013
comment
@wilsonmichaelpatrick Например, указатель может содержать адрес последнего байта в объекте, на который указывает указатель. Преобразование в uintptr_t сохраняет битовый шаблон удерживаемого адреса (uintptr_t)&myB == (uintptr_t)&myB.myA + sizeof (int). Достаточно зла? - person Daniel Fischer; 10.05.2013
comment
@DanialFischer Да, это зло. Несмотря на то, что в структуре есть поля, адреса которых увеличиваются в порядке их объявления, я думаю, если вы ссылаетесь на структуру в целом с указателем на конец, тогда все может быть обратным смещением От этого? Будет ли offsetof() возвращать отрицательное число для первого поля в структуре в описанной вами ситуации? (Я не собираюсь разглагольствовать о преднамеренно злом случае, просто для собственной информации.) Как насчет приведения указателя на одну структуру к указателю на более крупную структуру? Нужно ли будет тогда добавлять смещение? - person ; 10.05.2013
comment
@DanielFischer Я не думаю, что в C99 разрешена схема, в которой указатель указывает на последний байт объекта. И это, безусловно, нарушило бы правило, которое я процитировал. - person JeremyP; 10.05.2013
comment
@JeremyP Нет, это не нарушит правило. Когда вы конвертируете указатели из одного типа в другой, вы, конечно, должны настроить адрес, например, приведение int32_t * к int64_t * добавит 4 к адресу. Правило гласит соответствующим образом преобразовано, поэтому прямые приведения и приведения через void* должны работать (также указатели на символы), но через uintptr_t работать не обязательно. - person Daniel Fischer; 10.05.2013
comment
@wilsonmichaelpatrick offsetof дает size_t без знака. Это должно быть обычное значение. Вы получите его за (member_ptr - member_size) - (struct_ptr - struct_size). Такая схема потребовала бы большого возни с размерами и смещениями, так что это было бы не только злом, но и непрактичным, но вполне выполнимым. - person Daniel Fischer; 10.05.2013
comment
@DanielFischer Тогда я буду придерживаться прямого и пустого * приведения. (Если бы void* не работал, запоминание структур, вероятно, было бы обречено!) - person ; 10.05.2013
comment
Я думаю, что подходящее преобразование означает своего рода слепок. Можете ли вы представить какое-либо преобразование, позволяющее преобразовать указатель на произвольную структуру в указатель на ее первый элемент, если указатель ссылается на последний байт объекта? В своем ответе учтите, что C99 явно позволяет последнему члену быть массивом переменной длины. - person JeremyP; 10.05.2013
comment
@JeremyP Не массив переменной длины, а гибкий элемент массива, вы всегда можете предположить, что он имеет размер 0 для вычислений. Подходящее преобразование означает, что преобразования, согласно стандарту, должны работать. Это не так много. Хотя, согласно 6.3.2.3 (7), следует заставить его работать для всех приведений между типами указателей объектов, где требования выравнивания не нарушаются. - person Daniel Fischer; 10.05.2013
comment
@DanielFischer: нет гарантии, что &myB и (int32_t*)&myB имеют одинаковое побитовое представление, но я ожидаю, что если sizeof(myB) равен 12 и каждый указатель содержит числовой адрес последнего байта элемента данных, на который он ссылается, то приведение &myB к int32_t потребовало бы вычитания 8 из хранящегося в нем числового адреса. - person supercat; 10.05.2013
comment
@supercat Да, вот как это работает. В примере вопроса sizeof myB предположительно равно 8 (2 int, один завернутый в struct A), поэтому разница составляет 4. - person Daniel Fischer; 10.05.2013