Гарантировано ли правильное использование offsetof ?

#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 ).