Jaki jest wynik NULL + int?

Widziałem następujące makro używane w implementacjach OpenGL VBO:

#define BUFFER_OFFSET(i) ((char *)NULL + (i))
//...
glNormalPointer(GL_FLOAT, 32, BUFFER_OFFSET(x));
Czy mógłbyś podać trochę szczegółów na temat działania tego makra? Czy można go zastąpić funkcją? Dokładniej, jaki jest wynik zwiększania wskaźnika NULL?
Author: Dan Nestor, 2011-11-27

4 answers

[[18]} wróćmy do brudnej historii OpenGL. Dawno, dawno temu, był OpenGL 1.0. Użyłeś glBegin i glEnd do rysowania i to wszystko. Jeśli chcesz szybkiego rysowania, utknąłeś rzeczy na liście wyświetlania.

Wtedy ktoś wpadł na świetny pomysł, aby móc po prostu pobierać tablice obiektów do renderowania. I tak narodził się OpenGL 1.1, który przyniósł nam takie funkcje jak glVertexPointer. Możesz zauważyć, że ta funkcja kończy się słowem "wskaźnik". To dlatego, że potrzeba wskaźniki do rzeczywistej pamięci, która będzie dostępna po wywołaniu jednej z funkcji glDraw*.

Przewiń do przodu jeszcze kilka lat. Teraz ludzie chcą umieścić dane wierzchołków w pamięci GPU, ale nie chcą ufać listom wyświetlania. Są one zbyt ukryte i nie ma sposobu, aby dowiedzieć się, czy uzyskasz z nimi dobre wyniki. Wprowadź obiekty bufora.

Jednak, ponieważ ARB miał absolutną politykę uczynienia wszystkiego tak wstecznie kompatybilnym, jak to możliwe (bez względu na to, jak głupio to sprawiło, że wygląd API), uznali, że najlepszym sposobem na zaimplementowanie tego jest ponowne użycie tych samych funkcji. Tylko teraz istnieje przełącznik globalny, który zmienia zachowanie glVertexPointer z " pobiera wskaźnik "na" pobiera offset bajtów od obiektu bufora."Ten przełącznik oznacza, czy obiekt bufora jest powiązany z GL_ARRAY_BUFFER.

Oczywiście, jeśli chodzi o C/C++, funkcja nadal przyjmuje wskaźnik. A reguły C / C++ nie pozwalają na podanie liczby całkowitej jako wskaźnika. Nie bez gipsu. Dlatego istnieją makra takie jak BUFFER_OBJECT. Jest to jeden ze sposobów konwersji offsetu bajtów całkowitych na wskaźnik.

Część (char *)NULL po prostu pobiera wskaźnik NULL (który zwykle jest void*) i zamienia go w char*. + i robi tylko arytmetykę wskaźnika na char*. Ponieważ NULL jest zwykle wartością zerową, dodanie i do niego zwiększy przesunięcie bajtu o i, generując w ten sposób wskaźnik, którego wartość jest przesunięciem bajtu, które przekazałeś.

Oczywiście, C++ Specyfikacja wyświetla wyniki BUFFER_OBJECT jako undefined behavior. Używając go, naprawdę polegasz na kompilatorze, który zrobi coś rozsądnego. W końcu NULL nie ma być zerowe; Cała Specyfikacja mówi, że jest to zdefiniowana w implementacji stała wskaźnika null. W ogóle nie musi mieć wartości zero. Na większości realnych systemów, będzie. Ale nie ma do.

Dlatego używam gipsu.
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);

To nieokreślone zachowanie tak czy siak. Ale jest również krótszy niż wpisanie "BUFFER_OFFSET". GCC i Visual Studio wydają się być rozsądne. I nie zależy od wartości makra NULL.

Osobiście, gdybym był bardziej pedantyczny w C++, użyłbym reinterpret_cast<void*> na nim. Ale nie jestem.

Czy możesz napisać glVertexAttribBuffer, która przyjmuje przesunięcie zamiast wskaźnika? Oczywiście. Ale to nic wielkiego. Zrób gips.

 30
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-27 05:32:51
#define BUFFER_OFFSET(i) ((char *)NULL + (i))

Technicznie wynik tej operacji jest niezdefiniowany , A makro faktycznie jest błędne. Pozwól mi wyjaśnić:

C definiuje (A C++ podąża za nim), że wskaźniki mogą być rzucane na liczby całkowite, czyli typu uintptr_t, i że jeśli liczba całkowita uzyskana w ten sposób, rzucana z powrotem do oryginalnego typu wskaźnika, z którego pochodzi, da oryginalny wskaźnik.

Wtedy jest arytmetyka wskaźnikowa, co oznacza, że jeśli mam dwa wskaźniki wskazujące ten sam obiekt, mogę wziąć różnica między nimi, wynikająca z liczby całkowitej (typu ptrdiff_t), a liczbą całkowitą dodaną lub odejmowaną do jednego z oryginalnych wskaźników, daje drugą. Definiuje się również, że przez dodanie 1 do wskaźnika, zostanie wyświetlony wskaźnik do następnego elementu indeksowanego obiektu. Również różnica dwóch uintptr_t, podzielonych przez sizeof(type pointed to) wskaźników tego samego obiektu musi być równa odejmowanym wskaźnikom. I wreszcie, wartości {[2] } mogą być dowolne. Mogą to być nieprzezroczyste uchwyty też. Nie muszą być adresami (choć większość implementacji robi to w ten sposób, bo to ma sens).

Teraz możemy spojrzeć na niesławny wskaźnik null. C definiuje wskaźnik, który jest przypisany do for od typu uintptr_u wartość 0 jako nieprawidłowy wskaźnik. zauważ, że w Twoim kodzie źródłowym jest zawsze 0. Po stronie zaplecza, w skompilowanym programie, wartość binarna używana do reprezentacji maszyny może być czymś zupełnie innym! Zazwyczaj jest to nie, ale może być. C++ jest tym samym, ale C++ nie pozwala na tak Ukryte odlewania niż C, więc trzeba rzucić 0 jawnie do void*. Również dlatego, że wskaźnik null nie odnosi się do obiektu i dlatego nie ma dereferenced size wskaźnik arytmetyka jest niezdefiniowana dla wskaźnika null . Wskaźnik null odnoszący się do żadnego obiektu oznacza również, że nie ma definicji sensownego rzucania go do wpisanego wskaźnika.

Więc jeśli to wszystko jest nieokreślone, dlaczego to makro mimo wszystko działa? Ponieważ większość implementacji (czyli kompilatorów) jest wyjątkowo łatwowierna, a programiści leniwi w najwyższym stopniu. Wartość całkowita wskaźnika w większości implementacji jest tylko wartością samego wskaźnika po stronie zaplecza. Więc wskaźnik null jest w rzeczywistości 0. I chociaż arytmetyka wskaźnika na wskaźniku null nie jest sprawdzana, większość kompilatorów po cichu ją zaakceptuje, jeśli wskaźnik ma przypisany typ, nawet jeśli nie ma to sensu. char jest typem "wielkości jednostkowej" C, jeśli chcesz to powiedzieć. Tak więc arytmetyka wskaźnika na cast jest jak artihmetic na adresach po stronie zaplecza.

Mówiąc krótko, po prostu nie ma sensu próbować magii wskaźników z zamierzonym rezultatem, aby być przesunięciem po stronie języka C, to po prostu nie działa w ten sposób.

Cofnijmy się na chwilę i przypomnijmy sobie, co tak naprawdę staramy się zrobić: pierwotny problem polegał na tym, że funkcje gl…Pointer przyjmują wskaźnik jako parametr danych, ale dla wierzchołków Obiekty buforujące chcemy określić offset bajtowy w naszych danych, który jest liczbą. Do kompilatora C funkcja przyjmuje wskaźnik (nieprzezroczystą rzecz, jak się dowiedzieliśmy). Poprawnym rozwiązaniem byłoby wprowadzenie nowych funkcji, szczególnie do użytku z VBOs (powiedzmy gl…Offset – myślę, że zamierzam je wprowadzić). Zamiast tego to, co zostało zdefiniowane przez OpenGL, jest exploitem działania kompilatorów. Wskaźniki i ich ekwiwalent integer są zaimplementowane jako te same wartości binarne reprezentacja przez większość kompilatorów. Musimy więc sprawić, że kompilator wywoła te gl…Pointer funkcje z naszym numerem zamiast wskaźnika.

Więc technicznie jedyną rzeczą, którą musimy zrobić, to powiedzieć kompilatorowi " tak, Wiem, że uważasz, że ta zmienna a jest liczbą całkowitą, i masz rację, a ta funkcja glVertexPointer pobiera tylko void* dla jej parametru danych. Ale zgadnij co: ta liczba całkowita została uzyskana z void*", przez rzucenie jej na (void*), a następnie trzymanie kciuków, że kompilator jest właściwie tak głupio przekazać wartość całkowitą do glVertexPointer.

Więc wszystko sprowadza się do obejścia starej sygnatury funkcji. Rzucanie wskaźnikiem jest metodą IMHO brudną. Zrobiłbym to trochę inaczej: namieszałbym w sygnaturze funkcji:
typedef void (*TFPTR_VertexOffset)(GLint, GLenum, GLsizei, uintptr_t);
TFPTR_VertexOffset myglVertexOffset = (TFPTR_VertexOffset)glVertexPointer;

Teraz możesz używać myglVertexOffset bez robienia głupich rzutów, a parametr offset zostanie przekazany do funkcji, bez niebezpieczeństwa, że kompilator może z nim zadzierać. Jest to również metoda, której używam w moim programy.

 25
Author: datenwolf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 12:28:01

To nie jest "NULL + int", to jest "NULL cast to the type 'pointer to char'", a następnie zwiększa ten wskaźnik o i.

I tak, to może być zastąpione przez funkcję - ale jeśli nie wiesz, co ona robi, to dlaczego cię to obchodzi? Najpierw zrozum, co ona robi, potem zastanów się, czy byłaby lepsza jako funkcja.

 3
Author: Arafangion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-28 06:14:16

Dane atrybutów wierzchołków OpenGL są przypisywane przez tę samą funkcję (glVertexAttribPointer) jako wskaźniki w pamięci lub offsety znajdujące się w buforze wierzchołków w zależności od kontekstu.

Makro BUFFER_OFFSET() wydaje się konwertować offset bajtów całkowitych na wskaźnik, aby umożliwić kompilatorowi bezpieczne przekazanie go jako argument wskaźnika. "(Char*)NULL+i " wyraża tę konwersję przez wskaźnik-arytmetykę; wynik powinien być tym samym wzorem bitowym, zakładając sizeof (char)= = 1, bez którego to makro by się nie powiodło.

Jest to również możliwe dzięki prostemu ponownemu odlewaniu, ale makro może sprawić, że stylistycznie będzie jaśniejsze, co jest przekazywane; byłoby to również wygodne miejsce do pułapkowania przepełnień dla 32 / 64bit safety / futureproofing
struct MyVertex { float pos[3]; u8 color[4]; }
// general purpose Macro to find the byte offset of a structure member as an 'int'

#define OFFSET(TYPE, MEMBER) ( (int)&((TYPE*)0)->MEMBER)

// assuming a VBO holding an array of 'MyVertex', 
// specify that color data is held at an offset 12 bytes from the VBO start, for every vertex.
glVertexAttribPointer(
  colorIndex,4, GL_UNSIGNED_BYTE, GL_TRUE,
  sizeof(MyVertex), 
  (GLvoid*) OFFSET(MyVertex, color) // recast offset as pointer 
);
 2
Author: centaurian_slug,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-27 05:44:50