2011-06-29 11 views
9

Poniższy kod pochodzi z istniejącej aplikacji, która musi zostać skompilowana zarówno w C jak i C++. Jest makro:Proszę wyjaśnić to ostre makro, które wykonuje odlewanie i sprawdzanie typu:

/* 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)))) 

, który jest używany w następujący sposób:

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

tu chodzi o to, że dwa ostatnie parametry CoCreateInstance()IID& i void** i chwyta makro ISomeInterface** i konwertuje go do IID& i void** w tym samym czasie wymuszenie sprawdzania podczas kompilacji, czy adres przekazany w miejsce ISomeInterface** jest rzeczywiście adresem zmiennej wskaźnika ISomeInterface*.

Ok, ale co potrzeba

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

złożonego wyrażenia? Widzę, że sprawdzanie typu jest wymuszane podwyrażeniem (obj)-(type**)(obj). Jaka jest potrzeba dodania, a następnie odjęcia sizeof()? A jaka jest potrzeba rzutowania na void* przed rzutowaniem na void**?

Przypuszczam, że to samo można zrobić w następujący sposób:

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

tutaj pierwsza część operatora przecinkami zawierałby sizeof() które egzekwowania typecheck i oceny do stałej, druga część będzie tylko wytworzeniem ten sam wskaźnik i wskaźnik będą rzutować na void**.

Co oryginalne makro może zrobić zgodnie z sugestią tego, co sugeruję? Jaka jest potrzeba tych komplikacji?

+0

I dlatego jest to zły kod. Niech zgadnę, zmagałeś się z tym przez cały dzień? – orlp

+0

@noccracker: Nie, nie utrzymuję tego kodu, właśnie go znalazłem i byłem ciekawy jak to działa. – sharptooth

+2

+1 dla hardcore w tytule, powinien być tagiem! –

Odpowiedz

5

Może oryginalny autor nie był świadomy operatora przecinka? Nie jest to dokładnie niesłychane wśród programistów C/C++.

+0

Zaskakująco niewielu programistów C, z którymi pracowałem, zna operatora przecinka. – Mike

+0

Niektóre projekty mają standardy programowania, które zakazują korzystania z operatora przecinków. Makro, które używa zabronionej składni! Broń Boże! O wiele lepiej jest używać niejasnej składni i pisać addytywnie zero (lub multiplikatywny) w sposób raczej kreatywny niż przy użyciu operatora przecinka lub operatora tercji.

4

Może oryginalny autor nie znał szablonów funkcji. To makro prosi się o zastąpienie przez szablon funkcji.

Najwyraźniej czwarty argument do CoCreateInstance jest wskaźnikiem do jakiegoś globalnego obiektu typu IID, który odnosi się do type (argument typu do COMPTR) pod ręką. Piąty i ostatni argument do CoCreateInstance ma być wskaźnikiem type**.

Zamiast tego funkcja CoCreateInstance przyjmuje wskaźnik void** (yech!) Jako ostatni argument, uzyskany przez rzutowanie rzekomego wskaźnika type**. Obsada przechodzi jako void* jako pośrednik, ponieważ dowolny wskaźnik może być rzutowany na/z pustego * wskaźnika.

Sans ochrony w tym makro COMPTR, można przekazać wskaźnik double*, a nawet long long (nie wskaźnik!) Jako piąty argument do CoCreateInstance. Oczywiście, cały ten bałagan byłby unikany, gdyby oryginalny autor użył C++, co jest bardzo dobre, bezpieczeństwa typu. Zamiast tego zdecydował się przejść do pustej * trasy wskaźnika i umieścić ochronę w makrze.

Co robi głupota: Argumentem dla sizeof jest różnica wskaźnika wyrażenia (obj)-(type**)(obj).Jeśli obj jest wskaźnikiem type**, jest to 0 (jako typ ptrdiff_t). Jeśli obj jest czymś innym, ta różnica w wyrażeniu wskaźnika jest źle sformułowana. Tak więc dwa przypadki: obj to wskaźnik type** lub nie jest.

Case 1, obj jest type** wskazówka: Wyrażenie różnica wskaźnik jest ważny, więc ostatni argument do CoCreateInstance rozszerza się (void**)(void*)(obj+8-8), zakładając, że maszyna 64 bitów. (+ 8-8 staje się + 4-4 na maszynie 32-bitowej). Niezależnie od wielkości maszyny, offset jest dodawany i odejmowany, pozostawiając oryginalny wskaźnik.

Przypadek 2, obj nie jest wskaźnikiem type**: wyrażenie różnicy wskaźnika jest źle sformułowane, więc kod nie jest kompilowany.

+3

Uwaga: w C nie ma szablonów, a kod jest kompilowany zarówno w C jak i C++, a także 'CoCreateInstance()' jest funkcją Win32, która służy jako globalna fabryka klasy, która nie może być specyficzna dla żadnego interfejsu i właśnie dlatego akceptuje 'void **'. – sharptooth

+0

@sharptooth: Zgłosiłeś? Zauważ, że wyjaśniłem, jak dokładnie działa to makro. –

+0

Nie, nie pochwaliłem, właśnie wyjaśniłem te dwa dość ważne punkty - że szablony nie mogą być tutaj użyte i że "void **" jest tam z jakiegoś powodu. – sharptooth

Powiązane problemy