#c #offsetof
#c #offsetof
Вопрос:
Я читал, что макрос offsetof обычно реализуется как:
#define offsetof(st, m)
((size_t)amp;(((st *)0)->m))
И, согласно Википедии, ведутся споры о том, является ли это неопределенным поведением, поскольку оно может разыменовывать указатель. У меня есть буфер с информацией, которую мне нужно отправить в разные места, но буфер занимает только часть структуры. Мне было интересно, гарантировано ли следующее, чтобы дать правильный размер моей структуры до конца буфера, последнего элемента.
struct PerObjBuffer
{
mat4 matrix;
mat4 otherMatrix;
vec4 colour;
int sampleType = 0;
size_t getBufferSize() { return offsetof(PerObjBuffer, sampleType) sizeof(sampleType); }
void sendToGPU() { memcpy(destination, this, getBufferSize()); }
String shaderStringCode =
R"(layout(binding = 2, std140) uniform perObjectBuffer
{
mat4 WVPMatrix;
mat4 worldMatrix;
vec4 perObjColour;
int texSampleFormat;
};
)";
}
Если использование offsetof не является хорошей идеей, каков наилучший способ сделать то, что я хочу сделать?
Редактировать: как насчет sizeof(PerObjBuffer) — sizeof(String) ? Должен ли я учитывать проблемы с заполнением?
Комментарии:
1.
offsetof
реализовано стандартной библиотекой -> его использование (само по себе) безопасно.2. Это должно работать с любым допустимым указателем правильного типа. Что плохого в разыменовании действительного указателя?
3. amp;(((st *)0)->m) Я предполагаю, что это та часть, о которой говорила Википедия, о которой были дебаты?
4. @KamilCuk Он используется как макрос? Я не обращаюсь к нему с помощью std:: ?
5.
It's used as a macro?
Да, указано, что это макрос.I don't access it with std:: ?
Нет, вы этого не делаете, это макрос. Вы, кажется, обеспокоены тем, как что-то реализовано — как пользователь, который использует язык, вам все равно. Люди, которые внедряют стандартную библиотеку, должны реализовывать их таким образом, чтобы это работало. Как это реализовано.. это зависит от них.
Ответ №1:
Стандарт определяет поведение вещей, а не то, как они реализованы. Часть реализации остается за разработчиками — людьми, которые пишут компиляторы и библиотеки, — и они должны реализовывать вещи таким образом, чтобы они работали так, как указано в стандарте. Если эта реализация стандартной библиотеки сама по себе имеет неопределенное поведение, это не имеет значения — она должна работать с указанным компилятором, поэтому конкретный компилятор будет интерпретировать ее так, как этого хотят разработчики поведения. Неопределенное поведение означает, что стандарт не определяет никаких требований к поведению кода. В документации вашего компилятора могут быть указаны дополнительные требования, определяющие поведение кода, которое в соответствии со стандартом не определено — таким образом, код может быть неопределенным стандартом и быть совершенно точным и разумным для конкретного компилятора, который требуется / написан для его интерпретации таким образом.
Язык C использует макросы из языка C, когда #include
используется proper . В стандарте C указано C99 7.17p3:
The macros are [...] and
offsetof(type, member-designator)
which expands to an integer constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type). The type and member designator shall be such that given
static type t;
then the expression amp;(t.member-designator) evaluates to an address constant. (If the specified member is a bit-field, the behavior is undefined.)
Как пользователь языка, вам все равно, как он реализован. Если вы используете компилятор, совместимый со стандартом, что бы он ни делал за кулисами, это должно привести к поведению, указанному стандартом. Ваше использование offsetof(PerObjBuffer, sampleType)
допустимо — having static PerObjBuffer t;
затем amp;(t.member-sampleType)
вычисляется для адреса constant — поэтому offsetof(PerObjBuffer, sampleType)
вычисляется до целочисленного постоянного выражения. Вам нравится, что вам все равно, как компилятор получает результат — он может использовать для этого темную магию — какое значение имеет то, что компилятор делает это, а результат представляет смещение в байтах. (Я думаю, что известный пример memcpy
— это невозможно реализовать memcpy
стандартным способом, но функция… существует).
Тем не менее, я боюсь, что другие части вашего кода будут очень недопустимыми. Скорее всего memcpy
, это приведет к неопределенному поведению — вы скопируете отступы между элементами и, похоже, хотите отправить это в какое-то аппаратное местоположение. В любом случае, я советую провести обширные исследования по времени жизни и представлению объектов C , заполнению внутри структур, типам объектов (т. е. POD
/ стандартный макет / тривиальный) и способам работы с ними (т. е. когда можно использовать mem*
функции / размещение new/ std::launder
).