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

Переносимость использования stddef.h offsetof вместо того, чтобы

Это вопрос с детальными деталями с тремя частями. Контекст заключается в том, что я хочу убедить некоторых людей в том, что безопасно использовать <stddef.h> определение offsetof безоговорочно, а не (при некоторых обстоятельствах) сворачивать свои собственные. Программа, о которой идет речь, написана целиком на простом старом C, поэтому игнорировать С++ полностью при ответе.

Часть 1: При использовании в том же порядке, что и стандартный offsetof, расширяет ли этот макрос провоцирует поведение undefined на C89, почему или почему нет, и отличается ли он на C99?

#define offset_of(tp, member) (((char*) &((tp*)0)->member) - (char*)0)

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

Часть 2: Насколько вам известно, когда-либо была выпущена реализация C производства, которая при подаче расширения вышеупомянутого макроса будет (при некоторых обстоятельствах) вести себя по-другому, чем если бы ее offsetof вместо этого использовался макрос?

Часть 3: Насколько вам известно, какова последняя выпущенная реализация C, которая либо не предоставляла stddef.h, либо не предоставляла рабочее определение offsetof в этом заголовке? Соответствует ли эта реализация требованиям любой версии стандарта C?

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

4b9b3361

Ответ 1

Ответ # 2: да, gcc-4 * (я сейчас смотрю v4.3.4, выпущен 4 августа 2009 года, но это должно быть справедливо для всех выпусков gcc-4 на сегодняшний день). Следующее определение используется в их stddef.h:

#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)

где __builtin_offsetof - это компилятор, встроенный как sizeof (то есть он не реализован как макрос или функция времени исполнения). Компиляция кода:

#include <stddef.h>

struct testcase {
    char array[256];
};

int main (void) {
    char buffer[offsetof(struct testcase, array[0])];
    return 0;
}

приведет к ошибке с использованием расширения макроса, который вы предоставили ( "размер массива" не является интегральным константным выражением), но будет работать при использовании макроса, предоставленного в stddef.h. В сборках с использованием gcc-3 используется макрос, похожий на ваш. Я полагаю, что разработчики gcc были в значительной степени обеспокоены поведением undefined и т.д., Которые были выражены здесь, и создали компилятор в качестве более безопасной альтернативы попытке генерации эквивалентной операции в коде C.

Дополнительная информация:

Относительно ваших других вопросов: Я думаю, что ответ R и его последующие комментарии хорошо отражают соответствующие разделы стандарта в вопросе № 1. Что касается вашего третьего вопроса, я не слышал о современном компиляторе C, который не имеет stddef.h. Я, конечно же, не считаю, что какой-либо компилятор не имеет такого базового стандартного заголовка, как "производство". Аналогично, если их реализация offsetof не работала, то компилятор все еще должен работать, прежде чем его можно считать "производством", как если бы другие вещи в stddef.h(например, NULL) не работали. Компилятор C, выпущенный до стандартизации C, может не иметь таких вещей, но стандарту ANSI C более 20 лет, поэтому крайне маловероятно, что вы столкнетесь с одним из них.

Вся предпосылка на эти проблемы задает вопрос: если эти люди убеждены, что они не могут доверять версии offsetof, которую предоставляет компилятор, то чем они могут доверять? Доверяют ли они, что NULL определено правильно? Доверяют ли они, что long int не меньше обычного int? Доверяют ли они, что memcpy работает так, как предполагалось? Свертывают ли они собственные версии остальных функциональных возможностей библиотеки C? Одной из главных причин наличия языковых стандартов является то, что вы можете доверять компилятору, чтобы делать это правильно. Кажется глупым доверять компилятору для всего остального, кроме offsetof.

Обновление: (в ответ на ваши комментарии)

Я думаю, что мои коллеги ведут себя так же, как и ваши:-) Некоторые из наших старых кода по-прежнему имеют настраиваемые макросы, определяющие NULL, VOID и другие подобные вещи, поскольку "разные компиляторы могут реализовать их по-разному" (вздох). Часть этого кода была написана до того, как C был стандартизирован, и многие старые разработчики все еще находятся в этом мышлении, хотя стандарт C явно говорит об обратном.

Здесь одна вещь, которую вы можете сделать, и доказать, что они ошибаются, и сделать всех счастливыми одновременно:

#include <stddef.h>

#ifndef offsetof
  #define offsetof(tp, member) (((char*) &((tp*)0)->member) - (char*)0)
#endif

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

Основываясь на подобных разговорах, которые у меня были на протяжении многих лет, я думаю, что убеждение, что offsetof не является частью стандарта C, происходит из двух мест. Во-первых, это редко используется. Разработчики не видят этого очень часто, поэтому они забывают, что он существует. Во-вторых, offsetof вообще не упоминается в книгах Kernighan и Ritchie "Язык программирования C" (даже самое последнее издание). Первое издание книги было неофициальным стандартом до стандартизации C, и я часто слышу, как люди ошибочно ссылаются на эту книгу как стандарт для языка. Это гораздо легче читать, чем официальный стандарт, поэтому я не знаю, обвиняю ли я их в том, что они сделали его первой точкой отсчета. Независимо от того, что они считают, однако, стандарт ясен, что offsetof является частью ANSI C (см. Ответ R для ссылки).


Вот еще один способ взглянуть на вопрос №1. стандарт ANSI C дает следующее определение в разделе 4.1.5:

     offsetof( type,  member-designator)

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

Использование макроса offsetof не вызывает поведение undefined. Фактически, поведение - это все, что определяет стандарт. Это для писателя компилятора, чтобы определить макрос offsetof, чтобы его поведение соответствовало стандарту. Вне зависимости от того, реализован ли он с использованием макроса, встроенного компилятора или чего-то другого, гарантируя, что он ведет себя так, как ожидалось, разработчик должен глубоко понимать внутреннюю работу компилятора и как он будет интерпретировать код. Компилятор может реализовать его с помощью макроса, как и идиоматическая версия, которую вы предоставили, но только потому, что они знают, как компилятор будет обрабатывать нестандартный код.

С другой стороны, предоставленное вами макроопределение действительно вызывает поведение undefined. Поскольку вы недостаточно знаете о компиляторе, чтобы предсказать, как он будет обрабатывать код, вы не можете гарантировать, что конкретная реализация offsetof всегда будет работать. Многие люди определяют свою собственную версию и не сталкиваются с проблемами, но это не значит, что код верен. Даже если так, как определенный компилятор определяет offsetof, запись этого кода сама вызывает UB при использовании предоставленного макроса offsetof.

Перемещение собственного макроса для offsetof невозможно без вызова поведения undefined (раздел ANSI C A.6.2 "Undefined поведение", 27-я точка маркера). Использование stddef.h версии offsetof всегда приведет к поведению, определенному в стандарте (при условии, что это соответствует стандартизованному компилятору). Я бы посоветовал не создавать пользовательскую версию, так как это может вызвать проблемы с переносимостью, но если другие не могут быть убеждены, то приведенный выше фрагмент #ifndef offsetof может быть приемлемым компромиссом.

Ответ 2

Невозможно написать переносимый макрос offsetof. Вы должны использовать тот, который предоставляется stddef.h.

Относительно ваших конкретных вопросов:

  • Макрос вызывает поведение undefined. Вы не можете вычитать указатели, кроме тех случаев, когда они указывают на один и тот же массив.
  • Большая разница в практическом поведении заключается в том, что макрос не является целочисленным постоянным выражением, поэтому его нельзя безопасно использовать для статических инициализаторов, ширины битовых полей и т.д. Также строгие ограничения типа C-реализации могут полностью прервать его.
  • Никогда не было ни одного стандарта C, которому не хватало stddef.h и offsetof. У компиляторов предшествующих ANSI это может не хватать, но у них есть гораздо более фундаментальные проблемы, которые делают их непригодными для использования в современном коде (например, отсутствие void * и const).

Более того, даже если у какого-то теоретического компилятора не было stddef.h, вы могли бы просто предоставить замену для замены, точно так же, как люди заходят в stdint.h для использования с MSVC...

Ответ 3

(1) Поведение undefined уже существует до того, как вы сделаете это.

  • Прежде всего, (tp*)0 - это не то, что вы думаете. Это нулевое значение указатель, такой зверь не обязательно представляется с полным нолем бит.
  • Тогда оператор-член -> является не просто добавлением смещения. На процессоре с сегментированной памятью это может быть более сложная операция.
  • Взятие адреса с помощью операции & является UB, если выражение не действительный объект.

(2) Для точки 2. в дикой природе (вложенной материал), конечно же, имеются архивы, которые используют сегментированную память. Для 3. точка, которая имеет отношение R к целым константным выражениям, имеет еще один недостаток: если код плохо оптимизирован, операция & может выполняться во время выполнения и сигнализировать об ошибке.

(3) Никогда не слышал об этом, но этого, вероятно, недостаточно, чтобы убедить своих коллег.

Ответ 4

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

Что происходит, так это то, что какой-то младший инженер-компилятор решает, что, поскольку нулевая страница никогда не отображается на их платформе выбора, каждый раз, когда кто-либо делает что-либо с указателем на эту страницу, поведение undefined, и они могут безопасно оптимизировать целое выражение. В этот момент все домашние макросы макросов ломаются, пока люди не закричат ​​об этом, и те из нас, кто был достаточно умен, чтобы не сворачивать наши собственные, с радостью относятся к нашему бизнесу.

Я не знаю какого-либо компилятора, где это поведение в текущей выпущенной версии, но я думаю, что я видел, как это произошло в какой-то момент с каждым компилятором, с которым я когда-либо работал.