Чтобы ответить на вопрос №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 было много таких же проблем, связанных с неопределенным поведением и т. Д., Которые были здесь выражены, и они создали встроенную компилятор как более безопасную альтернативу попыткам сгенерировать эквивалентную операцию в коде C.
Дополнительная информация:
Относительно ваших других вопросов: я думаю, что ответ Р. и его последующие комментарии хорошо описывают соответствующие разделы стандарта в отношении вопроса № 1. Что касается вашего третьего вопроса, я не слышал о современном C компиляторе, который не имеет stddef.h
. Я, конечно, не считаю, что какой-либо компилятор не имеет такого основного стандартного заголовка, как «production». Точно так же, если их реализация 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
вообще не упоминается в оригинальной книге Кернигана и Ричи "Язык программирования Си" (даже самое последнее издание). Первое издание книги было неофициальным стандартом до того, как был стандартизирован язык Си, и я часто слышу, как люди ошибочно называют эту книгу стандартом языка. Его гораздо легче читать, чем официальный стандарт, поэтому я не знаю, виню ли я их за то, что они сделали это своей первой точкой отсчета. Однако независимо от того, во что они верят, стандарт ясно, что offsetof
является частью ANSI C (см. Ответ R для ссылки).
Вот еще один способ взглянуть на вопрос № 1. Стандарт ANSI C дает следующее определение в разделе 4.1.5:
offsetof( type, member-designator)
, который расширяется до целочисленного константного выражения, имеющего тип size_t,
значение которого является смещением в байтах к элементу структуры
(назначается членом-назначенным лицом), с начала его
структура (обозначена по типу).
Использование макроса offsetof
не вызывает неопределенного поведения. Фактически, поведение - это все, что фактически определяет стандарт. Написание компилятором макроса offsetof
зависит от его поведения в соответствии со стандартом. Независимо от того, реализован ли он с помощью макроса, встроенного компилятора или чего-то еще, для обеспечения его правильного поведения требуется, чтобы разработчик глубоко понимал внутреннюю работу компилятора и то, как он будет интерпретировать код. Компилятор может реализовать его с помощью макроса, подобного предоставленной вами идиоматической версии, но только потому, что он знает, как компилятор будет обрабатывать нестандартный код.
С другой стороны, расширение макросов, которое вы предоставили, действительно вызывает неопределенное поведение. Поскольку вы недостаточно знаете о компиляторе, чтобы предсказать, как он будет обрабатывать код, вы не можете гарантировать, что конкретная реализация offsetof
всегда будет работать. Многие люди определяют свою собственную версию таким образом и не сталкиваются с проблемами, но это не означает, что код правильный. Даже если так происходит, когда конкретный компилятор определяет offsetof
, написание этого кода самостоятельно вызывает UB, а использование предоставленного макроса offsetof
- нет.
Прокрутка собственного макроса для offsetof
не может быть выполнена без вызова неопределенного поведения (ANSI C раздел A.6.2 «Неопределенное поведение», 27-й пункт маркировки). Использование stddef.h
версии offsetof
всегда приводит к поведению, определенному в стандарте (при условии, что компилятор соответствует стандартам). Я бы посоветовал не определять пользовательскую версию, поскольку она может вызвать проблемы с переносимостью, но если другие не удастся убедить, приведенный выше фрагмент #ifndef offsetof
может быть приемлемым компромиссом.