Вопрос:

Как работает смещение макроса C?

c macros offset

13287 просмотра

4 ответа

48210 Репутация автора

Возможное дублирование:
почему этот код на C работает?
Как вы используете offsetof () в структуре?

Я читал об этом смещении макроса в Интернете, но он не объясняет, для чего он используется.

#define offsetof(a,b) ((int)(&(((a*)(0))->b)))

Что он пытается сделать и в чем его преимущество?

Автор: Samuel Liew Источник Размещён: 26.10.2011 01:47

Ответы (4)


13 плюса

166975 Репутация автора

Решение

Он не имеет преимуществ и не должен использоваться, поскольку он вызывает неопределенное поведение (и использует неправильный тип - intвместо size_t).

Стандарт C определяет offsetofмакрос, в stddef.hкотором действительно работает, для случаев, когда вам необходимо смещение элемента в структуре, например:

#include <stddef.h>

struct foo {
    int a;
    int b;
    char *c;
};

struct struct_desc {
    const char *name;
    int type;
    size_t off;
};

static const struct struct_desc foo_desc[] = {
    { "a", INT, offsetof(struct foo, a) },
    { "b", INT, offsetof(struct foo, b) },
    { "c", CHARPTR, offsetof(struct foo, c) },
};

что позволит вам программно заполнять поля struct fooпо имени, например, при чтении файла JSON.

Автор: R.. Размещён: 26.10.2011 01:55

5 плюса

323719 Репутация автора

Это нахождение байтового смещения конкретного члена struct. Например, если у вас была следующая структура:

struct MyStruct
{
    double d;
    int i;
    void *p;
};

Тогда вы бы offsetOf(MyStruct, d) == 0, offsetOf(MyStruct, i) == 8и offsetOf(MyStruct, p) == 12(то есть член с именем dсоставляет 0 байт от начала структуры и т.д.).

Он работает так, как будто притворяется, что экземпляр вашей структуры существует по адресу 0 ( ((a*)(0))деталь), а затем берет адрес нужного члена структуры и преобразует его в целое число. Хотя разыменование объекта по адресу 0 обычно было бы ошибкой, нормально брать адрес, потому что оператор address-of &и разыменование члена ->взаимно отменяют друг друга.

Обычно он используется для обобщенных сред сериализации. Если у вас есть код для преобразования между какими-либо проводными данными (например, байтами в файле или из сети) и структурами данных в памяти, часто бывает удобно создать отображение имени члена на смещение члена, чтобы вы могли сериализовать или десериализовать значения в общем виде.

Автор: Adam Rosenfield Размещён: 26.10.2011 01:58

32 плюса

2966 Репутация автора

R .. правильно ответил на вторую часть вашего вопроса: этот код не рекомендуется при использовании современного компилятора Си.

Но чтобы ответить на первую часть вашего вопроса, то, что это на самом деле делает:

(
  (int)(         // 4.
    &( (         // 3.
      (a*)(0)    // 1.
     )->b )      // 2.
  )
)

Работая изнутри, это ...

  1. Приведение значения ноль к типу указателя структуры a*
  2. Получение поля структуры b этого (незаконно размещенного) объекта структуры
  3. Получение адреса этого bполя
  4. Приведение адреса к int

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

Конечно, если вы на самом деле разыменуете нулевой указатель, ваша программа будет аварийно завершать работу, но на самом деле все происходит в компиляторе, и ни один фактический нулевой указатель не разыменовывается во время выполнения.

В большинстве оригинальных систем размер C был равен int32 битам и был таким же, как указатель, так что это действительно сработало.

Автор: Eamonn O'Brien-Strain Размещён: 26.10.2011 02:15

-2 плюса

15435 Репутация автора

Реализация смещения макроса действительно не имеет значения.

Фактический стандарт C определяет его как в 7.17.3:

offsetof(type, member-designator)

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

Доверьтесь ответу Адама Розенфилда.

R совершенно не прав, и у него много применений, особенно в том, что касается возможности определить, когда код не переносим между платформами.

(Хорошо, это C ++, но мы используем его в статических утверждениях времени компиляции шаблона, чтобы убедиться, что наши структуры данных не меняют размер между платформами / версиями.)

Автор: Adrian Cornish Размещён: 26.10.2011 02:26
Вопросы из категории :
32x32