Пожалуйста, объясните этот хардкорный макрос, который выполняет приведение типов и проверку типов. - PullRequest
9 голосов
/ 29 июня 2011

Следующий код взят из существующего приложения, которое должно быть скомпилировано в C и C ++. Есть макрос:

/* Type-checking macro to provide arguments for CoCreateInstance() etc.
 * The pointer arithmetic is a compile-time pointer type check that 'obj'
 * really is a 'type **', but is intended to have no effect at runtime. */
#define COMPTR(type, obj) &IID_##type, \
(void **)(void *)((obj) + (sizeof((obj)-(type **)(obj))) \
                - (sizeof((obj)-(type **)(obj))))

, который используется следующим образом:

ISomeInterface *object;
CoCreateInstance(&CLSID_SomeInterfaceImpl, NULL,
     CLSCTX_INPROC_SERVER, COMPTR(ISomeInterface, &object))));

здесь идея состоит в том, что последние два параметра CoCreateInstance() равны IID& и void**, и этот макрос захватывает ISomeInterface** и преобразует его в IID& и void** одновременно, обеспечивая принудительную компиляцию проверка времени, что адрес, переданный вместо ISomeInterface**, действительно является адресом ISomeInterface* переменной указателя.

Хорошо, но зачем нужен

((obj) + (sizeof((obj)-(type **)(obj))) \
     - (sizeof((obj)-(type **)(obj)))

сложное выражение? Я вижу, что проверка типов осуществляется с помощью подвыражения (obj)-(type**)(obj). Зачем нужно добавлять, а затем вычитать sizeof()? И что нужно для приведения к void* перед void**?

Полагаю, то же самое можно сделать следующим образом:

#define COMPTR(type, obj) &IID_##type, \
(void **)(sizeof((obj)-(type**)(obj)), obj)

здесь первая часть оператора запятой будет содержать sizeof(), который будет обеспечивать проверку типов и вычислять константу, вторая часть просто выдаст тот же указатель, и указатель будет приведен к void**.

Что может сделать оригинальный макрос, чего не может предложить тот, который я предлагаю? Зачем нужны эти осложнения?

Ответы [ 2 ]

5 голосов
/ 29 июня 2011

Может быть, оригинальный автор не знал об операторе запятой?Это не совсем неслыханно среди программистов на C / C ++.

4 голосов
/ 29 июня 2011

Возможно, оригинальный автор не знал о шаблонах функций.Этот макрос требует замены на шаблон функции.

Очевидно, четвертый аргумент CoCreateInstance - это указатель на некоторый глобальный объект типа IID, который относится к type (аргумент типа для COMPTR) врука.Пятый и последний аргумент CoCreateInstance должен быть указателем type**.

Вместо этого функция CoCreateInstance принимает указатель void** (yech!) В качестве последнего аргумента, полученного путем приведения.предполагаемый type** указатель.Приведение проходит через void* в качестве посредника, потому что любой указатель может быть приведен к / от указателя void *.

Без защиты в этом макросе COMPTR можно передать указатель double* или дажеlong long (не указатель!) В качестве пятого аргумента CoCreateInstance.Конечно, всего этого беспорядка можно было бы избежать, если бы оригинальный автор использовал C ++, что очень хорошо, безопасность типов.Вместо этого он / она решил пойти по пути указателя void * и поместить защиту в макрос.

Что делает глупость: аргумент sizeof - это выражение разности указателей (obj)-(type**)(obj).Если obj является указателем type**, это 0 (как тип ptrdiff_t).Если obj является чем-то другим, это разностное выражение указателя неверно сформировано.Итак, в двух случаях obj - это указатель type**, или это не так.

Case 1, obj - это указатель type**: Разностное выражение указателя является действительным, поэтому последнееаргумент CoCreateInstance расширяется до (void**)(void*)(obj+8-8), предполагая, что машина 64-битная.(+ 8-8 становится + 4-4 на 32-разрядной машине.) Независимо от размера машины смещение добавляется и вычитается, оставляя исходный указатель.

Случай 2, obj не являетсяtype** указатель: Разностное выражение указателя неверно сформировано, поэтому код не компилируется.

...